From ec3e0f51389c33a01361c2a356822efd7a13e8b8 Mon Sep 17 00:00:00 2001 From: Andraxion Date: Sat, 27 Jun 2026 04:36:26 -0400 Subject: [PATCH] Refactor launcher and studio modules --- .gitignore | 1 + docs/kb/request-system-flowchart.md | 52 + docs/kb/request-system-flowchart.png | Bin 0 -> 275568 bytes docs/kb/request-system-flowchart.svg | 148 + docs/kb/systems/request-board.md | 1 + src/App.tsx | 2 +- src/WorldshaperLauncher.tsx | 2309 +------- src/launcher/WorldshaperLauncher.tsx | 974 ++++ src/launcher/changelogData.ts | 39 + .../components/LauncherAdminPanel.tsx | 687 +++ src/launcher/components/LauncherLogsModal.tsx | 49 + src/launcher/components/LauncherNewsPanel.tsx | 46 + .../components/LauncherPublicRequestBoard.tsx | 234 + src/launcher/requestApi.ts | 122 + src/launcher/types.ts | 127 + src/launcher/useLauncherRequestBoard.ts | 641 +++ src/launcher/utils.tsx | 557 ++ src/main.tsx | 2 +- src/shared/windowing.ts | 210 + src/worldshaperHeightViewer/main.ts | 2 +- src/worldshaperStudio/changelogData.ts | 40 +- src/worldshaperStudio/dom.ts | 4722 +---------------- src/worldshaperStudio/domMarkup.ts | 34 + src/worldshaperStudio/domMarkupSections.ts | 475 ++ src/worldshaperStudio/domStyleSections.ts | 4237 +++++++++++++++ src/worldshaperStudio/domStyles.ts | 15 + src/worldshaperStudio/runtime.ts | 1093 +--- .../runtimeBootstrapHelpers.ts | 141 + .../runtimeControllerBootstrap.ts | 201 + src/worldshaperStudio/runtimeLogging.ts | 67 + src/worldshaperStudio/tileArtEditorHelpers.ts | 420 ++ .../tileArtEditorWindowController.ts | 439 +- src/worldshaperStudio/windowing.ts | 211 +- .../worldChunkRuntimeHelpers.ts | 602 +++ 34 files changed, 10300 insertions(+), 8600 deletions(-) create mode 100644 docs/kb/request-system-flowchart.md create mode 100644 docs/kb/request-system-flowchart.png create mode 100644 docs/kb/request-system-flowchart.svg create mode 100644 src/launcher/WorldshaperLauncher.tsx create mode 100644 src/launcher/changelogData.ts create mode 100644 src/launcher/components/LauncherAdminPanel.tsx create mode 100644 src/launcher/components/LauncherLogsModal.tsx create mode 100644 src/launcher/components/LauncherNewsPanel.tsx create mode 100644 src/launcher/components/LauncherPublicRequestBoard.tsx create mode 100644 src/launcher/requestApi.ts create mode 100644 src/launcher/types.ts create mode 100644 src/launcher/useLauncherRequestBoard.ts create mode 100644 src/launcher/utils.tsx create mode 100644 src/shared/windowing.ts create mode 100644 src/worldshaperStudio/domMarkup.ts create mode 100644 src/worldshaperStudio/domMarkupSections.ts create mode 100644 src/worldshaperStudio/domStyleSections.ts create mode 100644 src/worldshaperStudio/domStyles.ts create mode 100644 src/worldshaperStudio/runtimeBootstrapHelpers.ts create mode 100644 src/worldshaperStudio/runtimeControllerBootstrap.ts create mode 100644 src/worldshaperStudio/runtimeLogging.ts create mode 100644 src/worldshaperStudio/tileArtEditorHelpers.ts create mode 100644 src/worldshaperStudio/worldChunkRuntimeHelpers.ts diff --git a/.gitignore b/.gitignore index 17513c8..1dbba20 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,4 @@ backups/ # OS/editor noise Thumbs.db .DS_Store +.vscode/ diff --git a/docs/kb/request-system-flowchart.md b/docs/kb/request-system-flowchart.md new file mode 100644 index 0000000..25db960 --- /dev/null +++ b/docs/kb/request-system-flowchart.md @@ -0,0 +1,52 @@ +# Request System Flowchart + +This flow shows the current Worldshaper request pipeline from public submission through promotion into the public Active request list. + +![Worldshaper Request System Flowchart](./request-system-flowchart.svg) + +```mermaid +flowchart TD + A[User submits request from Launcher Requests tab] --> B[POST /api/launcher-requests] + B --> C[Server normalizes request
status = pending
stores sourceText + fallback title] + C --> D[Request saved in launcher request store] + + D --> E{How does analysis start?} + E -->|Auto / queued worker| F[Request analysis worker selects pending unprocessed requests] + E -->|Admin clicks Run Pending Queue| F + E -->|Admin manual resubmission| F + + F --> G[Mark request analysis.state = processing] + G --> H[Routing pass] + H --> H1[Map slang / loose wording to Worldshaper terminology] + H1 --> H2[Suggest standardized tags, likely systems, likely modules] + H2 --> I[Load only relevant KB docs] + I --> J[Deep analysis pass] + J --> K[Split submission into one or more atomic request items] + K --> L[Generate title, category, standardized tags,
parsed interpretation, implementation approach,
review rationale, and confidence] + + L --> M{Can it auto-promote?} + M -->|Yes| N[Requirements:
all items statusRecommendation = active
every item confidence >= promote threshold
routing ambiguity is not high] + N --> O[POST /api/launcher-requests/:id/process-analysis
action = promote] + O --> P[Server replaces pending submission with one or more active request rows] + P --> Q[Public Requests tab shows those rows as Active] + + M -->|No| R[POST /api/launcher-requests/:id/process-analysis
action = review] + R --> S[Original request stays pending] + S --> T[analysis.state = needs_review or error
routing + analysis metadata saved on the request] + T --> U[Admin reviews request in Admin window] + U --> V{Admin outcome} + V -->|Edit + approve| O + V -->|Edit + resubmit| F + V -->|Leave pending| S +``` + +## Notes + +- Public submission starts as a single `pending` request record, even if the worker later splits it into multiple active items. +- The original `sourceText` is preserved through the workflow. +- Auto-promotion is intentionally strict: + - every analyzed item must recommend `active` + - every item must meet the confidence threshold + - routing ambiguity cannot be `high` +- If the analyzer is unsure, the request is still interpreted and stored with review guidance instead of being dropped. +- Promotion replaces the pending submission with one or more normalized active request rows that appear on the public board. diff --git a/docs/kb/request-system-flowchart.png b/docs/kb/request-system-flowchart.png new file mode 100644 index 0000000000000000000000000000000000000000..6ea0f2dee74cd6ad8c3656ee2c61fb4129dd964b GIT binary patch literal 275568 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
    hi+fFAPbL+ju;bO3UMJ*wMfJN{eW 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

    5X}r0xIV(YP%)ItdT-3 zOX^0*3?%xYGKmD&Ue%kxKYliwVFB)#Bovy&2k19^I8F~co$gbj>XJ0eldop1I$Lhr zYJ7NvetGnnacU_oI>0_}#bqwXkw(qDc;Vtt_;IYOLEq8|QIGDw!y6(_KQPHpG2d7O zct>S-{?g3GQH+IvX4Vd5Z=aprPvt+xqL%izAohfZ0})F+-<8VzAm(*9b2OGjb+2Ey z`8ccqq$LmZx85FTRXQ~gaETY3edKx}*`8#fFcHWFjedC#7x(=**|Av}zI%$zvg%>vN=(+OKIdMSR7 zB74`gq6$KRk0!;%LtxNSw-^cH;k<)1KCl0VG5qc>HOFYRZ*Ze|l{eS+bJ*C&i$-KG zVku09smYw5@q2aHe95=JJSyJ#E!=9nC!arXNx6DPFDN|J z-0TXFlT*~d{2`606tiV7ShV;e+aEw*2Ye{=Vsz!zFaiO-$Z6XD;JczZ{gpJP%rn|elrA10&Xru-N1nCf@Q2#!jl0rqpv-$CznN)$5!3$5T3RC`B%B@g2gaTPUl$IgI?K< zIV3|MBT0ZX?sVk(jsTazdp%KGBM~V+yNmfd16zqB5*7pei|p7c%7S>O_kKrraNVoe zn6gf7E5o|!y)KsC}IxD|5j+1h|E{(LWXhcX&m#@$ct zbQs@ONBAlN6OBN~g_u4*A!9`3bRz7}bGk~7RG*_+9+H_n#_oBCWoZTVbBW%}AOIyW zwmA##eq)2LN+BbeCW0)hgiglD@QDGpVhgdW7lq8QqS;(-AYk9+DfEk*iO+A`#h89Y zMtl@f0D?Tlh@*LLDozX}pz&$~3i{>}GC`Y0j?An(j#fvRk9JruAAicdKC<3!NMCM< z$?);;v#64hrz;lm`gW#ie$M;#j)F=AI$$WQ--wR(?EAoGk0^Xyt<@CNR}tfUL@JRb zycnHGJJvJgy`)|6-LEc{wL%|2BbI+qdQne;b*DcjJo}NC6fWtATu<8Ruxy6^(h)J| zUUrC)I?W7ie4xp-=aJ3?ZB zY|VjoA07%X?O&47za_b0rkRa`DG=QmXTod|w+sZ6aS}LJ0t;IhSR%Kiv=-Y&<7gI0)wx(;Q^PY6BPcBSs8v?7 zx7Y5n(8f$f6Ds{qFs}!=Bk17nq4|3VWR>lO{Yb{L>Y2dcVm7VV63ABu>JGnG9B0E|Bg+AQdSOnqKjj2UYIxuI*z%2k z@XHKw3lSYNDk5S&;vz0}e0i7pscCzb9Gu4gLAEEH!eNfcn7@)sj!9h0=Lco%8o;R| zy>_^BdSAh#kjxO6NJXH_fZ}j(NjtZo9?4sR@AVTchGB%d#f+t2^rg?^x zdHShCgFo?YG>`bS1tAh~u;m9-xUMykJs0~7z2GH3p=dxtpx8@(O7UI#PLy@eZ8%Mb zfFyt<+(Q^+6rdj{$YJjoF4a*`oPch=6Qrj%LH|VeJ{Kp1ijTm!?Dt90E_UZfJZ2NT z@|7&94G2d{TJpkB1!}GVgQm%~$bdTGTdtDCi@d8#D`u47`Bz0A+J^ zmAKo(o^tv^E9WEZMGIdCeIyh%PG`_egnni;2VwVO__uIK?VGXMh=RwR?#EByx{VuO zmod~bkj+@6o}?_j3*9c}!TmDQgJtXrA4f1q7_5&Sgxv`8!ld78f-^+sn5+T+UeIcB zX)+2U5EFeZdYq7FhUaez(Xz z4slYoQA2Xy>D8W-C1oP>fiM5Ll|GUojr-A8oDJNG7fs>GAN7Z+(Sv=df4&#`_LJ#w zOg$*rI8y3l!rqjqmWW0+ugmomj*p+(nHS(D(Rgz3@YqmzA`DzQB7ryRi}SKW8tz%} z<;&riNIp1k=4t72PM!LW?Tn}$u`QM#@j;S1Z24(6iQBD0M=F9`PUA$biZ82-Ehx+; zAoJNXG$D|Xh$C7;0uOr=VRtXUENIO$?IQ{ed^-icg?gPQ;Y5|QQcc^|xumnTmO*hH zNJy>0|8=7Y+o|X_Wpv{+>`mGi`JDiCyO~t2B}WzCGk5guWz@cp44K01c)LaZr9j4 zm)w}uy2Lzn#NNGM{#YV{N7ZMva7uIfi0j(9)rZ&Cw5C_8kMQsru=Gne2EV!_Aab7Z z>xj`kDyZYJw`VWnBn6XM7n;^xpfDpY^l9_gGEBM&&Hp^DEa$l)upJ6zC)K{6MCY8y z*S&7$#`&aqCh$O=6k>Ub9YHmJi$1*PI`w5r(wNu0W?l01`F6ifTpcqhml>^&JRgzH zf2P)eR`(P>O5C1?8|mv1kZL~6I3G2UZUhdvY81>u9-e@A`mpHU0{Z*L_oI)J5s#(V z*na4)(y<#4{+pBgYZ_d_YAz;Vl~)qqh7d z?ZFSXr!R0auoN7AVWo3Rloi3o@RYU1JHQ0*B@xOj*321O<4AJJ!#JbLdacrM| zH~V`m261-mA5e3mn7^q^gNxDBeF!3+Nr;4q7P1ulWynnZW85a|OvLd$;t{=w5{q0o z>Bmd>)4>!qqJi|+K1y-9rqv8%_&rFSeS_eI@XUMHJN;nI3z4&_7E^?p9^LGVlyF?a z0sNTN>fPgNKxW|YHgXIG0!4;J2bH%~rY&y+L2OH=1Poz-o&Z3AKBHTf&<7pgx`UMUw=UO0DMx`n&93Z4@<#H4{VFNIe9Q=vN0~A<5cKRE3JsW<7)Gw-8KS z(;s&bFX44<)j&eT55RwH-;1ch_LO1HbS#?}oZ3H0V#k_;GCu7q%|0=#{X%H@@ZkBd zclOhP6`o&`cMi>3cod0_1nLP_Z%H3U4At%WJfHopL&0hAZ3-^!_u}@eEv-E53 zLRv~v55p%c*{i?B8P^5RtrtWVl)6``5`{3IkFPY0hrBtPIvyj3Kg#hSBsY2UGris- z=xUKYOHTLbw{`Ti1pQS}%#V9#=|3r`{(I*+nJDNjM4o2i z`63voUe>UG&0md)Z-9HAsU2GC{M+>VFRSpK#e}G4^j}>wY8U@?Bp#n(P_{3OcCiu8 zhcmI9;D0sSPa&ssTQq^a6}uh0hdk`WE{wzFqq<9<)ICXb+t*0}lbwug7W?|xNQxn5 zCd3$MGSzYD5`uOc`f*9lBRklBj)#{y2asJ4M={a>?i(Mj3MpY!dAz+=ru8hsshJY| zm;pi7{SWeZ56ibN^#EMS(h0oaI%E)ZO3}{ei2cV#G#flpkmBEYIVeU>sf_Y*dsF)Y zD=a319D)RZ1TK)Wg5BhWTba9sNd4XJHr!u(r>nFsD%GDxgi|(>{*AQre1UEL-4BIO zTK_Tk*QHu`a!w6BVF4jH4eZhMh7FDd4XeY#ILJ}N$% z#Q;xmW!{UbmUNz9kmK3VxnxCz8o1gIgMr=Sbg)#OA)r;_M*YF!UeBSBX*-jg^sjCt z{WH9#Ea7+iaXY;kdzKr?A8iNg^lYZiq`lj%;Au4?n0TLAAO0Tf-wdd@V1#1zd-WuK z`g+GRDEz!2iUW_MY4$rr)+y=&+iw3l8-EsdwgSpvcx9#E&s)Kr1iRo~M6=9QL=l*D z)|hJ=miuxnoCE;=d<9~Km6Y(beI+vMLZykL@R zYv0zwl9!f2^abdtdlpwsUmi1G7kH%p7v?0jm0AR zdAWz?P~qqP3ALftRvG%lzDUv3BE7$PJmVw`12fFYYJ;iR@#g+*V6K^@FjfI8{3sHi zHkA2XbBa&^&}f0G8(cof9Zu-_-G$g3dtxIie$*+MiHZ6qp3r*bBYN?gj$alrzND+5 z;@sw^FCsG>0Y5F1J6T0fKhb&$r8-l)kM9khA6X`u^`OJmpnGB~e6h{=Kdy(bvzXtr zqeJ1rjU{521Hq-vXGylOL+OKx#|ov!a|PmO+fB?^!5Ni2ZSU^^!zY3v73+5);$I^@ zt1P+ahy9%-@T|(db)|Ee77G6s{X(Ey>Vvf8?Uz9M3(n^Va0zm}k^XX%JyH{$YSXzN z$y~0J@3>U2JxIj5Kfug2@nx;5z{=E5{luUl_baRFKbp?DHQ4%OvoBnXUe>S7V|?M^ z`$=iV;FZOaUeT8#-x5Va`0_@rij{Zf$(f&ixv$1w#WTMwN{OSe^Om<=8OTDKqBjt< ze(_GpRnmsIUIz9BZqs{3c99|veYnv)kuy9`V>>3kP59|sM;hPW^_f-mxQg!J117^f z@Vi03OVqwdrR70X)qLgPqwRp>LqLOluh???AbrnLydyW*sb6-rAJ)1eU`PK>(u5%Z zr|E=lvDxc&{|)kr|E;>v<^9AbxEeHT_|a4EOzs41D@XOxiOJ(^F&#?p<_p$-6#n_i zclo2U`+LoX`c?MDx=&(YdZQ}VfTxF}Go3hSS!@p92mE;jb~j0|nPG*{1TM4PLkT-^| z*eM5s z3KUR{6X^!w`(6I!9KZcKk~6>?8iV@MR8L_BaQ8B8nHzVKy%F$|=K%TUVNu zb>-&4 zvBs7Vf3eDEPXi@l6+3ia8#C&G@1#nQ0mN6>;Va~!TiQp;Y@2OyHD-LzteY-<{|Yb6 zH7L+7?Om9g0^?T=!Ns@R!(wOSV@K`gFEHOo2Y+tc9av?@7y07@7m;RBme|2yxkUB!la9aB!!RhsNDLYWjMzTZ2kixGxl$9 z-Nh#)@uh~Yje30;4EmV*rYV2jv9*(F0pOxUn?v?r$u<*7!_2V{HR}_)C1*A0XFn48 z+KZfKO<%8D=-xQ4D&5QB6~R>u@E#_m3HQP@<|uH2{M!F(Q^=@^-$N{MdAQM^v(cK7Fo=cd^T%da8_hQvR2h*|-Z*(i)CxvCj3v1{s zZ6zAa@9_jz{^!C}OLBK0`i(ata>@gcb!bF)%0q2uYId@;IWREIRFFYJ#Io__*e^Ad4JWLHR=1 z3aBJ9GR-7~r6gJ%_HsJw{@-r1(v#Cad{w`B^Z7RLiZT7WyR~3?`e>5+uBzZ(_6n9e zqQpHUT6TJW?Tj~35vi~4;AL+s-IP=oYyE5ds}vd6pxrwgFGXzLRw#bZ z?h^y!c8%X{M?rA52nK`ymH`6_EOwDadye`17VKB<CNY%%(%GFl8>X76 z*OfVq%I`d(0#VrJGa?4)6i_72Jru!$zxv0htL^KnMz_)tzb9yFN1Z%NJZgEJ5H2MG z8{G+<%AD!BmcJX@es$iFwzJR4NJc&i&~7MXmBY;$-ZOjKf2DUUV2m>-fCSmx?B60O z!a!+HyLNqB{vy|;rQGZ}I39p)wsojbZL?@If0OxIlm4l}hV-jw)3fRi_E*Zvo) zHi$TPbKm)~dI`KAf>2^%w=0+@y!&y;cY0CQN%}Gg=jW3z3UcRu>qQnzD04HQJ(Y{> zf{l_@V|?9_!gzJ8_0))~`nq-}*ClZ~@EgGmY5l=Bs#$X|?p$^$n|3^v0;mQ&h@D~S zk7`v1Q)1{Q$^r@|gAn~d9sWm)44TH`V!CFNal~O?j4O`-L5?Cds0~|@rVnqJEyuK) z=dEO#sI_R_(K2q^gRtE5mFTjkk7>o(SsK%k#wMDihefKpQ_HMu4CQ^FXdU;tXojdC zO5KqQC)HOG6A&VA<2F^yhLmQ^zBl9PR(AOKSFaW`s%z{MOQGEp&{%L9633xVNMGzB zW?+FoSn8a}@9o;u6g~X)p+qvGNZMWw9a$>8Zzx#50Fp>JWczP2GDeIq)0H9Q_hCN2 zsD}0Z2HauKX?`tzF?ywSR^g-xyF;@(v%#YGYaDk0(_hWsQ_r<(5zU1xkp6-u zD(7Wu5w3aZOxpN#S@qcgX(sRDh@4s&Vg-pEd=B@5g%;`GTF>?yDyP3s>bNBt&(_v> zO{QV%%{@Z%Amw}BLjf^^y1Gu_2zeM92u`0j`QovmkO72}D>d_Z7mNu!?2Hi6jCIL{ zpU+?7(27xl&;(aM$SWP!|IZRV_f*YjrNe(uRQd;XdN1*?r6nQU4_>0A2y-_!$V$nt zDZKGaP%J-IDdC&&Z*1mM+8ox`eJIIm5(*xq%m&-Vms&QaF#68D4C!ZABg9Y>cx}+E zhFY>zOFbS2;LFk= z^t?qD&otBo*R(U<5fQqnc6t17k+Q+8){uR_8UnxlK`iRmrU?kLuosmrs7trVMasPG zxy(^Sr}m{7Ei~858u?*d0SWx(*mP`&J%3b#`g7Uo0;E7zR5BLm*V2x2n=wWCaS$$E zZ>kBq`Ffu;^z~~a8xA%dW|B+# z|LS)rOW6O}JPA_qMr$j964y-^&XpY;zsWm{+gdDe)(2j!btQB}vl$r) zA*-*Xq~N>~O10(}>%K}o74X2B6a3rUlw8HN*e!ZLaA@X#NG|tfnwEad(fl1qv;8A- zGBuB&)@@a%&Y>=ri0Ros-6St41kVs5Pmco^w5!D!kTrLzCfWphGrs3%+_>-a7%Tf! z-O!Ndc0j5DlzL6UgYCc@i3)w5p{{H_6&8qamIK;9#R<3FMCGyrFfx{nNF^|4ite(! zuoa{>H?jZp5-bIGp|@Q|?Xr2TCl-$*%R+MXj1tpaO&;I5xFI`?fuI5k;URcngv(sd zwiy}{DrKXZJa#~H_L>X^GhWzLE0%P~mZ_Y3F z!_AMZ4{wPY4LGy}g{JB&@FT62LsU_yCYj}!+gWmgIa4^mA}Z6p=hT!Neqcyqc$eeZ zu=}ciHZA(nzMpi&HY~Z8ArKM3$L-0Y_W#hEk`;Vz7We4Kk5XRpqq#Vb(oqo2Oa1R} zozM(XUhV*k_3-NoD&d7)m7={ zV{#Tt-9kr6ML(%cOV%)bk~kwtYyb4pNYDk(8>7{xt5@OW__U+#pr)L=En3pceZzO> zY2sn_l!)H07hA4a+9*icYO#xo_Zh`qrQe-G-EH!A`6OZ{AY)=drka zI`OUaB%SWq3&W^c&tCHGav&iwp}r!ELgje3{DmFj1F_Y=>BSPL>aTb7@0dH9-2&XT zb0n(~EPcheJXj7rf28-vkZ#k6&R%bPtf%g^FI0@p4hY_Y_ zmyDHh_ZiQk65{Kt;Yc30uTbW1x^FHInZKYqjUQs12tKxIGy!?(F?MJgmzY+y8%Zq) zoG+w~qjCQt_iJkjQI-W3j~_Ll<#WgZZ6R&H$d6mjq&6{$%Fx^Tq7D_@gA^5)SGbb$(%ij*s_P8Yqhonetg;CJ8L5opR<@Kk zKs(6MGYb|Mb|}JRIs2()hqnHoyzz#yQKr~MrIW%YDc;jbLa8*EKnjWln1$?nFw-By zGqpg=_|gcgXYzM`zsQ`97UUeRp2qb;t7Vw_Hw#T8Fvn~K-81HWkC&42j260kg&`31 zQFPfW-=~iQ7>V(_2u*dW?2MicunqLY_T8i3n;!2;&OAR;H(qD^=_k=7UEB(#gUh4X zg-EJeYS~5C>Z}Pzs2)Wf^o6l`g5t=qS?#F*dm01e{=1#tXR^SjVc43o#Ml8xeMA%j zFX&XUWU-E|Rb=Po-BYylxMku<4lpIT=au=nQI-Vf^h>T5msm9_M_}WK{z;@9)tXk9 zBl!<4>mWxU)Hgx#QEq7D!4sSFr9IJIcCAyq>L_u1Nd3pTkeQdMoe}mqiui#iz5lBk zi4p%Zl*6!@F4s5nDVVsB8f!qW_E+-6E0(G;725=8B_|RL$>}c**1)bSqXNPLTk7_( zfuoBA*2E(t1}I%qdkeDHzWdgiQJ|$bTjl;|G@VKvyi=+s5|eD$EAI#*i$qbvNPdNN z1|CW~8{Sbfx2V0$+caK5nJ_xvU5YBRb9_u!E(_8ES*?t1)o zNTY+U^71!(2~>VTFx=A6e{Q(PUHbmbHU^WnH(M(ZM6g|e`DY8@ID6={?JR&O9?8Kq zXS{&h*Yx$SKj8nHb)TVT$xb8BH=q*D3xYJrxJ}wiJ1TDs2Weg+b|%6i44NNsKt5VXsfMDD zFrnEj8+qu-7d76cpyV2{&ntR34mEp#j&1~iV2GA$Fp76SVPsHoCVK(qIS;A)K2FU% zKYM*iOQ9#owIHO&@>!77Z0`&l^$IIorxH^dMC>p9#{8-6344x$Cctqu@FaF|w?wmv zl&U_+QQ8;#NGdZXdzlWu?zybkl;H+!_i(fy3EY05dUzVQwCjrZ#F_k9m}J;xM^~sR!!u6WTBRxj{??wbmR`zRi!; zG5+qTYROx9-oFd-jzWt&o&90>e;Uu2=kK=8_It7a4()D11%?Cx%7^jgjLvP6v59p7 zkJdRs;r-s;`2K@5jUEd30*#(Lt#A{~s*@SBG5p%k2kg~%-E+vh=!5dE_ruq>a__c( zG}#`rSU=)|+nTnV;Y;YXD7T!LOuy~35zf?e0^i{c%r|s;#3HM^m|WHU(Ja(>S+^}g zkImMG;@*cZKdkyc%V1;EU7wfGMmZiQ_U@}M{NPxNt%uE8wgruqhXe6mMmSu8Xobs; z)!cTjKBfjWu#E(LvwymM98Bs!$W5ieqWftt6vSl}7xqz#CivZ@PH!!+^ewg2d#G5& zD$&lPa{bBUSsQ*`<%0fxwez260!hqj?75H9_V{eAyBt;vqldomwIhbP)&{4Bgec(6We4zxfFom}V$`nqdz?i}D@ZDJ)HYDx<8xG?< zd5>0d`G_-rHF$He_>be+fwu7EWg7dx)=Z6NDKGJf`I^OieaOMh4v2LlC;muqG2<~hs zsWyuJ8hgRietxYqp^~DgcOz$R^@3YD@0dd(d{gXlUi2~V zqI&&z|N3j1j;qP7lhO0V8Qk@T(Tn)Zfop{ljT@`@zS(tGZscSeNio>W|IsojX}9Qi62%Gro#X(nEe5^30hB5!29&8L zX&ZEP7`xbk*|>P+%bg`i0%s)g3`KsTO3ylnUvF%FzYPZ9^NLG_?+^us9l2Uc`M!}t z*LKkQqoAV0{`d%0ByR|)2Q_z8fnj)j>rlVTzaz$t!_#xY{|scl85=M6OQ}lX1>?5f z2LJYD%SQTvb65U#`S|5m#UN*!YnKmi^N-m<-R8-Y=P15#r!Q z=de^m8#bC1^zQBY#SaWRj~6I(7!s06r$@Z=GV8*+J)KmOZl@U95j2#$v*I-$Op z&~DTOT`3Mg=e*+8!{c+>1onW|b#490}oG$xmX6dYQbsYsjTawBvNvFCRsn&q4ZZhi?nzgB~)Qv9M)A~zv( z4o>deZj;M(pNR^f{u>!0pWQctF;3RD58oGRxC?YPNy1$q3tw&|iO`3uuW>65hg`Rq zgBnR>o)|(5{|5%2<=A6kEwj7>#khj~m!`uvI^58^!|8cd*8QaaVs9@J3AK?%Xj48W z!}I~8gvZ3FXooZ*dWBxzH^rrE!8h_0p5}ZuHv`u;ZyqnT7Zu0>f_3aZBWsCV0X<62 zK7v2A*tCe2yd~?eB^}C1_-bYEn~qoOUQL&}a4!p8zOVbCPNI%5u{0K>q*m($q#1Fn znyW^t6TK3>^Ozcg#~Z)?W<=5bA0+c%-RMrUSq^?c<&78!j*hkv)uKF@{(*$kT+7>s z5Ehe#k|_cWs_6pE^3xvc>76`n>FG-yunO=SoQAMz*2qxn3+)P&Ozhf?pjJN=h-9&I z_Xy)wII;~nVh}O0t<1@^W|uJ)O}$8%9sSM?oAa63CGP;*r((CLT=Aa2F8WqJq3-}Z z*wL`Tc|ME($@Kj=3U?{O?N<89CQ+%-D3(n>oQ=a%B8&`nXXMf{(C|<&QH>c&u$Vo0 z;J%cC%Ms_xbodG7bnB7NI=Y!q=QQt|o$ULHe&@9KdiH%%4J94tAOV7^l3G*dozNj^ zq=v$0-DhYmu;$NC^Ns!f5U}-QdGB6xlQ#be@dytpf!5eTfx;S%?3rO-QkgM~@D;Gr zQDqBuz?ryX@wyEAI_bN=GxwF!asTgG;be`f%zV&Kn2V4TdoOzxf{fK!tkqe()s%fs ziF0snZJBvG8n3K* zuTs<0wyUoLay!~t)u;qzkE@TCYv$m|`Vc*YyjqOKGZlLjLEj%s~*~MS=Cum$M zzG4O?r5#E%Exu<5LAXhOT;w65Tx~B$zFB#vbz`nC?PD195YxUUR}=T57|ggRXEoWr zYq@eFSn8{ST^5cdxgb+jk}q-dImCdxET`J%O9wc~xtEx8#^6(6cU)iKz!5y+Gmn>>t-9wfj;a#=}znlko39ZB#J)tEn&k^EIzF zc$WS1Ea|~-T;&&n*PHDZEpT%85)}Avw7r~qiIFNJuJhzsx<~?p|3g<{XBP}u9uCM; zddu+f`r&A75dD2@KQ}#NL-VKkYn#anYtg$BE}OtK`t4}o>N_?Sy;E^)I(KTz#9zBb zH3exW{cF3vH7~#26l+EtWWDcjvd}Qz<$8WZ&SVj#^ZZ z(#qw`dNLP#S3RwNm(TEC$1NDfk?mI!3#&SBe%6nfvd@)zUjPn@G68w?$b@$~FFglj zf6O`~2EY-BW(nw%?j2|^$7TcMbs)DfiaJ$O0dmdJ?cFQ-DZL4};VKFKOxk*zRqKEU z2Raxxe82I1+s%I~7l2K55%QWfv)%MzR2BG+mf8)nuPVOi#vsmDjpTcezi8%o=7wlJ z!k>e`1{ky`Nety;@q{?Jb3$^2kc6F~m<^Kh7z|@-)X2p;z3q!I_L%qQjJwI2J{X7P zoT_^ff{r%N{cx=0*Wjxot)h1|0mJZlL{>S8zAsxBer+c0z<2Uo&O=?3t;(Mok{x1W zT%OpNBSm@yIfl5-OPALe>$(8ITsLzc?j<+ZANM%O#@Vk|*vnHbzz#h``v>1aJ?-Vm zk}AIO_QK9RAlShVVx85}8I6$CkLqFYh|g}HVX1DZCrof~*u03e<@fjyfh+00EF!Ao zV%Vzl+1qN&EA{un!vSw0h0NUc?~xuX26Gbcj|)EETlPwo zfRz#QAY*g>tV6Z7+U1PNJRts?sV_|5AHs(UB_JXFoVxZ=jwLu{FLw&q1^+mYtNmz` zu*aeUu3^IeS@l)ANw`A4&5;3akCc?@^Q)V-7Xw*QJ6btJI?i^_*F0mPAB!{sM!_%~ zD|`tGZ~<52*sZ+(j3T0<0d>&7tkYCnaxhlwEN~x;`EH90{==YN8%_!V4ne zp}y-u;^j4uT4#H?uIUYJ$}8(twTUW6jUK5kkhV0p!RVXk`{;% zb03fGTHA%W@?R}vuJ`{Q*5tQgMu~55a%P;wAR^T`b#lBKiQRWaCCER(1$F})e?nDW z)N;~0nj(eMh3!HaLn2va60JPJ5Z`2rL|L|NInqp;R0x3<|&3XkHg)Y##BtHyLRl%`ko@8KGO$iy%Cq zqrfLVOFYTddoxNoNp9&^aw6nujNBVHBbw{aF}o8COU29!&c0-5=_Z~syJMXRK=T2% zTyQR99YtDTMl!G)duCG56t$89or0~5IqHkumaeJeErtr_*@t^SCp#~^7MeGr%eeNX z>RXva0v+Wo!~(lxik(Rw?dF`b=E?K07`|0Is1%EWgpcl%=cO>X-0Np{ zA`Vp_i@h~Le~zJovljv+oF?QR>=$8^HZG4270(7wt2Q}YO8Ep+bVqMM(3P)11+P(O zZ>rU%7G%5*XM&*K*Fu2!HRCH2VD#1%G@Lj520yNQ?6k}DD2qr;P~vmEn4*ROrx2xg}$)% zq^TMydV|LT@q~`^KP$AME|Z6qsT&2wPC?I`0=_I!Hi?df7orW zZubvfYP3_YVsVSsH~ZH~cnL)tQ>e8I=;XG32ML+tk+hiB8q-B_?Wd)VMt#T@H5eeLQ+l|o}mZNHjj?zgCOLyg+2#f z!7zW#`o z!ptey!;JjMandAY?^+4~GC>%Tl#n4Vs{JC+SADnJJ8}hI$gAhD&%m{~$7al7Kdo&s zFzlecNz#U%g-flo6V^-pE)TsrHZ=-FC}Jj#Nm^$-M)ut`_gRUs7&`fxC2KyyPqgr- z%@oDRFUNlRd<`qt^QJj_VC2c0G{%d3^`Cw7b1Wumwe?~s3PG-$zrM7n=FXKAy8@mK z_x|W$xQ0aO6{4sOJ_T9h-;%5`JBnam@$?}+aETK5-*f)V zThVT-u|^2y10x^tV;n))aSr^7ZTi#hU&@OKvn1fOmaxy-3P$ONo8^pRYz8U_g-yb`l5_U&x5_8>%dE`nWF@C{Sv87yti+ib^o~*bw9Gv#~wy{$` zp7zezG~VM*Klu$3RwSi*`D|C6bdB(xWL=wboz+EHmD%NF(|)3+7{sppGxAxGqd$z( z#7Y54=J|_hw@!f62_KQGH>vK~XYxEvewpc{%ZYZft&ZnpR`CJWYE__ZmkbFM~r~D4?YuhuD!4w6Ze3 zBaJU^fpWiS=3P#FguM~8DC6bR04C_Ofa*EJ=#IXy%$^x6pE52ZnQihwb61E#3X~vz z`ibY2Ie{MN=XRO~e@cbiqWN3t=m$;OSqGR#o%*J9e2+DGWhwC)P4lzNV+W~THcp`m zlzznDovqd58KV#-=I_NOoOSvCO*ao5a)pxYqrb#408rELC=`mOix)U*=ogmgtPy6*+|H`Z#tVwxBM04gL@1G*4puDlXD0ORXttW_0m4qbJzJQ4)k($^1x=er&yxv8ZA zpNUB@O~T=v9tL||n*j!CW_ud$jW#QU1YHIRBa#dPLB@OuK?%bA?RQxJYVnr9A{P2d zd1lvG)9jv%%wkcuarAt+tn<%Li1aa#5c#2gTqYPMw6AU<-=L?r1%P4ehXX@Ph|s>t z1$7pAy<^>(w7w17%l$bDdYA`!d(RuLxP}rd#%f z6B|geq#@uS<@0+yXOogOHO>2P=gxs9uN11%nJUvGtm>1jh)gm?krW~g_HGC^sWEAd z!~X3?Tf^PQ}#9Q#{B84(qVw|aSSI_?h5;& zPj4*Q1(uwl@AlBiq`n61-j}Sj66-imlkpG`XYoDS2ythK)6_eeV0UtWNmZtWKa>Q~ z89h;a-xq!?)C&u|`wGIdZUBgxa%$0bI~SaZXw@j_pddvVzp53{sQ}?PEL^^fp@b6R zMVtbh-xv|IDFrDJ=w5g~&3DK51wLQVCT?+(u!QWJVIthvS1O<9$~?f5!VB~)ip4bd z7*~L{@xg;i1qc@s29vxm&?Y_>po&tgnMfSS1og$dh6N*EHCWI;M{>Rm$rUXuMZ%B? zUeV!|{3f$BrOkl2HqU%c$&g&6CsO`fdWV`HD#Xiy|f0bC^P1wMn53|1%ZMJeZD$d?0X=@7)#i8og7!AX^qbe(ombl^m<-PkS7uwlkKv20FXi^RP@;1Tf>L zvM*Fvl3Nq1K;8j%Rhxk9>?elK&>qZ15gDYx;Z;4e_tKI0pPOYRSY`4tB?o1?~_ zvSvmsiD;#q))rlzN`yFW`=xc=V?K}9;aB?_+$(dz`e1pyD84Q!*1Uq3mGDfLUP}S? zxFqw1mCS6`MOKu|YkLJyR1dF_b}>rjLd~4ib3in2CBD@#9fG|zZ@6A1Xh@sN;Q!zi zLr@9mRlaa&eNQ8o58;QOE-u4Q(7T--F#73r4I&sUS2NQ0lNjpaTg{;n^9d0$Kwtue zOmLS6U*jktzfNFa^+~m(4q^xcV{mr6C<=P=!L`~AfG3gxLiNJCIXIhuaYL3=K$)L$ zBS@auduB3Z$xvK2GS--0cBLM#&$cBr1wq!=8F$>|mETL8Q-+4We-6+Q2to;^`e+)v zlNFS0%rCWVi|H(eqVA*t{5oD<@aOnSFom-h%Y#0|F-aMaAHrn^|J+Gr(hy{;T9_uf zSLfb0@?C0E9tx-C<^D2*6r}l!c_Tc!AxBAi`ne;z2%+L6BEj&skZmVBR8TTwa1G3Ez1u%9Nt3%=t2(fa zH5FjXJ5i=!KA|S`ZrtMe7UK>T^W(n9=eEM}KlGDPG;h~O z=}_56!Y9^Hd~c-S?A2VbmbV~t6oM?!Ta()M_2H6AtU-4%-wml(n1{@U%TiU^a4#94 z{Wq+=ve$Ra{5^~3_!QR*O@vy;#BHMyumx9?EGk35_LhVi(C<@Qbtr-KJBMYgmF#S6 zYS?3oTV#j;{6b2ui`=3GF}q;Q@E6yl2ML&xK`>@jK-%-=;VuTfbXCb`md$jtY66`aT{L* zcA+pCC3>t>yk^899Z4*O1XHsv!<-I2Ff5K!fn_Wx92Ylb&E?^q-|F6hMhIp^EEx26 zs@f3vjzdHyUJ%;G51i<5@Ch6zeGy0&mc*0^3XSElOF)=BPCVjgJBSNH#`Ok^`HSOu z8+vrMZnKx*cz+GjZ#Dl-@T$4hh0lN&XgrOL?xJVeyo`>!JxNS5?X;L;LaMuSUa(%K zhlU_IYOzHs(Hew-Nd;eigLf{nQ_5SW*=u|V!niiJ-TRPCf5EV?k!^H=ykjfuQa)`r zLf$M85;UmuBbaPW##GWVFrON?ts?typh*v4TZ&wI&}Nf#u6SfT^N{rI;*fkmAu?> zoY2^h0^!&va-p^4pz%Zm>S6PF-SfF(v!5By49+^7$RbPl;GF zVmL84C!g2|7*C?4hk$7?GVCpz1ou!kc$9wI<~DZ|YlDCR31$F{L0|67+!&r*5;7%^^N z>1Xs}?^1J^MdvY}e79Q=zC_yT6J)}vaAgLsUcMK^u1i(5Os)m1p$%BB!3+~y6)h%V zH6q_PS~sI|pFTH1u-JmKkM+YJnHd9y{5NRc`xB8kFb3I#E-hBo)S{v-0_Re9#Vo=w ziAewWnbEW^|E4lvczC4Y_Q9ErtsZ9L_&Kw}hg}n-Ifbz#e3E*o3V&OUd(6tmaJt-^ zDm0s$(eC$IAS1>^SD1;Zf^a0>H>w6gpu0RFKpZy%?U!k@o`cfTczvDmocWb`=RiNr z#-_6oCXvu?9)j#WrXhk@V!h)u)#~XKVH@u@dyU-?aw_r$2$ihuCOV|}$3m!md!Cqd zv+1mC>rGToe0DH-SM4>}n>T*^?|KurHVWS4*SxT?9d~}OZWnnfBVIwRFf`v_DPc4) zya%epXmYb23iu{hxl&~<-dl6%74jOm{>tethnF z1w5CaADsTZiHX~LRC$c~$2L3uy=8U??y$7t3!I%>naHv*YG&@?hj9V%3CEij6OrBS zv=5&ITTrvzQ~Q%y+`;}MgZ*+XtHvQ1vQ^zw&_1PV&QqvgAO@X@asq>LZi&IJ4TbN* zitIK6yz6DW3^DOi02;!v+h~hSRTUOFfXyNTd748!Az^$cwB6schw2JFUuy7{+Llvc<>=JC6>Bf?T%VXmAT%pSyPxtEr$+t#?}=-#Nrq ziKlknklu(-jd<)%Z@v0T6JN7Q%D@|~&As!vhw-k;y^*YM4@}1CLtLb59}_kUcq-*% zis@v%s98~*Dfsj@eTii!?M99l)`t0`)VRqnu4O^;#y>aIHj3HmiuJ)`U;`9md`p`>@UTN9Wdc&;mClIU_;N(c?gu2NYo3WPgt zQP8NLWep=MN4~2PrE)Sul(!Mr_7K7?-Ez$|gZ91jiIy`7*8Hm*u<*frFDCXM9CHgv z3P~zA(oPVhq$8e#-e~eP@O@1OOKmDPr*e(P`vuj#+ z5n@d0vyyo3V#jYB0Kp;QweTpJnj>Q)#HK1HB)P6al-}0K@f<=$~{VET^dnBi1uTT<4liQhPeP##ux8J%o!lq!Hh_6t! zLSy#vXB|@Hk9E?&-_-Ha{_&!IrKavsR~(Cki(UG$GHIxnV9@X`R~Cw+=L4}vO&-_p z><;gteDo|fPIPt$0;k}oI^#N`PsQh7-RYOUFU))11NWAj~ za*E#}YURntVz7^3pn!0WWw@gMf$saR=d9Kn{JkLPYl|c)|T-cZ9Ix7?nq`zu959 zv|d*m8+vdkvJLZ7-M8*5pzF`OeR-^lIcNQSUGM!s+a|>y#l!?(SNFK$t8%&o4ZWKxk1cmiX`nEjB z{=U>4e!*GDi$-wiytY`~BNRL27de~!kS@1kj=kn5B1k?E9|5>>~?grAS1rnQS#}~!LUGL~WFqf)gq~o`WV#8txrs}65z2Xzm}3|ZV0I+F zSUD!ebnD1$O-seh8SVYP+u$?bGbza(!77!#xkj~@VT}noY3$s`H~7%13_==7TzvyE zs_yqW#>C5#2FIv;qQXqSU-OiXL*LNs@mcm@N{T@_sfWeapR+=?8gdvRQ1e+Yf4@Gq zdi;BiwU1*%kGHQ0ypNOAaoN)y31$XS+Lsr;q*ZLo(uZK{#&(f5L%n< zHPH+Nv$4_5JEO;nLTa)tHHW#m{qh*CJ{sZ(sfs->M@TK*%H@RM0m`r>ZTcSfz_{nz zSM8BpHl$i%Aaf4x{l23JpSax{1nC-*J<2+)mRRzT(9|IU6A5w9wi;bM!W`g~wGE`k zlp~;`yCzr_^|Q0o{YS(fse#9I|`C%x* zzFz9H<&=9Vm&VUFW1OP{A+f@9UA~ugdC~LDt8r6TBJK+qS7<+sY33<1^20$)+{~AVjW;an_8>_UX-ZO_>)=8^Qd+O4WXhEvsErRX^PtXEe&*@3q2}Q;C!w}CBsW${%(F4MP*@) zrfUl~SFttJR0tsseXGDC6;#hlCTkNAGi->xd(2?W_ZnR-t15Q$n8DO3RF>lVEw+sP z-W}hqv^|eYh)_8Saan_uF|zK*-*H^Wo)Q{}9jyiViRD+KtcV$h#%e~a#?`LXoS`z0 zLxWjyE$x7!XMrR2h0p=YXau!bA!xO~PB1MfqqmSODfJ zan>krYZVmMyGc@|Memiy=e{}Q4&CdvD7Gk{%($kWTaC}^dbc4ByZd3v0M+~c`+*d} zE>aYlbZA8_k}qyy8~Uxkxu5m03|{{!m+*4tSNpKqpd}}BrE?nM&V~FnX8Gh4Jz~e( zV8H9v!Or*b$xK4ZhW0BoaM9=Hd9b{NqO-9hGc1vUkH@?1vw>4Kdu)Ll3_g=6w4*Gh z^?~3((J8R$cF&FpeH+Q0?vR(CltLYbyz)Kx`KGGq5zCL zD2&$h&AWG`pTRT->(ON|9sBc03~Gz^j%dt3-jka-33S?AWurYfvV$kMq( zP+N_iUs|40%tM^-Y5(Q%$TJgesbU_+-H;VVv{V#Th{H%&U`^-ymdgb9igPhA>0l#l zY!dV@;J#~Lz&KrRs7{4K$fAXvtOAxUUfN4nANjI$e8*vg zt*G{0&ZUo>@=un(RC{#zo*`VL)0sIz3Wk9=JI-cPyz#SFr?Ozl-J(mk5v81SQ{>I) zTnP#Z`j_ee0&IrerT3p$XFP>XkCV7ea88^-_Y)3%i=Kal(~E%iS3l2u&sJNcO>6zh z*Jrbr5KMqp3W7A-_HKw=s432Mby+6-v|Si+FFG0IN#lKgaaw5K?ak4;`tj)BcHHt; znj1yAqA5<#BP!kc{$QDx&LBXb!dZdB^52KZ}7|%zluYd3^LrTZK5`JWgE__mypN#_)*i||- zBB*+9DG>o{_yLOam1vV&?nErMpS3ec79!I_X9|%_SO*Upect!yRB%?B*PS*&64}^5 z{<*n#KP)2hsCao^wWJ})#16GW>SV*Zc~vzo>}06jm$lM^EKNP;Z7w)A120!t>JHj%Vd>Nnwmo5lGZ?uSk3u@ z*o^G#F~pjD-18z*06fej*WQ^pKd!@YyOfRKe(?{(StGYI`WrbsYd!_$9s0=av~1z`S8HUB*PbLo zB(cm%&+0#e&aE_k;__L|dT`Kh z$sgdZj;N$_TiYQFN4=3}sDwo?R9D_rB7No?)Z&Hem+cu)Ge1w%8jSb&oDP7oIHm&b zs9|nbG~Tz))}O{uOu!FM!vu%74oY%4j+7XP4oI!DtoDJ{Imxk_oU{KyC{h4@kc++k z?fOyk8+QLSaT4e2Fw{)eyzQ5b#SvDhRdgXn_rJZXQ(YfUBArNnde2nJyOgb(ukX?P zXWi}XM$e;F+?HE;CS)@6uWs@rrZR1+C&6*6PpvMm=2r}+o*2A0=gidiKQbtBN9VqM z`gSnT=pIY$!|yrzY_9k>XReX+uhmg*>j_D-e%zT) ze}zXh>>2YZafP3^;`#6ooUiD!&jkH4U6 z3a;B)sjwJKNas-RgK+&J{wBG<_|xS^xVJICRR%221#OA`wa{tSw!lITYGT9m`?#a& ziKEi_5Y^n>M#hA-clW|HwM(`cZF~0P5KP;&JF450KJ!lz3IQF!{(tn5TAW=LwfK=D@}~ExR{~NZc5USZ80t&q z!c2qD4e5`{FCn6r_dPJx>9;A|#ilM>2h0(0roGU9UL;MC69EB~ zejO1DV#;+QSQh3g4j@`UW=#`te1d?xK>z6m^q&+!|7qSEyXD7y_LFDulMT9o9rlQx zLN5G1iF7cFh`!erzMd)GMB6ByX01TDFTnoau2ligTFUbEQESt@E*uoB!FCrLmcTQ(3W^a@lU|mqN{;I`flNA3(EW|yrMe+IW-N&9281OhUL%Ssri4}28e z*QbBpsYXHTG$vgOXed0aw{01y>|KD${_wH_Xx5+$0;NRI(S*bAYJ1R-?z&Z>z0*&v zJ=0H$XtQWYpEzwv`?yYXEWJlr`wrX#Ojhp{sgjVtmX2|DL;SbYUeYS54GZM#u0GuqGuAg%BNKr=6FR%MfTHA zKwff|nTi^Jw$oEEHDYYK&Gu0Cn22Z~NG*@A9t(8xs;o0?n%N$}@6Nq5;rs{oLkH2> zK9^H3ey(m$VE34QA5dVuxK!|XiO|}TN%w#l9`VnytkJRw${uR@e3Az|anhs+>_c)r4A z0%+!^7IQ2atsp$t;?zOS)$B{FbBu*2@K>A}D4U$K(fdX|JCu3BdpkET(UsvUhHex< z&@rw&pW6NzC`sQsua_Fcm0ymId8YHTPKZQnSyZN#)moenMszdp2A%5YhL}Ge_V0??c-7BQe1KK)A0Zl zxp((r1+5-?@?q^u4_~pRqUd~8VQ^UuIYms8NGS1aH1g^;ERL9f?#Fph2;Avj#F&YA z;!P2Z-A$=Zg@?;l;|~Jy2JOCA?-ax=-kG@|BXM1(Xay66tX6(9bBfHEp_Pr~HOZ{7 zg=a*Pzo18^*vyDuW&(}~^{!x<#$)0lxO(+FM>+GdjP=bLKL5|goz2h&GXAK1 z4W@g=+M8TDA(Kqu1yZm?Mq(gN1f5b+?l!=Z^zo$kG2-YGR0CQ0(c?_eNG_ND7DXJ$ zE~dCp^*hBt}LSc;R3oRz8Z^95bEQgbmjkWqc3$)zvizu1CX_iV{41 z?-d=KZCY&^eTg1O<+l>EdK2AP&DLj?b(zN|#nc)<1fx1Ko5j-ZQW~n_;n`Sp0e^OoKi(3_sX^9E3mka z-=#H0g^|5|2nxpPk?Atpa+hqv($FYj0lXR0VHsu((3uP4v|IgBc_4|WYP6QfV>rPuPK=&EQV(24H+ z6v;oiZENorlSbGa5}TbG%G8__ze{AmJNNks`YW!RMENC5b@JpgSR~?DiG&R)T7D_Z zBBd+05nH>}*1e<*jOL?Dj%-twS?0|?&L#dLVC;)Sa*R|_gaPPFg46!mwx-&pHij@p|)q(@7t84OsSHUEsvWqC=VL2SX>m!vq1;G5!?1vHn2)qIq zwwGTLM)bg62g6y{<1sKBmSb;-rAN(~Js@a?T4~ifq))>i2jM*i1vc9aQN+xiY6PCg z{wU}nO6SJtJ%64pyTM zEziw9t-LkP_2!X2W+-_p~ppk>!v0vFoDHLz6_+fyYn(k0@1Qm((-MOQhwq zy*MQQ)6E{C#vYkV>frHG^q5~~7Q7J(&-Pa!B%!d5-YC+P)_AGNq=HH2mzZowe|lv? zwG-Xet>>Y_u!FpEn#u+y4h3`5!+h&{heU|S$l8d%?RB&szik{&5`lM>BTp(R=sXTZ z;rB3`B0kcxP9Av#wn4^=1e<7joP1h}FAEeGaxXPB&B?@#vq=hU zPdXgYs;ldxv<3G`Uh9uo$iMov?(WXT4p=$i7AumQkunaq%V@qV!r5E5nxLSin{cIl8 z4dj4qCQ%H>bLbP@$M{BQOqP!aHMA77FY|gyCnG!cF{SyW_JPi(ss(*-twdCg=22W| zEFTWrKrX>&Tb4S%#>i&2@1&$z#&4KVcLf`guM^}44 z1ribt>(uW?LmG{${EYM1#3Z)`WG#tH_GJ;tObyB}>hz(X@)Tzx?O&zKF4<_8^kG|@ zN_q}M1eJFEZaR!Bet_i3I`>Irtz3{2#}HP1RVMzatg=EGv?2&AzZ+~b84Q&1-1kg1 ztp|{twQpKRw_a`-bY9N}*Nr>wytkj(Ali!mc0HPklEjZ`_+52M1<3il5V5Rt&-WF} zHVDSAUg*b{75^j28#fW9^fiM#d58J0{YB1GIZtcI)bPIJV#HA;=mg3lqc8|e%!ZB0 zmPeQs_+|bAT<~fex#j~0mccDQ_bLkt?qn&P(^AQL1}_2gJ}MP+wuHkb`Q~hrDS55H z!Tjb=RNIYyh^^Q!@T^VYu-%okX;f|Jf32pQ#fI{vF(Chq1+x zgV5GCM5bY<3{KCeYGwAMmfyt(Y}S-oXV@#mGSDX&0gV>DpKh5#8l$9uPzK9=4Bch8 zA>jqQVVJ%(ptr&cdC?JqRnPqca9u(=u9*%t-qhKzBamc8Q{^5QvA)+Or*ghg57{O? z(xZIWP35)Rg&sIZJAWj%TOIn?6vMEs5Ddeg^a~!(-|lbT(%ODzr2FlSsmD9rC5Q%5 z2>$NMTS_Nid&`d6%uA{AL2>&#Lb9aEdQbW>G;6=5fFZ1I@}x-<$+qTK!R`RL1B+%f z=>{XnA+XJFWyY9tzjSy*veU44gh#5tQho2<=C#i8ga7q;U{pkie|8vf0>Is4P9Mo) z`;FU)5@Y_zyraZqVHz)|Y@}Wl2(O5*2u7NfpUQUu8&=^gGnbEqm(MrquPGNP-nfA% zrS!k7Y7#0S!pp{lRph8E=#EfW&=4EPA%Ad1QLUTslf^7V|BHHeW)vdc5uk zHl0DSZ>TzLlxuesyhkEB*&7)6$$t-6oz?y1O(}0Y9HdKhtVAb}TGTH_y_$aqwd0-! z`=P=Dm8#A92utoq3|m_%V+fe=Ce+6+s3x%cSing#!yfgj@k9@H4%wjHJk|Ju-a>Mmo?uVX3qhWm*$H{nbsMr1cav4HbvuqyzqV)gi@MN@Q{(F)8}`5 z7Sa{OTlNK&4E!Af;~Djr)pp2!nE_NbHsfT_x{4uA8BRto`g7cv{-{YWD!>J89FrA`c@cZ!s}&3MmGRqRZ1 zBp#|#%2BG-3W05O)$wLH368uP5^r6>kt;;O(CGWPE(UB3F=Lf6Vc8}{C0IfsSyLo0 z82x}m%a>nG_(}t27vc4qlPlV9Woe47GaP9xQbbX2bswR8ECV03!d8h-jr<@aoT+>; zE@CM5iurI4pG?v^h0x7+>&g~uLwN5c5@IPVVJq|VRP?JNEbJW11xU`%kZDRZ)m8o6 zOs|4td!r#%vzc-ooT^HO3dvuG21CJRDBN(2YuVO)8OFq99_Y=j#MVHjHgC2luw=GK z4!-WGI3A@4MqDahg2E>(ca?m+OB*alw4HQ{wN8rOBjKIg=NHcwkBase-;BI?neNZO zPV{#R%a?OH^=5?hd3lSj5d`uq-O3;j_=!<-w|;4v+0j<&u5(I+#iGH6f8dmp9uVIO z^|a)|wdwqzcvr!Mz|a?4#c}j+RAJG9 zBBD&5@1AF5*HX2=;#Z|QzND*ZmQ2X2SRh8-Dk73f$L$WLpvt%348-{OMRw>KTN=yU zK|K7EabfaAa%7G`xPr+*EhLbGDP=3SWjki!8NRMvoBA8|Xz1-OvT~HbUSt;O5xW0{ z{^1r@f+7VmG>St2uQ6`ffgigLO=ZBjqU)maS%0liN`O?<-2FOtqUk4DBlYKv(7Wn) zJHgjYMlCvBYpj3$NK*l1s&MA>Z=1$0*Tz!qi=wiGV$9aE#Ct2WtXyOki8)jlPi3cH zl-8?0&V#slM&`$L`UlX5U!TQPwVJrYrduUpL7S`InxOmWS+1fdY9>U|z>NFys2`WyqwR!sN8wL%pI-FSh) z{`9}$b;?Y!YT>{{jjS^vZBE^{044fLpp9UeqXh=ldEC$&Qia+;M$_2T|6`!3-w)|2 zRvRnV?LLVU)7ym72%4#16{EtyF5CTI*HGi-*nBcgTJ!z6OqMy!+we7yhZ)=By)m#! zr(WbKvLdI!2>8lHwGEcG^~bP^Pxk%*Nd5Y~K-Q~uqLa;!{@X(9q1|83>v;_f zxi^-W#R`Q@;*+n30GKgT?tF;D_Jwp~GOG;E+-#8Fe;1O(1mj*+(2x8tw?31+ zDkJz`&~m-emf&jH@%o8W(cJmfyWaQ^>z)IfnL<|k1rw%91imIb{Rn)GKn$Ls>C7i7 zknCpmVY9jMW=SS4`3whm`Ilp@!eypgg zo8d$S!5v?`M45i~7TD&(aEKMYx=B2(sR;cAbz)WDZt20VZs!jUi#&TR=%k@?D{;a=V zq&irQ8cx5$u6PL(5n-Bhwm_+_{XX)ue2{h);jl2NAZO=LUZ@>D6t2mNu8*RMCofEG zSl8Mq5bpOWfW$KX2R8Dvu-v~(Nh!12*a*iGpNG39{e!Nm$)q%7WLID>4s zm%}bPIhatm>A5A0|Dg$5!%yM{m*EK}tADv9|JxNgR8nAZhb#F$ocP0OVC19m zRb3oL-bqXA3pn#9WTKK~B$hi8E_WS}>GhjEC;%@l3O*6&gi10HPx}3t65ItYFuaVz zhMU>v_9ap%_)jzt;Zg;1Sm>jeLbE0&FV>-1JM-^#AWO0Ta*b&F({c1B-Qs*rN%XM) zlS5R7uE&>LByS)+`S)7P&;Wser`uY9QqQkm*FdR@*Fg4+_lp~(6Do|=W9-m79D`l9 zjAxrP6mTZ+4_j>!5+Qpk+cirmJ55lPd?0I`v8_lS?rU6<8z3V<(4CEm3smAezvtp@ zE>c$E%X-{wB5Z$!j^J>t)+NOo_yh288NlC50QdsNI5$Sj1&8RDn(^W?vO^#w(QlXq z+xhYSE6Q!Y{Nyjq&H~w$Vv!@Tqky3$kGsF0gy?TV&7#KjrG(jR%`xt48NSZ_>5bv+dbXQ@4?Q;bh zQh*U!zE;=XGv;^pSkFH)IXZA%7RJm@mOzUQD)ufrN{=TXhATkCQ!Pvuo%mZ)`5&i- z_a7^%L<`2pWmKqrYyH3MIb)mk^^*Xc&xsjGza`)xk$YEy^rK;65-xqCaT!Lmq2!1D z`@Gwpt%SY* zWz(VJvcFcua{sRt{Z=0ME8AN9Tn-9NuV;}M?L7GDYO}(^?b`n*t@Q8=Q9`JT)bDxtw>!v+~*Z;&w5&eT9vi0VA85F4U`ob!td+qm}U4Sv*9Rc=gmYf?1YTSo^g(ZKK*q?7+(e%?5A74Ia7<*ji6bC;&MK?&8 zbdieAdDuis9SVknk|uwZpf75cz@~}y0(1kNX|4J2MWQO-GVe&-cERvncynMdyF~wC zFuFI87dg_qV*sSnclY^7|86iyTb0#(Ikr4i-F?geWEB~NMghjv#u2gfz;Wd)G0t$x z`wjd^d-RqR5e>Lp=up1Crmbe{4Jsw3b8p3uR}=&yAAoNH+}`|ny6fvrWDWdHuNxsm zWB?S$W6+71e)e7Y|FPER;$t|VDoe6_AX|NUY_7k5`l0=p;eO>R=O3mDnY*I3gm-gM zNFEpc?Mh8UeVe?=ZdprjyU>3A8&JLbB_HubBh7^oL#DDS3bo~m0$2s`8u;Cyz<2Vi zGd7@MHSnVo8mq(cbVpU?P+Yt$yy69CEKMGv@tXcGd)e{>tkRz#*OOJ-ULG?^sEnl= z3e1IRi!v`ArmK}WQR$j_)fO!e#E!V*G&rgnwwh&d8o-IpLtpbU-G(TQk_pE~)#d`i zq=CPa?!O@J6S>UQd}GuZPCCLb>kppyc*&Z~#s7~my@j}?-{*I{lW^e9y$$d#BRV`S z63c6nTE%`R_j-1D{KP0=@=p2^))(SiCXezdK_1D3$F38y^uK*LH1PahS!<(+@*d}f znnGE7ZoD5uIKV%%)f@%@({8O|zGVV^?PT0dnqR@Re@ywxTWuJn^hJtlX(h-ht260iZHTYI)i3mXP#I7Sa4sSMG+= z2uQ04n8`8yRN4QH&p~onkD9IZx9e;ND_nRc9+Tvm>MslgX-2Ng0?C^d8VMi|H`24` zZ!!1`p&`RFXfu{^k-$GgB40=(!(>BXkC!6Lgn)4EN=w&#v-)2L3CPJ=B!HqODE$GR z0~g>41@9DU%671x2a8P+VAkWGBiFksIrz=DQPlPs>LDmxU5AJ$&GQj+pbCIfiWNT3 zQ9-U^QUdaQuv^#x`M3WCJxtcdvWuy4EOY;ei9k^LjW~}^m*T8Ji?N;Pa z^FYjmTh-0?Z6D0txrouU##~UVjla--L}R$I&k#*4&6$GGu3T+TnMj^Jh`%z6Jy>vT zUL4GiDx<2K!7Hofkw%GhodI_sO2-_Y5fQcA=Qk9O6ua8W@}K@~5KZOA`97(V+78i7 z4E#@X`l_(d#k;z6Q8;pa0(oSY;@%1uC6OZF(4(R*Ud6EFdJP7z&Y3s2dN$enI>>2# z;{g%$#SyVmxOcVx6RpaZ^N0)m32CIx-paPVeWDT_1_)$y5VOKbAj+UzkNJ;z!fb>-L(?fTVXZKfUv}g35=&hctq+Xk z^qJ48u!ISzSu-K;SEKL0j%fGB{&5^DYFsUJ`}Rr@;UI zYg{zmfJ>uduB<;ceRImv_J2B8JxDZRJ{>QiAFGT%18OKEWH%3VxUW2?h{{$_z6N)*WZ~WpG zF%~(9JQ^eZm8l<8{C^u_^KkI*^2Qsy76HG?ENIx686Ezh7u3oeIaOP8L^GhtXYo z;SU~Y$b7n`sa>UW2btghVy83jcyoXgRk0n_Yz#`txZ`tCO95%hjei5HB)QbtOp!TGP(ej!MIM3r`}Heaf24Ci!#h^#2n3rYNsr7x z-3mm4x|3VR=|r~W|7iz=fLBBtM4x$>jFJ7cx+#I)pY__=&yRDHM?1!80sc6ESesJ* zN7#pY9`06jz>b1+K^d8Mv)&sOH$x9ZS-REv+)1YinfET| ztRLhfu*x10*e;bNjOr7`U!oK|PXmSCCt_9+om%tyCLljCZyklvzOl4mQe$4t*&1kuee?2I8thFhJhSjdd`C*F}+ zJL{q%IWp=xzb109pM9dRwx+J1&Azp?$g`@2IV5hkYd6@5MiHIUt+jv9t&~09<+@72 zE+Q~+V?iGI2eAsGQ0`<2h*g9e+DCT3v-4%B?8{t(WHF%LujlcV*}m~ZfFzMcDC!(R ze31do(B<=FC3&(c{!^RdZ`%@yUFJ_;0!FQpGP!Fb{V*~lh4$(? zaz&n(%mAS3TAYXJKfuIDXkZ(aET!&MS<0J8a-cH>C-p4!5^7u@s?BA|7#o+m8-|_d zrTp`^!N8S(0oLJR1IOH7h0gsveybYrt!%Lw{<4G z=_1R9YwUL>Mc~;>@09Zccev&<<4m7EBZ2PY@GuEQaS-XFaO6!8#v)5~NbEtD?m-5^ z-WDJ0qSdZ*l2%nK{A79$C>b9kYe>HugtzqbImNV2ACV?DMU@7rC11?mY`>` zRYaatJ!bR{WX}I@!5fE&Tz3$oYOm@WqQDSj0^vr7ZTKvJf;h4DCt%C3v!p$|G&-p; z!fwDD*hW}oY~XF$8!vTe7ccHa2DDXsOox^_+sOnzNk9sMj)u$7^xB)N=r7EK{8@oS z_J7Fx`A2@O7)8BFQ>>__`LY@kK;(PD0!lt1ahEuZ!mC$47xHKVutZ`t()<37l)90S zVkL%e&?iv_!UG?9WDZv~!GZ7NhKrdC{8+0_$BUx<8{dSh%7U zj+%S7z+lkWy?}`474-W<3j-kOtbC~!Q_5y#3EXe)P z`7Ha;+H;`_dTHOfvBq?#k^`!{ksKtEl{$(`@1OGi4k!wOW5&}hn4i1iQ)k)SRkoIn zDYF849W2QtUOY6yl|=fo^uo=|x9j*gOsO?TRL2|BPv#3lobm4=X?%_bG!-*wMel?_ z=Dp?NVzh0^GZFTx9wTd2&;5;i`bE3q`@3JdtWh70A4u;{)|@rzo2HdApm|LvDw};5 z=GYuvHO#nj^AvN}m-Uso+>*bA>f36L=&y&Yj+HaT-KZ~l9Ee^He<5o|0S_4Cy}!vp zXh|pyg>7cR zHUuPVf<#>=!#0e8Nb+WBxW5&)h6P}u`I8ZNyn?RHu9vj}Zk0NESE;S}#r_OBJH;~g z`b=^4(J=v!_iHepHZ%=GHh_+wIq+Vq^BV#{JU{V>v%l z-Vool7AaI>d8c1Z>$<)-)v~g_CS2qHjM9CsJiL#GxfxWmV=(nmhF+)FSYP@*aEIxD zrW<6v_t0N*qrA&+c%MkODJHm_sS=~4dJ zGzBHo1lk7h_^V8emfKK)WA; zs%KK&3n$lk`*%O88JpKVy_TM=Z?qJdc71=yNM Sx9po1`Lh?X>hK88eAdL;7ZtT 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 M + Worldshaper Request System Flowchart + Flowchart showing the Worldshaper request system from public submission through automated analysis, review, and promotion into active requests. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Worldshaper Request System + From public submission through review and promotion into the Active request list + + + PUBLIC ENTRY + 1. User submits request + Launcher Requests tab posts raw submission text. + + + + + API WRITE + 2. Server stores pending request + `POST /api/launcher-requests` normalizes the record, + sets `status = pending`, and preserves `sourceText`. + + + + + QUEUE STATE + 3. Request sits in the pending queue + It keeps fallback title, timestamps, and raw source text + until automated or manual analysis begins. + + + + + 4. What starts analysis? + Autorun, admin queue trigger, + or manual resubmission + + + + + WORKER PICKUP + 5. Worker selects eligible pending requests + It targets pending records whose analysis state is still + unprocessed, then marks them as `processing`. + + + + + ROUTING PASS + 6. Route the request into Worldshaper terms + Map loose wording onto standardized tags, likely systems, + and likely modules before the deeper analysis call. + + + + + KB RETRIEVAL + 7. Load only relevant KB sections + The worker pulls matching systems, focused modules, + terminology hints, and standardized tag definitions. + + + + + ANALYSIS PASS + 8. Produce structured request items + Split the submission into atomic items, then generate + title, category, tags, interpretation, implementation path, + review rationale, options, and confidence. + + + + + 9. Can it auto-promote? + Every item must be `active`, + meet confidence threshold, + and avoid high ambiguity + + NO + YES + + + + PROMOTION + 10A. Promote analyzed items + `process-analysis` runs with `action = promote` + and replaces the pending request with active rows. + + + + 11A. Public board lists them as Active + + + + REVIEW HOLD + 10B. Save review metadata on the pending request + The original request stays pending with routing, + analysis items, rationale, and possible options attached. + + + + 11B. Admin reviews, edits, or resubmits + + + Approve or rerun + diff --git a/docs/kb/systems/request-board.md b/docs/kb/systems/request-board.md index f18406b..a119af9 100644 --- a/docs/kb/systems/request-board.md +++ b/docs/kb/systems/request-board.md @@ -41,6 +41,7 @@ Request records can now contain: - Depends on `Launcher Home` for the public site presentation. - Depends on the KB under `docs/kb/` for model-grounded request parsing. - Depends on the request-analysis worker for automated triage. +- Flow reference: `docs/kb/request-system-flowchart.md` ## Triage Hints diff --git a/src/App.tsx b/src/App.tsx index 39781d5..3339225 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -4,7 +4,7 @@ import ConfigSection from "./components/ConfigSection"; import EditorToolbar from "./components/EditorToolbar"; import StatusFooter from "./components/StatusFooter"; import TopNavTabs from "./components/TopNavTabs"; -import { openWorldshaperStudioWindow } from "./worldshaperStudio/windowing"; +import { openWorldshaperStudioWindow } from "./shared/windowing"; import { CONFIG_TAB_TO_KEY, DIALOGUE_NODE_FIELD_ORDER, diff --git a/src/WorldshaperLauncher.tsx b/src/WorldshaperLauncher.tsx index f2a2c02..893a47b 100644 --- a/src/WorldshaperLauncher.tsx +++ b/src/WorldshaperLauncher.tsx @@ -1,2308 +1 @@ -import type { CSSProperties } from "react"; -import { useEffect, useState } from "react"; -import { openWorldshaperStudioWindow } from "./worldshaperStudio/windowing"; -import { - CHANGELOG_SECTIONS, - CHANGELOG_SPLASH_FOOTNOTE, - CHANGELOG_SPLASH_KICKER, - CHANGELOG_SPLASH_TITLE, - CHANGELOG_SPLASH_VERSION, -} from "./worldshaperStudio/changelogData"; -import type { ChangelogItem } from "./worldshaperStudio/changelogData"; -import launcherBackground from "../background.png"; - -declare const __APP_BUILD__: string; - -type WorldDefaultPayload = { - worldId?: string; - world?: { - id?: string; - }; -}; - -type LaunchState = "ready" | "opening" | "opened" | "blocked" | "error"; -type BoardTab = "news" | "requests"; -type LauncherWindowMode = "public" | "admin"; -type LauncherRequestStatus = "pending" | "active" | "implemented"; -type AdminDetailTab = "routing" | "analysis"; - -type LauncherRequestAnalysisRouting = { - summary?: string; - ambiguity?: "low" | "medium" | "high"; - matchedTerms?: string[]; - suggestedTags?: string[]; - suggestedSystems?: string[]; - suggestedModules?: string[]; - rationale?: string; - possibleDirections?: string[]; - kbSections?: string[]; -}; - -type LauncherRequest = { - id: string; - sourceSubmissionId?: string; - title: string; - status: LauncherRequestStatus; - category: string; - tags: string[]; - sourceText: string; - summary: string; - implementationNotes: string; - analysis?: { - state?: "unprocessed" | "processing" | "processed" | "needs_review" | "error"; - confidence?: number | null; - model?: string; - createdAt?: string; - updatedAt?: string; - error?: string; - submissionId?: string; - sourceTextSnapshot?: string; - routing?: LauncherRequestAnalysisRouting; - itemCount?: number; - items?: Array<{ - title?: string; - primaryCategory?: string; - tags?: string[]; - statusRecommendation?: string; - parsedInterpretation?: string; - implementationApproach?: string; - affectedSystems?: string[]; - affectedFiles?: string[]; - problemType?: string; - rawExcerpt?: string; - confidence?: number | null; - reviewRationale?: string; - reviewOptions?: string[]; - notes?: string; - }>; - }; - createdAt: string; - updatedAt: string; -}; - -type LauncherRequestAnalysisItem = NonNullable["items"]>[number]; - -type LauncherRequestsPayload = { - requests?: LauncherRequest[]; -}; - -type RecentSaveEvent = { - at?: string; - type?: string; - requestId?: string; - textPreview?: string; - status?: string; - category?: string; - itemCount?: number; - model?: string; - reason?: string; - provider?: string; - pid?: number; - code?: number | null; - signal?: string; - error?: string; -}; - -type RecentSaveEventsPayload = { - saves?: RecentSaveEvent[]; -}; - -type ProcessPendingPayload = { - ok?: boolean; - launched?: boolean; - reason?: string; - autorunEnabled?: boolean; - configured?: boolean; - queuedPendingCount?: number; - pid?: number; -}; - -type RequeueAnalysisPayload = { - ok?: boolean; - launched?: boolean; - reason?: string; - request?: LauncherRequest; - requests?: LauncherRequest[]; - requestId?: string; - queuedPendingCount?: number; - pid?: number; -}; - -type LauncherRequestMetaPayload = { - allowedTags?: string[]; -}; - -type AdminAuthPayload = { - ok?: boolean; - accessGranted?: boolean; - adminConfigured?: boolean; - error?: string; -}; - -const DEFAULT_EDITOR_WORLD_ID_FALLBACK = "overworld"; - -function readLauncherWindowMode(): LauncherWindowMode { - if (typeof window === "undefined") { - return "public"; - } - const searchParams = new URLSearchParams(window.location.search); - return searchParams.get("admin") === "requests" ? "admin" : "public"; -} - -function normalizeStringList(values: string[]): string[] { - return Array.from(new Set( - values - .map((entry) => String(entry || "").trim()) - .filter(Boolean), - )).sort((left, right) => left.localeCompare(right)); -} - -function appendUniqueString(values: string[], value: string): string[] { - const normalizedValue = String(value || "").trim(); - if (!normalizedValue) { - return normalizeStringList(values); - } - return normalizeStringList([...values, normalizedValue]); -} - -function removeStringValue(values: string[], value: string): string[] { - const normalizedValue = String(value || "").trim().toLowerCase(); - return normalizeStringList(values.filter((entry) => entry.trim().toLowerCase() !== normalizedValue)); -} - -function toggleStringSelection(current: string[], value: string): string[] { - return current.includes(value) - ? current.filter((entry) => entry !== value) - : [...current, value].sort((left, right) => left.localeCompare(right)); -} - -function normalizeSearchText(value: string): string { - return String(value || "").replace(/\s+/g, " ").trim().toLowerCase(); -} - -function extractRoutingTerms(requestEntry: LauncherRequest): string[] { - const tagTerms = requestEntry.tags - .map((entry) => String(entry || "").trim()) - .filter(Boolean); - if (tagTerms.length > 0) { - return tagTerms.slice(0, 6); - } - const seen = new Set(); - const stopWords = new Set([ - "the", "and", "for", "with", "that", "this", "from", "into", "have", "need", - "want", "make", "more", "just", "like", "does", "dont", "cannot", "should", - "would", "could", "about", "because", "there", "their", "they", "them", "then", - "than", "over", "under", "your", "while", "where", - ]); - const matches = `${requestEntry.title} ${requestEntry.sourceText}`.match(/[A-Za-z][A-Za-z0-9/-]{2,}/g) || []; - return matches - .map((entry) => entry.trim()) - .filter((entry) => { - const normalized = entry.toLowerCase(); - if (stopWords.has(normalized) || seen.has(normalized)) { - return false; - } - seen.add(normalized); - return true; - }) - .slice(0, 6); -} - -function buildRoutingSummaryFallback(requestEntry: LauncherRequest, firstItem: LauncherRequestAnalysisItem | null): string { - const normalizedSummary = String(requestEntry.summary || "").trim(); - if (normalizedSummary && normalizedSummary !== "Awaiting parsing and categorization.") { - return normalizedSummary; - } - if (firstItem?.parsedInterpretation) { - return String(firstItem.parsedInterpretation).trim(); - } - const normalizedSource = String(requestEntry.sourceText || "").replace(/\s+/g, " ").trim(); - if (normalizedSource) { - return normalizedSource.length > 220 - ? `${normalizedSource.slice(0, 217).trim()}...` - : normalizedSource; - } - return "No routing summary has been stored yet."; -} - -function buildFallbackRouting(requestEntry: LauncherRequest): LauncherRequestAnalysisRouting { - const firstItem = Array.isArray(requestEntry.analysis?.items) ? requestEntry.analysis.items[0] : null; - const routedTags = Array.isArray(firstItem?.tags) && firstItem.tags.length > 0 - ? firstItem.tags - : requestEntry.tags.length > 0 - ? requestEntry.tags - : (requestEntry.category && requestEntry.category !== "Unsorted" ? [requestEntry.category] : []); - const likelySystems = Array.isArray(firstItem?.affectedSystems) && firstItem.affectedSystems.length > 0 - ? firstItem.affectedSystems - : (routedTags.length > 0 ? routedTags : []); - const possibleDirections = Array.isArray(firstItem?.reviewOptions) && firstItem.reviewOptions.length > 0 - ? firstItem.reviewOptions - : (requestEntry.implementationNotes.trim() ? [requestEntry.implementationNotes.trim()] : []); - return { - summary: buildRoutingSummaryFallback(requestEntry, firstItem), - ambiguity: requestEntry.status === "pending" ? "medium" : "low", - matchedTerms: extractRoutingTerms(requestEntry), - suggestedTags: Array.isArray(routedTags) ? routedTags : [], - suggestedSystems: likelySystems, - suggestedModules: [], - rationale: String( - firstItem?.reviewRationale - || requestEntry.implementationNotes - || `Routing was reconstructed from the saved request title, tags, and submission text for "${requestEntry.title}".` - ).trim(), - possibleDirections, - kbSections: [], - }; -} - -function mergeRoutingWithFallback( - requestEntry: LauncherRequest, - existingRouting: LauncherRequestAnalysisRouting | undefined, -): LauncherRequestAnalysisRouting { - const fallbackRouting = buildFallbackRouting(requestEntry); - const normalizedRouting = existingRouting || {}; - return { - summary: String(normalizedRouting.summary || "").trim() || fallbackRouting.summary, - ambiguity: normalizedRouting.ambiguity || fallbackRouting.ambiguity, - matchedTerms: Array.isArray(normalizedRouting.matchedTerms) && normalizedRouting.matchedTerms.length > 0 - ? normalizedRouting.matchedTerms - : fallbackRouting.matchedTerms, - suggestedTags: Array.isArray(normalizedRouting.suggestedTags) && normalizedRouting.suggestedTags.length > 0 - ? normalizedRouting.suggestedTags - : fallbackRouting.suggestedTags, - suggestedSystems: Array.isArray(normalizedRouting.suggestedSystems) && normalizedRouting.suggestedSystems.length > 0 - ? normalizedRouting.suggestedSystems - : fallbackRouting.suggestedSystems, - suggestedModules: Array.isArray(normalizedRouting.suggestedModules) && normalizedRouting.suggestedModules.length > 0 - ? normalizedRouting.suggestedModules - : fallbackRouting.suggestedModules, - rationale: String(normalizedRouting.rationale || "").trim() || fallbackRouting.rationale, - possibleDirections: Array.isArray(normalizedRouting.possibleDirections) && normalizedRouting.possibleDirections.length > 0 - ? normalizedRouting.possibleDirections - : fallbackRouting.possibleDirections, - kbSections: Array.isArray(normalizedRouting.kbSections) && normalizedRouting.kbSections.length > 0 - ? normalizedRouting.kbSections - : fallbackRouting.kbSections, - }; -} - -function hydrateLauncherRequestForUi(requestEntry: LauncherRequest): LauncherRequest { - const nextRequest = cloneLauncherRequest(requestEntry); - nextRequest.analysis = { - ...(nextRequest.analysis || {}), - createdAt: nextRequest.analysis?.createdAt || nextRequest.createdAt, - updatedAt: nextRequest.analysis?.updatedAt || nextRequest.updatedAt, - itemCount: nextRequest.analysis?.itemCount ?? (Array.isArray(nextRequest.analysis?.items) ? nextRequest.analysis?.items.length : 0), - items: Array.isArray(nextRequest.analysis?.items) ? nextRequest.analysis.items : [], - routing: mergeRoutingWithFallback(nextRequest, nextRequest.analysis?.routing), - }; - return nextRequest; -} - -function buildRequestSearchCorpus(requestEntry: LauncherRequest): string { - return normalizeSearchText([ - requestEntry.title, - requestEntry.category, - requestEntry.tags.join(" "), - requestEntry.sourceText, - requestEntry.summary, - requestEntry.implementationNotes, - requestEntry.analysis?.routing?.summary, - requestEntry.analysis?.routing?.rationale, - ...(Array.isArray(requestEntry.analysis?.routing?.matchedTerms) ? requestEntry.analysis?.routing?.matchedTerms : []), - ...(Array.isArray(requestEntry.analysis?.items) - ? requestEntry.analysis.items.flatMap((item) => [ - item.title, - item.primaryCategory, - item.parsedInterpretation, - item.implementationApproach, - ...(Array.isArray(item.tags) ? item.tags : []), - ]) - : []), - ].filter(Boolean).join(" ")); -} - -function matchesRequestFilterToken(requestEntry: LauncherRequest, token: string): boolean { - if (token === "pending") { - return requestEntry.status === "pending"; - } - if (token === "queued") { - return isQueuedPendingRequest(requestEntry); - } - if (token === "review") { - return isNeedsReviewRequest(requestEntry); - } - if (token === "active") { - return requestEntry.status === "active"; - } - if (token === "implemented") { - return requestEntry.status === "implemented"; - } - return false; -} - -function requestMatchesFilters( - requestEntry: LauncherRequest, - searchText: string, - statusSelections: string[], - tagSelections: string[], -): boolean { - const normalizedSearchText = normalizeSearchText(searchText); - if (normalizedSearchText && !buildRequestSearchCorpus(requestEntry).includes(normalizedSearchText)) { - return false; - } - if (statusSelections.length > 0 && !statusSelections.some((token) => matchesRequestFilterToken(requestEntry, token))) { - return false; - } - if (tagSelections.length > 0 && !tagSelections.some((tag) => requestEntry.tags.includes(tag))) { - return false; - } - return true; -} - -function FilterIcon() { - return ( - - ); -} - -function LogsIcon() { - return ( - - ); -} - -function SaveIcon() { - return ( - - ); -} - -function CheckIcon() { - return ( - - ); -} - -function PlayIcon() { - return ( - - ); -} - -type LauncherChipSelectorProps = { - label: string; - values: string[]; - options: string[]; - placeholder: string; - emptyLabel?: string; - onAdd: (value: string) => void; - onRemove: (value: string) => void; -}; - -function LauncherChipSelector({ - label, - values, - options, - placeholder, - emptyLabel = "No tags selected yet.", - onAdd, - onRemove, -}: LauncherChipSelectorProps) { - const availableOptions = options.filter((option) => !values.includes(option)); - return ( -
    -
    - {label} - -
    -
    - {values.length > 0 ? values.map((value) => ( - - {value} - - - )) : ( - {emptyLabel} - )} -
    -
    - ); -} - -async function resolveDefaultWorldId(): Promise { - const response = await fetch("/api/world-default"); - if (!response.ok) { - throw new Error(`Failed to load default world (${response.status}).`); - } - const payload = await response.json() as WorldDefaultPayload; - const resolvedWorldId = String(payload.worldId || payload.world?.id || "").trim(); - return resolvedWorldId || DEFAULT_EDITOR_WORLD_ID_FALLBACK; -} - -async function fetchJsonOrThrow(input: RequestInfo | URL, init?: RequestInit): Promise { - const response = await fetch(input, init); - if (!response.ok) { - let detail = `Request failed (${response.status}).`; - try { - const payload = await response.json() as { error?: string }; - detail = String(payload?.error || detail); - } catch { - // Ignore JSON parse failures and fall back to status text. - } - throw new Error(detail); - } - return response.json() as Promise; -} - -function buildAdminHeaders(password: string, headers?: HeadersInit): HeadersInit { - const normalizedPassword = String(password || "").trim(); - if (!normalizedPassword) { - return { - ...(headers || {}), - }; - } - return { - ...(headers || {}), - "x-worldshaper-admin-password": normalizedPassword, - }; -} - -function isAdminAccessError(error: unknown): boolean { - const text = String(error || "").toLowerCase(); - return text.includes("admin access denied") - || text.includes("admin access is not configured"); -} - -function cloneLauncherRequest(requestEntry: LauncherRequest): LauncherRequest { - return JSON.parse(JSON.stringify(requestEntry)) as LauncherRequest; -} - -function formatRequestTimestamp(value: string): string { - const parsed = Date.parse(String(value || "")); - if (!Number.isFinite(parsed)) { - return "Saved recently"; - } - return new Intl.DateTimeFormat(undefined, { - month: "short", - day: "numeric", - hour: "numeric", - minute: "2-digit", - }).format(parsed); -} - -function formatRequestSubmittedDate(value: string): string { - const parsed = Date.parse(String(value || "")); - if (!Number.isFinite(parsed)) { - return "Recently"; - } - return new Intl.DateTimeFormat(undefined, { - month: "short", - day: "numeric", - year: "numeric", - }).format(parsed); -} - -function normalizeAnalysisState(value: string | undefined): string { - return String(value || "").trim().toLowerCase(); -} - -function formatAnalysisStateLabel(value: string | undefined): string { - const normalized = normalizeAnalysisState(value); - if (normalized === "processing") { - return "Processing"; - } - if (normalized === "processed") { - return "Processed"; - } - if (normalized === "needs_review") { - return "Needs Review"; - } - if (normalized === "error") { - return "Error"; - } - return "Unprocessed"; -} - -function getRequestDisplayStateLabel(requestEntry: LauncherRequest): string { - if (requestEntry.status === "implemented") { - return "Implemented"; - } - if (requestEntry.status === "active") { - return "Active"; - } - const analysisState = normalizeAnalysisState(requestEntry.analysis?.state); - if (!analysisState || analysisState === "unprocessed") { - return "Queued"; - } - if (analysisState === "needs_review") { - return "Needs Review"; - } - if (analysisState === "error") { - return "Analysis Error"; - } - if (analysisState === "processed") { - return "Reviewed"; - } - return formatAnalysisStateLabel(analysisState); -} - -function getRequestDisplayStateClassName(requestEntry: LauncherRequest): string { - if (requestEntry.status === "implemented") { - return "implemented"; - } - if (requestEntry.status === "active") { - return "active"; - } - const analysisState = normalizeAnalysisState(requestEntry.analysis?.state); - if (!analysisState || analysisState === "unprocessed") { - return "queued"; - } - if (analysisState === "needs_review") { - return "needs-review"; - } - if (analysisState === "error") { - return "error"; - } - if (analysisState === "processed") { - return "processed"; - } - if (analysisState === "processing") { - return "processing"; - } - return "pending"; -} - -function isQueuedPendingRequest(requestEntry: LauncherRequest): boolean { - const analysisState = normalizeAnalysisState(requestEntry.analysis?.state); - return requestEntry.status === "pending" && (!analysisState || analysisState === "unprocessed" || analysisState === "processing"); -} - -function isNeedsReviewRequest(requestEntry: LauncherRequest): boolean { - const analysisState = normalizeAnalysisState(requestEntry.analysis?.state); - return requestEntry.status === "pending" && (analysisState === "needs_review" || analysisState === "error"); -} - -function formatEventLabel(event: RecentSaveEvent): string { - switch (String(event.type || "").trim()) { - case "launcher-request-add": - return "Request submitted"; - case "launcher-request-delete": - return "Request deleted"; - case "launcher-request-update": - return "Request updated"; - case "launcher-request-review": - return "Analysis saved for review"; - case "launcher-request-promote": - return "Pending request promoted"; - case "launcher-request-analysis-error": - return "Analysis failed"; - case "launcher-request-analysis-launch": - return "Queue worker launched"; - case "launcher-request-analysis-finish": - return "Queue worker finished"; - case "launcher-request-analysis-launch-error": - return "Queue worker launch error"; - case "launcher-request-analysis-requeue": - return "Request requeued for review"; - default: - return String(event.type || "Event"); - } -} - -function formatEventDetail(event: RecentSaveEvent): string { - const parts = [ - event.requestId ? `Request ${event.requestId}` : "", - event.category ? `Category ${event.category}` : "", - event.status ? `Status ${event.status}` : "", - event.itemCount ? `${event.itemCount} item${event.itemCount === 1 ? "" : "s"}` : "", - event.provider ? `Provider ${event.provider}` : "", - event.model ? `Model ${event.model}` : "", - event.reason ? `Reason ${event.reason}` : "", - event.pid ? `PID ${event.pid}` : "", - Number.isFinite(Number(event.code)) ? `Exit ${event.code}` : "", - event.signal ? `Signal ${event.signal}` : "", - event.error ? String(event.error) : "", - event.textPreview ? `Preview: ${event.textPreview}` : "", - ].filter(Boolean); - return parts.join(" • "); -} - -function openStudioPopup(worldId: string): boolean { - const popup = openWorldshaperStudioWindow(worldId, window, { worldId }); - return Boolean(popup); -} - -function openRepo(): void { - window.location.assign("https://repo.andraxion.net/"); -} - -function openAdminPanelWindow(): boolean { - const nextUrl = new URL(window.location.href); - nextUrl.searchParams.set("admin", "requests"); - nextUrl.searchParams.set("tab", "requests"); - const popup = window.open(nextUrl.toString(), "worldshaper-admin-panel", "popup=yes,width=1620,height=980,resizable=yes,scrollbars=yes"); - if (popup) { - popup.focus(); - } - return Boolean(popup); -} - -function WorldshaperLauncher() { - const launcherWindowMode = readLauncherWindowMode(); - const adminWindowMode = launcherWindowMode === "admin"; - const [launchState, setLaunchState] = useState("ready"); - const [error, setError] = useState(""); - const [worldId, setWorldId] = useState(DEFAULT_EDITOR_WORLD_ID_FALLBACK); - const [activeBoardTab, setActiveBoardTab] = useState(adminWindowMode ? "requests" : "news"); - const [requests, setRequests] = useState([]); - const [requestsLoading, setRequestsLoading] = useState(true); - const [requestsError, setRequestsError] = useState(""); - const [requestDraftOpen, setRequestDraftOpen] = useState(false); - const [requestDraft, setRequestDraft] = useState(""); - const [requestSubmitting, setRequestSubmitting] = useState(false); - const [requestMutatingId, setRequestMutatingId] = useState(""); - const [requestSearchText, setRequestSearchText] = useState(""); - const [requestFilterMenuOpen, setRequestFilterMenuOpen] = useState(false); - const [requestStatusFilters, setRequestStatusFilters] = useState([]); - const [requestTagFilters, setRequestTagFilters] = useState([]); - const [allowedRequestTags, setAllowedRequestTags] = useState([]); - const [expandedRequestIds, setExpandedRequestIds] = useState([]); - const [adminAccessGranted, setAdminAccessGranted] = useState(false); - const [adminPassword, setAdminPassword] = useState(""); - const [adminPasswordDraft, setAdminPasswordDraft] = useState(""); - const [adminAuthSubmitting, setAdminAuthSubmitting] = useState(false); - const [adminPasswordError, setAdminPasswordError] = useState(""); - const [selectedAdminRequestId, setSelectedAdminRequestId] = useState(""); - const [selectedAdminAnalysisIndex, setSelectedAdminAnalysisIndex] = useState(0); - const [adminSearchText, setAdminSearchText] = useState(""); - const [adminFilterMenuOpen, setAdminFilterMenuOpen] = useState(false); - const [adminStatusFilters, setAdminStatusFilters] = useState([]); - const [adminTagFilters, setAdminTagFilters] = useState([]); - const [adminEditorDraft, setAdminEditorDraft] = useState(null); - const [adminDetailTab, setAdminDetailTab] = useState("routing"); - const [adminSaving, setAdminSaving] = useState(false); - const [recentSaveEvents, setRecentSaveEvents] = useState([]); - const [logsLoading, setLogsLoading] = useState(false); - const [logsModalOpen, setLogsModalOpen] = useState(false); - const [logsError, setLogsError] = useState(""); - const [queueTriggering, setQueueTriggering] = useState(false); - const [requeueingMode, setRequeueingMode] = useState<"" | "saved" | "draft">(""); - const [adminNotice, setAdminNotice] = useState(""); - const adminPanelOpen = adminWindowMode; - - useEffect(() => { - let cancelled = false; - void resolveDefaultWorldId() - .then((resolvedWorldId) => { - if (cancelled) { - return; - } - setWorldId(resolvedWorldId); - }) - .catch(() => { - if (cancelled) { - return; - } - setWorldId(DEFAULT_EDITOR_WORLD_ID_FALLBACK); - }); - - return () => { - cancelled = true; - }; - }, []); - - async function loadRequests(options?: { silent?: boolean }): Promise { - const silent = options?.silent === true; - if (!silent) { - setRequestsLoading(true); - } - try { - const payload = await fetchJsonOrThrow("/api/launcher-requests"); - setRequests(Array.isArray(payload.requests) ? payload.requests.map(hydrateLauncherRequestForUi) : []); - setRequestsError(""); - } catch (nextError: unknown) { - setRequestsError(String(nextError || "Failed to load requests.")); - } finally { - if (!silent) { - setRequestsLoading(false); - } - } - } - - async function loadRequestMeta(): Promise { - try { - const payload = await fetchJsonOrThrow("/api/launcher-request-meta"); - setAllowedRequestTags(Array.isArray(payload.allowedTags) ? payload.allowedTags : []); - } catch { - setAllowedRequestTags([]); - } - } - - async function loadRecentSaveEvents(): Promise { - setLogsLoading(true); - try { - const payload = await fetchJsonOrThrow("/api/debug/recent-saves", { - headers: buildAdminHeaders(adminPassword), - }); - setRecentSaveEvents(Array.isArray(payload.saves) ? payload.saves : []); - setLogsError(""); - } catch (nextError: unknown) { - if (isAdminAccessError(nextError)) { - setAdminAccessGranted(false); - } - setLogsError(String(nextError || "Failed to load admin logs.")); - } finally { - setLogsLoading(false); - } - } - - async function verifyAdminPassword(password: string): Promise { - const payload = await fetchJsonOrThrow("/api/admin/auth-check", { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ password }), - }); - if (!payload.accessGranted) { - throw new Error(String(payload.error || "Admin access denied.")); - } - } - - async function refreshAdminData(options?: { includeLogs?: boolean; silentRequests?: boolean }): Promise { - await loadRequests({ silent: options?.silentRequests === true }); - if (options?.includeLogs && adminAccessGranted && adminPassword) { - await loadRecentSaveEvents(); - } - } - - useEffect(() => { - void loadRequests(); - void loadRequestMeta(); - }, []); - - useEffect(() => { - if (!adminPanelOpen || !adminAccessGranted || !adminPassword) { - return; - } - void loadRecentSaveEvents(); - }, [adminPanelOpen, adminAccessGranted, adminPassword]); - - useEffect(() => { - if (activeBoardTab !== "requests") { - return; - } - let cancelled = false; - const refreshBoard = async (): Promise => { - if (!adminPanelOpen) { - try { - const payload = await fetchJsonOrThrow("/api/launcher-requests"); - if (!cancelled) { - setRequests(Array.isArray(payload.requests) ? payload.requests.map(hydrateLauncherRequestForUi) : []); - } - } catch { - // Keep the current list visible during background refresh failures. - } - } - if (!adminPanelOpen || !adminAccessGranted || !adminPassword) { - return; - } - try { - const payload = await fetchJsonOrThrow("/api/debug/recent-saves", { - headers: buildAdminHeaders(adminPassword), - }); - if (!cancelled) { - setRecentSaveEvents(Array.isArray(payload.saves) ? payload.saves : []); - } - } catch { - // Avoid surfacing noisy polling failures in the admin panel. - } - }; - const intervalId = window.setInterval(() => { - void refreshBoard(); - }, 15000); - return () => { - cancelled = true; - window.clearInterval(intervalId); - }; - }, [activeBoardTab, adminPanelOpen, adminAccessGranted, adminPassword]); - - useEffect(() => { - if (!adminPanelOpen || !adminAccessGranted) { - return; - } - if (requests.length === 0) { - setSelectedAdminRequestId(""); - setAdminEditorDraft(null); - return; - } - const selectedRequest = requests.find((entry) => entry.id === selectedAdminRequestId); - if (selectedRequest) { - if (!adminEditorDraft || adminEditorDraft.id !== selectedRequest.id) { - setAdminEditorDraft(cloneLauncherRequest(hydrateLauncherRequestForUi(selectedRequest))); - } - return; - } - setSelectedAdminRequestId(requests[0].id); - setAdminEditorDraft(cloneLauncherRequest(hydrateLauncherRequestForUi(requests[0]))); - }, [adminPanelOpen, adminAccessGranted, requests, selectedAdminRequestId, adminEditorDraft]); - - useEffect(() => { - setSelectedAdminAnalysisIndex(0); - }, [selectedAdminRequestId]); - - useEffect(() => { - setAdminDetailTab("routing"); - }, [selectedAdminRequestId]); - - async function handleLaunch(): Promise { - setError(""); - const nextWorldId = worldId || DEFAULT_EDITOR_WORLD_ID_FALLBACK; - setLaunchState("opening"); - try { - const resolvedWorldId = nextWorldId || await resolveDefaultWorldId().catch(() => DEFAULT_EDITOR_WORLD_ID_FALLBACK); - setWorldId(resolvedWorldId); - if (openStudioPopup(resolvedWorldId)) { - setLaunchState("opened"); - return; - } - setLaunchState("blocked"); - } catch (nextError: unknown) { - const nextErrorText = String(nextError || "Failed to prepare Worldshaper Studio."); - setLaunchState("error"); - setError(nextErrorText); - } - } - - async function handleAddRequest(): Promise { - const text = requestDraft.trim(); - if (!text) { - setRequestsError("Write a request before saving it."); - return; - } - setRequestSubmitting(true); - try { - const payload = await fetchJsonOrThrow("/api/launcher-requests", { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ text }), - }); - setRequests(Array.isArray(payload.requests) ? payload.requests.map(hydrateLauncherRequestForUi) : []); - setRequestDraft(""); - setRequestDraftOpen(false); - setRequestsError(""); - setAdminNotice("Request saved. The VPS queue worker will pick it up if analysis autorun is enabled."); - if (adminPanelOpen && adminAccessGranted) { - void loadRecentSaveEvents(); - } - window.setTimeout(() => { - void refreshAdminData({ includeLogs: adminPanelOpen && adminAccessGranted, silentRequests: true }); - }, 3500); - } catch (nextError: unknown) { - setRequestsError(String(nextError || "Failed to save request.")); - } finally { - setRequestSubmitting(false); - } - } - - async function handleAdminPanelToggle(): Promise { - setRequestDraftOpen(false); - setRequestsError(""); - setLogsError(""); - if (adminWindowMode) { - return; - } - if (!openAdminPanelWindow()) { - setAdminNotice("Allow popups to open the admin review window."); - } - } - - async function handleAdminUnlock(): Promise { - const submittedPassword = adminPasswordDraft.trim(); - if (!submittedPassword) { - setAdminPasswordError("Enter the admin password to continue."); - return; - } - setAdminAuthSubmitting(true); - setAdminPasswordError(""); - try { - await verifyAdminPassword(submittedPassword); - setAdminPassword(submittedPassword); - setAdminAccessGranted(true); - setAdminNotice("Admin access granted."); - await refreshAdminData({ includeLogs: true, silentRequests: true }); - } catch (nextError: unknown) { - setAdminAccessGranted(false); - setAdminPassword(""); - setAdminPasswordError(String(nextError || "Failed to unlock the admin panel.")); - } finally { - setAdminAuthSubmitting(false); - } - } - - function handleSelectAdminRequest(requestId: string): void { - const nextRequest = requests.find((entry) => entry.id === requestId); - setSelectedAdminRequestId(requestId); - setSelectedAdminAnalysisIndex(0); - setAdminEditorDraft(nextRequest ? cloneLauncherRequest(hydrateLauncherRequestForUi(nextRequest)) : null); - setAdminNotice(""); - setAdminPasswordError(""); - } - - function updateAdminDraft(updater: (current: LauncherRequest) => LauncherRequest): void { - setAdminEditorDraft((current) => (current ? updater(current) : current)); - } - - function updateAdminDraftItem( - itemIndex: number, - updater: (item: LauncherRequestAnalysisItem) => LauncherRequestAnalysisItem, - ): void { - updateAdminDraft((current) => { - const next = cloneLauncherRequest(current); - if (!next.analysis) { - next.analysis = { - state: "needs_review", - items: [], - }; - } - const items = Array.isArray(next.analysis.items) ? [...next.analysis.items] : []; - const existingItem = items[itemIndex] || {}; - items[itemIndex] = updater({ - ...existingItem, - tags: Array.isArray(existingItem.tags) ? [...existingItem.tags] : [], - affectedSystems: Array.isArray(existingItem.affectedSystems) ? [...existingItem.affectedSystems] : [], - affectedFiles: Array.isArray(existingItem.affectedFiles) ? [...existingItem.affectedFiles] : [], - reviewOptions: Array.isArray(existingItem.reviewOptions) ? [...existingItem.reviewOptions] : [], - }); - next.analysis.items = items; - next.analysis.itemCount = items.length; - next.analysis.updatedAt = new Date().toISOString(); - return next; - }); - } - - function buildAdminSavePayload(requestEntry: LauncherRequest): RequestInit { - return { - method: "PATCH", - headers: buildAdminHeaders(adminPassword, { - "Content-Type": "application/json", - }), - body: JSON.stringify({ - title: requestEntry.title, - status: requestEntry.status, - category: requestEntry.category, - tags: requestEntry.tags, - sourceText: requestEntry.sourceText, - summary: requestEntry.summary, - implementationNotes: requestEntry.implementationNotes, - analysis: requestEntry.analysis, - }), - }; - } - - async function handleSaveAdminRequest(): Promise { - if (!adminEditorDraft) { - return; - } - setAdminSaving(true); - try { - const payload = await fetchJsonOrThrow<{ request?: LauncherRequest; requests?: LauncherRequest[] }>( - `/api/launcher-requests/${encodeURIComponent(adminEditorDraft.id)}`, - buildAdminSavePayload(adminEditorDraft), - ); - const nextRequests = Array.isArray(payload.requests) ? payload.requests.map(hydrateLauncherRequestForUi) : requests; - setRequests(nextRequests); - const refreshed = nextRequests.find((entry) => entry.id === adminEditorDraft.id) || hydrateLauncherRequestForUi(payload.request || adminEditorDraft); - setAdminEditorDraft(cloneLauncherRequest(hydrateLauncherRequestForUi(refreshed))); - setAdminNotice(`Saved admin changes for "${adminEditorDraft.title}".`); - if (adminPanelOpen) { - void loadRecentSaveEvents(); - } - } catch (nextError: unknown) { - if (isAdminAccessError(nextError)) { - setAdminAccessGranted(false); - setAdminPassword(""); - setAdminPasswordError("Admin access expired. Enter the password again."); - } - setLogsError(String(nextError || "Failed to save admin changes.")); - } finally { - setAdminSaving(false); - } - } - - async function handleApproveAdminRequest(): Promise { - if (!adminEditorDraft) { - return; - } - const nextDraft = cloneLauncherRequest(adminEditorDraft); - if (!nextDraft.analysis) { - setLogsError("This request has no analysis to approve yet."); - return; - } - const items = Array.isArray(nextDraft.analysis.items) ? nextDraft.analysis.items : []; - if (items.length === 0) { - setLogsError("This request does not have a structured analysis item to approve yet."); - return; - } - nextDraft.analysis.items = items.map((item) => ({ - ...item, - statusRecommendation: "active", - })); - nextDraft.analysis.state = "processed"; - nextDraft.analysis.updatedAt = new Date().toISOString(); - setAdminEditorDraft(nextDraft); - setAdminSaving(true); - try { - await fetchJsonOrThrow<{ request?: LauncherRequest; requests?: LauncherRequest[] }>( - `/api/launcher-requests/${encodeURIComponent(nextDraft.id)}`, - buildAdminSavePayload(nextDraft), - ); - const promotePayload = await fetchJsonOrThrow( - `/api/launcher-requests/${encodeURIComponent(nextDraft.id)}/process-analysis`, - { - method: "POST", - headers: buildAdminHeaders(adminPassword, { - "Content-Type": "application/json", - }), - body: JSON.stringify({ - action: "promote", - analysis: nextDraft.analysis, - }), - }, - ); - const nextRequests = Array.isArray(promotePayload.requests) ? promotePayload.requests.map(hydrateLauncherRequestForUi) : []; - setRequests(nextRequests); - const fallbackSelection = nextRequests[0] || null; - setSelectedAdminRequestId(fallbackSelection?.id || ""); - setAdminEditorDraft(fallbackSelection ? cloneLauncherRequest(hydrateLauncherRequestForUi(fallbackSelection)) : null); - setAdminNotice(`Approved "${nextDraft.title}" and promoted its active request item${(nextDraft.analysis.items?.length || 0) === 1 ? "" : "s"}.`); - if (adminPanelOpen) { - void loadRecentSaveEvents(); - } - } catch (nextError: unknown) { - if (isAdminAccessError(nextError)) { - setAdminAccessGranted(false); - setAdminPassword(""); - setAdminPasswordError("Admin access expired. Enter the password again."); - } - setLogsError(String(nextError || "Failed to approve this request.")); - } finally { - setAdminSaving(false); - } - } - - async function handleRequeueAnalysis(mode: "saved" | "draft"): Promise { - if (!adminEditorDraft) { - return; - } - setRequeueingMode(mode); - setLogsError(""); - try { - const payload = await fetchJsonOrThrow( - `/api/launcher-requests/${encodeURIComponent(adminEditorDraft.id)}/requeue-analysis`, - { - method: "POST", - headers: buildAdminHeaders(adminPassword, { - "Content-Type": "application/json", - }), - body: JSON.stringify({ - mode, - request: mode === "draft" - ? { - title: adminEditorDraft.title, - category: adminEditorDraft.category, - tags: adminEditorDraft.tags, - sourceText: adminEditorDraft.sourceText, - summary: adminEditorDraft.summary, - implementationNotes: adminEditorDraft.implementationNotes, - } - : undefined, - }), - }, - ); - const nextRequests = Array.isArray(payload.requests) ? payload.requests.map(hydrateLauncherRequestForUi) : requests; - setRequests(nextRequests); - const refreshed = nextRequests.find((entry) => entry.id === adminEditorDraft.id) || hydrateLauncherRequestForUi(payload.request || adminEditorDraft); - setAdminEditorDraft(cloneLauncherRequest(hydrateLauncherRequestForUi(refreshed))); - if (payload.launched) { - setAdminNotice(mode === "draft" - ? "Edited draft resubmitted to the analyzer." - : "Saved request resubmitted to the analyzer."); - } else { - const reason = String(payload.reason || "no-op"); - if (reason === "request-analysis-already-running") { - setAdminNotice("The queue worker is already running. This request will be picked up on the next pass."); - } else if (reason === "request-not-queued") { - setAdminNotice("That request is not currently eligible for review reruns."); - } else { - setAdminNotice(`Review rerun returned: ${reason}.`); - } - } - await refreshAdminData({ includeLogs: true, silentRequests: true }); - window.setTimeout(() => { - void refreshAdminData({ includeLogs: true, silentRequests: true }); - }, 4200); - } catch (nextError: unknown) { - if (isAdminAccessError(nextError)) { - setAdminAccessGranted(false); - setAdminPassword(""); - setAdminPasswordError("Admin access expired. Enter the password again."); - } - setLogsError(String(nextError || "Failed to requeue this request for review.")); - } finally { - setRequeueingMode(""); - } - } - - function handleToggleExpandedRequest(requestId: string): void { - setExpandedRequestIds((current) => ( - current.includes(requestId) - ? current.filter((entry) => entry !== requestId) - : [...current, requestId] - )); - } - - async function handleDeleteRequest(requestEntry: LauncherRequest): Promise { - const confirmed = window.confirm(`Delete this request?\n\n${requestEntry.title}`); - if (!confirmed) { - return; - } - setRequestMutatingId(requestEntry.id); - try { - const payload = await fetchJsonOrThrow(`/api/launcher-requests/${encodeURIComponent(requestEntry.id)}`, { - method: "DELETE", - headers: buildAdminHeaders(adminPassword), - }); - setRequests(Array.isArray(payload.requests) ? payload.requests.map(hydrateLauncherRequestForUi) : []); - setRequestsError(""); - setExpandedRequestIds((current) => current.filter((entry) => entry !== requestEntry.id)); - setAdminNotice(`Deleted request "${requestEntry.title}".`); - if (adminPanelOpen) { - void loadRecentSaveEvents(); - } - } catch (nextError: unknown) { - if (isAdminAccessError(nextError)) { - setAdminAccessGranted(false); - setAdminPassword(""); - setAdminPasswordError("Admin access expired. Enter the password again."); - } - setRequestsError(String(nextError || "Failed to delete request.")); - } finally { - setRequestMutatingId(""); - } - } - - async function handleProcessPendingQueue(): Promise { - setQueueTriggering(true); - try { - const payload = await fetchJsonOrThrow("/api/launcher-requests/process-pending", { - method: "POST", - headers: buildAdminHeaders(adminPassword), - }); - if (payload.launched) { - setAdminNotice(`Queue worker launched for ${payload.queuedPendingCount ?? 0} pending request${payload.queuedPendingCount === 1 ? "" : "s"}.`); - } else { - const reason = String(payload.reason || "no-op"); - if (reason === "no-pending-requests") { - setAdminNotice("No unprocessed pending requests are waiting in the queue."); - } else if (reason === "request-analysis-already-running") { - setAdminNotice("The request analysis worker is already running on the VPS."); - } else if (reason === "request-analysis-not-configured") { - setAdminNotice("Request analysis is not configured on the server."); - } else { - setAdminNotice(`Queue trigger returned: ${reason}.`); - } - } - await refreshAdminData({ includeLogs: true, silentRequests: true }); - if (payload.launched) { - window.setTimeout(() => { - void refreshAdminData({ includeLogs: true, silentRequests: true }); - }, 4200); - } - } catch (nextError: unknown) { - if (isAdminAccessError(nextError)) { - setAdminAccessGranted(false); - setAdminPassword(""); - setAdminPasswordError("Admin access expired. Enter the password again."); - } - setLogsError(String(nextError || "Failed to trigger the queue worker.")); - } finally { - setQueueTriggering(false); - } - } - - const isBusy = launchState === "opening"; - const requestCount = requests.length; - const pendingRequestCount = requests.filter((entry) => entry.status === "pending").length; - const activeRequestCount = requests.filter((entry) => entry.status === "active").length; - const implementedRequestCount = requests.filter((entry) => entry.status === "implemented").length; - const queuedPendingRequestCount = requests.filter(isQueuedPendingRequest).length; - const needsReviewRequestCount = requests.filter(isNeedsReviewRequest).length; - const requestTags = (allowedRequestTags.length > 0 - ? allowedRequestTags - : Array.from(new Set( - requests - .flatMap((entry) => Array.isArray(entry.tags) ? entry.tags : []) - .map((entry) => String(entry || "").trim()) - .filter(Boolean), - ))).sort((a, b) => a.localeCompare(b)); - const requestTagFilterOptions = requestTags - .map((tag) => ({ - tag, - count: requests.filter((entry) => entry.tags.includes(tag)).length, - })) - .filter((entry) => entry.count > 0); - const requestStatusFilterOptions = [ - { id: "pending", label: "Pending", count: pendingRequestCount }, - { id: "queued", label: "Queued", count: queuedPendingRequestCount }, - { id: "review", label: "Needs Review", count: needsReviewRequestCount }, - { id: "active", label: "Active", count: activeRequestCount }, - { id: "implemented", label: "Implemented", count: implementedRequestCount }, - ].filter((entry) => entry.count > 0); - const filteredRequests = requests.filter((entry) => requestMatchesFilters( - entry, - requestSearchText, - requestStatusFilters, - requestTagFilters, - )); - const adminFilteredRequests = requests.filter((entry) => requestMatchesFilters( - entry, - adminSearchText, - adminStatusFilters, - adminTagFilters, - )); - const selectedAnalysisItem = adminEditorDraft?.analysis?.items?.[selectedAdminAnalysisIndex] || null; - const standardizedTagOptions = normalizeStringList([ - ...allowedRequestTags, - ...requestTags, - ...requests.flatMap((entry) => [ - ...entry.tags, - ...(Array.isArray(entry.analysis?.routing?.suggestedTags) ? entry.analysis.routing.suggestedTags : []), - ...(Array.isArray(entry.analysis?.items) ? entry.analysis.items.flatMap((item) => Array.isArray(item.tags) ? item.tags : []) : []), - ]), - ...(adminEditorDraft?.tags || []), - ...(Array.isArray(adminEditorDraft?.analysis?.routing?.suggestedTags) ? adminEditorDraft.analysis.routing.suggestedTags : []), - ...(Array.isArray(selectedAnalysisItem?.tags) ? selectedAnalysisItem.tags : []), - ]); - const categoryOptions = normalizeStringList([ - ...standardizedTagOptions, - ...requests.map((entry) => entry.category), - ...requests.flatMap((entry) => Array.isArray(entry.analysis?.items) ? entry.analysis.items.map((item) => String(item.primaryCategory || "")) : []), - String(adminEditorDraft?.category || ""), - String(selectedAnalysisItem?.primaryCategory || ""), - ]); - const boardTitle = adminWindowMode ? "Worldshaper Admin" : "Worldshaper Board"; - const boardHint = adminWindowMode - ? `${queuedPendingRequestCount} queued, ${needsReviewRequestCount} review, ${activeRequestCount} active, ${implementedRequestCount} implemented` - : (activeBoardTab === "news" - ? "Latest announcements" - : `${queuedPendingRequestCount} queued, ${needsReviewRequestCount} review, ${activeRequestCount} active, ${implementedRequestCount} implemented`); - - return ( -
    -
    - {!adminWindowMode ? ( -
    -
    -
    -
    -

    Worldshaper Studio

    -
    -
    - - -
    - {launchState === "blocked" ?

    Popup blocked. Allow popups, then press Launch again.

    : null} - {error ?

    {error}

    : null} -
    -
    -
    - ) : null} -
    -
    -
    {boardTitle}
    -
    {boardHint}
    -
    -
    -
    - {!adminWindowMode ? ( -
    - - -
    - ) : null} - {activeBoardTab === "news" ? ( -
    -
    -
    {CHANGELOG_SPLASH_KICKER}
    -
    {CHANGELOG_SPLASH_TITLE}
    -
    Release {CHANGELOG_SPLASH_VERSION}
    -
    -
    - {CHANGELOG_SECTIONS.map((section) => ( -
    -

    {section.title}

    -
      - {section.items.map((item, index) => { - const key = `${section.title}-${index}`; - const normalizedItem: ChangelogItem = item; - if (typeof normalizedItem === "string") { - return
    • {normalizedItem}
    • ; - } - return ( -
    • -
      {normalizedItem.text}
      - {normalizedItem.note ?
      {normalizedItem.note}
      : null} -
    • - ); - })} -
    -
    - ))} -
    -
    -
    {CHANGELOG_SPLASH_FOOTNOTE}
    -
    -
    - ) : ( -
    - {!adminWindowMode ? ( -
    -
    Shared Request Board
    -
    Requests
    -
    - {requestCount} saved request{requestCount === 1 ? "" : "s"}: {queuedPendingRequestCount} queued, {needsReviewRequestCount} in review, {activeRequestCount} active, and {implementedRequestCount} implemented. -
    -
    -
    - - {!adminPanelOpen ? ( - - ) : null} -
    -
    - setRequestSearchText(event.target.value)} - placeholder="Search requests..." - /> -
    - - {requestFilterMenuOpen ? ( -
    - {requestStatusFilterOptions.length > 0 ? ( -
    -
    Status
    - {requestStatusFilterOptions.map((option) => ( - - ))} -
    - ) : null} - {requestTagFilterOptions.length > 0 ? ( -
    -
    Tags
    - {requestTagFilterOptions.map(({ tag, count }) => ( - - ))} -
    - ) : null} -
    - ) : null} -
    -
    -
    -
    - ) : null} - {adminPanelOpen ? ( -
    - {!adminAccessGranted ? ( -
    -
    Protected Tools
    -

    Admin Access Required

    -

    - Enter the admin password to manage deletions, run the queue worker, and read request logs. -

    - -
    - -
    - {adminPasswordError ?

    {adminPasswordError}

    : null} -
    - ) : ( - <> -
    -
    - - - -
    -
    - {queuedPendingRequestCount} queued - {needsReviewRequestCount} review - {pendingRequestCount} pending - {activeRequestCount} active - {implementedRequestCount} implemented -
    -
    - {adminNotice ?

    {adminNotice}

    : null} - {adminPasswordError ?

    {adminPasswordError}

    : null} - {logsError ?

    {logsError}

    : null} -
    -
    -
    -
    -

    Request Management

    -
    Select a request to load it on the right.
    -
    -
    - setAdminSearchText(event.target.value)} - placeholder="Search requests..." - /> -
    - - {adminFilterMenuOpen ? ( -
    - {requestStatusFilterOptions.length > 0 ? ( -
    -
    Status
    - {requestStatusFilterOptions.map((option) => ( - - ))} -
    - ) : null} - {requestTagFilterOptions.length > 0 ? ( -
    -
    Tags
    - {requestTagFilterOptions.map(({ tag, count }) => ( - - ))} -
    - ) : null} -
    - ) : null} -
    -
    -
    - {!requestsLoading && adminFilteredRequests.length === 0 ? ( -
    No requests match the current search or filters.
    - ) : null} - {adminFilteredRequests.map((requestEntry) => { - const isMutating = requestMutatingId === requestEntry.id; - const isSelected = requestEntry.id === selectedAdminRequestId; - const requestDisplayState = getRequestDisplayStateLabel(requestEntry); - const requestDisplayStateClassName = getRequestDisplayStateClassName(requestEntry); - return ( -
    handleSelectAdminRequest(requestEntry.id)} - > -
    -
    -
    {requestEntry.title}
    -
    - {requestDisplayState} -
    -
    -
    - {formatRequestTimestamp(requestEntry.updatedAt)} -
    - {requestEntry.tags.length > 0 ? ( -
    - {requestEntry.tags.slice(0, 3).map((tag) => ( - {tag} - ))} -
    - ) : null} -
    - -
    - ); - })} -
    -
    -
    -
    - {!adminEditorDraft ? ( -
    Select a request from the list to review it.
    - ) : ( -
    -
    -
    -
    Selected Request
    -

    {adminEditorDraft.title}

    -
    -
    - - -
    -
    - - {(adminEditorDraft.analysis?.items?.length || 0) > 1 ? ( - - ) : null} -
    - - - -
    -
    -
    -
    - {adminDetailTab === "routing" ? ( - <> -
    -
    -
    Routing Pass
    -
    KB Routing Summary
    -
    -
    -
    - - -
    -
    - updateAdminDraft((current) => ({ ...current, tags: appendUniqueString(current.tags, value) }))} - onRemove={(value) => updateAdminDraft((current) => ({ ...current, tags: removeStringValue(current.tags, value) }))} - /> - updateAdminDraft((current) => ({ - ...current, - analysis: { - ...(current.analysis || {}), - routing: { - ...(current.analysis?.routing || {}), - suggestedTags: appendUniqueString( - Array.isArray(current.analysis?.routing?.suggestedTags) ? current.analysis.routing.suggestedTags : [], - value, - ), - }, - }, - }))} - onRemove={(value) => updateAdminDraft((current) => ({ - ...current, - analysis: { - ...(current.analysis || {}), - routing: { - ...(current.analysis?.routing || {}), - suggestedTags: removeStringValue( - Array.isArray(current.analysis?.routing?.suggestedTags) ? current.analysis.routing.suggestedTags : [], - value, - ), - }, - }, - }))} - /> -
    -
    -
    - - -
    -
    -
    -
    -
    -
    - -
    - -
    -
    -
    -
    - - - - - - - - - - - - - - -
    -
    - - -
    -
    -
    -
    -
    - -
    -
    - -
    -
    - - - - `; -} - -export function getWorldshaperStudioBodyMarkup(): string { - return buildWorldshaperStudioPopupMarkup() - .replace(/^/i, "") - .replace(/<\/body>s*$/i, ""); -} - +export { buildWorldshaperStudioStyles } from "./domStyles"; +export { buildWorldshaperStudioPopupMarkup, getWorldshaperStudioBodyMarkup } from "./domMarkup"; diff --git a/src/worldshaperStudio/domMarkup.ts b/src/worldshaperStudio/domMarkup.ts new file mode 100644 index 0000000..940322a --- /dev/null +++ b/src/worldshaperStudio/domMarkup.ts @@ -0,0 +1,34 @@ +import { WORLDSHAPER_THEME_PRESETS } from "./themePresets"; +import { + WORLDSHAPER_STUDIO_MARKUP_SHELL, + WORLDSHAPER_STUDIO_MARKUP_SIDEBAR, + WORLDSHAPER_STUDIO_MARKUP_STAGE, +} from "./domMarkupSections"; + +export function buildWorldshaperStudioPopupMarkup(): string { + const themePresetButtons = WORLDSHAPER_THEME_PRESETS.map((preset) => ` + + `).join(""); + return ( + WORLDSHAPER_STUDIO_MARKUP_SHELL.replace("__THEME_PRESET_BUTTONS__", themePresetButtons) + + WORLDSHAPER_STUDIO_MARKUP_SIDEBAR + + WORLDSHAPER_STUDIO_MARKUP_STAGE + ); +} + +export function getWorldshaperStudioBodyMarkup(): string { + return buildWorldshaperStudioPopupMarkup() + .replace(/^/i, "") + .replace(/<\/body>\s*$/i, ""); +} diff --git a/src/worldshaperStudio/domMarkupSections.ts b/src/worldshaperStudio/domMarkupSections.ts new file mode 100644 index 0000000..793ec28 --- /dev/null +++ b/src/worldshaperStudio/domMarkupSections.ts @@ -0,0 +1,475 @@ +export const WORLDSHAPER_STUDIO_MARKUP_SHELL = ` + +
    + + +
    + +`; + +export const WORLDSHAPER_STUDIO_MARKUP_SIDEBAR = ` + + + +`; + +export const WORLDSHAPER_STUDIO_MARKUP_STAGE = ` +
    +
    +
    +
    +
    + +
    +
    + +
    +
    + +
    +
    + +`; diff --git a/src/worldshaperStudio/domStyleSections.ts b/src/worldshaperStudio/domStyleSections.ts new file mode 100644 index 0000000..11a177b --- /dev/null +++ b/src/worldshaperStudio/domStyleSections.ts @@ -0,0 +1,4237 @@ +export const WORLDSHAPER_STUDIO_STYLE_SHELL = ` + :root { color-scheme: dark; } + * { box-sizing: border-box; } + html, body { + margin: 0; + width: 100%; + height: 100%; + background: #0a1020; + color: #d8e8ff; + font-family: Segoe UI, Arial, sans-serif; + } + .shell { + display: grid; + grid-template-rows: 40px 1fr; + width: 100vw; + height: 100vh; + } + + /* Top menu bar, clamped to 40px */ + .menu-bar { + width: 100%; + height: 40px; + min-height: 40px; + max-height: 40px; + position: relative; + display: flex; + align-items: center; + gap: 8px; + padding: 4px 8px; + border-bottom: 1px solid #2e426c; + background: linear-gradient(180deg, #152645 0%, #10203c 100%); + overflow: hidden; + user-select: none; + } + .menu-btn { + height: 30px; + padding: 0 12px; + border: 1px solid #3c5e95; + border-radius: 8px; + background: #1a345e; + color: #d6e7ff; + font-size: 12px; + font-weight: 700; + cursor: pointer; + white-space: nowrap; + } + .menu-btn:hover { background: #214679; } + .menu-btn.menu-btn-right { + margin-left: auto; + } + .menu-bar-center { + position: absolute; + left: 50%; + top: 50%; + transform: translate(-50%, -50%); + display: inline-flex; + align-items: center; + gap: 8px; + min-width: 0; + pointer-events: none; + } + .menu-bar-center > * { + pointer-events: auto; + } + .menu-layer-label { + color: #cfe2ff; + font-size: 12px; + font-weight: 700; + white-space: nowrap; + } + .menu-layer-select { + height: 30px; + min-width: 220px; + max-width: min(320px, 40vw); + border: 1px solid #3c5e95; + border-radius: 8px; + background: #10284b; + color: #d6e7ff; + font-size: 12px; + padding: 0 10px; + } + + .body { + min-height: 0; + display: grid; + grid-template-columns: 325px 1fr; + position: relative; + } + +`; + +export const WORLDSHAPER_STUDIO_STYLE_SIDEBAR = ` + .sidebar { + min-height: 0; + border-right: 1px solid #2e426c; + background: #0e1a33; + display: flex; + flex-direction: column; + padding: 10px; + overflow: hidden; + position: relative; + } + .sidebar-panels-host { + display: flex; + flex-direction: column; + min-height: 0; + flex: 1 1 auto; + overflow-x: hidden; + overflow-y: auto; + padding-right: 2px; + } + .sidebar-panels-host.sidebar-drop-target { + outline: 1px solid rgba(255, 209, 102, 0.78); + outline-offset: 4px; + border-radius: 10px; + } + .sidebar h3 { + margin: 0 0 8px; + font-size: 13px; + text-transform: uppercase; + letter-spacing: 0.05em; + color: #9bb5e3; + text-align: center; + } + .sidebar-tabs { + display: grid; + gap: 6px; + margin-bottom: 10px; + position: sticky; + top: 0; + z-index: 30; + background: #0e1a33; + border: 1px solid #274472; + border-radius: 10px; + padding: 6px; + box-shadow: 0 8px 14px rgba(3, 8, 18, 0.8); + isolation: isolate; + } + .sidebar-tabs::before { + content: ""; + position: absolute; + inset: -1px; + background: #0e1a33; + border-radius: 10px; + z-index: -1; + } + .sidebar-tabs.dock-target { + border-color: #ffd166; + box-shadow: 0 0 0 1px rgba(255, 209, 102, 0.75), 0 8px 14px rgba(3, 8, 18, 0.8); + } + .sidebar-tab-row { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 6px; + } + .sidebar-tab-btn { + height: 28px; + width: 100%; + padding: 0 8px; + border: 1px solid #3f5e90; + border-radius: 7px; + background: #1a2f53; + color: #cfe2ff; + font-size: 12px; + line-height: 1; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + display: inline-flex; + align-items: center; + justify-content: center; + cursor: pointer; + } + .sidebar-tab-btn.active { + background: #1e4b82; + border-color: #64aaf8; + color: #e7f1ff; + } + .sidebar-tab-btn.tool-active-hidden { + border-color: var(--editor-tool-armed, #7ee8c6); + background: linear-gradient( + 180deg, + var(--editor-tool-armed-soft, #1a3c40) 0%, + color-mix(in srgb, var(--editor-panel-bg-alt, #132b4f) 78%, black 22%) 100% + ); + color: var(--editor-tool-armed, #7ee8c6); + box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--editor-tool-armed, #7ee8c6) 28%, transparent); + } + .sidebar-tab-btn.popped.tool-active-hidden { + border-style: dashed; + border-color: var(--editor-tool-armed, #7ee8c6); + color: var(--editor-tool-armed, #7ee8c6); + } + .sidebar-tab-btn.popped { + border-style: dashed; + border-color: #ffd166; + background: #173962; + color: #f6e4a4; + } + .sidebar-tab-btn.drag-armed { + border-color: #ffd166; + box-shadow: 0 0 0 1px rgba(255, 209, 102, 0.4); + } + .hidden { + display: none !important; + } + .sidebar-panel { + display: flex; + flex-direction: column; + align-items: stretch; + gap: 8px; + min-height: 0; + flex: 1 1 auto; + } + .sidebar-panel > * { + width: 100%; + max-width: 100%; + } + .sidebar-panel.hidden { + display: none; + } + .sidebar-static-footer { + flex: 0 0 auto; + display: grid; + gap: 6px; + margin-top: 10px; + padding-top: 10px; + border-top: 1px solid rgba(86, 118, 171, 0.4); + } + .layer-list { + display: flex; + flex-direction: column; + gap: 6px; + margin-bottom: 10px; + } + .layer-row { + width: 100%; + text-align: left; + padding: 6px; + border: 1px solid #2e426c; + border-radius: 8px; + background: #121f3b; + color: #d6e7ff; + font-size: 12px; + min-height: 32px; + display: inline-flex; + align-items: center; + cursor: pointer; + } + .layer-row.active { + border-color: #5fa8ff; + background: #19355e; + } + .layer-row.layer-add-row { + border-style: dashed; + border-color: #4e78b7; + background: #132848; + color: #cce3ff; + margin-top: 4px; + } + .layer-row-wrap { + display: grid; + grid-template-columns: auto auto minmax(0, 1fr) auto auto; + gap: 6px; + align-items: center; + } + .layer-drag-handle { + width: 32px; + height: 32px; + padding: 0; + border: 1px solid #3c5e95; + border-radius: 8px; + background: #132b4f; + color: #d6e7ff; + display: inline-flex; + align-items: center; + justify-content: center; + cursor: grab; + } + .layer-drag-handle:hover { + background: #1a3f6d; + } + .layer-drag-handle:active { + cursor: grabbing; + } + .layer-drag-handle:disabled { + opacity: 0.45; + cursor: not-allowed; + } + .layer-drag-icon { + display: block; + font-size: 15px; + line-height: 1; + } + .layer-visibility-btn { + width: 32px; + height: 32px; + padding: 0; + border: 1px solid #3c5e95; + border-radius: 8px; + background: #132b4f; + color: #d6e7ff; + display: inline-flex; + align-items: center; + justify-content: center; + cursor: pointer; + } + .layer-visibility-btn.active { + border-color: #64aaf8; + background: #1e4b82; + } + .layer-visibility-btn.hidden { + border-color: #35537f; + background: #0f1d35; + color: #7f9dca; + opacity: 0.82; + } + .layer-visibility-icon { + display: block; + font-size: 16px; + line-height: 1; + } + .layer-row-wrap.reorder-dragging { + opacity: 0.55; + } + .layer-row-wrap.reorder-drop-before { + position: relative; + } + .layer-row-wrap.reorder-drop-before::before { + content: ""; + position: absolute; + left: 4px; + right: 4px; + top: -4px; + height: 2px; + border-radius: 999px; + background: #64aaf8; + box-shadow: 0 0 0 1px rgba(100, 170, 248, 0.28); + } + .layer-row-wrap.reorder-drop-after { + position: relative; + } + .layer-row-wrap.reorder-drop-after::after { + content: ""; + position: absolute; + left: 4px; + right: 4px; + bottom: -4px; + height: 2px; + border-radius: 999px; + background: #64aaf8; + box-shadow: 0 0 0 1px rgba(100, 170, 248, 0.28); + } + .layer-delete-btn { + width: 28px; + height: 28px; + padding: 0; + border: 1px solid #516993; + border-radius: 7px; + background: #11213d; + color: #d6e7ff; + font-size: 14px; + line-height: 1; + cursor: pointer; + } + .layer-delete-btn:hover { + background: #173058; + } + .layer-delete-btn:disabled { + opacity: 0.5; + cursor: not-allowed; + } + .layer-z-select { + width: 58px; + height: 28px; + border: 1px solid #516993; + border-radius: 7px; + background: #11213d; + color: #d6e7ff; + font-size: 11px; + padding: 0 4px; + } + .layer-actions { + display: flex; + gap: 6px; + margin-bottom: 12px; + justify-content: center; + flex-wrap: wrap; + } + .map-manager { + display: grid; + gap: 8px; + } + .information-panel-layout { + display: flex; + flex-direction: column; + gap: 12px; + min-height: 0; + height: 100%; + flex: 1 1 auto; + } + .information-utility-actions { + display: flex; + gap: 8px; + flex-wrap: wrap; + } + .information-utility-actions .mini-btn { + flex: 1 1 auto; + } + .information-bottom-stack { + display: grid; + gap: 10px; + } + .info-help-panel, + .info-footer-bar { + border: 1px solid var(--editor-border, #2e426c); + border-radius: 8px; + background: var(--editor-panel-bg, #121f3b); + } + .experimental-import-panel { + border: 1px solid var(--editor-border, #2e426c); + border-radius: 8px; + background: var(--editor-panel-bg, #121f3b); + overflow: hidden; + } + .experimental-import-toggle { + width: 100%; + padding: 10px; + border: 0; + background: transparent; + color: var(--editor-control-fg, #d6e7ff); + display: grid; + grid-template-columns: auto minmax(0, 1fr) auto; + gap: 8px; + align-items: center; + text-align: left; + cursor: pointer; + } + .experimental-import-toggle:hover { + background: rgba(255, 255, 255, 0.03); + } + .experimental-import-toggle.expanded { + background: rgba(255, 255, 255, 0.04); + } + .experimental-import-check { + font-size: 12px; + font-weight: 700; + color: var(--editor-warn, #ffd166); + white-space: nowrap; + } + .experimental-import-copy { + min-width: 0; + display: grid; + gap: 3px; + } + .experimental-import-title { + font-size: 12px; + font-weight: 700; + color: var(--editor-shell-fg, #d8e8ff); + } + .experimental-import-meta { + font-size: 11px; + line-height: 1.35; + color: var(--editor-muted, #9fb8e5); + } + .experimental-import-chevron { + font-size: 12px; + color: var(--editor-muted, #9fb8e5); + } + .experimental-import-body { + padding: 0 10px 10px; + display: grid; + gap: 8px; + } + .experimental-import-warning { + border: 1px solid rgba(255, 209, 102, 0.32); + border-radius: 7px; + background: rgba(255, 209, 102, 0.08); + color: var(--editor-muted-strong, #cfe2ff); + font-size: 11px; + line-height: 1.4; + padding: 8px 9px; + } + .experimental-import-actions { + display: grid; + grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) 36px; + gap: 8px; + align-items: stretch; + } + .experimental-import-actions .mini-btn { + width: 100%; + min-width: 0; + } + .experimental-import-icon-btn { + width: 36px; + min-width: 36px; + padding: 0; + font-size: 16px; + line-height: 1; + } + .experimental-import-modal { + position: fixed; + inset: 0; + display: grid; + place-items: center; + padding: 16px; + background: rgba(4, 9, 18, 0.72); + z-index: 1200; + } + .experimental-import-modal-card { + width: min(100%, 520px); + display: grid; + gap: 10px; + padding: 12px; + border: 1px solid var(--editor-border, #2e426c); + border-radius: 10px; + background: var(--editor-panel-bg-elevated, #10284b); + box-shadow: 0 18px 40px rgba(2, 8, 18, 0.5); + } + .experimental-import-modal-head { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; + } + .experimental-import-modal-title { + margin: 0; + font-size: 13px; + font-weight: 700; + color: var(--editor-shell-fg, #d8e8ff); + } + .experimental-import-modal-copy { + font-size: 11px; + line-height: 1.4; + color: var(--editor-muted, #9fb8e5); + } + .experimental-import-modal-body { + display: grid; + gap: 10px; + } + .experimental-import-modal-body textarea { + width: 100%; + min-height: 212px; + resize: vertical; + border: 1px solid var(--editor-control-border, #3c5e95); + border-radius: 8px; + background: var(--editor-control-bg, #1a345e); + color: var(--editor-control-fg, #d6e7ff); + padding: 9px 10px; + font: 12px/1.4 Consolas, "Courier New", monospace; + box-sizing: border-box; + } + .experimental-import-modal-actions { + display: flex; + justify-content: flex-end; + gap: 8px; + } + .info-help-panel { + padding: 10px; + display: grid; + align-content: start; + flex: 1 1 auto; + min-height: 0; + gap: 8px; + } + .info-help-title { + font-size: 11px; + font-weight: 700; + color: var(--editor-muted, #9fb8e5); + text-transform: uppercase; + letter-spacing: 0.04em; + } + .info-help-list { + display: grid; + gap: 6px; + } + .shortcut-row { + display: grid; + grid-template-columns: minmax(0, 1fr) minmax(110px, auto); + gap: 10px; + align-items: center; + } + .shortcut-keys { + display: flex; + align-items: center; + gap: 6px; + flex-wrap: wrap; + min-width: 0; + } + .shortcut-plus { + color: var(--editor-muted, #9fb8e5); + font-size: 12px; + font-weight: 700; + line-height: 1; + } + .shortcut-keycap, + .shortcut-mouse-shell { + min-height: 24px; + padding: 0 8px; + border: 1px solid var(--editor-control-border, #3c5e95); + border-radius: 7px; + background: var(--editor-control-bg, #1a345e); + color: var(--editor-control-fg, #d6e7ff); + font-size: 11px; + display: inline-flex; + align-items: center; + justify-content: center; + white-space: nowrap; + } + .shortcut-mouse-shell { + gap: 4px; + padding-right: 9px; + } + .shortcut-mouse-dot { + width: 6px; + height: 6px; + border-radius: 999px; + background: var(--editor-accent, #64aaf8); + box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.08); + } + .shortcut-mouse-label { + font-size: 10px; + color: var(--editor-muted-strong, #cfe2ff); + text-transform: uppercase; + } + .shortcut-action { + font-size: 11px; + line-height: 1.35; + color: var(--editor-muted-strong, #cfe2ff); + text-align: right; + display: inline-flex; + align-items: center; + justify-content: flex-end; + gap: 6px; + flex-wrap: wrap; + } + .shortcut-shape-icon { + width: 14px; + height: 14px; + flex: 0 0 14px; + border: 2px solid var(--editor-accent, #64aaf8); + background: transparent; + box-sizing: border-box; + } + .shortcut-shape-icon.square { + border-radius: 3px; + } + .shortcut-shape-icon.circle { + border-radius: 999px; + } + .info-footer-stack, + .sidebar-footer-links { + display: grid; + gap: 6px; + } + .info-footer-bar, + .sidebar-footer-linkbar { + min-height: 34px; + padding: 0 10px; + display: flex; + align-items: center; + justify-content: center; + background: linear-gradient( + 180deg, + var(--editor-menu-grad-1, #152645) 0%, + var(--editor-menu-grad-2, #10203c) 100% + ); + } + .info-footer-link, + .sidebar-footer-link { + color: var(--editor-accent-strong, #8fd0ff); + font-size: 12px; + font-weight: 600; + text-decoration: none; + } + .info-footer-link:hover, + .info-footer-link:focus-visible, + .sidebar-footer-link:hover, + .sidebar-footer-link:focus-visible { + color: var(--editor-shell-fg, #d8e8ff); + text-decoration: underline; + } + .field-row { + display: grid; + grid-template-columns: 82px minmax(0, 1fr); + gap: 8px; + align-items: center; + } + .info-readonly + .info-dim-controls { + display: none; + } + .field-row label { + font-size: 11px; + color: #9fb8e5; + white-space: nowrap; + } + .field-row input { + min-width: 0; + } + .map-manager label { + font-size: 11px; + color: #9fb8e5; + } + .map-manager select { + height: 30px; + width: 100%; + border: 1px solid #3c5e95; + border-radius: 7px; + background: #10284b; + color: #d6e7ff; + font-size: 12px; + padding: 0 8px; + } + .mini-btn { + height: 28px; + padding: 0 10px; + border: 1px solid #3f5e90; + border-radius: 7px; + background: #1a2f53; + color: #cfe2ff; + font-size: 12px; + cursor: pointer; + } + .mini-btn.active { + border-color: #64aaf8; + background: #245081; + color: #eef7ff; + } + .history-list { + display: flex; + flex-direction: column; + gap: 6px; + min-height: 0; + } + .npc-list { + display: flex; + flex-direction: column; + gap: 6px; + } + .history-row { + width: 100%; + text-align: left; + padding: 7px; + border: 1px solid #2e426c; + border-radius: 8px; + background: #121f3b; + color: #d6e7ff; + font-size: 11px; + cursor: pointer; + line-height: 1.35; + } + .history-row.active { + border-color: #ffd166; + background: #2a3e5f; + } + .npc-row.active { + border-color: #64aaf8; + background: #22466e; + } + .npc-row-header { + display: flex; + align-items: center; + gap: 8px; + } + .npc-row-main { + display: grid; + grid-template-columns: minmax(0, 1fr) auto; + align-items: center; + gap: 8px; + } + .npc-row-edit-btn { + width: 28px; + height: 28px; + padding: 0; + border: 1px solid var(--editor-control-border, #3c5e95); + border-radius: 8px; + background: var(--editor-panel-bg-alt, #132b4f); + color: var(--editor-control-fg, #d6e7ff); + display: inline-flex; + align-items: center; + justify-content: center; + cursor: pointer; + flex: 0 0 auto; + font-size: 13px; + line-height: 1; + } + .npc-row-edit-btn:hover { + background: var(--editor-panel-bg-hover, #1a3f6d); + } + .npc-thumb { + width: 28px; + height: 28px; + flex: 0 0 28px; + border: 1px solid #35537f; + border-radius: 6px; + background: #0c1730; + object-fit: contain; + image-rendering: pixelated; + } + .npc-thumb-fallback { + display: inline-flex; + align-items: center; + justify-content: center; + width: 28px; + height: 28px; + flex: 0 0 28px; + border: 1px solid #35537f; + border-radius: 6px; + background: #0c1730; + color: #9fb8e5; + font-size: 10px; + } + .npc-editor-panel { + margin-top: 8px; + padding: 8px; + border: 1px solid #2d426e; + border-radius: 8px; + background: #0e1d36; + display: grid; + gap: 8px; + } + .npc-top-toolbar { + display: flex; + gap: 6px; + align-items: center; + flex-wrap: wrap; + } + .npc-icon-btn { + width: 30px; + height: 30px; + padding: 0; + border: 1px solid #3c5e95; + border-radius: 8px; + background: #132b4f; + color: #d6e7ff; + font-size: 12px; + font-weight: 700; + cursor: pointer; + } + .npc-icon-btn.active { + border-color: #64aaf8; + background: #1e4b82; + } + .npc-compact-menu { + display: grid; + gap: 4px; + margin-top: 2px; + padding: 4px; + border: 1px solid #3c5e95; + border-radius: 8px; + background: #0f2344; + } + .npc-compact-menu button { + height: 30px; + border: 1px solid #35537f; + border-radius: 7px; + background: #132b4f; + color: #d6e7ff; + font-size: 11px; + text-align: left; + padding: 0 8px; + cursor: pointer; + } + .npc-description-box { + min-height: 72px; + resize: vertical; + } + .selector-toolbar { + display: flex; + justify-content: center; + gap: 8px; + margin-top: -2px; + } + .entity-filter-tabs { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 8px; + margin: -2px 0 10px; + } + .entity-filter-tab { + min-height: 32px; + padding: 0 10px; + border: 1px solid var(--editor-control-border, #3c5e95); + border-radius: 9px; + background: var(--editor-panel-bg-alt, #132b4f); + color: var(--editor-control-fg, #d6e7ff); + font-size: 12px; + font-weight: 600; + cursor: pointer; + } + .entity-filter-tab:hover { + background: var(--editor-panel-bg-hover, #1a3f6d); + } + .entity-filter-tab.active { + border-color: var(--editor-accent, #64aaf8); + background: color-mix(in srgb, var(--editor-accent, #64aaf8) 22%, var(--editor-panel-bg-alt, #132b4f)); + color: var(--editor-shell-fg, #eef6ff); + box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--editor-accent, #64aaf8) 28%, transparent); + } + .entity-type-badge { + display: inline-flex; + align-items: center; + justify-content: center; + min-width: 62px; + height: 18px; + padding: 0 7px; + border-radius: 999px; + background: color-mix(in srgb, var(--editor-accent, #64aaf8) 18%, var(--editor-preview-bg, #0c1730)); + color: var(--editor-shell-fg, #eef6ff); + font-size: 10px; + font-weight: 700; + letter-spacing: 0.02em; + text-transform: uppercase; + } + .panel-square-btn { + width: 32px; + height: 32px; + padding: 0; + border: 1px solid var(--editor-control-border, #3c5e95); + border-radius: 8px; + background: var(--editor-panel-bg-alt, #132b4f); + color: var(--editor-control-fg, #d6e7ff); + display: inline-flex; + align-items: center; + justify-content: center; + cursor: pointer; + font-size: 16px; + line-height: 1; + } + .panel-square-btn:hover { + background: var(--editor-panel-bg-hover, #1a3f6d); + } + .panel-square-btn.active { + border-color: var(--editor-accent, #64aaf8); + background: var(--editor-control-bg-active, #1e4b82); + color: var(--editor-shell-fg, #eef6ff); + } + .panel-icon-image-plus { + position: relative; + display: inline-flex; + width: 18px; + height: 16px; + align-items: center; + justify-content: center; + } + .panel-icon-image-frame { + position: absolute; + left: 1px; + top: 2px; + width: 13px; + height: 10px; + border: 1px solid currentColor; + border-radius: 3px; + } + .panel-icon-image-frame::before { + content: ""; + position: absolute; + left: 2px; + bottom: 2px; + width: 7px; + height: 4px; + background: currentColor; + clip-path: polygon(0 100%, 28% 36%, 50% 68%, 73% 18%, 100% 100%); + opacity: 0.92; + } + .panel-icon-image-frame::after { + content: ""; + position: absolute; + right: 2px; + top: 2px; + width: 2px; + height: 2px; + border-radius: 999px; + background: currentColor; + opacity: 0.9; + } + .panel-icon-image-plus-mark { + position: absolute; + right: -1px; + bottom: -1px; + color: var(--editor-tool-armed, #7ee8c6); + font-size: 11px; + font-weight: 700; + line-height: 1; + text-shadow: 0 0 3px rgba(6, 12, 20, 0.95); + } + .panel-icon-search { + font-size: 15px; + line-height: 1; + transform: translateY(-0.5px); + } + .tile-search-mode { + display: grid; + grid-template-rows: auto auto minmax(0, 1fr); + gap: 8px; + min-height: 0; + } + .tile-search-field { + width: 100%; + min-width: 0; + height: 32px; + border: 1px solid var(--editor-control-border, #35537f); + border-radius: 8px; + background: var(--editor-panel-bg-alt, #132b4f); + color: var(--editor-control-fg, #eef6ff); + padding: 0 10px; + font-size: 12px; + } + .tile-search-field:focus { + outline: none; + border-color: var(--editor-accent, #64aaf8); + box-shadow: 0 0 0 1px color-mix(in srgb, var(--editor-accent, #64aaf8) 22%, transparent); + } + .tile-search-meta { + color: var(--editor-muted, #9fb8e5); + font-size: 11px; + line-height: 1.4; + min-height: 16px; + } + .tile-search-results { + min-height: 0; + display: grid; + align-content: start; + gap: 6px; + overflow: auto; + padding-right: 2px; + } + .tile-search-empty { + padding: 12px 10px; + border: 1px dashed color-mix(in srgb, var(--editor-muted, #8fb2e1) 28%, transparent); + border-radius: 10px; + color: var(--editor-muted, #8fb2e1); + font-size: 11px; + line-height: 1.45; + background: color-mix(in srgb, var(--editor-shell-bg, #0a1020) 55%, transparent); + } + .tile-search-result-row { + width: 100%; + text-align: left; + cursor: pointer; + } + .tile-search-result-row .npc-row-header { + cursor: pointer; + } + .selector-section { + display: grid; + gap: 8px; + } + .selector-section-header { + display: grid; + grid-template-columns: minmax(0, 1fr) auto; + align-items: center; + gap: 8px; + } + .selector-section-toggle { + min-width: 0; + width: 100%; + flex: 1 1 auto; + height: 32px; + padding: 0 12px; + border: 1px solid #3f5e90; + border-radius: 8px; + background: #13284b; + color: #d6e7ff; + display: inline-flex; + align-items: center; + justify-content: flex-start; + gap: 8px; + font-size: 12px; + font-weight: 700; + cursor: pointer; + } + .selector-section-label { + min-width: 0; + width: 100%; + flex: 1 1 auto; + height: 32px; + padding: 0 12px; + border: 1px solid #3f5e90; + border-radius: 8px; + background: #13284b; + color: #d6e7ff; + display: inline-flex; + align-items: center; + justify-content: flex-start; + gap: 8px; + font-size: 12px; + font-weight: 700; + } + .selector-section-chevron { + display: inline-flex; + align-items: center; + justify-content: center; + width: 12px; + color: #8fd0ff; + font-size: 10px; + transition: transform 120ms ease; + } + .selector-section-toggle[aria-expanded="false"] .selector-section-chevron { + transform: rotate(-90deg); + } + .selector-section-actions { + display: inline-flex; + align-items: center; + justify-content: flex-end; + gap: 8px; + } + .selector-section-body { + display: grid; + gap: 8px; + } + .selector-section-body.hidden { + display: none !important; + } + .selector-hint { + margin: 0; + text-align: center; + } + .elevation-toolbar { + display: grid; + gap: 10px; + } + .elevation-z-select { + height: 30px; + width: 100%; + border: 1px solid #3c5e95; + border-radius: 7px; + background: #10284b; + color: #d6e7ff; + font-size: 12px; + padding: 0 8px; + } + .elevation-brush-group { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 8px; + } + .elevation-summary { + padding: 9px 10px; + border: 1px solid #2e426c; + border-radius: 8px; + background: #121f3b; + color: #cfe2ff; + font-size: 11px; + text-align: center; + } + .folder-list-root { + display: grid; + gap: 8px; + width: 100%; + } + .selector-drag-handle { + width: 28px; + height: 28px; + padding: 0; + border: 1px solid #3c5e95; + border-radius: 7px; + background: #132b4f; + color: #d6e7ff; + display: inline-flex; + align-items: center; + justify-content: center; + cursor: grab; + flex: 0 0 28px; + } + .selector-drag-handle.dragging, + .selector-drag-handle:active { + cursor: grabbing; + background: #1d4777; + } + .selector-drag-icon { + display: block; + font-size: 14px; + line-height: 1; + } + .folder-block { + display: grid; + gap: 6px; + width: 100%; + } + .folder-block.collapsed .folder-children { + display: none; + } + .folder-row { + background: #162844; + border-color: #47618f; + } + .folder-row-header { + display: flex; + align-items: center; + gap: 8px; + min-width: 0; + } + .folder-toggle-btn { + width: 24px; + height: 24px; + padding: 0; + border: 1px solid #3b567f; + border-radius: 6px; + background: #10233f; + color: #b7d7ff; + display: inline-flex; + align-items: center; + justify-content: center; + cursor: pointer; + flex: 0 0 24px; + } + .folder-toggle-icon { + display: block; + font-size: 10px; + line-height: 1; + } + .folder-row-icon { + width: 22px; + height: 22px; + display: inline-flex; + align-items: center; + justify-content: center; + font-size: 16px; + flex: 0 0 22px; + } + .folder-row-copy { + min-width: 0; + display: grid; + gap: 2px; + } + .folder-children { + display: grid; + gap: 6px; + padding-left: 12px; + border-left: 1px dashed rgba(100, 170, 248, 0.35); + margin-left: 8px; + width: calc(100% - 8px); + } + .folder-empty { + min-height: 36px; + border: 1px dashed #42648f; + border-radius: 8px; + color: #8da7d3; + display: flex; + align-items: center; + justify-content: center; + font-size: 11px; + background: rgba(11, 24, 47, 0.68); + } + .folder-root-drop-zone { + min-height: 34px; + border: 1px dashed #466b99; + border-radius: 8px; + color: #93b2df; + display: flex; + align-items: center; + justify-content: center; + font-size: 11px; + background: rgba(12, 25, 48, 0.72); + text-align: center; + } + .folder-drop-before, + .folder-drop-after, + .folder-drop-inside, + .folder-root-drop-active { + position: relative; + } + .folder-drop-before::before { + content: ""; + position: absolute; + left: 6px; + right: 6px; + top: -5px; + height: 2px; + border-radius: 999px; + background: #64aaf8; + box-shadow: 0 0 0 1px rgba(100, 170, 248, 0.3); + } + .folder-drop-after::after { + content: ""; + position: absolute; + left: 6px; + right: 6px; + bottom: -5px; + height: 2px; + border-radius: 999px; + background: #64aaf8; + box-shadow: 0 0 0 1px rgba(100, 170, 248, 0.3); + } + .folder-drop-inside { + box-shadow: inset 0 0 0 1px rgba(100, 170, 248, 0.8); + border-radius: 8px; + } + .folder-root-drop-active { + border-style: solid; + border-color: #64aaf8; + color: #d9ecff; + background: rgba(25, 67, 112, 0.46); + } + .folder-list-empty { + margin: 0; + text-align: center; + } + .info-cell-value { + min-width: 0; + display: flex; + align-items: center; + gap: 6px; + } + .info-dim-input { + flex: 1 1 auto; + transition: flex-basis 120ms ease; + } + .info-dim-value.dirty .info-dim-input { + flex-basis: 68%; + } + .info-dim-controls { + display: none; + gap: 6px; + flex: 0 0 auto; + } + .info-dim-controls.visible { + display: flex; + } + .icon-action-btn { + width: 30px; + height: 30px; + padding: 0; + border: 1px solid #3c5e95; + border-radius: 8px; + background: #132b4f; + color: #d6e7ff; + font-size: 14px; + cursor: pointer; + } + .icon-action-btn:hover { background: #1e4b82; } + .icon-action-btn.danger { border-color: #7f4c4c; background: #3c1a1a; } + .icon-action-btn.danger:hover { background: #5a2323; } + .info-readonly { + opacity: 0.75; + cursor: default; + } + .background-mode-btn { + min-height: 46px; + width: 100%; + padding: 6px 8px; + border: 1px solid #3c5e95; + border-radius: 8px; + background: #10284b; + color: #d6e7ff; + display: grid; + grid-template-columns: 32px minmax(0, 1fr); + align-items: center; + gap: 8px; + cursor: pointer; + text-align: left; + } + .background-mode-btn:hover { + background: #16345d; + } + .background-mode-preview { + width: 32px; + height: 32px; + border: 1px solid #4f6992; + border-radius: 6px; + background: #0d1b34; + overflow: hidden; + display: inline-flex; + align-items: center; + justify-content: center; + position: relative; + } + .background-mode-preview img { + width: 100%; + height: 100%; + object-fit: contain; + image-rendering: pixelated; + display: block; + } + .background-mode-preview-hole::before, + .background-mode-preview-hole::after { + content: ""; + position: absolute; + background: rgba(255, 91, 145, 0.95); + border-radius: 999px; + } + .background-mode-preview-hole::before { + width: 18px; + height: 2px; + transform: rotate(45deg); + } + .background-mode-preview-hole::after { + width: 18px; + height: 2px; + transform: rotate(-45deg); + } + .background-mode-preview-inherit::before { + content: ""; + position: absolute; + inset: 6px; + border: 2px dashed rgba(111, 196, 255, 0.95); + border-radius: 5px; + } + .background-mode-copy { + min-width: 0; + display: grid; + gap: 2px; + } + .background-mode-title { + font-size: 12px; + font-weight: 700; + color: #e1eeff; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + .background-mode-meta { + font-size: 10px; + color: #9fb8e5; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + .npc-editor-row { + display: grid; + gap: 4px; + } + .npc-editor-row label { + font-size: 11px; + color: #9fb8e5; + } + .npc-editor-row input, + .npc-editor-row select, + .sprite-dropdown-btn { + height: 28px; + width: 100%; + border: 1px solid #3c5e95; + border-radius: 7px; + background: #10284b; + color: #d6e7ff; + font-size: 12px; + padding: 0 8px; + } + .sprite-dropdown-wrap { + position: relative; + } + .sprite-dropdown-btn { + display: flex; + align-items: center; + justify-content: space-between; + cursor: pointer; + gap: 8px; + } + .sprite-dropdown-current { + display: inline-flex; + align-items: center; + gap: 6px; + min-width: 0; + } + .sprite-dropdown-menu { + margin-top: 4px; + max-height: 180px; + overflow: auto; + border: 1px solid #3c5e95; + border-radius: 8px; + background: #0f2344; + display: grid; + gap: 4px; + padding: 4px; + } + .sprite-option-btn { + height: 30px; + border: 1px solid #35537f; + border-radius: 7px; + background: #132b4f; + color: #d6e7ff; + font-size: 11px; + cursor: pointer; + display: flex; + align-items: center; + gap: 6px; + text-align: left; + padding: 0 6px; + } + .sprite-option-btn.active { + border-color: #64aaf8; + background: #1a3f6d; + } + .history-meta { + color: #9fb8e5; + display: block; + margin-top: 3px; + font-size: 10px; + } + .history-preview { + padding: 8px; + border: 1px solid #2d426e; + border-radius: 8px; + background: #101d38; + font-size: 11px; + color: #d6e7ff; + min-height: 0; + overflow: auto; + } + .history-preview h4 { + margin: 0 0 6px; + font-size: 12px; + color: #e6f0ff; + } + .history-preview ul { + margin: 0; + padding-left: 16px; + } + .history-preview-empty { + color: #9fb8e5; + } + .history-panel-layout { + min-height: 0; + } + .history-stack { + display: grid; + grid-template-rows: minmax(0, 1fr) auto minmax(96px, auto); + gap: 8px; + min-height: 0; + flex: 1 1 auto; + } + .history-list-scroll { + min-height: 0; + overflow-y: auto; + padding-right: 2px; + } + .history-current { + padding: 8px; + border: 1px solid #40628f; + border-radius: 8px; + background: linear-gradient(180deg, #153155 0%, #132846 100%); + font-size: 11px; + color: #eef6ff; + } + .history-current-label { + margin-bottom: 4px; + font-size: 10px; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.06em; + color: #ffd166; + } + .history-current-empty { + color: #c7d8ef; + font-size: 11px; + } + .history-row.current-row { + border-color: #ffd166; + background: #28476c; + cursor: default; + } + .tool-window-layer { + position: absolute; + inset: 0; + pointer-events: none; + z-index: 40; + } + .tool-popout-window { + position: absolute; + min-width: 260px; + min-height: 220px; + max-width: calc(100% - 12px); + max-height: calc(100% - 12px); + border: 1px solid var(--editor-border-strong, #4f79af); + border-radius: 12px; + background: color-mix(in srgb, var(--editor-shell-bg, #0a1020) 88%, transparent); + color: var(--editor-shell-fg, #d8e8ff); + box-shadow: 0 16px 34px color-mix(in srgb, var(--editor-tab-shadow, rgba(3, 8, 18, 0.8)) 58%, transparent); + overflow: hidden; + pointer-events: auto; + display: grid; + grid-template-rows: 34px minmax(0, 1fr); + backdrop-filter: blur(6px); + } + .tool-popout-window.is-focused { + border-color: var(--editor-warn, #ffd166); + box-shadow: + 0 18px 36px color-mix(in srgb, var(--editor-tab-shadow, rgba(3, 8, 18, 0.8)) 68%, transparent), + 0 0 0 1px color-mix(in srgb, var(--editor-warn, #ffd166) 22%, transparent); + } + .tool-popout-window.tool-popout-window-inline { + position: relative; + inset: auto; + width: 100%; + max-width: 100%; + min-width: 0; + min-height: 0; + max-height: none; + margin-bottom: 10px; + box-shadow: 0 10px 20px rgba(2, 8, 18, 0.32); + } + .tool-popout-titlebar { + display: flex; + align-items: center; + gap: 8px; + padding: 0 10px; + background: linear-gradient(180deg, var(--editor-menu-grad-1, #1b365e) 0%, var(--editor-menu-grad-2, #122743) 100%); + border-bottom: 1px solid var(--editor-border, #365782); + cursor: grab; + user-select: none; + } + .tool-popout-titlebar:active { + cursor: grabbing; + } + .tool-popout-window-inline .tool-popout-titlebar { + cursor: grab; + } + .tool-popout-window-inline .tool-popout-titlebar:active { + cursor: grabbing; + } + .tool-popout-window-inline .tool-popout-dock-btn { + display: none; + } + .tool-popout-title { + min-width: 0; + flex: 1 1 auto; + font-size: 12px; + font-weight: 700; + color: var(--editor-shell-fg, #eef6ff); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + .tool-popout-hint { + font-size: 10px; + color: var(--editor-muted, #a9c2ec); + white-space: nowrap; + } + .tool-popout-dock-btn { + width: 24px; + height: 24px; + padding: 0; + border: 1px solid var(--editor-control-border, #3c5e95); + border-radius: 7px; + background: var(--editor-panel-bg-alt, #132b4f); + color: var(--editor-control-fg, #d6e7ff); + display: inline-flex; + align-items: center; + justify-content: center; + cursor: pointer; + flex: 0 0 auto; + font-size: 12px; + line-height: 1; + } + .tool-popout-dock-btn:hover { + background: var(--editor-panel-bg-hover, #1a3f6d); + } + .tool-popout-close-btn { + width: 24px; + height: 24px; + padding: 0; + border: 1px solid var(--editor-danger-border, #6f4a56); + border-radius: 7px; + background: var(--editor-danger, #3a1a24); + color: var(--editor-shell-fg, #ffe3ea); + display: inline-flex; + align-items: center; + justify-content: center; + cursor: pointer; + flex: 0 0 auto; + font-size: 11px; + line-height: 1; + font-weight: 700; + } + .tool-popout-close-btn:hover { + background: var(--editor-danger-hover, #572234); + } + .tool-popout-dock-icon { + font-weight: 700; + letter-spacing: -0.08em; + color: var(--editor-status-error, #ff8e8e); + transform: translateX(-0.5px); + } + .tool-popout-body { + min-height: 0; + overflow: auto; + padding: 10px; + } + .tool-popout-body > .sidebar-panel { + min-height: 100%; + gap: 6px; + } + .tool-popout-body > .sidebar-panel > h3 { + display: none; + } + .tool-popout-window .history-stack { + grid-template-rows: minmax(90px, 1fr) auto minmax(88px, auto); + } + .tool-popout-window-inline .history-stack { + grid-template-rows: minmax(160px, 1fr) auto minmax(96px, auto); + } + .tool-popout-resize { + position: absolute; + right: 0; + bottom: 0; + width: 18px; + height: 18px; + cursor: nwse-resize; + background: + linear-gradient(135deg, transparent 0 45%, color-mix(in srgb, var(--editor-shell-fg, #ffffff) 8%, transparent) 45% 52%, transparent 52% 62%, color-mix(in srgb, var(--editor-shell-fg, #ffffff) 18%, transparent) 62% 69%, transparent 69% 100%); + } + .tool-popout-window-inline .tool-popout-resize { + position: relative; + right: auto; + bottom: auto; + width: 100%; + height: 12px; + cursor: ns-resize; + background: + linear-gradient(180deg, color-mix(in srgb, var(--editor-shell-fg, #ffffff) 4%, transparent) 0%, color-mix(in srgb, var(--editor-shell-fg, #ffffff) 2%, transparent) 45%, transparent 45%, transparent 100%), + repeating-linear-gradient(90deg, transparent 0 10px, color-mix(in srgb, var(--editor-muted, #9fb8e5) 18%, transparent) 10px 12px, transparent 12px 22px); + border-top: 1px solid color-mix(in srgb, var(--editor-border-strong, #3c5e95) 55%, transparent); + } + .world-overview-window { + min-width: 320px; + min-height: 264px; + grid-template-rows: 34px 40px minmax(0, 1fr); + } + .entity-editor-window { + min-width: 380px; + min-height: 420px; + } + .entity-editor-card { + min-height: 100%; + display: grid; + grid-template-rows: auto auto auto minmax(0, 1fr) auto; + gap: 10px; + } + .entity-editor-head { + display: grid; + gap: 3px; + } + .entity-editor-title { + font-size: 13px; + font-weight: 700; + color: var(--editor-shell-fg, #eef6ff); + } + .entity-editor-subtitle { + font-size: 11px; + color: var(--editor-muted, #9fb8e5); + } + .entity-editor-grid { + display: grid; + grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); + gap: 10px; + } + .entity-editor-pane { + min-height: 0; + display: grid; + gap: 10px; + align-content: start; + } + .entity-editor-field { + display: grid; + gap: 5px; + min-width: 0; + } + .entity-editor-label { + font-size: 11px; + font-weight: 700; + color: var(--editor-shell-fg, #d6e7ff); + } + .entity-editor-field input, + .entity-editor-field select, + .entity-editor-field textarea, + .entity-editor-static { + width: 100%; + min-width: 0; + border: 1px solid var(--editor-control-border, #35537f); + border-radius: 8px; + background: var(--editor-panel-bg-alt, #132b4f); + color: var(--editor-control-fg, #eef6ff); + padding: 8px 10px; + font-size: 12px; + box-sizing: border-box; + } + .entity-editor-field textarea { + resize: vertical; + min-height: 110px; + } + .entity-editor-static { + min-height: 34px; + display: inline-flex; + align-items: center; + } + .entity-editor-footer { + display: grid; + grid-template-columns: minmax(0, 1fr) auto; + gap: 10px; + align-items: center; + } + .entity-editor-status { + font-size: 11px; + color: var(--editor-muted, #9fb8e5); + } + .entity-editor-actions { + display: inline-flex; + align-items: center; + gap: 8px; + } + .status-log-window { + min-width: 360px; + min-height: 240px; + } + .changelog-splash-window { + min-width: 520px; + min-height: 360px; + } + .changelog-splash-card { + min-height: 100%; + display: grid; + grid-template-rows: auto minmax(0, 1fr) auto; + gap: 12px; + } + .changelog-splash-hero { + padding: 14px 16px; + border: 1px solid var(--editor-border-strong, #4f79af); + border-radius: 12px; + background: + radial-gradient(circle at top right, color-mix(in srgb, var(--editor-accent, #64aaf8) 24%, transparent) 0%, transparent 48%), + linear-gradient(180deg, color-mix(in srgb, var(--editor-menu-grad-1, #1b365e) 64%, transparent) 0%, color-mix(in srgb, var(--editor-panel-bg, #11203f) 82%, transparent) 100%); + box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--editor-shell-bg, #0a1020) 22%, transparent); + } + .changelog-splash-kicker { + color: var(--editor-warn, #ffd166); + font-size: 10px; + font-weight: 800; + text-transform: uppercase; + letter-spacing: 0.08em; + margin-bottom: 6px; + } + .changelog-splash-title { + color: var(--editor-shell-fg, #eef6ff); + font-size: 22px; + font-weight: 800; + line-height: 1.1; + margin-bottom: 6px; + } + .changelog-splash-meta { + color: var(--editor-muted, #a9c2ec); + font-size: 12px; + line-height: 1.45; + } + .changelog-splash-list { + min-height: 0; + overflow: auto; + display: grid; + gap: 10px; + padding-right: 4px; + } + .changelog-splash-section { + padding: 12px 14px; + border: 1px solid var(--editor-border, #365782); + border-radius: 12px; + background: color-mix(in srgb, var(--editor-panel-bg, #11203f) 84%, transparent); + box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--editor-shell-bg, #0a1020) 14%, transparent); + } + .changelog-splash-section-title { + margin: 0 0 8px; + color: var(--editor-shell-fg, #eef6ff); + font-size: 13px; + font-weight: 800; + letter-spacing: 0.01em; + } + .changelog-splash-bullets { + margin: 0; + padding-left: 18px; + display: grid; + gap: 5px; + color: var(--editor-text-soft, #d7e7ff); + font-size: 12px; + line-height: 1.45; + } + .changelog-splash-bullet-note { + margin-top: 3px; + color: var(--editor-muted, #a9c2ec); + font-size: 11px; + line-height: 1.4; + font-style: italic; + } + .changelog-splash-footer { + display: grid; + grid-template-columns: minmax(0, 1fr) auto; + gap: 10px; + align-items: center; + } + .changelog-splash-footnote { + color: var(--editor-muted, #9fb8e5); + font-size: 11px; + line-height: 1.4; + } + .engine-overrides-launch-btn { + width: 100%; + justify-content: space-between; + gap: 10px; + } + .engine-overrides-summary { + margin-top: 6px; + color: var(--editor-muted, #9fb8e5); + font-size: 11px; + line-height: 1.4; + white-space: pre-wrap; + } + .engine-override-window { + min-width: 420px; + min-height: 260px; + } + .engine-override-card { + min-height: 100%; + display: grid; + grid-template-rows: auto minmax(0, 1fr); + gap: 10px; + } + .engine-override-head { + display: grid; + grid-template-columns: minmax(0, 1fr) auto; + gap: 6px 12px; + align-items: start; + } + .engine-override-title { + grid-column: 1 / 2; + font-size: 13px; + font-weight: 700; + color: var(--editor-shell-fg, #eef6ff); + } + .engine-override-meta { + grid-column: 1 / 2; + font-size: 11px; + color: var(--editor-muted, #9fb8e5); + } + .engine-override-head .mini-btn { + grid-column: 2 / 3; + grid-row: 1 / 3; + align-self: start; + } + .engine-override-list { + display: grid; + gap: 10px; + min-height: 0; + overflow: auto; + padding-right: 2px; + } + .engine-override-empty { + border: 1px dashed var(--editor-control-border, #3f5e90); + border-radius: 10px; + padding: 12px; + background: var(--editor-panel-bg-alt, #132848); + color: var(--editor-muted, #9fb8e5); + font-size: 12px; + line-height: 1.5; + } + .engine-override-row { + display: grid; + gap: 8px; + border: 1px solid var(--editor-border, #2e426c); + border-radius: 10px; + padding: 10px; + background: var(--editor-panel-bg, #121f3b); + } + .engine-override-row-head { + display: grid; + grid-template-columns: minmax(0, 1fr) auto; + gap: 8px; + align-items: center; + } + .engine-override-select, + .engine-override-number-input { + width: 100%; + min-width: 0; + border: 1px solid var(--editor-control-border, #3c5e95); + border-radius: 8px; + background: var(--editor-control-bg, #1a345e); + color: var(--editor-control-fg, #d6e7ff); + padding: 7px 9px; + font-size: 12px; + } + .engine-override-description { + color: var(--editor-muted, #9fb8e5); + font-size: 11px; + line-height: 1.45; + } + .engine-override-value-row { + display: grid; + grid-template-columns: auto minmax(0, 1fr); + gap: 10px; + align-items: center; + } + .engine-override-value-label { + color: var(--editor-muted-strong, #cfe2ff); + font-size: 11px; + font-weight: 700; + letter-spacing: 0.02em; + text-transform: uppercase; + } + .engine-override-toggle { + display: inline-flex; + align-items: center; + gap: 8px; + color: var(--editor-control-fg, #d6e7ff); + font-size: 12px; + } + .status-log-card { + min-height: 100%; + display: grid; + grid-template-rows: auto auto minmax(0, 1fr); + gap: 10px; + } + .status-log-head { + display: grid; + gap: 3px; + } + .status-log-title { + font-size: 13px; + font-weight: 700; + color: var(--editor-shell-fg, #eef6ff); + } + .status-log-meta { + font-size: 11px; + color: var(--editor-muted, #9fb8e5); + } + .status-log-actions { + display: inline-flex; + align-items: center; + justify-content: flex-end; + gap: 8px; + flex-wrap: wrap; + } + .status-log-list { + min-height: 0; + overflow: auto; + display: grid; + align-content: start; + gap: 8px; + padding: 2px; + } + .status-log-empty { + color: var(--editor-muted, #9fb8e5); + font-size: 11px; + line-height: 1.45; + } + .status-log-row { + display: grid; + gap: 6px; + padding: 9px 10px; + border: 1px solid var(--editor-border, #2e426c); + border-radius: 10px; + background: color-mix(in srgb, var(--editor-panel-bg, #121f3b) 96%, transparent); + } + .status-log-row-head { + display: flex; + align-items: center; + justify-content: space-between; + gap: 8px; + flex-wrap: wrap; + } + .status-log-row-level { + display: inline-flex; + align-items: center; + min-height: 22px; + padding: 0 8px; + border-radius: 999px; + border: 1px solid var(--editor-control-border, #35537f); + background: var(--editor-panel-bg-alt, #132b4f); + color: var(--editor-control-fg, #eef6ff); + font-size: 10px; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.04em; + } + .status-log-row-level-error { + border-color: color-mix(in srgb, var(--editor-status-error, #ff7b93) 50%, transparent); + color: var(--editor-status-error, #ffb7c3); + } + .status-log-row-level-information { + border-color: color-mix(in srgb, var(--editor-accent, #64aaf8) 42%, transparent); + color: var(--editor-shell-fg, #eef6ff); + } + .status-log-row-time { + font-size: 11px; + color: var(--editor-muted, #9fb8e5); + } + .status-log-row-message { + font-size: 12px; + line-height: 1.45; + color: var(--editor-shell-fg, #dbe9ff); + white-space: pre-wrap; + word-break: break-word; + } + .world-overview-body { + display: grid; + grid-template-columns: minmax(150px, 190px) minmax(0, 1fr); + gap: 8px; + overflow: hidden; + } + .world-overview-sidebar { + min-width: 0; + display: grid; + grid-template-rows: auto minmax(0, 1fr); + gap: 8px; + padding: 10px; + border: 1px solid var(--editor-border, #2e426c); + border-radius: 10px; + background: + linear-gradient( + 180deg, + color-mix(in srgb, var(--editor-panel-bg, #121f3b) 96%, transparent) 0%, + color-mix(in srgb, var(--editor-shell-bg, #0a1020) 98%, transparent) 100% + ); + overflow: hidden; + } + .world-overview-sidebar-head { + display: flex; + align-items: center; + justify-content: space-between; + gap: 8px; + color: var(--editor-shell-fg, #dbe9ff); + font-size: 11px; + font-weight: 700; + letter-spacing: 0.03em; + text-transform: uppercase; + } + .world-overview-sidebar-meta { + color: var(--editor-muted, #8fb2e1); + font-size: 10px; + font-weight: 600; + } + .world-overview-poi-list { + min-height: 0; + display: grid; + align-content: start; + gap: 6px; + overflow: auto; + padding-right: 2px; + } + .world-overview-poi-empty { + padding: 12px 10px; + border: 1px dashed color-mix(in srgb, var(--editor-muted, #8fb2e1) 28%, transparent); + border-radius: 10px; + color: var(--editor-muted, #8fb2e1); + font-size: 11px; + line-height: 1.45; + background: color-mix(in srgb, var(--editor-shell-bg, #0a1020) 55%, transparent); + } + .world-overview-poi-row { + width: 100%; + display: grid; + gap: 2px; + padding: 8px 9px; + border: 1px solid color-mix(in srgb, var(--editor-border, #2e426c) 92%, black 8%); + border-radius: 10px; + background: color-mix(in srgb, var(--editor-panel-bg-alt, #132b4f) 88%, transparent); + color: var(--editor-shell-fg, #dbe9ff); + text-align: left; + cursor: pointer; + transition: border-color 120ms ease, background 120ms ease, transform 120ms ease; + } + .world-overview-poi-row:hover { + border-color: var(--editor-border-strong, #4a73ae); + background: color-mix(in srgb, var(--editor-panel-bg-hover, #1a3f6d) 92%, transparent); + transform: translateY(-1px); + } + .world-overview-poi-row.is-active { + border-color: var(--editor-warn, #ffd166); + background: color-mix(in srgb, var(--editor-accent-soft, #22466e) 72%, var(--editor-panel-bg-alt, #132b4f)); + box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--editor-warn, #ffd166) 20%, transparent); + } + .world-overview-poi-title { + font-size: 12px; + font-weight: 700; + line-height: 1.25; + color: var(--editor-shell-fg, #f4f8ff); + } + .world-overview-poi-coords { + color: var(--editor-muted, #8fb2e1); + font-size: 10px; + font-variant-numeric: tabular-nums; + } + .world-overview-main { + min-width: 0; + display: grid; + grid-template-rows: minmax(0, 1fr) auto; + gap: 8px; + overflow: hidden; + } + .world-overview-action-banner { + display: flex; + align-items: center; + min-height: 0; + padding: 0 10px; + border: 1px solid color-mix(in srgb, var(--editor-border, #2e426c) 88%, transparent); + border-radius: 0; + border-left: 0; + border-right: 0; + border-top: 0; + background: color-mix(in srgb, var(--editor-panel-bg, #11203f) 90%, transparent); + color: var(--editor-text, #e6eefc); + font-size: 11px; + line-height: 1.3; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + } + .world-overview-action-banner.hidden { + display: none; + } + .world-overview-action-banner.is-idle { + border-color: color-mix(in srgb, var(--editor-border, #2e426c) 88%, transparent); + background: color-mix(in srgb, var(--editor-panel-bg, #11203f) 90%, transparent); + color: var(--editor-muted-strong, #bcd2f4); + } + .world-overview-action-banner.is-move { + border-color: color-mix(in srgb, #ff7070 70%, var(--editor-border, #2e426c)); + background: color-mix(in srgb, #5c1116 42%, var(--editor-panel-bg, #11203f)); + } + .world-overview-action-banner.is-duplicate { + border-color: color-mix(in srgb, #ffd166 70%, var(--editor-border, #2e426c)); + background: color-mix(in srgb, #5f4308 44%, var(--editor-panel-bg, #11203f)); + } + .world-overview-viewport { + position: relative; + min-height: 0; + border: 1px solid var(--editor-border, #2e426c); + border-radius: 10px; + background: + linear-gradient( + 180deg, + color-mix(in srgb, var(--editor-preview-bg, #0d1b34) 92%, transparent) 0%, + color-mix(in srgb, var(--editor-stage-bg, #060a14) 96%, transparent) 100% + ); + overflow: hidden; + cursor: grab; + } + .world-overview-viewport.is-panning { + cursor: grabbing; + } + .world-overview-canvas { + display: block; + width: 100%; + height: 100%; + image-rendering: pixelated; + } + .world-overview-empty { + position: absolute; + inset: 0; + display: flex; + align-items: center; + justify-content: center; + padding: 18px; + text-align: center; + color: var(--editor-muted-strong, #bcd2f4); + font-size: 12px; + background: color-mix(in srgb, var(--editor-stage-bg, #060a14) 82%, transparent); + } + .world-overview-empty.hidden { + display: none; + } + .world-overview-meta { + color: #b8d0f3; + font-size: 11px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + font-variant-numeric: tabular-nums; + } + .tile-art-window { + min-width: 520px; + width: 520px; + min-height: 628px; + } + .tile-art-preview-window { + min-width: 252px; + width: 252px; + min-height: 288px; + height: 288px; + } + .tile-art-preview-body { + min-height: 0; + display: block; + overflow: hidden; + } + .tile-art-preview-card { + height: 100%; + display: grid; + grid-template-rows: minmax(0, 1fr) auto auto; + gap: 10px; + } + .tile-art-preview-stage { + min-height: 0; + border: 1px solid var(--editor-border, #2d426b); + border-radius: 12px; + background: + linear-gradient( + 180deg, + color-mix(in srgb, var(--editor-preview-bg, #0d1b34) 94%, transparent) 0%, + color-mix(in srgb, var(--editor-stage-bg, #060a14) 98%, transparent) 100% + ); + display: flex; + align-items: center; + justify-content: center; + overflow: hidden; + } + .tile-art-preview-image { + width: 192px; + height: 192px; + object-fit: contain; + image-rendering: pixelated; + image-rendering: crisp-edges; + filter: drop-shadow(0 10px 18px rgba(3, 8, 18, 0.32)); + } + .tile-art-preview-image.hidden, + .tile-art-preview-empty.hidden { + display: none; + } + .tile-art-preview-empty { + color: var(--editor-muted-strong, #bcd2f4); + font-size: 12px; + text-align: center; + padding: 16px; + } + .tile-art-preview-frame-label { + color: var(--editor-shell-fg, #eef6ff); + font-size: 13px; + font-weight: 700; + text-align: center; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + .tile-art-preview-meta { + color: var(--editor-muted-strong, #bcd2f4); + font-size: 11px; + text-align: center; + font-variant-numeric: tabular-nums; + } + .tile-art-window.is-tags-tab { + min-height: 0; + } + .tile-art-body { + min-height: 0; + display: block; + overflow: hidden; + } + .tile-art-window.is-tags-tab .tile-art-body { + overflow: hidden; + } + .tile-art-window .tool-popout-resize { + display: none; + } + .tile-art-card { + height: 100%; + display: grid; + grid-template-rows: auto auto minmax(0, 1fr) auto; + gap: 10px; + } + .tile-art-window.is-tags-tab .tile-art-card { + height: auto; + grid-template-rows: auto auto auto; + } + .tile-art-head { + display: grid; + gap: 2px; + } + .tile-art-title-row { + display: grid; + grid-template-columns: minmax(0, 1fr) auto; + align-items: start; + gap: 10px; + } + .tile-art-title-stack { + min-width: 0; + display: grid; + gap: 2px; + } + .tile-art-title-display { + display: flex; + align-items: center; + gap: 5px; + min-width: 0; + } + .tile-art-record-title { + color: #e4efff; + font-size: 16px; + font-weight: 700; + line-height: 1.2; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + min-width: 0; + flex: 0 1 auto; + } + .tile-art-record-meta { + color: #8fb2e1; + font-size: 11px; + font-variant-numeric: tabular-nums; + } + .tile-art-title-input { + width: 100%; + min-width: 0; + height: 30px; + border: 1px solid var(--editor-control-border, #35537f); + border-radius: 8px; + background: color-mix(in srgb, var(--editor-panel-bg-alt, #132b4f) 86%, transparent); + color: var(--editor-control-fg, #eef6ff); + padding: 0 10px; + font-size: 12px; + } + .tile-art-title-input:focus { + outline: none; + border-color: var(--editor-accent, #64aaf8); + box-shadow: 0 0 0 1px color-mix(in srgb, var(--editor-accent, #64aaf8) 22%, transparent); + } + .tile-art-title-edit-btn { + width: 30px; + height: 30px; + padding: 0; + border: 1px solid var(--editor-control-border, #35537f); + border-radius: 8px; + background: color-mix(in srgb, var(--editor-panel-bg-alt, #132b4f) 86%, transparent); + color: var(--editor-control-fg, #eef6ff); + display: inline-flex; + align-items: center; + justify-content: center; + cursor: pointer; + flex: 0 0 auto; + } + .tile-art-title-edit-btn:hover { + border-color: var(--editor-border-strong, #5e84bd); + background: color-mix(in srgb, var(--editor-panel-bg, #11203f) 84%, transparent); + } + .tile-art-title-edit-btn:focus-visible { + outline: none; + border-color: var(--editor-accent, #64aaf8); + box-shadow: 0 0 0 1px color-mix(in srgb, var(--editor-accent, #64aaf8) 22%, transparent); + } + .tile-art-title-edit-icon { + position: relative; + display: inline-block; + width: 13px; + height: 4px; + border-radius: 999px; + background: #7ee8c6; + transform: rotate(-35deg); + box-shadow: inset -3px 0 0 #ffcf70; + } + .tile-art-title-edit-icon::before { + content: ""; + position: absolute; + left: -3px; + top: 0; + width: 0; + height: 0; + border-top: 2px solid transparent; + border-bottom: 2px solid transparent; + border-right: 3px solid #eef6ff; + } + .tile-art-title-edit-icon::after { + content: ""; + position: absolute; + right: -2px; + top: 0; + width: 2px; + height: 4px; + border-radius: 999px; + background: #ff7f9f; + } + .tile-art-tabs { + display: inline-flex; + align-items: center; + justify-content: flex-end; + gap: 6px; + flex: 0 0 auto; + align-self: start; + padding-top: 0; + margin-top: 0; + } + .tile-art-tab-help-btn { + width: 30px; + height: 30px; + padding: 0; + border: 1px solid var(--editor-control-border, #35537f); + border-radius: 8px; + background: color-mix(in srgb, var(--editor-panel-bg-alt, #132b4f) 86%, transparent); + color: var(--editor-text-soft, #c7dbfb); + display: inline-flex; + align-items: center; + justify-content: center; + cursor: pointer; + flex: 0 0 auto; + } + .tile-art-tab-help-btn:hover { + border-color: var(--editor-border-strong, #5e84bd); + background: color-mix(in srgb, var(--editor-panel-bg, #11203f) 84%, transparent); + } + .tile-art-tab-help-btn:focus-visible { + outline: none; + border-color: var(--editor-accent, #64aaf8); + box-shadow: 0 0 0 1px color-mix(in srgb, var(--editor-accent, #64aaf8) 22%, transparent); + } + .tile-art-tab-help-icon { + position: relative; + display: inline-block; + width: 14px; + height: 12px; + border: 1px solid color-mix(in srgb, var(--editor-shell-fg, #eef6ff) 88%, transparent); + border-radius: 3px; + background: color-mix(in srgb, var(--editor-panel-bg, #11203f) 76%, transparent); + box-shadow: inset 0 -1px 0 rgba(255, 255, 255, 0.08); + } + .tile-art-tab-help-icon::before { + content: ""; + position: absolute; + left: 2px; + right: 2px; + top: 2px; + height: 2px; + border-radius: 999px; + background: #7ee8c6; + box-shadow: + 0 3px 0 #ffd166, + 0 6px 0 #64aaf8; + } + .tile-art-tab-help-icon::after { + content: "?"; + position: absolute; + right: -4px; + bottom: -5px; + width: 10px; + height: 10px; + border-radius: 999px; + background: #ff5f6d; + color: #fff5f6; + font-size: 8px; + font-weight: 800; + line-height: 10px; + text-align: center; + box-shadow: 0 1px 4px rgba(3, 8, 18, 0.32); + } + .tile-art-tab-btn { + min-width: 74px; + height: 30px; + padding: 0 12px; + border: 1px solid var(--editor-control-border, #35537f); + border-radius: 8px; + background: color-mix(in srgb, var(--editor-panel-bg-alt, #132b4f) 86%, transparent); + color: var(--editor-text-soft, #c7dbfb); + font-size: 11px; + font-weight: 700; + cursor: pointer; + white-space: nowrap; + } + .tile-art-tab-btn:hover { + border-color: var(--editor-border-strong, #5e84bd); + background: color-mix(in srgb, var(--editor-panel-bg, #11203f) 84%, transparent); + } + .tile-art-tab-btn.is-active { + border-color: var(--editor-accent, #64aaf8); + box-shadow: 0 0 0 1px color-mix(in srgb, var(--editor-accent, #64aaf8) 22%, transparent); + background: color-mix(in srgb, var(--editor-panel-bg, #11203f) 92%, transparent); + color: var(--editor-shell-fg, #eef6ff); + } + .tile-art-tab-btn:focus-visible { + outline: none; + border-color: var(--editor-accent, #64aaf8); + box-shadow: 0 0 0 1px color-mix(in srgb, var(--editor-accent, #64aaf8) 22%, transparent); + } + .tile-art-pane { + min-height: 0; + } + .tile-art-design-pane { + min-height: 0; + display: grid; + grid-template-rows: auto auto minmax(0, 1fr); + gap: 10px; + } + .tile-art-animation-pane { + min-width: 0; + padding: 8px; + border: 1px solid var(--editor-border, #2d426b); + border-radius: 12px; + background: color-mix(in srgb, var(--editor-shell-bg, #0a1020) 90%, transparent); + overflow: hidden; + } + .tile-art-animation-controls { + display: grid; + grid-template-columns: 60px minmax(0, 1fr); + align-items: start; + gap: 8px; + margin-bottom: 8px; + } + .tile-art-animation-speed-host { + grid-column: 2; + display: flex; + justify-content: flex-start; + } + .tile-art-animation-speed-host.hidden { + display: none; + } + .tile-art-animation-speed-menu { + min-width: 220px; + display: grid; + gap: 8px; + padding: 8px; + border: 1px solid var(--editor-control-border, #35537f); + border-radius: 10px; + background: color-mix(in srgb, var(--editor-panel-bg-alt, #132b4f) 90%, transparent); + } + .tile-art-playback-options { + display: grid; + gap: 6px; + } + .tile-art-playback-option { + min-width: 0; + display: grid; + gap: 2px; + padding: 8px 10px; + border: 1px solid var(--editor-control-border, #35537f); + border-radius: 10px; + background: color-mix(in srgb, var(--editor-panel-bg, #11203f) 84%, transparent); + color: var(--editor-control-fg, #eef6ff); + text-align: left; + cursor: pointer; + } + .tile-art-playback-option:hover { + border-color: var(--editor-border-strong, #5e84bd); + background: color-mix(in srgb, var(--editor-panel-bg, #11203f) 92%, transparent); + } + .tile-art-playback-option.is-active { + border-color: var(--editor-accent, #64aaf8); + box-shadow: 0 0 0 1px color-mix(in srgb, var(--editor-accent, #64aaf8) 22%, transparent); + } + .tile-art-playback-option-title { + font-size: 11px; + font-weight: 700; + color: var(--editor-shell-fg, #eef6ff); + } + .tile-art-playback-option-help { + font-size: 10px; + color: var(--editor-muted-strong, #bcd2f4); + } + .tile-art-animation-timeline-row { + display: grid; + grid-template-columns: minmax(0, 1fr) auto; + align-items: stretch; + gap: 8px; + min-width: 0; + } + .tile-art-animation-timeline { + display: flex; + align-items: stretch; + gap: 8px; + min-width: 0; + overflow-x: auto; + overflow-y: hidden; + padding-bottom: 2px; + } + .tile-art-animation-add-host { + display: flex; + align-items: center; + justify-content: center; + flex: 0 0 auto; + } + .tile-art-frame-card { + width: 84px; + min-width: 84px; + min-height: 86px; + padding: 7px 7px 8px; + border: 1px solid var(--editor-control-border, #35537f); + border-radius: 10px; + background: color-mix(in srgb, var(--editor-panel-bg-alt, #132b4f) 86%, transparent); + color: var(--editor-control-fg, #eef6ff); + display: grid; + grid-template-rows: 56px auto; + gap: 7px; + cursor: pointer; + text-align: left; + flex: 0 0 auto; + } + .tile-art-frame-card:hover { + border-color: var(--editor-border-strong, #5e84bd); + background: color-mix(in srgb, var(--editor-panel-bg, #11203f) 84%, transparent); + } + .tile-art-frame-card.is-active { + border-color: var(--editor-accent, #64aaf8); + box-shadow: 0 0 0 1px color-mix(in srgb, var(--editor-accent, #64aaf8) 22%, transparent); + } + .tile-art-frame-card.is-disabled { + opacity: 0.65; + } + .tile-art-frame-card.is-dragging { + opacity: 0.5; + } + .tile-art-frame-card.is-drop-before, + .tile-art-frame-card.is-drop-after { + position: relative; + } + .tile-art-frame-card.is-drop-before::before, + .tile-art-frame-card.is-drop-after::after { + content: ""; + position: absolute; + top: 6px; + bottom: 6px; + width: 3px; + border-radius: 999px; + background: var(--editor-accent, #64aaf8); + box-shadow: 0 0 0 1px color-mix(in srgb, var(--editor-accent, #64aaf8) 28%, transparent); + pointer-events: none; + } + .tile-art-frame-card.is-drop-before::before { + left: -6px; + } + .tile-art-frame-card.is-drop-after::after { + right: -6px; + } + .tile-art-frame-card:focus-visible { + outline: none; + border-color: var(--editor-accent, #64aaf8); + box-shadow: 0 0 0 1px color-mix(in srgb, var(--editor-accent, #64aaf8) 22%, transparent); + } + .tile-art-frame-preview { + display: flex; + align-items: center; + justify-content: center; + border: 1px solid color-mix(in srgb, var(--editor-border, #2d426b) 82%, transparent); + border-radius: 8px; + background: + linear-gradient(45deg, rgba(255,255,255,0.05) 25%, transparent 25%, transparent 75%, rgba(255,255,255,0.05) 75%), + linear-gradient(45deg, rgba(255,255,255,0.05) 25%, transparent 25%, transparent 75%, rgba(255,255,255,0.05) 75%); + background-size: 10px 10px; + background-position: 0 0, 5px 5px; + overflow: hidden; + } + .tile-art-frame-preview-image { + width: 100%; + height: 100%; + object-fit: contain; + image-rendering: pixelated; + display: block; + } + .tile-art-frame-label { + display: inline-flex; + align-items: center; + gap: 4px; + font-size: 10px; + font-weight: 700; + color: var(--editor-text-soft, #c7dbfb); + min-width: 0; + } + .tile-art-frame-label-icons { + display: inline-flex; + align-items: center; + gap: 3px; + flex: 0 0 auto; + } + .tile-art-frame-label-icon { + display: inline-flex; + align-items: center; + justify-content: center; + min-width: 1.1em; + height: 1.1em; + border-radius: 999px; + font-size: 0.9em; + line-height: 1; + font-weight: 800; + border: 1px solid color-mix(in srgb, var(--editor-control-border, #35537f) 72%, transparent); + background: color-mix(in srgb, var(--editor-panel-bg, #11203f) 82%, transparent); + } + .tile-art-frame-label-icon.is-check { + color: #7ee8c6; + } + .tile-art-frame-label-icon.is-x { + color: #ff9cad; + } + .tile-art-frame-label-icon.is-key { + color: #ffd166; + } + .tile-art-frame-label-text { + min-width: 0; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + flex: 1 1 auto; + } + .tile-art-frame-add-btn { + width: 50px; + min-width: 50px; + min-height: 52px; + padding: 5px; + justify-content: center; + align-items: center; + grid-template-rows: 1fr; + text-align: center; + border-style: dashed; + } + .tile-art-frame-add-icon { + display: inline-flex; + align-items: center; + justify-content: center; + width: 18px; + height: 18px; + border-radius: 999px; + border: 1px solid color-mix(in srgb, var(--editor-accent, #64aaf8) 48%, transparent); + background: color-mix(in srgb, var(--editor-panel-bg, #11203f) 84%, transparent); + color: var(--editor-shell-fg, #eef6ff); + font-size: 14px; + line-height: 1; + font-weight: 500; + margin: 0 auto; + } + .tile-art-tool-icon-speed { + width: 14px; + height: 14px; + border: 1.5px solid #eef6ff; + border-radius: 999px; + box-sizing: border-box; + } + .tile-art-tool-icon-speed::before { + content: ""; + position: absolute; + left: 6px; + top: 2px; + width: 1.5px; + height: 4px; + border-radius: 999px; + background: #ffd166; + transform-origin: bottom center; + } + .tile-art-tool-icon-speed::after { + content: ""; + position: absolute; + left: 6px; + top: 6px; + width: 4px; + height: 1.5px; + border-radius: 999px; + background: #7ee8c6; + transform-origin: left center; + transform: rotate(28deg); + } + .tile-art-tool-icon-play { + position: relative; + width: 0; + height: 0; + border-top: 8px solid transparent; + border-bottom: 8px solid transparent; + border-left: 14px solid #4edb7f; + margin-left: 3px; + filter: drop-shadow(0 0 4px rgba(78, 219, 127, 0.3)); + } + .tile-art-tool-icon-play::after { + content: ""; + position: absolute; + left: -16px; + top: -9px; + width: 18px; + height: 18px; + border-radius: 999px; + border: 1px solid color-mix(in srgb, #4edb7f 74%, transparent); + background: color-mix(in srgb, #4edb7f 10%, transparent); + } + .tile-art-tool-icon-playback { + position: relative; + width: 18px; + height: 16px; + } + .tile-art-tool-icon-playback::before { + content: ""; + position: absolute; + left: 1px; + right: 1px; + top: 7px; + height: 2px; + border-radius: 999px; + background: linear-gradient(90deg, #56db86 0%, #56db86 100%); + box-shadow: + -5px 0 0 -1px #56db86, + 5px 0 0 -1px #56db86; + } + .tile-art-tool-icon-playback::after { + content: ""; + position: absolute; + left: 2px; + top: 0; + width: 12px; + height: 12px; + border-left: 2px solid #ff6f7f; + border-top: 2px solid #ff6f7f; + border-radius: 10px 0 0 0; + transform: rotate(-18deg); + } + .tile-art-tool-icon-playback .tile-art-tool-icon-playback-arrow-a, + .tile-art-tool-icon-playback .tile-art-tool-icon-playback-arrow-b { + position: absolute; + display: block; + width: 0; + height: 0; + border-top: 3px solid transparent; + border-bottom: 3px solid transparent; + } + .tile-art-tool-icon-playback .tile-art-tool-icon-playback-arrow-a { + right: -1px; + top: 5px; + border-left: 5px solid #56db86; + } + .tile-art-tool-icon-playback .tile-art-tool-icon-playback-arrow-b { + left: -1px; + top: 0; + border-right: 5px solid #ff6f7f; + transform: rotate(-30deg); + } + .tile-art-color-strip { + display: grid; + grid-template-columns: max-content max-content; + align-items: center; + justify-content: center; + gap: 10px; + } + .tile-art-current { + display: grid; + grid-template-rows: auto auto; + align-content: start; + gap: 8px; + padding: 0; + border: 0; + border-radius: 0; + background: transparent; + } + .tile-art-current-row { + display: flex; + justify-content: center; + } + .tile-art-current-swatch { + position: relative; + display: block; + width: 42px; + height: 42px; + border-radius: 6px; + border: 1px solid color-mix(in srgb, var(--editor-border, #2d426b) 72%, white 12%); + background: var(--swatch-color, transparent); + overflow: hidden; + box-shadow: inset 0 0 0 1px rgba(8, 17, 29, 0.34); + } + .tile-art-current-swatch.is-transparent { + background: + linear-gradient(45deg, rgba(255,255,255,0.12) 25%, transparent 25%, transparent 75%, rgba(255,255,255,0.12) 75%), + linear-gradient(45deg, rgba(255,255,255,0.12) 25%, transparent 25%, transparent 75%, rgba(255,255,255,0.12) 75%); + background-size: 12px 12px; + background-position: 0 0, 6px 6px; + border-style: dashed; + } + .tile-art-current-indicator { + position: absolute; + right: 3px; + bottom: 3px; + width: 14px; + height: 18px; + border: 1.5px solid rgba(226, 240, 255, 0.95); + border-radius: 8px 8px 9px 9px; + background: rgba(5, 10, 18, 0.62); + box-shadow: 0 2px 6px rgba(3, 8, 18, 0.28); + pointer-events: none; + overflow: hidden; + } + .tile-art-current-indicator::before { + content: ""; + position: absolute; + left: 50%; + top: 3px; + width: 1.5px; + height: 5px; + background: rgba(226, 240, 255, 0.9); + transform: translateX(-50%); + border-radius: 999px; + } + .tile-art-current-indicator-left, + .tile-art-current-indicator-right { + position: absolute; + top: 0; + width: 50%; + height: 7px; + background: rgba(226, 240, 255, 0.14); + } + .tile-art-current-indicator-left { + left: 0; + border-right: 1px solid rgba(226, 240, 255, 0.22); + border-radius: 7px 0 0 0; + } + .tile-art-current-indicator-right { + right: 0; + border-radius: 0 7px 0 0; + } + .tile-art-current-indicator.is-primary .tile-art-current-indicator-left, + .tile-art-current-indicator.is-secondary .tile-art-current-indicator-right { + background: #ff5f6d; + box-shadow: inset 0 0 0 1px rgba(111, 16, 25, 0.35); + } + .tile-art-current-meta { + color: #b7cdf5; + font-size: 11px; + font-weight: 700; + letter-spacing: 0.02em; + line-height: 1.35; + padding-top: 2px; + } + .tile-art-tools { + display: grid; + grid-template-columns: 60px minmax(0, 1fr); + align-items: start; + gap: 8px; + } + .tile-art-tools-label { + color: #dbe9ff; + font-size: 11px; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.03em; + } + .tile-art-tool-buttons { + display: flex; + gap: 8px; + flex-wrap: wrap; + } + .tile-art-tool-menu-host { + grid-column: 2; + display: flex; + justify-content: flex-start; + } + .tile-art-tool-menu-host.hidden { + display: none; + } + .tile-art-tool-btn { + width: 34px; + height: 34px; + padding: 0; + border: 1px solid var(--editor-control-border, #35537f); + border-radius: 8px; + background: color-mix(in srgb, var(--editor-panel-bg-alt, #132b4f) 86%, transparent); + color: var(--editor-control-fg, #eef6ff); + display: inline-flex; + align-items: center; + justify-content: center; + cursor: pointer; + font-size: 11px; + font-weight: 700; + } + .tile-art-tool-btn:hover { + border-color: var(--editor-border-strong, #5e84bd); + background: color-mix(in srgb, var(--editor-panel-bg, #11203f) 84%, transparent); + } + .tile-art-tool-btn.is-active { + border-color: var(--editor-warn, #ffd166); + box-shadow: 0 0 0 1px color-mix(in srgb, var(--editor-warn, #ffd166) 24%, transparent); + background: color-mix(in srgb, #5f4308 36%, var(--editor-panel-bg, #11203f)); + color: #fff2c8; + } + .tile-art-tool-btn.is-open { + border-color: var(--editor-tool-armed, #7ee8c6); + box-shadow: 0 0 0 1px color-mix(in srgb, var(--editor-tool-armed, #7ee8c6) 24%, transparent); + } + .tile-art-tool-btn:focus-visible { + outline: none; + border-color: var(--editor-accent, #64aaf8); + box-shadow: 0 0 0 1px color-mix(in srgb, var(--editor-accent, #64aaf8) 22%, transparent); + } + .tile-art-tool-copy { + display: none; + } + .tile-art-tool-icon { + position: relative; + display: inline-block; + flex: 0 0 auto; + } + .tile-art-tool-icon-pencil { + width: 13px; + height: 4px; + border-radius: 999px; + background: #7ee8c6; + transform: rotate(-35deg); + box-shadow: inset -3px 0 0 #ffcf70; + } + .tile-art-tool-icon-pencil::before { + content: ""; + position: absolute; + left: -3px; + top: 0; + width: 0; + height: 0; + border-top: 2px solid transparent; + border-bottom: 2px solid transparent; + border-right: 3px solid #eef6ff; + } + .tile-art-tool-icon-pencil::after { + content: ""; + position: absolute; + right: -2px; + top: 0; + width: 2px; + height: 4px; + border-radius: 999px; + background: #ff7f9f; + } + .tile-art-tool-icon-line { + width: 15px; + height: 12px; + } + .tile-art-tool-icon-line::before { + content: ""; + position: absolute; + left: 1px; + top: 8px; + width: 13px; + height: 2px; + border-radius: 999px; + background: #7ee8c6; + transform: rotate(-35deg); + transform-origin: left center; + box-shadow: 0 0 0 1px rgba(8, 17, 29, 0.24); + } + .tile-art-tool-icon-line::after { + content: ""; + position: absolute; + left: 0; + top: 0; + width: 15px; + height: 12px; + background: + radial-gradient(circle at 2px 9px, #eef6ff 0 1.4px, transparent 1.5px), + radial-gradient(circle at 13px 2px, #ffd166 0 1.4px, transparent 1.5px); + } + .tile-art-tool-icon-bucket { + width: 13px; + height: 11px; + border: 2px solid #7ee8c6; + border-top: 0; + border-radius: 2px 2px 5px 5px; + transform: rotate(-18deg); + box-sizing: border-box; + } + .tile-art-tool-icon-bucket::before { + content: ""; + position: absolute; + left: 1px; + top: -6px; + width: 7px; + height: 6px; + border: 2px solid #7ee8c6; + border-bottom: 0; + border-radius: 7px 7px 0 0; + box-sizing: border-box; + } + .tile-art-tool-icon-bucket::after { + content: ""; + position: absolute; + right: -4px; + bottom: -2px; + width: 4px; + height: 6px; + border-radius: 999px; + background: #64aaf8; + transform: rotate(20deg); + opacity: 0.92; + } + .tile-art-tool-icon-shape { + width: 14px; + height: 11px; + border: 2px solid #7ee8c6; + border-radius: 2px; + box-sizing: border-box; + } + .tile-art-tool-icon-shape::before { + content: ""; + position: absolute; + left: 3px; + top: 2px; + width: 6px; + height: 5px; + border: 2px solid #ffd166; + border-radius: 1px; + box-sizing: border-box; + background: rgba(255, 209, 102, 0.18); + } + .tile-art-tool-icon-eraser { + width: 14px; + height: 9px; + border-radius: 3px; + background: linear-gradient(135deg, #ff8aa6 0 48%, #eef6ff 48% 100%); + transform: rotate(-20deg); + box-shadow: inset 0 0 0 1px rgba(17, 29, 53, 0.45); + } + .tile-art-tool-icon-transform { + width: 14px; + height: 10px; + } + .tile-art-tool-icon-transform::before, + .tile-art-tool-icon-transform::after { + content: ""; + position: absolute; + top: 4px; + width: 5px; + height: 2px; + background: #7ee8c6; + } + .tile-art-tool-icon-transform::before { + left: 1px; + box-shadow: -2px 0 0 #7ee8c6; + } + .tile-art-tool-icon-transform::after { + right: 1px; + box-shadow: 2px 0 0 #7ee8c6; + } + .tile-art-tool-icon-transform { + border-left: 5px solid #7ee8c6; + border-right: 5px solid #7ee8c6; + border-top: 5px solid transparent; + border-bottom: 5px solid transparent; + box-sizing: border-box; + } + .tile-art-tool-icon-opacity { + width: 14px; + height: 14px; + border-radius: 999px; + background: linear-gradient(135deg, #05070d 0%, #f5f8ff 100%); + box-shadow: inset 0 0 0 1px rgba(17, 29, 53, 0.55); + } + .tile-art-tool-icon-shift { + width: 14px; + height: 14px; + background: center / contain no-repeat url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='14' height='14' viewBox='0 0 14 14'%3E%3Cg fill='none' stroke='%237ee8c6' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M7 1.5v11M1.5 7h11'/%3E%3Cpath d='M7 1.5 5.4 3.1M7 1.5l1.6 1.6M7 12.5l-1.6-1.6M7 12.5l1.6-1.6M1.5 7l1.6-1.6M1.5 7l1.6 1.6M12.5 7l-1.6-1.6M12.5 7l-1.6 1.6'/%3E%3C/g%3E%3C/svg%3E"); + } + .tile-art-tool-menu { + width: min(100%, 360px); + display: grid; + gap: 8px; + padding: 10px; + border: 1px solid var(--editor-control-border, #35537f); + border-radius: 10px; + background: color-mix(in srgb, var(--editor-panel-bg-alt, #132b4f) 94%, transparent); + box-shadow: 0 12px 28px rgba(5, 10, 22, 0.28); + } + .tile-art-tool-menu-title { + color: #eef6ff; + font-size: 11px; + font-weight: 800; + text-transform: uppercase; + letter-spacing: 0.03em; + } + .tile-art-tool-menu-help { + color: #9db8e4; + font-size: 11px; + line-height: 1.4; + } + .tile-art-tool-menu-row { + display: grid; + gap: 6px; + } + .tile-art-tool-menu-label { + color: #dbe9ff; + font-size: 11px; + font-weight: 700; + } + .tile-art-tool-menu-buttons { + display: flex; + flex-wrap: wrap; + gap: 6px; + } + .tile-art-tool-menu-btn { + min-height: 28px; + padding: 0 10px; + border: 1px solid var(--editor-control-border, #35537f); + border-radius: 8px; + background: color-mix(in srgb, var(--editor-panel-bg, #11203f) 86%, transparent); + color: var(--editor-control-fg, #eef6ff); + cursor: pointer; + font-size: 11px; + font-weight: 700; + } + .tile-art-tool-menu-btn:hover { + border-color: var(--editor-border-strong, #5e84bd); + background: color-mix(in srgb, var(--editor-panel-bg, #11203f) 92%, transparent); + } + .tile-art-tool-menu-btn.is-active { + border-color: var(--editor-warn, #ffd166); + background: color-mix(in srgb, #5f4308 36%, var(--editor-panel-bg, #11203f)); + color: #fff2c8; + } + .tile-art-tool-menu-btn:focus-visible { + outline: none; + border-color: var(--editor-accent, #64aaf8); + box-shadow: 0 0 0 1px color-mix(in srgb, var(--editor-accent, #64aaf8) 22%, transparent); + } + .tile-art-shape-menu-layout { + display: grid; + grid-template-columns: minmax(124px, auto) minmax(142px, 1fr); + gap: 8px; + align-items: start; + } + .tile-art-shape-menu-primary, + .tile-art-shape-menu-submenu { + display: grid; + gap: 6px; + } + .tile-art-shape-menu-submenu { + min-width: 0; + padding-left: 8px; + border-left: 1px solid color-mix(in srgb, var(--editor-control-border, #35537f) 72%, transparent); + } + .tile-art-shape-menu-subtitle { + margin-bottom: 2px; + } + .tile-art-shape-menu-trigger { + justify-content: space-between; + text-align: left; + } + .tile-art-opacity-controls { + display: grid; + grid-template-columns: minmax(0, 1fr) 64px auto; + align-items: center; + gap: 8px; + } + .tile-art-opacity-range { + width: 100%; + accent-color: var(--editor-accent, #64aaf8); + appearance: none; + -webkit-appearance: none; + height: 6px; + border-radius: 999px; + background: color-mix(in srgb, var(--editor-border, #2d426b) 78%, black 22%); + outline: none; + } + .tile-art-opacity-range::-webkit-slider-runnable-track { + height: 6px; + border-radius: 999px; + background: linear-gradient( + 90deg, + color-mix(in srgb, var(--editor-accent, #64aaf8) 88%, white 12%) 0%, + color-mix(in srgb, var(--editor-border, #2d426b) 78%, black 22%) 100% + ); + } + .tile-art-opacity-range::-webkit-slider-thumb { + -webkit-appearance: none; + appearance: none; + width: 14px; + height: 14px; + margin-top: -4px; + border-radius: 999px; + border: 1px solid color-mix(in srgb, var(--editor-accent, #64aaf8) 62%, white 20%); + background: var(--editor-shell-fg, #eef6ff); + box-shadow: 0 2px 6px rgba(3, 8, 18, 0.34); + cursor: pointer; + } + .tile-art-opacity-range::-moz-range-track { + height: 6px; + border-radius: 999px; + background: color-mix(in srgb, var(--editor-border, #2d426b) 78%, black 22%); + } + .tile-art-opacity-range::-moz-range-thumb { + width: 14px; + height: 14px; + border-radius: 999px; + border: 1px solid color-mix(in srgb, var(--editor-accent, #64aaf8) 62%, white 20%); + background: var(--editor-shell-fg, #eef6ff); + box-shadow: 0 2px 6px rgba(3, 8, 18, 0.34); + cursor: pointer; + } + .tile-art-opacity-range:focus-visible { + outline: none; + box-shadow: 0 0 0 1px color-mix(in srgb, var(--editor-accent, #64aaf8) 24%, transparent); + } + .tile-art-opacity-number { + width: 64px; + height: 30px; + border: 1px solid var(--editor-control-border, #35537f); + border-radius: 8px; + background: color-mix(in srgb, var(--editor-panel-bg, #11203f) 86%, transparent); + color: var(--editor-control-fg, #eef6ff); + padding: 0 8px; + font-size: 12px; + } + .tile-art-opacity-number:focus-visible { + outline: none; + border-color: var(--editor-accent, #64aaf8); + box-shadow: 0 0 0 1px color-mix(in srgb, var(--editor-accent, #64aaf8) 22%, transparent); + } + .tile-art-opacity-suffix { + color: #dbe9ff; + font-size: 11px; + font-weight: 700; + } + .tile-art-swatches { + display: grid; + grid-template-rows: repeat(3, 21px); + grid-auto-flow: column; + grid-auto-columns: 21px; + align-content: center; + align-self: center; + justify-content: start; + gap: 6px; + padding: 0; + border: 0; + border-radius: 0; + background: transparent; + } + .tile-art-swatch-btn { + position: relative; + width: 21px; + height: 21px; + padding: 0; + border: 1px solid var(--editor-border, #2d426b); + border-radius: 4px; + cursor: pointer; + background: var(--swatch-color, transparent); + overflow: hidden; + color: var(--editor-shell-fg, #f5f8ff); + } + .tile-art-swatch-indicator { + position: absolute; + width: 10px; + height: 13px; + border: 1px solid rgba(226, 240, 255, 0.95); + border-radius: 6px 6px 7px 7px; + background: rgba(5, 10, 18, 0.7); + box-shadow: 0 1px 3px rgba(3, 8, 18, 0.28); + pointer-events: none; + overflow: hidden; + z-index: 1; + } + .tile-art-swatch-indicator.is-primary { + left: 1px; + top: 1px; + } + .tile-art-swatch-indicator.is-secondary { + right: 1px; + bottom: 1px; + } + .tile-art-swatch-indicator::before { + content: ""; + position: absolute; + left: 50%; + top: 2px; + width: 1px; + height: 4px; + background: rgba(226, 240, 255, 0.92); + transform: translateX(-50%); + border-radius: 999px; + } + .tile-art-swatch-indicator-left, + .tile-art-swatch-indicator-right { + position: absolute; + top: 0; + width: 50%; + height: 5px; + background: rgba(226, 240, 255, 0.14); + } + .tile-art-swatch-indicator-left { + left: 0; + border-right: 1px solid rgba(226, 240, 255, 0.2); + border-radius: 5px 0 0 0; + } + .tile-art-swatch-indicator-right { + right: 0; + border-radius: 0 5px 0 0; + } + .tile-art-swatch-indicator.is-primary .tile-art-swatch-indicator-left, + .tile-art-swatch-indicator.is-secondary .tile-art-swatch-indicator-right { + background: #ff5f6d; + box-shadow: inset 0 0 0 1px rgba(111, 16, 25, 0.35); + } + .tile-art-swatch-btn:hover { + border-color: var(--editor-border-strong, #5e84bd); + } + .tile-art-swatch-btn:focus-visible { + outline: none; + border-color: var(--editor-accent, #64aaf8); + box-shadow: 0 0 0 1px color-mix(in srgb, var(--editor-accent, #64aaf8) 22%, transparent); + } + .tile-art-swatch-btn.is-active { + border-color: var(--editor-warn, #ffd166); + box-shadow: 0 0 0 1px color-mix(in srgb, var(--editor-warn, #ffd166) 24%, transparent); + } + .tile-art-swatch-btn.is-secondary { + border-color: #7ee8c6; + box-shadow: inset 0 0 0 1px rgba(126, 232, 198, 0.4); + } + .tile-art-swatch-btn.is-transparent { + background: + linear-gradient(45deg, rgba(255,255,255,0.08) 25%, transparent 25%, transparent 75%, rgba(255,255,255,0.08) 75%), + linear-gradient(45deg, rgba(255,255,255,0.08) 25%, transparent 25%, transparent 75%, rgba(255,255,255,0.08) 75%); + background-size: 12px 12px; + background-position: 0 0, 6px 6px; + border-style: dashed; + } + .tile-art-swatch-btn.is-transparent-action { + width: 32px; + height: 32px; + border-width: 2px; + border-color: rgba(214, 231, 255, 0.38); + border-radius: 6px; + box-shadow: inset 0 0 0 1px rgba(8, 17, 29, 0.58); + } + .tile-art-swatch-btn.is-transparent-action:hover { + border-color: #8fd0ff; + } + .tile-art-swatch-btn.is-transparent-action.is-active { + border-color: var(--editor-warn, #ffd166); + box-shadow: + 0 0 0 1px color-mix(in srgb, var(--editor-warn, #ffd166) 24%, transparent), + inset 0 0 0 1px rgba(8, 17, 29, 0.58); + } + .tile-art-swatch-btn.is-transparent-action.is-secondary { + border-color: #7ee8c6; + box-shadow: + inset 0 0 0 1px rgba(8, 17, 29, 0.58), + 0 0 0 1px rgba(126, 232, 198, 0.26); + } + .tile-art-grid-wrap { + min-height: 0; + overflow: hidden; + padding: 8px; + border: 1px solid var(--editor-border, #2d426b); + border-radius: 12px; + background: + linear-gradient(var(--preview-bg-color, transparent), var(--preview-bg-color, transparent)), + color-mix(in srgb, var(--editor-shell-bg, #0a1020) 92%, transparent); + } + .tile-art-grid-stage { + display: grid; + grid-template-columns: auto minmax(0, 1fr); + align-items: start; + gap: 8px; + min-width: 0; + } + .tile-art-grid-stage.is-template-drop-target .tile-art-grid-wrap { + border-color: var(--editor-tool-armed, #7ee8c6); + box-shadow: + 0 0 0 1px color-mix(in srgb, var(--editor-tool-armed, #7ee8c6) 28%, transparent), + inset 0 0 0 1px rgba(126, 232, 198, 0.16); + } + .tile-art-grid-stage.is-template-drop-target .tile-art-used-swatches { + border-color: var(--editor-tool-armed, #7ee8c6); + box-shadow: inset 0 0 0 1px rgba(126, 232, 198, 0.14); + } + .tile-art-used-swatches { + width: 38px; + min-height: 40px; + padding: 5px 6px; + border: 1px solid var(--editor-border, #2d426b); + border-radius: 10px; + background: color-mix(in srgb, var(--editor-shell-bg, #0a1020) 88%, transparent); + display: grid; + grid-auto-rows: min-content; + align-content: start; + justify-items: center; + justify-content: center; + gap: 6px; + box-sizing: border-box; + } + .tile-art-used-swatch-btn { + width: 24px; + height: 24px; + border-radius: 5px; + } + .tile-art-used-swatch-btn.is-transparent-action { + width: 24px; + height: 24px; + border-width: 1px; + border-radius: 5px; + } + .tile-art-grid { + display: grid; + gap: 2px; + width: max-content; + margin: 0 auto; + touch-action: none; + } + .tile-art-grid.is-eyedropper .tile-art-cell { + cursor: inherit; + } + .tile-art-preview-hint { + color: var(--editor-muted, #8fb2e1); + font-size: 11px; + line-height: 1.35; + text-align: center; + padding: 0 4px; + } + .tile-art-shortcut-help-panel { + min-width: 250px; + display: grid; + gap: 8px; + } + .tile-art-shortcut-help-title { + font-size: 11px; + font-weight: 700; + color: var(--editor-muted, #9fb8e5); + text-transform: uppercase; + letter-spacing: 0.04em; + } + .tile-art-shortcut-help-list { + display: grid; + gap: 6px; + } + .tile-art-shortcut-row { + grid-template-columns: minmax(0, 1fr) auto; + } + .tile-art-shortcut-action { + min-width: 72px; + } + .shortcut-mouse-dot.is-secondary { + background: #7ee8c6; + } + .tile-art-tags-pane { + min-height: 0; + display: grid; + grid-template-rows: auto minmax(0, 1fr); + gap: 10px; + padding: 2px 0 0; + } + .tile-art-window.is-tags-tab .tile-art-tags-pane { + grid-template-rows: auto auto; + align-content: start; + } + .tile-art-tag-field { + display: grid; + gap: 6px; + } + .tile-art-tag-head { + display: flex; + align-items: center; + justify-content: space-between; + gap: 8px; + } + .tile-art-tag-label { + color: var(--editor-shell-fg, #dbe9ff); + font-size: 11px; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.03em; + } + .tile-art-tag-actions { + display: inline-flex; + align-items: center; + gap: 6px; + flex-wrap: wrap; + justify-content: flex-end; + } + .tile-art-tag-input { + width: 100%; + min-width: 0; + height: 32px; + border: 1px solid var(--editor-control-border, #35537f); + border-radius: 8px; + background: color-mix(in srgb, var(--editor-panel-bg-alt, #132b4f) 86%, transparent); + color: var(--editor-control-fg, #eef6ff); + padding: 0 10px; + font-size: 12px; + } + .tile-art-tag-input:focus { + outline: none; + border-color: var(--editor-accent, #64aaf8); + box-shadow: 0 0 0 1px color-mix(in srgb, var(--editor-accent, #64aaf8) 22%, transparent); + } + .tile-art-tag-list { + min-height: 0; + display: flex; + align-content: flex-start; + align-items: flex-start; + flex-wrap: wrap; + gap: 8px; + overflow: auto; + padding: 8px; + border: 1px solid var(--editor-border, #2d426b); + border-radius: 12px; + background: color-mix(in srgb, var(--editor-shell-bg, #0a1020) 92%, transparent); + } + .tile-art-window.is-tags-tab .tile-art-tag-list { + min-height: 80px; + max-height: 240px; + } + .tile-art-tags-empty { + color: var(--editor-muted, #8fb2e1); + font-size: 11px; + line-height: 1.45; + } + .tile-art-tag-chip { + max-width: 100%; + min-height: 28px; + padding: 0 10px; + border: 1px solid var(--editor-control-border, #35537f); + border-radius: 999px; + background: color-mix(in srgb, var(--editor-panel-bg-alt, #132b4f) 86%, transparent); + color: var(--editor-control-fg, #eef6ff); + display: inline-flex; + align-items: center; + gap: 7px; + cursor: pointer; + font-size: 11px; + font-weight: 700; + } + .tile-art-tag-chip:hover { + border-color: var(--editor-danger-border, #ff9aa7); + background: color-mix(in srgb, var(--editor-danger, #3c1a1a) 88%, transparent); + } + .tile-art-tag-chip-label { + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + } + .tile-art-tag-chip-remove { + color: var(--editor-status-error, #ffb7c3); + font-size: 10px; + line-height: 1; + text-transform: uppercase; + } + .tile-art-cell { + width: 21px; + height: 21px; + border: 1px solid rgba(255,255,255,0.08); + padding: 0; + cursor: crosshair; + background: + linear-gradient(var(--paint-color, transparent), var(--paint-color, transparent)), + linear-gradient(45deg, rgba(255,255,255,0.09) 25%, transparent 25%, transparent 75%, rgba(255,255,255,0.09) 75%), + linear-gradient(45deg, rgba(255,255,255,0.09) 25%, transparent 25%, transparent 75%, rgba(255,255,255,0.09) 75%), + linear-gradient(var(--preview-bg-color, transparent), var(--preview-bg-color, transparent)); + background-size: cover, 12px 12px, 12px 12px, cover; + background-position: 0 0, 0 0, 6px 6px, 0 0; + } + .tile-art-cell:hover { + border-color: color-mix(in srgb, var(--editor-warn, #ffd166) 60%, transparent); + } + .tile-art-cell.is-transparent { + border-style: dashed; + } + .tile-art-footer { + display: grid; + grid-template-columns: minmax(0, 1fr) auto; + align-items: center; + gap: 10px; + } + .tile-art-window.is-tags-tab .tile-art-footer { + display: none; + } + .tile-art-status { + color: var(--editor-muted, #8fb2e1); + font-size: 11px; + line-height: 1.4; + min-height: 16px; + } + .tile-art-actions { + display: flex; + justify-content: flex-end; + gap: 8px; + } + .paint-palette { + display: flex; + flex-wrap: wrap; + gap: 6px; + margin-bottom: 12px; + } + .paint-swatch-btn { + display: inline-flex; + align-items: center; + gap: 6px; + height: 28px; + padding: 0 8px; + border: 1px solid #2d426e; + border-radius: 7px; + background: #132447; + color: #d6e7ff; + font-size: 11px; + cursor: pointer; + } + .paint-swatch-btn.active { + border-color: #ffd166; + background: #2b4669; + } + /* ── AtTooltip floating menu ── */ + .at-tooltip-panel { + position: fixed; + z-index: 9999; + background: #0f2344; + border: 1px solid #3c5e95; + border-radius: 10px; + padding: 5px; + min-width: 190px; + max-width: 290px; + overflow-y: auto; + display: grid; + gap: 3px; + box-shadow: 0 6px 28px rgba(0,0,20,0.8); + outline: none; + } + .at-tooltip-panel:focus-visible { + border-color: #64aaf8; + box-shadow: 0 0 0 1px rgba(100,170,248,0.4), 0 6px 28px rgba(0,0,20,0.8); + } + .at-tooltip-panel:empty::after { + content: 'No options'; + color: #7a9acc; + font-size: 11px; + padding: 6px 8px; + } + .at-tooltip-label { + padding: 4px 8px 2px; + color: #9fb8e5; + font-size: 10px; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.04em; + } + .at-tooltip-item { + display: flex; + align-items: center; + justify-content: space-between; + gap: 7px; + height: 32px; + padding: 0 8px; + border: 1px solid #35537f; + border-radius: 7px; + background: #132b4f; + color: #d6e7ff; + font-size: 11px; + text-align: left; + cursor: pointer; + white-space: nowrap; + overflow: hidden; + } + .at-tooltip-item:hover, .at-tooltip-item.active, .at-tooltip-item.is-active { + background: #1a3f6d; + border-color: #64aaf8; + } + .at-tooltip-item:focus-visible { + background: #1a3f6d; + border-color: #64aaf8; + outline: none; + } + .at-tooltip-item:disabled { + cursor: not-allowed; + opacity: 0.5; + } + .at-tooltip-item img { + flex: 0 0 22px; + width: 22px; + height: 22px; + border-radius: 4px; + object-fit: contain; + image-rendering: pixelated; + background: #0c1730; + } + .at-tooltip-item.has-submenu { + padding-right: 6px; + } + .at-tooltip-item.at-tooltip-icon-item { + width: 34px; + height: 34px; + min-height: 34px; + padding: 0; + justify-content: center; + border-radius: 10px; + overflow: hidden; + } + .at-tooltip-item.at-tooltip-icon-item.has-submenu { + padding-right: 0; + } + .at-tooltip-panel.at-tooltip-icon-stack-panel, + .at-tooltip-panel.at-tooltip-icon-row-panel, + .at-tooltip-panel.at-tooltip-icon-grid-panel { + min-width: 0; + width: max-content; + max-width: none; + grid-auto-rows: 34px; + gap: 6px; + padding: 6px; + } + .at-tooltip-panel.at-tooltip-icon-stack-panel { + grid-template-columns: 34px; + } + .at-tooltip-panel.at-tooltip-icon-row-panel { + grid-auto-flow: column; + grid-auto-columns: 34px; + grid-template-columns: none; + } + .at-tooltip-panel.at-tooltip-icon-grid-panel { + grid-template-columns: repeat(3, 34px); + } + .at-tooltip-panel.at-tooltip-icon-grid-panel.at-tooltip-icon-grid-panel-wide { + grid-template-columns: repeat(4, 34px); + } + .at-tooltip-panel.at-tooltip-icon-stack-panel:empty::after, + .at-tooltip-panel.at-tooltip-icon-row-panel:empty::after, + .at-tooltip-panel.at-tooltip-icon-grid-panel:empty::after { + display: none; + } + .at-tooltip-panel.at-tooltip-icon-stack-panel .at-tooltip-label, + .at-tooltip-panel.at-tooltip-icon-row-panel .at-tooltip-label, + .at-tooltip-panel.at-tooltip-icon-grid-panel .at-tooltip-label, + .at-tooltip-item.at-tooltip-icon-item .at-tooltip-submenu-arrow { + display: none; + } + .at-tooltip-submenu-arrow { + margin-left: auto; + color: #9fb8e5; + font-size: 14px; + line-height: 1; + opacity: 0.92; + } + .tile-art-menu-shape-icon, + .tile-art-menu-line-icon, + .tile-art-menu-transform-icon { + position: relative; + display: inline-flex; + align-items: center; + justify-content: center; + width: 18px; + height: 18px; + flex: 0 0 18px; + } + .tile-art-menu-shape-outline, + .tile-art-menu-shape-fill, + .tile-art-menu-line-stroke, + .tile-art-menu-transform-part { + position: absolute; + box-sizing: border-box; + pointer-events: none; + } + .tile-art-menu-shape-icon.is-rectangle .tile-art-menu-shape-outline { + inset: 3px; + border: 2px solid #7ee8c6; + border-radius: 3px; + background: transparent; + } + .tile-art-menu-shape-icon.is-rectangle .tile-art-menu-shape-fill { + inset: 6px; + border-radius: 2px; + background: #ffd166; + } + .tile-art-menu-shape-icon.is-circle .tile-art-menu-shape-outline { + inset: 3px; + border: 2px solid #7ee8c6; + border-radius: 999px; + background: transparent; + } + .tile-art-menu-shape-icon.is-circle .tile-art-menu-shape-fill { + inset: 6px; + border-radius: 999px; + background: #ffd166; + } + .tile-art-menu-shape-icon.is-triangle .tile-art-menu-shape-outline { + inset: 1px; + clip-path: polygon(50% 12%, 16% 84%, 84% 84%); + background: #7ee8c6; + } + .tile-art-menu-shape-icon.is-triangle .tile-art-menu-shape-fill { + inset: 1px; + clip-path: polygon(50% 27%, 31% 72%, 69% 72%); + background: #ffd166; + } + .tile-art-menu-shape-icon.is-outline .tile-art-menu-shape-fill { + display: none; + } + .tile-art-menu-shape-icon.is-fill .tile-art-menu-shape-outline { + display: none; + } + .tile-art-menu-shape-icon.is-fill.is-draw .tile-art-menu-shape-fill, + .tile-art-menu-shape-icon.is-two-tone .tile-art-menu-shape-fill { + background: #ffd166; + } + .tile-art-menu-shape-icon.is-fill.is-erase .tile-art-menu-shape-fill, + .tile-art-menu-shape-icon.is-two-tone.is-erase .tile-art-menu-shape-fill { + background: #ff8aa6; + } + .tile-art-menu-shape-icon.is-outline.is-erase.is-rectangle .tile-art-menu-shape-outline, + .tile-art-menu-shape-icon.is-outline.is-erase.is-circle .tile-art-menu-shape-outline { + border-color: #ff8aa6; + } + .tile-art-menu-shape-icon.is-outline.is-erase.is-triangle .tile-art-menu-shape-outline { + background: #ff8aa6; + } + .tile-art-menu-shape-icon.is-fill.is-erase .tile-art-menu-shape-fill { + background: #ff8aa6; + } + .tile-art-menu-line-stroke { + left: 1px; + top: 8px; + width: 16px; + height: 0; + border-top: 3px solid #ffd166; + transform: rotate(-34deg); + transform-origin: center; + border-radius: 999px; + } + .tile-art-menu-line-icon.is-erase .tile-art-menu-line-stroke { + border-top-color: #ff8aa6; + } + .tile-art-menu-transform-icon .tile-art-menu-transform-part { + background: transparent; + } + .tile-art-menu-transform-icon.is-rotate .part-a, + .tile-art-menu-transform-icon.is-rotate-cw .part-a, + .tile-art-menu-transform-icon.is-rotate-ccw .part-a { + left: 2px; + top: 2px; + width: 2px; + height: 14px; + border-radius: 999px; + background: #36e07a; + } + .tile-art-menu-transform-icon.is-rotate .part-b, + .tile-art-menu-transform-icon.is-rotate-cw .part-b, + .tile-art-menu-transform-icon.is-rotate-ccw .part-b { + left: 2px; + bottom: 2px; + width: 14px; + height: 2px; + border-radius: 999px; + background: #ff4b57; + } + .tile-art-menu-transform-icon.is-rotate-ccw .part-a { + background: #ff4b57; + } + .tile-art-menu-transform-icon.is-rotate-ccw .part-b { + background: #36e07a; + } + .tile-art-menu-transform-icon.is-flip .part-a, + .tile-art-menu-transform-icon.is-flip-h .part-a, + .tile-art-menu-transform-icon.is-flip-h .part-b { + top: 3px; + width: 7px; + height: 12px; + } + .tile-art-menu-transform-icon.is-flip .part-a, + .tile-art-menu-transform-icon.is-flip-h .part-a { + left: 1px; + clip-path: polygon(100% 0, 0 50%, 100% 100%); + background: #ff4b57; + } + .tile-art-menu-transform-icon.is-flip .part-b, + .tile-art-menu-transform-icon.is-flip-h .part-b { + right: 1px; + clip-path: polygon(0 0, 100% 50%, 0 100%); + background: #36e07a; + } + .tile-art-menu-transform-icon.is-flip-v .part-a, + .tile-art-menu-transform-icon.is-flip-v .part-b { + left: 2px; + width: 14px; + height: 7px; + } + .tile-art-menu-transform-icon.is-flip-v .part-a { + top: 2px; + clip-path: polygon(50% 0, 100% 100%, 0 100%); + background: #ff4b57; + } + .tile-art-menu-transform-icon.is-flip-v .part-b { + bottom: 2px; + clip-path: polygon(0 0, 100% 0, 50% 100%); + background: #36e07a; + } + .at-tooltip-separator { + height: 1px; + background: #2a426a; + margin: 2px 0; + } + + .legend { + display: flex; + flex-wrap: wrap; + gap: 6px; + font-size: 11px; + color: #b6caed; + } + .legend-item { + display: inline-flex; + align-items: center; + gap: 4px; + border: 1px solid #2d426e; + border-radius: 6px; + padding: 3px 6px; + background: #132447; + } + .swatch { + width: 11px; + height: 11px; + border-radius: 2px; + border: 1px solid rgba(255,255,255,0.2); + } + + +`; + +export const WORLDSHAPER_STUDIO_STYLE_STAGE = ` + .stage { + min-width: 0; + min-height: 0; + display: flex; + flex-direction: column; + background: #060a14; + position: relative; + } + .meta { + height: 28px; + display: flex; + align-items: center; + gap: 10px; + padding: 0 10px; + border-bottom: 1px solid #2a3d63; + font-size: 12px; + color: #a8bfeb; + flex-shrink: 0; + } + .meta-main { + min-width: 0; + flex: 1 1 auto; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + .meta-stats { + margin-left: auto; + color: #d7e7ff; + font-variant-numeric: tabular-nums; + white-space: nowrap; + } + .viewport { + position: relative; + min-height: 0; + flex: 1; + overflow: auto; + scrollbar-width: none; + -ms-overflow-style: none; + cursor: crosshair; + user-select: none; + background: var(--editor-stage-bg, #060a14); + } + .viewport::-webkit-scrollbar { + width: 0; + height: 0; + display: none; + } + .canvas-tool-btn { + position: absolute; + top: 38px; + left: 10px; + z-index: 4; + width: 42px; + height: 42px; + display: inline-flex; + align-items: center; + justify-content: center; + border: 1px solid #35507f; + border-radius: 10px; + background: rgba(10, 20, 37, 0.92); + color: #d9ebff; + box-shadow: 0 8px 20px rgba(3, 8, 18, 0.28); + cursor: pointer; + transition: background 120ms ease, border-color 120ms ease, transform 120ms ease; + } + .canvas-tool-btn:hover { + background: #17315b; + border-color: #4f78b5; + transform: translateY(-1px); + } + .canvas-tool-btn.active { + border-color: #5fc3ff; + background: #173d57; + color: #f4fbff; + } + .canvas-tool-btn:focus-visible { + outline: 2px solid rgba(95,195,255,0.75); + outline-offset: 2px; + } + .canvas-tool-btn-icon { + position: relative; + width: 20px; + height: 20px; + display: block; + } + .canvas-tool-btn-icon::before { + content: ""; + position: absolute; + left: 1px; + top: 1px; + width: 12px; + height: 12px; + border: 2px dashed currentColor; + border-radius: 2px; + box-sizing: border-box; + } + .canvas-tool-btn-icon::after { + content: ""; + position: absolute; + right: 0; + bottom: 0; + width: 0; + height: 0; + border-left: 7px solid currentColor; + border-top: 11px solid transparent; + transform: rotate(-18deg); + transform-origin: center; + } + .viewport-layer { + position: sticky; + top: 0; + left: 0; + width: 100%; + height: 0; + overflow: visible; + z-index: 1; + pointer-events: none; + } + .pixi-host { + position: absolute; + inset: 0; + pointer-events: none; + overflow: hidden; + } + .pixi-stage-canvas { + display: block; + width: 100%; + height: 100%; + pointer-events: none; + image-rendering: pixelated; + } + .viewport-layer canvas { + position: absolute; + inset: 0; + display: block; + width: 100%; + height: 100%; + pointer-events: auto; + } + .viewport-spacer { + position: relative; + z-index: 0; + pointer-events: none; + } + +`; diff --git a/src/worldshaperStudio/domStyles.ts b/src/worldshaperStudio/domStyles.ts new file mode 100644 index 0000000..716a10b --- /dev/null +++ b/src/worldshaperStudio/domStyles.ts @@ -0,0 +1,15 @@ +import { buildWorldshaperStudioThemeOverrideCss } from "./themePresets"; +import { + WORLDSHAPER_STUDIO_STYLE_SHELL, + WORLDSHAPER_STUDIO_STYLE_SIDEBAR, + WORLDSHAPER_STUDIO_STYLE_STAGE, +} from "./domStyleSections"; + +export function buildWorldshaperStudioStyles(): string { + return ( + WORLDSHAPER_STUDIO_STYLE_SHELL + + WORLDSHAPER_STUDIO_STYLE_SIDEBAR + + WORLDSHAPER_STUDIO_STYLE_STAGE + + buildWorldshaperStudioThemeOverrideCss() + ); +} diff --git a/src/worldshaperStudio/runtime.ts b/src/worldshaperStudio/runtime.ts index 3c6c8d0..74c6eb7 100644 --- a/src/worldshaperStudio/runtime.ts +++ b/src/worldshaperStudio/runtime.ts @@ -1,4 +1,4 @@ -/* eslint-disable @typescript-eslint/ban-ts-comment, @typescript-eslint/no-unused-vars, no-empty, no-useless-escape */ +/* eslint-disable @typescript-eslint/ban-ts-comment, @typescript-eslint/no-unused-vars, no-empty, no-useless-escape */ // @ts-nocheck import { buildSpritePreviewDataUrl, @@ -10,12 +10,7 @@ import { normalizeImageRecordForSave, normalizeTileRecordForSave, } from "../editorCore"; -import { - buildSpriteCatalog, - buildTileCatalogById, - DEFAULT_MAP_BACKGROUND_COLOR, - DEFAULT_TILE_COLOR, -} from "../components/worldshaperShared"; +import { DEFAULT_TILE_COLOR } from "../components/worldshaperShared"; import type { WorldshaperStudioBootstrap } from "./bootstrap"; import { cacheStandaloneWorldshaperBootstrap, @@ -31,13 +26,9 @@ import { openWorldshaperHeightViewerWindow, persistWorldshaperStudioBounds, } from "./windowing"; -import { createHistoryController } from "./historyController"; import { createHistoryStateStore } from "./historyStateStore"; -import { createInteractionController } from "./interactionController"; -import { createImportController } from "./importController"; import { createMapDocumentController } from "./mapDocumentController"; import { createMapDocumentStore } from "./mapDocumentStore"; -import { createNpcController } from "./npcController"; import { createPanelFolderLayoutFolder, deletePanelFolderLayoutFolder, @@ -46,22 +37,11 @@ import { togglePanelFolderLayoutFolder, } from "./panelFolders"; import { createEditorUiStore } from "./editorUiStore"; -import { createChangelogSplashWindowController } from "./changelogSplashWindowController"; -import { createEntityEditorWindowController } from "./entityEditorWindowController"; -import { createEngineOverrideWindowController } from "./engineOverrideWindowController"; import { getEngineOverrideValue, normalizeEngineOverrideEntries, } from "./engineOverrides"; -import { createPersistenceController } from "./persistenceController"; import { createPopupSessionStore } from "./popupSessionStore"; -import { createRenderController } from "./renderController"; -import { createSidebarController } from "./sidebarController"; -import { createStatusLogWindowController } from "./statusLogWindowController"; -import { createTileArtEditorWindowController } from "./tileArtEditorWindowController"; -import { createToolWindowController } from "./toolWindowController"; -import { createWorldOverviewWindowController } from "./worldOverviewWindowController"; -import { createDebouncedCallback } from "./debounce"; import { buildImageRecordFromSpriteRecord, buildImageRecordFromTileRecord, @@ -80,215 +60,42 @@ import { persistEditorSettings, } from "./themePresets"; import { createAtTooltip } from "./tooltip"; - -function cloneValue(value) { - if (typeof structuredClone === "function") { - return structuredClone(value); - } - return value == null ? value : JSON.parse(JSON.stringify(value)); -} - -function createFilledRows(width, height, fillChar) { - return Array.from({ length: Math.max(1, Number(height) || 1) }, () => String(fillChar || " ").repeat(Math.max(1, Number(width) || 1))); -} - -function writeRowSegment(rows, y, x, segment) { - if (!Array.isArray(rows) || !segment) { - return; - } - const targetY = Math.floor(Number(y) || 0); - if (targetY < 0 || targetY >= rows.length) { - return; - } - const safeX = Math.max(0, Math.floor(Number(x) || 0)); - const sourceRow = String(rows[targetY] || ""); - const paddedRow = sourceRow.length >= safeX - ? sourceRow - : (sourceRow + " ".repeat(Math.max(0, safeX - sourceRow.length))); - const before = paddedRow.slice(0, safeX); - const afterStart = safeX + segment.length; - const after = afterStart < paddedRow.length ? paddedRow.slice(afterStart) : ""; - rows[targetY] = before + segment + after; -} - -function composeWorldRoomLayers(chunks, chunkWidth, chunkHeight, originChunkX, originChunkY, worldWidth, worldHeight) { - const layerMap = new Map(); - (Array.isArray(chunks) ? chunks : []).forEach((chunk) => { - const baseChunkX = Math.floor(Number(chunk?.chunkX) || 0); - const baseChunkY = Math.floor(Number(chunk?.chunkY) || 0); - const offsetX = (baseChunkX - originChunkX) * chunkWidth; - const offsetY = (baseChunkY - originChunkY) * chunkHeight; - const rawLayers = Array.isArray(chunk?.roomLayers) ? chunk.roomLayers : []; - rawLayers.forEach((rawLayer) => { - const layerNumber = Number(rawLayer?.layer) || 0; - const fillChar = layerNumber === 0 ? "." : " "; - if (!layerMap.has(layerNumber)) { - layerMap.set(layerNumber, { - layer: layerNumber, - name: typeof rawLayer?.name === "string" && String(rawLayer.name).trim() ? String(rawLayer.name).trim() : undefined, - rows: createFilledRows(worldWidth, worldHeight, fillChar), - instanceIds: [], - }); - } - const targetLayer = layerMap.get(layerNumber); - const sourceRows = Array.isArray(rawLayer?.rows) ? rawLayer.rows.map((row) => String(row || "")) : []; - sourceRows.forEach((row, localY) => { - const targetY = offsetY + localY; - if (targetY < 0 || targetY >= targetLayer.rows.length) { - return; - } - const maxWidth = Math.max(0, worldWidth - offsetX); - writeRowSegment(targetLayer.rows, targetY, offsetX, row.slice(0, maxWidth)); - }); - const sourceInstanceIds = Array.isArray(rawLayer?.instanceIds) - ? rawLayer.instanceIds.map((entry) => String(entry || "").trim()).filter(Boolean) - : []; - targetLayer.instanceIds = Array.from(new Set([...(targetLayer.instanceIds || []), ...sourceInstanceIds])); - }); - }); - if (!layerMap.has(0)) { - layerMap.set(0, { - layer: 0, - rows: createFilledRows(worldWidth, worldHeight, "."), - instanceIds: [], - }); - } - return Array.from(layerMap.values()).sort((a, b) => (Number(a.layer) || 0) - (Number(b.layer) || 0)); -} - -function composeWorldHeightLayers(chunks, chunkWidth, chunkHeight, originChunkX, originChunkY) { - const patches = []; - (Array.isArray(chunks) ? chunks : []).forEach((chunk) => { - const baseChunkX = Math.floor(Number(chunk?.chunkX) || 0); - const baseChunkY = Math.floor(Number(chunk?.chunkY) || 0); - const offsetX = (baseChunkX - originChunkX) * chunkWidth; - const offsetY = (baseChunkY - originChunkY) * chunkHeight; - const rawHeightLayers = Array.isArray(chunk?.heightLayers) ? chunk.heightLayers : []; - rawHeightLayers.forEach((entry, index) => { - const fallbackId = `height_${baseChunkX}_${baseChunkY}_${index + 1}`; - patches.push({ - id: String(entry?.id || fallbackId).trim() || fallbackId, - name: typeof entry?.name === "string" && String(entry.name).trim() ? String(entry.name).trim() : undefined, - z: Math.max(1, Math.floor(Number(entry?.z) || 1)), - x: offsetX + Math.max(0, Number(entry?.x) || 0), - y: offsetY + Math.max(0, Number(entry?.y) || 0), - rows: Array.isArray(entry?.rows) ? entry.rows.map((row) => String(row || "")) : [], - }); - }); - }); - return patches.sort((a, b) => { - if (a.z !== b.z) { - return a.z - b.z; - } - return String(a.name || a.id).localeCompare(String(b.name || b.id)); - }); -} - -function buildWorldLayerMetadata(chunks) { - const layerMap = new Map(); - (Array.isArray(chunks) ? chunks : []).forEach((chunk) => { - const rawLayers = Array.isArray(chunk?.roomLayers) ? chunk.roomLayers : []; - rawLayers.forEach((rawLayer) => { - const layerNumber = Number(rawLayer?.layer) || 0; - if (layerMap.has(layerNumber)) { - return; - } - layerMap.set(layerNumber, { - layer: layerNumber, - name: typeof rawLayer?.name === "string" && String(rawLayer.name).trim() ? String(rawLayer.name).trim() : undefined, - rows: [], - instanceIds: Array.isArray(rawLayer?.instanceIds) ? rawLayer.instanceIds.map((entry) => String(entry || "").trim()).filter(Boolean) : [], - }); - }); - }); - if (!layerMap.has(0)) { - layerMap.set(0, { - layer: 0, - rows: [], - instanceIds: [], - }); - } - if (!Array.from(layerMap.keys()).some((layerNumber) => layerNumber > 0)) { - layerMap.set(1, { - layer: 1, - rows: [], - instanceIds: [], - }); - } - return Array.from(layerMap.values()).sort((a, b) => (Number(a.layer) || 0) - (Number(b.layer) || 0)); -} - -function getContentRecords(payload, key) { - const records = payload && Array.isArray(payload[key]) ? payload[key] : []; - return records.filter((entry) => Boolean(entry) && typeof entry === "object" && !Array.isArray(entry)); -} - -function buildSpriteCatalogFromBootstrap(bootstrap) { - const spriteRecords = getContentRecords(bootstrap?.contentByType?.sprites, "sprites"); - if (spriteRecords.length > 0) { - return buildSpriteCatalog(spriteRecords, buildSpritePreviewDataUrl); - } - return cloneValue(bootstrap?.spriteCatalog) || {}; -} - -function buildTileCatalogByIdFromBootstrap(bootstrap) { - const tileRecords = getContentRecords(bootstrap?.contentByType?.tiles, "tiles"); - if (tileRecords.length > 0) { - return buildTileCatalogById(tileRecords, buildSpritePreviewDataUrl); - } - return cloneValue(bootstrap?.tileCatalogById) || {}; -} - -function buildNpcOverlaysFromWorldChunks(chunks, spriteCatalog, chunkWidth, chunkHeight, originChunkX, originChunkY) { - return (Array.isArray(chunks) ? chunks : []).flatMap((chunk) => { - const baseChunkX = Math.floor(Number(chunk?.chunkX) || 0); - const baseChunkY = Math.floor(Number(chunk?.chunkY) || 0); - const offsetX = (baseChunkX - originChunkX) * chunkWidth; - const offsetY = (baseChunkY - originChunkY) * chunkHeight; - const instances = Array.isArray(chunk?.instances) ? chunk.instances : []; - return instances - .filter((entry) => Boolean(entry) && typeof entry === "object" && !Array.isArray(entry)) - .map((entry) => { - const record = entry.record && typeof entry.record === "object" && !Array.isArray(entry.record) - ? cloneValue(entry.record) - : {}; - const spriteId = String(record.spriteId || entry.spriteId || "").trim(); - const spriteEntry = spriteCatalog[spriteId] || null; - const overlayX = offsetX + Math.max(0, Number(entry.x) || 0); - const overlayY = offsetY + Math.max(0, Number(entry.y) || 0); - record.position = { - x: overlayX, - y: overlayY, - }; - return { - id: String(entry.id || "").trim(), - layer: Number(entry.layer) || 0, - name: String(record.name || entry.id || "NPC"), - spriteId, - x: overlayX, - y: overlayY, - dataUrl: spriteEntry ? spriteEntry.dataUrl : null, - spriteWidth: spriteEntry ? spriteEntry.spriteWidth : 28, - spriteHeight: spriteEntry ? spriteEntry.spriteHeight : 28, - opacity: spriteEntry ? spriteEntry.opacity : 1, - record, - }; - }) - .filter((entry) => entry.id); - }); -} - -const MAX_WORLD_CHUNK_CACHE_ENTRIES = 256; -const MAX_DYNAMIC_WORLD_CHUNK_RADIUS = 4; -const TILE_SYMBOL_POOL = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz!$%&()*+,-/:;<=>?@[]^_{|}~="; +import { initializeRuntimeControllers } from "./runtimeControllerBootstrap"; +import { + buildNpcOverlaysFromWorldChunks, + buildSpriteCatalogFromBootstrap, + buildTileCatalogByIdFromBootstrap, + cloneRuntimeValue, + createInitialWorldRuntimeState, + MAX_DYNAMIC_WORLD_CHUNK_RADIUS, + MAX_WORLD_CHUNK_CACHE_ENTRIES, + normalizeMapBackgroundColor, + TILE_SYMBOL_POOL, +} from "./runtimeBootstrapHelpers"; +import { createRuntimeLogging } from "./runtimeLogging"; +import { + buildChunkHeightLayersFromDocument as buildChunkHeightLayersFromDocumentHelper, + buildChunkInstancesFromDocument as buildChunkInstancesFromDocumentHelper, + buildWorldChunkLayerInstanceIds, + buildWorldLayerMetadata, + cloneWorldChunkHeightLayers, + composeWorldHeightLayers, + composeWorldRoomLayers, + createEmptyWorldChunkPayload as createEmptyWorldChunkPayloadHelper, + createFilledRows, + isChunkFillSymbol, + isWorldChunkPayloadEmpty as isWorldChunkPayloadEmptyHelper, + normalizeCachedWorldChunkPayload as normalizeCachedWorldChunkPayloadHelper, + normalizeWorldChunkInstances as normalizeWorldChunkInstancesHelper, + normalizeWorldChunkRows, + sliceNormalizedRows, + transformChunkHeightPatch, + transformChunkLocalCoord, + transformChunkRows, + transformWorldChunkPayload as transformWorldChunkPayloadHelper, +} from "./worldChunkRuntimeHelpers"; export function startWorldshaperStudio(bootstrap: WorldshaperStudioBootstrap, initialEditorSettings: unknown = getDefaultEditorSettings()): void { - function normalizeMapBackgroundColor(value, fallback) { - const f = fallback || DEFAULT_MAP_BACKGROUND_COLOR; - const raw = String(value || "").trim(); - return /^#[0-9a-fA-F]{6}$/.test(raw) ? raw.toUpperCase() : f; - } - const baseTileSize = Math.max(8, Number(bootstrap.tileSize) || 32); const minZoomLevel = 0.5; const maxZoomLevel = 4; @@ -296,51 +103,12 @@ export function startWorldshaperStudio(bootstrap: WorldshaperStudioBootstrap, in let tileSize = baseTileSize; let currentMapId = String(bootstrap.mapId || "").trim(); let currentBaseRows = Array.isArray(bootstrap.baseRows) ? bootstrap.baseRows.map((row) => String(row ?? "")) : []; - const worldRuntimeState = { - enabled: !!String(bootstrap.worldId || bootstrap.mapId || "").trim(), - worldId: String(bootstrap.worldId || bootstrap.mapId || "").trim(), - worldName: String(bootstrap.worldName || bootstrap.mapName || bootstrap.worldId || bootstrap.mapId || "World").trim() || "World", - defaultBackgroundTileId: String(bootstrap.backgroundTileId || "").trim(), - heightBlurStep: Math.max(0, Math.min(1, Number(bootstrap.heightBlurStep ?? bootstrap.heightDetailStep) || 0.1)), - chunkWidth: Math.max(1, Number(bootstrap.worldChunkWidth) || 32), - chunkHeight: Math.max(1, Number(bootstrap.worldChunkHeight) || 32), - chunkRadius: Math.max(0, Math.floor(Number(bootstrap.worldChunkRadius) || 0)), - originChunkX: Math.floor(Number(bootstrap.worldOriginChunkX) || 0), - originChunkY: Math.floor(Number(bootstrap.worldOriginChunkY) || 0), - tileOffsetX: Math.floor(Number(bootstrap.worldTileOffsetX) || 0), - tileOffsetY: Math.floor(Number(bootstrap.worldTileOffsetY) || 0), - spawnX: Math.floor(Number(bootstrap.worldSpawnX) || 0), - spawnY: Math.floor(Number(bootstrap.worldSpawnY) || 0), - centerChunkX: Math.floor(Number(bootstrap.worldOriginChunkX) || 0) + Math.max(0, Math.floor(Number(bootstrap.worldChunkRadius) || 0)), - centerChunkY: Math.floor(Number(bootstrap.worldOriginChunkY) || 0) + Math.max(0, Math.floor(Number(bootstrap.worldChunkRadius) || 0)), - sourceChunks: Array.isArray(bootstrap.sourceChunks) - ? bootstrap.sourceChunks.map((entry) => ({ - chunkX: Math.floor(Number(entry?.chunkX) || 0), - chunkY: Math.floor(Number(entry?.chunkY) || 0), - })) - : [], - bookmarks: Array.isArray(bootstrap.worldBookmarks) - ? bootstrap.worldBookmarks.map((entry, index) => ({ - id: String(entry?.id || `poi_${index + 1}`).trim() || `poi_${index + 1}`, - label: String(entry?.label || entry?.id || `POI ${index + 1}`).trim() || `POI ${index + 1}`, - x: Math.floor(Number(entry?.x) || 0), - y: Math.floor(Number(entry?.y) || 0), - })) - : [], - chunkCache: new Map(), - dirtyChunkKeys: new Set(), - pendingNeighborhoodFetches: new Map(), - prefetchedNeighborhoodKeys: new Set(), - pendingLoadKey: "", - pendingLoadPromise: null, - requestSerial: 0, - documentDirty: false, - }; + const worldRuntimeState = createInitialWorldRuntimeState(bootstrap); function isWorldModeActive() { return worldRuntimeState.enabled && !!worldRuntimeState.worldId; } const defaultTileColor = DEFAULT_TILE_COLOR; - const tileColors = cloneValue(bootstrap.tileColors) || {}; + const tileColors = cloneRuntimeValue(bootstrap.tileColors) || {}; let graphicsVisualRevision = 0; function applyGraphicsVisualRevision(dataUrl, revision = graphicsVisualRevision) { const normalizedDataUrl = String(dataUrl || "").trim(); @@ -511,9 +279,9 @@ export function startWorldshaperStudio(bootstrap: WorldshaperStudioBootstrap, in return merged; } const tileCatalog = buildMergedTileCatalog(); - const contentByType = cloneValue(bootstrap.contentByType) || {}; + const contentByType = cloneRuntimeValue(bootstrap.contentByType) || {}; const spriteCatalog = applySpriteCatalogVisualRevision(buildSpriteCatalogFromBootstrap(bootstrap)); - const defaultNpcTemplate = cloneValue(bootstrap.defaultNpcTemplate) || {}; + const defaultNpcTemplate = cloneRuntimeValue(bootstrap.defaultNpcTemplate) || {}; const apiBase = String(bootstrap.apiBase || "").replace(/\/+$/, ""); function deriveHistoryStorageKey(mapIdValue) { return "worldshaper:world-history:v2:" + String(mapIdValue || "").trim(); @@ -848,7 +616,7 @@ export function startWorldshaperStudio(bootstrap: WorldshaperStudioBootstrap, in : normalizedBackgroundTileId; const existingChunk = worldRuntimeState.chunkCache.get(chunkKey); const chunkValue = existingChunk - ? cloneValue(existingChunk) + ? cloneRuntimeValue(existingChunk) : (rebuildWorldChunkPayloadFromDocument(safeChunkX, safeChunkY) || { worldId: String(worldRuntimeState.worldId || currentMapId || "").trim(), chunkX: safeChunkX, @@ -942,7 +710,7 @@ export function startWorldshaperStudio(bootstrap: WorldshaperStudioBootstrap, in return; } touchWorldChunkCacheEntry(String(chunkKey || "").trim(), { - ...cloneValue(existingChunk), + ...cloneRuntimeValue(existingChunk), backgroundTileId: nextBackgroundTileId, }); changed = true; @@ -1081,7 +849,7 @@ export function startWorldshaperStudio(bootstrap: WorldshaperStudioBootstrap, in return Array.from(worldRuntimeState.dirtyChunkKeys.values()) .map((chunkKey) => worldRuntimeState.chunkCache.get(chunkKey) || null) .filter(Boolean) - .map((entry) => cloneValue(entry)); + .map((entry) => cloneRuntimeValue(entry)); } function pruneWorldChunkCache() { @@ -1131,102 +899,28 @@ export function startWorldshaperStudio(bootstrap: WorldshaperStudioBootstrap, in ); } - function sliceNormalizedRows(rows, startX, startY, width, height, fillChar) { - return Array.from({ length: Math.max(1, Number(height) || 1) }, (_, rowOffset) => { - const sourceRow = String((Array.isArray(rows) ? rows[startY + rowOffset] : "") || ""); - const paddedRow = sourceRow.length >= startX + width - ? sourceRow - : sourceRow + String(fillChar || " ").repeat(Math.max(0, (startX + width) - sourceRow.length)); - return paddedRow.slice(startX, startX + width); - }); - } - function buildChunkHeightLayersFromDocument(baseTileX, baseTileY, chunkWidth, chunkHeight) { - return (Array.isArray(mapDocument.heightLayers) ? cloneHeightLayers(mapDocument.heightLayers) : []) - .map((entry) => { - const patchX = Math.max(0, Number(entry?.x) || 0); - const patchY = Math.max(0, Number(entry?.y) || 0); - const rows = Array.isArray(entry?.rows) ? entry.rows.map((row) => String(row || "")) : []; - const patchWidth = rows.reduce((max, row) => Math.max(max, row.length), 0); - const patchHeight = rows.length; - const patchRight = patchX + patchWidth; - const patchBottom = patchY + patchHeight; - const chunkRight = baseTileX + chunkWidth; - const chunkBottom = baseTileY + chunkHeight; - const overlapLeft = Math.max(baseTileX, patchX); - const overlapTop = Math.max(baseTileY, patchY); - const overlapRight = Math.min(chunkRight, patchRight); - const overlapBottom = Math.min(chunkBottom, patchBottom); - if (overlapRight <= overlapLeft || overlapBottom <= overlapTop) { - return null; - } - const localRows = []; - for (let y = overlapTop; y < overlapBottom; y += 1) { - const sourceRow = String(rows[y - patchY] || ""); - localRows.push(sourceRow.slice(overlapLeft - patchX, overlapRight - patchX).replace(/\s+$/g, "")); - } - return { - id: String(entry?.id || "").trim(), - name: typeof entry?.name === "string" && entry.name.trim() ? entry.name.trim() : undefined, - z: Math.max(1, Number(entry?.z) || 1), - x: overlapLeft - baseTileX, - y: overlapTop - baseTileY, - rows: localRows, - }; - }) - .filter((entry) => entry && entry.id); + return buildChunkHeightLayersFromDocumentHelper({ + mapDocument, + cloneHeightLayers, + baseTileX, + baseTileY, + chunkWidth, + chunkHeight, + }); } function buildChunkInstancesFromDocument(baseTileX, baseTileY, chunkWidth, chunkHeight) { - const chunkInstances = cloneValue(mapDocument.npcOverlays) - .filter((npc) => { - const localX = Math.floor(Number(npc?.x)); - const localY = Math.floor(Number(npc?.y)); - return Number.isFinite(localX) - && Number.isFinite(localY) - && localX >= baseTileX - && localX < baseTileX + chunkWidth - && localY >= baseTileY - && localY < baseTileY + chunkHeight; - }) - .map((npc) => ({ - id: String(npc.id || "").trim(), - templateId: String(npc?.record?.templateId || "").trim(), - layer: Number(npc.layer) || 0, - x: Math.floor(Number(npc.x) || 0) - baseTileX, - y: Math.floor(Number(npc.y) || 0) - baseTileY, - record: { - ...cloneValue(npc.record || {}), - id: String(npc.id || "").trim(), - layer: Number(npc.layer) || 0, - templateId: String(npc?.record?.templateId || "").trim(), - name: String(npc.name || npc?.record?.name || ""), - entityType: String(npc?.record?.entityType || npc?.entityType || "friendly"), - faction: String(npc.faction || npc?.record?.faction || ""), - spriteId: String(npc.spriteId || npc?.record?.spriteId || ""), - dialogueId: String(npc.dialogueId || npc?.record?.dialogueId || ""), - description: String(npc.description || npc?.record?.description || ""), - tags: cloneValue(npc?.record?.tags) || [], - enabled: typeof npc?.record?.enabled === "boolean" ? npc.record.enabled : true, - position: { - x: Math.floor(Number(npc.x) || 0) + worldRuntimeState.tileOffsetX, - y: Math.floor(Number(npc.y) || 0) + worldRuntimeState.tileOffsetY, - }, - }, - })) - .filter((entry) => entry.id); - const npcIdsByLayer = new Map(); - chunkInstances.forEach((entry) => { - const layerNumber = Number(entry.layer) || 0; - if (!npcIdsByLayer.has(layerNumber)) { - npcIdsByLayer.set(layerNumber, []); - } - npcIdsByLayer.get(layerNumber).push(entry.id); + return buildChunkInstancesFromDocumentHelper({ + mapDocument, + cloneValue, + baseTileX, + baseTileY, + chunkWidth, + chunkHeight, + tileOffsetX: worldRuntimeState.tileOffsetX, + tileOffsetY: worldRuntimeState.tileOffsetY, }); - return { - chunkInstances, - npcIdsByLayer, - }; } function rebuildWorldChunkPayloadFromDocument(chunkX, chunkY) { @@ -1366,7 +1060,7 @@ export function startWorldshaperStudio(bootstrap: WorldshaperStudioBootstrap, in ? fallbackLayerNumber : (layerNumberMap ? (layerNumberMap[String(previousLayer)] ?? previousLayer) : previousLayer); return { - ...cloneValue(entry), + ...cloneRuntimeValue(entry), layer: nextLayer, }; }); @@ -1390,7 +1084,7 @@ export function startWorldshaperStudio(bootstrap: WorldshaperStudioBootstrap, in const metadata = metadataByLayer.get(nextLayer) || { layer: nextLayer, name: undefined }; const fillChar = nextLayer === 0 ? "." : " "; return { - ...cloneValue(entry), + ...cloneRuntimeValue(entry), layer: nextLayer, name: metadata.name, rows: Array.isArray(entry?.rows) @@ -1413,7 +1107,7 @@ export function startWorldshaperStudio(bootstrap: WorldshaperStudioBootstrap, in continue; } touchWorldChunkCacheEntry(chunkKey, { - ...cloneValue(chunkValue), + ...cloneRuntimeValue(chunkValue), roomLayers: nextRoomLayers, instances: nextInstances, }); @@ -1512,59 +1206,12 @@ export function startWorldshaperStudio(bootstrap: WorldshaperStudioBootstrap, in activeHeightLayerId: "", }); popupSessionStore.restorePersistedLayout(window); - const editorLogEntries = []; - const EDITOR_LOG_LIMIT = 500; let statusLogWindowController = null; - - function formatEditorLogTimestamp(timestamp) { - try { - return new Date(timestamp).toLocaleString(); - } catch { - return String(timestamp || ""); - } - } - - function appendEditorLogEntry(level, message) { - const normalizedMessage = String(message || "").trim(); - if (!normalizedMessage) { - return null; - } - const timestamp = Date.now(); - const entry = { - id: runtimeUniqueId(), - timestamp, - timestampLabel: formatEditorLogTimestamp(timestamp), - level: String(level || "Information").trim() || "Information", - message: normalizedMessage, - }; - editorLogEntries.push(entry); - while (editorLogEntries.length > EDITOR_LOG_LIMIT) { - editorLogEntries.shift(); - } - statusLogWindowController?.refresh?.(); - return entry; - } - - function getEditorLogEntries() { - return editorLogEntries.slice(); - } - - function clearEditorLogEntries() { - editorLogEntries.splice(0, editorLogEntries.length); - statusLogWindowController?.refresh?.(); - } - - window.addEventListener("error", (event) => { - const message = String(event?.message || event?.error?.message || "Unknown runtime error"); - appendEditorLogEntry("Error", message); - }); - window.addEventListener("unhandledrejection", (event) => { - const reason = event?.reason; - const message = typeof reason === "string" - ? reason - : String(reason?.message || reason || "Unhandled promise rejection"); - appendEditorLogEntry("Error", message); + const runtimeLogging = createRuntimeLogging({ + windowRef: window, + runtimeUniqueId, }); + const { appendEditorLogEntry, getEditorLogEntries, clearEditorLogEntries } = runtimeLogging; let renderController = null; const documentController = createMapDocumentController({ mapId: currentMapId, @@ -1585,7 +1232,7 @@ export function startWorldshaperStudio(bootstrap: WorldshaperStudioBootstrap, in }); let editorSettingsState = normalizeEditorSettings(initialEditorSettings); - // ── AtTooltip: reusable anchored floating context menu ────────────── + // ── AtTooltip: reusable anchored floating context menu ────────────── const atTooltip = createAtTooltip(); const initialEditorUiState = bootstrap.editorUi; @@ -1798,7 +1445,7 @@ export function startWorldshaperStudio(bootstrap: WorldshaperStudioBootstrap, in return null; } return { - ...cloneValue(entry), + ...cloneRuntimeValue(entry), id: metadata.id, name: metadata.name, z: metadata.z, @@ -1806,7 +1453,7 @@ export function startWorldshaperStudio(bootstrap: WorldshaperStudioBootstrap, in }) .filter(Boolean); nextEntries.push([chunkKey, { - ...cloneValue(chunkValue), + ...cloneRuntimeValue(chunkValue), heightLayers: nextHeightLayers, }]); } @@ -1901,349 +1548,65 @@ export function startWorldshaperStudio(bootstrap: WorldshaperStudioBootstrap, in return emptyChunk; } - function normalizeWorldChunkRows(rows, width, height, fillChar) { - const safeWidth = Math.max(1, Math.floor(Number(width) || 1)); - const safeHeight = Math.max(1, Math.floor(Number(height) || 1)); - return Array.from({ length: safeHeight }, (_entry, rowIndex) => { - const sourceRow = String((Array.isArray(rows) ? rows[rowIndex] : "") || ""); - return sourceRow.length >= safeWidth - ? sourceRow.slice(0, safeWidth) - : (sourceRow + String(fillChar || " ").repeat(Math.max(0, safeWidth - sourceRow.length))); - }); - } - - function cloneWorldChunkHeightLayers(source) { - return (Array.isArray(source) ? source : []) - .map((entry, index) => ({ - id: String(entry?.id || `height_patch_${index + 1}`).trim() || `height_patch_${index + 1}`, - name: typeof entry?.name === "string" && entry.name.trim() ? entry.name.trim() : undefined, - z: Math.max(1, Math.floor(Number(entry?.z) || 1)), - x: Math.max(0, Math.floor(Number(entry?.x) || 0)), - y: Math.max(0, Math.floor(Number(entry?.y) || 0)), - rows: Array.isArray(entry?.rows) ? entry.rows.map((row) => String(row || "")) : [], - })) - .filter((entry) => entry.id); - } - - function buildWorldChunkLayerInstanceIds(roomLayers, instances, width, height) { - const safeWidth = Math.max(1, Math.floor(Number(width) || 1)); - const safeHeight = Math.max(1, Math.floor(Number(height) || 1)); - const nextLayers = new Map(); - (Array.isArray(roomLayers) ? roomLayers : []).forEach((layer) => { - const layerNumber = Math.max(0, Math.floor(Number(layer?.layer) || 0)); - nextLayers.set(layerNumber, { - layer: layerNumber, - name: typeof layer?.name === "string" && layer.name.trim() ? layer.name.trim() : undefined, - rows: normalizeWorldChunkRows(layer?.rows, safeWidth, safeHeight, layerNumber === 0 ? "." : " "), - instanceIds: [], - }); - }); - if (!nextLayers.has(0)) { - nextLayers.set(0, { - layer: 0, - rows: normalizeWorldChunkRows([], safeWidth, safeHeight, "."), - instanceIds: [], - }); - } - if (!Array.from(nextLayers.keys()).some((layerNumber) => layerNumber > 0)) { - nextLayers.set(1, { - layer: 1, - rows: normalizeWorldChunkRows([], safeWidth, safeHeight, " "), - instanceIds: [], - }); - } - (Array.isArray(instances) ? instances : []).forEach((entry) => { - const layerNumber = Math.max(0, Math.floor(Number(entry?.layer) || 0)); - const instanceId = String(entry?.id || "").trim(); - if (!instanceId) { - return; - } - if (!nextLayers.has(layerNumber)) { - nextLayers.set(layerNumber, { - layer: layerNumber, - rows: normalizeWorldChunkRows([], safeWidth, safeHeight, layerNumber === 0 ? "." : " "), - instanceIds: [], - }); - } - nextLayers.get(layerNumber).instanceIds.push(instanceId); - }); - return Array.from(nextLayers.values()) - .map((entry) => ({ - ...entry, - instanceIds: Array.from(new Set((Array.isArray(entry.instanceIds) ? entry.instanceIds : []).map((id) => String(id || "").trim()).filter(Boolean))), - })) - .sort((left, right) => (Number(left.layer) || 0) - (Number(right.layer) || 0)); - } - function normalizeWorldChunkInstances(sourceInstances, chunkX, chunkY, width, height, options) { - const config = options && typeof options === "object" ? options : {}; - const duplicateIds = config.duplicateIds === true; - const safeChunkX = Math.floor(Number(chunkX) || 0); - const safeChunkY = Math.floor(Number(chunkY) || 0); - const safeWidth = Math.max(1, Math.floor(Number(width) || 1)); - const safeHeight = Math.max(1, Math.floor(Number(height) || 1)); - return (Array.isArray(sourceInstances) ? sourceInstances : []) - .map((entry) => { - const record = entry?.record && typeof entry.record === "object" && !Array.isArray(entry.record) - ? cloneValue(entry.record) - : {}; - const nextId = duplicateIds - ? runtimeUniqueId() - : (String(entry?.id || record?.id || runtimeUniqueId()).trim() || runtimeUniqueId()); - const nextLayer = Math.max(0, Math.floor(Number(entry?.layer ?? record?.layer) || 0)); - const nextX = Math.max(0, Math.min(safeWidth - 1, Math.floor(Number(entry?.x) || 0))); - const nextY = Math.max(0, Math.min(safeHeight - 1, Math.floor(Number(entry?.y) || 0))); - const nextTemplateId = String(entry?.templateId || record?.templateId || "").trim(); - record.id = nextId; - record.layer = nextLayer; - record.templateId = nextTemplateId; - record.position = { - x: (safeChunkX * safeWidth) + nextX, - y: (safeChunkY * safeHeight) + nextY, - }; - return { - id: nextId, - templateId: nextTemplateId, - layer: nextLayer, - x: nextX, - y: nextY, - record, - }; - }) - .filter((entry) => entry.id); + return normalizeWorldChunkInstancesHelper({ + sourceInstances, + chunkX, + chunkY, + width, + height, + options, + cloneValue, + runtimeUniqueId, + }); } function createEmptyWorldChunkPayload(chunkX, chunkY) { - const safeChunkX = Math.floor(Number(chunkX) || 0); - const safeChunkY = Math.floor(Number(chunkY) || 0); - const chunkWidth = Math.max(1, Number(worldRuntimeState.chunkWidth) || 32); - const chunkHeight = Math.max(1, Number(worldRuntimeState.chunkHeight) || 32); - return { - schemaVersion: 1, + return createEmptyWorldChunkPayloadHelper({ + chunkX, + chunkY, + chunkWidth: Math.max(1, Number(worldRuntimeState.chunkWidth) || 32), + chunkHeight: Math.max(1, Number(worldRuntimeState.chunkHeight) || 32), worldId: String(worldRuntimeState.worldId || currentMapId || "").trim(), - chunkX: safeChunkX, - chunkY: safeChunkY, - width: chunkWidth, - height: chunkHeight, - backgroundTileId: "", - roomLayers: [ - { - layer: 0, - rows: Array.from({ length: chunkHeight }, () => ".".repeat(chunkWidth)), - instanceIds: [], - }, - { - layer: 1, - rows: Array.from({ length: chunkHeight }, () => " ".repeat(chunkWidth)), - instanceIds: [], - }, - ], - heightLayers: [], - instances: [], - }; + }); } function normalizeCachedWorldChunkPayload(chunkPayload, chunkX, chunkY, options) { - const safeChunkX = Math.floor(Number(chunkX ?? chunkPayload?.chunkX) || 0); - const safeChunkY = Math.floor(Number(chunkY ?? chunkPayload?.chunkY) || 0); - const safeWidth = Math.max(1, Math.floor(Number(chunkPayload?.width) || Number(worldRuntimeState.chunkWidth) || 32)); - const safeHeight = Math.max(1, Math.floor(Number(chunkPayload?.height) || Number(worldRuntimeState.chunkHeight) || 32)); - const instances = normalizeWorldChunkInstances(chunkPayload?.instances, safeChunkX, safeChunkY, safeWidth, safeHeight, options); - const roomLayers = buildWorldChunkLayerInstanceIds(chunkPayload?.roomLayers, instances, safeWidth, safeHeight); - return { - schemaVersion: Math.max(1, Math.floor(Number(chunkPayload?.schemaVersion) || 1)), - worldId: String(chunkPayload?.worldId || worldRuntimeState.worldId || currentMapId || "").trim(), - chunkX: safeChunkX, - chunkY: safeChunkY, - width: safeWidth, - height: safeHeight, - backgroundTileId: String(chunkPayload?.backgroundTileId || "").trim(), - roomLayers, - heightLayers: cloneWorldChunkHeightLayers(chunkPayload?.heightLayers), - instances, - }; - } - - function isChunkFillSymbol(ch, fillChar) { - const symbol = String(ch || "").charAt(0); - return !symbol || symbol === fillChar || symbol === "." || symbol === " "; + return normalizeCachedWorldChunkPayloadHelper({ + chunkPayload, + chunkX, + chunkY, + chunkWidth: Number(worldRuntimeState.chunkWidth) || 32, + chunkHeight: Number(worldRuntimeState.chunkHeight) || 32, + worldId: String(worldRuntimeState.worldId || currentMapId || "").trim(), + cloneValue, + runtimeUniqueId, + options, + }); } function isWorldChunkPayloadEmpty(chunkPayload) { - const normalized = normalizeCachedWorldChunkPayload(chunkPayload, chunkPayload?.chunkX, chunkPayload?.chunkY); - if (String(normalized?.backgroundTileId || "").trim()) { - return false; - } - if (Array.isArray(normalized?.instances) && normalized.instances.length > 0) { - return false; - } - if ((Array.isArray(normalized?.heightLayers) ? normalized.heightLayers : []).some((entry) => ( - Array.isArray(entry?.rows) && entry.rows.some((row) => /[^ .]/.test(String(row || ""))) - ))) { - return false; - } - return !(Array.isArray(normalized?.roomLayers) ? normalized.roomLayers : []).some((layer) => { - const fillChar = (Number(layer?.layer) || 0) === 0 ? "." : " "; - return (Array.isArray(layer?.rows) ? layer.rows : []).some((row) => { - const sourceRow = String(row || ""); - for (let index = 0; index < sourceRow.length; index += 1) { - if (!isChunkFillSymbol(sourceRow.charAt(index), fillChar)) { - return true; - } - } - return false; - }); + return isWorldChunkPayloadEmptyHelper({ + chunkPayload, + chunkWidth: Number(worldRuntimeState.chunkWidth) || 32, + chunkHeight: Number(worldRuntimeState.chunkHeight) || 32, + worldId: String(worldRuntimeState.worldId || currentMapId || "").trim(), + cloneValue, + runtimeUniqueId, }); } - function transformChunkLocalCoord(localX, localY, width, height, operation) { - const safeX = Math.floor(Number(localX) || 0); - const safeY = Math.floor(Number(localY) || 0); - const safeWidth = Math.max(1, Math.floor(Number(width) || 1)); - const safeHeight = Math.max(1, Math.floor(Number(height) || 1)); - switch (String(operation || "").trim()) { - case "flipHorizontal": - return { x: (safeWidth - 1) - safeX, y: safeY }; - case "flipVertical": - return { x: safeX, y: (safeHeight - 1) - safeY }; - case "rotate180": - return { x: (safeWidth - 1) - safeX, y: (safeHeight - 1) - safeY }; - case "rotate90cw": - if (safeWidth !== safeHeight) { - return null; - } - return { x: (safeWidth - 1) - safeY, y: safeX }; - case "rotate90ccw": - if (safeWidth !== safeHeight) { - return null; - } - return { x: safeY, y: (safeHeight - 1) - safeX }; - default: - return { x: safeX, y: safeY }; - } - } - - function transformChunkRows(rows, width, height, fillChar, operation) { - const safeWidth = Math.max(1, Math.floor(Number(width) || 1)); - const safeHeight = Math.max(1, Math.floor(Number(height) || 1)); - const sourceRows = normalizeWorldChunkRows(rows, safeWidth, safeHeight, fillChar); - const nextRows = Array.from({ length: safeHeight }, () => Array.from({ length: safeWidth }, () => String(fillChar || " ").charAt(0) || " ")); - for (let rowIndex = 0; rowIndex < safeHeight; rowIndex += 1) { - const sourceRow = sourceRows[rowIndex]; - for (let columnIndex = 0; columnIndex < safeWidth; columnIndex += 1) { - const char = String(sourceRow.charAt(columnIndex) || fillChar).charAt(0) || String(fillChar || " ").charAt(0) || " "; - if (isChunkFillSymbol(char, fillChar)) { - continue; - } - const nextCoord = transformChunkLocalCoord(columnIndex, rowIndex, safeWidth, safeHeight, operation); - if (!nextCoord) { - continue; - } - nextRows[nextCoord.y][nextCoord.x] = char; - } - } - return nextRows.map((row) => row.join("")); - } - - function transformChunkHeightPatch(patch, width, height, operation) { - const safeWidth = Math.max(1, Math.floor(Number(width) || 1)); - const safeHeight = Math.max(1, Math.floor(Number(height) || 1)); - const sourceRows = Array.isArray(patch?.rows) ? patch.rows.map((row) => String(row || "")) : []; - const patchWidth = sourceRows.reduce((max, row) => Math.max(max, row.length), 0); - const patchHeight = sourceRows.length; - const transformedCells = []; - for (let localY = 0; localY < patchHeight; localY += 1) { - const row = sourceRows[localY] || ""; - for (let localX = 0; localX < patchWidth; localX += 1) { - const char = String(row.charAt(localX) || " ").charAt(0) || " "; - if (char === " " || char === ".") { - continue; - } - const worldX = Math.max(0, Math.floor(Number(patch?.x) || 0)) + localX; - const worldY = Math.max(0, Math.floor(Number(patch?.y) || 0)) + localY; - if (worldX < 0 || worldY < 0 || worldX >= safeWidth || worldY >= safeHeight) { - continue; - } - const nextCoord = transformChunkLocalCoord(worldX, worldY, safeWidth, safeHeight, operation); - if (!nextCoord) { - continue; - } - transformedCells.push({ - x: nextCoord.x, - y: nextCoord.y, - char, - }); - } - } - if (transformedCells.length <= 0) { - return null; - } - const minX = transformedCells.reduce((min, entry) => Math.min(min, entry.x), transformedCells[0].x); - const maxX = transformedCells.reduce((max, entry) => Math.max(max, entry.x), transformedCells[0].x); - const minY = transformedCells.reduce((min, entry) => Math.min(min, entry.y), transformedCells[0].y); - const maxY = transformedCells.reduce((max, entry) => Math.max(max, entry.y), transformedCells[0].y); - const nextRows = Array.from({ length: (maxY - minY) + 1 }, () => Array.from({ length: (maxX - minX) + 1 }, () => " ")); - transformedCells.forEach((entry) => { - nextRows[entry.y - minY][entry.x - minX] = entry.char; - }); - return { - id: String(patch?.id || "").trim(), - name: typeof patch?.name === "string" && patch.name.trim() ? patch.name.trim() : undefined, - z: Math.max(1, Math.floor(Number(patch?.z) || 1)), - x: minX, - y: minY, - rows: nextRows.map((row) => row.join("").replace(/\s+$/g, "")), - }; - } - function transformWorldChunkPayload(chunkPayload, operation, options) { - const config = options && typeof options === "object" ? options : {}; - const normalized = normalizeCachedWorldChunkPayload(chunkPayload, chunkPayload?.chunkX, chunkPayload?.chunkY, config); - const safeWidth = Math.max(1, Math.floor(Number(normalized?.width) || 1)); - const safeHeight = Math.max(1, Math.floor(Number(normalized?.height) || 1)); - const normalizedOperation = String(operation || "").trim(); - if ((normalizedOperation === "rotate90cw" || normalizedOperation === "rotate90ccw") && safeWidth !== safeHeight) { - throw new Error("Chunk rotation requires square chunks."); - } - const instances = normalizeWorldChunkInstances( - (Array.isArray(normalized.instances) ? normalized.instances : []).map((entry) => { - const nextCoord = transformChunkLocalCoord(entry.x, entry.y, safeWidth, safeHeight, normalizedOperation); - return { - ...cloneValue(entry), - x: nextCoord?.x ?? entry.x, - y: nextCoord?.y ?? entry.y, - }; - }), - normalized.chunkX, - normalized.chunkY, - safeWidth, - safeHeight, - config, - ); - const roomLayers = buildWorldChunkLayerInstanceIds( - (Array.isArray(normalized.roomLayers) ? normalized.roomLayers : []).map((layer) => ({ - ...cloneValue(layer), - rows: transformChunkRows(layer?.rows, safeWidth, safeHeight, (Number(layer?.layer) || 0) === 0 ? "." : " ", normalizedOperation), - })), - instances, - safeWidth, - safeHeight, - ); - const heightLayers = cloneWorldChunkHeightLayers(normalized.heightLayers) - .map((entry) => transformChunkHeightPatch(entry, safeWidth, safeHeight, normalizedOperation)) - .filter(Boolean) - .sort((left, right) => { - if ((Number(left?.z) || 0) !== (Number(right?.z) || 0)) { - return (Number(left?.z) || 0) - (Number(right?.z) || 0); - } - return String(left?.name || left?.id || "").localeCompare(String(right?.name || right?.id || "")); - }); - return { - ...normalized, - roomLayers, - heightLayers, - instances, - }; + return transformWorldChunkPayloadHelper({ + chunkPayload, + operation, + chunkWidth: Number(worldRuntimeState.chunkWidth) || 32, + chunkHeight: Number(worldRuntimeState.chunkHeight) || 32, + worldId: String(worldRuntimeState.worldId || currentMapId || "").trim(), + cloneValue, + runtimeUniqueId, + options, + }); } function commitWorldChunkPayloads(nextChunks, reason) { @@ -2283,7 +1646,7 @@ export function startWorldshaperStudio(bootstrap: WorldshaperStudioBootstrap, in return { ok: false, reason: "same-chunk" }; } const sourceChunk = normalizeCachedWorldChunkPayload( - cloneValue(await ensureWorldChunkCachedForEdit(safeSourceChunkX, safeSourceChunkY)) || createEmptyWorldChunkPayload(safeSourceChunkX, safeSourceChunkY), + cloneRuntimeValue(await ensureWorldChunkCachedForEdit(safeSourceChunkX, safeSourceChunkY)) || createEmptyWorldChunkPayload(safeSourceChunkX, safeSourceChunkY), safeSourceChunkX, safeSourceChunkY, ); @@ -2292,7 +1655,7 @@ export function startWorldshaperStudio(bootstrap: WorldshaperStudioBootstrap, in return { ok: false, reason: "source-empty" }; } const destinationChunk = normalizeCachedWorldChunkPayload( - cloneValue(await ensureWorldChunkCachedForEdit(safeTargetChunkX, safeTargetChunkY)) || createEmptyWorldChunkPayload(safeTargetChunkX, safeTargetChunkY), + cloneRuntimeValue(await ensureWorldChunkCachedForEdit(safeTargetChunkX, safeTargetChunkY)) || createEmptyWorldChunkPayload(safeTargetChunkX, safeTargetChunkY), safeTargetChunkX, safeTargetChunkY, ); @@ -2333,7 +1696,7 @@ export function startWorldshaperStudio(bootstrap: WorldshaperStudioBootstrap, in return { ok: false, reason: "same-chunk" }; } const sourceChunk = normalizeCachedWorldChunkPayload( - cloneValue(await ensureWorldChunkCachedForEdit(safeSourceChunkX, safeSourceChunkY)) || createEmptyWorldChunkPayload(safeSourceChunkX, safeSourceChunkY), + cloneRuntimeValue(await ensureWorldChunkCachedForEdit(safeSourceChunkX, safeSourceChunkY)) || createEmptyWorldChunkPayload(safeSourceChunkX, safeSourceChunkY), safeSourceChunkX, safeSourceChunkY, ); @@ -2342,7 +1705,7 @@ export function startWorldshaperStudio(bootstrap: WorldshaperStudioBootstrap, in return { ok: false, reason: "source-empty" }; } const destinationChunk = normalizeCachedWorldChunkPayload( - cloneValue(await ensureWorldChunkCachedForEdit(safeTargetChunkX, safeTargetChunkY)) || createEmptyWorldChunkPayload(safeTargetChunkX, safeTargetChunkY), + cloneRuntimeValue(await ensureWorldChunkCachedForEdit(safeTargetChunkX, safeTargetChunkY)) || createEmptyWorldChunkPayload(safeTargetChunkX, safeTargetChunkY), safeTargetChunkX, safeTargetChunkY, ); @@ -2351,7 +1714,7 @@ export function startWorldshaperStudio(bootstrap: WorldshaperStudioBootstrap, in return { ok: false, reason: "destination-occupied" }; } const duplicatedChunk = normalizeCachedWorldChunkPayload({ - ...cloneValue(sourceChunk), + ...cloneRuntimeValue(sourceChunk), instances: [], }, safeTargetChunkX, safeTargetChunkY); commitWorldChunkPayloads([duplicatedChunk], "world-chunk-duplicate"); @@ -2381,7 +1744,7 @@ export function startWorldshaperStudio(bootstrap: WorldshaperStudioBootstrap, in const safeChunkY = Math.floor(Number(chunkY) || 0); const normalizedOperation = String(operation || "").trim(); const sourceChunk = normalizeCachedWorldChunkPayload( - cloneValue(await ensureWorldChunkCachedForEdit(safeChunkX, safeChunkY)) || createEmptyWorldChunkPayload(safeChunkX, safeChunkY), + cloneRuntimeValue(await ensureWorldChunkCachedForEdit(safeChunkX, safeChunkY)) || createEmptyWorldChunkPayload(safeChunkX, safeChunkY), safeChunkX, safeChunkY, ); @@ -2414,7 +1777,7 @@ export function startWorldshaperStudio(bootstrap: WorldshaperStudioBootstrap, in const safeChunkX = Math.floor(Number(chunkX) || 0); const safeChunkY = Math.floor(Number(chunkY) || 0); const existingChunk = normalizeCachedWorldChunkPayload( - cloneValue(await ensureWorldChunkCachedForEdit(safeChunkX, safeChunkY)) || createEmptyWorldChunkPayload(safeChunkX, safeChunkY), + cloneRuntimeValue(await ensureWorldChunkCachedForEdit(safeChunkX, safeChunkY)) || createEmptyWorldChunkPayload(safeChunkX, safeChunkY), safeChunkX, safeChunkY, ); @@ -2452,7 +1815,7 @@ export function startWorldshaperStudio(bootstrap: WorldshaperStudioBootstrap, in ? normalizedBackgroundTileId : ""; const chunkKey = buildChunkKey(safeChunkX, safeChunkY); - const existingChunk = cloneValue(await ensureWorldChunkCachedForEdit(safeChunkX, safeChunkY)) || { + const existingChunk = cloneRuntimeValue(await ensureWorldChunkCachedForEdit(safeChunkX, safeChunkY)) || { chunkX: safeChunkX, chunkY: safeChunkY, width: chunkWidth, @@ -2871,13 +2234,13 @@ export function startWorldshaperStudio(bootstrap: WorldshaperStudioBootstrap, in backgroundTileId: normalizeBackgroundTileId(mapDocument.backgroundTileId), roomLayers: cloneLayers(mapDocument.roomLayers), heightLayers: cloneHeightLayers(mapDocument.heightLayers), - tileColors: cloneValue(tileColors), + tileColors: cloneRuntimeValue(tileColors), baseRows, npcOverlays: cloneNpcOverlays(mapDocument.npcOverlays), - contentByType: cloneValue(mapDocument.contentBundle), - spriteCatalog: cloneValue(spriteCatalog), - tileCatalogById: cloneValue(tileCatalogById), - defaultNpcTemplate: cloneValue(defaultNpcTemplate), + contentByType: cloneRuntimeValue(mapDocument.contentBundle), + spriteCatalog: cloneRuntimeValue(spriteCatalog), + tileCatalogById: cloneRuntimeValue(tileCatalogById), + defaultNpcTemplate: cloneRuntimeValue(defaultNpcTemplate), apiBase, backgroundColor: normalizeMapBackgroundColor(mapDocument.backgroundColor), heightBlurStep: Math.max(0, Math.min(1, Number(mapDocument.heightBlurStep ?? mapDocument.heightDetailStep) || 0.1)), @@ -2895,7 +2258,7 @@ export function startWorldshaperStudio(bootstrap: WorldshaperStudioBootstrap, in worldSpawnX: isWorldModeActive() ? worldRuntimeState.spawnX : undefined, worldSpawnY: isWorldModeActive() ? worldRuntimeState.spawnY : undefined, worldBookmarks: isWorldModeActive() ? cloneWorldBookmarks() : undefined, - sourceChunks: isWorldModeActive() ? cloneValue(worldRuntimeState.sourceChunks) : undefined, + sourceChunks: isWorldModeActive() ? cloneRuntimeValue(worldRuntimeState.sourceChunks) : undefined, }; } @@ -2983,7 +2346,7 @@ export function startWorldshaperStudio(bootstrap: WorldshaperStudioBootstrap, in } function getImagesPayload() { - return cloneValue(ensureDocumentContentPayload("images", { schemaVersion: 1, images: [] })) || { schemaVersion: 1, images: [] }; + return cloneRuntimeValue(ensureDocumentContentPayload("images", { schemaVersion: 1, images: [] })) || { schemaVersion: 1, images: [] }; } function buildDuplicateGraphicName(baseName, imagesPayload) { @@ -3036,9 +2399,9 @@ export function startWorldshaperStudio(bootstrap: WorldshaperStudioBootstrap, in const nextTilesPayload = buildTilesPayloadFromImagesPayload(normalizedImagesPayload); const nextSpritesPayload = buildSpritesPayloadFromImagesPayload(normalizedImagesPayload); graphicsVisualRevision += 1; - setDocumentContentPayload("images", cloneValue(normalizedImagesPayload) || { schemaVersion: 1, images: [] }); - setDocumentContentPayload("tiles", cloneValue(nextTilesPayload) || { schemaVersion: 1, tiles: [] }); - setDocumentContentPayload("sprites", cloneValue(nextSpritesPayload) || { schemaVersion: 1, sprites: [] }); + setDocumentContentPayload("images", cloneRuntimeValue(normalizedImagesPayload) || { schemaVersion: 1, images: [] }); + setDocumentContentPayload("tiles", cloneRuntimeValue(nextTilesPayload) || { schemaVersion: 1, tiles: [] }); + setDocumentContentPayload("sprites", cloneRuntimeValue(nextSpritesPayload) || { schemaVersion: 1, sprites: [] }); replaceObjectContents( tileCatalogById, applyTileCatalogVisualRevision( @@ -3179,14 +2542,14 @@ export function startWorldshaperStudio(bootstrap: WorldshaperStudioBootstrap, in } } const nextRecord = normalizeImageRecordForSave({ - ...cloneValue(sourceRecord), + ...cloneRuntimeValue(sourceRecord), id: (normalizedType === "tile" ? "tile_" : "sprite_") + runtimeUniqueId().replace(/^inst_/, ""), name: buildDuplicateGraphicName(String(sourceRecord.name || normalizedId || "Graphic"), imagesPayload), tileSymbol: normalizedType === "tile" ? nextTileSymbol : String(sourceRecord.tileSymbol || "").trim().charAt(0), rows: Array.isArray(sourceRecord.rows) ? sourceRecord.rows.map((row) => String(row || "")) : [], - frames: Array.isArray(sourceRecord.frames) ? cloneValue(sourceRecord.frames) : [], - tags: Array.isArray(sourceRecord.tags) ? cloneValue(sourceRecord.tags) : [], - roles: Array.isArray(sourceRecord.roles) ? cloneValue(sourceRecord.roles) : [], + frames: Array.isArray(sourceRecord.frames) ? cloneRuntimeValue(sourceRecord.frames) : [], + tags: Array.isArray(sourceRecord.tags) ? cloneRuntimeValue(sourceRecord.tags) : [], + roles: Array.isArray(sourceRecord.roles) ? cloneRuntimeValue(sourceRecord.roles) : [], }); const nextImages = Array.isArray(imagesPayload.images) ? imagesPayload.images.slice() : []; nextImages.push(nextRecord); @@ -3235,7 +2598,7 @@ export function startWorldshaperStudio(bootstrap: WorldshaperStudioBootstrap, in } const existing = nextImages[existingIndex] || {}; nextImages[existingIndex] = buildImageRecordFromTileRecord({ - ...cloneValue(record), + ...cloneRuntimeValue(record), symbol: String(record?.symbol || existing?.tileSymbol || "").charAt(0) || takeNextAvailableTileSymbol() || "T", }, existing, cloneValue); const nextPayload = { @@ -3284,7 +2647,7 @@ export function startWorldshaperStudio(bootstrap: WorldshaperStudioBootstrap, in return false; } const nextRecord = buildImageRecordFromTileRecord({ - ...cloneValue(sourceRecord), + ...cloneRuntimeValue(sourceRecord), symbol: nextSymbol, }, existing, cloneValue); if (existingIndex >= 0) { @@ -3412,7 +2775,7 @@ export function startWorldshaperStudio(bootstrap: WorldshaperStudioBootstrap, in clearedBackgrounds += 1; } touchWorldChunkCacheEntry(chunkKey, { - ...cloneValue(chunkValue), + ...cloneRuntimeValue(chunkValue), backgroundTileId: clearsBackground ? "" : String(chunkValue.backgroundTileId || "").trim(), roomLayers: scrubbedLayers.roomLayers, heightLayers: scrubbedHeightLayers.heightLayers, @@ -3439,7 +2802,7 @@ export function startWorldshaperStudio(bootstrap: WorldshaperStudioBootstrap, in if (!entry || typeof entry !== "object" || Array.isArray(entry)) { return entry; } - const nextEntry = cloneValue(entry) || {}; + const nextEntry = cloneRuntimeValue(entry) || {}; const nextRecord = nextEntry.record && typeof nextEntry.record === "object" && !Array.isArray(nextEntry.record) ? { ...nextEntry.record } : {}; @@ -3467,7 +2830,7 @@ export function startWorldshaperStudio(bootstrap: WorldshaperStudioBootstrap, in updatedChunks += 1; scrubbedEntities += changedEntities; touchWorldChunkCacheEntry(chunkKey, { - ...cloneValue(chunkValue), + ...cloneRuntimeValue(chunkValue), instances: nextInstances, }); } @@ -4413,7 +3776,7 @@ export function startWorldshaperStudio(bootstrap: WorldshaperStudioBootstrap, in Object.keys(target || {}).forEach((key) => { delete target[key]; }); - Object.assign(target, cloneValue(nextValue) || {}); + Object.assign(target, cloneRuntimeValue(nextValue) || {}); return target; } @@ -4430,7 +3793,7 @@ export function startWorldshaperStudio(bootstrap: WorldshaperStudioBootstrap, in const nextImagesPayload = mergeImagesPayloadWithSpritesPayload(getImagesPayload(), payload); syncRuntimeGraphicsFromImagesPayload(nextImagesPayload, config); } else { - setDocumentContentPayload(normalizedType, cloneValue(payload) || {}); + setDocumentContentPayload(normalizedType, cloneRuntimeValue(payload) || {}); } if (!config.deferRefresh) { @@ -4870,7 +4233,7 @@ export function startWorldshaperStudio(bootstrap: WorldshaperStudioBootstrap, in duplicateWorldChunkContent, transformWorldChunkAt, clearWorldChunkAt, - getCachedWorldChunkPayloads: () => Array.from(worldRuntimeState.chunkCache.values()).map((entry) => cloneValue(entry)), + getCachedWorldChunkPayloads: () => Array.from(worldRuntimeState.chunkCache.values()).map((entry) => cloneRuntimeValue(entry)), getDirtyWorldChunkKeys, getDirtyWorldChunkPayloads, clearDirtyWorldChunks, @@ -5352,142 +4715,44 @@ export function startWorldshaperStudio(bootstrap: WorldshaperStudioBootstrap, in scope.historyScope = historyScope; scope.uiScope = uiScope; scope.sessionScope = sessionScope; - const toolWindowController = createToolWindowController(scope); - const tileArtEditorWindowController = createTileArtEditorWindowController(scope); - const entityEditorWindowController = createEntityEditorWindowController(scope); - const engineOverrideWindowController = createEngineOverrideWindowController(scope); - const worldOverviewWindowController = createWorldOverviewWindowController(scope); - const changelogSplashWindowController = createChangelogSplashWindowController(scope); - statusLogWindowController = createStatusLogWindowController(scope); - const syncToolPanels = () => toolWindowController.syncPanels(); - const handleSidebarTabButtonClick = (tab) => toolWindowController.handleTabButtonClick(tab); - const restoreAllToolWindows = () => toolWindowController.restoreAllWindows(); - const openTileArtEditorWindow = (recordTypeOrId, maybeRecordId) => tileArtEditorWindowController.open(recordTypeOrId, maybeRecordId); - const closeTileArtEditorWindow = () => tileArtEditorWindowController.close(); - const openEntityEditorWindow = (entityId) => entityEditorWindowController.open(entityId); - const closeEntityEditorWindow = () => entityEditorWindowController.close(); - const openEngineOverrideWindow = () => engineOverrideWindowController.open(); - const closeEngineOverrideWindow = () => engineOverrideWindowController.close(); - const refreshEngineOverrideWindow = () => engineOverrideWindowController.refresh(); - const refreshEngineOverrideSummary = () => engineOverrideWindowController.updateSummary(); - const openWorldOverviewWindow = () => worldOverviewWindowController.open(); - const closeWorldOverviewWindow = () => worldOverviewWindowController.close(); - const refreshWorldOverviewWindow = () => worldOverviewWindowController.refresh(); - const invalidateWorldOverviewChunkSurfaces = (chunkKeys, options) => worldOverviewWindowController.invalidateChunkSurfaces?.(chunkKeys, options); - const openStatusLogWindow = () => statusLogWindowController.open(); - const closeStatusLogWindow = () => statusLogWindowController.close(); - const openNewsWindow = (options = {}) => changelogSplashWindowController.open({ markSeen: false, ...options }); - const resetWorkspaceLayoutFlow = () => { - resetWorkspaceLayout(); - toolWindowController.restoreAllWindows(); - setStatus("Workspace layout reset.", false); - }; - scope.syncToolPanels = syncToolPanels; - scope.handleSidebarTabButtonClick = handleSidebarTabButtonClick; - scope.restoreAllToolWindows = restoreAllToolWindows; - scope.resetWorkspaceLayout = resetWorkspaceLayoutFlow; - scope.createNewTile = createNewTile; - scope.createNewSpriteGraphic = createNewSpriteGraphic; - scope.duplicateGraphicRecord = duplicateGraphicRecord; - scope.openTileArtEditorWindow = openTileArtEditorWindow; - scope.closeTileArtEditorWindow = closeTileArtEditorWindow; - scope.openEntityEditorWindow = openEntityEditorWindow; - scope.closeEntityEditorWindow = closeEntityEditorWindow; - scope.openEngineOverrideWindow = openEngineOverrideWindow; - scope.closeEngineOverrideWindow = closeEngineOverrideWindow; - scope.refreshEngineOverrideWindow = refreshEngineOverrideWindow; - scope.refreshEngineOverrideSummary = refreshEngineOverrideSummary; - scope.openWorldOverviewWindow = openWorldOverviewWindow; - scope.closeWorldOverviewWindow = closeWorldOverviewWindow; - scope.refreshWorldOverviewWindow = refreshWorldOverviewWindow; - scope.invalidateWorldOverviewChunkSurfaces = invalidateWorldOverviewChunkSurfaces; - scope.openStatusLogWindow = openStatusLogWindow; - scope.closeStatusLogWindow = closeStatusLogWindow; - scope.openNewsWindow = openNewsWindow; - scope.openTilePaletteContextMenu = openTilePaletteContextMenu; - scope.openPlacedEntityContextMenu = openPlacedEntityContextMenu; - scope.applyNpcEditorChange = applyNpcEditorChange; - scope.getEditorEngineOverrides = getEditorEngineOverrides; - scope.saveEditorEngineOverrides = saveEditorEngineOverrides; - scope.getEffectiveHeightBlurStep = getEffectiveHeightBlurStep; - scope.isRendererDebugEnabled = isRendererDebugEnabled; - scope.reloadGraphicsContentFromApi = reloadGraphicsContentFromApi; - uiScope.syncToolPanels = syncToolPanels; - uiScope.handleSidebarTabButtonClick = handleSidebarTabButtonClick; - uiScope.restoreAllToolWindows = restoreAllToolWindows; - uiScope.resetWorkspaceLayout = resetWorkspaceLayoutFlow; - uiScope.createNewTile = createNewTile; - uiScope.createNewSpriteGraphic = createNewSpriteGraphic; - uiScope.duplicateGraphicRecord = duplicateGraphicRecord; - uiScope.openEntityEditorWindow = openEntityEditorWindow; - uiScope.closeEntityEditorWindow = closeEntityEditorWindow; - uiScope.openEngineOverrideWindow = openEngineOverrideWindow; - uiScope.closeEngineOverrideWindow = closeEngineOverrideWindow; - uiScope.refreshEngineOverrideWindow = refreshEngineOverrideWindow; - uiScope.refreshEngineOverrideSummary = refreshEngineOverrideSummary; - uiScope.openWorldOverviewWindow = openWorldOverviewWindow; - uiScope.closeWorldOverviewWindow = closeWorldOverviewWindow; - uiScope.refreshWorldOverviewWindow = refreshWorldOverviewWindow; - uiScope.invalidateWorldOverviewChunkSurfaces = invalidateWorldOverviewChunkSurfaces; - uiScope.openStatusLogWindow = openStatusLogWindow; - uiScope.closeStatusLogWindow = closeStatusLogWindow; - uiScope.openNewsWindow = openNewsWindow; - uiScope.openTilePaletteContextMenu = openTilePaletteContextMenu; - uiScope.openPlacedEntityContextMenu = openPlacedEntityContextMenu; - uiScope.applyNpcEditorChange = applyNpcEditorChange; - uiScope.getEditorEngineOverrides = getEditorEngineOverrides; - uiScope.saveEditorEngineOverrides = saveEditorEngineOverrides; - uiScope.getEffectiveHeightBlurStep = getEffectiveHeightBlurStep; - uiScope.isRendererDebugEnabled = isRendererDebugEnabled; - uiScope.reloadGraphicsContentFromApi = reloadGraphicsContentFromApi; - - syncDocumentTitle(); - const historyController = createHistoryController(scope); - const npcController = createNpcController(scope); - const sidebarController = createSidebarController(scope); - renderController = createRenderController(scope); - const persistenceController = createPersistenceController(scope); - const importController = createImportController(scope); - const interactionController = createInteractionController(scope); const persistPopupBounds = () => { persistWorldshaperStudioBounds(window); }; - const persistPopupBoundsDeferred = createDebouncedCallback(() => { - persistPopupBounds(); - }, 160); - syncCanvasDimensionsToTileSize(); - toolWindowController.initialize(); - tileArtEditorWindowController.initialize(); - entityEditorWindowController.initialize(); - engineOverrideWindowController.initialize(); - worldOverviewWindowController.initialize(); - changelogSplashWindowController.initialize(); - statusLogWindowController.initialize(); - renderController.initializeRenderAssets(); - interactionController.initializeEditorState(); - interactionController.bindDomEvents(); - interactionController.initializeUi(); - refreshEditorEngineOverridesUi(); - cacheStandaloneMapBootstrap(currentMapId); - if (isWorldModeActive()) { - window.requestAnimationFrame(() => { - const initialWorldView = getInitialWorldViewTile(); - centerViewportOnWorldTile(initialWorldView.worldTileX, initialWorldView.worldTileY); - prefetchAdjacentWorldNeighborhoods(worldRuntimeState.centerChunkX, worldRuntimeState.centerChunkY); - syncWorldNeighborhoodForViewport(); - drawNow(); - setStatus("World mode loaded. Endless navigation is active.", false); - }); - } - window.requestAnimationFrame(() => { - changelogSplashWindowController.maybeOpenForCurrentVersion(); - }); - window.addEventListener("resize", () => { - persistPopupBoundsDeferred(); - }); - window.addEventListener("beforeunload", () => { - popupSessionStore.flushPersistedLayout(window); - persistPopupBounds(); + const runtimeControllerBootstrap = initializeRuntimeControllers({ + scope, + uiScope, + resetWorkspaceLayout, + setStatus, + createNewTile, + createNewSpriteGraphic, + duplicateGraphicRecord, + openTilePaletteContextMenu, + openPlacedEntityContextMenu, + applyNpcEditorChange, + getEditorEngineOverrides, + saveEditorEngineOverrides, + getEffectiveHeightBlurStep, + isRendererDebugEnabled, + reloadGraphicsContentFromApi, + syncDocumentTitle, + syncCanvasDimensionsToTileSize, + refreshEditorEngineOverridesUi, + cacheStandaloneMapBootstrap, + currentMapId, + persistPopupBounds, + popupSessionStore, + windowRef: window, + isWorldModeActive, + getInitialWorldViewTile, + centerViewportOnWorldTile, + prefetchAdjacentWorldNeighborhoods, + worldRuntimeState, + syncWorldNeighborhoodForViewport, + drawNow, }); + renderController = runtimeControllerBootstrap.renderController; + statusLogWindowController = runtimeControllerBootstrap.statusLogWindowController; + runtimeLogging.setStatusLogWindowController(statusLogWindowController); } + diff --git a/src/worldshaperStudio/runtimeBootstrapHelpers.ts b/src/worldshaperStudio/runtimeBootstrapHelpers.ts new file mode 100644 index 0000000..8b19ee2 --- /dev/null +++ b/src/worldshaperStudio/runtimeBootstrapHelpers.ts @@ -0,0 +1,141 @@ +/* eslint-disable @typescript-eslint/ban-ts-comment, @typescript-eslint/no-unused-vars, no-empty, no-useless-escape */ +// @ts-nocheck +import { buildSpritePreviewDataUrl } from "../editorCore"; +import { + buildSpriteCatalog, + buildTileCatalogById, + DEFAULT_MAP_BACKGROUND_COLOR, +} from "../components/worldshaperShared"; +import type { WorldshaperStudioBootstrap } from "./bootstrap"; + +function getContentRecords(payload: unknown, key: string) { + const records = payload && Array.isArray(payload[key]) ? payload[key] : []; + return records.filter((entry) => Boolean(entry) && typeof entry === "object" && !Array.isArray(entry)); +} + +export function cloneRuntimeValue(value: T): T { + if (typeof structuredClone === "function") { + return structuredClone(value); + } + return value == null ? value : JSON.parse(JSON.stringify(value)); +} + +export function buildSpriteCatalogFromBootstrap(bootstrap: WorldshaperStudioBootstrap) { + const spriteRecords = getContentRecords(bootstrap?.contentByType?.sprites, "sprites"); + if (spriteRecords.length > 0) { + return buildSpriteCatalog(spriteRecords, buildSpritePreviewDataUrl); + } + return cloneRuntimeValue(bootstrap?.spriteCatalog) || {}; +} + +export function buildTileCatalogByIdFromBootstrap(bootstrap: WorldshaperStudioBootstrap) { + const tileRecords = getContentRecords(bootstrap?.contentByType?.tiles, "tiles"); + if (tileRecords.length > 0) { + return buildTileCatalogById(tileRecords, buildSpritePreviewDataUrl); + } + return cloneRuntimeValue(bootstrap?.tileCatalogById) || {}; +} + +export function buildNpcOverlaysFromWorldChunks( + chunks: unknown[], + spriteCatalog: Record, + chunkWidth: number, + chunkHeight: number, + originChunkX: number, + originChunkY: number, +) { + return (Array.isArray(chunks) ? chunks : []).flatMap((chunk) => { + const baseChunkX = Math.floor(Number(chunk?.chunkX) || 0); + const baseChunkY = Math.floor(Number(chunk?.chunkY) || 0); + const offsetX = (baseChunkX - originChunkX) * chunkWidth; + const offsetY = (baseChunkY - originChunkY) * chunkHeight; + const instances = Array.isArray(chunk?.instances) ? chunk.instances : []; + return instances + .filter((entry) => Boolean(entry) && typeof entry === "object" && !Array.isArray(entry)) + .map((entry) => { + const record = entry.record && typeof entry.record === "object" && !Array.isArray(entry.record) + ? cloneRuntimeValue(entry.record) + : {}; + const spriteId = String(record.spriteId || entry.spriteId || "").trim(); + const spriteEntry = spriteCatalog[spriteId] || null; + const overlayX = offsetX + Math.max(0, Number(entry.x) || 0); + const overlayY = offsetY + Math.max(0, Number(entry.y) || 0); + record.position = { + x: overlayX, + y: overlayY, + }; + return { + id: String(entry.id || "").trim(), + layer: Number(entry.layer) || 0, + name: String(record.name || entry.id || "NPC"), + spriteId, + x: overlayX, + y: overlayY, + dataUrl: spriteEntry ? spriteEntry.dataUrl : null, + spriteWidth: spriteEntry ? spriteEntry.spriteWidth : 28, + spriteHeight: spriteEntry ? spriteEntry.spriteHeight : 28, + opacity: spriteEntry ? spriteEntry.opacity : 1, + record, + }; + }) + .filter((entry) => entry.id); + }); +} + +export function normalizeMapBackgroundColor(value: unknown, fallback?: string) { + const safeFallback = fallback || DEFAULT_MAP_BACKGROUND_COLOR; + const raw = String(value || "").trim(); + return /^#[0-9a-fA-F]{6}$/.test(raw) ? raw.toUpperCase() : safeFallback; +} + +export function createInitialWorldRuntimeState(bootstrap: WorldshaperStudioBootstrap) { + const worldId = String(bootstrap.worldId || bootstrap.mapId || "").trim(); + const chunkRadius = Math.max(0, Math.floor(Number(bootstrap.worldChunkRadius) || 0)); + const originChunkX = Math.floor(Number(bootstrap.worldOriginChunkX) || 0); + const originChunkY = Math.floor(Number(bootstrap.worldOriginChunkY) || 0); + + return { + enabled: !!worldId, + worldId, + worldName: String(bootstrap.worldName || bootstrap.mapName || bootstrap.worldId || bootstrap.mapId || "World").trim() || "World", + defaultBackgroundTileId: String(bootstrap.backgroundTileId || "").trim(), + heightBlurStep: Math.max(0, Math.min(1, Number(bootstrap.heightBlurStep ?? bootstrap.heightDetailStep) || 0.1)), + chunkWidth: Math.max(1, Number(bootstrap.worldChunkWidth) || 32), + chunkHeight: Math.max(1, Number(bootstrap.worldChunkHeight) || 32), + chunkRadius, + originChunkX, + originChunkY, + tileOffsetX: Math.floor(Number(bootstrap.worldTileOffsetX) || 0), + tileOffsetY: Math.floor(Number(bootstrap.worldTileOffsetY) || 0), + spawnX: Math.floor(Number(bootstrap.worldSpawnX) || 0), + spawnY: Math.floor(Number(bootstrap.worldSpawnY) || 0), + centerChunkX: originChunkX + chunkRadius, + centerChunkY: originChunkY + chunkRadius, + sourceChunks: Array.isArray(bootstrap.sourceChunks) + ? bootstrap.sourceChunks.map((entry) => ({ + chunkX: Math.floor(Number(entry?.chunkX) || 0), + chunkY: Math.floor(Number(entry?.chunkY) || 0), + })) + : [], + bookmarks: Array.isArray(bootstrap.worldBookmarks) + ? bootstrap.worldBookmarks.map((entry, index) => ({ + id: String(entry?.id || `poi_${index + 1}`).trim() || `poi_${index + 1}`, + label: String(entry?.label || entry?.id || `POI ${index + 1}`).trim() || `POI ${index + 1}`, + x: Math.floor(Number(entry?.x) || 0), + y: Math.floor(Number(entry?.y) || 0), + })) + : [], + chunkCache: new Map(), + dirtyChunkKeys: new Set(), + pendingNeighborhoodFetches: new Map(), + prefetchedNeighborhoodKeys: new Set(), + pendingLoadKey: "", + pendingLoadPromise: null, + requestSerial: 0, + documentDirty: false, + }; +} + +export const MAX_WORLD_CHUNK_CACHE_ENTRIES = 256; +export const MAX_DYNAMIC_WORLD_CHUNK_RADIUS = 4; +export const TILE_SYMBOL_POOL = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz!$%&()*+,-/:;<=>?@[]^_{|}~="; diff --git a/src/worldshaperStudio/runtimeControllerBootstrap.ts b/src/worldshaperStudio/runtimeControllerBootstrap.ts new file mode 100644 index 0000000..9149b45 --- /dev/null +++ b/src/worldshaperStudio/runtimeControllerBootstrap.ts @@ -0,0 +1,201 @@ +// @ts-nocheck + +import { createHistoryController } from "./historyController"; +import { createInteractionController } from "./interactionController"; +import { createImportController } from "./importController"; +import { createNpcController } from "./npcController"; +import { createChangelogSplashWindowController } from "./changelogSplashWindowController"; +import { createEntityEditorWindowController } from "./entityEditorWindowController"; +import { createEngineOverrideWindowController } from "./engineOverrideWindowController"; +import { createPersistenceController } from "./persistenceController"; +import { createRenderController } from "./renderController"; +import { createSidebarController } from "./sidebarController"; +import { createStatusLogWindowController } from "./statusLogWindowController"; +import { createTileArtEditorWindowController } from "./tileArtEditorWindowController"; +import { createToolWindowController } from "./toolWindowController"; +import { createWorldOverviewWindowController } from "./worldOverviewWindowController"; +import { createDebouncedCallback } from "./debounce"; + +export function initializeRuntimeControllers(config) { + const { + scope, + uiScope, + resetWorkspaceLayout, + setStatus, + createNewTile, + createNewSpriteGraphic, + duplicateGraphicRecord, + openTilePaletteContextMenu, + openPlacedEntityContextMenu, + applyNpcEditorChange, + getEditorEngineOverrides, + saveEditorEngineOverrides, + getEffectiveHeightBlurStep, + isRendererDebugEnabled, + reloadGraphicsContentFromApi, + syncDocumentTitle, + syncCanvasDimensionsToTileSize, + refreshEditorEngineOverridesUi, + cacheStandaloneMapBootstrap, + currentMapId, + persistPopupBounds, + popupSessionStore, + windowRef, + isWorldModeActive, + getInitialWorldViewTile, + centerViewportOnWorldTile, + prefetchAdjacentWorldNeighborhoods, + worldRuntimeState, + syncWorldNeighborhoodForViewport, + drawNow, + } = config; + + const toolWindowController = createToolWindowController(scope); + const tileArtEditorWindowController = createTileArtEditorWindowController(scope); + const entityEditorWindowController = createEntityEditorWindowController(scope); + const engineOverrideWindowController = createEngineOverrideWindowController(scope); + const worldOverviewWindowController = createWorldOverviewWindowController(scope); + const changelogSplashWindowController = createChangelogSplashWindowController(scope); + const statusLogWindowController = createStatusLogWindowController(scope); + + const syncToolPanels = () => toolWindowController.syncPanels(); + const handleSidebarTabButtonClick = (tab) => toolWindowController.handleTabButtonClick(tab); + const restoreAllToolWindows = () => toolWindowController.restoreAllWindows(); + const openTileArtEditorWindow = (recordTypeOrId, maybeRecordId) => tileArtEditorWindowController.open(recordTypeOrId, maybeRecordId); + const closeTileArtEditorWindow = () => tileArtEditorWindowController.close(); + const openEntityEditorWindow = (entityId) => entityEditorWindowController.open(entityId); + const closeEntityEditorWindow = () => entityEditorWindowController.close(); + const openEngineOverrideWindow = () => engineOverrideWindowController.open(); + const closeEngineOverrideWindow = () => engineOverrideWindowController.close(); + const refreshEngineOverrideWindow = () => engineOverrideWindowController.refresh(); + const refreshEngineOverrideSummary = () => engineOverrideWindowController.updateSummary(); + const openWorldOverviewWindow = () => worldOverviewWindowController.open(); + const closeWorldOverviewWindow = () => worldOverviewWindowController.close(); + const refreshWorldOverviewWindow = () => worldOverviewWindowController.refresh(); + const invalidateWorldOverviewChunkSurfaces = (chunkKeys, options) => worldOverviewWindowController.invalidateChunkSurfaces?.(chunkKeys, options); + const openStatusLogWindow = () => statusLogWindowController.open(); + const closeStatusLogWindow = () => statusLogWindowController.close(); + const openNewsWindow = (options = {}) => changelogSplashWindowController.open({ markSeen: false, ...options }); + const resetWorkspaceLayoutFlow = () => { + resetWorkspaceLayout(); + toolWindowController.restoreAllWindows(); + setStatus("Workspace layout reset.", false); + }; + + scope.syncToolPanels = syncToolPanels; + scope.handleSidebarTabButtonClick = handleSidebarTabButtonClick; + scope.restoreAllToolWindows = restoreAllToolWindows; + scope.resetWorkspaceLayout = resetWorkspaceLayoutFlow; + scope.createNewTile = createNewTile; + scope.createNewSpriteGraphic = createNewSpriteGraphic; + scope.duplicateGraphicRecord = duplicateGraphicRecord; + scope.openTileArtEditorWindow = openTileArtEditorWindow; + scope.closeTileArtEditorWindow = closeTileArtEditorWindow; + scope.openEntityEditorWindow = openEntityEditorWindow; + scope.closeEntityEditorWindow = closeEntityEditorWindow; + scope.openEngineOverrideWindow = openEngineOverrideWindow; + scope.closeEngineOverrideWindow = closeEngineOverrideWindow; + scope.refreshEngineOverrideWindow = refreshEngineOverrideWindow; + scope.refreshEngineOverrideSummary = refreshEngineOverrideSummary; + scope.openWorldOverviewWindow = openWorldOverviewWindow; + scope.closeWorldOverviewWindow = closeWorldOverviewWindow; + scope.refreshWorldOverviewWindow = refreshWorldOverviewWindow; + scope.invalidateWorldOverviewChunkSurfaces = invalidateWorldOverviewChunkSurfaces; + scope.openStatusLogWindow = openStatusLogWindow; + scope.closeStatusLogWindow = closeStatusLogWindow; + scope.openNewsWindow = openNewsWindow; + scope.openTilePaletteContextMenu = openTilePaletteContextMenu; + scope.openPlacedEntityContextMenu = openPlacedEntityContextMenu; + scope.applyNpcEditorChange = applyNpcEditorChange; + scope.getEditorEngineOverrides = getEditorEngineOverrides; + scope.saveEditorEngineOverrides = saveEditorEngineOverrides; + scope.getEffectiveHeightBlurStep = getEffectiveHeightBlurStep; + scope.isRendererDebugEnabled = isRendererDebugEnabled; + scope.reloadGraphicsContentFromApi = reloadGraphicsContentFromApi; + + uiScope.syncToolPanels = syncToolPanels; + uiScope.handleSidebarTabButtonClick = handleSidebarTabButtonClick; + uiScope.restoreAllToolWindows = restoreAllToolWindows; + uiScope.resetWorkspaceLayout = resetWorkspaceLayoutFlow; + uiScope.createNewTile = createNewTile; + uiScope.createNewSpriteGraphic = createNewSpriteGraphic; + uiScope.duplicateGraphicRecord = duplicateGraphicRecord; + uiScope.openEntityEditorWindow = openEntityEditorWindow; + uiScope.closeEntityEditorWindow = closeEntityEditorWindow; + uiScope.openEngineOverrideWindow = openEngineOverrideWindow; + uiScope.closeEngineOverrideWindow = closeEngineOverrideWindow; + uiScope.refreshEngineOverrideWindow = refreshEngineOverrideWindow; + uiScope.refreshEngineOverrideSummary = refreshEngineOverrideSummary; + uiScope.openWorldOverviewWindow = openWorldOverviewWindow; + uiScope.closeWorldOverviewWindow = closeWorldOverviewWindow; + uiScope.refreshWorldOverviewWindow = refreshWorldOverviewWindow; + uiScope.invalidateWorldOverviewChunkSurfaces = invalidateWorldOverviewChunkSurfaces; + uiScope.openStatusLogWindow = openStatusLogWindow; + uiScope.closeStatusLogWindow = closeStatusLogWindow; + uiScope.openNewsWindow = openNewsWindow; + uiScope.openTilePaletteContextMenu = openTilePaletteContextMenu; + uiScope.openPlacedEntityContextMenu = openPlacedEntityContextMenu; + uiScope.applyNpcEditorChange = applyNpcEditorChange; + uiScope.getEditorEngineOverrides = getEditorEngineOverrides; + uiScope.saveEditorEngineOverrides = saveEditorEngineOverrides; + uiScope.getEffectiveHeightBlurStep = getEffectiveHeightBlurStep; + uiScope.isRendererDebugEnabled = isRendererDebugEnabled; + uiScope.reloadGraphicsContentFromApi = reloadGraphicsContentFromApi; + + syncDocumentTitle(); + createHistoryController(scope); + createNpcController(scope); + createSidebarController(scope); + const renderController = createRenderController(scope); + createPersistenceController(scope); + createImportController(scope); + const interactionController = createInteractionController(scope); + + const persistPopupBoundsDeferred = createDebouncedCallback(() => { + persistPopupBounds(); + }, 160); + + syncCanvasDimensionsToTileSize(); + toolWindowController.initialize(); + tileArtEditorWindowController.initialize(); + entityEditorWindowController.initialize(); + engineOverrideWindowController.initialize(); + worldOverviewWindowController.initialize(); + changelogSplashWindowController.initialize(); + statusLogWindowController.initialize(); + renderController.initializeRenderAssets(); + interactionController.initializeEditorState(); + interactionController.bindDomEvents(); + interactionController.initializeUi(); + refreshEditorEngineOverridesUi(); + cacheStandaloneMapBootstrap(currentMapId); + + if (isWorldModeActive()) { + windowRef.requestAnimationFrame(() => { + const initialWorldView = getInitialWorldViewTile(); + centerViewportOnWorldTile(initialWorldView.worldTileX, initialWorldView.worldTileY); + prefetchAdjacentWorldNeighborhoods(worldRuntimeState.centerChunkX, worldRuntimeState.centerChunkY); + syncWorldNeighborhoodForViewport(); + drawNow(); + setStatus("World mode loaded. Endless navigation is active.", false); + }); + } + + windowRef.requestAnimationFrame(() => { + changelogSplashWindowController.maybeOpenForCurrentVersion(); + }); + windowRef.addEventListener("resize", () => { + persistPopupBoundsDeferred(); + }); + windowRef.addEventListener("beforeunload", () => { + popupSessionStore.flushPersistedLayout(windowRef); + persistPopupBounds(); + }); + + return { + renderController, + statusLogWindowController, + changelogSplashWindowController, + persistPopupBoundsDeferred, + }; +} diff --git a/src/worldshaperStudio/runtimeLogging.ts b/src/worldshaperStudio/runtimeLogging.ts new file mode 100644 index 0000000..8d08890 --- /dev/null +++ b/src/worldshaperStudio/runtimeLogging.ts @@ -0,0 +1,67 @@ +// @ts-nocheck + +export function createRuntimeLogging({ windowRef, runtimeUniqueId }) { + const editorLogEntries = []; + const EDITOR_LOG_LIMIT = 500; + let statusLogWindowController = null; + + function formatEditorLogTimestamp(timestamp) { + try { + return new Date(timestamp).toLocaleString(); + } catch { + return String(timestamp || ""); + } + } + + function appendEditorLogEntry(level, message) { + const normalizedMessage = String(message || "").trim(); + if (!normalizedMessage) { + return null; + } + const timestamp = Date.now(); + const entry = { + id: runtimeUniqueId(), + timestamp, + timestampLabel: formatEditorLogTimestamp(timestamp), + level: String(level || "Information").trim() || "Information", + message: normalizedMessage, + }; + editorLogEntries.push(entry); + while (editorLogEntries.length > EDITOR_LOG_LIMIT) { + editorLogEntries.shift(); + } + statusLogWindowController?.refresh?.(); + return entry; + } + + function getEditorLogEntries() { + return editorLogEntries.slice(); + } + + function clearEditorLogEntries() { + editorLogEntries.splice(0, editorLogEntries.length); + statusLogWindowController?.refresh?.(); + } + + windowRef.addEventListener("error", (event) => { + const message = String(event?.message || event?.error?.message || "Unknown runtime error"); + appendEditorLogEntry("Error", message); + }); + + windowRef.addEventListener("unhandledrejection", (event) => { + const reason = event?.reason; + const message = typeof reason === "string" + ? reason + : String(reason?.message || reason || "Unhandled promise rejection"); + appendEditorLogEntry("Error", message); + }); + + return { + appendEditorLogEntry, + getEditorLogEntries, + clearEditorLogEntries, + setStatusLogWindowController(nextController) { + statusLogWindowController = nextController; + }, + }; +} diff --git a/src/worldshaperStudio/tileArtEditorHelpers.ts b/src/worldshaperStudio/tileArtEditorHelpers.ts new file mode 100644 index 0000000..2110c56 --- /dev/null +++ b/src/worldshaperStudio/tileArtEditorHelpers.ts @@ -0,0 +1,420 @@ +/* eslint-disable @typescript-eslint/ban-ts-comment */ +// @ts-nocheck + +import { + buildSpritePreviewDataUrl, + getSpritePalette, + normalizeImagePlayback, +} from "../editorCore"; +import { normalizeEditorTags } from "./tagUtils"; + +export const TILE_ART_SIZE = 16; + +export const EYEDROPPER_CURSOR = `url("data:image/svg+xml,${encodeURIComponent( + ` + + + + `, +)}") 4 28, crosshair`; + +export function cloneValue(value) { + if (typeof structuredClone === "function") { + return structuredClone(value); + } + return value == null ? value : JSON.parse(JSON.stringify(value)); +} + +function normalizeRoleList(value) { + if (!Array.isArray(value)) { + return []; + } + return Array.from(new Set( + value + .map((entry) => String(entry || "").trim().toLowerCase()) + .filter((entry) => entry === "tile" || entry === "sprite"), + )); +} + +export function normalizeTimelineRows(rows) { + return Array.from({ length: TILE_ART_SIZE }, (_entry, rowIndex) => { + const row = Array.isArray(rows) ? String(rows[rowIndex] || "") : ""; + return row.padEnd(TILE_ART_SIZE, ".").slice(0, TILE_ART_SIZE); + }); +} + +export function normalizeWorkingFrames(record) { + const rawFrames = Array.isArray(record?.frames) ? record.frames.filter((entry) => entry && typeof entry === "object" && !Array.isArray(entry)) : []; + const normalizedFrames = rawFrames.map((entry, index) => ({ + ...cloneValue(entry), + id: String(entry.id || `frame_${index}`).trim() || `frame_${index}`, + enabled: entry.enabled !== false, + index: Number.isFinite(Number(entry.index)) ? Math.max(0, Math.floor(Number(entry.index))) : index, + rows: normalizeTimelineRows(entry.rows), + })); + if (normalizedFrames.length > 0) { + return normalizedFrames; + } + return [{ + id: "frame_0", + enabled: true, + index: 0, + rows: normalizeTimelineRows(record?.rows), + }]; +} + +export function sortWorkingFrames(frames) { + return frames + .map((frame, sourceIndex) => ({ + frame, + sourceIndex, + sortIndex: Number.isFinite(Number(frame?.index)) ? Number(frame.index) : sourceIndex, + })) + .sort((left, right) => ( + left.sortIndex !== right.sortIndex + ? left.sortIndex - right.sortIndex + : left.sourceIndex - right.sourceIndex + )) + .map((entry) => entry.frame); +} + +export function normalizeWorkingGraphicRecord(recordType, record) { + const source = cloneValue(record) || {}; + const roles = normalizeRoleList(source.roles); + const nextRoles = recordType === "tile" + ? Array.from(new Set([...roles, "tile"])) + : ( + recordType === "sprite" + ? Array.from(new Set([...roles, "sprite"])) + : roles.filter((entry) => entry !== "sprite") + ); + const frames = normalizeWorkingFrames(source).map((frame, index) => ({ + ...frame, + index, + })); + const requestedDefaultFrameId = String(source.defaultFrame || "").trim(); + const defaultFrameId = String( + frames.find((frame) => String(frame.id || "").trim() === requestedDefaultFrameId)?.id + || frames[0]?.id + || "frame_0", + ).trim() || "frame_0"; + const workingRows = normalizeTimelineRows( + Array.isArray(source.rows) && source.rows.length > 0 + ? source.rows + : (frames.find((frame) => String(frame.id || "").trim() === defaultFrameId)?.rows || frames[0]?.rows || []) + ); + return { + ...source, + id: String(source.id || `${recordType === "tile" ? "tile" : "sprite"}_${Date.now()}`).trim(), + name: typeof source.name === "string" ? source.name : "", + description: typeof source.description === "string" ? source.description : "", + width: TILE_ART_SIZE, + height: TILE_ART_SIZE, + pixelScale: Math.max(1, Number(source.pixelScale) || 2), + opacity: Number.isFinite(Number(source.opacity)) ? Math.max(0, Math.min(1, Number(source.opacity))) : 1, + tags: normalizeEditorTags(source.tags), + roles: nextRoles, + tileSymbol: nextRoles.includes("tile") + ? (String(source.tileSymbol ?? source.symbol ?? source.id ?? "T").trim().charAt(0) || "T") + : "", + defaultFrame: defaultFrameId, + speed: Number.isFinite(Number(source.speed)) && Number(source.speed) >= 0 ? Number(source.speed) : 0, + playback: normalizeImagePlayback(source.playback), + frames, + rows: workingRows, + }; +} + +export function normalizeOpacityValue(value, fallback = 1) { + const parsed = Number(value); + if (!Number.isFinite(parsed)) { + return fallback; + } + return Math.max(0, Math.min(1, parsed)); +} + +export function formatOpacityValue(value) { + const normalized = normalizeOpacityValue(value, 1); + return normalized.toFixed(2).replace(/\.?0+$/, ""); +} + +export function cloneRows(rows) { + return Array.isArray(rows) + ? rows.map((row) => String(row || "").padEnd(TILE_ART_SIZE, ".").slice(0, TILE_ART_SIZE)) + : Array.from({ length: TILE_ART_SIZE }, () => ".".repeat(TILE_ART_SIZE)); +} + +export function buildRowsPreviewRecord(rows) { + return { + width: TILE_ART_SIZE, + height: TILE_ART_SIZE, + rows: cloneRows(rows), + }; +} + +export function formatPlaybackLabel(value) { + const normalized = normalizeImagePlayback(value); + if (normalized === "rewind") { + return "Rewind"; + } + if (normalized === "stop") { + return "Stop"; + } + return "Normal"; +} + +export function getWorkingCellSymbol(record, x, y) { + const rows = Array.isArray(record?.rows) ? record.rows : []; + const row = String(rows[y] || "").padEnd(TILE_ART_SIZE, ".").slice(0, TILE_ART_SIZE); + return String(row.charAt(x) || ".").charAt(0) || "."; +} + +export function paintWorkingRowsCell(rows, x, y, symbol) { + const nextRows = cloneRows(rows); + const nextSymbol = String(symbol || ".").charAt(0) || "."; + const targetRow = String(nextRows[y] || "").padEnd(TILE_ART_SIZE, ".").slice(0, TILE_ART_SIZE); + nextRows[y] = `${targetRow.slice(0, x)}${nextSymbol}${targetRow.slice(x + 1)}`; + return nextRows; +} + +export function getRowsMatrix(rows) { + return cloneRows(rows).map((row) => Array.from(row)); +} + +export function buildRowsFromMatrix(matrix) { + return Array.from({ length: TILE_ART_SIZE }, (_entry, rowIndex) => { + const sourceRow = Array.isArray(matrix?.[rowIndex]) ? matrix[rowIndex] : []; + return sourceRow.join("").padEnd(TILE_ART_SIZE, ".").slice(0, TILE_ART_SIZE); + }); +} + +export function getAlternatePaintSymbol(record, preferredSymbol) { + const normalizedPreferred = String(preferredSymbol || "").charAt(0) || "."; + const palette = getSpritePalette(record || undefined); + const nextSymbol = Object.keys(palette) + .map((symbol) => String(symbol || "").charAt(0)) + .find((symbol) => symbol && symbol !== normalizedPreferred && symbol !== "."); + return nextSymbol || "."; +} + +export function shiftRows(rows, offsetX, offsetY) { + const nextRows = Array.from({ length: TILE_ART_SIZE }, () => ".".repeat(TILE_ART_SIZE).split("")); + const sourceRows = cloneRows(rows); + for (let y = 0; y < TILE_ART_SIZE; y += 1) { + const row = sourceRows[y] || ".".repeat(TILE_ART_SIZE); + for (let x = 0; x < TILE_ART_SIZE; x += 1) { + const nextX = x + offsetX; + const nextY = y + offsetY; + if (nextX < 0 || nextX >= TILE_ART_SIZE || nextY < 0 || nextY >= TILE_ART_SIZE) { + continue; + } + nextRows[nextY][nextX] = String(row.charAt(x) || ".").charAt(0) || "."; + } + } + return buildRowsFromMatrix(nextRows); +} + +export function flipRowsHorizontally(rows) { + return cloneRows(rows).map((row) => row.split("").reverse().join("")); +} + +export function flipRowsVertically(rows) { + return cloneRows(rows).slice().reverse(); +} + +export function rotateRowsClockwise(rows) { + const matrix = getRowsMatrix(rows); + const nextMatrix = Array.from({ length: TILE_ART_SIZE }, () => Array.from({ length: TILE_ART_SIZE }, () => ".")); + for (let y = 0; y < TILE_ART_SIZE; y += 1) { + for (let x = 0; x < TILE_ART_SIZE; x += 1) { + nextMatrix[x][TILE_ART_SIZE - 1 - y] = String(matrix[y]?.[x] || ".").charAt(0) || "."; + } + } + return buildRowsFromMatrix(nextMatrix); +} + +export function rotateRowsCounterClockwise(rows) { + const matrix = getRowsMatrix(rows); + const nextMatrix = Array.from({ length: TILE_ART_SIZE }, () => Array.from({ length: TILE_ART_SIZE }, () => ".")); + for (let y = 0; y < TILE_ART_SIZE; y += 1) { + for (let x = 0; x < TILE_ART_SIZE; x += 1) { + nextMatrix[TILE_ART_SIZE - 1 - x][y] = String(matrix[y]?.[x] || ".").charAt(0) || "."; + } + } + return buildRowsFromMatrix(nextMatrix); +} + +export function buildShapeFillMask(shapeKind, startX, startY, endX, endY) { + const minX = Math.max(0, Math.min(startX, endX)); + const maxX = Math.min(TILE_ART_SIZE - 1, Math.max(startX, endX)); + const minY = Math.max(0, Math.min(startY, endY)); + const maxY = Math.min(TILE_ART_SIZE - 1, Math.max(startY, endY)); + const fillMask = new Set(); + const shape = shapeKind === "circle" || shapeKind === "triangle" ? shapeKind : "rectangle"; + const width = Math.max(1, (maxX - minX) + 1); + const height = Math.max(1, (maxY - minY) + 1); + const centerX = minX + (width / 2); + const centerY = minY + (height / 2); + const denomX = Math.max(0.5, width / 2); + const denomY = Math.max(0.5, height / 2); + const triangleAx = minX + (width - 1) / 2; + const triangleAy = minY; + const triangleBx = minX; + const triangleBy = maxY; + const triangleCx = maxX; + const triangleCy = maxY; + const triangleDenominator = ((triangleBy - triangleCy) * (triangleAx - triangleCx)) + ((triangleCx - triangleBx) * (triangleAy - triangleCy)); + for (let y = minY; y <= maxY; y += 1) { + for (let x = minX; x <= maxX; x += 1) { + let include; + const sampleX = x + 0.5; + const sampleY = y + 0.5; + if (shape === "rectangle") { + include = true; + } else if (shape === "circle") { + const normX = (sampleX - centerX) / denomX; + const normY = (sampleY - centerY) / denomY; + include = (normX * normX) + (normY * normY) <= 1; + } else if (triangleDenominator !== 0) { + const a = (((triangleBy - triangleCy) * (sampleX - triangleCx)) + ((triangleCx - triangleBx) * (sampleY - triangleCy))) / triangleDenominator; + const b = (((triangleCy - triangleAy) * (sampleX - triangleCx)) + ((triangleAx - triangleCx) * (sampleY - triangleCy))) / triangleDenominator; + const c = 1 - a - b; + include = a >= 0 && b >= 0 && c >= 0; + } else { + include = x === Math.round(triangleAx) && y >= minY && y <= maxY; + } + if (include === true) { + fillMask.add(`${x}:${y}`); + } + } + } + return fillMask; +} + +export function buildOutlineMask(fillMask) { + const outlineMask = new Set(); + fillMask.forEach((key) => { + const [xText, yText] = String(key || "").split(":"); + const x = Number(xText); + const y = Number(yText); + const neighbors = [ + `${x - 1}:${y}`, + `${x + 1}:${y}`, + `${x}:${y - 1}`, + `${x}:${y + 1}`, + ]; + if (neighbors.some((neighbor) => !fillMask.has(neighbor))) { + outlineMask.add(key); + } + }); + return outlineMask; +} + +export function applyMaskToRows(baseRows, mask, symbol) { + const matrix = getRowsMatrix(baseRows); + mask.forEach((key) => { + const [xText, yText] = String(key || "").split(":"); + const x = Number(xText); + const y = Number(yText); + if (x < 0 || x >= TILE_ART_SIZE || y < 0 || y >= TILE_ART_SIZE) { + return; + } + matrix[y][x] = String(symbol || ".").charAt(0) || "."; + }); + return buildRowsFromMatrix(matrix); +} + +export function getLineRows(baseRows, startX, startY, endX, endY, symbol) { + const normalizedSymbol = String(symbol || ".").charAt(0) || "."; + const matrix = getRowsMatrix(baseRows); + let x0 = Math.max(0, Math.min(TILE_ART_SIZE - 1, Number(startX) || 0)); + let y0 = Math.max(0, Math.min(TILE_ART_SIZE - 1, Number(startY) || 0)); + const x1 = Math.max(0, Math.min(TILE_ART_SIZE - 1, Number(endX) || 0)); + const y1 = Math.max(0, Math.min(TILE_ART_SIZE - 1, Number(endY) || 0)); + const deltaX = Math.abs(x1 - x0); + const deltaY = Math.abs(y1 - y0); + const stepX = x0 < x1 ? 1 : -1; + const stepY = y0 < y1 ? 1 : -1; + let error = deltaX - deltaY; + while (true) { + matrix[y0][x0] = normalizedSymbol; + if (x0 === x1 && y0 === y1) { + break; + } + const nextError = error * 2; + if (nextError > -deltaY) { + error -= deltaY; + x0 += stepX; + } + if (nextError < deltaX) { + error += deltaX; + y0 += stepY; + } + } + return buildRowsFromMatrix(matrix); +} + +export function buildShapeOptionIconMarkup(shapeKind, variant, tone = "draw") { + const normalizedShape = shapeKind === "circle" || shapeKind === "triangle" ? shapeKind : "rectangle"; + const normalizedVariant = variant === "outline" || variant === "two-tone" ? variant : "fill"; + const normalizedTone = tone === "erase" ? "erase" : "draw"; + return "" + + `"; +} + +export function buildLineOptionIconMarkup(tone = "draw") { + const normalizedTone = tone === "erase" ? "erase" : "draw"; + return "" + + `"; +} + +export function buildCurrentShapeToolIconMarkup(state) { + if (state?.activeTool === "line" || String(state?.activeShapeMenuId || "").trim() === "line") { + return buildLineOptionIconMarkup("draw"); + } + return buildShapeOptionIconMarkup( + state?.activeShapeKind || "rectangle", + state?.activeShapeVariant || "outline", + "draw", + ); +} + +export function buildCurrentEraseToolIconMarkup(state) { + return buildShapeOptionIconMarkup( + state?.activeEraseKind || "rectangle", + "fill", + "erase", + ); +} + +export function buildTransformCategoryIconMarkup(kind) { + const normalizedKind = kind === "flip" ? "flip" : "rotate"; + return "" + + `"; +} + +export function buildTransformOptionIconMarkup(kind) { + const normalizedKind = [ + "rotate-cw", + "rotate-ccw", + "flip-h", + "flip-v", + ].includes(String(kind || "").trim()) ? String(kind || "").trim() : "rotate-cw"; + return "" + + `"; +} + +export function buildFramePreviewDataUrl(rows, scale = 10) { + return buildSpritePreviewDataUrl(buildRowsPreviewRecord(rows), scale); +} diff --git a/src/worldshaperStudio/tileArtEditorWindowController.ts b/src/worldshaperStudio/tileArtEditorWindowController.ts index 597f0ee..8087005 100644 --- a/src/worldshaperStudio/tileArtEditorWindowController.ts +++ b/src/worldshaperStudio/tileArtEditorWindowController.ts @@ -22,9 +22,40 @@ import { } from "./textTransferUtils"; import { clampFloatingWindowRect } from "./floatingWindowUtils"; import { appendContextMenuItems, menuItem, menuSubmenu, openContextMenuAtPoint } from "./contextMenuSchema"; +import { + applyMaskToRows, + buildCurrentEraseToolIconMarkup, + buildCurrentShapeToolIconMarkup, + buildFramePreviewDataUrl, + buildLineOptionIconMarkup, + buildOutlineMask, + buildShapeFillMask, + buildShapeOptionIconMarkup, + buildTransformCategoryIconMarkup, + buildTransformOptionIconMarkup, + cloneRows, + cloneValue, + EYEDROPPER_CURSOR, + flipRowsHorizontally, + flipRowsVertically, + formatOpacityValue, + formatPlaybackLabel, + getAlternatePaintSymbol, + getLineRows, + getWorkingCellSymbol, + normalizeOpacityValue, + normalizeTimelineRows, + normalizeWorkingFrames, + normalizeWorkingGraphicRecord, + paintWorkingRowsCell, + rotateRowsClockwise, + rotateRowsCounterClockwise, + shiftRows, + sortWorkingFrames, + TILE_ART_SIZE, +} from "./tileArtEditorHelpers"; const TILE_ART_WINDOW_KEY = "tileArtEditor"; -const TILE_ART_SIZE = 16; const GRID_CELL_SIZE = 21; const MIN_WIDTH = 452; const MIN_HEIGHT = 628; @@ -38,415 +69,11 @@ const TOOL_MENU_TAG_PREFIX = "tile-art-tool-menu:"; const SHORTCUT_HELP_TOOLTIP_TAG = "tile-art-shortcut-help"; const ANIMATION_SPEED_TOOLTIP_TAG = "tile-art-animation-speed"; const ANIMATION_PLAYBACK_TOOLTIP_TAG = "tile-art-animation-playback"; -const EYEDROPPER_CURSOR = `url("data:image/svg+xml,${encodeURIComponent( - ` - - - - `, -)}") 4 28, crosshair`; - -function cloneValue(value) { - if (typeof structuredClone === "function") { - return structuredClone(value); - } - return value == null ? value : JSON.parse(JSON.stringify(value)); -} function clampWindowRect(layerRect, left, top, width, height) { return clampFloatingWindowRect(layerRect, left, top, width, height, MIN_WIDTH, MIN_HEIGHT, DEFAULT_WIDTH, DEFAULT_HEIGHT); } -function normalizeRoleList(value) { - if (!Array.isArray(value)) { - return []; - } - return Array.from(new Set( - value - .map((entry) => String(entry || "").trim().toLowerCase()) - .filter((entry) => entry === "tile" || entry === "sprite"), - )); -} - -function normalizeTimelineRows(rows) { - return Array.from({ length: TILE_ART_SIZE }, (_entry, rowIndex) => { - const row = Array.isArray(rows) ? String(rows[rowIndex] || "") : ""; - return row.padEnd(TILE_ART_SIZE, ".").slice(0, TILE_ART_SIZE); - }); -} - -function normalizeWorkingFrames(record) { - const rawFrames = Array.isArray(record?.frames) ? record.frames.filter((entry) => entry && typeof entry === "object" && !Array.isArray(entry)) : []; - const normalizedFrames = rawFrames.map((entry, index) => ({ - ...cloneValue(entry), - id: String(entry.id || `frame_${index}`).trim() || `frame_${index}`, - enabled: entry.enabled !== false, - index: Number.isFinite(Number(entry.index)) ? Math.max(0, Math.floor(Number(entry.index))) : index, - rows: normalizeTimelineRows(entry.rows), - })); - if (normalizedFrames.length > 0) { - return normalizedFrames; - } - return [{ - id: "frame_0", - enabled: true, - index: 0, - rows: normalizeTimelineRows(record?.rows), - }]; -} - -function sortWorkingFrames(frames) { - return frames - .map((frame, sourceIndex) => ({ - frame, - sourceIndex, - sortIndex: Number.isFinite(Number(frame?.index)) ? Number(frame.index) : sourceIndex, - })) - .sort((left, right) => ( - left.sortIndex !== right.sortIndex - ? left.sortIndex - right.sortIndex - : left.sourceIndex - right.sourceIndex - )) - .map((entry) => entry.frame); -} - -function normalizeWorkingGraphicRecord(recordType, record) { - const source = cloneValue(record) || {}; - const roles = normalizeRoleList(source.roles); - const nextRoles = recordType === "tile" - ? Array.from(new Set([...roles, "tile"])) - : ( - recordType === "sprite" - ? Array.from(new Set([...roles, "sprite"])) - : roles.filter((entry) => entry !== "sprite") - ); - const frames = normalizeWorkingFrames(source).map((frame, index) => ({ - ...frame, - index, - })); - const requestedDefaultFrameId = String(source.defaultFrame || "").trim(); - const defaultFrameId = String( - frames.find((frame) => String(frame.id || "").trim() === requestedDefaultFrameId)?.id - || frames[0]?.id - || "frame_0", - ).trim() || "frame_0"; - const workingRows = normalizeTimelineRows( - Array.isArray(source.rows) && source.rows.length > 0 - ? source.rows - : (frames.find((frame) => String(frame.id || "").trim() === defaultFrameId)?.rows || frames[0]?.rows || []) - ); - return { - ...source, - id: String(source.id || `${recordType === "tile" ? "tile" : "sprite"}_${Date.now()}`).trim(), - name: typeof source.name === "string" ? source.name : "", - description: typeof source.description === "string" ? source.description : "", - width: TILE_ART_SIZE, - height: TILE_ART_SIZE, - pixelScale: Math.max(1, Number(source.pixelScale) || 2), - opacity: Number.isFinite(Number(source.opacity)) ? Math.max(0, Math.min(1, Number(source.opacity))) : 1, - tags: normalizeEditorTags(source.tags), - roles: nextRoles, - tileSymbol: nextRoles.includes("tile") - ? (String(source.tileSymbol ?? source.symbol ?? source.id ?? "T").trim().charAt(0) || "T") - : "", - defaultFrame: defaultFrameId, - speed: Number.isFinite(Number(source.speed)) && Number(source.speed) >= 0 ? Number(source.speed) : 0, - playback: normalizeImagePlayback(source.playback), - frames, - rows: workingRows, - }; -} - -function normalizeOpacityValue(value, fallback = 1) { - const parsed = Number(value); - if (!Number.isFinite(parsed)) { - return fallback; - } - return Math.max(0, Math.min(1, parsed)); -} - -function formatOpacityValue(value) { - const normalized = normalizeOpacityValue(value, 1); - return normalized.toFixed(2).replace(/\.?0+$/, ""); -} - -function buildRowsPreviewRecord(rows) { - return { - width: TILE_ART_SIZE, - height: TILE_ART_SIZE, - rows: cloneRows(rows), - }; -} - -function formatPlaybackLabel(value) { - const normalized = normalizeImagePlayback(value); - if (normalized === "rewind") { - return "Rewind"; - } - if (normalized === "stop") { - return "Stop"; - } - return "Normal"; -} - -function cloneRows(rows) { - return Array.isArray(rows) - ? rows.map((row) => String(row || "").padEnd(TILE_ART_SIZE, ".").slice(0, TILE_ART_SIZE)) - : Array.from({ length: TILE_ART_SIZE }, () => ".".repeat(TILE_ART_SIZE)); -} - -function getWorkingCellSymbol(record, x, y) { - const rows = Array.isArray(record?.rows) ? record.rows : []; - const row = String(rows[y] || "").padEnd(TILE_ART_SIZE, ".").slice(0, TILE_ART_SIZE); - return String(row.charAt(x) || ".").charAt(0) || "."; -} - -function paintWorkingRowsCell(rows, x, y, symbol) { - const nextRows = cloneRows(rows); - const nextSymbol = String(symbol || ".").charAt(0) || "."; - const targetRow = String(nextRows[y] || "").padEnd(TILE_ART_SIZE, ".").slice(0, TILE_ART_SIZE); - nextRows[y] = `${targetRow.slice(0, x)}${nextSymbol}${targetRow.slice(x + 1)}`; - return nextRows; -} - -function getRowsMatrix(rows) { - return cloneRows(rows).map((row) => Array.from(row)); -} - -function buildRowsFromMatrix(matrix) { - return Array.from({ length: TILE_ART_SIZE }, (_entry, rowIndex) => { - const sourceRow = Array.isArray(matrix?.[rowIndex]) ? matrix[rowIndex] : []; - return sourceRow.join("").padEnd(TILE_ART_SIZE, ".").slice(0, TILE_ART_SIZE); - }); -} - -function getAlternatePaintSymbol(record, preferredSymbol) { - const normalizedPreferred = String(preferredSymbol || "").charAt(0) || "."; - const palette = getSpritePalette(record || undefined); - const nextSymbol = Object.keys(palette) - .map((symbol) => String(symbol || "").charAt(0)) - .find((symbol) => symbol && symbol !== normalizedPreferred && symbol !== "."); - return nextSymbol || "."; -} - -function shiftRows(rows, offsetX, offsetY) { - const nextRows = Array.from({ length: TILE_ART_SIZE }, () => ".".repeat(TILE_ART_SIZE).split("")); - const sourceRows = cloneRows(rows); - for (let y = 0; y < TILE_ART_SIZE; y += 1) { - const row = sourceRows[y] || ".".repeat(TILE_ART_SIZE); - for (let x = 0; x < TILE_ART_SIZE; x += 1) { - const nextX = x + offsetX; - const nextY = y + offsetY; - if (nextX < 0 || nextX >= TILE_ART_SIZE || nextY < 0 || nextY >= TILE_ART_SIZE) { - continue; - } - nextRows[nextY][nextX] = String(row.charAt(x) || ".").charAt(0) || "."; - } - } - return buildRowsFromMatrix(nextRows); -} - -function flipRowsHorizontally(rows) { - return cloneRows(rows).map((row) => row.split("").reverse().join("")); -} - -function flipRowsVertically(rows) { - return cloneRows(rows).slice().reverse(); -} - -function rotateRowsClockwise(rows) { - const matrix = getRowsMatrix(rows); - const nextMatrix = Array.from({ length: TILE_ART_SIZE }, () => Array.from({ length: TILE_ART_SIZE }, () => ".")); - for (let y = 0; y < TILE_ART_SIZE; y += 1) { - for (let x = 0; x < TILE_ART_SIZE; x += 1) { - nextMatrix[x][TILE_ART_SIZE - 1 - y] = String(matrix[y]?.[x] || ".").charAt(0) || "."; - } - } - return buildRowsFromMatrix(nextMatrix); -} - -function rotateRowsCounterClockwise(rows) { - const matrix = getRowsMatrix(rows); - const nextMatrix = Array.from({ length: TILE_ART_SIZE }, () => Array.from({ length: TILE_ART_SIZE }, () => ".")); - for (let y = 0; y < TILE_ART_SIZE; y += 1) { - for (let x = 0; x < TILE_ART_SIZE; x += 1) { - nextMatrix[TILE_ART_SIZE - 1 - x][y] = String(matrix[y]?.[x] || ".").charAt(0) || "."; - } - } - return buildRowsFromMatrix(nextMatrix); -} - -function buildShapeFillMask(shapeKind, startX, startY, endX, endY) { - const minX = Math.max(0, Math.min(startX, endX)); - const maxX = Math.min(TILE_ART_SIZE - 1, Math.max(startX, endX)); - const minY = Math.max(0, Math.min(startY, endY)); - const maxY = Math.min(TILE_ART_SIZE - 1, Math.max(startY, endY)); - const fillMask = new Set(); - const shape = shapeKind === "circle" || shapeKind === "triangle" ? shapeKind : "rectangle"; - const width = Math.max(1, (maxX - minX) + 1); - const height = Math.max(1, (maxY - minY) + 1); - const centerX = minX + (width / 2); - const centerY = minY + (height / 2); - const denomX = Math.max(0.5, width / 2); - const denomY = Math.max(0.5, height / 2); - const triangleAx = minX + (width - 1) / 2; - const triangleAy = minY; - const triangleBx = minX; - const triangleBy = maxY; - const triangleCx = maxX; - const triangleCy = maxY; - const triangleDenominator = ((triangleBy - triangleCy) * (triangleAx - triangleCx)) + ((triangleCx - triangleBx) * (triangleAy - triangleCy)); - for (let y = minY; y <= maxY; y += 1) { - for (let x = minX; x <= maxX; x += 1) { - let include; - const sampleX = x + 0.5; - const sampleY = y + 0.5; - if (shape === "rectangle") { - include = true; - } else if (shape === "circle") { - const normX = (sampleX - centerX) / denomX; - const normY = (sampleY - centerY) / denomY; - include = (normX * normX) + (normY * normY) <= 1; - } else if (triangleDenominator !== 0) { - const a = (((triangleBy - triangleCy) * (sampleX - triangleCx)) + ((triangleCx - triangleBx) * (sampleY - triangleCy))) / triangleDenominator; - const b = (((triangleCy - triangleAy) * (sampleX - triangleCx)) + ((triangleAx - triangleCx) * (sampleY - triangleCy))) / triangleDenominator; - const c = 1 - a - b; - include = a >= 0 && b >= 0 && c >= 0; - } else { - include = x === Math.round(triangleAx) && y >= minY && y <= maxY; - } - if (include === true) { - fillMask.add(`${x}:${y}`); - } - } - } - return fillMask; -} - -function buildOutlineMask(fillMask) { - const outlineMask = new Set(); - fillMask.forEach((key) => { - const [xText, yText] = String(key || "").split(":"); - const x = Number(xText); - const y = Number(yText); - const neighbors = [ - `${x - 1}:${y}`, - `${x + 1}:${y}`, - `${x}:${y - 1}`, - `${x}:${y + 1}`, - ]; - if (neighbors.some((neighbor) => !fillMask.has(neighbor))) { - outlineMask.add(key); - } - }); - return outlineMask; -} - -function applyMaskToRows(baseRows, mask, symbol) { - const matrix = getRowsMatrix(baseRows); - mask.forEach((key) => { - const [xText, yText] = String(key || "").split(":"); - const x = Number(xText); - const y = Number(yText); - if (x < 0 || x >= TILE_ART_SIZE || y < 0 || y >= TILE_ART_SIZE) { - return; - } - matrix[y][x] = String(symbol || ".").charAt(0) || "."; - }); - return buildRowsFromMatrix(matrix); -} - -function getLineRows(baseRows, startX, startY, endX, endY, symbol) { - const normalizedSymbol = String(symbol || ".").charAt(0) || "."; - const matrix = getRowsMatrix(baseRows); - let x0 = Math.max(0, Math.min(TILE_ART_SIZE - 1, Number(startX) || 0)); - let y0 = Math.max(0, Math.min(TILE_ART_SIZE - 1, Number(startY) || 0)); - const x1 = Math.max(0, Math.min(TILE_ART_SIZE - 1, Number(endX) || 0)); - const y1 = Math.max(0, Math.min(TILE_ART_SIZE - 1, Number(endY) || 0)); - const deltaX = Math.abs(x1 - x0); - const deltaY = Math.abs(y1 - y0); - const stepX = x0 < x1 ? 1 : -1; - const stepY = y0 < y1 ? 1 : -1; - let error = deltaX - deltaY; - while (true) { - matrix[y0][x0] = normalizedSymbol; - if (x0 === x1 && y0 === y1) { - break; - } - const nextError = error * 2; - if (nextError > -deltaY) { - error -= deltaY; - x0 += stepX; - } - if (nextError < deltaX) { - error += deltaX; - y0 += stepY; - } - } - return buildRowsFromMatrix(matrix); -} - -function buildShapeOptionIconMarkup(shapeKind, variant, tone = "draw") { - const normalizedShape = shapeKind === "circle" || shapeKind === "triangle" ? shapeKind : "rectangle"; - const normalizedVariant = variant === "outline" || variant === "two-tone" ? variant : "fill"; - const normalizedTone = tone === "erase" ? "erase" : "draw"; - return "" - + `"; -} - -function buildLineOptionIconMarkup(tone = "draw") { - const normalizedTone = tone === "erase" ? "erase" : "draw"; - return "" - + `"; -} - -function buildCurrentShapeToolIconMarkup(state) { - if (state?.activeTool === "line" || String(state?.activeShapeMenuId || "").trim() === "line") { - return buildLineOptionIconMarkup("draw"); - } - return buildShapeOptionIconMarkup( - state?.activeShapeKind || "rectangle", - state?.activeShapeVariant || "outline", - "draw", - ); -} - -function buildCurrentEraseToolIconMarkup(state) { - return buildShapeOptionIconMarkup( - state?.activeEraseKind || "rectangle", - "fill", - "erase", - ); -} - -function buildTransformCategoryIconMarkup(kind) { - const normalizedKind = kind === "flip" ? "flip" : "rotate"; - return "" - + `"; -} - -function buildTransformOptionIconMarkup(kind) { - const normalizedKind = [ - "rotate-cw", - "rotate-ccw", - "flip-h", - "flip-v", - ].includes(String(kind || "").trim()) ? String(kind || "").trim() : "rotate-cw"; - return "" - + `"; -} - export function createTileArtEditorWindowController(scope) { let initialized = false; const uiScope = scope.uiScope || scope; @@ -833,7 +460,7 @@ export function createTileArtEditorWindowController(scope) { } const currentFrame = playbackFrames.find((frame) => String(frame.id || "").trim() === String(state.animationPreviewFrameId || "").trim()) || playbackFrames[0]; state.animationPreviewFrameId = String(currentFrame?.id || "").trim(); - const previewUrl = buildSpritePreviewDataUrl(buildRowsPreviewRecord(currentFrame?.rows), 10); + const previewUrl = buildFramePreviewDataUrl(currentFrame?.rows, 10); if (previewUrl) { state.animationPreviewImageEl.src = previewUrl; state.animationPreviewImageEl.classList.remove("hidden"); diff --git a/src/worldshaperStudio/windowing.ts b/src/worldshaperStudio/windowing.ts index 4a0b176..6038ed8 100644 --- a/src/worldshaperStudio/windowing.ts +++ b/src/worldshaperStudio/windowing.ts @@ -1,210 +1 @@ -export type PopupBounds = { - left: number; - top: number; - width: number; - height: number; -}; - -export const WORLDSHAPER_STUDIO_WINDOW_NAME = "worldshaper-studio"; -export const WORLDSHAPER_STUDIO_BOUNDS_STORAGE_KEY = "worldshaper:studio-window-bounds"; -export const WORLDSHAPER_HEIGHT_VIEWER_WINDOW_NAME = "worldshaper-height-viewer"; -export const WORLDSHAPER_HEIGHT_VIEWER_BOUNDS_STORAGE_KEY = "worldshaper:height-viewer-window-bounds"; - -export function buildWorldshaperStudioUrl(mapId: string, hostWindow: Window = window, options?: { worldId?: string }): string { - const popupUrl = new URL(`${import.meta.env.BASE_URL}worldshaper-studio.html`, hostWindow.location.origin); - const normalizedMapId = String(mapId || "").trim(); - const normalizedWorldId = String(options?.worldId || "").trim(); - if (normalizedMapId) { - popupUrl.searchParams.set("mapId", normalizedMapId); - } - if (normalizedWorldId) { - popupUrl.searchParams.set("worldId", normalizedWorldId); - } - return popupUrl.toString(); -} - -export function buildWorldshaperHeightViewerUrl(mapId: string, token = "", hostWindow: Window = window): string { - const popupUrl = new URL(`${import.meta.env.BASE_URL}worldshaper-height-viewer.html`, hostWindow.location.origin); - const normalizedMapId = String(mapId || "").trim(); - const normalizedToken = String(token || "").trim(); - if (normalizedMapId) { - popupUrl.searchParams.set("mapId", normalizedMapId); - } - if (normalizedToken) { - popupUrl.searchParams.set("token", normalizedToken); - } - return popupUrl.toString(); -} - -export function getCenteredWorldshaperStudioBounds(hostWindow: Window = window): PopupBounds { - const width = 1360; - const height = 900; - const hostScreenX = Number.isFinite(hostWindow.screenX) ? hostWindow.screenX : 0; - const hostScreenY = Number.isFinite(hostWindow.screenY) ? hostWindow.screenY : 0; - const hostOuterWidth = Number.isFinite(hostWindow.outerWidth) && hostWindow.outerWidth > 0 - ? hostWindow.outerWidth - : hostWindow.innerWidth; - const hostOuterHeight = Number.isFinite(hostWindow.outerHeight) && hostWindow.outerHeight > 0 - ? hostWindow.outerHeight - : hostWindow.innerHeight; - const left = Math.max(0, Math.round(hostScreenX + (hostOuterWidth - width) / 2)); - const top = Math.max(0, Math.round(hostScreenY + (hostOuterHeight - height) / 2)); - return { left, top, width, height }; -} - -export function getCenteredWorldshaperHeightViewerBounds(hostWindow: Window = window): PopupBounds { - const width = 1280; - const height = 820; - const hostScreenX = Number.isFinite(hostWindow.screenX) ? hostWindow.screenX : 0; - const hostScreenY = Number.isFinite(hostWindow.screenY) ? hostWindow.screenY : 0; - const hostOuterWidth = Number.isFinite(hostWindow.outerWidth) && hostWindow.outerWidth > 0 - ? hostWindow.outerWidth - : hostWindow.innerWidth; - const hostOuterHeight = Number.isFinite(hostWindow.outerHeight) && hostWindow.outerHeight > 0 - ? hostWindow.outerHeight - : hostWindow.innerHeight; - const left = Math.max(0, Math.round(hostScreenX + (hostOuterWidth - width) / 2)); - const top = Math.max(0, Math.round(hostScreenY + (hostOuterHeight - height) / 2)); - return { left, top, width, height }; -} - -export function readWorldshaperStudioBounds(hostWindow: Window = window): PopupBounds { - try { - const raw = hostWindow.localStorage.getItem(WORLDSHAPER_STUDIO_BOUNDS_STORAGE_KEY); - if (!raw) { - return getCenteredWorldshaperStudioBounds(hostWindow); - } - const parsed = JSON.parse(raw) as Partial; - const width = Math.max(640, Number(parsed.width) || 0); - const height = Math.max(480, Number(parsed.height) || 0); - const left = Math.max(0, Number(parsed.left) || 0); - const top = Math.max(0, Number(parsed.top) || 0); - if (!Number.isFinite(width) || !Number.isFinite(height) || !Number.isFinite(left) || !Number.isFinite(top)) { - return getCenteredWorldshaperStudioBounds(hostWindow); - } - return { left, top, width, height }; - } catch { - return getCenteredWorldshaperStudioBounds(hostWindow); - } -} - -export function readWorldshaperHeightViewerBounds(hostWindow: Window = window): PopupBounds { - try { - const raw = hostWindow.localStorage.getItem(WORLDSHAPER_HEIGHT_VIEWER_BOUNDS_STORAGE_KEY); - if (!raw) { - return getCenteredWorldshaperHeightViewerBounds(hostWindow); - } - const parsed = JSON.parse(raw) as Partial; - const width = Math.max(640, Number(parsed.width) || 0); - const height = Math.max(480, Number(parsed.height) || 0); - const left = Math.max(0, Number(parsed.left) || 0); - const top = Math.max(0, Number(parsed.top) || 0); - if (!Number.isFinite(width) || !Number.isFinite(height) || !Number.isFinite(left) || !Number.isFinite(top)) { - return getCenteredWorldshaperHeightViewerBounds(hostWindow); - } - return { left, top, width, height }; - } catch { - return getCenteredWorldshaperHeightViewerBounds(hostWindow); - } -} - -export function persistWorldshaperStudioBounds(sourceWindow: Window = window): void { - if (sourceWindow.closed) { - return; - } - try { - const width = Math.max(640, Math.round(Number(sourceWindow.outerWidth) || 0)); - const height = Math.max(480, Math.round(Number(sourceWindow.outerHeight) || 0)); - const left = Math.max(0, Math.round(Number(sourceWindow.screenX) || 0)); - const top = Math.max(0, Math.round(Number(sourceWindow.screenY) || 0)); - sourceWindow.localStorage.setItem( - WORLDSHAPER_STUDIO_BOUNDS_STORAGE_KEY, - JSON.stringify({ left, top, width, height }), - ); - } catch { - // Ignore storage and same-origin failures. - } -} - -export function persistWorldshaperHeightViewerBounds(sourceWindow: Window = window): void { - if (sourceWindow.closed) { - return; - } - try { - const width = Math.max(640, Math.round(Number(sourceWindow.outerWidth) || 0)); - const height = Math.max(480, Math.round(Number(sourceWindow.outerHeight) || 0)); - const left = Math.max(0, Math.round(Number(sourceWindow.screenX) || 0)); - const top = Math.max(0, Math.round(Number(sourceWindow.screenY) || 0)); - sourceWindow.localStorage.setItem( - WORLDSHAPER_HEIGHT_VIEWER_BOUNDS_STORAGE_KEY, - JSON.stringify({ left, top, width, height }), - ); - } catch { - // Ignore storage and same-origin failures. - } -} - -export function openWorldshaperStudioWindow( - mapId: string, - hostWindow: Window = window, - options?: { worldId?: string }, -): Window | null { - const popupUrl = buildWorldshaperStudioUrl(mapId, hostWindow, options); - const initialBounds = readWorldshaperStudioBounds(hostWindow); - const popupFeatures = [ - "popup=yes", - "resizable=yes", - "scrollbars=no", - "width=" + initialBounds.width, - "height=" + initialBounds.height, - "left=" + initialBounds.left, - "top=" + initialBounds.top, - ].join(","); - - const popup = hostWindow.open(popupUrl, WORLDSHAPER_STUDIO_WINDOW_NAME, popupFeatures); - if (!popup) { - return null; - } - - try { - popup.moveTo(initialBounds.left, initialBounds.top); - popup.resizeTo(initialBounds.width, initialBounds.height); - } catch { - // Ignore browser restrictions. - } - - popup.location.href = popupUrl; - popup.focus(); - return popup; -} - -export function openWorldshaperHeightViewerWindow(mapId: string, token = "", hostWindow: Window = window): Window | null { - const popupUrl = buildWorldshaperHeightViewerUrl(mapId, token, hostWindow); - const initialBounds = readWorldshaperHeightViewerBounds(hostWindow); - const popupFeatures = [ - "popup=yes", - "resizable=yes", - "scrollbars=no", - "width=" + initialBounds.width, - "height=" + initialBounds.height, - "left=" + initialBounds.left, - "top=" + initialBounds.top, - ].join(","); - - const popup = hostWindow.open(popupUrl, WORLDSHAPER_HEIGHT_VIEWER_WINDOW_NAME, popupFeatures); - if (!popup) { - return null; - } - - try { - popup.moveTo(initialBounds.left, initialBounds.top); - popup.resizeTo(initialBounds.width, initialBounds.height); - } catch { - // Ignore browser restrictions. - } - - popup.location.href = popupUrl; - popup.focus(); - return popup; -} - +export * from "../shared/windowing"; diff --git a/src/worldshaperStudio/worldChunkRuntimeHelpers.ts b/src/worldshaperStudio/worldChunkRuntimeHelpers.ts new file mode 100644 index 0000000..857d1b4 --- /dev/null +++ b/src/worldshaperStudio/worldChunkRuntimeHelpers.ts @@ -0,0 +1,602 @@ +// @ts-nocheck + +export function createFilledRows(width, height, fillChar) { + return Array.from({ length: Math.max(1, Number(height) || 1) }, () => String(fillChar || " ").repeat(Math.max(1, Number(width) || 1))); +} + +function writeRowSegment(rows, y, x, segment) { + if (!Array.isArray(rows) || !segment) { + return; + } + const targetY = Math.floor(Number(y) || 0); + if (targetY < 0 || targetY >= rows.length) { + return; + } + const safeX = Math.max(0, Math.floor(Number(x) || 0)); + const sourceRow = String(rows[targetY] || ""); + const paddedRow = sourceRow.length >= safeX + ? sourceRow + : (sourceRow + " ".repeat(Math.max(0, safeX - sourceRow.length))); + const before = paddedRow.slice(0, safeX); + const afterStart = safeX + segment.length; + const after = afterStart < paddedRow.length ? paddedRow.slice(afterStart) : ""; + rows[targetY] = before + segment + after; +} + +export function composeWorldRoomLayers(chunks, chunkWidth, chunkHeight, originChunkX, originChunkY, worldWidth, worldHeight) { + const layerMap = new Map(); + (Array.isArray(chunks) ? chunks : []).forEach((chunk) => { + const baseChunkX = Math.floor(Number(chunk?.chunkX) || 0); + const baseChunkY = Math.floor(Number(chunk?.chunkY) || 0); + const offsetX = (baseChunkX - originChunkX) * chunkWidth; + const offsetY = (baseChunkY - originChunkY) * chunkHeight; + const rawLayers = Array.isArray(chunk?.roomLayers) ? chunk.roomLayers : []; + rawLayers.forEach((rawLayer) => { + const layerNumber = Number(rawLayer?.layer) || 0; + const fillChar = layerNumber === 0 ? "." : " "; + if (!layerMap.has(layerNumber)) { + layerMap.set(layerNumber, { + layer: layerNumber, + name: typeof rawLayer?.name === "string" && String(rawLayer.name).trim() ? String(rawLayer.name).trim() : undefined, + rows: createFilledRows(worldWidth, worldHeight, fillChar), + instanceIds: [], + }); + } + const targetLayer = layerMap.get(layerNumber); + const sourceRows = Array.isArray(rawLayer?.rows) ? rawLayer.rows.map((row) => String(row || "")) : []; + sourceRows.forEach((row, localY) => { + const targetY = offsetY + localY; + if (targetY < 0 || targetY >= targetLayer.rows.length) { + return; + } + const maxWidth = Math.max(0, worldWidth - offsetX); + writeRowSegment(targetLayer.rows, targetY, offsetX, row.slice(0, maxWidth)); + }); + const sourceInstanceIds = Array.isArray(rawLayer?.instanceIds) + ? rawLayer.instanceIds.map((entry) => String(entry || "").trim()).filter(Boolean) + : []; + targetLayer.instanceIds = Array.from(new Set([...(targetLayer.instanceIds || []), ...sourceInstanceIds])); + }); + }); + if (!layerMap.has(0)) { + layerMap.set(0, { + layer: 0, + rows: createFilledRows(worldWidth, worldHeight, "."), + instanceIds: [], + }); + } + return Array.from(layerMap.values()).sort((a, b) => (Number(a.layer) || 0) - (Number(b.layer) || 0)); +} + +export function composeWorldHeightLayers(chunks, chunkWidth, chunkHeight, originChunkX, originChunkY) { + const patches = []; + (Array.isArray(chunks) ? chunks : []).forEach((chunk) => { + const baseChunkX = Math.floor(Number(chunk?.chunkX) || 0); + const baseChunkY = Math.floor(Number(chunk?.chunkY) || 0); + const offsetX = (baseChunkX - originChunkX) * chunkWidth; + const offsetY = (baseChunkY - originChunkY) * chunkHeight; + const rawHeightLayers = Array.isArray(chunk?.heightLayers) ? chunk.heightLayers : []; + rawHeightLayers.forEach((entry, index) => { + const fallbackId = `height_${baseChunkX}_${baseChunkY}_${index + 1}`; + patches.push({ + id: String(entry?.id || fallbackId).trim() || fallbackId, + name: typeof entry?.name === "string" && String(entry.name).trim() ? String(entry.name).trim() : undefined, + z: Math.max(1, Math.floor(Number(entry?.z) || 1)), + x: offsetX + Math.max(0, Number(entry?.x) || 0), + y: offsetY + Math.max(0, Number(entry?.y) || 0), + rows: Array.isArray(entry?.rows) ? entry.rows.map((row) => String(row || "")) : [], + }); + }); + }); + return patches.sort((a, b) => { + if (a.z !== b.z) { + return a.z - b.z; + } + return String(a.name || a.id).localeCompare(String(b.name || b.id)); + }); +} + +export function buildWorldLayerMetadata(chunks) { + const layerMap = new Map(); + (Array.isArray(chunks) ? chunks : []).forEach((chunk) => { + const rawLayers = Array.isArray(chunk?.roomLayers) ? chunk.roomLayers : []; + rawLayers.forEach((rawLayer) => { + const layerNumber = Number(rawLayer?.layer) || 0; + if (layerMap.has(layerNumber)) { + return; + } + layerMap.set(layerNumber, { + layer: layerNumber, + name: typeof rawLayer?.name === "string" && String(rawLayer.name).trim() ? String(rawLayer.name).trim() : undefined, + rows: [], + instanceIds: Array.isArray(rawLayer?.instanceIds) ? rawLayer.instanceIds.map((entry) => String(entry || "").trim()).filter(Boolean) : [], + }); + }); + }); + if (!layerMap.has(0)) { + layerMap.set(0, { + layer: 0, + rows: [], + instanceIds: [], + }); + } + if (!Array.from(layerMap.keys()).some((layerNumber) => layerNumber > 0)) { + layerMap.set(1, { + layer: 1, + rows: [], + instanceIds: [], + }); + } + return Array.from(layerMap.values()).sort((a, b) => (Number(a.layer) || 0) - (Number(b.layer) || 0)); +} + +export function sliceNormalizedRows(rows, startX, startY, width, height, fillChar) { + return Array.from({ length: Math.max(1, Number(height) || 1) }, (_, rowOffset) => { + const sourceRow = String((Array.isArray(rows) ? rows[startY + rowOffset] : "") || ""); + const paddedRow = sourceRow.length >= startX + width + ? sourceRow + : sourceRow + String(fillChar || " ").repeat(Math.max(0, (startX + width) - sourceRow.length)); + return paddedRow.slice(startX, startX + width); + }); +} + +export function buildChunkHeightLayersFromDocument({ mapDocument, cloneHeightLayers, baseTileX, baseTileY, chunkWidth, chunkHeight }) { + return (Array.isArray(mapDocument.heightLayers) ? cloneHeightLayers(mapDocument.heightLayers) : []) + .map((entry) => { + const patchX = Math.max(0, Number(entry?.x) || 0); + const patchY = Math.max(0, Number(entry?.y) || 0); + const rows = Array.isArray(entry?.rows) ? entry.rows.map((row) => String(row || "")) : []; + const patchWidth = rows.reduce((max, row) => Math.max(max, row.length), 0); + const patchHeight = rows.length; + const patchRight = patchX + patchWidth; + const patchBottom = patchY + patchHeight; + const chunkRight = baseTileX + chunkWidth; + const chunkBottom = baseTileY + chunkHeight; + const overlapLeft = Math.max(baseTileX, patchX); + const overlapTop = Math.max(baseTileY, patchY); + const overlapRight = Math.min(chunkRight, patchRight); + const overlapBottom = Math.min(chunkBottom, patchBottom); + if (overlapRight <= overlapLeft || overlapBottom <= overlapTop) { + return null; + } + const localRows = []; + for (let y = overlapTop; y < overlapBottom; y += 1) { + const sourceRow = String(rows[y - patchY] || ""); + localRows.push(sourceRow.slice(overlapLeft - patchX, overlapRight - patchX).replace(/\s+$/g, "")); + } + return { + id: String(entry?.id || "").trim(), + name: typeof entry?.name === "string" && entry.name.trim() ? entry.name.trim() : undefined, + z: Math.max(1, Number(entry?.z) || 1), + x: overlapLeft - baseTileX, + y: overlapTop - baseTileY, + rows: localRows, + }; + }) + .filter((entry) => entry && entry.id); +} + +export function buildChunkInstancesFromDocument({ mapDocument, cloneValue, baseTileX, baseTileY, chunkWidth, chunkHeight, tileOffsetX, tileOffsetY }) { + const chunkInstances = cloneValue(mapDocument.npcOverlays) + .filter((npc) => { + const localX = Math.floor(Number(npc?.x)); + const localY = Math.floor(Number(npc?.y)); + return Number.isFinite(localX) + && Number.isFinite(localY) + && localX >= baseTileX + && localX < baseTileX + chunkWidth + && localY >= baseTileY + && localY < baseTileY + chunkHeight; + }) + .map((npc) => ({ + id: String(npc.id || "").trim(), + templateId: String(npc?.record?.templateId || "").trim(), + layer: Number(npc.layer) || 0, + x: Math.floor(Number(npc.x) || 0) - baseTileX, + y: Math.floor(Number(npc.y) || 0) - baseTileY, + record: { + ...cloneValue(npc.record || {}), + id: String(npc.id || "").trim(), + layer: Number(npc.layer) || 0, + templateId: String(npc?.record?.templateId || "").trim(), + name: String(npc.name || npc?.record?.name || ""), + entityType: String(npc?.record?.entityType || npc?.entityType || "friendly"), + faction: String(npc.faction || npc?.record?.faction || ""), + spriteId: String(npc.spriteId || npc?.record?.spriteId || ""), + dialogueId: String(npc.dialogueId || npc?.record?.dialogueId || ""), + description: String(npc.description || npc?.record?.description || ""), + tags: cloneValue(npc?.record?.tags) || [], + enabled: typeof npc?.record?.enabled === "boolean" ? npc.record.enabled : true, + position: { + x: Math.floor(Number(npc.x) || 0) + tileOffsetX, + y: Math.floor(Number(npc.y) || 0) + tileOffsetY, + }, + }, + })) + .filter((entry) => entry.id); + const npcIdsByLayer = new Map(); + chunkInstances.forEach((entry) => { + const layerNumber = Number(entry.layer) || 0; + if (!npcIdsByLayer.has(layerNumber)) { + npcIdsByLayer.set(layerNumber, []); + } + npcIdsByLayer.get(layerNumber).push(entry.id); + }); + return { + chunkInstances, + npcIdsByLayer, + }; +} + +export function normalizeWorldChunkRows(rows, width, height, fillChar) { + const safeWidth = Math.max(1, Math.floor(Number(width) || 1)); + const safeHeight = Math.max(1, Math.floor(Number(height) || 1)); + return Array.from({ length: safeHeight }, (_entry, rowIndex) => { + const sourceRow = String((Array.isArray(rows) ? rows[rowIndex] : "") || ""); + return sourceRow.length >= safeWidth + ? sourceRow.slice(0, safeWidth) + : (sourceRow + String(fillChar || " ").repeat(Math.max(0, safeWidth - sourceRow.length))); + }); +} + +export function cloneWorldChunkHeightLayers(source) { + return (Array.isArray(source) ? source : []) + .map((entry, index) => ({ + id: String(entry?.id || `height_patch_${index + 1}`).trim() || `height_patch_${index + 1}`, + name: typeof entry?.name === "string" && entry.name.trim() ? entry.name.trim() : undefined, + z: Math.max(1, Math.floor(Number(entry?.z) || 1)), + x: Math.max(0, Math.floor(Number(entry?.x) || 0)), + y: Math.max(0, Math.floor(Number(entry?.y) || 0)), + rows: Array.isArray(entry?.rows) ? entry.rows.map((row) => String(row || "")) : [], + })) + .filter((entry) => entry.id); +} + +export function buildWorldChunkLayerInstanceIds(roomLayers, instances, width, height) { + const safeWidth = Math.max(1, Math.floor(Number(width) || 1)); + const safeHeight = Math.max(1, Math.floor(Number(height) || 1)); + const nextLayers = new Map(); + (Array.isArray(roomLayers) ? roomLayers : []).forEach((layer) => { + const layerNumber = Math.max(0, Math.floor(Number(layer?.layer) || 0)); + nextLayers.set(layerNumber, { + layer: layerNumber, + name: typeof layer?.name === "string" && layer.name.trim() ? layer.name.trim() : undefined, + rows: normalizeWorldChunkRows(layer?.rows, safeWidth, safeHeight, layerNumber === 0 ? "." : " "), + instanceIds: [], + }); + }); + if (!nextLayers.has(0)) { + nextLayers.set(0, { + layer: 0, + rows: normalizeWorldChunkRows([], safeWidth, safeHeight, "."), + instanceIds: [], + }); + } + if (!Array.from(nextLayers.keys()).some((layerNumber) => layerNumber > 0)) { + nextLayers.set(1, { + layer: 1, + rows: normalizeWorldChunkRows([], safeWidth, safeHeight, " "), + instanceIds: [], + }); + } + (Array.isArray(instances) ? instances : []).forEach((entry) => { + const layerNumber = Math.max(0, Math.floor(Number(entry?.layer) || 0)); + const instanceId = String(entry?.id || "").trim(); + if (!instanceId) { + return; + } + if (!nextLayers.has(layerNumber)) { + nextLayers.set(layerNumber, { + layer: layerNumber, + rows: normalizeWorldChunkRows([], safeWidth, safeHeight, layerNumber === 0 ? "." : " "), + instanceIds: [], + }); + } + nextLayers.get(layerNumber).instanceIds.push(instanceId); + }); + return Array.from(nextLayers.values()) + .map((entry) => ({ + ...entry, + instanceIds: Array.from(new Set((Array.isArray(entry.instanceIds) ? entry.instanceIds : []).map((id) => String(id || "").trim()).filter(Boolean))), + })) + .sort((left, right) => (Number(left.layer) || 0) - (Number(right.layer) || 0)); +} + +export function normalizeWorldChunkInstances({ sourceInstances, chunkX, chunkY, width, height, options, cloneValue, runtimeUniqueId }) { + const config = options && typeof options === "object" ? options : {}; + const duplicateIds = config.duplicateIds === true; + const safeChunkX = Math.floor(Number(chunkX) || 0); + const safeChunkY = Math.floor(Number(chunkY) || 0); + const safeWidth = Math.max(1, Math.floor(Number(width) || 1)); + const safeHeight = Math.max(1, Math.floor(Number(height) || 1)); + return (Array.isArray(sourceInstances) ? sourceInstances : []) + .map((entry) => { + const record = entry?.record && typeof entry.record === "object" && !Array.isArray(entry.record) + ? cloneValue(entry.record) + : {}; + const nextId = duplicateIds + ? runtimeUniqueId() + : (String(entry?.id || record?.id || runtimeUniqueId()).trim() || runtimeUniqueId()); + const nextLayer = Math.max(0, Math.floor(Number(entry?.layer ?? record?.layer) || 0)); + const nextX = Math.max(0, Math.min(safeWidth - 1, Math.floor(Number(entry?.x) || 0))); + const nextY = Math.max(0, Math.min(safeHeight - 1, Math.floor(Number(entry?.y) || 0))); + const nextTemplateId = String(entry?.templateId || record?.templateId || "").trim(); + record.id = nextId; + record.layer = nextLayer; + record.templateId = nextTemplateId; + record.position = { + x: (safeChunkX * safeWidth) + nextX, + y: (safeChunkY * safeHeight) + nextY, + }; + return { + id: nextId, + templateId: nextTemplateId, + layer: nextLayer, + x: nextX, + y: nextY, + record, + }; + }) + .filter((entry) => entry.id); +} + +export function createEmptyWorldChunkPayload({ chunkX, chunkY, chunkWidth, chunkHeight, worldId }) { + const safeChunkX = Math.floor(Number(chunkX) || 0); + const safeChunkY = Math.floor(Number(chunkY) || 0); + return { + schemaVersion: 1, + worldId: String(worldId || "").trim(), + chunkX: safeChunkX, + chunkY: safeChunkY, + width: chunkWidth, + height: chunkHeight, + backgroundTileId: "", + roomLayers: [ + { + layer: 0, + rows: Array.from({ length: chunkHeight }, () => ".".repeat(chunkWidth)), + instanceIds: [], + }, + { + layer: 1, + rows: Array.from({ length: chunkHeight }, () => " ".repeat(chunkWidth)), + instanceIds: [], + }, + ], + heightLayers: [], + instances: [], + }; +} + +export function normalizeCachedWorldChunkPayload({ chunkPayload, chunkX, chunkY, chunkWidth, chunkHeight, worldId, cloneValue, runtimeUniqueId, options }) { + const safeChunkX = Math.floor(Number(chunkX ?? chunkPayload?.chunkX) || 0); + const safeChunkY = Math.floor(Number(chunkY ?? chunkPayload?.chunkY) || 0); + const safeWidth = Math.max(1, Math.floor(Number(chunkPayload?.width) || Number(chunkWidth) || 32)); + const safeHeight = Math.max(1, Math.floor(Number(chunkPayload?.height) || Number(chunkHeight) || 32)); + const instances = normalizeWorldChunkInstances({ + sourceInstances: chunkPayload?.instances, + chunkX: safeChunkX, + chunkY: safeChunkY, + width: safeWidth, + height: safeHeight, + options, + cloneValue, + runtimeUniqueId, + }); + const roomLayers = buildWorldChunkLayerInstanceIds(chunkPayload?.roomLayers, instances, safeWidth, safeHeight); + return { + schemaVersion: Math.max(1, Math.floor(Number(chunkPayload?.schemaVersion) || 1)), + worldId: String(chunkPayload?.worldId || worldId || "").trim(), + chunkX: safeChunkX, + chunkY: safeChunkY, + width: safeWidth, + height: safeHeight, + backgroundTileId: String(chunkPayload?.backgroundTileId || "").trim(), + roomLayers, + heightLayers: cloneWorldChunkHeightLayers(chunkPayload?.heightLayers), + instances, + }; +} + +export function isChunkFillSymbol(ch, fillChar) { + const symbol = String(ch || "").charAt(0); + return !symbol || symbol === fillChar || symbol === "." || symbol === " "; +} + +export function isWorldChunkPayloadEmpty({ chunkPayload, chunkWidth, chunkHeight, worldId, cloneValue, runtimeUniqueId }) { + const normalized = normalizeCachedWorldChunkPayload({ + chunkPayload, + chunkX: chunkPayload?.chunkX, + chunkY: chunkPayload?.chunkY, + chunkWidth, + chunkHeight, + worldId, + cloneValue, + runtimeUniqueId, + }); + if (String(normalized?.backgroundTileId || "").trim()) { + return false; + } + if (Array.isArray(normalized?.instances) && normalized.instances.length > 0) { + return false; + } + if ((Array.isArray(normalized?.heightLayers) ? normalized.heightLayers : []).some((entry) => ( + Array.isArray(entry?.rows) && entry.rows.some((row) => /[^ .]/.test(String(row || ""))) + ))) { + return false; + } + return !(Array.isArray(normalized?.roomLayers) ? normalized.roomLayers : []).some((layer) => { + const fillChar = (Number(layer?.layer) || 0) === 0 ? "." : " "; + return (Array.isArray(layer?.rows) ? layer.rows : []).some((row) => { + const sourceRow = String(row || ""); + for (let index = 0; index < sourceRow.length; index += 1) { + if (!isChunkFillSymbol(sourceRow.charAt(index), fillChar)) { + return true; + } + } + return false; + }); + }); +} + +export function transformChunkLocalCoord(localX, localY, width, height, operation) { + const safeX = Math.floor(Number(localX) || 0); + const safeY = Math.floor(Number(localY) || 0); + const safeWidth = Math.max(1, Math.floor(Number(width) || 1)); + const safeHeight = Math.max(1, Math.floor(Number(height) || 1)); + switch (String(operation || "").trim()) { + case "flipHorizontal": + return { x: (safeWidth - 1) - safeX, y: safeY }; + case "flipVertical": + return { x: safeX, y: (safeHeight - 1) - safeY }; + case "rotate180": + return { x: (safeWidth - 1) - safeX, y: (safeHeight - 1) - safeY }; + case "rotate90cw": + if (safeWidth !== safeHeight) { + return null; + } + return { x: (safeWidth - 1) - safeY, y: safeX }; + case "rotate90ccw": + if (safeWidth !== safeHeight) { + return null; + } + return { x: safeY, y: (safeHeight - 1) - safeX }; + default: + return { x: safeX, y: safeY }; + } +} + +export function transformChunkRows(rows, width, height, fillChar, operation) { + const safeWidth = Math.max(1, Math.floor(Number(width) || 1)); + const safeHeight = Math.max(1, Math.floor(Number(height) || 1)); + const sourceRows = normalizeWorldChunkRows(rows, safeWidth, safeHeight, fillChar); + const nextRows = Array.from({ length: safeHeight }, () => Array.from({ length: safeWidth }, () => String(fillChar || " ").charAt(0) || " ")); + for (let rowIndex = 0; rowIndex < safeHeight; rowIndex += 1) { + const sourceRow = sourceRows[rowIndex]; + for (let columnIndex = 0; columnIndex < safeWidth; columnIndex += 1) { + const char = String(sourceRow.charAt(columnIndex) || fillChar).charAt(0) || String(fillChar || " ").charAt(0) || " "; + if (isChunkFillSymbol(char, fillChar)) { + continue; + } + const nextCoord = transformChunkLocalCoord(columnIndex, rowIndex, safeWidth, safeHeight, operation); + if (!nextCoord) { + continue; + } + nextRows[nextCoord.y][nextCoord.x] = char; + } + } + return nextRows.map((row) => row.join("")); +} + +export function transformChunkHeightPatch(patch, width, height, operation) { + const safeWidth = Math.max(1, Math.floor(Number(width) || 1)); + const safeHeight = Math.max(1, Math.floor(Number(height) || 1)); + const sourceRows = Array.isArray(patch?.rows) ? patch.rows.map((row) => String(row || "")) : []; + const patchWidth = sourceRows.reduce((max, row) => Math.max(max, row.length), 0); + const patchHeight = sourceRows.length; + const transformedCells = []; + for (let localY = 0; localY < patchHeight; localY += 1) { + const row = sourceRows[localY] || ""; + for (let localX = 0; localX < patchWidth; localX += 1) { + const char = String(row.charAt(localX) || " ").charAt(0) || " "; + if (char === " " || char === ".") { + continue; + } + const worldX = Math.max(0, Math.floor(Number(patch?.x) || 0)) + localX; + const worldY = Math.max(0, Math.floor(Number(patch?.y) || 0)) + localY; + if (worldX < 0 || worldY < 0 || worldX >= safeWidth || worldY >= safeHeight) { + continue; + } + const nextCoord = transformChunkLocalCoord(worldX, worldY, safeWidth, safeHeight, operation); + if (!nextCoord) { + continue; + } + transformedCells.push({ + x: nextCoord.x, + y: nextCoord.y, + char, + }); + } + } + if (transformedCells.length <= 0) { + return null; + } + const minX = transformedCells.reduce((min, entry) => Math.min(min, entry.x), transformedCells[0].x); + const maxX = transformedCells.reduce((max, entry) => Math.max(max, entry.x), transformedCells[0].x); + const minY = transformedCells.reduce((min, entry) => Math.min(min, entry.y), transformedCells[0].y); + const maxY = transformedCells.reduce((max, entry) => Math.max(max, entry.y), transformedCells[0].y); + const nextRows = Array.from({ length: (maxY - minY) + 1 }, () => Array.from({ length: (maxX - minX) + 1 }, () => " ")); + transformedCells.forEach((entry) => { + nextRows[entry.y - minY][entry.x - minX] = entry.char; + }); + return { + id: String(patch?.id || "").trim(), + name: typeof patch?.name === "string" && patch.name.trim() ? patch.name.trim() : undefined, + z: Math.max(1, Math.floor(Number(patch?.z) || 1)), + x: minX, + y: minY, + rows: nextRows.map((row) => row.join("").replace(/\s+$/g, "")), + }; +} + +export function transformWorldChunkPayload({ chunkPayload, operation, chunkWidth, chunkHeight, worldId, cloneValue, runtimeUniqueId, options }) { + const config = options && typeof options === "object" ? options : {}; + const normalized = normalizeCachedWorldChunkPayload({ + chunkPayload, + chunkX: chunkPayload?.chunkX, + chunkY: chunkPayload?.chunkY, + chunkWidth, + chunkHeight, + worldId, + cloneValue, + runtimeUniqueId, + options: config, + }); + const safeWidth = Math.max(1, Math.floor(Number(normalized?.width) || 1)); + const safeHeight = Math.max(1, Math.floor(Number(normalized?.height) || 1)); + const normalizedOperation = String(operation || "").trim(); + if ((normalizedOperation === "rotate90cw" || normalizedOperation === "rotate90ccw") && safeWidth !== safeHeight) { + throw new Error("Chunk rotation requires square chunks."); + } + const instances = normalizeWorldChunkInstances({ + sourceInstances: (Array.isArray(normalized.instances) ? normalized.instances : []).map((entry) => { + const nextCoord = transformChunkLocalCoord(entry.x, entry.y, safeWidth, safeHeight, normalizedOperation); + return { + ...cloneValue(entry), + x: nextCoord?.x ?? entry.x, + y: nextCoord?.y ?? entry.y, + }; + }), + chunkX: normalized.chunkX, + chunkY: normalized.chunkY, + width: safeWidth, + height: safeHeight, + options: config, + cloneValue, + runtimeUniqueId, + }); + const roomLayers = buildWorldChunkLayerInstanceIds( + (Array.isArray(normalized.roomLayers) ? normalized.roomLayers : []).map((layer) => ({ + ...cloneValue(layer), + rows: transformChunkRows(layer?.rows, safeWidth, safeHeight, (Number(layer?.layer) || 0) === 0 ? "." : " ", normalizedOperation), + })), + instances, + safeWidth, + safeHeight, + ); + const heightLayers = cloneWorldChunkHeightLayers(normalized.heightLayers) + .map((entry) => transformChunkHeightPatch(entry, safeWidth, safeHeight, normalizedOperation)) + .filter(Boolean) + .sort((left, right) => { + if ((Number(left?.z) || 0) !== (Number(right?.z) || 0)) { + return (Number(left?.z) || 0) - (Number(right?.z) || 0); + } + return String(left?.name || left?.id || "").localeCompare(String(right?.name || right?.id || "")); + }); + return { + ...normalized, + roomLayers, + heightLayers, + instances, + }; +}