diff --git a/app/srv/api/prod.ts b/app/srv/api/prod.ts index 01341fbb..b0086ea6 100644 --- a/app/srv/api/prod.ts +++ b/app/srv/api/prod.ts @@ -58,20 +58,22 @@ export const _ = { } } - const res = JSON.stringify(await parseTypeDef(path)); - await Bun.write( - dir.data( - `/code/${site_id}/site/type_def.${file.lastModified}.json` - ), - res - ); + try { + const res = JSON.stringify(await parseTypeDef(path)); + await Bun.write( + dir.data( + `/code/${site_id}/site/type_def.${file.lastModified}.json` + ), + res + ); - return new Response(Bun.gzipSync(res), { - headers: { - "content-type": "application/json", - "content-encoding": "gzip", - }, - }); + return new Response(Bun.gzipSync(res), { + headers: { + "content-type": "application/json", + "content-encoding": "gzip", + }, + }); + } catch (e) {} } return new Response("{}", { headers: { "content-type": "application/json" }, @@ -79,17 +81,25 @@ export const _ = { } case "typings.d.ts": { const build_path = dir.data(`/code/${site_id}/site/typings.d.ts`); - const file = Bun.file(build_path); + let file = Bun.file(build_path); if (!(await file.exists())) { const root = `/code/${site_id}/site/src`; await initFrontEnd(root, site_id); + file = Bun.file(build_path); } - const body = Bun.gzipSync(await file.arrayBuffer()); - return new Response(body, { - headers: { "content-type": file.type, "content-encoding": "gzip" }, - }); + if (await file.exists()) { + const body = Bun.gzipSync(await file.arrayBuffer()); + + return new Response(body, { + headers: { + "content-type": file.type, + "content-encoding": "gzip", + }, + }); + } + return new Response("", { status: 403 }); } case "code": { const arr = pathname.split("/").slice(2); diff --git a/app/srv/core/main.js b/app/srv/core/main.js index 46d8faef..ec079b7d 100644 --- a/app/srv/core/main.js +++ b/app/srv/core/main.js @@ -93,7 +93,7 @@ Error generating stack: `+o.message+` ${cg(e,t.mode)} ${Q0(e,t.mode)} ${pg(e,t.mode)} - `,(t?.hover||t?.active)&&dg({item:e,hover:t?.hover,active:t?.active}),K0(e,t.mode)])}catch(n){console.log(n)}return cx([])};var gg=A(Z()),yo=(e,t,n,r,i)=>{let o=t.item,l;o.component?.style&&(l={style:o.component.style,className:pf(o.component.style,{mode:e.mode})});let s={className:pf(o,{mode:e.mode}),inherit:l},c={},a=t.item.childs,u;return t.item.type==="text"?(u=null,s.dangerouslySetInnerHTML={__html:t.item.html||""}):u=Array.isArray(a)&&a?.map(d=>{if(!d)return null;let{id:f}=d,p=n?e.layout?.meta[f]:e.meta[f];return p?(0,gg.jsx)(Xt,{meta:p,is_layout:n,passprop:{...r},depth:i+1},f):null}),s.children=u,t.item.adv?.html?(delete s.children,s.dangerouslySetInnerHTML={__html:t.item.adv?.html}):t.item.adv?.js&&!t.item.adv.js.includes("children")&&(delete s.children,delete s.dangerouslySetInnerHTML),{props:s,text_props:c}};var xy=A(ge());var _y=A(xf());var qr={NORMAL:0,WILDCARD:1,PLACEHOLDER:2};function Ms(e={}){let t={options:e,rootNode:fy(),staticRoutesMap:{}},n=r=>e.strictTrailingSlash?r:r.replace(/\/$/,"")||"/";if(e.routes)for(let r in e.routes)cy(t,n(r),e.routes[r]);return{ctx:t,lookup:r=>zS(t,n(r)),insert:(r,i)=>cy(t,n(r),i),remove:r=>DS(t,n(r))}}function zS(e,t){let n=e.staticRoutesMap[t];if(n)return n.data;let r=t.split("/"),i={},o=!1,l=null,s=e.rootNode,c=null;for(let a=0;a{},db:null,api:null,init_local_effect:{}},page:{history:{id:"",show:!1},root_id:"root",cur:FS,doc:null,list:{},building:!1,meta:{},entry:[],tree:[],render:()=>{}},comp:{doc:null,item:null,loaded:{},list:{},group:{}},code:{},global_prop:[],ui:{comp_editable:localStorage.getItem("prasi-comp-editable")==="yes",zoom:localStorage.zoom||"100%",side:{prop:!0},layout:{left:parseInt(localStorage.getItem("prasi-layout-left")||"250"),right:parseInt(localStorage.getItem("prasi-layout-right")||"250")},prevent_indent_hook:!1,syncing:!1,tree:{item_loading:[],search:"",search_ref:null,search_mode:{Name:!0,JS:!1,HTML:!1,CSS:!1},rename_id:"",open:{}},popup:{file:{enabled:!1,open:!1,picker:{value:"",on_pick:!1,multi:!1},path:"/",expanded:JSON.parse(localStorage.getItem("panel-file-expanded")||"{}"),entry:{},selected:new Set,action:null,file_renaming:"",file_ctx_menu_event:null,tree:[],tree_renaming:"",tree_ctx_path:"",tree_ctx_menu_event:null,preview:!0,upload:{started:!1,progress:{}}},code:{init:!1,open:!1,name:"site",log:"",loading:!1,startup_status:"init",error:!1,show_log:!1,list:{}},page:{open:null,form:null},script:{open:!1,mode:"js",lastMode:"js",type:"item",prop_kind:"",prop_name:"",on_close:()=>{},typings:{status:"ok",err_msg:""},wb_render:()=>{}},site:null,site_form:null,comp:{preview_id:"",open:null,import:!1},comp_group:null,api:{open:!1}}}};var js=e=>({isMobile:e.mode==="mobile",isDesktop:e.mode==="desktop",isEditor:location.pathname.startsWith("/ed/")});var sr=A(ge());var at=window;var US=tf("prasi-cache","prasi-cache-store"),Eo={timeout:null,store:US},Fs=(e,t)=>{let n=[...dy(t,"navigate(",")"),...dy(t,"href = ",";")],r=e.page.cur.id;e.page.navs[r]||(e.page.navs[r]=new Set);for(let i of n)e.page.navs[r].add(i);clearTimeout(Eo.timeout),Eo.timeout=setTimeout(()=>{e.on_nav_loaded&&e.on_nav_loaded({urls:Array.from(e.page.navs[r])})},100)},dy=(e,t,n)=>{let r=0,i=0,o=[];for(;;){let l=e.indexOf(t,r);if(i=r,l>=0){let s=e[l+t.length];if(s==='"'||s==="'"||s==="`"){let c=e.indexOf(`${s}${n}`,l+t.length+1),a=e.substring(l+t.length+1,c);r=c+2+n.length,o.push(a)}}if(i===r)break}return o};var lr=A(ge());var my=A(df()),or=A(ge());var kf=A(Z()),hy=(e,t,n,r)=>i=>{let[o,l]=(0,or.useState)({}),s=i.internal_key;n.item.script||(n.item.script={});let c=n.item.script;s&&(n.item.script_keyed||(n.item.script_keyed={}),n.item.script_keyed[s]||(n.item.script_keyed[s]={}),c=n.item.script_keyed[s]),c.passprop||(c.passprop={});let a={};if(c.passprop){let f=!1;for(let[p,g]of Object.entries(i))["children","key"].includes(p)||(f=!0,a[p]=g,c.passprop[p]={end:0,start:0,value:g})}let u={...r,...a};if(!Array.isArray(i.children)&&(0,or.isValidElement)(i.children)&&typeof i.children=="object"){let f=(0,my.default)(i.children,"props.meta.item.component.props.child.content.childs");if(Array.isArray(f)){let p=!0;for(let g of f)!(0,or.isValidElement)(g)&&typeof g=="object"||(p=!1);if(p)return f.map(g=>{let _=e.meta[g.id];if(_||(e.meta[g.id]={item:g},_=e.meta[g.id]),_){if(Object.keys(_.item).length<=3&&_.mitem){let x={..._.item},h=_.mitem.toJSON();_.item={...h,...x}}return(0,kf.jsx)(Xt,{is_layout:t,meta:_,passprop:u,parent_key:i.internal_key},g.id)}return null})}}if(!Array.isArray(i.children)&&!(0,or.isValidElement)(i.children)&&typeof i.children=="object"){let f=i.children.id;if(f){let p=e.meta[f];if(!p){e.meta[f]={item:i.children},p=e.meta[f];let g=p.item.component?.id;g&&e.comp.load(g).then(_=>{if(_){for(let[h,m]of Object.entries(_)){let y=p.item;y[h]||(y[h]=m)}let x=p.item.component?.props;for(let[h,m]of Object.entries(_.component?.props||{}))x[h]||(x[h]=m);l({})}})}return(0,kf.jsx)(Xt,{is_layout:t,meta:p,passprop:u,parent_key:i.internal_key})}}return Sf(i,u,s?{parent_key:s}:void 0)},Sf=(e,t,n)=>{let r={};if(Array.isArray(e)?r.children=e:r=e,Array.isArray(r.children)){let i=[];for(let o of r.children)i.push(py(o,r,t,n));return i}return py(r.children,r,t,n)},py=(e,t,n,r)=>{if((0,or.isValidElement)(e)){let i={...t};return delete i.children,{...e,props:{...e.props,...r,passprop:{...n,...i}}}}return e};var Jr={},gy=(e,t,n)=>r=>{let i=["localhost","prasi.avolut.com"].includes(location.hostname)&&location.pathname.startsWith("/ed/"),o=n.item.id,{children:l,parent_key:s}=r,c=e.script?.init_local_effect,a=t?e.layout?.meta:e.meta,u=(0,lr.useRef)(Jr[o]?Jr[o]:r.value),[d,f]=(0,lr.useState)({}),p=u.current;return p.render=()=>{window.prasiContext.render?window.prasiContext.render():f({})},Us(e,n,n.script?.scope,s),r.hook&&r.hook(p),(0,lr.useEffect)(()=>{if(n.parent?.instance_id&&a){let _=a[n.parent?.instance_id];if(_&&_.instances){for(let[x,h]of Object.entries(_.instances[n.parent.instance_id]))if(h===n.item.id){o=x;break}}}return!c[o]&&(typeof c=="object"&&(c[o]=!0),(async()=>r.effect&&(await r.effect(p),i&&(Jr[o]=p)))()),()=>{}},[...r.deps||[],location.pathname]),(0,lr.useEffect)(()=>{i&&Jr[o]===null&&(async()=>r.effect&&(await r.effect(p),i&&(Jr[o]=p)))()},[Jr[o]]),Sf(l,{...n.script?.scope,[r.name]:p})};var Ef=A(Z()),yy=(e,t,n,r,i,o)=>{let l=yo(e,t,n,r,i);e.visit&&e.visit(t,l),t.script?t.script.scope=r:t.script={scope:r,result:null,Local:gy(e,n,t),PassProp:hy(e,n,t,r)};let s=t.script,c=window.exports,a={useEffect:sr.useEffect,children:l.props.children,props:l.props,Local:s?.Local,db:e.site.db,api:e.site.api,PassProp:s?.PassProp,ErrorBox:Nt,newElement:()=>{},render:f=>{let p=f;if((0,sr.isValidElement)(f)&&f.props.children){let g=x=>{let h=!1,m=[];if((0,sr.isValidElement)(x)){if(x.type===t.script?.PassProp)return{should_replace:!0,el:{...x,props:{...x.props,internal_key:x.key}}};if(Array.isArray(x.props?.children))for(let y of x.props?.children)if(Array.isArray(y)){let v=[],S=!1;for(let R of y){let k=g(R);k.should_replace?(v.push(k.el),S=!0):v.push(R)}S?(h=!0,m.push(v)):m.push(y)}else typeof y=="object"?y.type===t.script?.PassProp&&(h=!0,m.push({...y,props:{...y.props,internal_key:y.props.key}})):m.push(y)}return{should_replace:h,el:{...x,props:{...x.props,children:m}}}},_=g(f);_.should_replace&&(p=_.el)}s&&(s.result=(0,Ef.jsx)(sr.Suspense,{children:p}))},params,...js(e),...c,...r,_meta:e.meta,_item:t.item,_syncm:typeof syncronize<"u"?syncronize:void 0};if(typeof r=="object"){for(let[f,p]of Object.entries(r))if(typeof p=="object"&&p&&p._jsx){let g=p;a[f]=(0,Ef.jsx)(BS,{fn:g.fn,passprop:{...r},meta:t})}}!at.isEditor&&t.item.adv?.js&&Fs(e,t.item.adv.js);let u=t.item.adv?.jsBuilt||"",d=Rf(u,bf)||"";try{new Function(...Object.keys(a),"___js",`// ${t.item.name}: ${t.item.id} + `,(t?.hover||t?.active)&&dg({item:e,hover:t?.hover,active:t?.active}),K0(e,t.mode)])}catch(n){console.log(n)}return cx([])};var gg=A(Z()),yo=(e,t,n,r,i)=>{let o=t.item,l;o.component?.style&&(l={style:o.component.style,className:pf(o.component.style,{mode:e.mode})});let s={className:pf(o,{mode:e.mode}),inherit:l},c={},a=t.item.childs,u;return t.item.type==="text"?(u=null,s.dangerouslySetInnerHTML={__html:t.item.html||""}):u=Array.isArray(a)&&a?.map(d=>{if(!d)return null;let{id:f}=d,p=n?e.layout?.meta[f]:e.meta[f];return p?(0,gg.jsx)(Xt,{meta:p,is_layout:n,passprop:{...r},depth:i+1},f):null}),s.children=u,t.item.adv?.html?(delete s.children,s.dangerouslySetInnerHTML={__html:t.item.adv?.html}):t.item.adv?.js&&!t.item.adv.js.includes("children")&&(delete s.children,delete s.dangerouslySetInnerHTML),{props:s,text_props:c}};var xy=A(ge());var _y=A(xf());var qr={NORMAL:0,WILDCARD:1,PLACEHOLDER:2};function Ms(e={}){let t={options:e,rootNode:fy(),staticRoutesMap:{}},n=r=>e.strictTrailingSlash?r:r.replace(/\/$/,"")||"/";if(e.routes)for(let r in e.routes)cy(t,n(r),e.routes[r]);return{ctx:t,lookup:r=>zS(t,n(r)),insert:(r,i)=>cy(t,n(r),i),remove:r=>DS(t,n(r))}}function zS(e,t){let n=e.staticRoutesMap[t];if(n)return n.data;let r=t.split("/"),i={},o=!1,l=null,s=e.rootNode,c=null;for(let a=0;a{},db:null,api:null,init_local_effect:{}},page:{history:{id:"",show:!1},root_id:"root",cur:FS,doc:null,list:{},building:!1,meta:{},entry:[],tree:[],render:()=>{}},comp:{doc:null,item:null,loaded:{},list:{},group:{}},code:{},global_prop:[],ui:{monaco:null,comp_editable:localStorage.getItem("prasi-comp-editable")==="yes",zoom:localStorage.zoom||"100%",side:{prop:!0},layout:{left:parseInt(localStorage.getItem("prasi-layout-left")||"250"),right:parseInt(localStorage.getItem("prasi-layout-right")||"250")},prevent_indent_hook:!1,syncing:!1,tree:{item_loading:[],search:"",search_ref:null,search_mode:{Name:!0,JS:!1,HTML:!1,CSS:!1},rename_id:"",open:{}},popup:{file:{enabled:!1,open:!1,picker:{value:"",on_pick:!1,multi:!1},path:"/",expanded:JSON.parse(localStorage.getItem("panel-file-expanded")||"{}"),entry:{},selected:new Set,action:null,file_renaming:"",file_ctx_menu_event:null,tree:[],tree_renaming:"",tree_ctx_path:"",tree_ctx_menu_event:null,preview:!0,upload:{started:!1,progress:{}}},code:{init:!1,open:!1,name:"site",log:"",loading:!1,startup_status:"init",error:!1,show_log:!1,list:{}},page:{open:null,form:null},script:{open:!1,mode:"js",lastMode:"js",type:"item",prop_kind:"",prop_name:"",on_close:()=>{},typings:{status:"ok",err_msg:""},wb_render:()=>{}},site:null,site_form:null,comp:{preview_id:"",open:null,import:!1},comp_group:null,api:{open:!1}}}};var js=e=>({isMobile:e.mode==="mobile",isDesktop:e.mode==="desktop",isEditor:location.pathname.startsWith("/ed/")});var sr=A(ge());var at=window;var US=tf("prasi-cache","prasi-cache-store"),Eo={timeout:null,store:US},Fs=(e,t)=>{let n=[...dy(t,"navigate(",")"),...dy(t,"href = ",";")],r=e.page.cur.id;e.page.navs[r]||(e.page.navs[r]=new Set);for(let i of n)e.page.navs[r].add(i);clearTimeout(Eo.timeout),Eo.timeout=setTimeout(()=>{e.on_nav_loaded&&e.on_nav_loaded({urls:Array.from(e.page.navs[r])})},100)},dy=(e,t,n)=>{let r=0,i=0,o=[];for(;;){let l=e.indexOf(t,r);if(i=r,l>=0){let s=e[l+t.length];if(s==='"'||s==="'"||s==="`"){let c=e.indexOf(`${s}${n}`,l+t.length+1),a=e.substring(l+t.length+1,c);r=c+2+n.length,o.push(a)}}if(i===r)break}return o};var lr=A(ge());var my=A(df()),or=A(ge());var kf=A(Z()),hy=(e,t,n,r)=>i=>{let[o,l]=(0,or.useState)({}),s=i.internal_key;n.item.script||(n.item.script={});let c=n.item.script;s&&(n.item.script_keyed||(n.item.script_keyed={}),n.item.script_keyed[s]||(n.item.script_keyed[s]={}),c=n.item.script_keyed[s]),c.passprop||(c.passprop={});let a={};if(c.passprop){let f=!1;for(let[p,g]of Object.entries(i))["children","key"].includes(p)||(f=!0,a[p]=g,c.passprop[p]={end:0,start:0,value:g})}let u={...r,...a};if(!Array.isArray(i.children)&&(0,or.isValidElement)(i.children)&&typeof i.children=="object"){let f=(0,my.default)(i.children,"props.meta.item.component.props.child.content.childs");if(Array.isArray(f)){let p=!0;for(let g of f)!(0,or.isValidElement)(g)&&typeof g=="object"||(p=!1);if(p)return f.map(g=>{let _=e.meta[g.id];if(_||(e.meta[g.id]={item:g},_=e.meta[g.id]),_){if(Object.keys(_.item).length<=3&&_.mitem){let x={..._.item},h=_.mitem.toJSON();_.item={...h,...x}}return(0,kf.jsx)(Xt,{is_layout:t,meta:_,passprop:u,parent_key:i.internal_key},g.id)}return null})}}if(!Array.isArray(i.children)&&!(0,or.isValidElement)(i.children)&&typeof i.children=="object"){let f=i.children.id;if(f){let p=e.meta[f];if(!p){e.meta[f]={item:i.children},p=e.meta[f];let g=p.item.component?.id;g&&e.comp.load(g).then(_=>{if(_){for(let[h,m]of Object.entries(_)){let y=p.item;y[h]||(y[h]=m)}let x=p.item.component?.props;for(let[h,m]of Object.entries(_.component?.props||{}))x[h]||(x[h]=m);l({})}})}return(0,kf.jsx)(Xt,{is_layout:t,meta:p,passprop:u,parent_key:i.internal_key})}}return Sf(i,u,s?{parent_key:s}:void 0)},Sf=(e,t,n)=>{let r={};if(Array.isArray(e)?r.children=e:r=e,Array.isArray(r.children)){let i=[];for(let o of r.children)i.push(py(o,r,t,n));return i}return py(r.children,r,t,n)},py=(e,t,n,r)=>{if((0,or.isValidElement)(e)){let i={...t};return delete i.children,{...e,props:{...e.props,...r,passprop:{...n,...i}}}}return e};var Jr={},gy=(e,t,n)=>r=>{let i=["localhost","prasi.avolut.com"].includes(location.hostname)&&location.pathname.startsWith("/ed/"),o=n.item.id,{children:l,parent_key:s}=r,c=e.script?.init_local_effect,a=t?e.layout?.meta:e.meta,u=(0,lr.useRef)(Jr[o]?Jr[o]:r.value),[d,f]=(0,lr.useState)({}),p=u.current;return p.render=()=>{window.prasiContext.render?window.prasiContext.render():f({})},Us(e,n,n.script?.scope,s),r.hook&&r.hook(p),(0,lr.useEffect)(()=>{if(n.parent?.instance_id&&a){let _=a[n.parent?.instance_id];if(_&&_.instances){for(let[x,h]of Object.entries(_.instances[n.parent.instance_id]))if(h===n.item.id){o=x;break}}}return!c[o]&&(typeof c=="object"&&(c[o]=!0),(async()=>r.effect&&(await r.effect(p),i&&(Jr[o]=p)))()),()=>{}},[...r.deps||[],location.pathname]),(0,lr.useEffect)(()=>{i&&Jr[o]===null&&(async()=>r.effect&&(await r.effect(p),i&&(Jr[o]=p)))()},[Jr[o]]),Sf(l,{...n.script?.scope,[r.name]:p})};var Ef=A(Z()),yy=(e,t,n,r,i,o)=>{let l=yo(e,t,n,r,i);e.visit&&e.visit(t,l),t.script?t.script.scope=r:t.script={scope:r,result:null,Local:gy(e,n,t),PassProp:hy(e,n,t,r)};let s=t.script,c=window.exports,a={useEffect:sr.useEffect,children:l.props.children,props:l.props,Local:s?.Local,db:e.site.db,api:e.site.api,PassProp:s?.PassProp,ErrorBox:Nt,newElement:()=>{},render:f=>{let p=f;if((0,sr.isValidElement)(f)&&f.props.children){let g=x=>{let h=!1,m=[];if((0,sr.isValidElement)(x)){if(x.type===t.script?.PassProp)return{should_replace:!0,el:{...x,props:{...x.props,internal_key:x.key}}};if(Array.isArray(x.props?.children))for(let y of x.props?.children)if(Array.isArray(y)){let v=[],S=!1;for(let R of y){let k=g(R);k.should_replace?(v.push(k.el),S=!0):v.push(R)}S?(h=!0,m.push(v)):m.push(y)}else typeof y=="object"?y.type===t.script?.PassProp&&(h=!0,m.push({...y,props:{...y.props,internal_key:y.props.key}})):m.push(y)}return{should_replace:h,el:{...x,props:{...x.props,children:m}}}},_=g(f);_.should_replace&&(p=_.el)}s&&(s.result=(0,Ef.jsx)(sr.Suspense,{children:p}))},params,...js(e),...c,...r,_meta:e.meta,_item:t.item,_syncm:typeof syncronize<"u"?syncronize:void 0};if(typeof r=="object"){for(let[f,p]of Object.entries(r))if(typeof p=="object"&&p&&p._jsx){let g=p;a[f]=(0,Ef.jsx)(BS,{fn:g.fn,passprop:{...r},meta:t})}}!at.isEditor&&t.item.adv?.js&&Fs(e,t.item.adv.js);let u=t.item.adv?.jsBuilt||"",d=Rf(u,bf)||"";try{new Function(...Object.keys(a),"___js",`// ${t.item.name}: ${t.item.id} try { ${d} } catch(e) { diff --git a/app/srv/util/parse-type-def.ts b/app/srv/util/parse-type-def.ts index 631cc8a4..58b372c2 100644 --- a/app/srv/util/parse-type-def.ts +++ b/app/srv/util/parse-type-def.ts @@ -10,7 +10,6 @@ export const parseTypeDef = async (path: string) => { const ast = await parseFile(path, { syntax: "typescript" }); const exports = {} as Record; - visit(ast, { visitWithPath: { visitDecl(node, path) { diff --git a/app/srv/ws/sync/code/parts/init/frontend.ts b/app/srv/ws/sync/code/parts/init/frontend.ts index ff58ec94..9505d4bf 100644 --- a/app/srv/ws/sync/code/parts/init/frontend.ts +++ b/app/srv/ws/sync/code/parts/init/frontend.ts @@ -6,6 +6,10 @@ import { cleanPlugin } from "esbuild-clean-plugin"; import isEqual from "lodash.isequal"; import { appendFile } from "node:fs/promises"; import { code } from "../../code"; +import { user } from "../../../entity/user"; +import { conns } from "../../../entity/conn"; +import { SyncType } from "../../../type"; +import { sendWS } from "../../../sync-handler"; const decoder = new TextDecoder(); export const initFrontEnd = async ( @@ -74,6 +78,22 @@ export const initFrontEnd = async ( await installDeps(root, res, id_site); } else { await codeError(id_site, ""); + + const client_ids = new Set(); + user.active.findAll({ site_id: id_site }).forEach((e) => { + client_ids.add(e.client_id); + }); + + const now = Date.now(); + client_ids.forEach((client_id) => { + const ws = conns.get(client_id)?.ws; + if (ws) + sendWS(ws, { + type: SyncType.Event, + event: "code_changes", + data: { ts: now, mode: "frontend" }, + }); + }); } }); } catch (e) { diff --git a/app/srv/ws/sync/code/parts/init/typings.ts b/app/srv/ws/sync/code/parts/init/typings.ts index 556c5186..67487add 100644 --- a/app/srv/ws/sync/code/parts/init/typings.ts +++ b/app/srv/ws/sync/code/parts/init/typings.ts @@ -4,6 +4,10 @@ import { watch } from "fs"; import { Glob } from "bun"; import { removeAsync } from "fs-jetpack"; import { parseTypeDef } from "../../../../../util/parse-type-def"; +import { user } from "../../../entity/user"; +import { conns } from "../../../entity/conn"; +import { sendWS } from "../../../sync-handler"; +import { SyncType } from "../../../type"; export const initTypings = async ( root: string, id_site: string, @@ -21,9 +25,15 @@ export const initTypings = async ( } try { + const typings_path = dir.data(`/code/${id_site}/site/typings.d.ts`); + const typings_file = Bun.file(typings_path); + + if (!(await typings_file.exists())) { + return false; + } code.internal.typings[id_site] = { timeout: Date.now(), - watch: watch(dir.data(`/code/${id_site}/site/typings.d.ts`)), + watch: watch(typings_path), spawn: Bun.spawn({ cmd: [ ...`tsc --watch --moduleResolution node --emitDeclarationOnly --outFile ../typings.d.ts --declaration --noEmit false`.split( @@ -69,6 +79,22 @@ export const initTypings = async ( dir.data(`/code/${id_site}/site/type_def.${file.lastModified}.json`), res ); + + const client_ids = new Set(); + user.active.findAll({ site_id: id_site }).forEach((e) => { + client_ids.add(e.client_id); + }); + + const now = Date.now(); + client_ids.forEach((client_id) => { + const ws = conns.get(client_id)?.ws; + if (ws) + sendWS(ws, { + type: SyncType.Event, + event: "code_changes", + data: { ts: now, mode: "typings" }, + }); + }); }, 180); }); } catch (e) { diff --git a/app/srv/ws/sync/code/templates/tsconfig_json b/app/srv/ws/sync/code/templates/tsconfig_json index 21a4c9cc..f958ab45 100644 --- a/app/srv/ws/sync/code/templates/tsconfig_json +++ b/app/srv/ws/sync/code/templates/tsconfig_json @@ -1,18 +1,28 @@ { "compilerOptions": { "paths": { - "@/*": ["./lib/*"], - "app/*": ["./app/*"], - "server/*": ["./server/*"] + "lib/*": [ + "./lib/*" + ], + "app/*": [ + "./app/*" + ], + "server/*": [ + "./server/*" + ], + "@/*": [ + "./lib/*" + ] }, - "lib": ["ESNext", "DOM"], + "lib": [ + "ESNext", + "DOM" + ], "module": "esnext", "target": "esnext", - "moduleResolution": "bundler", "moduleDetection": "force", - "declaration": true, - "outFile": "types.d.ts", - "emitDeclarationOnly": true, + "moduleResolution": "Node", + "noEmit": true, "composite": true, "strict": true, "downlevelIteration": true, @@ -21,6 +31,9 @@ "allowSyntheticDefaultImports": true, "forceConsistentCasingInFileNames": true, "allowJs": true, - "typeRoots": ["./node_modules/@types", "./lib/types"] + "typeRoots": [ + "./node_modules/@types", + "./lib/types" + ] } -} +} \ No newline at end of file diff --git a/app/web/src/nova/ed/logic/code-loader.ts b/app/web/src/nova/ed/logic/code-loader.ts index 64d3a6b0..9057bf67 100644 --- a/app/web/src/nova/ed/logic/code-loader.ts +++ b/app/web/src/nova/ed/logic/code-loader.ts @@ -1,6 +1,6 @@ import { PG } from "./ed-global"; -export const loadCode = async (p: PG, ts?: number) => { +export const loadFrontEnd = async (p: PG, ts?: number) => { const id_site = p.site.id; const url = `/prod/${id_site}/_prasi/code/index.js?ts=${ts}`; const fn = new Function( @@ -11,41 +11,48 @@ import("${url}") .then(callback)` ); try { - Promise.all([ - fetch(`/prod/${id_site}/_prasi/typings.d.ts`) - .catch(() => {}) - .then(async (res) => { - if (res) { - p.site_dts = await res.text(); - p.render(); + await new Promise((resolve) => { + try { + fn((exports: any) => { + const w = window as any; + for (const [k, v] of Object.entries(exports)) { + w[k] = v; + p.site_exports[k] = v; } - }), - fetch(`/prod/${id_site}/_prasi/type_def`) - .catch(() => {}) - .then(async (res) => { - if (res) { - p.site_dts_entry = await res.json(); - p.render(); - } - }), - new Promise((resolve) => { - try { - fn((exports: any) => { - const w = window as any; - for (const [k, v] of Object.entries(exports)) { - w[k] = v; - p.site_exports[k] = v; - } - resolve(exports); - }); - } catch (e) { - console.log("Failed to load site code", e); + resolve(exports); + }); + } catch (e) { + console.log("Failed to load site code", e); + } + }); + } catch (e) {} +}; +export const loadTypings = async (p: PG) => { + const id_site = p.site.id; + await Promise.all([ + fetch(`/prod/${id_site}/_prasi/typings.d.ts`) + .catch(() => {}) + .then(async (res) => { + if (res) { + p.site_dts = await res.text(); + p.render(); } }), - ]); + fetch(`/prod/${id_site}/_prasi/type_def`) + .catch(() => {}) + .then(async (res) => { + if (res) { + p.site_dts_entry = await res.json(); + p.render(); + } + }), + ]); +}; + +export const loadCode = async (p: PG, ts?: number) => { + try { + await Promise.all([loadTypings(p), loadFrontEnd(p, ts)]); } catch (e) { console.log("Failed to load site code", e); } - - return {}; }; diff --git a/app/web/src/nova/ed/logic/ed-global.ts b/app/web/src/nova/ed/logic/ed-global.ts index b38a5411..d2df0629 100644 --- a/app/web/src/nova/ed/logic/ed-global.ts +++ b/app/web/src/nova/ed/logic/ed-global.ts @@ -202,6 +202,7 @@ export const EDGlobal = { code: {} as Record, global_prop: [] as string[], ui: { + monaco: null as any, comp_editable: localStorage.getItem("prasi-comp-editable") === "yes", zoom: localStorage.zoom || "100%", side: { prop: true }, diff --git a/app/web/src/nova/ed/logic/ed-sync.tsx b/app/web/src/nova/ed/logic/ed-sync.tsx index 315258c1..40879b95 100644 --- a/app/web/src/nova/ed/logic/ed-sync.tsx +++ b/app/web/src/nova/ed/logic/ed-sync.tsx @@ -10,7 +10,8 @@ import { reloadPage } from "./ed-route"; import { loadSite } from "./ed-site"; import { updateComponentMeta } from "./comp/load"; import { createRouter, RadixRouter } from "radix3"; -import { loadCode } from "./code-loader"; +import { loadCode, loadFrontEnd, loadTypings } from "./code-loader"; +import { registerSiteTypings } from "../../../utils/script/typings"; const decoder = new TextDecoder(); @@ -183,8 +184,16 @@ export const edInitSync = (p: PG) => { } p.render(); }, - async code_changes({ ts }) { - await loadCode(p, ts); + async code_changes({ ts, mode }) { + if (mode === "frontend") { + await loadFrontEnd(p, ts); + } else { + console.log("Code updated"); + await loadTypings(p); + if (p.ui.monaco) { + registerSiteTypings(p.ui.monaco, p); + } + } await treeRebuild(p); p.render(); }, diff --git a/app/web/src/nova/ed/panel/popup/script/monaco.tsx b/app/web/src/nova/ed/panel/popup/script/monaco.tsx index 78c5795f..3ee373a9 100644 --- a/app/web/src/nova/ed/panel/popup/script/monaco.tsx +++ b/app/web/src/nova/ed/panel/popup/script/monaco.tsx @@ -52,10 +52,15 @@ export const EdScriptMonaco: FC<{}> = () => { let val = ""; useEffect(() => { return () => { + p.ui.monaco = null; p.script.do_edit = async () => {}; }; }, []); + if (local.monaco) { + p.ui.monaco = local.monaco; + } + useEffect(() => { clearTimeout(scriptEdit.timeout); (async () => { diff --git a/app/web/src/utils/script/mount.tsx b/app/web/src/utils/script/mount.tsx index 60c0161d..9f7be616 100644 --- a/app/web/src/utils/script/mount.tsx +++ b/app/web/src/utils/script/mount.tsx @@ -40,7 +40,7 @@ export const jsMount = async (editor: MonacoEditor, monaco: Monaco, p?: PG) => { } const compilerOptions: CompilerOptions = { - jsx: monaco.languages.typescript.JsxEmit.ReactNative, + jsx: monaco.languages.typescript.JsxEmit.ReactJSX, jsxFactory: "React.createElement", jsxFragmentFactory: "React.Fragment", target: monaco.languages.typescript.ScriptTarget.ES2015, diff --git a/app/web/src/utils/script/typings.ts b/app/web/src/utils/script/typings.ts index b894f5ad..52f8fd3c 100644 --- a/app/web/src/utils/script/typings.ts +++ b/app/web/src/utils/script/typings.ts @@ -8,16 +8,12 @@ type Monaco = Parameters[1]; const map = new WeakMap(); -export const monacoTypings = async ( +export const registerSiteTypings = ( + monaco: Monaco, p: { site_dts: string; site_dts_entry: any; - site: { api_url: string }; - site_exports: Record; - script: { siteTypes: Record }; - }, - monaco: Monaco, - prop: { values: Record; types: Record } + } ) => { if (p.site_dts) { register(monaco, p.site_dts, "ts:site.d.ts"); @@ -38,7 +34,20 @@ export const monacoTypings = async ( "ts:active_global.d.ts" ); } +}; +export const monacoTypings = async ( + p: { + site_dts: string; + site_dts_entry: any; + site: { api_url: string }; + site_exports: Record; + script: { siteTypes: Record }; + }, + monaco: Monaco, + prop: { values: Record; types: Record } +) => { + registerSiteTypings(monaco, p); if (!map.has(prop.values)) { map.set(prop.values, true); } else { @@ -94,15 +103,19 @@ declare module "ts:prisma" { monaco.languages.typescript.typescriptDefaults.setExtraLibs([ { filePath: "react.d.ts", - content: await loadText( - "https://cdn.jsdelivr.net/npm/@types/react@18.2.0/index.d.ts" - ), + content: `declare module "react" { +${await loadText("https://cdn.jsdelivr.net/npm/@types/react@18.3.1/index.d.ts")} +}`, }, { filePath: "jsx-runtime.d.ts", - content: await loadText( - "https://cdn.jsdelivr.net/npm/@types/react@18.2.0/jsx-runtime.d.ts" - ), + content: `declare module "react/jsx-runtime" { +${( + await loadText( + "https://cdn.jsdelivr.net/npm/@types/react@18.3.1/jsx-runtime.d.ts" + ) +).replaceAll('from "./"', 'from "react"')} +}`, }, ]); diff --git a/app/web/src/utils/sync/ws-client.ts b/app/web/src/utils/sync/ws-client.ts index 652855af..de35e05a 100644 --- a/app/web/src/utils/sync/ws-client.ts +++ b/app/web/src/utils/sync/ws-client.ts @@ -97,7 +97,7 @@ export const clientStartSync = async (arg: { } > ) => void; - code_changes: (arg: { ts: number }) => void; + code_changes: (arg: { ts: number; mode: "frontend" | "typings" }) => void; disconnected: () => { reconnect: boolean }; opened: () => void; shakehand: (client_id: string) => void; diff --git a/pkgs/core/index.ts b/pkgs/core/index.ts index f39bb8e2..cfa8b053 100644 --- a/pkgs/core/index.ts +++ b/pkgs/core/index.ts @@ -13,7 +13,6 @@ import { ensureNotRunning } from "./utils/ensure"; import { g } from "./utils/global"; import { createLogger } from "./utils/logger"; import { preparePrisma } from "./utils/prisma"; -// import "../docker-prep"; g.status = "init"; diff --git a/pkgs/core/utils/sync-def.ts b/pkgs/core/utils/sync-def.ts index ed7292f0..bc98ab37 100644 --- a/pkgs/core/utils/sync-def.ts +++ b/pkgs/core/utils/sync-def.ts @@ -47,18 +47,24 @@ export const ${name}: SAction${saction} = async function ( index_js.push(`export * from "./${name}";`); } - await writeAsync( - dir.path(`app/srv/ws/sync/actions/index.ts`), - index_js.join("\n") - ); + const existing = await Bun.file( + dir.path(`app/srv/ws/sync/actions/index.ts`) + ).text(); - const content = `\ + if (existing !== index_js.join("\n")) { + await writeAsync( + dir.path(`app/srv/ws/sync/actions/index.ts`), + index_js.join("\n") + ); + + const content = `\ export const SyncActionDefinition = ${JSON.stringify(def, null, 2)}; export const SyncActionPaths = ${JSON.stringify(paths, null, 2)}; `; - const path = dir.path("app/srv/ws/sync/actions-def.ts"); - if ((await readAsync(path)) !== content) { - await writeAsync(path, content); + const path = dir.path("app/srv/ws/sync/actions-def.ts"); + if ((await readAsync(path)) !== content) { + await writeAsync(path, content); + } } };