@hienlh/ppm 0.9.3 → 0.9.5

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.
Files changed (30) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/dist/web/assets/{browser-tab-LFNnCzgB.js → browser-tab-D2Z_380K.js} +1 -1
  3. package/dist/web/assets/{chat-tab-rYBo5Mff.js → chat-tab-DyCHqHNy.js} +1 -1
  4. package/dist/web/assets/{code-editor-BdM11-0K.js → code-editor-CLBY3U7l.js} +1 -1
  5. package/dist/web/assets/{database-viewer-CINo6teP.js → database-viewer-BWGt4Ufn.js} +1 -1
  6. package/dist/web/assets/{diff-viewer-_MPL-DRu.js → diff-viewer-CsPH6s_E.js} +1 -1
  7. package/dist/web/assets/{extension-webview-BU1T2a8n.js → extension-webview-DYuYgKhD.js} +1 -1
  8. package/dist/web/assets/{git-graph-Dde-j8cK.js → git-graph-XDyQYPZk.js} +1 -1
  9. package/dist/web/assets/{index-CyXEMb4g.js → index-BffsIdqe.js} +4 -4
  10. package/dist/web/assets/index-Dx21KBME.css +2 -0
  11. package/dist/web/assets/keybindings-store-B7gFbUFf.js +1 -0
  12. package/dist/web/assets/{markdown-renderer-Djgmbi23.js → markdown-renderer-C1hJEMtm.js} +1 -1
  13. package/dist/web/assets/{postgres-viewer-FCpA6nh4.js → postgres-viewer-BXJLyZp4.js} +1 -1
  14. package/dist/web/assets/{settings-tab-Y37tD1kM.js → settings-tab-C_jaua57.js} +1 -1
  15. package/dist/web/assets/{sqlite-viewer-Cjl4uXyo.js → sqlite-viewer-BFdZBZgC.js} +1 -1
  16. package/dist/web/assets/{terminal-tab-CnYqGghP.js → terminal-tab-COW3438-.js} +1 -1
  17. package/dist/web/assets/{use-monaco-theme-DGjkK3eO.js → use-monaco-theme-DqA6WXo2.js} +1 -1
  18. package/dist/web/index.html +2 -2
  19. package/dist/web/sw.js +1 -1
  20. package/package.json +1 -1
  21. package/packages/ext-database/package.json +9 -2
  22. package/packages/ext-database/src/connection-tree.ts +64 -5
  23. package/packages/ext-database/src/extension.ts +234 -2
  24. package/packages/ext-database/src/query-panel.ts +29 -16
  25. package/packages/ext-database/src/table-viewer-panel.ts +16 -6
  26. package/src/server/ws/chat.ts +10 -13
  27. package/src/web/components/layout/mobile-nav.tsx +5 -5
  28. package/src/web/lib/ws-client.ts +15 -0
  29. package/dist/web/assets/index-BkidPsSC.css +0 -2
  30. package/dist/web/assets/keybindings-store-6_p_JT0B.js +0 -1
package/dist/web/sw.js CHANGED
@@ -1 +1 @@
1
- try{self[`workbox:core:7.3.0`]&&_()}catch{}var e=(e,...t)=>{let n=e;return t.length>0&&(n+=` :: ${JSON.stringify(t)}`),n},t=class extends Error{constructor(t,n){let r=e(t,n);super(r),this.name=t,this.details=n}},n={googleAnalytics:`googleAnalytics`,precache:`precache-v2`,prefix:`workbox`,runtime:`runtime`,suffix:typeof registration<`u`?registration.scope:``},r=e=>[n.prefix,e,n.suffix].filter(e=>e&&e.length>0).join(`-`),i=e=>{for(let t of Object.keys(n))e(t)},a={updateDetails:e=>{i(t=>{typeof e[t]==`string`&&(n[t]=e[t])})},getGoogleAnalyticsName:e=>e||r(n.googleAnalytics),getPrecacheName:e=>e||r(n.precache),getPrefix:()=>n.prefix,getRuntimeName:e=>e||r(n.runtime),getSuffix:()=>n.suffix};function o(e,t){let n=t();return e.waitUntil(n),n}try{self[`workbox:precaching:7.3.0`]&&_()}catch{}var s=`__WB_REVISION__`;function c(e){if(!e)throw new t(`add-to-cache-list-unexpected-type`,{entry:e});if(typeof e==`string`){let t=new URL(e,location.href);return{cacheKey:t.href,url:t.href}}let{revision:n,url:r}=e;if(!r)throw new t(`add-to-cache-list-unexpected-type`,{entry:e});if(!n){let e=new URL(r,location.href);return{cacheKey:e.href,url:e.href}}let i=new URL(r,location.href),a=new URL(r,location.href);return i.searchParams.set(s,n),{cacheKey:i.href,url:a.href}}var l=class{constructor(){this.updatedURLs=[],this.notUpdatedURLs=[],this.handlerWillStart=async({request:e,state:t})=>{t&&(t.originalRequest=e)},this.cachedResponseWillBeUsed=async({event:e,state:t,cachedResponse:n})=>{if(e.type===`install`&&t&&t.originalRequest&&t.originalRequest instanceof Request){let e=t.originalRequest.url;n?this.notUpdatedURLs.push(e):this.updatedURLs.push(e)}return n}}},u=class{constructor({precacheController:e}){this.cacheKeyWillBeUsed=async({request:e,params:t})=>{let n=t?.cacheKey||this._precacheController.getCacheKeyForURL(e.url);return n?new Request(n,{headers:e.headers}):e},this._precacheController=e}},d;function f(){if(d===void 0){let e=new Response(``);if(`body`in e)try{new Response(e.body),d=!0}catch{d=!1}d=!1}return d}async function p(e,n){let r=null;if(e.url&&(r=new URL(e.url).origin),r!==self.location.origin)throw new t(`cross-origin-copy-response`,{origin:r});let i=e.clone(),a={headers:new Headers(i.headers),status:i.status,statusText:i.statusText},o=n?n(a):a,s=f()?i.body:await i.blob();return new Response(s,o)}var m=e=>new URL(String(e),location.href).href.replace(RegExp(`^${location.origin}`),``);function h(e,t){let n=new URL(e);for(let e of t)n.searchParams.delete(e);return n.href}async function g(e,t,n,r){let i=h(t.url,n);if(t.url===i)return e.match(t,r);let a=Object.assign(Object.assign({},r),{ignoreSearch:!0}),o=await e.keys(t,a);for(let t of o)if(i===h(t.url,n))return e.match(t,r)}var v=class{constructor(){this.promise=new Promise((e,t)=>{this.resolve=e,this.reject=t})}},y=new Set;async function b(){for(let e of y)await e()}function x(e){return new Promise(t=>setTimeout(t,e))}try{self[`workbox:strategies:7.3.0`]&&_()}catch{}function S(e){return typeof e==`string`?new Request(e):e}var C=class{constructor(e,t){this._cacheKeys={},Object.assign(this,t),this.event=t.event,this._strategy=e,this._handlerDeferred=new v,this._extendLifetimePromises=[],this._plugins=[...e.plugins],this._pluginStateMap=new Map;for(let e of this._plugins)this._pluginStateMap.set(e,{});this.event.waitUntil(this._handlerDeferred.promise)}async fetch(e){let{event:n}=this,r=S(e);if(r.mode===`navigate`&&n instanceof FetchEvent&&n.preloadResponse){let e=await n.preloadResponse;if(e)return e}let i=this.hasCallback(`fetchDidFail`)?r.clone():null;try{for(let e of this.iterateCallbacks(`requestWillFetch`))r=await e({request:r.clone(),event:n})}catch(e){if(e instanceof Error)throw new t(`plugin-error-request-will-fetch`,{thrownErrorMessage:e.message})}let a=r.clone();try{let e;e=await fetch(r,r.mode===`navigate`?void 0:this._strategy.fetchOptions);for(let t of this.iterateCallbacks(`fetchDidSucceed`))e=await t({event:n,request:a,response:e});return e}catch(e){throw i&&await this.runCallbacks(`fetchDidFail`,{error:e,event:n,originalRequest:i.clone(),request:a.clone()}),e}}async fetchAndCachePut(e){let t=await this.fetch(e),n=t.clone();return this.waitUntil(this.cachePut(e,n)),t}async cacheMatch(e){let t=S(e),n,{cacheName:r,matchOptions:i}=this._strategy,a=await this.getCacheKey(t,`read`),o=Object.assign(Object.assign({},i),{cacheName:r});n=await caches.match(a,o);for(let e of this.iterateCallbacks(`cachedResponseWillBeUsed`))n=await e({cacheName:r,matchOptions:i,cachedResponse:n,request:a,event:this.event})||void 0;return n}async cachePut(e,n){let r=S(e);await x(0);let i=await this.getCacheKey(r,`write`);if(!n)throw new t(`cache-put-with-no-response`,{url:m(i.url)});let a=await this._ensureResponseSafeToCache(n);if(!a)return!1;let{cacheName:o,matchOptions:s}=this._strategy,c=await self.caches.open(o),l=this.hasCallback(`cacheDidUpdate`),u=l?await g(c,i.clone(),[`__WB_REVISION__`],s):null;try{await c.put(i,l?a.clone():a)}catch(e){if(e instanceof Error)throw e.name===`QuotaExceededError`&&await b(),e}for(let e of this.iterateCallbacks(`cacheDidUpdate`))await e({cacheName:o,oldResponse:u,newResponse:a.clone(),request:i,event:this.event});return!0}async getCacheKey(e,t){let n=`${e.url} | ${t}`;if(!this._cacheKeys[n]){let r=e;for(let e of this.iterateCallbacks(`cacheKeyWillBeUsed`))r=S(await e({mode:t,request:r,event:this.event,params:this.params}));this._cacheKeys[n]=r}return this._cacheKeys[n]}hasCallback(e){for(let t of this._strategy.plugins)if(e in t)return!0;return!1}async runCallbacks(e,t){for(let n of this.iterateCallbacks(e))await n(t)}*iterateCallbacks(e){for(let t of this._strategy.plugins)if(typeof t[e]==`function`){let n=this._pluginStateMap.get(t);yield r=>{let i=Object.assign(Object.assign({},r),{state:n});return t[e](i)}}}waitUntil(e){return this._extendLifetimePromises.push(e),e}async doneWaiting(){for(;this._extendLifetimePromises.length;){let e=this._extendLifetimePromises.splice(0),t=(await Promise.allSettled(e)).find(e=>e.status===`rejected`);if(t)throw t.reason}}destroy(){this._handlerDeferred.resolve(null)}async _ensureResponseSafeToCache(e){let t=e,n=!1;for(let e of this.iterateCallbacks(`cacheWillUpdate`))if(t=await e({request:this.request,response:t,event:this.event})||void 0,n=!0,!t)break;return n||t&&t.status!==200&&(t=void 0),t}},w=class{constructor(e={}){this.cacheName=a.getRuntimeName(e.cacheName),this.plugins=e.plugins||[],this.fetchOptions=e.fetchOptions,this.matchOptions=e.matchOptions}handle(e){let[t]=this.handleAll(e);return t}handleAll(e){e instanceof FetchEvent&&(e={event:e,request:e.request});let t=e.event,n=typeof e.request==`string`?new Request(e.request):e.request,r=`params`in e?e.params:void 0,i=new C(this,{event:t,request:n,params:r}),a=this._getResponse(i,n,t);return[a,this._awaitComplete(a,i,n,t)]}async _getResponse(e,n,r){await e.runCallbacks(`handlerWillStart`,{event:r,request:n});let i;try{if(i=await this._handle(n,e),!i||i.type===`error`)throw new t(`no-response`,{url:n.url})}catch(t){if(t instanceof Error){for(let a of e.iterateCallbacks(`handlerDidError`))if(i=await a({error:t,event:r,request:n}),i)break}if(!i)throw t}for(let t of e.iterateCallbacks(`handlerWillRespond`))i=await t({event:r,request:n,response:i});return i}async _awaitComplete(e,t,n,r){let i,a;try{i=await e}catch{}try{await t.runCallbacks(`handlerDidRespond`,{event:r,request:n,response:i}),await t.doneWaiting()}catch(e){e instanceof Error&&(a=e)}if(await t.runCallbacks(`handlerDidComplete`,{event:r,request:n,response:i,error:a}),t.destroy(),a)throw a}},T=class e extends w{constructor(t={}){t.cacheName=a.getPrecacheName(t.cacheName),super(t),this._fallbackToNetwork=t.fallbackToNetwork!==!1,this.plugins.push(e.copyRedirectedCacheableResponsesPlugin)}async _handle(e,t){return await t.cacheMatch(e)||(t.event&&t.event.type===`install`?await this._handleInstall(e,t):await this._handleFetch(e,t))}async _handleFetch(e,n){let r,i=n.params||{};if(this._fallbackToNetwork){let t=i.integrity,a=e.integrity,o=!a||a===t;r=await n.fetch(new Request(e,{integrity:e.mode===`no-cors`?void 0:a||t})),t&&o&&e.mode!==`no-cors`&&(this._useDefaultCacheabilityPluginIfNeeded(),await n.cachePut(e,r.clone()))}else throw new t(`missing-precache-entry`,{cacheName:this.cacheName,url:e.url});return r}async _handleInstall(e,n){this._useDefaultCacheabilityPluginIfNeeded();let r=await n.fetch(e);if(!await n.cachePut(e,r.clone()))throw new t(`bad-precaching-response`,{url:e.url,status:r.status});return r}_useDefaultCacheabilityPluginIfNeeded(){let t=null,n=0;for(let[r,i]of this.plugins.entries())i!==e.copyRedirectedCacheableResponsesPlugin&&(i===e.defaultPrecacheCacheabilityPlugin&&(t=r),i.cacheWillUpdate&&n++);n===0?this.plugins.push(e.defaultPrecacheCacheabilityPlugin):n>1&&t!==null&&this.plugins.splice(t,1)}};T.defaultPrecacheCacheabilityPlugin={async cacheWillUpdate({response:e}){return!e||e.status>=400?null:e}},T.copyRedirectedCacheableResponsesPlugin={async cacheWillUpdate({response:e}){return e.redirected?await p(e):e}};var E=class{constructor({cacheName:e,plugins:t=[],fallbackToNetwork:n=!0}={}){this._urlsToCacheKeys=new Map,this._urlsToCacheModes=new Map,this._cacheKeysToIntegrities=new Map,this._strategy=new T({cacheName:a.getPrecacheName(e),plugins:[...t,new u({precacheController:this})],fallbackToNetwork:n}),this.install=this.install.bind(this),this.activate=this.activate.bind(this)}get strategy(){return this._strategy}precache(e){this.addToCacheList(e),this._installAndActiveListenersAdded||=(self.addEventListener(`install`,this.install),self.addEventListener(`activate`,this.activate),!0)}addToCacheList(e){let n=[];for(let r of e){typeof r==`string`?n.push(r):r&&r.revision===void 0&&n.push(r.url);let{cacheKey:e,url:i}=c(r),a=typeof r!=`string`&&r.revision?`reload`:`default`;if(this._urlsToCacheKeys.has(i)&&this._urlsToCacheKeys.get(i)!==e)throw new t(`add-to-cache-list-conflicting-entries`,{firstEntry:this._urlsToCacheKeys.get(i),secondEntry:e});if(typeof r!=`string`&&r.integrity){if(this._cacheKeysToIntegrities.has(e)&&this._cacheKeysToIntegrities.get(e)!==r.integrity)throw new t(`add-to-cache-list-conflicting-integrities`,{url:i});this._cacheKeysToIntegrities.set(e,r.integrity)}if(this._urlsToCacheKeys.set(i,e),this._urlsToCacheModes.set(i,a),n.length>0){let e=`Workbox is precaching URLs without revision info: ${n.join(`, `)}\nThis is generally NOT safe. Learn more at https://bit.ly/wb-precache`;console.warn(e)}}}install(e){return o(e,async()=>{let t=new l;this.strategy.plugins.push(t);for(let[t,n]of this._urlsToCacheKeys){let r=this._cacheKeysToIntegrities.get(n),i=this._urlsToCacheModes.get(t),a=new Request(t,{integrity:r,cache:i,credentials:`same-origin`});await Promise.all(this.strategy.handleAll({params:{cacheKey:n},request:a,event:e}))}let{updatedURLs:n,notUpdatedURLs:r}=t;return{updatedURLs:n,notUpdatedURLs:r}})}activate(e){return o(e,async()=>{let e=await self.caches.open(this.strategy.cacheName),t=await e.keys(),n=new Set(this._urlsToCacheKeys.values()),r=[];for(let i of t)n.has(i.url)||(await e.delete(i),r.push(i.url));return{deletedURLs:r}})}getURLsToCacheKeys(){return this._urlsToCacheKeys}getCachedURLs(){return[...this._urlsToCacheKeys.keys()]}getCacheKeyForURL(e){let t=new URL(e,location.href);return this._urlsToCacheKeys.get(t.href)}getIntegrityForCacheKey(e){return this._cacheKeysToIntegrities.get(e)}async matchPrecache(e){let t=e instanceof Request?e.url:e,n=this.getCacheKeyForURL(t);if(n)return(await self.caches.open(this.strategy.cacheName)).match(n)}createHandlerBoundToURL(e){let n=this.getCacheKeyForURL(e);if(!n)throw new t(`non-precached-url`,{url:e});return t=>(t.request=new Request(e),t.params=Object.assign({cacheKey:n},t.params),this.strategy.handle(t))}},D,O=()=>(D||=new E,D);try{self[`workbox:routing:7.3.0`]&&_()}catch{}var k=e=>e&&typeof e==`object`?e:{handle:e},A=class{constructor(e,t,n=`GET`){this.handler=k(t),this.match=e,this.method=n}setCatchHandler(e){this.catchHandler=k(e)}},j=class extends A{constructor(e,t,n){super(({url:t})=>{let n=e.exec(t.href);if(n&&!(t.origin!==location.origin&&n.index!==0))return n.slice(1)},t,n)}},M=class{constructor(){this._routes=new Map,this._defaultHandlerMap=new Map}get routes(){return this._routes}addFetchListener(){self.addEventListener(`fetch`,(e=>{let{request:t}=e,n=this.handleRequest({request:t,event:e});n&&e.respondWith(n)}))}addCacheListener(){self.addEventListener(`message`,(e=>{if(e.data&&e.data.type===`CACHE_URLS`){let{payload:t}=e.data,n=Promise.all(t.urlsToCache.map(t=>{typeof t==`string`&&(t=[t]);let n=new Request(...t);return this.handleRequest({request:n,event:e})}));e.waitUntil(n),e.ports&&e.ports[0]&&n.then(()=>e.ports[0].postMessage(!0))}}))}handleRequest({request:e,event:t}){let n=new URL(e.url,location.href);if(!n.protocol.startsWith(`http`))return;let r=n.origin===location.origin,{params:i,route:a}=this.findMatchingRoute({event:t,request:e,sameOrigin:r,url:n}),o=a&&a.handler,s=e.method;if(!o&&this._defaultHandlerMap.has(s)&&(o=this._defaultHandlerMap.get(s)),!o)return;let c;try{c=o.handle({url:n,request:e,event:t,params:i})}catch(e){c=Promise.reject(e)}let l=a&&a.catchHandler;return c instanceof Promise&&(this._catchHandler||l)&&(c=c.catch(async r=>{if(l)try{return await l.handle({url:n,request:e,event:t,params:i})}catch(e){e instanceof Error&&(r=e)}if(this._catchHandler)return this._catchHandler.handle({url:n,request:e,event:t});throw r})),c}findMatchingRoute({url:e,sameOrigin:t,request:n,event:r}){let i=this._routes.get(n.method)||[];for(let a of i){let i,o=a.match({url:e,sameOrigin:t,request:n,event:r});if(o)return i=o,(Array.isArray(i)&&i.length===0||o.constructor===Object&&Object.keys(o).length===0||typeof o==`boolean`)&&(i=void 0),{route:a,params:i}}return{}}setDefaultHandler(e,t=`GET`){this._defaultHandlerMap.set(t,k(e))}setCatchHandler(e){this._catchHandler=k(e)}registerRoute(e){this._routes.has(e.method)||this._routes.set(e.method,[]),this._routes.get(e.method).push(e)}unregisterRoute(e){if(!this._routes.has(e.method))throw new t(`unregister-route-but-not-found-with-method`,{method:e.method});let n=this._routes.get(e.method).indexOf(e);if(n>-1)this._routes.get(e.method).splice(n,1);else throw new t(`unregister-route-route-not-registered`)}},N,P=()=>(N||(N=new M,N.addFetchListener(),N.addCacheListener()),N);function F(e,n,r){let i;if(typeof e==`string`){let t=new URL(e,location.href);i=new A(({url:e})=>e.href===t.href,n,r)}else if(e instanceof RegExp)i=new j(e,n,r);else if(typeof e==`function`)i=new A(e,n,r);else if(e instanceof A)i=e;else throw new t(`unsupported-route-type`,{moduleName:`workbox-routing`,funcName:`registerRoute`,paramName:`capture`});return P().registerRoute(i),i}function I(e,t=[]){for(let n of[...e.searchParams.keys()])t.some(e=>e.test(n))&&e.searchParams.delete(n);return e}function*L(e,{ignoreURLParametersMatching:t=[/^utm_/,/^fbclid$/],directoryIndex:n=`index.html`,cleanURLs:r=!0,urlManipulation:i}={}){let a=new URL(e,location.href);a.hash=``,yield a.href;let o=I(a,t);if(yield o.href,n&&o.pathname.endsWith(`/`)){let e=new URL(o.href);e.pathname+=n,yield e.href}if(r){let e=new URL(o.href);e.pathname+=`.html`,yield e.href}if(i){let e=i({url:a});for(let t of e)yield t.href}}var R=class extends A{constructor(e,t){super(({request:n})=>{let r=e.getURLsToCacheKeys();for(let i of L(n.url,t)){let t=r.get(i);if(t)return{cacheKey:t,integrity:e.getIntegrityForCacheKey(t)}}},e.strategy)}};function z(e){F(new R(O(),e))}function B(e){O().precache(e)}function V(e,t){B(e),z(t)}V([{"revision":"1872c500de691dce40960bb85481de07","url":"registerSW.js"},{"revision":"78e9ddb337f8fad69c24ac9ff2c8a3f7","url":"index.html"},{"revision":"a0fb34fc84eb148d51812cd62669f20d","url":"icon-512.svg"},{"revision":"a0fb34fc84eb148d51812cd62669f20d","url":"icon-192.svg"},{"revision":"948e060affb598c339be40d69e1f6f9c","url":"monacoeditorwork/ts.worker.bundle.js"},{"revision":"a5d8a1acfc29c2a4c882a54ffc93def3","url":"monacoeditorwork/json.worker.bundle.js"},{"revision":"d0f94ce046cf8cf09605ee7664dac557","url":"monacoeditorwork/html.worker.bundle.js"},{"revision":"a424156a79b9c1b907db93aa3180585a","url":"monacoeditorwork/editor.worker.bundle.js"},{"revision":"b3a7f967560c9816492a1567b3f7f0dc","url":"monacoeditorwork/css.worker.bundle.js"},{"revision":null,"url":"assets/xychartDiagram-JWTSCODW-DRa_TH4B.js"},{"revision":null,"url":"assets/vennDiagram-LZ73GAT5-s9Z71fz-.js"},{"revision":null,"url":"assets/utils-BNytJOb1.js"},{"revision":null,"url":"assets/use-monaco-theme-DGjkK3eO.js"},{"revision":null,"url":"assets/treemap-KZPCXAKY-B2Xkyv-K.js"},{"revision":null,"url":"assets/timeline-definition-YZTLITO2-DwZqB3nn.js"},{"revision":null,"url":"assets/terminal-tab-CnYqGghP.js"},{"revision":null,"url":"assets/terminal-tab-BrP-ENHg.css"},{"revision":null,"url":"assets/tag-CXMT0QB6.js"},{"revision":null,"url":"assets/table-DFevCOMd.js"},{"revision":null,"url":"assets/tab-store-BOgTrqRr.js"},{"revision":null,"url":"assets/stateDiagram-v2-FVOUBMTO-BGVqj_g9.js"},{"revision":null,"url":"assets/stateDiagram-RAJIS63D-66vhiIuk.js"},{"revision":null,"url":"assets/src-BqX54PbV.js"},{"revision":null,"url":"assets/sqlite-viewer-Cjl4uXyo.js"},{"revision":null,"url":"assets/settings-tab-Y37tD1kM.js"},{"revision":null,"url":"assets/sequenceDiagram-2WXFIKYE-BQDJ4CVs.js"},{"revision":null,"url":"assets/sankeyDiagram-WA2Y5GQK-T6RgG-N8.js"},{"revision":null,"url":"assets/rough.esm-JX0wREDd.js"},{"revision":null,"url":"assets/requirementDiagram-Z7DCOOCP-pQyah6WB.js"},{"revision":null,"url":"assets/react-nm2Ru1Pt.js"},{"revision":null,"url":"assets/react-dom-Bpkvzu3U.js"},{"revision":null,"url":"assets/react-SKk5z-bm.js"},{"revision":null,"url":"assets/radar-KQ55EAFF-DH0AOkUy.js"},{"revision":null,"url":"assets/quadrantDiagram-337W2JSQ-C8bzJCjQ.js"},{"revision":null,"url":"assets/preload-helper-Bf_JiD2A.js"},{"revision":null,"url":"assets/postgres-viewer-FCpA6nh4.js"},{"revision":null,"url":"assets/pieDiagram-SKSYHLDU-C1Gjrtzy.js"},{"revision":null,"url":"assets/pie-UPGHQEXC-BHncZutv.js"},{"revision":null,"url":"assets/path-6uRLdFF7.js"},{"revision":null,"url":"assets/packet-RMMSAZCW-DY5PNnZU.js"},{"revision":null,"url":"assets/ordinal-_K3x1fkz.js"},{"revision":null,"url":"assets/mindmap-definition-YRQLILUH-x0MTutJp.js"},{"revision":null,"url":"assets/mermaid-parser.core-C7UwoIh6.js"},{"revision":null,"url":"assets/math-069Z4SuC.js"},{"revision":null,"url":"assets/markdown-renderer-Djgmbi23.js"},{"revision":null,"url":"assets/linear-DP4mkX3m.js"},{"revision":null,"url":"assets/line-CVvo3dRu.js"},{"revision":null,"url":"assets/lib-BQ34Db2e.js"},{"revision":null,"url":"assets/keybindings-store-6_p_JT0B.js"},{"revision":null,"url":"assets/katex-Bqvo_ZG0.js"},{"revision":null,"url":"assets/kanban-definition-K7BYSVSG-CZ535BbZ.js"},{"revision":null,"url":"assets/jsx-runtime-kMwlnEGE.js"},{"revision":null,"url":"assets/journeyDiagram-4ABVD52K-B_L20qMe.js"},{"revision":null,"url":"assets/ishikawaDiagram-PHBUUO56-D05_LyL7.js"},{"revision":null,"url":"assets/isEmpty-bnrF3Qbc.js"},{"revision":null,"url":"assets/isArrayLikeObject-B_v2FtYn.js"},{"revision":null,"url":"assets/init-DlZdxViB.js"},{"revision":null,"url":"assets/infoDiagram-LFFYTUFH-DWwumDkq.js"},{"revision":null,"url":"assets/info-3K5VOQVL-_vRxVNUm.js"},{"revision":null,"url":"assets/index-CyXEMb4g.js"},{"revision":null,"url":"assets/index-BkidPsSC.css"},{"revision":null,"url":"assets/graphlib-BcsNnGcW.js"},{"revision":null,"url":"assets/gitGraphDiagram-K3NZZRJ6-CMoukSrY.js"},{"revision":null,"url":"assets/gitGraph-HDMCJU4V-Bwna3and.js"},{"revision":null,"url":"assets/git-graph-Dde-j8cK.js"},{"revision":null,"url":"assets/ganttDiagram-A5KZAMGK-DmL26q2P.js"},{"revision":null,"url":"assets/flowDiagram-PKNHOUZH-oYaovqyp.js"},{"revision":null,"url":"assets/extension-webview-BU1T2a8n.js"},{"revision":null,"url":"assets/erDiagram-INFDFZHY-BSh2z9Df.js"},{"revision":null,"url":"assets/dist-ovWkrgO-.js"},{"revision":null,"url":"assets/dist-DIV6WgAG.js"},{"revision":null,"url":"assets/dist-CSJdAyA9.js"},{"revision":null,"url":"assets/diff-viewer-_MPL-DRu.js"},{"revision":null,"url":"assets/diagram-P4PSJMXO-C8tjJsev.js"},{"revision":null,"url":"assets/diagram-IFDJBPK2-xKoeuiJx.js"},{"revision":null,"url":"assets/diagram-E7M64L7V-_db4pBVA.js"},{"revision":null,"url":"assets/defaultLocale-5eAKkKJC.js"},{"revision":null,"url":"assets/database-viewer-CINo6teP.js"},{"revision":null,"url":"assets/dagre-KLK3FWXG-BdJr7Byp.js"},{"revision":null,"url":"assets/dagre-DHq9bhnd.js"},{"revision":null,"url":"assets/cytoscape.esm-BW-DbntU.js"},{"revision":null,"url":"assets/csv-preview-ncSOnJSC.js"},{"revision":null,"url":"assets/createLucideIcon-PuMiQgHl.js"},{"revision":null,"url":"assets/cose-bilkent-S5V4N54A-B_AWZsOP.js"},{"revision":null,"url":"assets/columns-2-cEVJHYd7.js"},{"revision":null,"url":"assets/code-editor-BdM11-0K.js"},{"revision":null,"url":"assets/clone-LRxlvnMj.js"},{"revision":null,"url":"assets/classDiagram-v2-RAHNMMFH-DjYu-6mn.js"},{"revision":null,"url":"assets/classDiagram-VBA2DB6C-BA8Nj-_C.js"},{"revision":null,"url":"assets/chunk-YBOYWFTD-rQG3QH5s.js"},{"revision":null,"url":"assets/chunk-XZSTWKYB-DxAOx4hG.js"},{"revision":null,"url":"assets/chunk-XPW4576I-BPQQBakK.js"},{"revision":null,"url":"assets/chunk-XIRO2GV7-Djlmrely.js"},{"revision":null,"url":"assets/chunk-WL4C6EOR-ByUrSRin.js"},{"revision":null,"url":"assets/chunk-R5LLSJPH-CFwSJijQ.js"},{"revision":null,"url":"assets/chunk-QZHKN3VN-CYaTbeZf.js"},{"revision":null,"url":"assets/chunk-PU5JKC2W-ek7k4QVB.js"},{"revision":null,"url":"assets/chunk-PQ6SQG4A-TF58UVMU.js"},{"revision":null,"url":"assets/chunk-OZEHJAEY-BXhYx3nO.js"},{"revision":null,"url":"assets/chunk-O4XLMI2P-nDhi_cVu.js"},{"revision":null,"url":"assets/chunk-NQ4KR5QH-z_blpjxi.js"},{"revision":null,"url":"assets/chunk-MX3YWQON-BpS_PtKp.js"},{"revision":null,"url":"assets/chunk-L3YUKLVL-C7qGJrfV.js"},{"revision":null,"url":"assets/chunk-KYZI473N-Bb0MCaIO.js"},{"revision":null,"url":"assets/chunk-KX2RTZJC-CRq1OBZv.js"},{"revision":null,"url":"assets/chunk-JSJVCQXG-99JzIdPr.js"},{"revision":null,"url":"assets/chunk-HHEYEP7N-C7vxA5i9.js"},{"revision":null,"url":"assets/chunk-GLR3WWYH-DKikpoJM.js"},{"revision":null,"url":"assets/chunk-GEFDOKGD-D-pKjlVd.js"},{"revision":null,"url":"assets/chunk-FMBD7UC4-DXncblvW.js"},{"revision":null,"url":"assets/chunk-EGIJ26TM-DzqmU2Z7.js"},{"revision":null,"url":"assets/chunk-CFjPhJqf.js"},{"revision":null,"url":"assets/chunk-C72U2L5F-D21mS_6G.js"},{"revision":null,"url":"assets/chunk-7R4GIKGN-Dv-4cAYn.js"},{"revision":null,"url":"assets/chunk-7E7YKBS2-CiyUJxNI.js"},{"revision":null,"url":"assets/chunk-55IACEB6-DJ6BynZ4.js"},{"revision":null,"url":"assets/chunk-4BX2VUAB-D4tOov49.js"},{"revision":null,"url":"assets/chevron-right-5HgK6l7K.js"},{"revision":null,"url":"assets/chat-tab-rYBo5Mff.js"},{"revision":null,"url":"assets/channel-By7bn0Yq.js"},{"revision":null,"url":"assets/c4Diagram-IC4MRINW-0Vp0Jeas.js"},{"revision":null,"url":"assets/browser-tab-LFNnCzgB.js"},{"revision":null,"url":"assets/blockDiagram-WCTKOSBZ-BCLqzhuZ.js"},{"revision":null,"url":"assets/arrow-up-BYhx9ckd.js"},{"revision":null,"url":"assets/array-B9UHiPd-.js"},{"revision":null,"url":"assets/architectureDiagram-2XIMDMQ5-Z-4eN4za.js"},{"revision":null,"url":"assets/architecture-PBZL5I3N-DEO2f3VD.js"},{"revision":null,"url":"assets/arc-BAOivWpI.js"},{"revision":null,"url":"assets/api-settings-BUvk6Saw.js"},{"revision":null,"url":"assets/api-client-BfBM3I7n.js"},{"revision":null,"url":"assets/_baseUniq-BT4Ow4Kk.js"},{"revision":null,"url":"assets/_basePickBy-5PGDJbfF.js"},{"revision":"79c8870653c8f419f2e3323085e1f4be","url":"manifest.webmanifest"}]),self.addEventListener(`push`,e=>{e.waitUntil(self.clients.matchAll({type:`window`,includeUncontrolled:!0}).then(t=>{if(t.some(e=>e.visibilityState===`visible`))return;let n=e.data?.json()??{title:`PPM`,body:`Chat completed`};return self.registration.showNotification(n.title,{body:n.body,icon:`/icon-192.png`,badge:`/icon-192.png`,tag:`ppm-chat-done`,silent:!1,data:{url:self.location.origin}})}))}),self.addEventListener(`notificationclick`,e=>{e.notification.close(),e.waitUntil(self.clients.matchAll({type:`window`,includeUncontrolled:!0}).then(t=>{for(let e of t)if(e.url.includes(self.location.origin)&&`focus`in e)return e.focus();return self.clients.openWindow(e.notification.data?.url||`/`)}))});
1
+ try{self[`workbox:core:7.3.0`]&&_()}catch{}var e=(e,...t)=>{let n=e;return t.length>0&&(n+=` :: ${JSON.stringify(t)}`),n},t=class extends Error{constructor(t,n){let r=e(t,n);super(r),this.name=t,this.details=n}},n={googleAnalytics:`googleAnalytics`,precache:`precache-v2`,prefix:`workbox`,runtime:`runtime`,suffix:typeof registration<`u`?registration.scope:``},r=e=>[n.prefix,e,n.suffix].filter(e=>e&&e.length>0).join(`-`),i=e=>{for(let t of Object.keys(n))e(t)},a={updateDetails:e=>{i(t=>{typeof e[t]==`string`&&(n[t]=e[t])})},getGoogleAnalyticsName:e=>e||r(n.googleAnalytics),getPrecacheName:e=>e||r(n.precache),getPrefix:()=>n.prefix,getRuntimeName:e=>e||r(n.runtime),getSuffix:()=>n.suffix};function o(e,t){let n=t();return e.waitUntil(n),n}try{self[`workbox:precaching:7.3.0`]&&_()}catch{}var s=`__WB_REVISION__`;function c(e){if(!e)throw new t(`add-to-cache-list-unexpected-type`,{entry:e});if(typeof e==`string`){let t=new URL(e,location.href);return{cacheKey:t.href,url:t.href}}let{revision:n,url:r}=e;if(!r)throw new t(`add-to-cache-list-unexpected-type`,{entry:e});if(!n){let e=new URL(r,location.href);return{cacheKey:e.href,url:e.href}}let i=new URL(r,location.href),a=new URL(r,location.href);return i.searchParams.set(s,n),{cacheKey:i.href,url:a.href}}var l=class{constructor(){this.updatedURLs=[],this.notUpdatedURLs=[],this.handlerWillStart=async({request:e,state:t})=>{t&&(t.originalRequest=e)},this.cachedResponseWillBeUsed=async({event:e,state:t,cachedResponse:n})=>{if(e.type===`install`&&t&&t.originalRequest&&t.originalRequest instanceof Request){let e=t.originalRequest.url;n?this.notUpdatedURLs.push(e):this.updatedURLs.push(e)}return n}}},u=class{constructor({precacheController:e}){this.cacheKeyWillBeUsed=async({request:e,params:t})=>{let n=t?.cacheKey||this._precacheController.getCacheKeyForURL(e.url);return n?new Request(n,{headers:e.headers}):e},this._precacheController=e}},d;function f(){if(d===void 0){let e=new Response(``);if(`body`in e)try{new Response(e.body),d=!0}catch{d=!1}d=!1}return d}async function p(e,n){let r=null;if(e.url&&(r=new URL(e.url).origin),r!==self.location.origin)throw new t(`cross-origin-copy-response`,{origin:r});let i=e.clone(),a={headers:new Headers(i.headers),status:i.status,statusText:i.statusText},o=n?n(a):a,s=f()?i.body:await i.blob();return new Response(s,o)}var m=e=>new URL(String(e),location.href).href.replace(RegExp(`^${location.origin}`),``);function h(e,t){let n=new URL(e);for(let e of t)n.searchParams.delete(e);return n.href}async function g(e,t,n,r){let i=h(t.url,n);if(t.url===i)return e.match(t,r);let a=Object.assign(Object.assign({},r),{ignoreSearch:!0}),o=await e.keys(t,a);for(let t of o)if(i===h(t.url,n))return e.match(t,r)}var v=class{constructor(){this.promise=new Promise((e,t)=>{this.resolve=e,this.reject=t})}},y=new Set;async function b(){for(let e of y)await e()}function x(e){return new Promise(t=>setTimeout(t,e))}try{self[`workbox:strategies:7.3.0`]&&_()}catch{}function S(e){return typeof e==`string`?new Request(e):e}var C=class{constructor(e,t){this._cacheKeys={},Object.assign(this,t),this.event=t.event,this._strategy=e,this._handlerDeferred=new v,this._extendLifetimePromises=[],this._plugins=[...e.plugins],this._pluginStateMap=new Map;for(let e of this._plugins)this._pluginStateMap.set(e,{});this.event.waitUntil(this._handlerDeferred.promise)}async fetch(e){let{event:n}=this,r=S(e);if(r.mode===`navigate`&&n instanceof FetchEvent&&n.preloadResponse){let e=await n.preloadResponse;if(e)return e}let i=this.hasCallback(`fetchDidFail`)?r.clone():null;try{for(let e of this.iterateCallbacks(`requestWillFetch`))r=await e({request:r.clone(),event:n})}catch(e){if(e instanceof Error)throw new t(`plugin-error-request-will-fetch`,{thrownErrorMessage:e.message})}let a=r.clone();try{let e;e=await fetch(r,r.mode===`navigate`?void 0:this._strategy.fetchOptions);for(let t of this.iterateCallbacks(`fetchDidSucceed`))e=await t({event:n,request:a,response:e});return e}catch(e){throw i&&await this.runCallbacks(`fetchDidFail`,{error:e,event:n,originalRequest:i.clone(),request:a.clone()}),e}}async fetchAndCachePut(e){let t=await this.fetch(e),n=t.clone();return this.waitUntil(this.cachePut(e,n)),t}async cacheMatch(e){let t=S(e),n,{cacheName:r,matchOptions:i}=this._strategy,a=await this.getCacheKey(t,`read`),o=Object.assign(Object.assign({},i),{cacheName:r});n=await caches.match(a,o);for(let e of this.iterateCallbacks(`cachedResponseWillBeUsed`))n=await e({cacheName:r,matchOptions:i,cachedResponse:n,request:a,event:this.event})||void 0;return n}async cachePut(e,n){let r=S(e);await x(0);let i=await this.getCacheKey(r,`write`);if(!n)throw new t(`cache-put-with-no-response`,{url:m(i.url)});let a=await this._ensureResponseSafeToCache(n);if(!a)return!1;let{cacheName:o,matchOptions:s}=this._strategy,c=await self.caches.open(o),l=this.hasCallback(`cacheDidUpdate`),u=l?await g(c,i.clone(),[`__WB_REVISION__`],s):null;try{await c.put(i,l?a.clone():a)}catch(e){if(e instanceof Error)throw e.name===`QuotaExceededError`&&await b(),e}for(let e of this.iterateCallbacks(`cacheDidUpdate`))await e({cacheName:o,oldResponse:u,newResponse:a.clone(),request:i,event:this.event});return!0}async getCacheKey(e,t){let n=`${e.url} | ${t}`;if(!this._cacheKeys[n]){let r=e;for(let e of this.iterateCallbacks(`cacheKeyWillBeUsed`))r=S(await e({mode:t,request:r,event:this.event,params:this.params}));this._cacheKeys[n]=r}return this._cacheKeys[n]}hasCallback(e){for(let t of this._strategy.plugins)if(e in t)return!0;return!1}async runCallbacks(e,t){for(let n of this.iterateCallbacks(e))await n(t)}*iterateCallbacks(e){for(let t of this._strategy.plugins)if(typeof t[e]==`function`){let n=this._pluginStateMap.get(t);yield r=>{let i=Object.assign(Object.assign({},r),{state:n});return t[e](i)}}}waitUntil(e){return this._extendLifetimePromises.push(e),e}async doneWaiting(){for(;this._extendLifetimePromises.length;){let e=this._extendLifetimePromises.splice(0),t=(await Promise.allSettled(e)).find(e=>e.status===`rejected`);if(t)throw t.reason}}destroy(){this._handlerDeferred.resolve(null)}async _ensureResponseSafeToCache(e){let t=e,n=!1;for(let e of this.iterateCallbacks(`cacheWillUpdate`))if(t=await e({request:this.request,response:t,event:this.event})||void 0,n=!0,!t)break;return n||t&&t.status!==200&&(t=void 0),t}},w=class{constructor(e={}){this.cacheName=a.getRuntimeName(e.cacheName),this.plugins=e.plugins||[],this.fetchOptions=e.fetchOptions,this.matchOptions=e.matchOptions}handle(e){let[t]=this.handleAll(e);return t}handleAll(e){e instanceof FetchEvent&&(e={event:e,request:e.request});let t=e.event,n=typeof e.request==`string`?new Request(e.request):e.request,r=`params`in e?e.params:void 0,i=new C(this,{event:t,request:n,params:r}),a=this._getResponse(i,n,t);return[a,this._awaitComplete(a,i,n,t)]}async _getResponse(e,n,r){await e.runCallbacks(`handlerWillStart`,{event:r,request:n});let i;try{if(i=await this._handle(n,e),!i||i.type===`error`)throw new t(`no-response`,{url:n.url})}catch(t){if(t instanceof Error){for(let a of e.iterateCallbacks(`handlerDidError`))if(i=await a({error:t,event:r,request:n}),i)break}if(!i)throw t}for(let t of e.iterateCallbacks(`handlerWillRespond`))i=await t({event:r,request:n,response:i});return i}async _awaitComplete(e,t,n,r){let i,a;try{i=await e}catch{}try{await t.runCallbacks(`handlerDidRespond`,{event:r,request:n,response:i}),await t.doneWaiting()}catch(e){e instanceof Error&&(a=e)}if(await t.runCallbacks(`handlerDidComplete`,{event:r,request:n,response:i,error:a}),t.destroy(),a)throw a}},T=class e extends w{constructor(t={}){t.cacheName=a.getPrecacheName(t.cacheName),super(t),this._fallbackToNetwork=t.fallbackToNetwork!==!1,this.plugins.push(e.copyRedirectedCacheableResponsesPlugin)}async _handle(e,t){return await t.cacheMatch(e)||(t.event&&t.event.type===`install`?await this._handleInstall(e,t):await this._handleFetch(e,t))}async _handleFetch(e,n){let r,i=n.params||{};if(this._fallbackToNetwork){let t=i.integrity,a=e.integrity,o=!a||a===t;r=await n.fetch(new Request(e,{integrity:e.mode===`no-cors`?void 0:a||t})),t&&o&&e.mode!==`no-cors`&&(this._useDefaultCacheabilityPluginIfNeeded(),await n.cachePut(e,r.clone()))}else throw new t(`missing-precache-entry`,{cacheName:this.cacheName,url:e.url});return r}async _handleInstall(e,n){this._useDefaultCacheabilityPluginIfNeeded();let r=await n.fetch(e);if(!await n.cachePut(e,r.clone()))throw new t(`bad-precaching-response`,{url:e.url,status:r.status});return r}_useDefaultCacheabilityPluginIfNeeded(){let t=null,n=0;for(let[r,i]of this.plugins.entries())i!==e.copyRedirectedCacheableResponsesPlugin&&(i===e.defaultPrecacheCacheabilityPlugin&&(t=r),i.cacheWillUpdate&&n++);n===0?this.plugins.push(e.defaultPrecacheCacheabilityPlugin):n>1&&t!==null&&this.plugins.splice(t,1)}};T.defaultPrecacheCacheabilityPlugin={async cacheWillUpdate({response:e}){return!e||e.status>=400?null:e}},T.copyRedirectedCacheableResponsesPlugin={async cacheWillUpdate({response:e}){return e.redirected?await p(e):e}};var E=class{constructor({cacheName:e,plugins:t=[],fallbackToNetwork:n=!0}={}){this._urlsToCacheKeys=new Map,this._urlsToCacheModes=new Map,this._cacheKeysToIntegrities=new Map,this._strategy=new T({cacheName:a.getPrecacheName(e),plugins:[...t,new u({precacheController:this})],fallbackToNetwork:n}),this.install=this.install.bind(this),this.activate=this.activate.bind(this)}get strategy(){return this._strategy}precache(e){this.addToCacheList(e),this._installAndActiveListenersAdded||=(self.addEventListener(`install`,this.install),self.addEventListener(`activate`,this.activate),!0)}addToCacheList(e){let n=[];for(let r of e){typeof r==`string`?n.push(r):r&&r.revision===void 0&&n.push(r.url);let{cacheKey:e,url:i}=c(r),a=typeof r!=`string`&&r.revision?`reload`:`default`;if(this._urlsToCacheKeys.has(i)&&this._urlsToCacheKeys.get(i)!==e)throw new t(`add-to-cache-list-conflicting-entries`,{firstEntry:this._urlsToCacheKeys.get(i),secondEntry:e});if(typeof r!=`string`&&r.integrity){if(this._cacheKeysToIntegrities.has(e)&&this._cacheKeysToIntegrities.get(e)!==r.integrity)throw new t(`add-to-cache-list-conflicting-integrities`,{url:i});this._cacheKeysToIntegrities.set(e,r.integrity)}if(this._urlsToCacheKeys.set(i,e),this._urlsToCacheModes.set(i,a),n.length>0){let e=`Workbox is precaching URLs without revision info: ${n.join(`, `)}\nThis is generally NOT safe. Learn more at https://bit.ly/wb-precache`;console.warn(e)}}}install(e){return o(e,async()=>{let t=new l;this.strategy.plugins.push(t);for(let[t,n]of this._urlsToCacheKeys){let r=this._cacheKeysToIntegrities.get(n),i=this._urlsToCacheModes.get(t),a=new Request(t,{integrity:r,cache:i,credentials:`same-origin`});await Promise.all(this.strategy.handleAll({params:{cacheKey:n},request:a,event:e}))}let{updatedURLs:n,notUpdatedURLs:r}=t;return{updatedURLs:n,notUpdatedURLs:r}})}activate(e){return o(e,async()=>{let e=await self.caches.open(this.strategy.cacheName),t=await e.keys(),n=new Set(this._urlsToCacheKeys.values()),r=[];for(let i of t)n.has(i.url)||(await e.delete(i),r.push(i.url));return{deletedURLs:r}})}getURLsToCacheKeys(){return this._urlsToCacheKeys}getCachedURLs(){return[...this._urlsToCacheKeys.keys()]}getCacheKeyForURL(e){let t=new URL(e,location.href);return this._urlsToCacheKeys.get(t.href)}getIntegrityForCacheKey(e){return this._cacheKeysToIntegrities.get(e)}async matchPrecache(e){let t=e instanceof Request?e.url:e,n=this.getCacheKeyForURL(t);if(n)return(await self.caches.open(this.strategy.cacheName)).match(n)}createHandlerBoundToURL(e){let n=this.getCacheKeyForURL(e);if(!n)throw new t(`non-precached-url`,{url:e});return t=>(t.request=new Request(e),t.params=Object.assign({cacheKey:n},t.params),this.strategy.handle(t))}},D,O=()=>(D||=new E,D);try{self[`workbox:routing:7.3.0`]&&_()}catch{}var k=e=>e&&typeof e==`object`?e:{handle:e},A=class{constructor(e,t,n=`GET`){this.handler=k(t),this.match=e,this.method=n}setCatchHandler(e){this.catchHandler=k(e)}},j=class extends A{constructor(e,t,n){super(({url:t})=>{let n=e.exec(t.href);if(n&&!(t.origin!==location.origin&&n.index!==0))return n.slice(1)},t,n)}},M=class{constructor(){this._routes=new Map,this._defaultHandlerMap=new Map}get routes(){return this._routes}addFetchListener(){self.addEventListener(`fetch`,(e=>{let{request:t}=e,n=this.handleRequest({request:t,event:e});n&&e.respondWith(n)}))}addCacheListener(){self.addEventListener(`message`,(e=>{if(e.data&&e.data.type===`CACHE_URLS`){let{payload:t}=e.data,n=Promise.all(t.urlsToCache.map(t=>{typeof t==`string`&&(t=[t]);let n=new Request(...t);return this.handleRequest({request:n,event:e})}));e.waitUntil(n),e.ports&&e.ports[0]&&n.then(()=>e.ports[0].postMessage(!0))}}))}handleRequest({request:e,event:t}){let n=new URL(e.url,location.href);if(!n.protocol.startsWith(`http`))return;let r=n.origin===location.origin,{params:i,route:a}=this.findMatchingRoute({event:t,request:e,sameOrigin:r,url:n}),o=a&&a.handler,s=e.method;if(!o&&this._defaultHandlerMap.has(s)&&(o=this._defaultHandlerMap.get(s)),!o)return;let c;try{c=o.handle({url:n,request:e,event:t,params:i})}catch(e){c=Promise.reject(e)}let l=a&&a.catchHandler;return c instanceof Promise&&(this._catchHandler||l)&&(c=c.catch(async r=>{if(l)try{return await l.handle({url:n,request:e,event:t,params:i})}catch(e){e instanceof Error&&(r=e)}if(this._catchHandler)return this._catchHandler.handle({url:n,request:e,event:t});throw r})),c}findMatchingRoute({url:e,sameOrigin:t,request:n,event:r}){let i=this._routes.get(n.method)||[];for(let a of i){let i,o=a.match({url:e,sameOrigin:t,request:n,event:r});if(o)return i=o,(Array.isArray(i)&&i.length===0||o.constructor===Object&&Object.keys(o).length===0||typeof o==`boolean`)&&(i=void 0),{route:a,params:i}}return{}}setDefaultHandler(e,t=`GET`){this._defaultHandlerMap.set(t,k(e))}setCatchHandler(e){this._catchHandler=k(e)}registerRoute(e){this._routes.has(e.method)||this._routes.set(e.method,[]),this._routes.get(e.method).push(e)}unregisterRoute(e){if(!this._routes.has(e.method))throw new t(`unregister-route-but-not-found-with-method`,{method:e.method});let n=this._routes.get(e.method).indexOf(e);if(n>-1)this._routes.get(e.method).splice(n,1);else throw new t(`unregister-route-route-not-registered`)}},N,P=()=>(N||(N=new M,N.addFetchListener(),N.addCacheListener()),N);function F(e,n,r){let i;if(typeof e==`string`){let t=new URL(e,location.href);i=new A(({url:e})=>e.href===t.href,n,r)}else if(e instanceof RegExp)i=new j(e,n,r);else if(typeof e==`function`)i=new A(e,n,r);else if(e instanceof A)i=e;else throw new t(`unsupported-route-type`,{moduleName:`workbox-routing`,funcName:`registerRoute`,paramName:`capture`});return P().registerRoute(i),i}function I(e,t=[]){for(let n of[...e.searchParams.keys()])t.some(e=>e.test(n))&&e.searchParams.delete(n);return e}function*L(e,{ignoreURLParametersMatching:t=[/^utm_/,/^fbclid$/],directoryIndex:n=`index.html`,cleanURLs:r=!0,urlManipulation:i}={}){let a=new URL(e,location.href);a.hash=``,yield a.href;let o=I(a,t);if(yield o.href,n&&o.pathname.endsWith(`/`)){let e=new URL(o.href);e.pathname+=n,yield e.href}if(r){let e=new URL(o.href);e.pathname+=`.html`,yield e.href}if(i){let e=i({url:a});for(let t of e)yield t.href}}var R=class extends A{constructor(e,t){super(({request:n})=>{let r=e.getURLsToCacheKeys();for(let i of L(n.url,t)){let t=r.get(i);if(t)return{cacheKey:t,integrity:e.getIntegrityForCacheKey(t)}}},e.strategy)}};function z(e){F(new R(O(),e))}function B(e){O().precache(e)}function V(e,t){B(e),z(t)}V([{"revision":"1872c500de691dce40960bb85481de07","url":"registerSW.js"},{"revision":"fad83859fcbc83cfa1d4c3fd5ebec12e","url":"index.html"},{"revision":"a0fb34fc84eb148d51812cd62669f20d","url":"icon-512.svg"},{"revision":"a0fb34fc84eb148d51812cd62669f20d","url":"icon-192.svg"},{"revision":"948e060affb598c339be40d69e1f6f9c","url":"monacoeditorwork/ts.worker.bundle.js"},{"revision":"a5d8a1acfc29c2a4c882a54ffc93def3","url":"monacoeditorwork/json.worker.bundle.js"},{"revision":"d0f94ce046cf8cf09605ee7664dac557","url":"monacoeditorwork/html.worker.bundle.js"},{"revision":"a424156a79b9c1b907db93aa3180585a","url":"monacoeditorwork/editor.worker.bundle.js"},{"revision":"b3a7f967560c9816492a1567b3f7f0dc","url":"monacoeditorwork/css.worker.bundle.js"},{"revision":null,"url":"assets/xychartDiagram-JWTSCODW-DRa_TH4B.js"},{"revision":null,"url":"assets/vennDiagram-LZ73GAT5-s9Z71fz-.js"},{"revision":null,"url":"assets/utils-BNytJOb1.js"},{"revision":null,"url":"assets/use-monaco-theme-DqA6WXo2.js"},{"revision":null,"url":"assets/treemap-KZPCXAKY-B2Xkyv-K.js"},{"revision":null,"url":"assets/timeline-definition-YZTLITO2-DwZqB3nn.js"},{"revision":null,"url":"assets/terminal-tab-COW3438-.js"},{"revision":null,"url":"assets/terminal-tab-BrP-ENHg.css"},{"revision":null,"url":"assets/tag-CXMT0QB6.js"},{"revision":null,"url":"assets/table-DFevCOMd.js"},{"revision":null,"url":"assets/tab-store-BOgTrqRr.js"},{"revision":null,"url":"assets/stateDiagram-v2-FVOUBMTO-BGVqj_g9.js"},{"revision":null,"url":"assets/stateDiagram-RAJIS63D-66vhiIuk.js"},{"revision":null,"url":"assets/src-BqX54PbV.js"},{"revision":null,"url":"assets/sqlite-viewer-BFdZBZgC.js"},{"revision":null,"url":"assets/settings-tab-C_jaua57.js"},{"revision":null,"url":"assets/sequenceDiagram-2WXFIKYE-BQDJ4CVs.js"},{"revision":null,"url":"assets/sankeyDiagram-WA2Y5GQK-T6RgG-N8.js"},{"revision":null,"url":"assets/rough.esm-JX0wREDd.js"},{"revision":null,"url":"assets/requirementDiagram-Z7DCOOCP-pQyah6WB.js"},{"revision":null,"url":"assets/react-nm2Ru1Pt.js"},{"revision":null,"url":"assets/react-dom-Bpkvzu3U.js"},{"revision":null,"url":"assets/react-SKk5z-bm.js"},{"revision":null,"url":"assets/radar-KQ55EAFF-DH0AOkUy.js"},{"revision":null,"url":"assets/quadrantDiagram-337W2JSQ-C8bzJCjQ.js"},{"revision":null,"url":"assets/preload-helper-Bf_JiD2A.js"},{"revision":null,"url":"assets/postgres-viewer-BXJLyZp4.js"},{"revision":null,"url":"assets/pieDiagram-SKSYHLDU-C1Gjrtzy.js"},{"revision":null,"url":"assets/pie-UPGHQEXC-BHncZutv.js"},{"revision":null,"url":"assets/path-6uRLdFF7.js"},{"revision":null,"url":"assets/packet-RMMSAZCW-DY5PNnZU.js"},{"revision":null,"url":"assets/ordinal-_K3x1fkz.js"},{"revision":null,"url":"assets/mindmap-definition-YRQLILUH-x0MTutJp.js"},{"revision":null,"url":"assets/mermaid-parser.core-C7UwoIh6.js"},{"revision":null,"url":"assets/math-069Z4SuC.js"},{"revision":null,"url":"assets/markdown-renderer-C1hJEMtm.js"},{"revision":null,"url":"assets/linear-DP4mkX3m.js"},{"revision":null,"url":"assets/line-CVvo3dRu.js"},{"revision":null,"url":"assets/lib-BQ34Db2e.js"},{"revision":null,"url":"assets/keybindings-store-B7gFbUFf.js"},{"revision":null,"url":"assets/katex-Bqvo_ZG0.js"},{"revision":null,"url":"assets/kanban-definition-K7BYSVSG-CZ535BbZ.js"},{"revision":null,"url":"assets/jsx-runtime-kMwlnEGE.js"},{"revision":null,"url":"assets/journeyDiagram-4ABVD52K-B_L20qMe.js"},{"revision":null,"url":"assets/ishikawaDiagram-PHBUUO56-D05_LyL7.js"},{"revision":null,"url":"assets/isEmpty-bnrF3Qbc.js"},{"revision":null,"url":"assets/isArrayLikeObject-B_v2FtYn.js"},{"revision":null,"url":"assets/init-DlZdxViB.js"},{"revision":null,"url":"assets/infoDiagram-LFFYTUFH-DWwumDkq.js"},{"revision":null,"url":"assets/info-3K5VOQVL-_vRxVNUm.js"},{"revision":null,"url":"assets/index-Dx21KBME.css"},{"revision":null,"url":"assets/index-BffsIdqe.js"},{"revision":null,"url":"assets/graphlib-BcsNnGcW.js"},{"revision":null,"url":"assets/gitGraphDiagram-K3NZZRJ6-CMoukSrY.js"},{"revision":null,"url":"assets/gitGraph-HDMCJU4V-Bwna3and.js"},{"revision":null,"url":"assets/git-graph-XDyQYPZk.js"},{"revision":null,"url":"assets/ganttDiagram-A5KZAMGK-DmL26q2P.js"},{"revision":null,"url":"assets/flowDiagram-PKNHOUZH-oYaovqyp.js"},{"revision":null,"url":"assets/extension-webview-DYuYgKhD.js"},{"revision":null,"url":"assets/erDiagram-INFDFZHY-BSh2z9Df.js"},{"revision":null,"url":"assets/dist-ovWkrgO-.js"},{"revision":null,"url":"assets/dist-DIV6WgAG.js"},{"revision":null,"url":"assets/dist-CSJdAyA9.js"},{"revision":null,"url":"assets/diff-viewer-CsPH6s_E.js"},{"revision":null,"url":"assets/diagram-P4PSJMXO-C8tjJsev.js"},{"revision":null,"url":"assets/diagram-IFDJBPK2-xKoeuiJx.js"},{"revision":null,"url":"assets/diagram-E7M64L7V-_db4pBVA.js"},{"revision":null,"url":"assets/defaultLocale-5eAKkKJC.js"},{"revision":null,"url":"assets/database-viewer-BWGt4Ufn.js"},{"revision":null,"url":"assets/dagre-KLK3FWXG-BdJr7Byp.js"},{"revision":null,"url":"assets/dagre-DHq9bhnd.js"},{"revision":null,"url":"assets/cytoscape.esm-BW-DbntU.js"},{"revision":null,"url":"assets/csv-preview-ncSOnJSC.js"},{"revision":null,"url":"assets/createLucideIcon-PuMiQgHl.js"},{"revision":null,"url":"assets/cose-bilkent-S5V4N54A-B_AWZsOP.js"},{"revision":null,"url":"assets/columns-2-cEVJHYd7.js"},{"revision":null,"url":"assets/code-editor-CLBY3U7l.js"},{"revision":null,"url":"assets/clone-LRxlvnMj.js"},{"revision":null,"url":"assets/classDiagram-v2-RAHNMMFH-DjYu-6mn.js"},{"revision":null,"url":"assets/classDiagram-VBA2DB6C-BA8Nj-_C.js"},{"revision":null,"url":"assets/chunk-YBOYWFTD-rQG3QH5s.js"},{"revision":null,"url":"assets/chunk-XZSTWKYB-DxAOx4hG.js"},{"revision":null,"url":"assets/chunk-XPW4576I-BPQQBakK.js"},{"revision":null,"url":"assets/chunk-XIRO2GV7-Djlmrely.js"},{"revision":null,"url":"assets/chunk-WL4C6EOR-ByUrSRin.js"},{"revision":null,"url":"assets/chunk-R5LLSJPH-CFwSJijQ.js"},{"revision":null,"url":"assets/chunk-QZHKN3VN-CYaTbeZf.js"},{"revision":null,"url":"assets/chunk-PU5JKC2W-ek7k4QVB.js"},{"revision":null,"url":"assets/chunk-PQ6SQG4A-TF58UVMU.js"},{"revision":null,"url":"assets/chunk-OZEHJAEY-BXhYx3nO.js"},{"revision":null,"url":"assets/chunk-O4XLMI2P-nDhi_cVu.js"},{"revision":null,"url":"assets/chunk-NQ4KR5QH-z_blpjxi.js"},{"revision":null,"url":"assets/chunk-MX3YWQON-BpS_PtKp.js"},{"revision":null,"url":"assets/chunk-L3YUKLVL-C7qGJrfV.js"},{"revision":null,"url":"assets/chunk-KYZI473N-Bb0MCaIO.js"},{"revision":null,"url":"assets/chunk-KX2RTZJC-CRq1OBZv.js"},{"revision":null,"url":"assets/chunk-JSJVCQXG-99JzIdPr.js"},{"revision":null,"url":"assets/chunk-HHEYEP7N-C7vxA5i9.js"},{"revision":null,"url":"assets/chunk-GLR3WWYH-DKikpoJM.js"},{"revision":null,"url":"assets/chunk-GEFDOKGD-D-pKjlVd.js"},{"revision":null,"url":"assets/chunk-FMBD7UC4-DXncblvW.js"},{"revision":null,"url":"assets/chunk-EGIJ26TM-DzqmU2Z7.js"},{"revision":null,"url":"assets/chunk-CFjPhJqf.js"},{"revision":null,"url":"assets/chunk-C72U2L5F-D21mS_6G.js"},{"revision":null,"url":"assets/chunk-7R4GIKGN-Dv-4cAYn.js"},{"revision":null,"url":"assets/chunk-7E7YKBS2-CiyUJxNI.js"},{"revision":null,"url":"assets/chunk-55IACEB6-DJ6BynZ4.js"},{"revision":null,"url":"assets/chunk-4BX2VUAB-D4tOov49.js"},{"revision":null,"url":"assets/chevron-right-5HgK6l7K.js"},{"revision":null,"url":"assets/chat-tab-DyCHqHNy.js"},{"revision":null,"url":"assets/channel-By7bn0Yq.js"},{"revision":null,"url":"assets/c4Diagram-IC4MRINW-0Vp0Jeas.js"},{"revision":null,"url":"assets/browser-tab-D2Z_380K.js"},{"revision":null,"url":"assets/blockDiagram-WCTKOSBZ-BCLqzhuZ.js"},{"revision":null,"url":"assets/arrow-up-BYhx9ckd.js"},{"revision":null,"url":"assets/array-B9UHiPd-.js"},{"revision":null,"url":"assets/architectureDiagram-2XIMDMQ5-Z-4eN4za.js"},{"revision":null,"url":"assets/architecture-PBZL5I3N-DEO2f3VD.js"},{"revision":null,"url":"assets/arc-BAOivWpI.js"},{"revision":null,"url":"assets/api-settings-BUvk6Saw.js"},{"revision":null,"url":"assets/api-client-BfBM3I7n.js"},{"revision":null,"url":"assets/_baseUniq-BT4Ow4Kk.js"},{"revision":null,"url":"assets/_basePickBy-5PGDJbfF.js"},{"revision":"79c8870653c8f419f2e3323085e1f4be","url":"manifest.webmanifest"}]),self.addEventListener(`push`,e=>{e.waitUntil(self.clients.matchAll({type:`window`,includeUncontrolled:!0}).then(t=>{if(t.some(e=>e.visibilityState===`visible`))return;let n=e.data?.json()??{title:`PPM`,body:`Chat completed`};return self.registration.showNotification(n.title,{body:n.body,icon:`/icon-192.png`,badge:`/icon-192.png`,tag:`ppm-chat-done`,silent:!1,data:{url:self.location.origin}})}))}),self.addEventListener(`notificationclick`,e=>{e.notification.close(),e.waitUntil(self.clients.matchAll({type:`window`,includeUncontrolled:!0}).then(t=>{for(let e of t)if(e.url.includes(self.location.origin)&&`focus`in e)return e.focus();return self.clients.openWindow(e.notification.data?.url||`/`)}))});
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hienlh/ppm",
3
- "version": "0.9.3",
3
+ "version": "0.9.5",
4
4
  "description": "Personal Project Manager — mobile-first web IDE with AI assistance",
5
5
  "author": "hienlh",
6
6
  "license": "MIT",
@@ -9,7 +9,12 @@
9
9
  { "command": "ppm-db.openViewer", "title": "Database: Open Viewer" },
10
10
  { "command": "ppm-db.runQuery", "title": "Database: Run SQL Query" },
11
11
  { "command": "ppm-db.refreshConnections", "title": "Database: Refresh Connections", "icon": "refresh" },
12
- { "command": "ppm-db.addConnection", "title": "Add connection", "icon": "plus" }
12
+ { "command": "ppm-db.addConnection", "title": "Add connection", "icon": "plus" },
13
+ { "command": "ppm-db.editConnection", "title": "Database: Edit Connection" },
14
+ { "command": "ppm-db.deleteConnection", "title": "Database: Delete Connection" },
15
+ { "command": "ppm-db.testConnection", "title": "Database: Test Connection" },
16
+ { "command": "ppm-db.exportConnections", "title": "Database: Export Connections" },
17
+ { "command": "ppm-db.importConnections", "title": "Database: Import Connections" }
13
18
  ],
14
19
  "views": {
15
20
  "sidebar": [
@@ -19,7 +24,9 @@
19
24
  "menus": {
20
25
  "commandPalette": [
21
26
  { "command": "ppm-db.openViewer" },
22
- { "command": "ppm-db.runQuery" }
27
+ { "command": "ppm-db.runQuery" },
28
+ { "command": "ppm-db.exportConnections" },
29
+ { "command": "ppm-db.importConnections" }
23
30
  ],
24
31
  "view/title": [
25
32
  { "command": "ppm-db.refreshConnections", "when": "view == ppm-db.connections", "group": "navigation" },
@@ -6,13 +6,17 @@
6
6
  interface ConnectionNode {
7
7
  id: string;
8
8
  name: string;
9
- type: "connection" | "table" | "column";
9
+ type: "connection" | "group" | "table" | "column";
10
10
  connectionId?: number;
11
11
  connectionName?: string;
12
12
  connectionType?: string;
13
13
  connectionColor?: string | null;
14
+ connectionReadonly?: number;
15
+ groupName?: string | null;
14
16
  schemaName?: string;
15
17
  dataType?: string;
18
+ rowCount?: number;
19
+ children?: ConnectionNode[];
16
20
  }
17
21
 
18
22
  interface ApiConnection {
@@ -20,6 +24,8 @@ interface ApiConnection {
20
24
  name: string;
21
25
  type: string;
22
26
  color: string | null;
27
+ readonly: number;
28
+ group_name: string | null;
23
29
  }
24
30
 
25
31
  interface ApiTable {
@@ -54,7 +60,8 @@ export class ConnectionTreeProvider {
54
60
  }
55
61
 
56
62
  async getChildren(element?: ConnectionNode): Promise<ConnectionNode[]> {
57
- if (!element) return this.getConnections();
63
+ if (!element) return this.getRootNodes();
64
+ if (element.type === "group") return element.children ?? [];
58
65
  if (element.type === "connection") return this.getTables(element);
59
66
  if (element.type === "table") return this.getColumns(element);
60
67
  return [];
@@ -62,13 +69,26 @@ export class ConnectionTreeProvider {
62
69
 
63
70
  getTreeItem(element: ConnectionNode): Record<string, unknown> {
64
71
  const isConn = element.type === "connection";
72
+ const isGroup = element.type === "group";
65
73
  const isTable = element.type === "table";
66
74
  const isCol = element.type === "column";
67
75
 
76
+ // Table description: row count
77
+ let description: string | undefined;
78
+ if (isCol) description = element.dataType;
79
+ else if (isTable && element.rowCount !== undefined) description = `${element.rowCount.toLocaleString()} rows`;
80
+
81
+ // Connection badge: type + readonly
82
+ let badge: string | undefined;
83
+ if (isConn) {
84
+ badge = element.connectionType === "postgres" ? "PG" : "DB";
85
+ if (element.connectionReadonly === 1) badge += " 🔒";
86
+ }
87
+
68
88
  return {
69
89
  id: element.id,
70
90
  label: element.name,
71
- description: isCol ? element.dataType : undefined,
91
+ description,
72
92
  collapsibleState: isCol ? "none" : "collapsed",
73
93
  contextValue: element.type,
74
94
  command: isTable ? "ppm-db.openViewer" : undefined,
@@ -76,13 +96,49 @@ export class ConnectionTreeProvider {
76
96
  ? [element.connectionId, element.connectionName ?? "Database", element.name, element.schemaName ?? "public"]
77
97
  : undefined,
78
98
  color: isConn ? (element.connectionColor ?? undefined) : undefined,
79
- badge: isConn ? (element.connectionType === "postgres" ? "PG" : "DB") : undefined,
99
+ badge,
80
100
  actions: isConn ? [
81
101
  { icon: "refresh", tooltip: "Refresh tables", command: "ppm-db.refreshConnection", commandArgs: [element.connectionId] },
82
- ] : undefined,
102
+ { icon: "edit", tooltip: "Edit connection", command: "ppm-db.editConnection", commandArgs: [element.connectionId] },
103
+ { icon: "trash", tooltip: "Delete connection", command: "ppm-db.deleteConnection", commandArgs: [element.connectionId, element.name] },
104
+ ] : isGroup ? [] : undefined,
83
105
  };
84
106
  }
85
107
 
108
+ /** Build root tree: group nodes wrapping connection nodes, or flat if no groups */
109
+ private async getRootNodes(): Promise<ConnectionNode[]> {
110
+ const connections = await this.getConnections();
111
+ // Group by group_name
112
+ const groups = new Map<string, ConnectionNode[]>();
113
+ for (const conn of connections) {
114
+ const key = conn.groupName ?? "__ungrouped__";
115
+ const list = groups.get(key) ?? [];
116
+ list.push(conn);
117
+ groups.set(key, list);
118
+ }
119
+ // If only one group (ungrouped), return connections flat
120
+ if (groups.size <= 1 && groups.has("__ungrouped__")) {
121
+ return connections;
122
+ }
123
+ // Build group nodes
124
+ const result: ConnectionNode[] = [];
125
+ const sortedKeys = Array.from(groups.keys()).sort((a, b) => {
126
+ if (a === "__ungrouped__") return 1;
127
+ if (b === "__ungrouped__") return -1;
128
+ return a.localeCompare(b);
129
+ });
130
+ for (const key of sortedKeys) {
131
+ const label = key === "__ungrouped__" ? "Ungrouped" : key;
132
+ result.push({
133
+ id: `group:${key}`,
134
+ name: label,
135
+ type: "group",
136
+ children: groups.get(key) ?? [],
137
+ });
138
+ }
139
+ return result;
140
+ }
141
+
86
142
  private async getConnections(): Promise<ConnectionNode[]> {
87
143
  try {
88
144
  const res = await fetch(`${this.baseUrl}/api/db/connections`);
@@ -95,6 +151,8 @@ export class ConnectionTreeProvider {
95
151
  connectionId: c.id,
96
152
  connectionType: c.type,
97
153
  connectionColor: c.color,
154
+ connectionReadonly: c.readonly,
155
+ groupName: c.group_name,
98
156
  }));
99
157
  } catch {
100
158
  return [];
@@ -114,6 +172,7 @@ export class ConnectionTreeProvider {
114
172
  connectionName: conn.name,
115
173
  connectionType: conn.connectionType,
116
174
  schemaName: t.schema,
175
+ rowCount: t.rowCount,
117
176
  }));
118
177
  } catch {
119
178
  return [];
@@ -96,6 +96,67 @@ export function activate(context: ExtensionContext, vscode: VscodeApi): void {
96
96
  }),
97
97
  );
98
98
 
99
+ context.subscriptions.push(
100
+ vscode.commands.registerCommand("ppm-db.editConnection", async (...args: unknown[]) => {
101
+ const connectionId = args[0] as number;
102
+ if (connectionId) await editConnection(vscode, treeProvider, connectionId);
103
+ }),
104
+ );
105
+
106
+ context.subscriptions.push(
107
+ vscode.commands.registerCommand("ppm-db.deleteConnection", async (...args: unknown[]) => {
108
+ const connectionId = args[0] as number;
109
+ const connectionName = (args[1] as string) ?? "this connection";
110
+ if (!connectionId) return;
111
+ const confirm = await vscode.window.showQuickPick(["Yes, delete", "Cancel"], {
112
+ placeHolder: `Delete "${connectionName}"?`,
113
+ });
114
+ if (confirm !== "Yes, delete") return;
115
+ try {
116
+ const res = await fetch(`${baseUrl}/api/db/connections/${connectionId}`, { method: "DELETE" });
117
+ const json = await res.json() as { ok: boolean; error?: string };
118
+ if (json.ok) {
119
+ await vscode.window.showInformationMessage(`Connection "${connectionName}" deleted`);
120
+ treeProvider.refresh();
121
+ } else {
122
+ await vscode.window.showErrorMessage(json.error ?? "Failed to delete connection");
123
+ }
124
+ } catch (e) {
125
+ await vscode.window.showErrorMessage(`Error: ${e instanceof Error ? e.message : String(e)}`);
126
+ }
127
+ }),
128
+ );
129
+
130
+ context.subscriptions.push(
131
+ vscode.commands.registerCommand("ppm-db.testConnection", async (...args: unknown[]) => {
132
+ const connectionId = args[0] as number;
133
+ if (!connectionId) return;
134
+ try {
135
+ const res = await fetch(`${baseUrl}/api/db/connections/${connectionId}/test`, { method: "POST" });
136
+ const json = await res.json() as { ok: boolean; data?: { ok: boolean; error?: string } };
137
+ if (json.ok && json.data?.ok) {
138
+ await vscode.window.showInformationMessage("Connection successful");
139
+ } else {
140
+ await vscode.window.showErrorMessage(`Connection failed: ${json.data?.error ?? "Unknown error"}`);
141
+ }
142
+ } catch (e) {
143
+ await vscode.window.showErrorMessage(`Error: ${e instanceof Error ? e.message : String(e)}`);
144
+ }
145
+ }),
146
+ );
147
+
148
+ context.subscriptions.push(
149
+ vscode.commands.registerCommand("ppm-db.exportConnections", async () => {
150
+ await exportConnections(vscode);
151
+ }),
152
+ );
153
+
154
+ context.subscriptions.push(
155
+ vscode.commands.registerCommand("ppm-db.importConnections", async () => {
156
+ await importConnections(vscode, treeProvider);
157
+ }),
158
+ );
159
+
99
160
  // --- Status Bar ---
100
161
  const statusItem = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Right, 10);
101
162
  statusItem.text = "DB";
@@ -297,6 +358,27 @@ function openQueryPanel(
297
358
  // Add Connection — collect info via QuickPick + InputBox, then POST to API
298
359
  // ---------------------------------------------------------------------------
299
360
 
361
+ /** Collect optional group/color/readonly via InputBox + QuickPick */
362
+ async function collectConnectionExtras(vscode: VscodeApi): Promise<{ groupName?: string; color?: string; readonly?: number } | null> {
363
+ const groupName = await vscode.window.showInputBox({ prompt: "Group name (optional)", placeHolder: "e.g. Production" });
364
+ if (groupName === undefined) return null; // cancelled
365
+
366
+ const COLOR_OPTIONS = ["None", "#ef4444", "#f97316", "#eab308", "#22c55e", "#06b6d4", "#3b82f6", "#8b5cf6", "#ec4899"];
367
+ const color = await vscode.window.showQuickPick(COLOR_OPTIONS, { placeHolder: "Pick a color (optional)" });
368
+ if (color === undefined) return null;
369
+
370
+ const readonlyChoice = await vscode.window.showQuickPick(["Yes (recommended)", "No"], {
371
+ placeHolder: "Readonly mode? (blocks write queries)",
372
+ });
373
+ if (readonlyChoice === undefined) return null;
374
+
375
+ return {
376
+ groupName: groupName || undefined,
377
+ color: color === "None" ? undefined : color,
378
+ readonly: readonlyChoice.startsWith("Yes") ? 1 : 0,
379
+ };
380
+ }
381
+
300
382
  async function addConnection(
301
383
  vscode: VscodeApi,
302
384
  treeProvider: ConnectionTreeProvider,
@@ -326,12 +408,16 @@ async function addConnection(
326
408
  connectionConfig = { type: "postgres", connectionString: connStr };
327
409
  }
328
410
 
329
- // 4. Create via API
411
+ // 4. Group, color, readonly
412
+ const extras = await collectConnectionExtras(vscode);
413
+ if (extras === null) return;
414
+
415
+ // 5. Create via API
330
416
  try {
331
417
  const res = await fetch(`${baseUrl}/api/db/connections`, {
332
418
  method: "POST",
333
419
  headers: { "Content-Type": "application/json" },
334
- body: JSON.stringify({ type, name, connectionConfig }),
420
+ body: JSON.stringify({ type, name, connectionConfig, ...extras }),
335
421
  });
336
422
  const json = await res.json() as { ok: boolean; error?: string };
337
423
  if (json.ok) {
@@ -344,3 +430,149 @@ async function addConnection(
344
430
  await vscode.window.showErrorMessage(`Error: ${e instanceof Error ? e.message : String(e)}`);
345
431
  }
346
432
  }
433
+
434
+ // ---------------------------------------------------------------------------
435
+ // Edit Connection — update name, group, color, readonly via QuickPick/InputBox
436
+ // ---------------------------------------------------------------------------
437
+
438
+ async function editConnection(
439
+ vscode: VscodeApi,
440
+ treeProvider: ConnectionTreeProvider,
441
+ connectionId: number,
442
+ ): Promise<void> {
443
+ // Fetch current connection data
444
+ let conn: { id: number; name: string; type: string; group_name?: string; color?: string; readonly?: number };
445
+ try {
446
+ const res = await fetch(`${baseUrl}/api/db/connections/${connectionId}`);
447
+ const json = await res.json() as { ok: boolean; data?: typeof conn };
448
+ if (!json.ok || !json.data) {
449
+ await vscode.window.showErrorMessage("Failed to load connection");
450
+ return;
451
+ }
452
+ conn = json.data;
453
+ } catch (e) {
454
+ await vscode.window.showErrorMessage(`Error: ${e instanceof Error ? e.message : String(e)}`);
455
+ return;
456
+ }
457
+
458
+ // Name
459
+ const name = await vscode.window.showInputBox({ prompt: "Connection name", value: conn.name });
460
+ if (name === undefined) return;
461
+
462
+ // Connection config (optional update)
463
+ let connectionConfig: Record<string, string> | undefined;
464
+ const updateConfig = await vscode.window.showQuickPick(["Keep current", "Update connection config"], {
465
+ placeHolder: "Connection config",
466
+ });
467
+ if (updateConfig === undefined) return;
468
+ if (updateConfig === "Update connection config") {
469
+ if (conn.type === "sqlite") {
470
+ const path = await vscode.window.showInputBox({ prompt: "SQLite file path", placeHolder: "/path/to/database.db" });
471
+ if (path === undefined) return;
472
+ if (path) connectionConfig = { type: "sqlite", path };
473
+ } else {
474
+ const connStr = await vscode.window.showInputBox({ prompt: "PostgreSQL connection string", placeHolder: "postgres://user:pass@host:5432/dbname" });
475
+ if (connStr === undefined) return;
476
+ if (connStr) connectionConfig = { type: "postgres", connectionString: connStr };
477
+ }
478
+ }
479
+
480
+ // Group, color, readonly
481
+ const groupName = await vscode.window.showInputBox({ prompt: "Group name", value: conn.group_name ?? "" });
482
+ if (groupName === undefined) return;
483
+
484
+ const COLOR_OPTIONS = ["None", "#ef4444", "#f97316", "#eab308", "#22c55e", "#06b6d4", "#3b82f6", "#8b5cf6", "#ec4899"];
485
+ const color = await vscode.window.showQuickPick(COLOR_OPTIONS, { placeHolder: `Current color: ${conn.color ?? "None"}` });
486
+ if (color === undefined) return;
487
+
488
+ const readonlyChoice = await vscode.window.showQuickPick(["Yes (recommended)", "No"], {
489
+ placeHolder: `Readonly? (current: ${conn.readonly === 1 ? "Yes" : "No"})`,
490
+ });
491
+ if (readonlyChoice === undefined) return;
492
+
493
+ // Update via API
494
+ try {
495
+ const body: Record<string, unknown> = {
496
+ name: name || conn.name,
497
+ groupName: groupName || null,
498
+ color: color === "None" ? null : color,
499
+ readonly: readonlyChoice.startsWith("Yes") ? 1 : 0,
500
+ };
501
+ if (connectionConfig) body.connectionConfig = connectionConfig;
502
+
503
+ const res = await fetch(`${baseUrl}/api/db/connections/${connectionId}`, {
504
+ method: "PUT",
505
+ headers: { "Content-Type": "application/json" },
506
+ body: JSON.stringify(body),
507
+ });
508
+ const json = await res.json() as { ok: boolean; error?: string };
509
+ if (json.ok) {
510
+ await vscode.window.showInformationMessage(`Connection "${name || conn.name}" updated`);
511
+ treeProvider.refresh();
512
+ } else {
513
+ await vscode.window.showErrorMessage(json.error ?? "Failed to update connection");
514
+ }
515
+ } catch (e) {
516
+ await vscode.window.showErrorMessage(`Error: ${e instanceof Error ? e.message : String(e)}`);
517
+ }
518
+ }
519
+
520
+ // ---------------------------------------------------------------------------
521
+ // Export Connections — copy JSON to clipboard via showInformationMessage
522
+ // ---------------------------------------------------------------------------
523
+
524
+ async function exportConnections(vscode: VscodeApi): Promise<void> {
525
+ try {
526
+ const res = await fetch(`${baseUrl}/api/db/connections/export`);
527
+ const json = await res.json() as { ok: boolean; data?: unknown };
528
+ if (!json.ok || !json.data) {
529
+ await vscode.window.showErrorMessage("Failed to export connections");
530
+ return;
531
+ }
532
+ // In extension context, we can't write to clipboard directly — show data as info
533
+ const data = json.data as { connections: unknown[] };
534
+ await vscode.window.showInformationMessage(`Exported ${data.connections.length} connection(s). Use built-in UI for file export.`);
535
+ } catch (e) {
536
+ await vscode.window.showErrorMessage(`Error: ${e instanceof Error ? e.message : String(e)}`);
537
+ }
538
+ }
539
+
540
+ // ---------------------------------------------------------------------------
541
+ // Import Connections — paste JSON via InputBox
542
+ // ---------------------------------------------------------------------------
543
+
544
+ async function importConnections(
545
+ vscode: VscodeApi,
546
+ treeProvider: ConnectionTreeProvider,
547
+ ): Promise<void> {
548
+ const jsonStr = await vscode.window.showInputBox({
549
+ prompt: "Paste connections JSON (from export)",
550
+ placeHolder: '{"connections": [...]}',
551
+ });
552
+ if (!jsonStr) return;
553
+
554
+ try {
555
+ const data = JSON.parse(jsonStr);
556
+ const conns = data.connections ?? data;
557
+ if (!Array.isArray(conns)) {
558
+ await vscode.window.showErrorMessage("Invalid format: expected connections array");
559
+ return;
560
+ }
561
+ const res = await fetch(`${baseUrl}/api/db/connections/import`, {
562
+ method: "POST",
563
+ headers: { "Content-Type": "application/json" },
564
+ body: JSON.stringify({ connections: conns }),
565
+ });
566
+ const json = await res.json() as { ok: boolean; data?: { imported: number; skipped: number; errors: string[] } };
567
+ if (json.ok && json.data) {
568
+ let msg = `Imported ${json.data.imported} connection(s)`;
569
+ if (json.data.skipped > 0) msg += `, ${json.data.skipped} skipped`;
570
+ await vscode.window.showInformationMessage(msg);
571
+ treeProvider.refresh();
572
+ } else {
573
+ await vscode.window.showErrorMessage("Import failed");
574
+ }
575
+ } catch (e) {
576
+ await vscode.window.showErrorMessage(`Error: ${e instanceof Error ? e.message : String(e)}`);
577
+ }
578
+ }
@@ -15,25 +15,38 @@ export function getQueryPanelHtml(connectionName: string, tableName?: string): s
15
15
  <head>
16
16
  <meta charset="utf-8">
17
17
  <style>
18
- * { box-sizing: border-box; margin: 0; padding: 0; }
19
- body { font-family: -apple-system, BlinkMacSystemFont, sans-serif; background: #1e1e2e; color: #cdd6f4; padding: 12px; font-size: 13px; }
18
+ *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
19
+ :root {
20
+ --bg: #ffffff; --surface: #f4f4f5; --text: #09090b; --subtext: #71717a; --subtle: #a1a1aa;
21
+ --border: #e4e4e7; --border2: #d4d4d8; --blue: #3b82f6; --red: #ef4444; --green: #22c55e;
22
+ --surface-hover: #f4f4f5;
23
+ }
24
+ @media (prefers-color-scheme: dark) {
25
+ :root {
26
+ --bg: #09090b; --surface: #18181b; --text: #fafafa; --subtext: #a1a1aa; --subtle: #52525b;
27
+ --border: #27272a; --border2: #3f3f46; --blue: #3b82f6; --red: #ef4444; --green: #22c55e;
28
+ --surface-hover: #27272a;
29
+ }
30
+ }
31
+ body { font-family: -apple-system, BlinkMacSystemFont, sans-serif; background: var(--bg); color: var(--text); padding: 12px; font-size: 13px; }
20
32
  .header { display: flex; align-items: center; gap: 8px; margin-bottom: 8px; }
21
33
  .header h3 { font-size: 14px; font-weight: 600; }
22
- .header .badge { background: #313244; padding: 2px 8px; border-radius: 4px; font-size: 11px; color: #a6adc8; }
23
- textarea { width: 100%; height: 80px; background: #313244; border: 1px solid #45475a; border-radius: 6px; color: #cdd6f4; padding: 8px; font-family: 'SF Mono', 'Fira Code', monospace; font-size: 12px; resize: vertical; }
24
- textarea:focus { outline: none; border-color: #89b4fa; }
34
+ .header .badge { background: var(--surface); padding: 2px 8px; border-radius: 4px; font-size: 11px; color: var(--subtext); }
35
+ textarea { width: 100%; height: 80px; background: var(--surface); border: 1px solid var(--border2); border-radius: 6px; color: var(--text); padding: 8px; font-family: 'SF Mono', 'Fira Code', monospace; font-size: 12px; resize: vertical; }
36
+ textarea:focus { outline: none; border-color: var(--blue); }
25
37
  .actions { display: flex; gap: 8px; margin: 8px 0; }
26
- button { background: #89b4fa; color: #1e1e2e; border: none; padding: 6px 16px; border-radius: 4px; font-size: 12px; font-weight: 600; cursor: pointer; }
27
- button:hover { background: #74c7ec; }
28
- button.secondary { background: #313244; color: #cdd6f4; }
29
- .status { font-size: 11px; color: #a6adc8; margin: 4px 0; }
30
- .error { color: #f38ba8; background: #45475a; padding: 8px; border-radius: 4px; margin: 8px 0; font-size: 12px; }
38
+ button { background: var(--blue); color: #fff; border: none; padding: 6px 16px; border-radius: 4px; font-size: 12px; font-weight: 600; cursor: pointer; }
39
+ button:hover { opacity: 0.9; }
40
+ button.secondary { background: var(--surface); color: var(--text); }
41
+ .status { font-size: 11px; color: var(--subtext); margin: 4px 0; }
42
+ .error { color: var(--red); background: var(--surface); padding: 8px; border-radius: 4px; margin: 8px 0; font-size: 12px; }
31
43
  table { width: 100%; border-collapse: collapse; margin-top: 8px; font-size: 12px; }
32
- th { background: #313244; text-align: left; padding: 6px 8px; border-bottom: 1px solid #45475a; font-weight: 600; position: sticky; top: 0; }
33
- td { padding: 4px 8px; border-bottom: 1px solid #313244; max-width: 300px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
34
- tr:hover td { background: #313244; }
35
- .results { max-height: 60vh; overflow: auto; border: 1px solid #45475a; border-radius: 6px; }
36
- .empty { text-align: center; padding: 24px; color: #6c7086; }
44
+ th { background: var(--surface); text-align: left; padding: 6px 8px; border-bottom: 1px solid var(--border2); font-weight: 600; position: sticky; top: 0; color: var(--subtext); }
45
+ td { padding: 4px 8px; border-bottom: 1px solid var(--border); max-width: 300px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
46
+ tr:hover td { background: var(--surface-hover); }
47
+ .results { max-height: 60vh; overflow: auto; border: 1px solid var(--border2); border-radius: 6px; }
48
+ .empty { text-align: center; padding: 24px; color: var(--subtle); }
49
+ .null-val { color: var(--subtle); font-style: italic; }
37
50
  </style>
38
51
  </head>
39
52
  <body>
@@ -101,7 +114,7 @@ export function getQueryPanelHtml(connectionName: string, tableName?: string): s
101
114
  html += '<tr>';
102
115
  cols.forEach(c => {
103
116
  const v = row[c];
104
- html += '<td>' + (v === null ? '<span style="color:#6c7086">NULL</span>' : esc(v)) + '</td>';
117
+ html += v === null ? '<td class="null-val">NULL</td>' : '<td>' + esc(v) + '</td>';
105
118
  });
106
119
  html += '</tr>';
107
120
  });