diff --git a/.comfyignore b/.comfyignore index 74d2368..8a37e42 100644 --- a/.comfyignore +++ b/.comfyignore @@ -7,6 +7,10 @@ # dev test harnesses tests/ +# GitHub repo metadata (PR template etc.) — only meaningful on GitHub, not in an +# installed pack. CONTRIBUTING.md is kept so the contributor terms ship with it. +.github/ + # comfy-node-publish build artifact node.zip diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..d143135 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,15 @@ +> **By opening this pull request, I agree to the terms in +> [CONTRIBUTING.md](../CONTRIBUTING.md)** — including the grant that lets the +> maintainer relicense my contribution (for example, under the MIT License). + + + +## What does this PR do? + + +## Related issues + + +## Checklist + +- [ ] I have read and agree to [CONTRIBUTING.md](../CONTRIBUTING.md). diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..34e3839 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,41 @@ +# Contributing to PromptChain + +Thanks for your interest in contributing! PromptChain is licensed under the +[GNU AGPL-3.0](LICENSE), and it stays that way. The terms below let the project +keep that license today while preserving the maintainer's option to relicense +later (for example, to a permissive license such as MIT). Please read them +before opening a pull request. + +## Contributor terms + +By submitting a contribution to this project — a pull request, patch, or any +other change — you agree that: + +1. **License.** Your contribution is licensed to the project and its users + under the project's current license, the GNU AGPL-3.0. + +2. **Relicensing grant.** In consideration of the project reviewing, merging, + and maintaining your contribution, you also grant the project maintainer + (mobcat40) and their successors and assigns a perpetual, worldwide, + irrevocable, non-exclusive, royalty-free, sublicensable right to use, + reproduce, modify, prepare derivative works of, publicly display and perform, + distribute, and relicense your contribution under any license — including + permissive licenses such as the MIT License — as part of this project or any + future version or derivative of it. This grant does not revoke the AGPL-3.0 + license in point 1; it adds the maintainer's ability to also distribute your + contribution under other terms. + +3. **Right to contribute.** The contribution is your own original work (or you + otherwise have the right to submit it), and you have the legal right to make + the contribution and to grant the rights above. If your employer has any + rights to your contribution, you confirm you have permission to submit it + under these terms (or your employer has waived those rights). + +4. **No warranty.** Unless required by law or agreed in writing, you provide + your contribution on an "AS IS" basis, without warranties or conditions of + any kind, express or implied. + +If you do not agree to these terms, please do not submit a contribution. + +These terms apply to contributions submitted after these terms were added to +the project. Opening a pull request is taken as your agreement to them. diff --git a/README.md b/README.md index 690c026..d37a3fa 100644 --- a/README.md +++ b/README.md @@ -112,6 +112,10 @@ Add the **Prompt Chain** node from the node menu, and onboarding takes it from t | Prompt Chain Background Mask (Depth) | Depth-based background mask for bokeh-preserving upscales | | Prompt Chain OpenPose / Depth Anything / Canny / Tile / Luminance / Scribble / Soft Edge / Line Art | Bundled, license-clean ControlNet image preprocessors | +## Contributing + +Contributions are welcome. By opening a pull request you agree to the terms in [CONTRIBUTING.md](CONTRIBUTING.md), which keep PromptChain on AGPL-3.0 today while preserving the maintainer's option to relicense contributions (for example, to MIT) in the future. + ## License [AGPL-3.0](LICENSE). © 2026 mobcat40. If you run a modified PromptChain as part of a hosted service, you must offer your users the modified source. diff --git a/data/templates/krea2-turbo-t2i-3d-regional.json b/data/templates/krea2-turbo-t2i-3d-regional.json deleted file mode 100644 index 35681d9..0000000 --- a/data/templates/krea2-turbo-t2i-3d-regional.json +++ /dev/null @@ -1,270 +0,0 @@ -{ - "name": "Text-to-Image 3D Regional", - "scope": { - "type": "family", - "architecture": "krea2", - "family": "krea2_turbo" - }, - "created_at": 1781920000, - "nodes": [ - { - "type": "PromptChain_RegionalConditioning", - "offset": [ - 820, - 120 - ], - "size": [ - 300, - 170 - ], - "widgets_values": [ - 22 - ], - "properties": { - "Node name for S&R": "PromptChain_RegionalConditioning" - } - }, - { - "type": "KSampler", - "offset": [ - 1830, - 60 - ], - "size": [ - 344.15625, - 565.328125 - ], - "widgets_values": [ - 674787291728667, - "randomize", - 8, - 1, - "euler", - "simple", - 1 - ], - "properties": { - "Node name for S&R": "KSampler" - } - }, - { - "type": "VAEDecode", - "offset": [ - 2230, - 60 - ], - "size": [ - 225, - 72 - ], - "widgets_values": [], - "properties": { - "Node name for S&R": "VAEDecode" - } - }, - { - "type": "SaveImage", - "offset": [ - 2230, - 200 - ], - "size": [ - 247.25, - 419.046875 - ], - "widgets_values": [ - "Krea2/%date:yyyy-MM-dd_hhmmss%" - ], - "properties": {} - }, - { - "type": "CLIPLoader", - "offset": [ - 820, - 320 - ], - "size": [ - 346.78125, - 117.328125 - ], - "widgets_values": [ - "qwen3vl_4b_fp8_scaled.safetensors", - "krea2", - "default" - ], - "properties": { - "Node name for S&R": "CLIPLoader" - } - }, - { - "type": "UNETLoader", - "offset": [ - 820, - 520 - ], - "size": [ - 304.953125, - 82.65625 - ], - "widgets_values": [ - "krea2_turbo_fp8_scaled.safetensors", - "default" - ], - "properties": { - "Node name for S&R": "UNETLoader" - } - }, - { - "type": "EmptySD3LatentImage", - "offset": [ - 1180, - 320 - ], - "size": [ - 270, - 144 - ], - "widgets_values": [ - 1024, - 1536, - 1 - ], - "properties": { - "Node name for S&R": "EmptySD3LatentImage" - } - }, - { - "type": "VAELoader", - "offset": [ - 820, - 680 - ], - "size": [ - 246, - 83 - ], - "widgets_values": [ - "qwen_image_vae.safetensors" - ], - "properties": { - "Node name for S&R": "VAELoader" - } - }, - { - "type": "PromptChain_PoseStudio", - "offset": [ - -1350, - 0 - ], - "size": [ - 360, - 470 - ], - "widgets_values": [ - "", - "{\"version\":3,\"camera\":{\"position\":[-0.11421765907369465,2.2679667675054747,3.7640721683793985],\"target\":[-0.08116832790578885,0.9626632788387957,0.003294416573047413]},\"activeIndex\":1,\"figures\":[{\"uid\":4,\"body\":\"custom\",\"root\":[-0.6544460683107087,1.83557839158723e-7,-0.1296069154697062],\"rootQuat\":[0,0,0,1],\"rootScale\":[1.0847089667745293,1.0847089667745293,1.0847089667745293],\"joints\":{\"root\":[0.530589759349823,0,0,0.8476287722587585],\"shoulderL\":[-0.008142700418829918,0.012219684198498726,-0.33881765604019165,0.9407374858856201],\"elbowL\":[0.3669451177120209,0.0018381300615146756,0.0031449354719370604,0.9302355051040649],\"wristL\":[0.11023613810539246,0.41108429431915283,-0.016057291999459267,0.9047650694847107],\"shoulderR\":[-0.008142700418829918,-0.012219684198498726,0.33881765604019165,0.9407374858856201],\"elbowR\":[0.3669451177120209,-0.0018381300615146756,-0.0031449354719370604,0.9302355051040649],\"wristR\":[0.11023613810539246,-0.41108429431915283,0.016057291999459267,0.9047650694847107],\"neck\":[0.16243939101696014,0,0,0.9867185354232788],\"head\":[-0.20577044785022736,0,0,0.978600263595581],\"hipL\":[-0.9580745100975037,-0.09174012392759323,0.21390390396118164,0.16709932684898376],\"kneeL\":[0.058844611048698425,0.23217718303203583,-0.008530011400580406,0.9708544611930847],\"ankleL\":[-0.5353211164474487,-0.0010657769162207842,0.0007613753550685942,0.8446476459503174],\"hipR\":[-0.9580745100975037,0.09174012392759323,-0.21390390396118164,0.16709932684898376],\"kneeR\":[0.058844611048698425,-0.23217718303203583,0.008530011400580406,0.9708544611930847],\"ankleR\":[-0.5353211164474487,0.0010657769162207842,-0.0007613753550685942,0.8446476459503174],\"chest\":[0.008510730229318142,0,0,0.9999638199806213],\"spine\":[0.3344581127166748,0,0,0.9424106478691101],\"back\":[0,0,0,1]},\"jointPos\":{\"Root\":[0,-0.000562518835067749,6.100162863731384e-8],\"pelvis\":[0,-0.015393643795202175,0.8765072291716933],\"spine_01\":[0,0.08837844353790084,-0.0000035875321676330854],\"spine_02\":[0,0.06657692409741382,-0.0015471302564643939],\"spine_03\":[0,0.07465957864769734,-0.005422550541500943],\"spine_04\":[0,0.1554920266911116,0.008057116608445842],\"clavicle_l\":[0.022795276308897883,0.08298090375804652,0.026856951939407736],\"upperarm_l\":[-0.002402820275165144,0.14708170919523886,-0.0024412409402430058],\"lowerarm_l\":[-0.0036035171942785382,0.2335675825791744,0.007550966273508189],\"hand_l\":[-0.0022912098793312925,0.23001713307652003,0.00286084097691249],\"index_01_l\":[-0.014275551244888145,0.10155431967965949,0.00913081387989223],\"index_02_l\":[-0.00032961389198741625,0.024996880173906295,-0.00044333142189619445],\"index_03_l\":[-0.00028979239747665514,0.02228745664691208,-0.000271643739930975],\"middle_01_l\":[0.0011213888258983697,0.0983148121922568,-0.010979589074850082],\"middle_02_l\":[-0.0004448858817340806,0.032625457237979084,-0.00045865285210311413],\"middle_03_l\":[-0.0003890491255636637,0.026699312448423978,-0.00020532777125481538],\"pinky_01_l\":[0.030837264833583806,0.08091115868228371,-0.0306820534557725],\"pinky_02_l\":[-0.0004330708179622891,0.020605956754783012,-0.0003710941079286083],\"pinky_03_l\":[-0.0002436345233339428,0.015057439972830858,0.000014291528107908793],\"ring_01_l\":[0.014671692488870274,0.09115619229805816,-0.023141342176434897],\"ring_02_l\":[-0.00039252417445823095,0.028744175334092386,-0.0004921690172826243],\"ring_03_l\":[-0.00043109167184714653,0.02398470316741926,-0.0001844997920367557],\"thumb_01_l\":[-0.014337413944303988,0.037947991551694336,0.014606909942813218],\"thumb_02_l\":[0.0002165158900121842,0.029646236808427297,-0.0005050097582473731],\"thumb_03_l\":[0.0001837790356754948,0.035048878259355355,-0.0005737583730175779],\"clavicle_r\":[-0.022795276308897883,0.08298090375804652,0.026856951939407736],\"upperarm_r\":[0.002402820275165144,0.14708170919523886,-0.0024412409402430058],\"lowerarm_r\":[0.0036035171942785382,0.2335675825791744,0.007550966273508189],\"hand_r\":[0.0022912098793312925,0.23001713307652003,0.00286084097691249],\"index_01_r\":[0.014275551244888145,0.10155431967965949,0.00913081387989223],\"index_02_r\":[0.00032961389198741625,0.024996880173906295,-0.00044333142189619445],\"index_03_r\":[0.00028979239747665514,0.02228745664691208,-0.000271643739930975],\"middle_01_r\":[-0.0011213888258983697,0.0983148121922568,-0.010979589074850082],\"middle_02_r\":[0.0004448858817340806,0.032625457237979084,-0.00045865285210311413],\"middle_03_r\":[0.0003890491255636637,0.026699312448423978,-0.00020532777125481538],\"pinky_01_r\":[-0.030837264833583806,0.08091115868228371,-0.0306820534557725],\"pinky_02_r\":[0.0004330708179622891,0.020605956754783012,-0.0003710941079286083],\"pinky_03_r\":[0.0002436345233339428,0.015057439972830858,0.000014291528107908793],\"ring_01_r\":[-0.014671692488870274,0.09115619229805816,-0.023141342176434897],\"ring_02_r\":[0.00039252417445823095,0.028744175334092386,-0.0004921690172826243],\"ring_03_r\":[0.00043109167184714653,0.02398470316741926,-0.0001844997920367557],\"thumb_01_r\":[0.014337413944303988,0.037947991551694336,0.014606909942813218],\"thumb_02_r\":[-0.0002165158900121842,0.029646236808427297,-0.0005050097582473731],\"thumb_03_r\":[-0.0001837790356754948,0.035048878259355355,-0.0005737583730175779],\"neck_01\":[0,0.1554920266911116,0.008057112766740222],\"head\":[0,0.09913267713272944,-0.0024949000217020546],\"thigh_l\":[0.10406423709112762,-0.01035807409789413,-0.015486810940274154],\"calf_l\":[-0.0018221052984396628,0.38333685158674297,0.0019527052451546005],\"foot_l\":[0.0012040060549528171,0.41225113686717424,0.0060748945009739454],\"ball_l\":[-0.004144214297411963,0.1319912777786764,0.0008641794556751847],\"thigh_r\":[-0.10406423709112762,-0.01035807409789413,-0.015486810940274154],\"calf_r\":[0.0018221052984396628,0.38333685158674297,0.0019527052451546005],\"foot_r\":[-0.0012040060549528171,0.41225113686717424,0.0060748945009739454],\"ball_r\":[0.004144214297411963,0.1319912777786764,0.0008641794556751847]},\"digits\":{\"index_01_l\":[0.11009154468774796,0.47919291257858276,-0.0033358719665557146,0.8707714676856995],\"index_02_l\":[0.10867490619421005,0.0028905405197292566,0.006149682216346264,0.9940541386604309],\"index_03_l\":[0.06272030621767044,0.0006448967615142465,0.018671324476599693,0.9978562593460083],\"middle_01_l\":[0.09819573163986206,0.41311776638031006,-0.15238040685653687,0.8924525380134583],\"middle_02_l\":[0.04684380069375038,0.0005204545450396836,0.03666960448026657,0.9982287883758545],\"middle_03_l\":[0.06767940521240234,0.0009195368620567024,-0.005850149318575859,0.9976895451545715],\"pinky_01_l\":[0.04672148823738098,0.29210352897644043,-0.3033210337162018,0.9058084487915039],\"pinky_02_l\":[0.08093646168708801,0.0011062186677008867,0.024598531424999237,0.9964150786399841],\"pinky_03_l\":[-0.008727755397558212,-0.00023478623188566417,-0.01132167037576437,0.9998977780342102],\"ring_01_l\":[0.05214594677090645,0.3895569145679474,-0.2304530292749405,0.890178382396698],\"ring_02_l\":[0.059518661350011826,0.00033943928428925574,0.04314512386918068,0.9972943067550659],\"ring_03_l\":[0.05962633714079857,0.00068375637056306,-0.02453024499118328,0.9979191422462463],\"thumb_01_l\":[0.2744263708591461,0.731201171875,0.43757393956184387,0.44560524821281433],\"thumb_02_l\":[0.06898587942123413,-0.017102878540754318,-0.18289531767368317,0.9805599451065063],\"thumb_03_l\":[0.14205099642276764,-0.006924070883542299,-0.032810747623443604,0.9892911911010742],\"index_01_r\":[0.11009154468774796,-0.47919291257858276,0.0033358719665557146,0.8707714676856995],\"index_02_r\":[0.10867490619421005,-0.0028905405197292566,-0.006149682216346264,0.9940541386604309],\"index_03_r\":[0.06272030621767044,-0.0006448967615142465,-0.018671324476599693,0.9978562593460083],\"middle_01_r\":[0.09819573163986206,-0.41311776638031006,0.15238040685653687,0.8924525380134583],\"middle_02_r\":[0.04684380069375038,-0.0005204545450396836,-0.03666960448026657,0.9982287883758545],\"middle_03_r\":[0.06767940521240234,-0.0009195368620567024,0.005850149318575859,0.9976895451545715],\"pinky_01_r\":[0.04672148823738098,-0.29210352897644043,0.3033210337162018,0.9058084487915039],\"pinky_02_r\":[0.08093646168708801,-0.0011062186677008867,-0.024598531424999237,0.9964150786399841],\"pinky_03_r\":[-0.008727755397558212,0.00023478623188566417,0.01132167037576437,0.9998977780342102],\"ring_01_r\":[0.05214594677090645,-0.3895569145679474,0.2304530292749405,0.890178382396698],\"ring_02_r\":[0.059518661350011826,-0.00033943928428925574,-0.04314512386918068,0.9972943067550659],\"ring_03_r\":[0.05962633714079857,-0.00068375637056306,0.02453024499118328,0.9979191422462463],\"thumb_01_r\":[0.2744263708591461,-0.731201171875,-0.43757393956184387,0.44560524821281433],\"thumb_02_r\":[0.06898587942123413,0.017102878540754318,0.18289531767368317,0.9805599451065063],\"thumb_03_r\":[0.14205099642276764,0.006924070883542299,0.032810747623443604,0.9892911911010742],\"ball_l\":[-0.26220664381980896,0.0072789317928254604,0.029169803485274315,0.964543342590332],\"ball_r\":[-0.26220664381980896,-0.0072789317928254604,-0.029169803485274315,0.964543342590332]},\"spine\":{\"spine_01\":[0.3344581127166748,0,0,0.9424106478691101],\"spine_02\":[-0.10261907428503036,0,0,0.9947206974029541],\"spine_03\":[0.008510730229318142,0,0,0.9999638199806213],\"spine_04\":[0,0,0,1],\"neck_01\":[0.16243939101696014,0,0,0.9867185354232788],\"head\":[-0.20577044785022736,0,0,0.978600263595581]},\"morphs\":{},\"skeletalAmount\":{\"__custom\":1},\"footPins\":{},\"shoes\":false,\"sliders\":{\"gender\":0.5,\"age\":0.5,\"muscle\":0.5,\"weight\":0.5,\"height\":0.5,\"proportions\":0.5,\"african\":0.3333333333333333,\"asian\":0.3333333333333333,\"caucasian\":0.3333333333333333,\"cupsize\":0.5,\"firmness\":0.5,\"breast-trans\":0,\"breast-dist\":0,\"breast-point\":0,\"breast-volume-vert\":0,\"nipple-size\":0,\"nipple-point\":0,\"fem-apple\":0,\"fem-diamond\":0,\"fem-full-hourglass\":0,\"fem-neat-hourglass\":0,\"fem-invert-triangle\":0,\"fem-lean-column\":0,\"fem-rectangle\":0,\"fem-triangle\":0,\"fem-adrenal\":0,\"fem-liver\":0,\"fem-ovary\":0,\"fem-thyroid\":0,\"man-apple\":0,\"man-rectangle\":0,\"man-lean-column\":0,\"man-triangle\":0,\"man-invert-triangle\":0,\"man-trapezoid\":0,\"man-adrenal\":0,\"man-liver\":0,\"man-gonadal\":0,\"man-thyroid\":0,\"torso-scale-depth\":0,\"torso-scale-horiz\":0,\"torso-scale-vert\":0,\"torso-trans-horiz\":0,\"torso-trans-vert\":0,\"torso-trans-depth\":0,\"torso-vshape\":0,\"torso-dorsi\":0,\"torso-pectoral\":0,\"fingers-distance\":0,\"fingers-diameter\":0,\"fingers-length\":0,\"hand-scale\":0,\"hand-position\":0,\"hips\":0,\"glutes\":0,\"stomach\":0,\"neck-circ\":0,\"neck-height\":0,\"head-age\":0,\"head-fat\":0,\"head-angle\":0,\"head-oval\":0,\"head-round\":0,\"head-rectangular\":0,\"head-square\":0,\"head-triangular\":0,\"head-invtriangular\":0,\"head-diamond\":0,\"head-parietal\":0}},{\"uid\":5,\"body\":\"custom\",\"root\":[0.5167425402036215,1.83557839158723e-7,-0.1296069154697062],\"rootQuat\":[0,0,0,1],\"rootScale\":[1.0847089667745293,1.0847089667745293,1.0847089667745293],\"joints\":{\"root\":[0.530589759349823,0,0,0.8476287722587585],\"shoulderL\":[-0.008142700418829918,0.012219684198498726,-0.33881765604019165,0.9407374858856201],\"elbowL\":[0.3669451177120209,0.0018381300615146756,0.0031449354719370604,0.9302355051040649],\"wristL\":[0.11023613810539246,0.41108429431915283,-0.016057291999459267,0.9047650694847107],\"shoulderR\":[-0.008142700418829918,-0.012219684198498726,0.33881765604019165,0.9407374858856201],\"elbowR\":[0.3669451177120209,-0.0018381300615146756,-0.0031449354719370604,0.9302355051040649],\"wristR\":[0.11023613810539246,-0.41108429431915283,0.016057291999459267,0.9047650694847107],\"neck\":[0.16243939101696014,0,0,0.9867185354232788],\"head\":[-0.20577044785022736,0,0,0.978600263595581],\"hipL\":[-0.9580745100975037,-0.09174012392759323,0.21390390396118164,0.16709932684898376],\"kneeL\":[0.058844611048698425,0.23217718303203583,-0.008530011400580406,0.9708544611930847],\"ankleL\":[-0.5353211164474487,-0.0010657769162207842,0.0007613753550685942,0.8446476459503174],\"hipR\":[-0.9580745100975037,0.09174012392759323,-0.21390390396118164,0.16709932684898376],\"kneeR\":[0.058844611048698425,-0.23217718303203583,0.008530011400580406,0.9708544611930847],\"ankleR\":[-0.5353211164474487,0.0010657769162207842,-0.0007613753550685942,0.8446476459503174],\"chest\":[0.008510730229318142,0,0,0.9999638199806213],\"spine\":[0.3344581127166748,0,0,0.9424106478691101],\"back\":[0,0,0,1]},\"jointPos\":{\"Root\":[0,-0.000562518835067749,6.100162863731384e-8],\"pelvis\":[0,-0.015393643795202175,0.8765072291716933],\"spine_01\":[0,0.08837844353790084,-0.0000035875321676330854],\"spine_02\":[0,0.06657692409741382,-0.0015471302564643939],\"spine_03\":[0,0.07465957864769734,-0.005422550541500943],\"spine_04\":[0,0.1554920266911116,0.008057116608445842],\"clavicle_l\":[0.022795276308897883,0.08298090375804652,0.026856951939407736],\"upperarm_l\":[-0.002402820275165144,0.14708170919523886,-0.0024412409402430058],\"lowerarm_l\":[-0.0036035171942785382,0.2335675825791744,0.007550966273508189],\"hand_l\":[-0.0022912098793312925,0.23001713307652003,0.00286084097691249],\"index_01_l\":[-0.014275551244888145,0.10155431967965949,0.00913081387989223],\"index_02_l\":[-0.00032961389198741625,0.024996880173906295,-0.00044333142189619445],\"index_03_l\":[-0.00028979239747665514,0.02228745664691208,-0.000271643739930975],\"middle_01_l\":[0.0011213888258983697,0.0983148121922568,-0.010979589074850082],\"middle_02_l\":[-0.0004448858817340806,0.032625457237979084,-0.00045865285210311413],\"middle_03_l\":[-0.0003890491255636637,0.026699312448423978,-0.00020532777125481538],\"pinky_01_l\":[0.030837264833583806,0.08091115868228371,-0.0306820534557725],\"pinky_02_l\":[-0.0004330708179622891,0.020605956754783012,-0.0003710941079286083],\"pinky_03_l\":[-0.0002436345233339428,0.015057439972830858,0.000014291528107908793],\"ring_01_l\":[0.014671692488870274,0.09115619229805816,-0.023141342176434897],\"ring_02_l\":[-0.00039252417445823095,0.028744175334092386,-0.0004921690172826243],\"ring_03_l\":[-0.00043109167184714653,0.02398470316741926,-0.0001844997920367557],\"thumb_01_l\":[-0.014337413944303988,0.037947991551694336,0.014606909942813218],\"thumb_02_l\":[0.0002165158900121842,0.029646236808427297,-0.0005050097582473731],\"thumb_03_l\":[0.0001837790356754948,0.035048878259355355,-0.0005737583730175779],\"clavicle_r\":[-0.022795276308897883,0.08298090375804652,0.026856951939407736],\"upperarm_r\":[0.002402820275165144,0.14708170919523886,-0.0024412409402430058],\"lowerarm_r\":[0.0036035171942785382,0.2335675825791744,0.007550966273508189],\"hand_r\":[0.0022912098793312925,0.23001713307652003,0.00286084097691249],\"index_01_r\":[0.014275551244888145,0.10155431967965949,0.00913081387989223],\"index_02_r\":[0.00032961389198741625,0.024996880173906295,-0.00044333142189619445],\"index_03_r\":[0.00028979239747665514,0.02228745664691208,-0.000271643739930975],\"middle_01_r\":[-0.0011213888258983697,0.0983148121922568,-0.010979589074850082],\"middle_02_r\":[0.0004448858817340806,0.032625457237979084,-0.00045865285210311413],\"middle_03_r\":[0.0003890491255636637,0.026699312448423978,-0.00020532777125481538],\"pinky_01_r\":[-0.030837264833583806,0.08091115868228371,-0.0306820534557725],\"pinky_02_r\":[0.0004330708179622891,0.020605956754783012,-0.0003710941079286083],\"pinky_03_r\":[0.0002436345233339428,0.015057439972830858,0.000014291528107908793],\"ring_01_r\":[-0.014671692488870274,0.09115619229805816,-0.023141342176434897],\"ring_02_r\":[0.00039252417445823095,0.028744175334092386,-0.0004921690172826243],\"ring_03_r\":[0.00043109167184714653,0.02398470316741926,-0.0001844997920367557],\"thumb_01_r\":[0.014337413944303988,0.037947991551694336,0.014606909942813218],\"thumb_02_r\":[-0.0002165158900121842,0.029646236808427297,-0.0005050097582473731],\"thumb_03_r\":[-0.0001837790356754948,0.035048878259355355,-0.0005737583730175779],\"neck_01\":[0,0.1554920266911116,0.008057112766740222],\"head\":[0,0.09913267713272944,-0.0024949000217020546],\"thigh_l\":[0.10406423709112762,-0.01035807409789413,-0.015486810940274154],\"calf_l\":[-0.0018221052984396628,0.38333685158674297,0.0019527052451546005],\"foot_l\":[0.0012040060549528171,0.41225113686717424,0.0060748945009739454],\"ball_l\":[-0.004144214297411963,0.1319912777786764,0.0008641794556751847],\"thigh_r\":[-0.10406423709112762,-0.01035807409789413,-0.015486810940274154],\"calf_r\":[0.0018221052984396628,0.38333685158674297,0.0019527052451546005],\"foot_r\":[-0.0012040060549528171,0.41225113686717424,0.0060748945009739454],\"ball_r\":[0.004144214297411963,0.1319912777786764,0.0008641794556751847]},\"digits\":{\"index_01_l\":[0.11009154468774796,0.47919291257858276,-0.0033358719665557146,0.8707714676856995],\"index_02_l\":[0.10867490619421005,0.0028905405197292566,0.006149682216346264,0.9940541386604309],\"index_03_l\":[0.06272030621767044,0.0006448967615142465,0.018671324476599693,0.9978562593460083],\"middle_01_l\":[0.09819573163986206,0.41311776638031006,-0.15238040685653687,0.8924525380134583],\"middle_02_l\":[0.04684380069375038,0.0005204545450396836,0.03666960448026657,0.9982287883758545],\"middle_03_l\":[0.06767940521240234,0.0009195368620567024,-0.005850149318575859,0.9976895451545715],\"pinky_01_l\":[0.04672148823738098,0.29210352897644043,-0.3033210337162018,0.9058084487915039],\"pinky_02_l\":[0.08093646168708801,0.0011062186677008867,0.024598531424999237,0.9964150786399841],\"pinky_03_l\":[-0.008727755397558212,-0.00023478623188566417,-0.01132167037576437,0.9998977780342102],\"ring_01_l\":[0.05214594677090645,0.3895569145679474,-0.2304530292749405,0.890178382396698],\"ring_02_l\":[0.059518661350011826,0.00033943928428925574,0.04314512386918068,0.9972943067550659],\"ring_03_l\":[0.05962633714079857,0.00068375637056306,-0.02453024499118328,0.9979191422462463],\"thumb_01_l\":[0.2744263708591461,0.731201171875,0.43757393956184387,0.44560524821281433],\"thumb_02_l\":[0.06898587942123413,-0.017102878540754318,-0.18289531767368317,0.9805599451065063],\"thumb_03_l\":[0.14205099642276764,-0.006924070883542299,-0.032810747623443604,0.9892911911010742],\"index_01_r\":[0.11009154468774796,-0.47919291257858276,0.0033358719665557146,0.8707714676856995],\"index_02_r\":[0.10867490619421005,-0.0028905405197292566,-0.006149682216346264,0.9940541386604309],\"index_03_r\":[0.06272030621767044,-0.0006448967615142465,-0.018671324476599693,0.9978562593460083],\"middle_01_r\":[0.09819573163986206,-0.41311776638031006,0.15238040685653687,0.8924525380134583],\"middle_02_r\":[0.04684380069375038,-0.0005204545450396836,-0.03666960448026657,0.9982287883758545],\"middle_03_r\":[0.06767940521240234,-0.0009195368620567024,0.005850149318575859,0.9976895451545715],\"pinky_01_r\":[0.04672148823738098,-0.29210352897644043,0.3033210337162018,0.9058084487915039],\"pinky_02_r\":[0.08093646168708801,-0.0011062186677008867,-0.024598531424999237,0.9964150786399841],\"pinky_03_r\":[-0.008727755397558212,0.00023478623188566417,0.01132167037576437,0.9998977780342102],\"ring_01_r\":[0.05214594677090645,-0.3895569145679474,0.2304530292749405,0.890178382396698],\"ring_02_r\":[0.059518661350011826,-0.00033943928428925574,-0.04314512386918068,0.9972943067550659],\"ring_03_r\":[0.05962633714079857,-0.00068375637056306,0.02453024499118328,0.9979191422462463],\"thumb_01_r\":[0.2744263708591461,-0.731201171875,-0.43757393956184387,0.44560524821281433],\"thumb_02_r\":[0.06898587942123413,0.017102878540754318,0.18289531767368317,0.9805599451065063],\"thumb_03_r\":[0.14205099642276764,0.006924070883542299,0.032810747623443604,0.9892911911010742],\"ball_l\":[-0.26220664381980896,0.0072789317928254604,0.029169803485274315,0.964543342590332],\"ball_r\":[-0.26220664381980896,-0.0072789317928254604,-0.029169803485274315,0.964543342590332]},\"spine\":{\"spine_01\":[0.3344581127166748,0,0,0.9424106478691101],\"spine_02\":[-0.10261907428503036,0,0,0.9947206974029541],\"spine_03\":[0.008510730229318142,0,0,0.9999638199806213],\"spine_04\":[0,0,0,1],\"neck_01\":[0.16243939101696014,0,0,0.9867185354232788],\"head\":[-0.20577044785022736,0,0,0.978600263595581]},\"morphs\":{},\"skeletalAmount\":{\"__custom\":1},\"footPins\":{},\"shoes\":false,\"sliders\":{\"gender\":0.5,\"age\":0.5,\"muscle\":0.5,\"weight\":0.5,\"height\":0.5,\"proportions\":0.5,\"african\":0.3333333333333333,\"asian\":0.3333333333333333,\"caucasian\":0.3333333333333333,\"cupsize\":0.5,\"firmness\":0.5,\"breast-trans\":0,\"breast-dist\":0,\"breast-point\":0,\"breast-volume-vert\":0,\"nipple-size\":0,\"nipple-point\":0,\"fem-apple\":0,\"fem-diamond\":0,\"fem-full-hourglass\":0,\"fem-neat-hourglass\":0,\"fem-invert-triangle\":0,\"fem-lean-column\":0,\"fem-rectangle\":0,\"fem-triangle\":0,\"fem-adrenal\":0,\"fem-liver\":0,\"fem-ovary\":0,\"fem-thyroid\":0,\"man-apple\":0,\"man-rectangle\":0,\"man-lean-column\":0,\"man-triangle\":0,\"man-invert-triangle\":0,\"man-trapezoid\":0,\"man-adrenal\":0,\"man-liver\":0,\"man-gonadal\":0,\"man-thyroid\":0,\"torso-scale-depth\":0,\"torso-scale-horiz\":0,\"torso-scale-vert\":0,\"torso-trans-horiz\":0,\"torso-trans-vert\":0,\"torso-trans-depth\":0,\"torso-vshape\":0,\"torso-dorsi\":0,\"torso-pectoral\":0,\"fingers-distance\":0,\"fingers-diameter\":0,\"fingers-length\":0,\"hand-scale\":0,\"hand-position\":0,\"hips\":0,\"glutes\":0,\"stomach\":0,\"neck-circ\":0,\"neck-height\":0,\"head-age\":0,\"head-fat\":0,\"head-angle\":0,\"head-oval\":0,\"head-round\":0,\"head-rectangular\":0,\"head-square\":0,\"head-triangular\":0,\"head-invtriangular\":0,\"head-diamond\":0,\"head-parietal\":0}}],\"props\":[],\"attachments\":[],\"heads\":[{\"box\":[0.1916,0.2178,0.3413,0.3343],\"facing\":0.979},{\"box\":[0.6677,0.2175,0.817,0.3336],\"facing\":0.976}],\"regionEntities\":[{\"kind\":\"figure\",\"name\":\"mannequin1\"},{\"kind\":\"figure\",\"name\":\"mannequin2\"}]}", - 832, - 1216, - "" - ], - "properties": { - "Node name for S&R": "PromptChain_PoseStudio", - "pcrOutputMode": "depth", - "pcrTrueDepth": true - } - }, - { - "type": "PreviewImage", - "offset": [ - -945, - 0 - ], - "size": [ - 250, - 360 - ], - "widgets_values": [], - "properties": { - "Node name for S&R": "PreviewImage" - } - } - ], - "connections": [ - { - "from_node_idx": 4, - "from_slot": 0, - "to_node_idx": 0, - "to_slot": 0 - }, - { - "from_node_idx": 8, - "from_slot": 2, - "to_node_idx": 0, - "to_slot": 2 - }, - { - "from_node_idx": 8, - "from_slot": 1, - "to_node_idx": 0, - "to_slot": 3 - }, - { - "from_node_idx": 0, - "from_slot": 0, - "to_node_idx": 1, - "to_slot": 1 - }, - { - "from_node_idx": 0, - "from_slot": 1, - "to_node_idx": 1, - "to_slot": 2 - }, - { - "from_node_idx": 5, - "from_slot": 0, - "to_node_idx": 1, - "to_slot": 0 - }, - { - "from_node_idx": 6, - "from_slot": 0, - "to_node_idx": 1, - "to_slot": 3 - }, - { - "from_node_idx": 1, - "from_slot": 0, - "to_node_idx": 2, - "to_slot": 0 - }, - { - "from_node_idx": 7, - "from_slot": 0, - "to_node_idx": 2, - "to_slot": 1 - }, - { - "from_node_idx": 2, - "from_slot": 0, - "to_node_idx": 3, - "to_slot": 0 - }, - { - "from_node_idx": 8, - "from_slot": 0, - "to_node_idx": 9, - "to_slot": 0 - } - ], - "anchorConnections": [ - { - "from_slot": 3, - "to_node_idx": 0, - "to_slot": 1 - } - ], - "id": "krea2-turbo-t2i-3d-regional", - "category": "Generation", - "order": 2 -} \ No newline at end of file diff --git a/docs/RELICENSING.md b/docs/RELICENSING.md new file mode 100644 index 0000000..8906f95 --- /dev/null +++ b/docs/RELICENSING.md @@ -0,0 +1,43 @@ +# Relicensing checklist (maintainer) + +PromptChain ships under [AGPL-3.0](../LICENSE). This note records what must be +true to relicense the project — or a future version — under a permissive license +such as MIT. It is a maintainer reference, not part of the contributor terms. + +Two independent things must **both** hold. The contributor grant only handles +the first; the second is on you regardless of any contributor terms. + +## 1. Every contributor's code is relicensable + +- Outside contributions are covered by the relicensing grant in + [CONTRIBUTING.md](../CONTRIBUTING.md) **only if** they were submitted after + that file existed and the contributor accepted the terms (by opening a PR). +- **Invariant:** no outside contribution may predate the CONTRIBUTING.md commit. + History is currently 100% single-author (mobcat40), so this holds. If it ever + stops holding, get an explicit written grant from that author before + relicensing — the grant cannot reach backwards. +- The grant is a *license*, not an assignment: contributors keep copyright, and + the AGPL availability of already-published versions cannot be withdrawn. That + does not block offering *future* versions under MIT. +- Route all outside contributions through PRs so the assent trigger always + fires. A DCO ("Signed-off-by") is **not** a substitute — it certifies origin, + not a relicensing right. +- Strongest hardening, cheapest before outside PRs arrive: enable **CLA + Assistant** as a required status check, so each contributor's grant is backed + by a recorded, identity-bound signature rather than only the PR-template line. + +## 2. Every bundled / vendored third-party component is MIT-compatible + +The contributor grant covers only what contributors submit — it does nothing for +third-party code the project incorporates. A whole-project MIT relicense +additionally requires that **every** copyrightable component be MIT-compatible. + +- Copyleft third-party code (GPL / AGPL / LGPL source) **cannot** be relicensed + to MIT. Keep it as an *external dependency* loaded at runtime, not vendored + into this tree. Vendoring a copyleft node pack's source would block a clean + MIT relicense — watch this when doing full-install / vendoring work. +- Permissive third-party code (MIT / BSD / Apache-2.0) may be included, but its + own license notices and attribution must be retained. The result is then + "MIT project + retained third-party notices," not a single bare MIT file. +- Before relicensing, inventory `bundled_packs/`, `vendor/`, and + `requirements.txt` and confirm each component's license. diff --git a/js/lib/asset-drop.js b/js/lib/asset-drop.js index 2639237..0b46986 100644 --- a/js/lib/asset-drop.js +++ b/js/lib/asset-drop.js @@ -108,12 +108,15 @@ async function handleDrop(e) { if (!fetchResponse.ok) throw new Error(`Failed to fetch: ${fetchResponse.status}`); const blob = await fetchResponse.blob(); - // upload to ComfyUI's input folder + // Upload into the input ROOT, not a subfolder. Stock LoadImage's INPUT_TYPES + // only enumerates top-level input files, so a "subfolder/name" combo value is + // never in its option list — the node loads flagged/errored even though the + // backend's VALIDATE_INPUTS (exists_annotated_filepath) still finds the file + // and the graph runs. A bare filename keeps the combo valid. const filename = assetData.path.split("/").pop(); const file = new File([blob], filename, { type: blob.type }); const formData = new FormData(); formData.append("image", file); - formData.append("subfolder", "promptchain_assets"); formData.append("type", "input"); formData.append("overwrite", "true"); const uploadResponse = await api.fetchApi("/upload/image", { method: "POST", body: formData }); diff --git a/js/lib/model-settings-bridge.js b/js/lib/model-settings-bridge.js index c3af9f3..bf5da04 100644 --- a/js/lib/model-settings-bridge.js +++ b/js/lib/model-settings-bridge.js @@ -1816,9 +1816,10 @@ function injectControlNet(pcNode, controlType) { if (!graph) return false; const LG = window.LiteGraph || graph.constructor?.LiteGraph; if (!LG) return false; - // "depth-pose" is the depth branch with a Pose Studio figure as the image - // source instead of a LoadImage — the rendered figure goes THROUGH - // DepthAnything just like an uploaded photo would. + // "depth-pose" is the depth branch sourced from a 3D Poser figure instead of a + // LoadImage. The Poser renders its OWN true geometric depth, so it wires straight + // into Apply ControlNet — no DepthAnything estimator (that exists only for the + // uploaded-image branches, which have no scene geometry to read). const usePose = controlType === "depth-pose"; const cfg = CONTROLNET_TYPES[usePose ? "depth" : controlType]; if (!cfg) return false; @@ -1838,12 +1839,16 @@ function injectControlNet(pcNode, controlType) { if (!posSrc?.node || !negSrc?.node) return false; // Image source: a shared LoadImage (uploaded reference) reused across stacked - // branches — or, for "depth + 3D pose", a Pose Studio node whose rendered figure - // feeds the depth preprocessor. Pose nodes aren't shared (one figure per branch). + // branches — or, for "depth + 3D pose", a Pose Studio node whose true-depth + // render IS the control map. Pose nodes aren't shared (one figure per branch). let loadImage = null, newLoadImage = false, poseNode = null; if (usePose) { poseNode = LG.createNode("PromptChain_PoseStudio"); if (!poseNode) return false; + // Switch the Poser to true geometric depth before its mount seeds the first + // capture (read live from pcrOutputMode) — the default disparity curve matches + // what the depth ControlNets were trained on. + poseNode.properties = { ...(poseNode.properties || {}), pcrOutputMode: "depth", pcrTrueDepth: true }; } else { loadImage = graph._nodes?.find(n => n.comfyClass === "LoadImage" && n.title === "ControlNet Reference"); newLoadImage = !loadImage; @@ -1851,12 +1856,13 @@ function injectControlNet(pcNode, controlType) { } const sourceNode = poseNode || loadImage; - const preNode = LG.createNode(resolvePre(cfg.pre)); + // Pose-direct depth needs no preprocessor — the Poser's IMAGE already IS the map. + const preNode = usePose ? null : LG.createNode(resolvePre(cfg.pre)); const loaderNode = LG.createNode("ControlNetLoader"); const applyNode = LG.createNode("ControlNetApplyAdvanced"); const setUnion = cfg.unionType ? LG.createNode("SetUnionControlNetType") : null; const preview = LG.createNode("PreviewImage"); - if (!preNode || !loaderNode || !applyNode) return false; + if ((!usePose && !preNode) || !loaderNode || !applyNode) return false; if (cfg.unionType && !setUnion) return false; // Layout mirrors the hand-arranged reference (janku-depth-3): the cluster @@ -1869,7 +1875,7 @@ function injectControlNet(pcNode, controlType) { // Loader sits just below the preprocessor, whose height varies a lot by type // (pose tallest, scribble shortest) — derive from the live node height so one // rule fits all controls. SetUnion is 150 below the loader. - const preH = preNode.size?.[1] || 130; + const preH = preNode?.size?.[1] || 130; const loaderY = preH + 65; const setUnionY = loaderY + 150; if (poseNode) { @@ -1882,12 +1888,12 @@ function injectControlNet(pcNode, controlType) { graph.add(loadImage); loadImage.size = [250, 340]; } - preNode.pos = [baseX - 643, baseY + dy]; + if (preNode) preNode.pos = [baseX - 643, baseY + dy]; loaderNode.pos = [baseX - 633, baseY + loaderY + dy]; if (setUnion) setUnion.pos = [baseX - 633, baseY + setUnionY + dy]; applyNode.pos = [baseX - 313, baseY + dy]; if (preview) preview.pos = [baseX - 933, baseY + dy]; - graph.add(preNode); + if (preNode) graph.add(preNode); graph.add(loaderNode); if (setUnion) graph.add(setUnion); graph.add(applyNode); @@ -1895,7 +1901,7 @@ function injectControlNet(pcNode, controlType) { // Sizes set after add so the litegraph size setter runs on a registered node. // Preprocessor: fix width, keep its auto height (varies by widget count — // canny has more rows than depth). - preNode.size = [291, preNode.size?.[1] ?? 130]; + if (preNode) preNode.size = [291, preNode.size?.[1] ?? 130]; loaderNode.size = [270, 83]; if (setUnion) setUnion.size = [270, 83]; applyNode.size = [270, 240]; @@ -1917,15 +1923,22 @@ function injectControlNet(pcNode, controlType) { else if (w.name === "start_percent") { w.value = 0.0; w.callback?.(0.0); } else if (w.name === "end_percent") { w.value = cfg.end; w.callback?.(cfg.end); } } - for (const w of preNode.widgets || []) { + for (const w of preNode?.widgets || []) { if (w.name === "resolution") { w.value = 1024; w.callback?.(1024); } // DWPose ships a toggle specifically for xinsir ControlNet stick scaling. else if (w.name === "scale_stick_for_xinsr_cn") { w.value = "enable"; w.callback?.("enable"); } } - sourceNode.connect(0, preNode, preNode.inputs?.findIndex(i => i.name === "image") ?? 0); - preNode.connect(0, applyNode, applyNode.inputs?.findIndex(i => i.name === "image") ?? 3); - if (preview) preNode.connect(0, preview, 0); // show the preprocessed map + const applyImageIdx = applyNode.inputs?.findIndex(i => i.name === "image") ?? 3; + if (preNode) { + sourceNode.connect(0, preNode, preNode.inputs?.findIndex(i => i.name === "image") ?? 0); + preNode.connect(0, applyNode, applyImageIdx); + if (preview) preNode.connect(0, preview, 0); // show the preprocessed map + } else { + // Pose-direct: the Poser's true-depth IMAGE is the control map itself. + sourceNode.connect(0, applyNode, applyImageIdx); + if (preview) sourceNode.connect(0, preview, 0); // the depth-map flash + } if (setUnion) { loaderNode.connect(0, setUnion, 0); setUnion.connect(0, applyNode, applyNode.inputs?.findIndex(i => i.name === "control_net") ?? 2); @@ -2344,7 +2357,11 @@ function injectFluxControlNet(pcNode, controlType) { if (!graph) return false; const LG = window.LiteGraph || graph.constructor?.LiteGraph; if (!LG) return false; - const cfg = FLUX_CONTROLNET_TYPES[controlType]; + // "depth-pose" = the depth branch sourced from a 3D Poser figure (true geometric + // depth) instead of a LoadImage — wired straight into Apply ControlNet. + const usePose = controlType === "depth-pose"; + const baseControl = usePose ? "depth" : controlType; + const cfg = FLUX_CONTROLNET_TYPES[baseControl]; if (!cfg) return false; const downstream = findDownstreamNodes(pcNode); @@ -2373,47 +2390,62 @@ function injectFluxControlNet(pcNode, controlType) { } } - // Reuse one shared reference image across stacked branches. - let loadImage = graph._nodes?.find(n => n.comfyClass === "LoadImage" && n.title === "ControlNet Reference"); - const newLoadImage = !loadImage; - if (!loadImage) loadImage = LG.createNode("LoadImage"); + // Image source: a 3D Poser figure (depth + 3D pose) whose true-depth render IS + // the control map — or a shared LoadImage reference reused across stacked branches. + let loadImage = null, newLoadImage = false, poseNode = null; + if (usePose) { + poseNode = LG.createNode("PromptChain_PoseStudio"); + if (!poseNode) return false; + // True geometric depth, set before its mount seeds the first capture. + poseNode.properties = { ...(poseNode.properties || {}), pcrOutputMode: "depth", pcrTrueDepth: true }; + } else { + loadImage = graph._nodes?.find(n => n.comfyClass === "LoadImage" && n.title === "ControlNet Reference"); + newLoadImage = !loadImage; + if (!loadImage) loadImage = LG.createNode("LoadImage"); + } + const sourceNode = poseNode || loadImage; - const preNode = LG.createNode(resolvePre(cfg.pre)); + // Pose-direct depth needs no preprocessor — the Poser's IMAGE already IS the map. + const preNode = usePose ? null : LG.createNode(resolvePre(cfg.pre)); const loaderNode = LG.createNode("ControlNetLoader"); const applyNode = LG.createNode("ControlNetApplyAdvanced"); const preview = LG.createNode("PreviewImage"); - if (!preNode || !loaderNode || !applyNode) return false; + if ((!usePose && !preNode) || !loaderNode || !applyNode) return false; // Same left-of-node layout as the SDXL cluster, minus the SetUnion node. const existingApplies = downstream.filter(n => n.comfyClass === "ControlNetApplyAdvanced").length; const baseX = pcNode.pos?.[0] ?? 0; const baseY = pcNode.pos?.[1] ?? 0; const dy = existingApplies * 660; - const preH = preNode.size?.[1] || 130; + const preH = preNode?.size?.[1] || 130; const loaderY = preH + 65; - if (newLoadImage) { + if (poseNode) { + poseNode.pos = [baseX - 1383, baseY + dy]; + graph.add(poseNode); + poseNode.size = [360, 470]; + } else if (newLoadImage) { loadImage.title = "ControlNet Reference"; loadImage.pos = [baseX - 1223, baseY]; graph.add(loadImage); loadImage.size = [250, 340]; } - preNode.pos = [baseX - 643, baseY + dy]; + if (preNode) preNode.pos = [baseX - 643, baseY + dy]; loaderNode.pos = [baseX - 633, baseY + loaderY + dy]; applyNode.pos = [baseX - 313, baseY + dy]; if (preview) preview.pos = [baseX - 933, baseY + dy]; - graph.add(preNode); + if (preNode) graph.add(preNode); graph.add(loaderNode); graph.add(applyNode); if (preview) graph.add(preview); - preNode.size = [291, preNode.size?.[1] ?? 130]; + if (preNode) preNode.size = [291, preNode.size?.[1] ?? 130]; loaderNode.size = [270, 83]; applyNode.size = [270, 240]; if (preview) preview.size = [250, 360]; // Flux branches carry a "flux-" prefixed control type so their settings // sections + recommended markers stay distinct from the SDXL defaults. - const settingsType = `flux-${controlType}`; - applyNode.title = `ControlNet — ${CN_LABELS[settingsType] || controlType}`; + const settingsType = `flux-${baseControl}`; + applyNode.title = `ControlNet — ${usePose ? "Depth (pose)" : (CN_LABELS[settingsType] || controlType)}`; applyNode.properties = { ...(applyNode.properties || {}), pcrControlType: settingsType }; setControlNetModel(loaderNode, "fluxunion"); @@ -2422,13 +2454,20 @@ function injectFluxControlNet(pcNode, controlType) { else if (w.name === "start_percent") { w.value = 0.0; w.callback?.(0.0); } else if (w.name === "end_percent") { w.value = cfg.end; w.callback?.(cfg.end); } } - for (const w of preNode.widgets || []) { + for (const w of preNode?.widgets || []) { if (w.name === "resolution") { w.value = 1024; w.callback?.(1024); } } - loadImage.connect(0, preNode, preNode.inputs?.findIndex(i => i.name === "image") ?? 0); - preNode.connect(0, applyNode, applyNode.inputs?.findIndex(i => i.name === "image") ?? 3); - if (preview) preNode.connect(0, preview, 0); + const applyImageIdx = applyNode.inputs?.findIndex(i => i.name === "image") ?? 3; + if (preNode) { + sourceNode.connect(0, preNode, preNode.inputs?.findIndex(i => i.name === "image") ?? 0); + preNode.connect(0, applyNode, applyImageIdx); + if (preview) preNode.connect(0, preview, 0); + } else { + // Pose-direct: the Poser's true-depth IMAGE is the control map itself. + sourceNode.connect(0, applyNode, applyImageIdx); + if (preview) sourceNode.connect(0, preview, 0); + } loaderNode.connect(0, applyNode, applyNode.inputs?.findIndex(i => i.name === "control_net") ?? 2); if (vaeSrc?.node) { const vaeIdx = applyNode.inputs?.findIndex(i => i.name === "vae") ?? -1; @@ -2452,9 +2491,10 @@ function injectZImageControlNet(pcNode, controlType, isBase = false) { if (!graph) return false; const LG = window.LiteGraph || graph.constructor?.LiteGraph; if (!LG) return false; - // "depth-pose" = the depth branch driven by a 3D Poser figure instead of a - // LoadImage — the rendered figure goes THROUGH the depth preprocessor, and its - // MASKS/POSE outputs are what a follow-up Regional inject binds to. + // "depth-pose" = the depth branch sourced from a 3D Poser figure instead of a + // LoadImage. The Poser emits its own true geometric depth, so it wires straight + // into ZImageFunControlnet (no DepthAnything); its MASKS/POSE outputs are what a + // follow-up Regional inject binds to. const usePose = controlType === "depth-pose"; const baseControl = usePose ? "depth" : controlType; const cfg = ZIMAGE_CONTROLNET_TYPES[baseControl]; @@ -2487,6 +2527,9 @@ function injectZImageControlNet(pcNode, controlType, isBase = false) { if (usePose) { poseNode = LG.createNode("PromptChain_PoseStudio"); if (!poseNode) return false; + // Poser emits its own true geometric depth → straight into ZImageFunControlnet, + // no DepthAnything re-estimation. Set before its mount seeds the first capture. + poseNode.properties = { ...(poseNode.properties || {}), pcrOutputMode: "depth", pcrTrueDepth: true }; } else { loadImage = graph._nodes?.find(n => n.comfyClass === "LoadImage" && n.title === "ControlNet Reference"); newLoadImage = !loadImage; @@ -2494,17 +2537,18 @@ function injectZImageControlNet(pcNode, controlType, isBase = false) { } const sourceNode = poseNode || loadImage; - const preNode = LG.createNode(resolvePre(cfg.pre)); + // Pose-direct depth needs no preprocessor — the Poser's IMAGE already IS the map. + const preNode = usePose ? null : LG.createNode(resolvePre(cfg.pre)); const patchLoader = LG.createNode("ModelPatchLoader"); const zcnNode = LG.createNode("ZImageFunControlnet"); const preview = LG.createNode("PreviewImage"); - if (!preNode || !patchLoader || !zcnNode) return false; + if ((!usePose && !preNode) || !patchLoader || !zcnNode) return false; const existing = downstream.filter(n => n.comfyClass === "ZImageFunControlnet").length; const baseX = pcNode.pos?.[0] ?? 0; const baseY = pcNode.pos?.[1] ?? 0; const dy = existing * 660; - const preH = preNode.size?.[1] || 130; + const preH = preNode?.size?.[1] || 130; const loaderY = preH + 65; if (poseNode) { poseNode.pos = [baseX - 1383, baseY + dy]; @@ -2516,15 +2560,15 @@ function injectZImageControlNet(pcNode, controlType, isBase = false) { graph.add(loadImage); loadImage.size = [250, 340]; } - preNode.pos = [baseX - 643, baseY + dy]; + if (preNode) preNode.pos = [baseX - 643, baseY + dy]; patchLoader.pos = [baseX - 633, baseY + loaderY + dy]; zcnNode.pos = [baseX - 313, baseY + dy]; if (preview) preview.pos = [baseX - 933, baseY + dy]; - graph.add(preNode); + if (preNode) graph.add(preNode); graph.add(patchLoader); graph.add(zcnNode); if (preview) graph.add(preview); - preNode.size = [291, preNode.size?.[1] ?? 130]; + if (preNode) preNode.size = [291, preNode.size?.[1] ?? 130]; patchLoader.size = [270, 58]; zcnNode.size = [270, 130]; if (preview) preview.size = [250, 360]; @@ -2549,13 +2593,20 @@ function injectZImageControlNet(pcNode, controlType, isBase = false) { for (const w of zcnNode.widgets || []) { if (w.name === "strength") { w.value = cfg.strength; w.callback?.(cfg.strength); } } - for (const w of preNode.widgets || []) { + for (const w of preNode?.widgets || []) { if (w.name === "resolution") { w.value = 1024; w.callback?.(1024); } } - sourceNode.connect(0, preNode, preNode.inputs?.findIndex(i => i.name === "image") ?? 0); - preNode.connect(0, zcnNode, zcnNode.inputs?.findIndex(i => i.name === "image") ?? -1); - if (preview) preNode.connect(0, preview, 0); + const zcnImageIdx = zcnNode.inputs?.findIndex(i => i.name === "image") ?? -1; + if (preNode) { + sourceNode.connect(0, preNode, preNode.inputs?.findIndex(i => i.name === "image") ?? 0); + preNode.connect(0, zcnNode, zcnImageIdx); + if (preview) preNode.connect(0, preview, 0); + } else { + // Pose-direct: the Poser's true-depth IMAGE feeds the Z-Image control patch. + sourceNode.connect(0, zcnNode, zcnImageIdx); + if (preview) sourceNode.connect(0, preview, 0); + } patchLoader.connect(0, zcnNode, zcnNode.inputs?.findIndex(i => i.name === "model_patch") ?? 1); if (vaeSrc?.node) vaeSrc.node.connect(vaeSrc.slot, zcnNode, zcnNode.inputs?.findIndex(i => i.name === "vae") ?? 2); // Model line: source → ZImageFunControlnet → sampler. @@ -3049,6 +3100,7 @@ export async function showModelModal(pcNode, modelInfo, anchorEl, { tab = null } label: "ControlNet", children: [ { label: "Depth", value: "FluxControlNet:depth" }, + { label: "Depth + 3D pose", value: "FluxControlNet:depth-pose" }, { label: "Canny", value: "FluxControlNet:canny" }, { label: "Pose", value: "FluxControlNet:pose" }, { label: "Soft Edge", value: "FluxControlNet:softedge" }, diff --git a/js/lib/svelte/ConfirmModal-BGcVYndw.js b/js/lib/svelte/ConfirmModal-CtR7InHn.js similarity index 95% rename from js/lib/svelte/ConfirmModal-BGcVYndw.js rename to js/lib/svelte/ConfirmModal-CtR7InHn.js index 31bdd11..b830b89 100644 --- a/js/lib/svelte/ConfirmModal-BGcVYndw.js +++ b/js/lib/svelte/ConfirmModal-CtR7InHn.js @@ -1,5 +1,5 @@ -import { d as delegate, p as push, a as prop, u as user_effect, D as comment, v as first_child, i as if_block, k as append, l as pop, g as get, s as state, f as sibling, o as bind_this, e as set, t as template_effect, y as set_text, j as delegated, A as from_html, n as child } from "./disclose-version-uq4tn5Y6.js"; -import { a as action } from "./actions-WPfqiWYB.js"; +import { d as delegate, p as push, a as prop, u as user_effect, D as comment, v as first_child, i as if_block, k as append, l as pop, g as get, s as state, f as sibling, o as bind_this, e as set, t as template_effect, y as set_text, j as delegated, A as from_html, n as child } from "./disclose-version-et9wt-4m.js"; +import { a as action } from "./actions-zjmT0mOr.js"; function portal(node) { document.body.appendChild(node); return { destroy() { @@ -87,4 +87,4 @@ export { ConfirmModal as C, portal as p }; -//# sourceMappingURL=ConfirmModal-BGcVYndw.js.map +//# sourceMappingURL=ConfirmModal-CtR7InHn.js.map diff --git a/js/lib/svelte/GeneratedGallery-D5XbkMk7.js b/js/lib/svelte/GeneratedGallery-DgtlJz3l.js similarity index 99% rename from js/lib/svelte/GeneratedGallery-D5XbkMk7.js rename to js/lib/svelte/GeneratedGallery-DgtlJz3l.js index f4b7a53..6cefc3f 100644 --- a/js/lib/svelte/GeneratedGallery-D5XbkMk7.js +++ b/js/lib/svelte/GeneratedGallery-DgtlJz3l.js @@ -1,9 +1,9 @@ -import { d as delegate, p as push, a as prop, u as user_effect, i as if_block, g as get, o as bind_this, t as template_effect, k as append, l as pop, m as user_derived, n as child, v as first_child, f as sibling, j as delegated, r as event, A as from_html, s as state, y as set_text, w as each, h as set_class, z as index, e as set, c as proxy, C as tick, x as set_attribute, D as comment } from "./disclose-version-uq4tn5Y6.js"; -import { s as set_style } from "./style-Boi27oOu.js"; +import { d as delegate, p as push, a as prop, u as user_effect, i as if_block, g as get, o as bind_this, t as template_effect, k as append, l as pop, m as user_derived, n as child, v as first_child, f as sibling, j as delegated, r as event, A as from_html, s as state, y as set_text, w as each, h as set_class, z as index, e as set, c as proxy, C as tick, x as set_attribute, D as comment } from "./disclose-version-et9wt-4m.js"; +import { s as set_style } from "./style-B3hsaAru.js"; import { app } from "/scripts/app.js"; import { j as justifiedLayout } from "./justified-layout-cyVM7i96.js"; -import { a as action } from "./actions-WPfqiWYB.js"; -import { p as portal, C as ConfirmModal } from "./ConfirmModal-BGcVYndw.js"; +import { a as action } from "./actions-zjmT0mOr.js"; +import { p as portal, C as ConfirmModal } from "./ConfirmModal-CtR7InHn.js"; const GALLERY_ROW_HEIGHT_MIN = 40; const GALLERY_ROW_HEIGHT_MAX = 400; const ZOOM_FACTOR = 1.15; @@ -801,4 +801,4 @@ export { clampGalleryRowHeight as c, zoomGalleryRowHeight as z }; -//# sourceMappingURL=GeneratedGallery-D5XbkMk7.js.map +//# sourceMappingURL=GeneratedGallery-DgtlJz3l.js.map diff --git a/js/lib/svelte/Menubar-DC12j8gE.js b/js/lib/svelte/Menubar-CMm15KuK.js similarity index 98% rename from js/lib/svelte/Menubar-DC12j8gE.js rename to js/lib/svelte/Menubar-CMm15KuK.js index 301dc86..247c27b 100644 --- a/js/lib/svelte/Menubar-DC12j8gE.js +++ b/js/lib/svelte/Menubar-CMm15KuK.js @@ -1,10 +1,10 @@ -import { d as delegate, p as push, a as prop, u as user_effect, g as get, e as set, v as first_child, i as if_block, f as sibling, k as append, l as pop, s as state, m as user_derived, o as bind_this, j as delegated, n as child, D as comment, w as each, z as index, A as from_html, t as template_effect, h as set_class, r as event, y as set_text, x as set_attribute } from "./disclose-version-uq4tn5Y6.js"; -import { o as onDestroy } from "./index-client-iMRCrpBY.js"; -import { h as html, P as PopupAnchor } from "./PopupAnchor-C6OWGGTI.js"; -import { a as action } from "./actions-WPfqiWYB.js"; -import { s as set_style } from "./style-Boi27oOu.js"; +import { d as delegate, p as push, a as prop, u as user_effect, g as get, e as set, v as first_child, i as if_block, f as sibling, k as append, l as pop, s as state, m as user_derived, o as bind_this, j as delegated, n as child, D as comment, w as each, z as index, A as from_html, t as template_effect, h as set_class, r as event, y as set_text, x as set_attribute } from "./disclose-version-et9wt-4m.js"; +import { o as onDestroy } from "./index-client-6amB1qrM.js"; +import { h as html, P as PopupAnchor } from "./PopupAnchor-D5Lvfjom.js"; +import { a as action } from "./actions-zjmT0mOr.js"; +import { s as set_style } from "./style-B3hsaAru.js"; import { app } from "/scripts/app.js"; -import { b as bind_value } from "./input-DFQhebEz.js"; +import { b as bind_value } from "./input-B9kD0bWJ.js"; var root_1$2 = from_html(`
`, 1); var root_2$2 = from_html(`
No matching options
`); var root_5$1 = from_html(``); @@ -606,4 +606,4 @@ export { Menubar as M, SearchableList as S }; -//# sourceMappingURL=Menubar-DC12j8gE.js.map +//# sourceMappingURL=Menubar-CMm15KuK.js.map diff --git a/js/lib/svelte/PopupAnchor-C6OWGGTI.js b/js/lib/svelte/PopupAnchor-D5Lvfjom.js similarity index 96% rename from js/lib/svelte/PopupAnchor-C6OWGGTI.js rename to js/lib/svelte/PopupAnchor-D5Lvfjom.js index 6feb55c..31a9bb8 100644 --- a/js/lib/svelte/PopupAnchor-C6OWGGTI.js +++ b/js/lib/svelte/PopupAnchor-D5Lvfjom.js @@ -1,5 +1,5 @@ -import { t as template_effect, S as active_effect, T as assign_nodes, U as get_first_child, V as remove_effect_dom, W as create_element, X as NAMESPACE_SVG, Y as NAMESPACE_MATHML, b as block, Z as EFFECT_TRANSPARENT, B as BranchManager, c as proxy, d as delegate, p as push, a as prop, u as user_effect, o as bind_this, j as delegated, k as append, l as pop, A as from_html, n as child } from "./disclose-version-uq4tn5Y6.js"; -import { a as onMount, o as onDestroy } from "./index-client-iMRCrpBY.js"; +import { t as template_effect, S as active_effect, T as assign_nodes, U as get_first_child, V as remove_effect_dom, W as create_element, X as NAMESPACE_SVG, Y as NAMESPACE_MATHML, b as block, Z as EFFECT_TRANSPARENT, B as BranchManager, c as proxy, d as delegate, p as push, a as prop, u as user_effect, o as bind_this, j as delegated, k as append, l as pop, A as from_html, n as child } from "./disclose-version-et9wt-4m.js"; +import { a as onMount, o as onDestroy } from "./index-client-6amB1qrM.js"; function html(node, get_value, is_controlled = false, svg = false, mathml = false, skip_warning = false) { var anchor = node; var value = ""; @@ -155,4 +155,4 @@ export { PopupAnchor as P, html as h }; -//# sourceMappingURL=PopupAnchor-C6OWGGTI.js.map +//# sourceMappingURL=PopupAnchor-D5Lvfjom.js.map diff --git a/js/lib/svelte/SettingsSlider-CKF_XmgB.js b/js/lib/svelte/SettingsSlider-C9Ps16J6.js similarity index 98% rename from js/lib/svelte/SettingsSlider-CKF_XmgB.js rename to js/lib/svelte/SettingsSlider-C9Ps16J6.js index d68903d..ea362fa 100644 --- a/js/lib/svelte/SettingsSlider-CKF_XmgB.js +++ b/js/lib/svelte/SettingsSlider-C9Ps16J6.js @@ -1,5 +1,5 @@ -import { d as delegate, p as push, a as prop, s as state, c as proxy, u as user_effect, e as set, i as if_block, g as get, f as sibling, t as template_effect, x as set_attribute, q as set_value, j as delegated, k as append, l as pop, m as user_derived, n as child, o as bind_this, w as each, h as set_class, y as set_text, z as index, A as from_html } from "./disclose-version-uq4tn5Y6.js"; -import { s as set_style } from "./style-Boi27oOu.js"; +import { d as delegate, p as push, a as prop, s as state, c as proxy, u as user_effect, e as set, i as if_block, g as get, f as sibling, t as template_effect, x as set_attribute, q as set_value, j as delegated, k as append, l as pop, m as user_derived, n as child, o as bind_this, w as each, h as set_class, y as set_text, z as index, A as from_html } from "./disclose-version-et9wt-4m.js"; +import { s as set_style } from "./style-B3hsaAru.js"; var root_2 = from_html(`
`); var root_3 = from_html(`
`); var root_4 = from_html(`
`); @@ -267,4 +267,4 @@ delegate([ export { SettingsSlider as S }; -//# sourceMappingURL=SettingsSlider-CKF_XmgB.js.map +//# sourceMappingURL=SettingsSlider-C9Ps16J6.js.map diff --git a/js/lib/svelte/actions-WPfqiWYB.js b/js/lib/svelte/actions-zjmT0mOr.js similarity index 93% rename from js/lib/svelte/actions-WPfqiWYB.js rename to js/lib/svelte/actions-zjmT0mOr.js index d398fc4..dd3da02 100644 --- a/js/lib/svelte/actions-WPfqiWYB.js +++ b/js/lib/svelte/actions-zjmT0mOr.js @@ -1,4 +1,4 @@ -import { a4 as effect, E as untrack, a9 as render_effect, aa as deep_read_state, ab as safe_not_equal } from "./disclose-version-uq4tn5Y6.js"; +import { a4 as effect, E as untrack, a9 as render_effect, aa as deep_read_state, ab as safe_not_equal } from "./disclose-version-et9wt-4m.js"; function action(dom, action2, get_value) { effect(() => { var payload = untrack(() => action2(dom, get_value == null ? void 0 : get_value()) || {}); @@ -29,4 +29,4 @@ function action(dom, action2, get_value) { export { action as a }; -//# sourceMappingURL=actions-WPfqiWYB.js.map +//# sourceMappingURL=actions-zjmT0mOr.js.map diff --git a/js/lib/svelte/api-context-BFKo1mCD.js b/js/lib/svelte/api-context-BNqvELYR.js similarity index 93% rename from js/lib/svelte/api-context-BFKo1mCD.js rename to js/lib/svelte/api-context-BNqvELYR.js index a652f8a..b8f97a6 100644 --- a/js/lib/svelte/api-context-BFKo1mCD.js +++ b/js/lib/svelte/api-context-BNqvELYR.js @@ -1,4 +1,4 @@ -import { P as setContext, O as getContext } from "./disclose-version-uq4tn5Y6.js"; +import { P as setContext, O as getContext } from "./disclose-version-et9wt-4m.js"; const API_KEY = "pcr-api"; function provideApi(api) { setContext(API_KEY, api); @@ -41,4 +41,4 @@ export { safeJson as s, useApi as u }; -//# sourceMappingURL=api-context-BFKo1mCD.js.map +//# sourceMappingURL=api-context-BNqvELYR.js.map diff --git a/js/lib/svelte/assets/promptchain-svelte.css b/js/lib/svelte/assets/promptchain-svelte.css index 85ff5b5..eafcde9 100644 --- a/js/lib/svelte/assets/promptchain-svelte.css +++ b/js/lib/svelte/assets/promptchain-svelte.css @@ -902,6 +902,7 @@ display: flex; align-items: center; gap: 8px; padding: 5px 7px; border-radius: 4px; cursor: pointer; background: #1c1c1c; border: 1px solid #2e2e2e; + -webkit-user-select: none; user-select: none; /* the name text must not hijack a press-drag */ } .pcr-ed-layer-row.svelte-1ozzqkm:hover { border-color: #3d3d3d; } .pcr-ed-layer-row.selected.svelte-1ozzqkm { background: rgba(200, 89, 9, 0.10); border-color: #5a4630; } @@ -919,6 +920,20 @@ } .pcr-ed-layer-menu.svelte-1ozzqkm button:where(.svelte-1ozzqkm):hover { background: #c85909; color: #fff; } .pcr-ed-layer-menu.svelte-1ozzqkm button.danger:where(.svelte-1ozzqkm):hover { background: #b23b3b; } + .pcr-ed-menu-sep.svelte-1ozzqkm { height: 1px; margin: 4px 6px; background: #3a3a3a; } + /* Mask actions collapse into a hover flyout (CSS-only, no timers). The flyout is + a DOM descendant of .pcr-ed-layer-menu so the outside-click dismiss still works. */ + .pcr-ed-submenu-host.svelte-1ozzqkm { position: relative; display: flex; flex-direction: column; } + .pcr-ed-submenu-trigger.svelte-1ozzqkm { display: flex; align-items: center; justify-content: space-between; } + .pcr-ed-submenu-caret.svelte-1ozzqkm { color: #888; font-size: 11px; margin-left: 8px; } + .pcr-ed-submenu.svelte-1ozzqkm { + position: absolute; left: 100%; top: -5px; min-width: 160px; + background: #232323; border: 1px solid #444; border-radius: 6px; padding: 4px; + box-shadow: 0 6px 22px rgba(0, 0, 0, 0.5); display: none; flex-direction: column; z-index: 1; + } + .pcr-ed-submenu-host.flip.svelte-1ozzqkm .pcr-ed-submenu:where(.svelte-1ozzqkm) { left: auto; right: 100%; } + .pcr-ed-submenu-host.svelte-1ozzqkm:hover .pcr-ed-submenu:where(.svelte-1ozzqkm), .pcr-ed-submenu.svelte-1ozzqkm:hover { display: flex; } + .pcr-ed-submenu-host.svelte-1ozzqkm:hover .pcr-ed-submenu-trigger:where(.svelte-1ozzqkm) { background: #c85909; color: #fff; } /* (unused) .pcr-ed-layer-bg { cursor: default; opacity: 0.85; }*/ .pcr-ed-layer-actions.svelte-1ozzqkm { display: flex; gap: 4px; } .pcr-ed-layer-btn.svelte-1ozzqkm { @@ -928,8 +943,8 @@ .pcr-ed-layer-btn.svelte-1ozzqkm:hover:not(:disabled) { color: #fff; border-color: #555; } .pcr-ed-layer-btn.svelte-1ozzqkm:disabled { opacity: 0.4; cursor: default; } .pcr-ed-layer-grip.svelte-1ozzqkm { - flex: none; width: 12px; text-align: center; - font-size: 11px; line-height: 1; color: #555; + flex: none; width: 16px; text-align: center; + font-size: 12px; line-height: 1; color: #666; cursor: grab; user-select: none; } .pcr-ed-layer-grip.svelte-1ozzqkm:hover { color: #aaa; } @@ -940,11 +955,19 @@ user-select: none; color: #ccc; } .pcr-ed-eye.off.svelte-1ozzqkm { color: #666; } + /* Photoshop-style layer-mask thumbnail (shows the mask shape; white glow = active paint target). */ + .pcr-ed-mask-thumb.svelte-1ozzqkm { + flex: none; width: 18px; height: 18px; border-radius: 3px; + object-fit: cover; background: #000; border: 1px solid #555; cursor: pointer; + } + .pcr-ed-mask-thumb.svelte-1ozzqkm:hover { border-color: #888; } + .pcr-ed-mask-thumb.editing.svelte-1ozzqkm { border-color: #fff; box-shadow: 0 0 0 1px #fff, 0 0 0 3px rgba(255, 255, 255, 0.30); } .pcr-ed-layer-name.svelte-1ozzqkm { flex: 1; font-size: 12px; color: #ddd; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .pcr-ed-layer-blendtag.svelte-1ozzqkm { margin-left: 5px; font-size: 10px; color: #8b96a5; } .pcr-ed-layer-rename.svelte-1ozzqkm { flex: 1; min-width: 0; font-size: 12px; color: #eee; background: #1b1b1b; border: 1px solid #c85909; border-radius: 3px; padding: 1px 4px; + -webkit-user-select: text; user-select: text; /* stays editable under the row's user-select:none */ } .pcr-ed-blend-select.svelte-1ozzqkm { width: 100%; margin: 0 0 4px; padding: 3px 6px; font-size: 12px; @@ -952,6 +975,13 @@ } .pcr-ed-blend-select.svelte-1ozzqkm:disabled { opacity: 0.45; } .pcr-ed-layer-opacity.svelte-1ozzqkm { width: 70px; accent-color: #c85909; } + .pcr-ed-group-row.svelte-1ozzqkm { display: flex; align-items: center; gap: 4px; padding: 3px 5px; border: 1px solid #333; border-radius: 4px; + background: rgba(120, 130, 150, 0.12); -webkit-user-select: none; user-select: none; } + .pcr-ed-group-row.svelte-1ozzqkm .pcr-ed-eye:where(.svelte-1ozzqkm) { cursor: pointer; } + .pcr-ed-group-name.svelte-1ozzqkm { flex: 1; font-size: 12px; font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; cursor: default; } + .pcr-ed-group-x.svelte-1ozzqkm { background: none; border: none; color: #999; cursor: pointer; font-size: 13px; padding: 0 2px; } + .pcr-ed-group-x.svelte-1ozzqkm:hover { color: #fff; } + .pcr-ed-layer-row.in-group.svelte-1ozzqkm { margin-left: 14px; } .pcr-ed-hist.svelte-1ozzqkm { display: flex; flex-direction: column; max-height: 170px; overflow-y: auto; diff --git a/js/lib/svelte/disclose-version-uq4tn5Y6.js b/js/lib/svelte/disclose-version-et9wt-4m.js similarity index 99% rename from js/lib/svelte/disclose-version-uq4tn5Y6.js rename to js/lib/svelte/disclose-version-et9wt-4m.js index 05f1fb5..dd678cc 100644 --- a/js/lib/svelte/disclose-version-uq4tn5Y6.js +++ b/js/lib/svelte/disclose-version-et9wt-4m.js @@ -4282,9 +4282,9 @@ export { untrack as E, update as F, set_checked as G, - autofocus as H, - to_array as I, - from_svg as J, + to_array as H, + from_svg as I, + autofocus as J, noop as K, mount as L, unmount as M, @@ -4343,4 +4343,4 @@ export { set_text as y, index as z }; -//# sourceMappingURL=disclose-version-uq4tn5Y6.js.map +//# sourceMappingURL=disclose-version-et9wt-4m.js.map diff --git a/js/lib/svelte/index-client-iMRCrpBY.js b/js/lib/svelte/index-client-6amB1qrM.js similarity index 80% rename from js/lib/svelte/index-client-iMRCrpBY.js rename to js/lib/svelte/index-client-6amB1qrM.js index 7f40ebc..68b525d 100644 --- a/js/lib/svelte/index-client-iMRCrpBY.js +++ b/js/lib/svelte/index-client-6amB1qrM.js @@ -1,4 +1,4 @@ -import { _ as component_context, a0 as lifecycle_outside_component, u as user_effect, E as untrack } from "./disclose-version-uq4tn5Y6.js"; +import { _ as component_context, a0 as lifecycle_outside_component, u as user_effect, E as untrack } from "./disclose-version-et9wt-4m.js"; function onMount(fn) { if (component_context === null) { lifecycle_outside_component(); @@ -23,4 +23,4 @@ export { onMount as a, onDestroy as o }; -//# sourceMappingURL=index-client-iMRCrpBY.js.map +//# sourceMappingURL=index-client-6amB1qrM.js.map diff --git a/js/lib/svelte/input-DFQhebEz.js b/js/lib/svelte/input-B9kD0bWJ.js similarity index 97% rename from js/lib/svelte/input-DFQhebEz.js rename to js/lib/svelte/input-B9kD0bWJ.js index 53cd115..dc54803 100644 --- a/js/lib/svelte/input-DFQhebEz.js +++ b/js/lib/svelte/input-B9kD0bWJ.js @@ -1,4 +1,4 @@ -import { a2 as listen_to_event_and_reset_event, a3 as current_batch, C as tick, E as untrack, a9 as render_effect, a7 as is, a8 as teardown, ad as queue_micro_task } from "./disclose-version-uq4tn5Y6.js"; +import { a2 as listen_to_event_and_reset_event, a3 as current_batch, C as tick, E as untrack, a9 as render_effect, a7 as is, a8 as teardown, ad as queue_micro_task } from "./disclose-version-et9wt-4m.js"; function bind_value(input, get, set = get) { var batches = /* @__PURE__ */ new WeakSet(); listen_to_event_and_reset_event(input, "input", async (is_reset) => { @@ -150,4 +150,4 @@ export { bind_value as b, bind_group as c }; -//# sourceMappingURL=input-DFQhebEz.js.map +//# sourceMappingURL=input-B9kD0bWJ.js.map diff --git a/js/lib/svelte/promptchain-fullscreen.js b/js/lib/svelte/promptchain-fullscreen.js index 32c57b5..53d4ae8 100644 --- a/js/lib/svelte/promptchain-fullscreen.js +++ b/js/lib/svelte/promptchain-fullscreen.js @@ -5,11 +5,11 @@ var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj)); var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value); var _activeNodeId, _treeRoots, _openTabs, _activeWildcardTab, _compiledOutput, _compiledNegOutput, _imageUrl, _previewUrl, _progress, _isGenerating, _sidebarView, _fontSize, _renamingNodeId; -import { d as delegate, p as push, a as prop, i as if_block, f as sibling, t as template_effect, e as set, g as get, j as delegated, k as append, l as pop, s as state, n as child, A as from_html, x as set_attribute, u as user_effect, h as set_class, r as event, m as user_derived, R as text, y as set_text, o as bind_this, w as each, z as index, D as comment, v as first_child, c as proxy, M as unmount, L as mount } from "./disclose-version-uq4tn5Y6.js"; -import { a as onMount, o as onDestroy } from "./index-client-iMRCrpBY.js"; -import { s as set_style } from "./style-Boi27oOu.js"; -import { b as bind_value } from "./input-DFQhebEz.js"; -import { M as Menubar, S as SearchableList } from "./Menubar-DC12j8gE.js"; +import { d as delegate, p as push, a as prop, i as if_block, f as sibling, t as template_effect, e as set, g as get, j as delegated, k as append, l as pop, s as state, n as child, A as from_html, x as set_attribute, u as user_effect, h as set_class, r as event, m as user_derived, R as text, y as set_text, o as bind_this, w as each, z as index, D as comment, v as first_child, c as proxy, M as unmount, L as mount } from "./disclose-version-et9wt-4m.js"; +import { a as onMount, o as onDestroy } from "./index-client-6amB1qrM.js"; +import { s as set_style } from "./style-B3hsaAru.js"; +import { b as bind_value } from "./input-B9kD0bWJ.js"; +import { M as Menubar, S as SearchableList } from "./Menubar-CMm15KuK.js"; var root_1$4 = from_html(``); var root$5 = from_html(`
Run 0 active
`); function Topbar($$anchor, $$props) { diff --git a/js/lib/svelte/promptchain-gallery.js b/js/lib/svelte/promptchain-gallery.js index 70c9827..4d3bba0 100644 --- a/js/lib/svelte/promptchain-gallery.js +++ b/js/lib/svelte/promptchain-gallery.js @@ -1,5 +1,5 @@ -import { M as unmount, L as mount } from "./disclose-version-uq4tn5Y6.js"; -import { G as GeneratedGallery } from "./GeneratedGallery-D5XbkMk7.js"; +import { M as unmount, L as mount } from "./disclose-version-et9wt-4m.js"; +import { G as GeneratedGallery } from "./GeneratedGallery-DgtlJz3l.js"; function mountGallery(target, props) { return mount(GeneratedGallery, { target, props }); } diff --git a/js/lib/svelte/promptchain-model-indicator.js b/js/lib/svelte/promptchain-model-indicator.js index d1e1c60..1f06df8 100644 --- a/js/lib/svelte/promptchain-model-indicator.js +++ b/js/lib/svelte/promptchain-model-indicator.js @@ -1,9 +1,9 @@ -import { p as push, a as prop, D as comment, v as first_child, i as if_block, g as get, k as append, l as pop, s as state, e as set, f as sibling, t as template_effect, r as event, A as from_html, n as child, y as set_text, x as set_attribute, d as delegate, c as proxy, u as user_effect, E as untrack, o as bind_this, j as delegated, C as tick, m as user_derived, w as each, z as index, h as set_class, F as update, M as unmount, L as mount } from "./disclose-version-uq4tn5Y6.js"; -import { s as set_style } from "./style-Boi27oOu.js"; -import { b as bind_value } from "./input-DFQhebEz.js"; +import { p as push, a as prop, D as comment, v as first_child, i as if_block, g as get, k as append, l as pop, s as state, e as set, f as sibling, t as template_effect, r as event, A as from_html, n as child, y as set_text, x as set_attribute, d as delegate, c as proxy, u as user_effect, E as untrack, o as bind_this, j as delegated, C as tick, m as user_derived, w as each, z as index, h as set_class, F as update, M as unmount, L as mount } from "./disclose-version-et9wt-4m.js"; +import { s as set_style } from "./style-B3hsaAru.js"; +import { b as bind_value } from "./input-B9kD0bWJ.js"; import { e as extractPrecisions, r as resolveFilesForPrecision } from "./model-constants-cVxhUf51.js"; import { api } from "/scripts/api.js"; -import { s as safeJson, H as HttpError } from "./api-context-BFKo1mCD.js"; +import { s as safeJson, H as HttpError } from "./api-context-BNqvELYR.js"; var root_2$4 = from_html(`
`); var root_3$4 = from_html(`
`); var root_4$3 = from_html(`
`); diff --git a/js/lib/svelte/promptchain-model-settings.js b/js/lib/svelte/promptchain-model-settings.js index af80e05..9653bce 100644 --- a/js/lib/svelte/promptchain-model-settings.js +++ b/js/lib/svelte/promptchain-model-settings.js @@ -1,11 +1,11 @@ -import { d as delegate, p as push, a as prop, s as state, c as proxy, u as user_effect, e as set, f as sibling, o as bind_this, t as template_effect, g as get, x as set_attribute, q as set_value, j as delegated, k as append, l as pop, m as user_derived, n as child, A as from_html, w as each, z as index, i as if_block, G as set_checked, y as set_text, h as set_class, v as first_child, D as comment, a1 as set_selected, I as to_array, R as text, E as untrack, M as unmount, L as mount } from "./disclose-version-uq4tn5Y6.js"; -import { s as set_style } from "./style-Boi27oOu.js"; +import { d as delegate, p as push, a as prop, s as state, c as proxy, u as user_effect, e as set, f as sibling, o as bind_this, t as template_effect, g as get, x as set_attribute, q as set_value, j as delegated, k as append, l as pop, m as user_derived, n as child, A as from_html, w as each, z as index, i as if_block, G as set_checked, y as set_text, h as set_class, v as first_child, D as comment, a1 as set_selected, H as to_array, R as text, E as untrack, M as unmount, L as mount } from "./disclose-version-et9wt-4m.js"; +import { s as set_style } from "./style-B3hsaAru.js"; import { F as FAMILIES, A as ARCHITECTURES } from "./model-constants-cVxhUf51.js"; import { api } from "/scripts/api.js"; -import { S as SettingsSlider } from "./SettingsSlider-CKF_XmgB.js"; -import { b as bind_value } from "./input-DFQhebEz.js"; -import { b as bind_select_value } from "./select-Dp4ExMMc.js"; -import { a as action } from "./actions-WPfqiWYB.js"; +import { S as SettingsSlider } from "./SettingsSlider-C9Ps16J6.js"; +import { b as bind_value } from "./input-B9kD0bWJ.js"; +import { b as bind_select_value } from "./select-Bp4FHWwK.js"; +import { a as action } from "./actions-zjmT0mOr.js"; var root$7 = from_html(`
–
`); function RangeSlider($$anchor, $$props) { push($$props, true); diff --git a/js/lib/svelte/promptchain-node.js b/js/lib/svelte/promptchain-node.js index 158460d..101875a 100644 --- a/js/lib/svelte/promptchain-node.js +++ b/js/lib/svelte/promptchain-node.js @@ -5,15 +5,15 @@ var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj)); var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value); var _mode, _switchIndex, _locked, _disabled, _collapsed, _connectedCount, _hasLabels, _switchLabel, _iterateCurrent, _iterateTotal, _iterateCycle, _switchOptions, _outputPanelOpen, _imagePanelVisible, _aiAssistantOpen, _posePanelVisible, _regionPanelVisible, _hasPoseStudio, _hasRegionBox, _compiledOutput, _compiledNegOutput, _imageUrl, _previewUrl, _progress, _isGenerating, _rollSelected; -import { p as push, o as bind_this, k as append, l as pop, A as from_html, d as delegate, a as prop, g as get, j as delegated, m as user_derived, s as state, c as proxy, u as user_effect, i as if_block, f as sibling, r as event, e as set, C as tick, n as child, D as comment, v as first_child, w as each, t as template_effect, y as set_text, h as set_class, x as set_attribute, Q as clsx, z as index, R as text, E as untrack, M as unmount, L as mount } from "./disclose-version-uq4tn5Y6.js"; -import { u as useApi, p as provideApi } from "./api-context-BFKo1mCD.js"; -import { M as Menubar } from "./Menubar-DC12j8gE.js"; -import { o as onDestroy, a as onMount } from "./index-client-iMRCrpBY.js"; -import { s as set_style } from "./style-Boi27oOu.js"; -import { h as html, P as PopupAnchor } from "./PopupAnchor-C6OWGGTI.js"; +import { p as push, o as bind_this, k as append, l as pop, A as from_html, d as delegate, a as prop, g as get, j as delegated, m as user_derived, s as state, c as proxy, u as user_effect, i as if_block, f as sibling, r as event, e as set, C as tick, n as child, D as comment, v as first_child, w as each, t as template_effect, y as set_text, h as set_class, x as set_attribute, Q as clsx, z as index, R as text, E as untrack, M as unmount, L as mount } from "./disclose-version-et9wt-4m.js"; +import { u as useApi, p as provideApi } from "./api-context-BNqvELYR.js"; +import { M as Menubar } from "./Menubar-CMm15KuK.js"; +import { o as onDestroy, a as onMount } from "./index-client-6amB1qrM.js"; +import { s as set_style } from "./style-B3hsaAru.js"; +import { h as html, P as PopupAnchor } from "./PopupAnchor-D5Lvfjom.js"; import { api } from "/scripts/api.js"; -import { c as clampGalleryRowHeight, z as zoomGalleryRowHeight, G as GeneratedGallery } from "./GeneratedGallery-D5XbkMk7.js"; -import { b as bind_value } from "./input-DFQhebEz.js"; +import { c as clampGalleryRowHeight, z as zoomGalleryRowHeight, G as GeneratedGallery } from "./GeneratedGallery-DgtlJz3l.js"; +import { b as bind_value } from "./input-B9kD0bWJ.js"; import { app } from "/scripts/app.js"; import { r as readNodePrompt, c as cryptoId, e as extractNGrams, m as matchCharactersInDb, i as isStandaloneMainPromptChain, a as applyPromptText } from "./ai-patch-helpers-Bayqv0oF.js"; var root$d = from_html(`
`); diff --git a/js/lib/svelte/promptchain-sidebar.js b/js/lib/svelte/promptchain-sidebar.js index c77b542..ab1b9f8 100644 --- a/js/lib/svelte/promptchain-sidebar.js +++ b/js/lib/svelte/promptchain-sidebar.js @@ -1,11 +1,11 @@ -import { c as proxy, N as effect_root, u as user_effect, d as delegate, p as push, r as event, $ as $window, v as first_child, w as each, f as sibling, i as if_block, g as get, o as bind_this, t as template_effect, j as delegated, k as append, l as pop, s as state, e as set, m as user_derived, n as child, A as from_html, J as from_svg, h as set_class, y as set_text, z as index, q as set_value, x as set_attribute, a as prop, O as getContext, C as tick, D as comment, P as setContext, L as mount, M as unmount } from "./disclose-version-uq4tn5Y6.js"; -import { a as onMount } from "./index-client-iMRCrpBY.js"; -import { s as set_style } from "./style-Boi27oOu.js"; +import { c as proxy, N as effect_root, u as user_effect, d as delegate, p as push, r as event, $ as $window, v as first_child, w as each, f as sibling, i as if_block, g as get, o as bind_this, t as template_effect, j as delegated, k as append, l as pop, s as state, e as set, m as user_derived, n as child, A as from_html, I as from_svg, h as set_class, y as set_text, z as index, q as set_value, x as set_attribute, a as prop, O as getContext, C as tick, D as comment, P as setContext, L as mount, M as unmount } from "./disclose-version-et9wt-4m.js"; +import { a as onMount } from "./index-client-6amB1qrM.js"; +import { s as set_style } from "./style-B3hsaAru.js"; import { j as justifiedLayout } from "./justified-layout-cyVM7i96.js"; -import { a as action } from "./actions-WPfqiWYB.js"; -import { b as bind_value, c as bind_group } from "./input-DFQhebEz.js"; -import { p as portal, C as ConfirmModal } from "./ConfirmModal-BGcVYndw.js"; -import { s as safeJson, H as HttpError } from "./api-context-BFKo1mCD.js"; +import { a as action } from "./actions-zjmT0mOr.js"; +import { b as bind_value, c as bind_group } from "./input-B9kD0bWJ.js"; +import { p as portal, C as ConfirmModal } from "./ConfirmModal-CtR7InHn.js"; +import { s as safeJson, H as HttpError } from "./api-context-BNqvELYR.js"; const VIEW_MODES = [ { id: "grid", label: "Grid" }, { id: "justified", label: "Justified" }, @@ -3011,7 +3011,10 @@ function AssetBrowser($$anchor, $$props) { const item = (_c = get(ctxMenu)) == null ? void 0 : _c.item; if (item) { const root2 = scopeRoots[nav.scope] || ""; - const full = root2 ? `${root2}/${item.path}` : item.path; + let full = root2 ? `${root2}/${item.path}` : item.path; + if (/^[a-zA-Z]:[/\\]/.test(full) || full.startsWith("//")) { + full = full.replace(/\//g, "\\"); + } await navigator.clipboard.writeText(full); (_d = $$props.toast) == null ? void 0 : _d.call($$props, "info", "Path copied to clipboard"); } diff --git a/js/lib/svelte/promptchain-tag-builder.js b/js/lib/svelte/promptchain-tag-builder.js index 8467c36..2552cac 100644 --- a/js/lib/svelte/promptchain-tag-builder.js +++ b/js/lib/svelte/promptchain-tag-builder.js @@ -1,9 +1,9 @@ -import { d as delegate, p as push, a as prop, w as each, z as index, g as get, f as sibling, i as if_block, k as append, l as pop, m as user_derived, n as child, h as set_class, j as delegated, A as from_html, t as template_effect, x as set_attribute, q as set_value, v as first_child, y as set_text, D as comment, s as state, c as proxy, u as user_effect, e as set, I as to_array, E as untrack, o as bind_this, G as set_checked, M as unmount, L as mount } from "./disclose-version-uq4tn5Y6.js"; -import { a as onMount, o as onDestroy } from "./index-client-iMRCrpBY.js"; -import { a as action } from "./actions-WPfqiWYB.js"; -import { a as bind_checked, b as bind_value } from "./input-DFQhebEz.js"; +import { d as delegate, p as push, a as prop, w as each, z as index, g as get, f as sibling, i as if_block, k as append, l as pop, m as user_derived, n as child, h as set_class, j as delegated, A as from_html, t as template_effect, x as set_attribute, q as set_value, v as first_child, y as set_text, D as comment, s as state, c as proxy, u as user_effect, e as set, H as to_array, E as untrack, o as bind_this, G as set_checked, M as unmount, L as mount } from "./disclose-version-et9wt-4m.js"; +import { a as onMount, o as onDestroy } from "./index-client-6amB1qrM.js"; +import { a as action } from "./actions-zjmT0mOr.js"; +import { a as bind_checked, b as bind_value } from "./input-B9kD0bWJ.js"; import { b as buildInsertText } from "./tag-builder-utils-ng134QDV.js"; -import { b as bind_select_value } from "./select-Dp4ExMMc.js"; +import { b as bind_select_value } from "./select-Bp4FHWwK.js"; const MULTI_SELECT_GROUPS = ["props"]; const CUSTOMIZABLE_CLOTHING_GROUPS = [ "legwear", diff --git a/js/lib/svelte/promptchain-tag-builder2.js b/js/lib/svelte/promptchain-tag-builder2.js index d679572..2e59672 100644 --- a/js/lib/svelte/promptchain-tag-builder2.js +++ b/js/lib/svelte/promptchain-tag-builder2.js @@ -1,8 +1,8 @@ -import { d as delegate, p as push, a as prop, s as state, c as proxy, u as user_effect, e as set, f as sibling, n as child, i as if_block, g as get, t as template_effect, j as delegated, k as append, l as pop, v as first_child, h as set_class, y as set_text, A as from_html, m as user_derived, w as each, z as index, I as to_array, C as tick, o as bind_this, D as comment, x as set_attribute, q as set_value, R as text, M as unmount, L as mount } from "./disclose-version-uq4tn5Y6.js"; -import { a as onMount } from "./index-client-iMRCrpBY.js"; -import { a as action } from "./actions-WPfqiWYB.js"; -import { s as set_style } from "./style-Boi27oOu.js"; -import { a as bind_checked, b as bind_value } from "./input-DFQhebEz.js"; +import { d as delegate, p as push, a as prop, s as state, c as proxy, u as user_effect, e as set, f as sibling, n as child, i as if_block, g as get, t as template_effect, j as delegated, k as append, l as pop, v as first_child, h as set_class, y as set_text, A as from_html, m as user_derived, w as each, z as index, H as to_array, C as tick, o as bind_this, D as comment, x as set_attribute, q as set_value, R as text, M as unmount, L as mount } from "./disclose-version-et9wt-4m.js"; +import { a as onMount } from "./index-client-6amB1qrM.js"; +import { a as action } from "./actions-zjmT0mOr.js"; +import { s as set_style } from "./style-B3hsaAru.js"; +import { a as bind_checked, b as bind_value } from "./input-B9kD0bWJ.js"; import { b as isStructuralLine, d as isInNegativeBlock } from "./ai-patch-helpers-Bayqv0oF.js"; import { f as formatTagsForModel } from "./tag-builder-utils-ng134QDV.js"; var root_1$3 = from_html(`
Loading…
`); diff --git a/js/lib/svelte/promptchain-viewer.js b/js/lib/svelte/promptchain-viewer.js index 3c01e4d..ad8a9b6 100644 --- a/js/lib/svelte/promptchain-viewer.js +++ b/js/lib/svelte/promptchain-viewer.js @@ -1,12 +1,12 @@ -import { b as block, B as BranchManager, d as delegate, p as push, a as prop, s as state, c as proxy, u as user_effect, g as get, e as set, i as if_block, f as sibling, t as template_effect, h as set_class, j as delegated, k as append, l as pop, m as user_derived, n as child, o as bind_this, q as set_value, r as event, v as first_child, w as each, x as set_attribute, y as set_text, z as index, A as from_html, C as tick, D as comment, E as untrack, F as update, G as set_checked, H as autofocus, I as to_array, J as from_svg, $ as $window, K as noop, L as mount, M as unmount } from "./disclose-version-uq4tn5Y6.js"; -import { o as onDestroy } from "./index-client-iMRCrpBY.js"; -import { s as set_style } from "./style-Boi27oOu.js"; -import { a as action } from "./actions-WPfqiWYB.js"; -import { b as bind_value, a as bind_checked } from "./input-DFQhebEz.js"; -import { i as init_select, s as select_option, b as bind_select_value } from "./select-Dp4ExMMc.js"; -import { p as portal, C as ConfirmModal } from "./ConfirmModal-BGcVYndw.js"; -import { S as SettingsSlider } from "./SettingsSlider-CKF_XmgB.js"; -import { P as PopupAnchor, h as html } from "./PopupAnchor-C6OWGGTI.js"; +import { b as block, B as BranchManager, d as delegate, p as push, a as prop, s as state, c as proxy, u as user_effect, g as get, e as set, i as if_block, f as sibling, t as template_effect, h as set_class, j as delegated, k as append, l as pop, m as user_derived, n as child, o as bind_this, q as set_value, r as event, v as first_child, w as each, x as set_attribute, y as set_text, z as index, A as from_html, C as tick, D as comment, E as untrack, F as update, G as set_checked, H as to_array, I as from_svg, J as autofocus, $ as $window, K as noop, L as mount, M as unmount } from "./disclose-version-et9wt-4m.js"; +import { o as onDestroy } from "./index-client-6amB1qrM.js"; +import { s as set_style } from "./style-B3hsaAru.js"; +import { a as action } from "./actions-zjmT0mOr.js"; +import { b as bind_value, a as bind_checked } from "./input-B9kD0bWJ.js"; +import { i as init_select, s as select_option, b as bind_select_value } from "./select-Bp4FHWwK.js"; +import { p as portal, C as ConfirmModal } from "./ConfirmModal-CtR7InHn.js"; +import { S as SettingsSlider } from "./SettingsSlider-C9Ps16J6.js"; +import { P as PopupAnchor, h as html } from "./PopupAnchor-D5Lvfjom.js"; const NAN = Symbol("NaN"); function key(node, get_key, render_fn) { var branches = new BranchManager(node); @@ -592,8 +592,8 @@ var root_20$4 = from_html(`
M var root_24$4 = from_html(``); var root_25$3 = from_html(``); var root_23$4 = from_html(`
Character lock
keeps the character from drifting while tiles re-detail — the image itself is the reference
`); -var root_28$3 = from_html(`
`); -var root_29$4 = from_html(``); +var root_28$4 = from_html(`
`); +var root_29$3 = from_html(``); var root_30$3 = from_html(`Workflow prompt (reference) `, 1); var root_26$4 = from_html(`
`); var root_32$3 = from_html(`
This quality is bigger than the region's footprint — how should it land?
The whole document scales up and the region lands pixel-for-pixel — full quality kept. The UltraSharp-enlarged image becomes the new Background, so erasing the upscale layer reveals it.
The render is rescaled back into the region — re-detailed, but softer than 1:1.
`); @@ -619,7 +619,7 @@ var root_52$2 = from_html(``); var root_55$2 = from_html(``); var root_53$2 = from_html(` `, 1); -var root_57$2 = from_html(``); +var root_57$1 = from_html(``); var root_56$2 = from_html(` `, 1); var root_1$9 = from_html(`
`); function UpscaleOptionsModal($$anchor, $$props) { @@ -1795,12 +1795,12 @@ function UpscaleOptionsModal($$anchor, $$props) { var node_23 = first_child(fragment_4); { var consequent_22 = ($$anchor5) => { - var div_33 = root_28$3(); + var div_33 = root_28$4(); action(div_33, ($$node) => promptEditor == null ? void 0 : promptEditor($$node)); append($$anchor5, div_33); }; var alternate_1 = ($$anchor5) => { - var textarea = root_29$4(); + var textarea = root_29$3(); bind_value(textarea, () => get(prompt), ($$value) => set(prompt, $$value)); append($$anchor5, textarea); }; @@ -2346,7 +2346,7 @@ function UpscaleOptionsModal($$anchor, $$props) { var node_48 = sibling(button_9, 2); { var consequent_43 = ($$anchor4) => { - var button_10 = root_57$2(); + var button_10 = root_57$1(); delegated("click", button_10, () => confirm("workflow")); append($$anchor4, button_10); }; @@ -2428,11 +2428,11 @@ var root_23$3 = from_html(`refe
 var root_24$3 = from_html(`<img class= this image (default)`, 1); var root_27$3 = from_html(``); var root_26$3 = from_html(`
Start
End
`, 1); -var root_28$2 = from_html(`
Fidelity
Start
End
`, 1); +var root_28$3 = from_html(`
Fidelity
Start
End
`, 1); var root_22$2 = from_html(`
Strength
`, 1); var root_20$3 = from_html(`
Condition
`); var root_30$2 = from_html(``); -var root_29$3 = from_html(`
Mode
`); +var root_29$2 = from_html(`
Mode
`); var root_32$2 = from_html(` `); var root_33$2 = from_html(``); var root_31$2 = from_html(`
`, 1); @@ -3815,7 +3815,7 @@ function InpaintModal($$anchor, $$props) { append($$anchor6, fragment_8); }; var alternate_3 = ($$anchor6) => { - var fragment_9 = root_28$2(); + var fragment_9 = root_28$3(); var div_38 = first_child(fragment_9); var node_23 = sibling(child(div_38), 2); SettingsSlider(node_23, { @@ -3919,7 +3919,7 @@ function InpaintModal($$anchor, $$props) { var node_26 = sibling(node_15, 2); { var consequent_22 = ($$anchor3) => { - var div_41 = root_29$3(); + var div_41 = root_29$2(); var select_2 = sibling(child(div_41), 2); each(select_2, 21, () => MODE_ROWS, index, ($$anchor4, row) => { var option_4 = root_30$2(); @@ -4383,8 +4383,8 @@ var root_22$1 = from_html(`
Steps
CFG
On the source model, 0 = inherit the image's own steps/cfg.
`, 1); var root_26$2 = from_html(`
`); var root_27$2 = from_html(``); -var root_29$2 = from_html(``); -var root_28$1 = from_html(` `, 1); +var root_29$1 = from_html(``); +var root_28$2 = from_html(` `, 1); var root_1$7 = from_html(`
`); function I2IModal($$anchor, $$props) { push($$props, true); @@ -5213,14 +5213,14 @@ function I2IModal($$anchor, $$props) { append($$anchor3, button_3); }; var alternate_3 = ($$anchor3) => { - var fragment_7 = root_28$1(); + var fragment_7 = root_28$2(); var button_4 = first_child(fragment_7); var button_5 = sibling(button_4, 2); var text_8 = child(button_5); var node_23 = sibling(button_5, 2); { var consequent_20 = ($$anchor4) => { - var button_6 = root_29$2(); + var button_6 = root_29$1(); template_effect(() => button_6.disabled = !get(doneState)); delegated("click", button_6, () => onUseInEdit()(get(doneState))); append($$anchor4, button_6); @@ -5301,7 +5301,7 @@ function AdjustModal($$anchor, $$props) { "sourceCanvas", 3, null - // flattened composite to adjust + // the active layer's pixels to adjust (caller scopes this) ), filename = prop($$props, "filename", 3, ""), onApply = prop( $$props, "onApply", @@ -5650,6 +5650,7 @@ function AdjustModal($$anchor, $$props) { if (cH && y > 0.75) add += cH * (1 - Math.cos(Math.PI * (y - 0.75) / 0.25)) / 2; lut[i] = Math.max(0, Math.min(1, y + 0.25 * add)); } + for (let i = 1; i < N; i++) if (lut[i] < lut[i - 1]) lut[i] = lut[i - 1]; return lut; } function buildChannelLUTs(s) { @@ -6095,61 +6096,69 @@ var root_21$1 = from_html(`
Opacity
Ctrl+J copy / Ctrl+Shift+J cut to a new layer · Ctrl+C/V · Esc deselects · Feather softens the edge`, 1); var root_23$1 = from_html(``); var root_22 = from_html(` `, 1); -var root_24$1 = from_html(`
Sample
click the image to pick a color · hold to preview · Screen pick grabs from anywhere`, 1); +var root_24$1 = from_html(`
Sample
From
click the image to pick a color · hold to preview · Screen pick grabs from anywhere`, 1); var root_25$1 = from_html(``); var root_27$1 = from_html(``); var root_26$1 = from_html(`
Color
click the image to pick a color
`); -var root_29$1 = from_html(``); -var root_30$1 = from_html(`
`); -var root_31$1 = from_html(`
`); -var root_32$1 = from_svg(``); -var root_33$1 = from_html(`
`); +var root_28$1 = from_html(`
✎ Editing layer mask — paint white to reveal, black to hide
`); +var root_30$1 = from_html(``); +var root_31$1 = from_html(`
`); +var root_32$1 = from_html(`
`); +var root_33$1 = from_svg(``); var root_34$1 = from_html(`
`); -var root_35$1 = from_svg(``); -var root_37$1 = from_html(`
`, 1); -var root_38$1 = from_html(`
`); -var root_36$1 = from_html(`
`, 1); -var root_40$1 = from_html(`
`); -var root_41$1 = from_html(`
`, 1); -var root_39$1 = from_html(`
`); -var root_42$1 = from_html(`
`); -var root_43$1 = from_html(`
`); -var root_44$1 = from_html(`
`); -var root_45$1 = from_html(`
`); -var root_46$1 = from_html(`
drag / stretch · Enter applies · Esc cancels
`); -var root_47$1 = from_html(`
`); -var root_49$1 = from_html(``); -var root_50$1 = from_html(``); -var root_51$1 = from_html(``); -var root_52$1 = from_html(``); -var root_48$1 = from_html(`
`); -var root_53$1 = from_html(` `); -var root_55$1 = from_html(``); -var root_57$1 = from_html(``); -var root_59$1 = from_html(`▦`); -var root_60$1 = from_html(` `); -var root_58$1 = from_html(` `); -var root_56$1 = from_html(`
⠿
`); -var root_54$1 = from_html(`
`, 1); -var root_62$1 = from_html(`
`); -var root_61$1 = from_html(`
`); -var root_64$1 = from_html(``); -var root_65$1 = from_html(``); -var root_67$1 = from_html(``); -var root_68 = from_html(` `, 1); -var root_69 = from_html(``); -var root_70$1 = from_html(``); -var root_63$1 = from_html(`
`); -var root_72$1 = from_html(` `, 1); -var root_73$1 = from_html(` `, 1); -var root_71$1 = from_html(`
`); -var root_74$1 = from_html(`
`); -var root_75$1 = from_html(`
`); -var root_76$1 = from_html(`
⏳ Selecting subject…
`); -var root_77$1 = from_html(`
Alt-click a clean area of the image to define a source point, then paint.
`); -var root_78$1 = from_html(`
`); -var root_79$1 = from_html(`✓ Edits saved`); -var root_1$5 = from_html(`
Editing:
Navigator
`); +var root_35$1 = from_html(`
`); +var root_36$1 = from_svg(``); +var root_38$1 = from_html(`
`, 1); +var root_39$1 = from_html(`
`); +var root_37$1 = from_html(`
`, 1); +var root_41$1 = from_html(`
`); +var root_42$1 = from_html(`
`, 1); +var root_40$1 = from_html(`
`); +var root_43$1 = from_html(`
`); +var root_44$1 = from_html(`
`); +var root_45$1 = from_html(`
`); +var root_46$1 = from_html(`
`); +var root_47$1 = from_html(`
drag / stretch · Enter applies · Esc cancels
`); +var root_48$1 = from_html(`
`); +var root_50$1 = from_html(``); +var root_51$1 = from_html(``); +var root_52$1 = from_html(``); +var root_53$1 = from_html(``); +var root_49$1 = from_html(`
`); +var root_54$1 = from_html(` `); +var root_56$1 = from_html(``); +var root_59$1 = from_html(``); +var root_60$1 = from_html(` `); +var root_58$1 = from_html(`
`); +var root_62$1 = from_html(``); +var root_63$1 = from_html(``); +var root_65$1 = from_html(` `); +var root_64$1 = from_html(` `); +var root_61$1 = from_html(`
⠿
`); +var root_55$1 = from_html(`
`, 1); +var root_67$1 = from_html(`
`); +var root_66$1 = from_html(`
`); +var root_69 = from_html(``); +var root_70$1 = from_html(``); +var root_71$1 = from_html(``); +var root_73$1 = from_html(``); +var root_74$1 = from_html(``); +var root_72$1 = from_html(`
`, 1); +var root_76$1 = from_html(``); +var root_77$1 = from_html(`
`); +var root_75$1 = from_html(`
`, 1); +var root_78$1 = from_html(``); +var root_68 = from_html(`
`); +var root_80$1 = from_html(` `, 1); +var root_81$1 = from_html(` `, 1); +var root_79$1 = from_html(`
`); +var root_82$1 = from_html(`
`); +var root_83$1 = from_html(`
`); +var root_84$1 = from_html(`
⏳ Selecting subject…
`); +var root_85$1 = from_html(`
Alt-click a clean area of the image to define a source point, then paint.
`); +var root_86$1 = from_html(`
`); +var root_87$1 = from_html(`✓ Edits saved`); +var root_1$5 = from_html(`
Editing:
Navigator
`); function EditModal($$anchor, $$props) { push($$props, true); let srcUrlProp = prop($$props, "sourceUrl", 3, ""), widthProp = prop($$props, "width", 3, 0), heightProp = prop($$props, "height", 3, 0), filename = prop($$props, "filename", 3, ""), caps = prop($$props, "caps", 3, null), fetchApi = prop($$props, "fetchApi", 3, null); @@ -6339,6 +6348,14 @@ function EditModal($$anchor, $$props) { 1 // Pick: 1 | 3 | 5 px average ); + let sampleMerged = state( + true + // Pick from the flattened composite (true) or the active layer only (false) + ); + let maskEdit = state( + false + // editing the active layer's MASK (paintCanvas shows it as grayscale) vs its pixels + ); let fillTolerance = state( 32 // Paint bucket: color-match threshold @@ -6368,11 +6385,18 @@ function EditModal($$anchor, $$props) { // per-inactive-layer refs (by index) — ABOVE active only )); let belowCanvasEl; + let customEl; let nextLayerNum = 0; let selectedIds = state(proxy( [] // multi-selected layer ids (for merge); always includes the active layer )); + let groups = state(proxy( + [] + // lightweight layer groups: { id, name, collapsed, visible, opacity } + )); + let nextGroupNum = 0; + let renamingGroupId = state(null); let lhTab = state( "layers" // Layers/History share one tabbed panel (PS-style) @@ -6412,7 +6436,7 @@ function EditModal($$anchor, $$props) { let panning = false; let lastPt = null; let strokeResidual = 0; - const HIST_MAX = 12; + const HIST_MAX = 30; let history2 = state(proxy([{ label: "Open", snap: null }])); let histIndex = state(0); function commitAction(label) { @@ -6426,8 +6450,10 @@ function EditModal($$anchor, $$props) { head.push({ label, layers: [...get(layers)], + groups: [...get(groups)], activeIndex: get(activeIndex), selectedIds: [...get(selectedIds)], + sel: selSnapshot(), url: get(sourceUrl), w: get(width), h: get(height) @@ -6458,12 +6484,21 @@ function EditModal($$anchor, $$props) { const restore = () => { var _a; set(layers, [...h.layers], true); + set( + groups, + h.groups ? h.groups.map((g) => { + const cur = get(groups).find((x) => x.id === g.id); + return cur ? { ...g, collapsed: cur.collapsed } : g; + }) : [], + true + ); set(activeIndex, Math.min(h.activeIndex ?? 0, get(layers).length - 1), true); set( selectedIds, h.selectedIds ? [...h.selectedIds] : [(_a = get(layers)[get(activeIndex)]) == null ? void 0 : _a.id].filter((x) => x != null), true ); + restoreSelection(h.sel); loadActiveBitmap(); if (dimsChanged) resetView(); }; @@ -6614,6 +6649,33 @@ function EditModal($$anchor, $$props) { recomputeSelBBox(); update(selVersion); } + let _selSnapCache = { version: -1, snap: null }; + function selSnapshot() { + if (!get(selActive) || !selMaskCanvas) return null; + if (_selSnapCache.version === get(selVersion) && _selSnapCache.snap) return _selSnapCache.snap; + const snap = { + mask: selMaskCanvas.getContext("2d").getImageData(0, 0, get(width), get(height)), + bbox: selBBox ? { ...selBBox } : null + }; + _selSnapCache = { version: get(selVersion), snap }; + return snap; + } + function restoreSelection(sel) { + if (!sel || !sel.mask) { + selBBox = null; + set(selActive, false); + update(selVersion); + if (selMaskCanvas) selMaskCanvas.getContext("2d").clearRect(0, 0, get(width), get(height)); + return; + } + ensureSelMask(); + const ctx = selMaskCanvas.getContext("2d"); + ctx.clearRect(0, 0, get(width), get(height)); + ctx.putImageData(sel.mask, 0, 0); + selBBox = sel.bbox ? { ...sel.bbox } : null; + set(selActive, true); + update(selVersion); + } function selectAllRegion() { const ctx = ensureSelMask().getContext("2d"); ctx.clearRect(0, 0, get(width), get(height)); @@ -6637,7 +6699,7 @@ function EditModal($$anchor, $$props) { } let flatBlobCache = null; function flatKey() { - return `${get(histIndex)}|${get(activeIndex)}|${get(layers).map((L) => `${L.id}:${L.visible}:${L.opacity}:${L.blend || ""}:${L.ox || 0}:${L.oy || 0}`).join(",")}`; + return `${get(histIndex)}|${get(activeIndex)}|${get(layers).map((L) => `${L.id}:${L.visible}:${L.opacity}:${L.blend || ""}:${L.ox || 0}:${L.oy || 0}:${L.groupId || ""}`).join(",")}|${get(groups).map((g) => `${g.id}:${g.visible}:${g.opacity}`).join(",")}`; } async function flattenedBlobCached() { const key2 = flatKey(); @@ -6836,14 +6898,15 @@ function EditModal($$anchor, $$props) { if (x < 0 || y < 0 || x >= get(width) || y >= get(height)) return false; return selMaskCanvas.getContext("2d").getImageData(x, y, 1, 1).data[3] > 127; } - function captureSelection() { + function captureSelection(merged = false) { if (!get(selActive) || !selBBox) return null; const bb = selBBox; const c = document.createElement("canvas"); c.width = bb.w; c.height = bb.h; const cx = c.getContext("2d"); - cx.drawImage(paintCanvas, bb.x, bb.y, bb.w, bb.h, 0, 0, bb.w, bb.h); + if (merged) drawFlattened(cx, bb.x, bb.y, bb.w, bb.h, 0, 0, bb.w, bb.h); + else cx.drawImage(paintCanvas, bb.x, bb.y, bb.w, bb.h, 0, 0, bb.w, bb.h); const mask = document.createElement("canvas"); mask.width = bb.w; mask.height = bb.h; @@ -6891,6 +6954,7 @@ function EditModal($$anchor, $$props) { commitAction("Delete"); } function newLayerFromCanvas(srcCanvas, x, y, label, sourceRect = null) { + var _a; flushActive(); const lc = document.createElement("canvas"); lc.width = get(width); @@ -6907,6 +6971,7 @@ function EditModal($$anchor, $$props) { visible: true, opacity: 1, sourceRect, + groupId: (_a = get(layers)[get(activeIndex)]) == null ? void 0 : _a.groupId, bitmap: lc.getContext("2d").getImageData(0, 0, get(width), get(height)) }, ...get(layers).slice(get(activeIndex) + 1) @@ -6934,8 +6999,8 @@ function EditModal($$anchor, $$props) { if (cut) eraseSelectionFromActive(cap); newLayerFromCanvas(cap.canvas, cap.x, cap.y, cut ? "Layer via Cut" : "Layer via Copy", capSourceRect(cap)); } - function copySelection(cut) { - const cap = captureSelection(); + function copySelection(cut, merged = false) { + const cap = captureSelection(merged); if (!cap) return; clipboard = { canvas: cap.canvas, @@ -7062,6 +7127,10 @@ function EditModal($$anchor, $$props) { return p; }); let mods = state(proxy({ shift: false, alt: false, ctrl: false })); + let spaceDown = state( + false + // hold Space → temporary Hand tool (drag to pan) + ); let isSelTool = user_derived(() => get(tool) === "select" || get(tool) === "lasso" || get(tool) === "objsel"); let hiddenEditBlocked = user_derived(() => { var _a; @@ -7084,9 +7153,11 @@ function EditModal($$anchor, $$props) { }, true ); + if (e.code === "Space") set(spaceDown, e.type === "keydown"); }; const clear = () => { set(mods, { shift: false, alt: false, ctrl: false }, true); + set(spaceDown, false); }; window.addEventListener("keydown", upd, true); window.addEventListener("keyup", upd, true); @@ -7480,8 +7551,8 @@ function EditModal($$anchor, $$props) { const uctx = under.getContext("2d"); for (let j = 0; j < i; j++) { const Lj = get(layers)[j]; - if (!Lj.visible) continue; - uctx.globalAlpha = Lj.opacity ?? 1; + if (!layerShown(Lj)) continue; + uctx.globalAlpha = layerEffOpacity(Lj); if (Lj.isBackground && !Lj.bitmap && baseImg) { uctx.drawImage(baseImg, 0, 0, get(width), get(height)); } else { @@ -7618,6 +7689,7 @@ function EditModal($$anchor, $$props) { return true; } function insertResultLayer(c, maskCanvas, label, underCanvas = null) { + var _a, _b; let fm = null; if (maskCanvas) { fm = document.createElement("canvas"); @@ -7646,6 +7718,7 @@ function EditModal($$anchor, $$props) { name: `UltraSharp ${uid}`, visible: true, opacity: 1, + groupId: (_a = get(layers)[get(activeIndex)]) == null ? void 0 : _a.groupId, bitmap: underCanvas.getContext("2d").getImageData(0, 0, get(width), get(height)) }); } @@ -7655,6 +7728,7 @@ function EditModal($$anchor, $$props) { name: `${label} ${id}`, visible: true, opacity: 1, + groupId: (_b = get(layers)[get(activeIndex)]) == null ? void 0 : _b.groupId, bitmap: c.getContext("2d").getImageData(0, 0, get(width), get(height)) }); set( @@ -7966,6 +8040,19 @@ function EditModal($$anchor, $$props) { icon: `` } ]; + const TOOL_KEYS = { + v: "move", + m: "select", + l: "lasso", + b: "brush", + e: "eraser", + g: "bucket", + w: "objsel", + j: "spot", + s: "stamp", + c: "crop", + i: "eyedrop" + }; const ACTION_ICONS = { undo: ``, redo: ``, @@ -8035,16 +8122,27 @@ function EditModal($$anchor, $$props) { set(patch, null); } function cancelPatch() { - var _a; + var _a, _b; if (((_a = get(patch)) == null ? void 0 : _a.restore) && paintCanvas) { const ctx = paintCanvas.getContext("2d"); ctx.clearRect(0, 0, get(width), get(height)); ctx.drawImage(get(patch).restore, 0, 0); } + if ((_b = get(patch)) == null ? void 0 : _b.maskRestore) { + get(layers)[get(activeIndex)] = { + ...get(layers)[get(activeIndex)], + mask: get(patch).maskRestore.mask, + maskUrl: get(patch).maskRestore.maskUrl + }; + } set(patch, null); } function beginTransform() { if (get(patch)) return true; + if (get( + maskEdit + // transform operates on the layer, not its mask + )) exitMaskEdit(); if (blockHiddenEdit("transform")) return false; if (!paintCanvas || !get(layers)[get(activeIndex)]) return false; flushActive(); @@ -8105,6 +8203,18 @@ function EditModal($$anchor, $$props) { set(errorMsg, "This layer's content is off-canvas — move it back to transform."); return false; } + let maskRestore = null; + if (L.mask) { + const mc = document.createElement("canvas"); + mc.width = get(width); + mc.height = get(height); + mc.getContext("2d").putImageData(L.mask, 0, 0); + pctx.globalCompositeOperation = "destination-in"; + pctx.drawImage(mc, ox, oy); + pctx.globalCompositeOperation = "source-over"; + maskRestore = { mask: L.mask, maskUrl: L.maskUrl }; + get(layers)[get(activeIndex)] = { ...L, mask: void 0, maskUrl: void 0 }; + } const c = document.createElement("canvas"); c.width = dw; c.height = dh; @@ -8127,6 +8237,7 @@ function EditModal($$anchor, $$props) { flipV: false, transform: true, restore, + maskRestore, home: { x: dx0, y: dy0, w: dw, h: dh } }, true @@ -8276,7 +8387,6 @@ function EditModal($$anchor, $$props) { src.height = bh; const sctx = src.getContext("2d"); try { - drawBackdrop(sctx, bx, by, bw, bh, 0, 0, bw, bh); sctx.drawImage(paintCanvas, bx, by, bw, bh, 0, 0, bw, bh); } catch (e) { console.warn("[PromptChain] smooth lift failed", e); @@ -8347,7 +8457,6 @@ function EditModal($$anchor, $$props) { c.height = get(height); const ctx = c.getContext("2d", { willReadFrequently: true }); try { - drawBackdrop(ctx, 0, 0, get(width), get(height), 0, 0, get(width), get(height)); ctx.drawImage(paintCanvas, 0, 0); } catch (e) { console.warn("[PromptChain] liquify session failed", e); @@ -8558,6 +8667,7 @@ function EditModal($$anchor, $$props) { o[idx] = s[j]; o[idx + 1] = s[j + 1]; o[idx + 2] = s[j + 2]; + o[idx + 3] = s[j + 3]; } else { const fx = Math.min(maxX, Math.max(0, X + dx)); const fy = Math.min(maxY, Math.max(0, Y + dy)); @@ -8570,11 +8680,15 @@ function EditModal($$anchor, $$props) { o[idx] = s[i00] * w00 + s[i10] * w10 + s[i01] * w01 + s[i11] * w11; o[idx + 1] = s[i00 + 1] * w00 + s[i10 + 1] * w10 + s[i01 + 1] * w01 + s[i11 + 1] * w11; o[idx + 2] = s[i00 + 2] * w00 + s[i10 + 2] * w10 + s[i01 + 2] * w01 + s[i11 + 2] * w11; + o[idx + 3] = s[i00 + 3] * w00 + s[i10 + 3] * w10 + s[i01 + 3] * w01 + s[i11 + 3] * w11; } - o[idx + 3] = 255; } } paintCanvas.getContext("2d").putImageData(img, x0, y0); + if (get( + hasCustomBlend + // refresh the single-canvas custom-blend composite mid-stroke + )) update(customTick); } let cropBox = state( null @@ -8604,6 +8718,10 @@ function EditModal($$anchor, $$props) { } function setTool(id) { commitPatch(); + if (get( + maskEdit + // these don't operate on a mask + ) && (id === "adjust" || id === "crop" || id === "liquify")) exitMaskEdit(); if (id === "adjust") { openAdjust(); return; @@ -8623,7 +8741,6 @@ function EditModal($$anchor, $$props) { c.height = get(height); const ctx = c.getContext("2d", { willReadFrequently: true }); try { - drawBackdrop(ctx, 0, 0, get(width), get(height), 0, 0, get(width), get(height)); ctx.drawImage(paintCanvas, 0, 0); } catch (e) { console.warn("[PromptChain] adjust open failed", e); @@ -8633,7 +8750,22 @@ function EditModal($$anchor, $$props) { set(adjustOpen, true); } function onAdjustApply(imageData) { - paintCanvas.getContext("2d").putImageData(imageData, 0, 0); + const ctx = paintCanvas.getContext("2d"); + if (get(selActive) && selMaskCanvas) { + const orig = ctx.getImageData(0, 0, get(width), get(height)).data; + const sel = selMaskCanvas.getContext("2d").getImageData(0, 0, get(width), get(height)).data; + const a = imageData.data; + for (let i = 0; i < a.length; i += 4) { + const t = sel[i + 3] / 255; + if (t >= 1) continue; + const it = 1 - t; + a[i] = a[i] * t + orig[i] * it; + a[i + 1] = a[i + 1] * t + orig[i + 1] * it; + a[i + 2] = a[i + 2] * t + orig[i + 2] * it; + a[i + 3] = a[i + 3] * t + orig[i + 3] * it; + } + } + ctx.putImageData(imageData, 0, 0); commitAction("Camera Raw"); set(adjustOpen, false); set(adjustSrcCanvas, null); @@ -8739,6 +8871,10 @@ function EditModal($$anchor, $$props) { const ch = Math.min(get(height) - cy, Math.round(get(cropBox).h)); if (cw < 4 || ch < 4) return; flushActive(); + if (get( + selActive + // selection mask is in old-document coordinates — invalid after crop + )) deselect(); const sc = document.createElement("canvas"); sc.width = cw; sc.height = ch; @@ -8750,27 +8886,34 @@ function EditModal($$anchor, $$props) { } const newUrl = sc.toDataURL("image/png"); const ow = get(width), oh = get(height); - const cropPlane = (data) => { + const cropPlane = (data, lox, loy) => { if (!data) return void 0; const t = document.createElement("canvas"); t.width = ow; t.height = oh; t.getContext("2d").putImageData(data, 0, 0); + const doc = document.createElement("canvas"); + doc.width = ow; + doc.height = oh; + doc.getContext("2d").drawImage(t, lox, loy); const cc = document.createElement("canvas"); cc.width = cw; cc.height = ch; - cc.getContext("2d").drawImage(t, cx, cy, cw, ch, 0, 0, cw, ch); + cc.getContext("2d").drawImage(doc, cx, cy, cw, ch, 0, 0, cw, ch); return cc; }; const newLayers = get(layers).map((L) => { const next = { ...L }; - const bc = cropPlane(L.bitmap); + const lox = L.ox || 0, loy = L.oy || 0; + const bc = cropPlane(L.bitmap, lox, loy); if (bc) next.bitmap = bc.getContext("2d").getImageData(0, 0, cw, ch); - const mc = cropPlane(L.mask); + const mc = cropPlane(L.mask, lox, loy); if (mc) { next.mask = mc.getContext("2d").getImageData(0, 0, cw, ch); next.maskUrl = mc.toDataURL(); } + next.ox = 0; + next.oy = 0; return next; }); set(sourceUrl, newUrl, true); @@ -8858,6 +9001,9 @@ function EditModal($$anchor, $$props) { set(saving, false); set(errorMsg, ""); set(tool, "brush"); + set(maskEdit, false); + set(groups, [], true); + set(renamingGroupId, null); set(sourceUrl, srcUrlProp()); set(width, widthProp()); set(height, heightProp()); @@ -8874,6 +9020,7 @@ function EditModal($$anchor, $$props) { user_effect(() => { if (!$$props.open) return; const onKey = (e) => { + var _a; if (get( adjustOpen // Camera Raw / an elevated handoff modal owns the keyboard @@ -8906,10 +9053,21 @@ function EditModal($$anchor, $$props) { e.preventDefault(); e.stopPropagation(); redo(); - } else if ((e.ctrlKey || e.metaKey) && (e.key === "j" || e.key === "J") && get(selActive)) { + } else if ((e.ctrlKey || e.metaKey) && (e.key === "j" || e.key === "J")) { e.preventDefault(); e.stopPropagation(); - layerViaCopy(e.shiftKey); + if (get( + selActive + // Ctrl+J copy / Ctrl+Shift+J cut → new layer + )) layerViaCopy(e.shiftKey); + else if (!e.shiftKey) duplicateLayer(get( + activeIndex + // no selection → duplicate the whole layer (PS) + )); + } else if ((e.ctrlKey || e.metaKey) && e.shiftKey && (e.key === "c" || e.key === "C") && get(selActive)) { + e.preventDefault(); + e.stopPropagation(); + copySelection(false, true); } else if ((e.ctrlKey || e.metaKey) && (e.key === "c" || e.key === "C") && get(selActive)) { e.preventDefault(); e.stopPropagation(); @@ -8926,14 +9084,25 @@ function EditModal($$anchor, $$props) { e.preventDefault(); e.stopPropagation(); addLayer(); + } else if ((e.ctrlKey || e.metaKey) && e.shiftKey && (e.key === "g" || e.key === "G")) { + e.preventDefault(); + e.stopPropagation(); + const g = groupById((_a = get(layers)[get(activeIndex)]) == null ? void 0 : _a.groupId); + if (g) ungroup(g.id); + } else if ((e.ctrlKey || e.metaKey) && (e.key === "g" || e.key === "G")) { + e.preventDefault(); + e.stopPropagation(); + if (get(selectedIds).length) groupSelected(); } else if ((e.ctrlKey || e.metaKey) && (e.key === "a" || e.key === "A")) { e.preventDefault(); e.stopPropagation(); selectAllRegion(); + commitAction("Select All"); } else if ((e.ctrlKey || e.metaKey) && e.shiftKey && (e.key === "i" || e.key === "I") && get(selActive)) { e.preventDefault(); e.stopPropagation(); selectInverse(); + commitAction("Select Inverse"); } else if (e.key === "Backspace" && (e.altKey || e.ctrlKey || e.metaKey)) { e.preventDefault(); e.stopPropagation(); @@ -8953,6 +9122,57 @@ function EditModal($$anchor, $$props) { e.preventDefault(); e.stopPropagation(); deselect(); + commitAction("Deselect"); + } else if ((e.ctrlKey || e.metaKey) && e.key === "0") { + e.preventDefault(); + e.stopPropagation(); + resetView(); + } else if ((e.ctrlKey || e.metaKey) && (e.key === "=" || e.key === "+")) { + e.preventDefault(); + e.stopPropagation(); + zoomBy(1.25); + } else if ((e.ctrlKey || e.metaKey) && (e.key === "-" || e.key === "_")) { + e.preventDefault(); + e.stopPropagation(); + zoomBy(1 / 1.25); + } else if (!e.ctrlKey && !e.metaKey && !e.altKey && (e.key === "[" || e.key === "]")) { + e.preventDefault(); + e.stopPropagation(); + const dir = e.key === "]" ? 1 : -1; + const step = get(brushSize) < 10 ? 1 : get(brushSize) < 50 ? 2 : get(brushSize) < 100 ? 5 : 10; + set(brushSize, Math.max(1, Math.min(1024, get(brushSize) + dir * step)), true); + } else if (!e.ctrlKey && !e.metaKey && !e.altKey && !get(colorPopout) && (e.key === "x" || e.key === "X")) { + e.preventDefault(); + e.stopPropagation(); + const fg = get( + rgb + // swap foreground/background + ); + (($$value) => { + var $$array = to_array($$value, 3); + set(hue, $$array[0], true); + set(sat, $$array[1], true); + set(val, $$array[2], true); + })(rgbToHsv(get(bgRgb)[0], get(bgRgb)[1], get(bgRgb)[2])); + set(bgRgb, fg, true); + } else if (!e.ctrlKey && !e.metaKey && !e.altKey && !get(colorPopout) && (e.key === "d" || e.key === "D")) { + e.preventDefault(); + e.stopPropagation(); + set( + hue, + 0 + // default colors: black foreground / white background + ); + set(sat, 0); + set(val, 0); + set(bgRgb, [255, 255, 255], true); + } else if (!e.ctrlKey && !e.metaKey && !e.altKey && !get(patch) && !get(colorPopout) && TOOL_KEYS[(e.key || "").toLowerCase()]) { + e.preventDefault(); + e.stopPropagation(); + setTool(TOOL_KEYS[e.key.toLowerCase()]); + } else if (e.key === " ") { + e.preventDefault(); + e.stopPropagation(); } else if ((e.key === "ArrowUp" || e.key === "ArrowDown" || e.key === "ArrowLeft" || e.key === "ArrowRight") && !e.ctrlKey && !e.metaKey && !e.altKey && !get(patch) && !get(colorPopout) && !get(healDialog) && get(tool) !== "crop") { e.preventDefault(); e.stopPropagation(); @@ -8991,8 +9211,10 @@ function EditModal($$anchor, $$props) { else if (get( selActive // first Esc drops the selection - )) deselect(); - else if (get(tool) === "crop" && get(cropBox) && !cropBoxIsFull()) resetCropBox(); + )) { + deselect(); + commitAction("Deselect"); + } else if (get(tool) === "crop" && get(cropBox) && !cropBoxIsFull()) resetCropBox(); else close(); } }; @@ -9024,10 +9246,10 @@ function EditModal($$anchor, $$props) { } function setColorFromRgb(r, g, b) { (($$value) => { - var $$array = to_array($$value, 3); - set(hue, $$array[0], true); - set(sat, $$array[1], true); - set(val, $$array[2], true); + var $$array_1 = to_array($$value, 3); + set(hue, $$array_1[0], true); + set(sat, $$array_1[1], true); + set(val, $$array_1[2], true); })(rgbToHsv(r, g, b)); } const NAV_W = 256; @@ -9178,6 +9400,16 @@ function EditModal($$anchor, $$props) { set(panX, (rect.width - get(width) * get(zoom)) / 2); set(panY, (rect.height - get(height) * get(zoom)) / 2); } + function zoomBy(factor) { + if (!viewportEl) return; + const rect = viewportEl.getBoundingClientRect(); + const cx = rect.width / 2, cy = rect.height / 2; + const newZoom = Math.max(0.05, Math.min(12, get(zoom) * factor)); + set(panX, cx - (cx - get(panX)) / get(zoom) * newZoom); + set(panY, cy - (cy - get(panY)) / get(zoom) * newZoom); + set(zoom, newZoom, true); + set(fitMode, false); + } function toImageCoords(e) { const rect = viewportEl.getBoundingClientRect(); return { @@ -9360,7 +9592,8 @@ function EditModal($$anchor, $$props) { for (let x = 0; x < bw; x++) { const i = y * bw + x; const gi = (y0 + y) * get(width) + x0 + x; - const cov = Math.min(1, strokeCov[gi]); + let cov = Math.min(1, strokeCov[gi]); + if (selStrokeData) cov *= selStrokeData[gi * 4 + 3] / 255; const o = i * 4, j = gi * 4; if (cov <= 0) { px[o] = pd[j]; @@ -9375,7 +9608,7 @@ function EditModal($$anchor, $$props) { const target = cov * healed + (1 - cov) * dest[i * 3 + ch]; px[o + ch] = Math.max(0, Math.min(255, Math.round(target + d))); } - px[o + 3] = 255; + px[o + 3] = pd[j + 3]; } } paintCanvas.getContext("2d").putImageData(img, x0, y0); @@ -9476,7 +9709,6 @@ function EditModal($$anchor, $$props) { const img = new ImageData(bw, bh); const px = img.data; const pd = preStroke.data; - const sd = sourceData.data; for (let y = 0; y < bh; y++) { const row = (y0 + y) * get(width) + x0; for (let x = 0; x < bw; x++) { @@ -9496,15 +9728,34 @@ function EditModal($$anchor, $$props) { const d = dither(x0 + x, y0 + y); const sj = stampMode ? (Math.max(0, Math.min(get(height) - 1, y0 + y + ody)) * get(width) + Math.max(0, Math.min(get(width) - 1, x0 + x + odx))) * 4 : 0; const spa = stampMode ? pd[sj + 3] / 255 : 0; + const se = spa * cov; + const na = healMode ? pa : ( + // heal preview re-textures; alpha unchanged + erase ? pa * (1 - cov) : ( + // eraser = destination-out on own alpha + stampMode ? se + pa * (1 - se) : ( + // clone source-over + cov + pa * (1 - cov) + ) + ) + ); + const naInv = na > 0 ? 1 / na : 0; for (let ch = 0; ch < 3; ch++) { - const comp = pd[j + ch] * pa + sd[j + ch] * (1 - pa); - const target = healMode ? comp * (1 - cov * 0.4) + 200 * cov * 0.4 : stampMode ? (pd[sj + ch] * spa + sd[sj + ch] * (1 - spa)) * cov + comp * (1 - cov) : erase ? comp * (1 - cov) + sd[j + ch] * cov : (ch === 0 ? cr : ch === 1 ? cg : cb) * cov + comp * (1 - cov); - px[i + ch] = Math.max(0, Math.min(255, Math.round(target + d))); + const lc = pd[j + ch]; + const target = healMode ? lc * (1 - cov * 0.4) + 200 * cov * 0.4 : erase ? lc : ( + // RGB stays; alpha carries the erase + stampMode ? (pd[sj + ch] * se + lc * pa * (1 - se)) * naInv : ((ch === 0 ? cr : ch === 1 ? cg : cb) * cov + lc * pa * (1 - cov)) * naInv + ); + px[i + ch] = erase ? lc : Math.max(0, Math.min(255, Math.round(target + d))); } - px[i + 3] = 255; + px[i + 3] = Math.max(0, Math.min(255, Math.round(na * 255))); } } paintCanvas.getContext("2d").putImageData(img, x0, y0); + if (get( + hasCustomBlend + // refresh the single-canvas custom-blend composite mid-stroke + )) update(customTick); } const AIRBRUSH_DABS_PER_SEC = 30; let airRafId = 0; @@ -9780,6 +10031,110 @@ function EditModal($$anchor, $$props) { } } const activeLayer = () => get(layers)[get(activeIndex)] || get(layers)[0]; + const groupById = (gid) => gid ? get(groups).find((g) => g.id === gid) : null; + const layerShown = (L) => { + var _a; + return !!(L == null ? void 0 : L.visible) && ((_a = groupById(L == null ? void 0 : L.groupId)) == null ? void 0 : _a.visible) !== false; + }; + const layerEffOpacity = (L) => { + var _a; + return ((L == null ? void 0 : L.opacity) ?? 1) * (((_a = groupById(L == null ? void 0 : L.groupId)) == null ? void 0 : _a.opacity) ?? 1); + }; + let panelRows = user_derived(() => { + const rows = []; + const seen = /* @__PURE__ */ new Set(); + for (let i = get(layers).length - 1; i >= 0; i--) { + const L = get(layers)[i]; + const g = groupById(L.groupId); + if (g && !seen.has(g.id)) { + seen.add(g.id); + rows.push({ type: "group", group: g }); + } + if (g && g.collapsed) continue; + rows.push({ type: "layer", L, i, inGroup: !!g }); + } + return rows; + }); + function groupSelected() { + var _a; + const ids = get(selectedIds).filter((id) => { + const L = get(layers).find((x) => x.id === id); + return L && !L.isBackground; + }); + if (!ids.length) return; + flushActive(); + const gid = ++nextGroupNum; + const members = get(layers).filter((L) => ids.includes(L.id)).map((L) => ({ ...L, groupId: gid })); + const rest = get(layers).filter((L) => !ids.includes(L.id)); + const topSelIdx = Math.max(...get(layers).map((L, i) => ids.includes(L.id) ? i : -1)); + let insertAt = 0; + for (let i = 0; i < topSelIdx; i++) if (!ids.includes(get(layers)[i].id)) insertAt++; + const activeId = (_a = get(layers)[get(activeIndex)]) == null ? void 0 : _a.id; + set( + groups, + [ + ...get(groups), + { + id: gid, + name: `Group ${gid}`, + collapsed: false, + visible: true, + opacity: 1 + } + ], + true + ); + set( + layers, + [ + ...rest.slice(0, insertAt), + ...members, + ...rest.slice(insertAt) + ], + true + ); + set(activeIndex, Math.max(0, get(layers).findIndex((L) => L.id === activeId)), true); + set(selectedIds, members.map((L) => L.id), true); + loadActiveBitmap(); + commitAction("Group Layers"); + } + function ungroup(gid) { + if (!get(groups).some((g) => g.id === gid)) return; + flushActive(); + set(layers, get(layers).map((L) => L.groupId === gid ? { ...L, groupId: void 0 } : L), true); + set(groups, get(groups).filter((g) => g.id !== gid), true); + commitAction("Ungroup"); + } + function toggleGroupCollapsed(gid) { + set(groups, get( + groups + // view-only, no history + ).map((g) => g.id === gid ? { ...g, collapsed: !g.collapsed } : g), true); + } + function setGroupVisible(gid, v) { + set(groups, get(groups).map((g) => g.id === gid ? { ...g, visible: v } : g), true); + commitAction("Group Visibility"); + } + function setGroupOpacity(gid, o) { + set(groups, get( + groups + // commit on release (slider onchange) + ).map((g) => g.id === gid ? { ...g, opacity: o } : g), true); + } + function startGroupRename(g) { + set(renamingGroupId, g.id, true); + set(renameText, g.name, true); + } + function commitGroupRename() { + if (get(renamingGroupId) == null) return; + const gid = get(renamingGroupId); + set(renamingGroupId, null); + const name = get(renameText).trim(); + if (name) { + set(groups, get(groups).map((g) => g.id === gid ? { ...g, name } : g), true); + commitAction("Rename Group"); + } + } let hiddenEditDialog = state( null // PS "target layer is hidden" modal text, or null @@ -9807,6 +10162,7 @@ function EditModal($$anchor, $$props) { } function activeMaskedCanvas() { const L = activeLayer(); + if (get(maskEdit) && (L == null ? void 0 : L.mask)) return maskedRenderCanvas(L); if (!(L == null ? void 0 : L.mask)) return paintCanvas; const t = document.createElement("canvas"); t.width = get(width); @@ -9875,6 +10231,18 @@ function EditModal($$anchor, $$props) { if (!paintCanvas || !get(layers)[get(activeIndex)]) return; const L = get(layers)[get(activeIndex)]; const ox = L.ox || 0, oy = L.oy || 0; + if (get(maskEdit)) { + if (!L.mask) return; + const gray = document.createElement("canvas"); + gray.width = get(width); + gray.height = get(height); + const gx = gray.getContext("2d"); + gx.drawImage(maskToGrayCanvas(L.mask), 0, 0); + gx.clearRect(-ox, -oy, get(width), get(height)); + gx.drawImage(paintCanvas, -ox, -oy); + writeLayerMask(get(activeIndex), grayToMaskImageData(gx.getImageData(0, 0, get(width), get(height)))); + return; + } if (!ox && !oy) { get(layers)[get(activeIndex)] = { ...L, @@ -9897,22 +10265,46 @@ function EditModal($$anchor, $$props) { function loadActiveBitmap() { const ctx = paintCanvas == null ? void 0 : paintCanvas.getContext("2d"); if (!ctx) return; - ctx.clearRect(0, 0, get(width), get(height)); const L = get(layers)[get(activeIndex)]; - if (L) drawLayerRaw(ctx, L, 0, 0, get(width), get(height), 0, 0, get(width), get(height)); + if (get( + maskEdit + // active layer has no mask → leave mask-edit (safety net) + ) && !(L == null ? void 0 : L.mask)) set(maskEdit, false); + ctx.clearRect(0, 0, get(width), get(height)); + if (!L) return; + if (get(maskEdit) && L.mask) { + ctx.drawImage(maskToGrayCanvas(L.mask), L.ox || 0, L.oy || 0); + return; + } + drawLayerRaw(ctx, L, 0, 0, get(width), get(height), 0, 0, get(width), get(height)); } function selectLayer(i) { if (i < 0 || i >= get(layers).length || painting) return; - if (i !== get(activeIndex)) { - flushActive(); - set(activeIndex, i, true); - loadActiveBitmap(); - } + if (i === get(activeIndex)) return; + if (get( + maskEdit + // clicking another layer targets its pixels, not a mask + )) exitMaskEdit(); + commitPatch(); + flushActive(); + set(activeIndex, i, true); + loadActiveBitmap(); } function clickLayer(i, e) { + var _a; const id = get(layers)[i].id; if ((e.ctrlKey || e.metaKey) && get(selectedIds).length) { - set(selectedIds, get(selectedIds).includes(id) ? get(selectedIds) : [...get(selectedIds), id], true); + if (get(selectedIds).includes(id)) { + if (get(selectedIds).length > 1) { + set(selectedIds, get(selectedIds).filter((x) => x !== id), true); + if (id === ((_a = get(layers)[get(activeIndex)]) == null ? void 0 : _a.id)) { + const s = get(layers).findIndex((L) => get(selectedIds).includes(L.id)); + if (s >= 0) selectLayer(s); + } + } + return; + } + set(selectedIds, [...get(selectedIds), id], true); } else if (e.shiftKey && get(selectedIds).length) { const lo = Math.min(get(activeIndex), i), hi = Math.max(get(activeIndex), i); set(selectedIds, get(layers).slice(lo, hi + 1).map((L) => L.id), true); @@ -9923,6 +10315,7 @@ function EditModal($$anchor, $$props) { if (!get(selectedIds).includes(id)) set(selectedIds, [...get(selectedIds), id], true); } function addLayer(at = get(activeIndex)) { + var _a; if (painting) return; flushActive(); const id = ++nextLayerNum; @@ -9935,7 +10328,8 @@ function EditModal($$anchor, $$props) { name: `Layer ${id}`, visible: true, opacity: 1, - bitmap: null + bitmap: null, + groupId: (_a = get(layers)[at]) == null ? void 0 : _a.groupId }, ...get(layers).slice(at + 1) ], @@ -9944,6 +10338,7 @@ function EditModal($$anchor, $$props) { set(activeIndex, at + 1); set(selectedIds, [id], true); loadActiveBitmap(); + commitAction("New Layer"); } function canDeleteSelected() { return get(layers).length > 1 && get(layers).some((L) => get(selectedIds).includes(L.id) && !L.isBackground); @@ -9962,6 +10357,7 @@ function EditModal($$anchor, $$props) { ); k++) if (!doomedIds.has(get(layers)[k].id)) na++; const survivors = get(layers).filter((L) => !doomedIds.has(L.id)); set(layers, survivors, true); + set(groups, get(groups).filter((g) => get(layers).some((L) => L.groupId === g.id)), true); set(activeIndex, Math.min(na, survivors.length - 1), true); set(selectedIds, [(_b = get(layers)[get(activeIndex)]) == null ? void 0 : _b.id].filter((x) => x != null), true); loadActiveBitmap(); @@ -9995,6 +10391,7 @@ function EditModal($$anchor, $$props) { mask: src.mask, maskUrl: src.maskUrl, sourceRect: src.sourceRect, + groupId: src.groupId, bitmap }, ...get(layers).slice(i + 1) @@ -10024,16 +10421,78 @@ function EditModal($$anchor, $$props) { ["lighten", "Lighten"], ["color-dodge", "Color Dodge"], ["color-burn", "Color Burn"], + ["linear-dodge", "Linear Dodge (Add)"], + ["subtract", "Subtract"], + ["divide", "Divide"], ["hard-light", "Hard Light"], ["soft-light", "Soft Light"], + ["vivid-light", "Vivid Light"], + ["linear-light", "Linear Light"], + ["pin-light", "Pin Light"], + ["hard-mix", "Hard Mix"], ["difference", "Difference"], ["exclusion", "Exclusion"], ["hue", "Hue"], ["saturation", "Saturation"], ["color", "Color"], - ["luminosity", "Luminosity"] + ["luminosity", "Luminosity"], + ["darker-color", "Darker Color"], + ["lighter-color", "Lighter Color"] ]; - const layerBlendCss = (L) => (L == null ? void 0 : L.blend) && L.blend !== "normal" ? L.blend : null; + const CUSTOM_BLENDS = /* @__PURE__ */ new Set([ + "linear-dodge", + "subtract", + "divide", + "vivid-light", + "linear-light", + "pin-light", + "hard-mix", + "darker-color", + "lighter-color" + ]); + const isCustomBlend = (b) => CUSTOM_BLENDS.has(b); + let hasCustomBlend = user_derived(() => get(layers).some((L) => layerShown(L) && isCustomBlend(L.blend))); + let customTick = state( + 0 + // bumped after a paint dab so the single-canvas composite refreshes live + ); + function blendChannel(mode, b, s) { + switch (mode) { + case "linear-dodge": + return Math.min(1, b + s); + case "subtract": + return Math.max(0, b - s); + case "divide": + return s <= 0 ? 1 : Math.min(1, b / s); + case "linear-light": + return Math.max(0, Math.min(1, b + 2 * s - 1)); + case "pin-light": + return s <= 0.5 ? Math.min(b, 2 * s) : Math.max(b, 2 * s - 1); + case "vivid-light": + if (s <= 0.5) { + const d = 2 * s; + return d <= 0 ? 0 : Math.max(0, 1 - (1 - b) / d); + } + { + const d = 2 * (1 - s); + return d <= 0 ? 1 : Math.min(1, b / d); + } + case "hard-mix": { + let v; + if (s <= 0.5) { + const d = 2 * s; + v = d <= 0 ? 0 : Math.max(0, 1 - (1 - b) / d); + } else { + const d = 2 * (1 - s); + v = d <= 0 ? 1 : Math.min(1, b / d); + } + return v < 0.5 ? 0 : 1; + } + default: + return s; + } + } + const layerBlendCss = (L) => (L == null ? void 0 : L.blend) && L.blend !== "normal" && !isCustomBlend(L.blend) ? L.blend : null; function setLayerBlend(i, blend) { get(layers)[i] = { ...get(layers)[i], @@ -10105,9 +10564,107 @@ function EditModal($$anchor, $$props) { function deleteLayerMask(i) { var _a; if (!((_a = get(layers)[i]) == null ? void 0 : _a.mask)) return; + if (get(maskEdit) && i === get(activeIndex)) set(maskEdit, false); get(layers)[i] = { ...get(layers)[i], mask: void 0, maskUrl: void 0 }; + if (i === get(activeIndex)) loadActiveBitmap(); commitAction("Delete Layer Mask"); } + function maskToGrayCanvas(mask) { + const c = document.createElement("canvas"); + c.width = get(width); + c.height = get(height); + const ctx = c.getContext("2d"); + if (mask) { + const g = new ImageData(get(width), get(height)), d = g.data, m = mask.data; + for (let i = 0; i < d.length; i += 4) { + const v = m[i + 3]; + d[i] = v; + d[i + 1] = v; + d[i + 2] = v; + d[i + 3] = 255; + } + ctx.putImageData(g, 0, 0); + } else { + ctx.fillStyle = "#fff"; + ctx.fillRect(0, 0, get(width), get(height)); + } + return c; + } + function grayToMaskImageData(gray) { + const s = gray.data, out = new ImageData(get(width), get(height)), d = out.data; + for (let i = 0; i < s.length; i += 4) { + const lum = s[i] * 0.299 + s[i + 1] * 0.587 + s[i + 2] * 0.114; + d[i] = 255; + d[i + 1] = 255; + d[i + 2] = 255; + d[i + 3] = Math.round(lum * (s[i + 3] / 255)); + } + return out; + } + function writeLayerMask(i, maskId) { + const mc = document.createElement("canvas"); + mc.width = get(width); + mc.height = get(height); + mc.getContext("2d").putImageData(maskId, 0, 0); + get(layers)[i] = { ...get(layers)[i], mask: maskId, maskUrl: mc.toDataURL() }; + } + function enterMaskEdit(i = get(activeIndex)) { + var _a; + if (i !== get(activeIndex)) selectLayer(i); + if (!((_a = get(layers)[get(activeIndex)]) == null ? void 0 : _a.mask) || get(maskEdit) || painting) return; + flushActive(); + set(maskEdit, true); + set( + hue, + 0 + // default to white = reveal (X/D still work) + ); + set(sat, 0); + set(val, 1); + loadActiveBitmap(); + } + function exitMaskEdit() { + if (!get(maskEdit)) return; + flushActive(); + set(maskEdit, false); + loadActiveBitmap(); + } + function toggleMaskEdit(i) { + if (get(maskEdit) && i === get(activeIndex)) exitMaskEdit(); + else enterMaskEdit(i); + } + function invertLayerMask(i) { + var _a; + if (!((_a = get(layers)[i]) == null ? void 0 : _a.mask)) return; + if (get( + maskEdit + // pull live paint into the mask first + ) && i === get(activeIndex)) flushActive(); + const m = get(layers)[i].mask, inv = new ImageData(get(width), get(height)), d = inv.data, s = m.data; + for (let k = 0; k < d.length; k += 4) { + d[k] = 255; + d[k + 1] = 255; + d[k + 2] = 255; + d[k + 3] = 255 - s[k + 3]; + } + writeLayerMask(i, inv); + if (get(maskEdit) && i === get(activeIndex)) loadActiveBitmap(); + commitAction("Invert Mask"); + } + function fillLayerMask(i, vis) { + var _a; + if (!((_a = get(layers)[i]) == null ? void 0 : _a.mask)) return; + const fill = new ImageData(get(width), get(height)), d = fill.data; + for (let k = 0; k < d.length; k += 4) { + d[k] = 255; + d[k + 1] = 255; + d[k + 2] = 255; + d[k + 3] = vis; + } + writeLayerMask(i, fill); + if (get(maskEdit) && i === get(activeIndex)) loadActiveBitmap(); + commitAction(vis ? "Reveal All" : "Hide All"); + } function startRename(L) { set(renamingId, L.id, true); set(renameText, L.name, true); @@ -10122,6 +10679,7 @@ function EditModal($$anchor, $$props) { set(renamingId, null); } function dropLayer(targetId) { + var _a; set(dragOverId, null); if (dragId == null || dragId === targetId) { dragId = null; @@ -10138,12 +10696,53 @@ function EditModal($$anchor, $$props) { const ti = rest.findIndex((L) => L.id === targetId); let at = ti < 0 ? rest.length : ti + 1; if (at < 1) at = 1; - rest.splice(at, 0, moved); + const targetGroupId = (_a = rest[ti]) == null ? void 0 : _a.groupId; + rest.splice(at, 0, { ...moved, groupId: targetGroupId }); set(layers, rest, true); + set(groups, get(groups).filter((g) => get(layers).some((L) => L.groupId === g.id)), true); set(activeIndex, get(layers).findIndex((L) => L.id === activeId), true); dragId = null; commitAction("Reorder Layers"); } + function blendLayerFull(ctx, srcCanvas, mode, eff) { + const baseImg = ctx.getImageData(0, 0, get(width), get(height)); + const srcImg = srcCanvas.getContext("2d").getImageData(0, 0, get(width), get(height)); + const bd = baseImg.data, sd = srcImg.data; + const sep = mode !== "darker-color" && mode !== "lighter-color"; + for (let p = 0; p < bd.length; p += 4) { + const sa = sd[p + 3] / 255 * eff; + if (sa <= 0) continue; + const br = bd[p] / 255, bg = bd[p + 1] / 255, bb = bd[p + 2] / 255; + const sr = sd[p] / 255, sg = sd[p + 1] / 255, sbb = sd[p + 2] / 255; + let rr, rg, rb; + if (sep) { + rr = blendChannel(mode, br, sr); + rg = blendChannel(mode, bg, sg); + rb = blendChannel(mode, bb, sbb); + } else { + const lb = 0.299 * br + 0.587 * bg + 0.114 * bb, ls = 0.299 * sr + 0.587 * sg + 0.114 * sbb; + const keepBase = mode === "darker-color" ? lb <= ls : lb >= ls; + rr = keepBase ? br : sr; + rg = keepBase ? bg : sg; + rb = keepBase ? bb : sbb; + } + const ba = bd[p + 3] / 255, oa = sa + ba * (1 - sa), inv = oa > 0 ? 1 / oa : 0; + const cr = (1 - ba) * sr + ba * rr, cg = (1 - ba) * sg + ba * rg, cb2 = (1 - ba) * sbb + ba * rb; + bd[p] = Math.round((cr * sa + br * ba * (1 - sa)) * inv * 255); + bd[p + 1] = Math.round((cg * sa + bg * ba * (1 - sa)) * inv * 255); + bd[p + 2] = Math.round((cb2 * sa + bb * ba * (1 - sa)) * inv * 255); + bd[p + 3] = Math.round(oa * 255); + } + ctx.putImageData(baseImg, 0, 0); + } + function layerFullCanvas(L, isActive) { + const tmp = document.createElement("canvas"); + tmp.width = get(width); + tmp.height = get(height); + if (isActive) tmp.getContext("2d").drawImage(activeMaskedCanvas(), 0, 0); + else drawLayerContent(tmp.getContext("2d"), L, 0, 0, get(width), get(height), 0, 0, get(width), get(height)); + return tmp; + } function compositeLayers(idxs) { const c = document.createElement("canvas"); c.width = get(width); @@ -10151,8 +10750,13 @@ function EditModal($$anchor, $$props) { const ctx = c.getContext("2d"); for (const i of idxs) { const L = get(layers)[i]; - if (!L.visible) continue; - ctx.globalAlpha = L.opacity; + if (!layerShown(L)) continue; + const eff = layerEffOpacity(L); + if (isCustomBlend(L.blend)) { + blendLayerFull(ctx, layerFullCanvas(L, i === get(activeIndex)), L.blend, eff); + continue; + } + ctx.globalAlpha = eff; ctx.globalCompositeOperation = layerBlendCss(L) || "source-over"; if (i === get(activeIndex)) ctx.drawImage(activeMaskedCanvas(), 0, 0); else drawLayerContent(ctx, L, 0, 0, get(width), get(height), 0, 0, get(width), get(height)); @@ -10171,18 +10775,21 @@ function EditModal($$anchor, $$props) { const lo = idxs[0]; const hasBg = idxs.some((i) => get(layers)[i].isBackground); const bitmap = compositeLayers(idxs); + const grp = !hasBg && get(layers)[lo].groupId && idxs.every((i) => get(layers)[i].groupId === get(layers)[lo].groupId) ? get(layers)[lo].groupId : void 0; const merged = { id: hasBg ? 0 : ++nextLayerNum, name: hasBg ? "Background" : get(layers)[lo].name, visible: true, opacity: 1, bitmap, - isBackground: hasBg || void 0 + isBackground: hasBg || void 0, + groupId: grp }; const keptBelow = get(layers).filter((L, i) => i < lo && !idxs.includes(i)).length; const rest = get(layers).filter((_, i) => !idxs.includes(i)); rest.splice(keptBelow, 0, merged); set(layers, rest, true); + set(groups, get(groups).filter((g) => get(layers).some((L) => L.groupId === g.id)), true); set(activeIndex, get(layers).findIndex((L) => L.id === merged.id), true); set(selectedIds, [merged.id], true); loadActiveBitmap(); @@ -10214,6 +10821,7 @@ function EditModal($$anchor, $$props) { ], true ); + set(groups, [], true); set(activeIndex, 0); set(selectedIds, [0], true); loadActiveBitmap(); @@ -10234,8 +10842,8 @@ function EditModal($$anchor, $$props) { function drawBackdrop(ctx, sx, sy, sw, sh, dx, dy, dw, dh) { for (let k = 0; k < get(activeIndex); k++) { const L = get(layers)[k]; - if (!L.visible) continue; - ctx.globalAlpha = L.opacity; + if (!layerShown(L)) continue; + ctx.globalAlpha = layerEffOpacity(L); ctx.globalCompositeOperation = layerBlendCss(L) || "source-over"; drawLayerContent(ctx, L, sx, sy, sw, sh, dx, dy, dw, dh); ctx.globalAlpha = 1; @@ -10243,10 +10851,24 @@ function EditModal($$anchor, $$props) { } } function drawFlattened(ctx, sx, sy, sw, sh, dx, dy, dw, dh) { + const fullFrame = sx === 0 && sy === 0 && dx === 0 && dy === 0 && sw === get(width) && sh === get(height) && dw === get(width) && dh === get(height); + if (!fullFrame && get(layers).some((L) => layerShown(L) && isCustomBlend(L.blend))) { + const full = document.createElement("canvas"); + full.width = get(width); + full.height = get(height); + drawFlattened(full.getContext("2d"), 0, 0, get(width), get(height), 0, 0, get(width), get(height)); + ctx.drawImage(full, sx, sy, sw, sh, dx, dy, dw, dh); + return; + } for (let k = 0; k < get(layers).length; k++) { const L = get(layers)[k]; - if (!L.visible) continue; - ctx.globalAlpha = L.opacity; + if (!layerShown(L)) continue; + const eff = layerEffOpacity(L); + if (fullFrame && isCustomBlend(L.blend)) { + blendLayerFull(ctx, layerFullCanvas(L, k === get(activeIndex)), L.blend, eff); + continue; + } + ctx.globalAlpha = eff; ctx.globalCompositeOperation = layerBlendCss(L) || "source-over"; if (k === get(activeIndex)) ctx.drawImage(activeMaskedCanvas(), sx, sy, sw, sh, dx, dy, dw, dh); else drawLayerContent(ctx, L, sx, sy, sw, sh, dx, dy, dw, dh); @@ -10283,6 +10905,16 @@ function EditModal($$anchor, $$props) { drawLayerContent(ctx, get(layers)[i], 0, 0, get(width), get(height), 0, 0, get(width), get(height)); } }); + user_effect(() => { + get(customTick); + get(imgLoadTick); + if (!get(hasCustomBlend) || !customEl) return; + if (customEl.width !== get(width)) customEl.width = get(width); + if (customEl.height !== get(height)) customEl.height = get(height); + const ctx = customEl.getContext("2d"); + ctx.clearRect(0, 0, get(width), get(height)); + drawFlattened(ctx, 0, 0, get(width), get(height), 0, 0, get(width), get(height)); + }); function clearAll() { if (get(histIndex) === 0 && get(history2).length === 1) return; set(patch, null); @@ -10303,7 +10935,9 @@ function EditModal($$anchor, $$props) { } try { sampleCtx.clearRect(0, 0, s, s); - drawFlattened(sampleCtx, x, y, s, s, 0, 0, s, s); + if (get(sampleMerged) && get(hasCustomBlend) && customEl) sampleCtx.drawImage(customEl, x, y, s, s, 0, 0, s, s); + else if (get(sampleMerged)) drawFlattened(sampleCtx, x, y, s, s, 0, 0, s, s); + else sampleCtx.drawImage(paintCanvas, x, y, s, s, 0, 0, s, s); const d = sampleCtx.getImageData(0, 0, s, s).data; const sum = [0, 0, 0]; for (let i = 0; i < s * s; i++) { @@ -10345,10 +10979,10 @@ function EditModal($$anchor, $$props) { function onPointerDown(e) { var _a; if (e.button > 2) return; - if (e.button === 1 || e.button === 2 || e.shiftKey && !get( - isSelTool - // Shift adds to a selection / snaps a transform rotation — doesn't pan - ) && !get(transforming)) { + if (e.button === 1 || e.button === 2 || get( + spaceDown + // middle/right or Space-drag pans; Shift adds to a selection / snaps a transform rotation + ) && e.button === 0 || e.shiftKey && !get(isSelTool) && !get(transforming)) { panning = true; lastPt = { x: e.clientX, y: e.clientY }; e.preventDefault(); @@ -10615,6 +11249,7 @@ function EditModal($$anchor, $$props) { } if (selecting) { selecting = false; + const had = get(selActive); if (get(selRect) && get(selRect).w >= 1 && get(selRect).h >= 1) { applySelectionShape( { @@ -10626,8 +11261,10 @@ function EditModal($$anchor, $$props) { }, selOp ); + commitAction("Select"); } else if (selOp === "replace") { deselect(); + if (had) commitAction("Deselect"); } set(selRect, null); return; @@ -10635,8 +11272,14 @@ function EditModal($$anchor, $$props) { if (lassoing) { lassoing = false; if (get(tool) === "lasso") { - if (get(lassoPts) && get(lassoPts).length >= 3) applySelectionShape({ type: "lasso", pts: get(lassoPts) }, selOp); - else if (selOp === "replace") deselect(); + const had = get(selActive); + if (get(lassoPts) && get(lassoPts).length >= 3) { + applySelectionShape({ type: "lasso", pts: get(lassoPts) }, selOp); + commitAction("Select"); + } else if (selOp === "replace") { + deselect(); + if (had) commitAction("Deselect"); + } } else if (get(lassoPts) && get(lassoPts).length >= 3) { applySmooth(get( lassoPts @@ -10765,7 +11408,7 @@ function EditModal($$anchor, $$props) { var fragment = comment(); var node = first_child(fragment); { - var consequent_61 = ($$anchor2) => { + var consequent_67 = ($$anchor2) => { var div = root_1$5(); var div_1 = child(div); let classes; @@ -11070,9 +11713,9 @@ function EditModal($$anchor, $$props) { ], index, ($$anchor4, $$item) => { - var $$array_1 = user_derived(() => to_array($$item, 2)); - let id = () => get($$array_1)[0]; - let label = () => get($$array_1)[1]; + var $$array_2 = user_derived(() => to_array($$item, 2)); + let id = () => get($$array_2)[0]; + let label = () => get($$array_2)[1]; var button_8 = root_16$3(); let classes_3; var text_6 = child(button_8); @@ -11276,7 +11919,14 @@ function EditModal($$anchor, $$props) { option_2.value = option_2.__value = 3; var option_3 = sibling(option_2); option_3.value = option_3.__value = 5; + var div_28 = sibling(div_27, 2); + var select_2 = sibling(child(div_28), 2); + var option_4 = child(select_2); + option_4.value = option_4.__value = true; + var option_5 = sibling(option_4); + option_5.value = option_5.__value = false; bind_select_value(select_1, () => get(sampleSize), ($$value) => set(sampleSize, $$value)); + bind_select_value(select_2, () => get(sampleMerged), ($$value) => set(sampleMerged, $$value)); append($$anchor3, fragment_11); }; if_block(node_4, ($$render) => { @@ -11292,9 +11942,9 @@ function EditModal($$anchor, $$props) { else $$render(alternate_2, -1); }); } - var div_28 = sibling(div_7, 2); - var div_29 = child(div_28); - var node_27 = child(div_29); + var div_29 = sibling(div_7, 2); + var div_30 = child(div_29); + var node_27 = child(div_30); each(node_27, 17, () => TOOLS, index, ($$anchor3, t) => { var button_16 = root_25$1(); let classes_4; @@ -11323,13 +11973,13 @@ function EditModal($$anchor, $$props) { var node_28 = sibling(button_21, 2); { var consequent_17 = ($$anchor3) => { - var div_30 = root_26$1(); - var div_31 = sibling(child(div_30), 2); - var div_32 = sibling(child(div_31), 4); - var div_33 = sibling(div_31, 2); - var div_34 = child(div_33); - var div_35 = sibling(div_33, 2); - var span_11 = child(div_35); + var div_31 = root_26$1(); + var div_32 = sibling(child(div_31), 2); + var div_33 = sibling(child(div_32), 4); + var div_34 = sibling(div_32, 2); + var div_35 = child(div_34); + var div_36 = sibling(div_34, 2); + var span_11 = child(div_36); var span_12 = sibling(span_11, 2); var text_15 = child(span_12); var node_29 = sibling(span_12, 2); @@ -11343,50 +11993,64 @@ function EditModal($$anchor, $$props) { if (typeof window !== "undefined" && window.EyeDropper) $$render(consequent_16); }); } - var div_36 = sibling(div_35, 4); - var button_23 = child(div_36); + var div_37 = sibling(div_36, 4); + var button_23 = child(div_37); var button_24 = sibling(button_23, 2); - bind_this(div_30, ($$value) => set(popoutEl, $$value), () => get(popoutEl)); + bind_this(div_31, ($$value) => set(popoutEl, $$value), () => get(popoutEl)); template_effect(() => { var _a; - set_style(div_30, `top: ${((_a = get(colorChipEl)) == null ? void 0 : _a.offsetTop) ?? 0 ?? ""}px;`); - set_style(div_31, `background-color: hsl(${get(hue) ?? ""}, 100%, 50%);`); - set_style(div_32, `left: ${get(sat) * 100}%; top: ${(1 - get(val)) * 100}%;`); - set_style(div_34, `left: ${get(hue) / 360 * 100}%;`); + set_style(div_31, `top: ${((_a = get(colorChipEl)) == null ? void 0 : _a.offsetTop) ?? 0 ?? ""}px;`); + set_style(div_32, `background-color: hsl(${get(hue) ?? ""}, 100%, 50%);`); + set_style(div_33, `left: ${get(sat) * 100}%; top: ${(1 - get(val)) * 100}%;`); + set_style(div_35, `left: ${get(hue) / 360 * 100}%;`); set_style(span_11, `background: ${get(hexColor) ?? ""};`); set_text(text_15, get(hexColor)); }); - delegated("pointerdown", div_31, svDrag); - delegated("pointerdown", div_33, hueDrag); + delegated("pointerdown", div_32, svDrag); + delegated("pointerdown", div_34, hueDrag); delegated("click", button_23, cancelColorPop); delegated("click", button_24, okColorPop); - append($$anchor3, div_30); + append($$anchor3, div_31); }; if_block(node_28, ($$render) => { if (get(colorPopout)) $$render(consequent_17); }); } - var div_37 = sibling(div_29, 2); - var div_38 = child(div_37); + var div_38 = sibling(div_30, 2); + var div_39 = child(div_38); let classes_6; let styles; - var div_39 = child(div_38); - var img_1 = child(div_39); + var node_30 = child(div_39); + { + var consequent_18 = ($$anchor3) => { + var div_40 = root_28$1(); + var button_25 = sibling(child(div_40), 2); + delegated("pointerdown", div_40, (e) => e.stopPropagation()); + delegated("click", button_25, exitMaskEdit); + append($$anchor3, div_40); + }; + if_block(node_30, ($$render) => { + if (get(maskEdit)) $$render(consequent_18); + }); + } + var div_41 = sibling(node_30, 2); + var img_1 = child(div_41); set_style(img_1, "", {}, { display: "none" }); bind_this(img_1, ($$value) => imgEl = $$value, () => imgEl); var canvas = sibling(img_1, 2); + let styles_1; bind_this(canvas, ($$value) => belowCanvasEl = $$value, () => belowCanvasEl); var canvas_1 = sibling(canvas, 2); - let styles_1; + let styles_2; bind_this(canvas_1, ($$value) => paintCanvas = $$value, () => paintCanvas); - var node_30 = sibling(canvas_1, 2); - each(node_30, 19, () => get(layers), (L) => L.id, ($$anchor3, L, i) => { + var node_31 = sibling(canvas_1, 2); + each(node_31, 19, () => get(layers), (L) => L.id, ($$anchor3, L, i) => { var fragment_12 = comment(); - var node_31 = first_child(fragment_12); + var node_32 = first_child(fragment_12); { - var consequent_18 = ($$anchor4) => { - var canvas_2 = root_29$1(); - let styles_2; + var consequent_19 = ($$anchor4) => { + var canvas_2 = root_30$1(); + let styles_3; bind_this(canvas_2, ($$value, i2) => get(layerCanvasEls)[i2] = $$value, (i2) => { var _a; return (_a = get(layerCanvasEls)) == null ? void 0 : _a[i2]; @@ -11395,51 +12059,54 @@ function EditModal($$anchor, $$props) { ($0) => { set_attribute(canvas_2, "width", get(width)); set_attribute(canvas_2, "height", get(height)); - styles_2 = set_style(canvas_2, "", styles_2, $0); + styles_3 = set_style(canvas_2, "", styles_3, $0); }, [ () => ({ - display: get(L).visible ? null : "none", - opacity: get(L).opacity, + display: get(hasCustomBlend) || !layerShown(get(L)) ? "none" : null, + opacity: layerEffOpacity(get(L)), "mix-blend-mode": layerBlendCss(get(L)) }) ] ); append($$anchor4, canvas_2); }; - if_block(node_31, ($$render) => { - if (get(i) > get(activeIndex)) $$render(consequent_18); + if_block(node_32, ($$render) => { + if (get(i) > get(activeIndex)) $$render(consequent_19); }); } append($$anchor3, fragment_12); }); - var node_32 = sibling(node_30, 2); - { - var consequent_19 = ($$anchor3) => { - var div_40 = root_30$1(); - template_effect(() => set_style(div_40, `left:${get(healSource).x ?? ""}px; top:${get(healSource).y ?? ""}px; width:${18 / get(zoom)}px; height:${18 / get(zoom)}px; border-width:${1.5 / get(zoom)}px;`)); - append($$anchor3, div_40); - }; - if_block(node_32, ($$render) => { - if ((get(tool) === "heal" || get(tool) === "stamp") && get(healSource) && !get(healPaintingUi)) $$render(consequent_19); - }); - } - var node_33 = sibling(node_32, 2); + var canvas_3 = sibling(node_31, 2); + let styles_4; + bind_this(canvas_3, ($$value) => customEl = $$value, () => customEl); + var node_33 = sibling(canvas_3, 2); { var consequent_20 = ($$anchor3) => { - var div_41 = root_31$1(); - template_effect(() => set_style(div_41, `left:${get(selRect).x ?? ""}px; top:${get(selRect).y ?? ""}px; width:${get(selRect).w ?? ""}px; height:${get(selRect).h ?? ""}px; --ant-w:${1.5 / get(zoom)}px; --ant-step:${8 / get(zoom)}px;`)); - append($$anchor3, div_41); + var div_42 = root_31$1(); + template_effect(() => set_style(div_42, `left:${get(healSource).x ?? ""}px; top:${get(healSource).y ?? ""}px; width:${18 / get(zoom)}px; height:${18 / get(zoom)}px; border-width:${1.5 / get(zoom)}px;`)); + append($$anchor3, div_42); }; if_block(node_33, ($$render) => { - if (get(selRect)) $$render(consequent_20); + if ((get(tool) === "heal" || get(tool) === "stamp") && get(healSource) && !get(healPaintingUi)) $$render(consequent_20); }); } var node_34 = sibling(node_33, 2); { var consequent_21 = ($$anchor3) => { + var div_43 = root_32$1(); + template_effect(() => set_style(div_43, `left:${get(selRect).x ?? ""}px; top:${get(selRect).y ?? ""}px; width:${get(selRect).w ?? ""}px; height:${get(selRect).h ?? ""}px; --ant-w:${1.5 / get(zoom)}px; --ant-step:${8 / get(zoom)}px;`)); + append($$anchor3, div_43); + }; + if_block(node_34, ($$render) => { + if (get(selRect)) $$render(consequent_21); + }); + } + var node_35 = sibling(node_34, 2); + { + var consequent_22 = ($$anchor3) => { const pts = user_derived(() => get(lassoPts).map((p) => `${p.x},${p.y}`).join(" ")); - var svg_7 = root_32$1(); + var svg_7 = root_33$1(); var polyline = child(svg_7); var polyline_1 = sibling(polyline); template_effect(() => { @@ -11453,36 +12120,36 @@ function EditModal($$anchor, $$props) { }); append($$anchor3, svg_7); }; - if_block(node_34, ($$render) => { - if (get(lassoPts) && get(lassoPts).length > 1) $$render(consequent_21); - }); - } - var node_35 = sibling(node_34, 2); - { - var consequent_22 = ($$anchor3) => { - var div_42 = root_33$1(); - template_effect(() => set_style(div_42, `left:0; top:0; width:${1 / get(zoom)}px; height:${get(height) ?? ""}px; transform: translateX(${get(snapGuides).v ?? ""}px);`)); - append($$anchor3, div_42); - }; if_block(node_35, ($$render) => { - if (get(snapGuides).v != null) $$render(consequent_22); + if (get(lassoPts) && get(lassoPts).length > 1) $$render(consequent_22); }); } var node_36 = sibling(node_35, 2); { var consequent_23 = ($$anchor3) => { - var div_43 = root_34$1(); - template_effect(() => set_style(div_43, `left:0; top:0; width:${get(width) ?? ""}px; height:${1 / get(zoom)}px; transform: translateY(${get(snapGuides).h ?? ""}px);`)); - append($$anchor3, div_43); + var div_44 = root_34$1(); + template_effect(() => set_style(div_44, `left:0; top:0; width:${1 / get(zoom)}px; height:${get(height) ?? ""}px; transform: translateX(${get(snapGuides).v ?? ""}px);`)); + append($$anchor3, div_44); }; if_block(node_36, ($$render) => { - if (get(snapGuides).h != null) $$render(consequent_23); + if (get(snapGuides).v != null) $$render(consequent_23); }); } var node_37 = sibling(node_36, 2); { var consequent_24 = ($$anchor3) => { - var svg_8 = root_35$1(); + var div_45 = root_35$1(); + template_effect(() => set_style(div_45, `left:0; top:0; width:${get(width) ?? ""}px; height:${1 / get(zoom)}px; transform: translateY(${get(snapGuides).h ?? ""}px);`)); + append($$anchor3, div_45); + }; + if_block(node_37, ($$render) => { + if (get(snapGuides).h != null) $$render(consequent_24); + }); + } + var node_38 = sibling(node_37, 2); + { + var consequent_25 = ($$anchor3) => { + var svg_8 = root_36$1(); var g_1 = child(svg_8); var path = child(g_1); var path_1 = sibling(path); @@ -11496,49 +12163,49 @@ function EditModal($$anchor, $$props) { }); append($$anchor3, svg_8); }; - if_block(node_37, ($$render) => { - if (get(selContour)) $$render(consequent_24); + if_block(node_38, ($$render) => { + if (get(selContour)) $$render(consequent_25); }); } - var node_38 = sibling(node_37, 2); + var node_39 = sibling(node_38, 2); { - var consequent_26 = ($$anchor3) => { - var fragment_13 = root_36$1(); + var consequent_27 = ($$anchor3) => { + var fragment_13 = root_37$1(); var svg_9 = first_child(fragment_13); var path_2 = child(svg_9); - var div_44 = sibling(svg_9, 2); + var div_46 = sibling(svg_9, 2); let classes_7; - var node_39 = child(div_44); + var node_40 = child(div_46); { - var consequent_25 = ($$anchor4) => { - var fragment_14 = root_37$1(); - var div_45 = first_child(fragment_14); - var div_46 = sibling(div_45, 2); - var div_47 = sibling(div_46, 2); + var consequent_26 = ($$anchor4) => { + var fragment_14 = root_38$1(); + var div_47 = first_child(fragment_14); var div_48 = sibling(div_47, 2); + var div_49 = sibling(div_48, 2); + var div_50 = sibling(div_49, 2); template_effect(() => { - set_style(div_45, `left: 33.333%; width:${1 / get(zoom)}px;`); - set_style(div_46, `left: 66.666%; width:${1 / get(zoom)}px;`); - set_style(div_47, `top: 33.333%; height:${1 / get(zoom)}px;`); - set_style(div_48, `top: 66.666%; height:${1 / get(zoom)}px;`); + set_style(div_47, `left: 33.333%; width:${1 / get(zoom)}px;`); + set_style(div_48, `left: 66.666%; width:${1 / get(zoom)}px;`); + set_style(div_49, `top: 33.333%; height:${1 / get(zoom)}px;`); + set_style(div_50, `top: 66.666%; height:${1 / get(zoom)}px;`); }); append($$anchor4, fragment_14); }; - if_block(node_39, ($$render) => { - if (get(cropDragging)) $$render(consequent_25); + if_block(node_40, ($$render) => { + if (get(cropDragging)) $$render(consequent_26); }); } - var node_40 = sibling(div_44, 2); - each(node_40, 17, () => HANDLES, index, ($$anchor4, dir) => { - var div_49 = root_38$1(); - template_effect(($0) => set_style(div_49, $0), [ + var node_41 = sibling(div_46, 2); + each(node_41, 17, () => HANDLES, index, ($$anchor4, dir) => { + var div_51 = root_39$1(); + template_effect(($0) => set_style(div_51, $0), [ () => cropHandleStyle(get(dir), get(cropBox), get(zoom)) ]); - delegated("pointerdown", div_49, (e) => cropPointerDown(e, get(dir))); - delegated("pointermove", div_49, cropPointerMove); - delegated("pointerup", div_49, cropPointerUp); - event("pointercancel", div_49, cropPointerUp); - append($$anchor4, div_49); + delegated("pointerdown", div_51, (e) => cropPointerDown(e, get(dir))); + delegated("pointermove", div_51, cropPointerMove); + delegated("pointerup", div_51, cropPointerUp); + event("pointercancel", div_51, cropPointerUp); + append($$anchor4, div_51); }); template_effect( ($0) => { @@ -11546,409 +12213,503 @@ function EditModal($$anchor, $$props) { set_attribute(svg_9, "height", get(height)); set_attribute(svg_9, "viewBox", `0 0 ${get(width) ?? ""} ${get(height) ?? ""}`); set_attribute(path_2, "d", `M0 0H${get(width) ?? ""}V${get(height) ?? ""}H0Z M${get(cropBox).x ?? ""} ${get(cropBox).y ?? ""}h${get(cropBox).w ?? ""}v${get(cropBox).h ?? ""}h-${get(cropBox).w ?? ""}Z`); - classes_7 = set_class(div_44, 1, "pcr-ed-crop-border svelte-1ozzqkm", null, classes_7, $0); - set_style(div_44, `left:${get(cropBox).x ?? ""}px; top:${get(cropBox).y ?? ""}px; width:${get(cropBox).w ?? ""}px; height:${get(cropBox).h ?? ""}px; border-width:${1 / get(zoom)}px;`); + classes_7 = set_class(div_46, 1, "pcr-ed-crop-border svelte-1ozzqkm", null, classes_7, $0); + set_style(div_46, `left:${get(cropBox).x ?? ""}px; top:${get(cropBox).y ?? ""}px; width:${get(cropBox).w ?? ""}px; height:${get(cropBox).h ?? ""}px; border-width:${1 / get(zoom)}px;`); }, [() => ({ fresh: cropBoxIsFull() })] ); - delegated("pointerdown", div_44, (e) => cropPointerDown(e, "move")); - delegated("pointermove", div_44, cropPointerMove); - delegated("pointerup", div_44, cropPointerUp); - event("pointercancel", div_44, cropPointerUp); + delegated("pointerdown", div_46, (e) => cropPointerDown(e, "move")); + delegated("pointermove", div_46, cropPointerMove); + delegated("pointerup", div_46, cropPointerUp); + event("pointercancel", div_46, cropPointerUp); append($$anchor3, fragment_13); }; - if_block(node_38, ($$render) => { - if (get(tool) === "crop" && get(cropBox)) $$render(consequent_26); + if_block(node_39, ($$render) => { + if (get(tool) === "crop" && get(cropBox)) $$render(consequent_27); }); } - var node_41 = sibling(node_38, 2); + var node_42 = sibling(node_39, 2); { - var consequent_28 = ($$anchor3) => { + var consequent_29 = ($$anchor3) => { const hs = user_derived(() => 11 / get(zoom)); - var div_50 = root_39$1(); - var canvas_3 = child(div_50); - bind_this(canvas_3, ($$value) => set(patchEl, $$value), () => get(patchEl)); - var div_51 = sibling(canvas_3, 2); - var node_42 = sibling(div_51, 2); - each(node_42, 17, () => HANDLES, index, ($$anchor4, dir) => { - var div_52 = root_40$1(); - template_effect(($0) => set_style(div_52, $0), [() => tHandleStyle(get(dir), get(patch), get(zoom))]); - delegated("pointerdown", div_52, (e) => patchPointerDown(e, get(dir))); - delegated("pointermove", div_52, patchPointerMove); - delegated("pointerup", div_52, patchPointerUp); - event("pointercancel", div_52, patchPointerUp); - append($$anchor4, div_52); + var div_52 = root_40$1(); + var canvas_4 = child(div_52); + bind_this(canvas_4, ($$value) => set(patchEl, $$value), () => get(patchEl)); + var div_53 = sibling(canvas_4, 2); + var node_43 = sibling(div_53, 2); + each(node_43, 17, () => HANDLES, index, ($$anchor4, dir) => { + var div_54 = root_41$1(); + template_effect(($0) => set_style(div_54, $0), [() => tHandleStyle(get(dir), get(patch), get(zoom))]); + delegated("pointerdown", div_54, (e) => patchPointerDown(e, get(dir))); + delegated("pointermove", div_54, patchPointerMove); + delegated("pointerup", div_54, patchPointerUp); + event("pointercancel", div_54, patchPointerUp); + append($$anchor4, div_54); }); - var node_43 = sibling(node_42, 2); + var node_44 = sibling(node_43, 2); { - var consequent_27 = ($$anchor4) => { - var fragment_15 = root_41$1(); - var div_53 = first_child(fragment_15); - var div_54 = sibling(div_53, 2); + var consequent_28 = ($$anchor4) => { + var fragment_15 = root_42$1(); + var div_55 = first_child(fragment_15); + var div_56 = sibling(div_55, 2); template_effect(() => { - set_style(div_53, `left:50%; top:${-22 / get(zoom)}px; width:${1 / get(zoom)}px; height:${22 / get(zoom)}px;`); - set_style(div_54, `left:calc(50% - ${get(hs) / 2}px); top:${-22 / get(zoom) - get(hs) / 2}px; width:${get(hs) ?? ""}px; height:${get(hs) ?? ""}px;`); + set_style(div_55, `left:50%; top:${-22 / get(zoom)}px; width:${1 / get(zoom)}px; height:${22 / get(zoom)}px;`); + set_style(div_56, `left:calc(50% - ${get(hs) / 2}px); top:${-22 / get(zoom) - get(hs) / 2}px; width:${get(hs) ?? ""}px; height:${get(hs) ?? ""}px;`); }); - delegated("pointerdown", div_54, (e) => patchPointerDown(e, "rot")); - delegated("pointermove", div_54, patchPointerMove); - delegated("pointerup", div_54, patchPointerUp); - event("pointercancel", div_54, patchPointerUp); + delegated("pointerdown", div_56, (e) => patchPointerDown(e, "rot")); + delegated("pointermove", div_56, patchPointerMove); + delegated("pointerup", div_56, patchPointerUp); + event("pointercancel", div_56, patchPointerUp); append($$anchor4, fragment_15); }; - if_block(node_43, ($$render) => { - if (get(patch).transform) $$render(consequent_27); + if_block(node_44, ($$render) => { + if (get(patch).transform) $$render(consequent_28); }); } template_effect(() => { - set_style(div_50, `left:${get(patch).x ?? ""}px; top:${get(patch).y ?? ""}px; width:${get(patch).w ?? ""}px; height:${get(patch).h ?? ""}px; transform: rotate(${(get(patch).rot || 0) ?? ""}rad);`); - set_style(canvas_3, `left:0; top:0; width:100%; height:100%; opacity:${get(patchOpacity) ?? ""}; transform: scale(${get(patch).flipH ? -1 : 1}, ${get(patch).flipV ? -1 : 1});`); - set_style(div_51, `inset:0; border-width:${1 / get(zoom)}px;`); + set_style(div_52, `left:${get(patch).x ?? ""}px; top:${get(patch).y ?? ""}px; width:${get(patch).w ?? ""}px; height:${get(patch).h ?? ""}px; transform: rotate(${(get(patch).rot || 0) ?? ""}rad);`); + set_style(canvas_4, `left:0; top:0; width:100%; height:100%; opacity:${get(patchOpacity) ?? ""}; transform: scale(${get(patch).flipH ? -1 : 1}, ${get(patch).flipV ? -1 : 1});`); + set_style(div_53, `inset:0; border-width:${1 / get(zoom)}px;`); }); - delegated("pointerdown", canvas_3, (e) => patchPointerDown(e, "move")); - delegated("pointermove", canvas_3, patchPointerMove); - delegated("pointerup", canvas_3, patchPointerUp); - event("pointercancel", canvas_3, patchPointerUp); - append($$anchor3, div_50); + delegated("pointerdown", canvas_4, (e) => patchPointerDown(e, "move")); + delegated("pointermove", canvas_4, patchPointerMove); + delegated("pointerup", canvas_4, patchPointerUp); + event("pointercancel", canvas_4, patchPointerUp); + append($$anchor3, div_52); }; - if_block(node_41, ($$render) => { - if (get(patch)) $$render(consequent_28); + if_block(node_42, ($$render) => { + if (get(patch)) $$render(consequent_29); }); } - var node_44 = sibling(div_39, 2); - { - var consequent_29 = ($$anchor3) => { - var div_55 = root_42$1(); - template_effect(() => set_style(div_55, `left: ${get(cursorPos).x ?? ""}px; top: ${get(cursorPos).y ?? ""}px; width: ${(get(tool) === "liquify" ? get(liqSize) : get(brushSize)) * get(zoom)}px; height: ${(get(tool) === "liquify" ? get(liqSize) : get(brushSize)) * get(zoom)}px;`)); - append($$anchor3, div_55); - }; - if_block(node_44, ($$render) => { - if (get(cursorPos) && !get(colorPopout) && !get(transforming) && !get(hiddenEditBlocked) && (get(tool) === "brush" || get(tool) === "eraser" || get(tool) === "spot" || get(tool) === "heal" || get(tool) === "stamp" || get(tool) === "liquify")) $$render(consequent_29); - }); - } - var node_45 = sibling(node_44, 2); + var node_45 = sibling(div_41, 2); { var consequent_30 = ($$anchor3) => { - var div_56 = root_43$1(); - var text_16 = child(div_56); - template_effect(() => { - set_style(div_56, `left: ${get(cursorPos).x + 12}px; top: ${get(cursorPos).y - 16}px;`); - set_text(text_16, get(selBadge)); - }); - append($$anchor3, div_56); + var div_57 = root_43$1(); + template_effect(() => set_style(div_57, `left: ${get(cursorPos).x ?? ""}px; top: ${get(cursorPos).y ?? ""}px; width: ${(get(tool) === "liquify" ? get(liqSize) : get(brushSize)) * get(zoom)}px; height: ${(get(tool) === "liquify" ? get(liqSize) : get(brushSize)) * get(zoom)}px;`)); + append($$anchor3, div_57); }; if_block(node_45, ($$render) => { - if (get(selBadge) && get(cursorPos)) $$render(consequent_30); + if (get(cursorPos) && !get(colorPopout) && !get(transforming) && !get(hiddenEditBlocked) && (get(tool) === "brush" || get(tool) === "eraser" || get(tool) === "spot" || get(tool) === "heal" || get(tool) === "stamp" || get(tool) === "liquify")) $$render(consequent_30); }); } var node_46 = sibling(node_45, 2); { var consequent_31 = ($$anchor3) => { - var div_57 = root_44$1(); - template_effect(() => set_style(div_57, `left: ${get(cursorPos).x + get(healStrokeOffset).dx * get(zoom)}px; top: ${get(cursorPos).y + get(healStrokeOffset).dy * get(zoom)}px;`)); - append($$anchor3, div_57); + var div_58 = root_44$1(); + var text_16 = child(div_58); + template_effect(() => { + set_style(div_58, `left: ${get(cursorPos).x + 12}px; top: ${get(cursorPos).y - 16}px;`); + set_text(text_16, get(selBadge)); + }); + append($$anchor3, div_58); }; if_block(node_46, ($$render) => { - if ((get(tool) === "heal" || get(tool) === "stamp") && get(healPaintingUi) && get(healStrokeOffset) && get(cursorPos)) $$render(consequent_31); + if (get(selBadge) && get(cursorPos)) $$render(consequent_31); }); } var node_47 = sibling(node_46, 2); { var consequent_32 = ($$anchor3) => { - var div_58 = root_45$1(); - template_effect(() => set_style(div_58, `left: ${get(pickPreview).x ?? ""}px; top: ${get(pickPreview).y ?? ""}px; background: ${get(pickPreview).hex ?? ""};`)); - append($$anchor3, div_58); + var div_59 = root_45$1(); + template_effect(() => set_style(div_59, `left: ${get(cursorPos).x + get(healStrokeOffset).dx * get(zoom)}px; top: ${get(cursorPos).y + get(healStrokeOffset).dy * get(zoom)}px;`)); + append($$anchor3, div_59); }; if_block(node_47, ($$render) => { - if (get(pickPreview)) $$render(consequent_32); + if ((get(tool) === "heal" || get(tool) === "stamp") && get(healPaintingUi) && get(healStrokeOffset) && get(cursorPos)) $$render(consequent_32); }); } var node_48 = sibling(node_47, 2); { var consequent_33 = ($$anchor3) => { - var div_59 = root_46$1(); - append($$anchor3, div_59); + var div_60 = root_46$1(); + template_effect(() => set_style(div_60, `left: ${get(pickPreview).x ?? ""}px; top: ${get(pickPreview).y ?? ""}px; background: ${get(pickPreview).hex ?? ""};`)); + append($$anchor3, div_60); }; if_block(node_48, ($$render) => { - if (get(patch)) $$render(consequent_33); + if (get(pickPreview)) $$render(consequent_33); }); } - bind_this(div_38, ($$value) => viewportEl = $$value, () => viewportEl); - var div_60 = sibling(div_37, 2); - var div_61 = child(div_60); - var div_62 = child(div_61); - var span_13 = sibling(child(div_62), 2); - var text_17 = child(span_13); - var div_63 = sibling(div_62, 2); - var img_2 = child(div_63); - var node_49 = sibling(img_2, 2); + var node_49 = sibling(node_48, 2); { var consequent_34 = ($$anchor3) => { - var div_64 = root_47$1(); - template_effect(() => set_style(div_64, `left: ${get(navRect).x ?? ""}px; top: ${get(navRect).y ?? ""}px; width: ${get(navRect).w ?? ""}px; height: ${get(navRect).h ?? ""}px;`)); - append($$anchor3, div_64); + var div_61 = root_47$1(); + append($$anchor3, div_61); }; if_block(node_49, ($$render) => { - if (get(navRect)) $$render(consequent_34); + if (get(patch)) $$render(consequent_34); }); } - var div_65 = sibling(div_63, 2); - var button_25 = child(div_65); - var input = sibling(button_25, 2); + bind_this(div_39, ($$value) => viewportEl = $$value, () => viewportEl); + var div_62 = sibling(div_38, 2); + var div_63 = child(div_62); + var div_64 = child(div_63); + var span_13 = sibling(child(div_64), 2); + var text_17 = child(span_13); + var div_65 = sibling(div_64, 2); + var img_2 = child(div_65); + var node_50 = sibling(img_2, 2); + { + var consequent_35 = ($$anchor3) => { + var div_66 = root_48$1(); + template_effect(() => set_style(div_66, `left: ${get(navRect).x ?? ""}px; top: ${get(navRect).y ?? ""}px; width: ${get(navRect).w ?? ""}px; height: ${get(navRect).h ?? ""}px;`)); + append($$anchor3, div_66); + }; + if_block(node_50, ($$render) => { + if (get(navRect)) $$render(consequent_35); + }); + } + var div_67 = sibling(div_65, 2); + var button_26 = child(div_67); + var input = sibling(button_26, 2); set_attribute(input, "min", Math.log(0.05)); set_attribute(input, "max", Math.log(12)); - var button_26 = sibling(input, 2); - var node_50 = sibling(div_61, 2); + var button_27 = sibling(input, 2); + var node_51 = sibling(div_63, 2); { - var consequent_39 = ($$anchor3) => { - var div_66 = root_48$1(); - var span_14 = child(div_66); + var consequent_40 = ($$anchor3) => { + var div_68 = root_49$1(); + var span_14 = child(div_68); var text_18 = child(span_14); - var div_67 = sibling(span_14, 2); - var node_51 = child(div_67); - { - var consequent_35 = ($$anchor4) => { - var button_27 = root_49$1(); - template_effect(() => { - button_27.disabled = !!get(handoffBusy); - set_attribute(button_27, "title", get(selActive) ? "Inpaint the selected region — it's pre-masked; the result returns as a new layer." : "Inpaint — an alpha-bounded layer pre-masks its silhouette, else paint the mask in the Inpaint editor; the result returns as a new layer."); - }); - delegated("click", button_27, openInpaintHandoff); - append($$anchor4, button_27); - }; - if_block(node_51, ($$render) => { - if (onOpenInpaint()) $$render(consequent_35); - }); - } - var node_52 = sibling(node_51, 2); + var div_69 = sibling(span_14, 2); + var node_52 = child(div_69); { var consequent_36 = ($$anchor4) => { var button_28 = root_50$1(); - template_effect(() => button_28.disabled = !!get(handoffBusy)); - delegated("click", button_28, openI2iHandoff); + template_effect(() => { + button_28.disabled = !!get(handoffBusy); + set_attribute(button_28, "title", get(selActive) ? "Inpaint the selected region — it's pre-masked; the result returns as a new layer." : "Inpaint — an alpha-bounded layer pre-masks its silhouette, else paint the mask in the Inpaint editor; the result returns as a new layer."); + }); + delegated("click", button_28, openInpaintHandoff); append($$anchor4, button_28); }; if_block(node_52, ($$render) => { - if (onOpenI2i()) $$render(consequent_36); + if (onOpenInpaint()) $$render(consequent_36); }); } var node_53 = sibling(node_52, 2); { var consequent_37 = ($$anchor4) => { var button_29 = root_51$1(); - template_effect(() => { - button_29.disabled = !!get(handoffBusy); - set_attribute(button_29, "title", get(selActive) ? "Upscale the selected region at the quality you pick." : "Upscale the active layer's content at the quality you pick — grow the canvas to keep it 1:1, or squeeze it back in place."); - }); - delegated("click", button_29, openUpscaleHandoff); + template_effect(() => button_29.disabled = !!get(handoffBusy)); + delegated("click", button_29, openI2iHandoff); append($$anchor4, button_29); }; if_block(node_53, ($$render) => { - if (onOpenUpscale()) $$render(consequent_37); + if (onOpenI2i()) $$render(consequent_37); }); } var node_54 = sibling(node_53, 2); { var consequent_38 = ($$anchor4) => { var button_30 = root_52$1(); - template_effect(() => button_30.disabled = !!get(handoffBusy)); - delegated("click", button_30, openReposeHandoff); + template_effect(() => { + button_30.disabled = !!get(handoffBusy); + set_attribute(button_30, "title", get(selActive) ? "Upscale the selected region at the quality you pick." : "Upscale the active layer's content at the quality you pick — grow the canvas to keep it 1:1, or squeeze it back in place."); + }); + delegated("click", button_30, openUpscaleHandoff); append($$anchor4, button_30); }; if_block(node_54, ($$render) => { - if (onOpenRepose()) $$render(consequent_38); + if (onOpenUpscale()) $$render(consequent_38); + }); + } + var node_55 = sibling(node_54, 2); + { + var consequent_39 = ($$anchor4) => { + var button_31 = root_53$1(); + template_effect(() => button_31.disabled = !!get(handoffBusy)); + delegated("click", button_31, openReposeHandoff); + append($$anchor4, button_31); + }; + if_block(node_55, ($$render) => { + if (onOpenRepose()) $$render(consequent_39); }); } template_effect(() => set_text(text_18, `Render ${get(selActive) ? "selection" : "layer"}`)); - append($$anchor3, div_66); + append($$anchor3, div_68); }; - if_block(node_50, ($$render) => { - if (onOpenInpaint() || onOpenUpscale() || onOpenRepose() || onOpenI2i()) $$render(consequent_39); + if_block(node_51, ($$render) => { + if (onOpenInpaint() || onOpenUpscale() || onOpenRepose() || onOpenI2i()) $$render(consequent_40); }); } - var div_68 = sibling(node_50, 2); - var div_69 = child(div_68); - var span_15 = child(div_69); - var button_31 = child(span_15); + var div_70 = sibling(node_51, 2); + var div_71 = child(div_70); + var span_15 = child(div_71); + var button_32 = child(span_15); let classes_8; - var button_32 = sibling(button_31, 2); + var button_33 = sibling(button_32, 2); let classes_9; - var node_55 = sibling(span_15, 2); + var node_56 = sibling(span_15, 2); { - var consequent_40 = ($$anchor3) => { - var span_16 = root_53$1(); - var button_33 = child(span_16); - var button_34 = sibling(button_33, 2); + var consequent_41 = ($$anchor3) => { + var span_16 = root_54$1(); + var button_34 = child(span_16); var button_35 = sibling(button_34, 2); var button_36 = sibling(button_35, 2); + var button_37 = sibling(button_36, 2); template_effect( ($0) => { - button_34.disabled = get(selectedIds).length < 2; - button_35.disabled = get(layers).length <= 1; - set_attribute(button_36, "title", get(selectedIds).length > 1 ? "Delete selected layers" : "Delete layer"); - button_36.disabled = $0; + button_35.disabled = get(selectedIds).length < 2; + button_36.disabled = get(layers).length <= 1; + set_attribute(button_37, "title", get(selectedIds).length > 1 ? "Delete selected layers" : "Delete layer"); + button_37.disabled = $0; }, [() => !canDeleteSelected()] ); - delegated("click", button_33, () => addLayer()); - delegated("click", button_34, mergeSelected); - delegated("click", button_35, flattenImage); - delegated("click", button_36, deleteSelectedLayers); + delegated("click", button_34, () => addLayer()); + delegated("click", button_35, mergeSelected); + delegated("click", button_36, flattenImage); + delegated("click", button_37, deleteSelectedLayers); append($$anchor3, span_16); }; - if_block(node_55, ($$render) => { - if (get(lhTab) === "layers") $$render(consequent_40); + if_block(node_56, ($$render) => { + if (get(lhTab) === "layers") $$render(consequent_41); }); } - var node_56 = sibling(div_69, 2); + var node_57 = sibling(div_71, 2); { - var consequent_44 = ($$anchor3) => { - var fragment_16 = root_54$1(); - var select_2 = first_child(fragment_16); - each(select_2, 21, () => BLEND_MODES, ([v, label]) => v, ($$anchor4, $$item) => { - var $$array_2 = user_derived(() => to_array(get($$item), 2)); - let v = () => get($$array_2)[0]; - let label = () => get($$array_2)[1]; - var option_4 = root_55$1(); - var text_19 = child(option_4); - var option_4_value = {}; + var consequent_47 = ($$anchor3) => { + var fragment_16 = root_55$1(); + var select_3 = first_child(fragment_16); + each(select_3, 21, () => BLEND_MODES, ([v, label]) => v, ($$anchor4, $$item) => { + var $$array_3 = user_derived(() => to_array(get($$item), 2)); + let v = () => get($$array_3)[0]; + let label = () => get($$array_3)[1]; + var option_6 = root_56$1(); + var text_19 = child(option_6); + var option_6_value = {}; template_effect(() => { set_text(text_19, label()); - if (option_4_value !== (option_4_value = v())) { - option_4.value = (option_4.__value = v()) ?? ""; + if (option_6_value !== (option_6_value = v())) { + option_6.value = (option_6.__value = v()) ?? ""; } }); - append($$anchor4, option_4); + append($$anchor4, option_6); }); - var select_2_value; - init_select(select_2); - var div_70 = sibling(select_2, 2); - each(div_70, 21, () => [...get(layers)].reverse(), (L) => L.id, ($$anchor4, L) => { - const i = user_derived(() => get(layers).indexOf(get(L))); - var div_71 = root_56$1(); - let classes_10; - var span_17 = child(div_71); - let classes_11; - var span_18 = sibling(span_17, 2); - let classes_12; - var text_20 = child(span_18); - var node_57 = sibling(span_18, 2); + var select_3_value; + init_select(select_3); + var div_72 = sibling(select_3, 2); + each(div_72, 21, () => get(panelRows), (row) => row.type === "group" ? "g" + row.group.id : row.L.id, ($$anchor4, row) => { + var fragment_17 = comment(); + var node_58 = first_child(fragment_17); { - var consequent_41 = ($$anchor5) => { - var input_1 = root_57$1(); - autofocus(input_1); - delegated("click", input_1, (e) => e.stopPropagation()); - delegated("keydown", input_1, (e) => { - e.stopPropagation(); - if (e.key === "Enter") commitRename(); - else if (e.key === "Escape") set(renamingId, null); - }); - event("blur", input_1, commitRename); - bind_value(input_1, () => get(renameText), ($$value) => set(renameText, $$value)); - append($$anchor5, input_1); - }; - var alternate_3 = ($$anchor5) => { - var span_19 = root_58$1(); - var text_21 = child(span_19); - var node_58 = sibling(text_21); + var consequent_43 = ($$anchor5) => { + const g = user_derived(() => get(row).group); + var div_73 = root_58$1(); + var span_17 = child(div_73); + var text_20 = child(span_17); + var span_18 = sibling(span_17, 2); + let classes_10; + var text_21 = child(span_18); + var node_59 = sibling(span_18, 2); { var consequent_42 = ($$anchor6) => { - var span_20 = root_59$1(); - append($$anchor6, span_20); + var input_1 = root_59$1(); + autofocus(input_1); + delegated("click", input_1, (e) => e.stopPropagation()); + delegated("keydown", input_1, (e) => { + e.stopPropagation(); + if (e.key === "Enter") commitGroupRename(); + else if (e.key === "Escape") set(renamingGroupId, null); + }); + event("blur", input_1, commitGroupRename); + bind_value(input_1, () => get(renameText), ($$value) => set(renameText, $$value)); + append($$anchor6, input_1); }; - if_block(node_58, ($$render) => { - if (get(L).mask) $$render(consequent_42); - }); - } - var node_59 = sibling(node_58); - { - var consequent_43 = ($$anchor6) => { - var span_21 = root_60$1(); - var text_22 = child(span_21); - template_effect(() => set_text(text_22, get(L).blend)); - append($$anchor6, span_21); + var alternate_3 = ($$anchor6) => { + var span_19 = root_60$1(); + var text_22 = child(span_19); + template_effect(() => set_text(text_22, `📁 ${get(g).name ?? ""}`)); + delegated("dblclick", span_19, (e) => { + e.stopPropagation(); + startGroupRename(get(g)); + }); + append($$anchor6, span_19); }; - var d_1 = user_derived(() => layerBlendCss(get(L))); if_block(node_59, ($$render) => { - if (get(d_1)) $$render(consequent_43); + if (get(renamingGroupId) === get(g).id) $$render(consequent_42); + else $$render(alternate_3, -1); }); } - template_effect(() => set_text(text_21, get(L).name)); - delegated("dblclick", span_19, (e) => { - e.stopPropagation(); - startRename(get(L)); + var input_2 = sibling(node_59, 2); + var button_38 = sibling(input_2, 2); + template_effect(() => { + set_text(text_20, get(g).collapsed ? "▸" : "▾"); + classes_10 = set_class(span_18, 1, "pcr-ed-eye svelte-1ozzqkm", null, classes_10, { off: !get(g).visible }); + set_text(text_21, get(g).visible ? "👁" : "—"); + set_value(input_2, get(g).opacity); }); - append($$anchor5, span_19); + delegated("click", span_17, () => toggleGroupCollapsed(get(g).id)); + delegated("click", span_18, () => setGroupVisible(get(g).id, !get(g).visible)); + delegated("pointerdown", input_2, () => { + opacityDragStart = get(g).opacity; + }); + delegated("input", input_2, (e) => setGroupOpacity(get(g).id, +e.currentTarget.value)); + delegated("change", input_2, (e) => { + if (opacityDragStart === null || +e.currentTarget.value !== opacityDragStart) commitAction("Group Opacity"); + opacityDragStart = null; + }); + delegated("click", input_2, (e) => e.stopPropagation()); + delegated("click", button_38, () => ungroup(get(g).id)); + append($$anchor5, div_73); }; - if_block(node_57, ($$render) => { - if (get(renamingId) === get(L).id) $$render(consequent_41); - else $$render(alternate_3, -1); + var alternate_5 = ($$anchor5) => { + const L = user_derived(() => get(row).L); + const i = user_derived(() => get(row).i); + var div_74 = root_61$1(); + let classes_11; + var span_20 = child(div_74); + let classes_12; + var span_21 = sibling(span_20, 2); + let classes_13; + var text_23 = child(span_21); + var node_60 = sibling(span_21, 2); + { + var consequent_44 = ($$anchor6) => { + var img_3 = root_62$1(); + let classes_14; + template_effect(() => { + classes_14 = set_class(img_3, 1, "pcr-ed-mask-thumb svelte-1ozzqkm", null, classes_14, { editing: get(maskEdit) && get(i) === get(activeIndex) }); + set_attribute(img_3, "src", get(L).maskUrl); + set_attribute(img_3, "title", get(maskEdit) && get(i) === get(activeIndex) ? "Editing layer mask — click the layer to edit pixels" : "Click to edit layer mask"); + }); + delegated("click", img_3, (e) => { + e.stopPropagation(); + toggleMaskEdit(get(i)); + }); + append($$anchor6, img_3); + }; + if_block(node_60, ($$render) => { + if (get(L).mask) $$render(consequent_44); + }); + } + var node_61 = sibling(node_60, 2); + { + var consequent_45 = ($$anchor6) => { + var input_3 = root_63$1(); + autofocus(input_3); + delegated("click", input_3, (e) => e.stopPropagation()); + delegated("keydown", input_3, (e) => { + e.stopPropagation(); + if (e.key === "Enter") commitRename(); + else if (e.key === "Escape") set(renamingId, null); + }); + event("blur", input_3, commitRename); + bind_value(input_3, () => get(renameText), ($$value) => set(renameText, $$value)); + append($$anchor6, input_3); + }; + var alternate_4 = ($$anchor6) => { + var span_22 = root_64$1(); + var text_24 = child(span_22); + var node_62 = sibling(text_24); + { + var consequent_46 = ($$anchor7) => { + var span_23 = root_65$1(); + var text_25 = child(span_23); + template_effect(() => { + set_attribute(span_23, "title", `Blend: ${get(L).blend}`); + set_text(text_25, get(L).blend); + }); + append($$anchor7, span_23); + }; + if_block(node_62, ($$render) => { + if (get(L).blend && get(L).blend !== "normal") $$render(consequent_46); + }); + } + template_effect(() => set_text(text_24, get(L).name)); + delegated("dblclick", span_22, (e) => { + e.stopPropagation(); + startRename(get(L)); + }); + append($$anchor6, span_22); + }; + if_block(node_61, ($$render) => { + if (get(renamingId) === get(L).id) $$render(consequent_45); + else $$render(alternate_4, -1); + }); + } + var input_4 = sibling(node_61, 2); + template_effect( + ($0) => { + classes_11 = set_class(div_74, 1, "pcr-ed-layer-row svelte-1ozzqkm", null, classes_11, $0); + classes_12 = set_class(span_20, 1, "pcr-ed-layer-grip svelte-1ozzqkm", null, classes_12, { hidden: get(L).isBackground }); + set_attribute(span_20, "draggable", !get(L).isBackground); + classes_13 = set_class(span_21, 1, "pcr-ed-eye svelte-1ozzqkm", null, classes_13, { off: !get(L).visible }); + set_text(text_23, get(L).visible ? "👁" : "—"); + set_value(input_4, get(L).opacity); + }, + [ + () => ({ + active: get(i) === get(activeIndex), + selected: get(selectedIds).includes(get(L).id), + dragover: get(dragOverId) === get(L).id, + "in-group": get(row).inGroup + }) + ] + ); + delegated("click", div_74, (e) => clickLayer(get(i), e)); + delegated("contextmenu", div_74, (e) => { + e.preventDefault(); + if (!get(selectedIds).includes(get(L).id)) clickLayer(get(i), e); + set( + layerMenu, + { + x: e.clientX, + y: e.clientY, + i: get(i), + flip: e.clientX > window.innerWidth - 340 + }, + true + ); + }); + event("dragover", div_74, (e) => { + e.preventDefault(); + e.dataTransfer.dropEffect = "move"; + set(dragOverId, get(L).id, true); + }); + event("dragleave", div_74, () => { + if (get(dragOverId) === get(L).id) set(dragOverId, null); + }); + event("drop", div_74, (e) => { + e.preventDefault(); + dropLayer(get(L).id); + }); + event("dragstart", span_20, (e) => { + dragId = get(L).id; + e.dataTransfer.effectAllowed = "move"; + e.dataTransfer.setData("text/plain", String(get(L).id)); + }); + event("dragend", span_20, () => { + dragId = null; + set(dragOverId, null); + }); + delegated("click", span_21, (e) => { + e.stopPropagation(); + setLayerVisible(get(i), !get(L).visible); + }); + delegated("pointerdown", input_4, () => { + opacityDragStart = get(L).opacity; + }); + delegated("input", input_4, (e) => setLayerOpacity(get(i), +e.currentTarget.value)); + delegated("change", input_4, (e) => { + if (opacityDragStart === null || +e.currentTarget.value !== opacityDragStart) commitAction("Opacity"); + opacityDragStart = null; + }); + delegated("click", input_4, (e) => e.stopPropagation()); + append($$anchor5, div_74); + }; + if_block(node_58, ($$render) => { + if (get(row).type === "group") $$render(consequent_43); + else $$render(alternate_5, -1); }); } - var input_2 = sibling(node_57, 2); - template_effect( - ($0) => { - classes_10 = set_class(div_71, 1, "pcr-ed-layer-row svelte-1ozzqkm", null, classes_10, $0); - classes_11 = set_class(span_17, 1, "pcr-ed-layer-grip svelte-1ozzqkm", null, classes_11, { hidden: get(L).isBackground }); - set_attribute(span_17, "draggable", !get(L).isBackground); - classes_12 = set_class(span_18, 1, "pcr-ed-eye svelte-1ozzqkm", null, classes_12, { off: !get(L).visible }); - set_text(text_20, get(L).visible ? "👁" : "—"); - set_value(input_2, get(L).opacity); - }, - [ - () => ({ - active: get(i) === get(activeIndex), - selected: get(selectedIds).includes(get(L).id), - dragover: get(dragOverId) === get(L).id - }) - ] - ); - delegated("click", div_71, (e) => clickLayer(get(i), e)); - delegated("contextmenu", div_71, (e) => { - e.preventDefault(); - if (!get(selectedIds).includes(get(L).id)) clickLayer(get(i), e); - set(layerMenu, { x: e.clientX, y: e.clientY, i: get(i) }, true); - }); - event("dragover", div_71, (e) => { - e.preventDefault(); - e.dataTransfer.dropEffect = "move"; - set(dragOverId, get(L).id, true); - }); - event("dragleave", div_71, () => { - if (get(dragOverId) === get(L).id) set(dragOverId, null); - }); - event("drop", div_71, (e) => { - e.preventDefault(); - dropLayer(get(L).id); - }); - event("dragstart", span_17, (e) => { - dragId = get(L).id; - e.dataTransfer.effectAllowed = "move"; - e.dataTransfer.setData("text/plain", String(get(L).id)); - }); - event("dragend", span_17, () => { - dragId = null; - set(dragOverId, null); - }); - delegated("click", span_18, (e) => { - e.stopPropagation(); - setLayerVisible(get(i), !get(L).visible); - }); - delegated("pointerdown", input_2, () => { - opacityDragStart = get(L).opacity; - }); - delegated("input", input_2, (e) => setLayerOpacity(get(i), +e.currentTarget.value)); - delegated("change", input_2, (e) => { - if (opacityDragStart === null || +e.currentTarget.value !== opacityDragStart) commitAction("Opacity"); - opacityDragStart = null; - }); - delegated("click", input_2, (e) => e.stopPropagation()); - append($$anchor4, div_71); + append($$anchor4, fragment_17); }); template_effect( ($0, $1) => { - select_2.disabled = $0; - if (select_2_value !== (select_2_value = $1)) { - select_2.value = (select_2.__value = $1) ?? "", select_option(select_2, $1); + select_3.disabled = $0; + if (select_3_value !== (select_3_value = $1)) { + select_3.value = (select_3.__value = $1) ?? "", select_option(select_3, $1); } }, [ @@ -11962,140 +12723,215 @@ function EditModal($$anchor, $$props) { } ] ); - delegated("change", select_2, (e) => setLayerBlend(get(activeIndex), e.currentTarget.value)); + delegated("change", select_3, (e) => setLayerBlend(get(activeIndex), e.currentTarget.value)); append($$anchor3, fragment_16); }; - var alternate_4 = ($$anchor3) => { - var div_72 = root_61$1(); - each(div_72, 21, () => get(history2), index, ($$anchor4, h, i) => { - var div_73 = root_62$1(); - let classes_13; - var text_23 = child(div_73); + var alternate_6 = ($$anchor3) => { + var div_75 = root_66$1(); + each(div_75, 21, () => get(history2), index, ($$anchor4, h, i) => { + var div_76 = root_67$1(); + let classes_15; + var text_26 = child(div_76); template_effect(() => { - classes_13 = set_class(div_73, 1, "pcr-ed-hist-row svelte-1ozzqkm", null, classes_13, { active: i === get(histIndex), future: i > get(histIndex) }); - set_text(text_23, get(h).label); + classes_15 = set_class(div_76, 1, "pcr-ed-hist-row svelte-1ozzqkm", null, classes_15, { active: i === get(histIndex), future: i > get(histIndex) }); + set_text(text_26, get(h).label); }); - delegated("click", div_73, () => jumpTo(i)); - append($$anchor4, div_73); + delegated("click", div_76, () => jumpTo(i)); + append($$anchor4, div_76); }); - bind_this(div_72, ($$value) => histEl = $$value, () => histEl); - append($$anchor3, div_72); + bind_this(div_75, ($$value) => histEl = $$value, () => histEl); + append($$anchor3, div_75); }; - if_block(node_56, ($$render) => { - if (get(lhTab) === "layers") $$render(consequent_44); - else $$render(alternate_4, -1); + if_block(node_57, ($$render) => { + if (get(lhTab) === "layers") $$render(consequent_47); + else $$render(alternate_6, -1); }); } - var node_60 = sibling(div_68, 2); + var node_63 = sibling(div_70, 2); { - var consequent_51 = ($$anchor3) => { - var div_74 = root_63$1(); - var button_37 = child(div_74); - var node_61 = sibling(button_37, 2); + var consequent_57 = ($$anchor3) => { + const Lm = user_derived(() => get(layers)[get(layerMenu).i]); + const multi = user_derived(() => get(selectedIds).length > 1); + const groupable = user_derived(() => get(selectedIds).filter((id) => { + var _a; + return !((_a = get(layers).find((ly) => ly.id === id)) == null ? void 0 : _a.isBackground); + }).length); + var div_77 = root_68(); + var button_39 = child(div_77); + var button_40 = sibling(button_39, 2); + var node_64 = sibling(button_40, 2); { - var consequent_45 = ($$anchor4) => { - var button_38 = root_64$1(); - delegated("click", button_38, () => { + var consequent_48 = ($$anchor4) => { + var button_41 = root_69(); + delegated("click", button_41, () => { mergeSelected(); set(layerMenu, null); }); - append($$anchor4, button_38); + append($$anchor4, button_41); }; - var consequent_46 = ($$anchor4) => { - var button_39 = root_65$1(); - delegated("click", button_39, () => { + var consequent_49 = ($$anchor4) => { + var button_42 = root_70$1(); + delegated("click", button_42, () => { mergeDown(get(layerMenu).i); set(layerMenu, null); }); - append($$anchor4, button_39); + append($$anchor4, button_42); }; - if_block(node_61, ($$render) => { - if (get(selectedIds).length > 1) $$render(consequent_45); - else if (get(layerMenu).i > 0) $$render(consequent_46, 1); + if_block(node_64, ($$render) => { + if (get(multi)) $$render(consequent_48); + else if (get(layerMenu).i > 0) $$render(consequent_49, 1); }); } - var button_40 = sibling(node_61, 2); - var node_62 = sibling(button_40, 2); + var node_65 = sibling(node_64, 2); { - var consequent_48 = ($$anchor4) => { - var fragment_17 = comment(); - var node_63 = first_child(fragment_17); - { - var consequent_47 = ($$anchor5) => { - var button_41 = root_67$1(); - var text_24 = child(button_41); - template_effect(() => set_text(text_24, `Add Layer Mask${get(selActive) ? " (Reveal Selection)" : ""}`)); - delegated("click", button_41, () => { - addLayerMask(get(layerMenu).i); - set(layerMenu, null); - }); - append($$anchor5, button_41); - }; - var alternate_5 = ($$anchor5) => { - var fragment_18 = root_68(); - var button_42 = first_child(fragment_18); - var button_43 = sibling(button_42, 2); - delegated("click", button_42, () => { - applyLayerMask(get(layerMenu).i); - set(layerMenu, null); - }); - delegated("click", button_43, () => { - deleteLayerMask(get(layerMenu).i); - set(layerMenu, null); - }); - append($$anchor5, fragment_18); - }; - if_block(node_63, ($$render) => { - var _a; - if (!((_a = get(layers)[get(layerMenu).i]) == null ? void 0 : _a.mask)) $$render(consequent_47); - else $$render(alternate_5, -1); - }); - } - append($$anchor4, fragment_17); - }; - if_block(node_62, ($$render) => { - var _a; - if (!((_a = get(layers)[get(layerMenu).i]) == null ? void 0 : _a.isBackground)) $$render(consequent_48); - }); - } - var node_64 = sibling(node_62, 2); - { - var consequent_49 = ($$anchor4) => { - var button_44 = root_69(); - delegated("click", button_44, () => { + var consequent_50 = ($$anchor4) => { + var button_43 = root_71$1(); + delegated("click", button_43, () => { const i = get(layerMenu).i; set(layerMenu, null); if (i === get(activeIndex)) flushActive(); if (matchLayerColorsToBelow(i)) commitAction("Match Colors"); else set(errorMsg, "No usable edge band — the layer's feathered edge must overlap solid content below."); }); - append($$anchor4, button_44); + append($$anchor4, button_43); }; - if_block(node_64, ($$render) => { + if_block(node_65, ($$render) => { var _a; - if (get(layerMenu).i > 0 && ((_a = get(layers)[get(layerMenu).i]) == null ? void 0 : _a.bitmap)) $$render(consequent_49); + if (get(layerMenu).i > 0 && ((_a = get(Lm)) == null ? void 0 : _a.bitmap)) $$render(consequent_50); }); } - var button_45 = sibling(node_64, 2); - var node_65 = sibling(button_45, 2); + var node_66 = sibling(node_65, 2); { - var consequent_50 = ($$anchor4) => { - var button_46 = root_70$1(); - var text_25 = child(button_46); - template_effect(() => set_text(text_25, get(selectedIds).length > 1 ? "Delete Layers" : "Delete Layer")); - delegated("click", button_46, () => { + var consequent_53 = ($$anchor4) => { + var fragment_18 = root_72$1(); + var node_67 = sibling(first_child(fragment_18), 2); + { + var consequent_51 = ($$anchor5) => { + var button_44 = root_73$1(); + var text_27 = child(button_44); + template_effect(() => set_text(text_27, `Group ${get(multi) ? "Layers" : "Layer"}`)); + delegated("click", button_44, () => { + groupSelected(); + set(layerMenu, null); + }); + append($$anchor5, button_44); + }; + if_block(node_67, ($$render) => { + if (get(groupable)) $$render(consequent_51); + }); + } + var node_68 = sibling(node_67, 2); + { + var consequent_52 = ($$anchor5) => { + var button_45 = root_74$1(); + delegated("click", button_45, () => { + ungroup(get(Lm).groupId); + set(layerMenu, null); + }); + append($$anchor5, button_45); + }; + if_block(node_68, ($$render) => { + var _a; + if ((_a = get(Lm)) == null ? void 0 : _a.groupId) $$render(consequent_52); + }); + } + append($$anchor4, fragment_18); + }; + if_block(node_66, ($$render) => { + var _a; + if (get(groupable) || ((_a = get(Lm)) == null ? void 0 : _a.groupId)) $$render(consequent_53); + }); + } + var node_69 = sibling(node_66, 2); + { + var consequent_55 = ($$anchor4) => { + var fragment_19 = root_75$1(); + var node_70 = sibling(first_child(fragment_19), 2); + { + var consequent_54 = ($$anchor5) => { + var button_46 = root_76$1(); + var text_28 = child(button_46); + template_effect(() => set_text(text_28, `Add Layer Mask${get(selActive) ? " (Reveal Selection)" : ""}`)); + delegated("click", button_46, () => { + addLayerMask(get(layerMenu).i); + set(layerMenu, null); + }); + append($$anchor5, button_46); + }; + var alternate_7 = ($$anchor5) => { + var div_78 = root_77$1(); + let classes_16; + var div_79 = sibling(child(div_78), 2); + var button_47 = child(div_79); + var text_29 = child(button_47); + var button_48 = sibling(button_47, 2); + var button_49 = sibling(button_48, 2); + var button_50 = sibling(button_49, 2); + var button_51 = sibling(button_50, 4); + var button_52 = sibling(button_51, 2); + template_effect(() => { + classes_16 = set_class(div_78, 1, "pcr-ed-submenu-host svelte-1ozzqkm", null, classes_16, { flip: get(layerMenu).flip }); + set_text(text_29, get(maskEdit) && get(layerMenu).i === get(activeIndex) ? "Done Editing Mask" : "Edit Mask (paint)"); + }); + delegated("click", button_47, () => { + toggleMaskEdit(get(layerMenu).i); + set(layerMenu, null); + }); + delegated("click", button_48, () => { + invertLayerMask(get(layerMenu).i); + set(layerMenu, null); + }); + delegated("click", button_49, () => { + fillLayerMask(get(layerMenu).i, 255); + set(layerMenu, null); + }); + delegated("click", button_50, () => { + fillLayerMask(get(layerMenu).i, 0); + set(layerMenu, null); + }); + delegated("click", button_51, () => { + applyLayerMask(get(layerMenu).i); + set(layerMenu, null); + }); + delegated("click", button_52, () => { + deleteLayerMask(get(layerMenu).i); + set(layerMenu, null); + }); + append($$anchor5, div_78); + }; + if_block(node_70, ($$render) => { + var _a; + if (!((_a = get(Lm)) == null ? void 0 : _a.mask)) $$render(consequent_54); + else $$render(alternate_7, -1); + }); + } + append($$anchor4, fragment_19); + }; + if_block(node_69, ($$render) => { + var _a; + if (!((_a = get(Lm)) == null ? void 0 : _a.isBackground)) $$render(consequent_55); + }); + } + var button_53 = sibling(node_69, 4); + var node_71 = sibling(button_53, 2); + { + var consequent_56 = ($$anchor4) => { + var button_54 = root_78$1(); + var text_30 = child(button_54); + template_effect(() => set_text(text_30, get(multi) ? "Delete Layers" : "Delete Layer")); + delegated("click", button_54, () => { deleteSelectedLayers(); set(layerMenu, null); }); - append($$anchor4, button_46); + append($$anchor4, button_54); }; - var d_2 = user_derived(() => canDeleteSelected()); - if_block(node_65, ($$render) => { - if (get(d_2)) $$render(consequent_50); + var d_1 = user_derived(() => canDeleteSelected()); + if_block(node_71, ($$render) => { + if (get(d_1)) $$render(consequent_56); }); } - template_effect(() => set_style(div_74, `left:${get(layerMenu).x ?? ""}px; top:${get(layerMenu).y ?? ""}px;`)); - delegated("click", button_37, () => { + template_effect(() => set_style(div_77, `left:${get(layerMenu).x ?? ""}px; top:${get(layerMenu).y ?? ""}px;`)); + delegated("click", button_39, () => { addLayer(get(layerMenu).i); set(layerMenu, null); }); @@ -12103,120 +12939,123 @@ function EditModal($$anchor, $$props) { duplicateLayer(get(layerMenu).i); set(layerMenu, null); }); - delegated("click", button_45, () => { + delegated("click", button_53, () => { flattenImage(); set(layerMenu, null); }); - append($$anchor3, div_74); + append($$anchor3, div_77); }; - if_block(node_60, ($$render) => { - if (get(layerMenu)) $$render(consequent_51); + if_block(node_63, ($$render) => { + if (get(layerMenu)) $$render(consequent_57); }); } - var node_66 = sibling(node_60, 2); + var node_72 = sibling(node_63, 2); { - var consequent_54 = ($$anchor3) => { - var div_75 = root_71$1(); - var node_67 = child(div_75); + var consequent_60 = ($$anchor3) => { + var div_80 = root_79$1(); + var node_73 = child(div_80); { - var consequent_52 = ($$anchor4) => { - var fragment_19 = root_72$1(); - var button_47 = first_child(fragment_19); - var button_48 = sibling(button_47, 2); - delegated("click", button_47, () => { + var consequent_58 = ($$anchor4) => { + var fragment_20 = root_80$1(); + var button_55 = first_child(fragment_20); + var button_56 = sibling(button_55, 2); + delegated("click", button_55, () => { deselect(); + commitAction("Deselect"); set(canvasMenu, null); }); - delegated("click", button_48, () => { + delegated("click", button_56, () => { selectInverse(); + commitAction("Select Inverse"); set(canvasMenu, null); }); - append($$anchor4, fragment_19); - }; - if_block(node_67, ($$render) => { - if (get(selActive)) $$render(consequent_52); - }); - } - var button_49 = sibling(node_67, 2); - var text_26 = child(button_49); - var button_50 = sibling(button_49, 2); - var button_51 = sibling(button_50, 2); - var text_27 = child(button_51); - var node_68 = sibling(button_51, 2); - { - var consequent_53 = ($$anchor4) => { - var fragment_20 = root_73$1(); - var button_52 = first_child(fragment_20); - var button_53 = sibling(button_52, 2); - delegated("click", button_52, () => { - set(canvasMenu, null); - layerViaCopy(false); - }); - delegated("click", button_53, () => { - set(canvasMenu, null); - layerViaCopy(true); - }); append($$anchor4, fragment_20); }; - if_block(node_68, ($$render) => { - if (get(selActive)) $$render(consequent_53); + if_block(node_73, ($$render) => { + if (get(selActive)) $$render(consequent_58); + }); + } + var button_57 = sibling(node_73, 2); + var text_31 = child(button_57); + var button_58 = sibling(button_57, 2); + var button_59 = sibling(button_58, 2); + var text_32 = child(button_59); + var node_74 = sibling(button_59, 2); + { + var consequent_59 = ($$anchor4) => { + var fragment_21 = root_81$1(); + var button_60 = first_child(fragment_21); + var button_61 = sibling(button_60, 2); + delegated("click", button_60, () => { + set(canvasMenu, null); + layerViaCopy(false); + }); + delegated("click", button_61, () => { + set(canvasMenu, null); + layerViaCopy(true); + }); + append($$anchor4, fragment_21); + }; + if_block(node_74, ($$render) => { + if (get(selActive)) $$render(consequent_59); }); } template_effect(() => { - set_style(div_75, `left:${get(canvasMenu).x ?? ""}px; top:${get(canvasMenu).y ?? ""}px;`); - set_text(text_26, get(selActive) ? "Free Transform Selection" : "Free Transform Layer"); - button_51.disabled = get(subjectBusy); - set_text(text_27, get(subjectBusy) ? "Selecting Subject…" : "Select Subject"); + set_style(div_80, `left:${get(canvasMenu).x ?? ""}px; top:${get(canvasMenu).y ?? ""}px;`); + set_text(text_31, get(selActive) ? "Free Transform Selection" : "Free Transform Layer"); + button_59.disabled = get(subjectBusy); + set_text(text_32, get(subjectBusy) ? "Selecting Subject…" : "Select Subject"); }); - delegated("click", button_49, () => { + delegated("click", button_57, () => { set(canvasMenu, null); beginTransform(); }); - delegated("click", button_50, () => { + delegated("click", button_58, () => { selectAllRegion(); + commitAction("Select All"); set(canvasMenu, null); }); - delegated("click", button_51, () => { + delegated("click", button_59, () => { set(canvasMenu, null); selectSubject(); }); - append($$anchor3, div_75); + append($$anchor3, div_80); }; - if_block(node_66, ($$render) => { - if (get(canvasMenu)) $$render(consequent_54); + if_block(node_72, ($$render) => { + if (get(canvasMenu)) $$render(consequent_60); }); } - var node_69 = sibling(div_6, 2); + var node_75 = sibling(div_6, 2); { - var consequent_55 = ($$anchor3) => { - var div_76 = root_74$1(); - var text_28 = child(div_76); - template_effect(() => set_text(text_28, `⚠ ${get(errorMsg) ?? ""}`)); - append($$anchor3, div_76); + var consequent_61 = ($$anchor3) => { + var div_81 = root_82$1(); + var text_33 = child(div_81); + template_effect(() => set_text(text_33, `⚠ ${get(errorMsg) ?? ""}`)); + append($$anchor3, div_81); }; - if_block(node_69, ($$render) => { - if (get(errorMsg)) $$render(consequent_55); + if_block(node_75, ($$render) => { + if (get(errorMsg)) $$render(consequent_61); }); } - var node_70 = sibling(node_69, 2); + var node_76 = sibling(node_75, 2); { - var consequent_56 = ($$anchor3) => { - var div_77 = root_75$1(); - var text_29 = child(div_77); - template_effect(() => set_text(text_29, `⏳ ${get(selSetupMsg) ?? ""}`)); - append($$anchor3, div_77); + var consequent_62 = ($$anchor3) => { + var div_82 = root_83$1(); + var text_34 = child(div_82); + template_effect(() => set_text(text_34, `⏳ ${get(selSetupMsg) ?? ""}`)); + append($$anchor3, div_82); }; - var consequent_57 = ($$anchor3) => { - var div_78 = root_76$1(); - append($$anchor3, div_78); + var consequent_63 = ($$anchor3) => { + var div_83 = root_84$1(); + append($$anchor3, div_83); }; - if_block(node_70, ($$render) => { - if (get(selSetupMsg)) $$render(consequent_56); - else if (get(subjectBusy)) $$render(consequent_57, 1); + if_block(node_76, ($$render) => { + if (get(selSetupMsg)) $$render(consequent_62); + else if (get(subjectBusy)) $$render(consequent_63, 1); }); } - var node_71 = sibling(node_70, 2); - AdjustModal(node_71, { + var node_77 = sibling(node_76, 2); + AdjustModal(node_77, { get open() { return get(adjustOpen); }, @@ -12232,54 +13071,54 @@ function EditModal($$anchor, $$props) { set(adjustSrcCanvas, null); } }); - var node_72 = sibling(node_71, 2); + var node_78 = sibling(node_77, 2); { - var consequent_58 = ($$anchor3) => { - var div_79 = root_77$1(); - var div_80 = child(div_79); - var div_81 = child(div_80); - var text_30 = child(div_81); - var div_82 = sibling(div_81, 2); - var button_54 = child(div_82); - template_effect(() => set_text(text_30, `Could not use the ${get(tool) === "stamp" ? "clone stamp" : "healing brush"} because the area to sample from has not been defined. `)); - delegated("click", div_79, () => { - set(healDialog, false); - }); - delegated("click", button_54, () => { - set(healDialog, false); - }); - append($$anchor3, div_79); - }; - if_block(node_72, ($$render) => { - if (get(healDialog)) $$render(consequent_58); - }); - } - var node_73 = sibling(node_72, 2); - { - var consequent_59 = ($$anchor3) => { - var div_83 = root_78$1(); - var div_84 = child(div_83); + var consequent_64 = ($$anchor3) => { + var div_84 = root_85$1(); var div_85 = child(div_84); - var text_31 = child(div_85); - var div_86 = sibling(div_85, 2); - var button_55 = child(div_86); - template_effect(() => set_text(text_31, get(hiddenEditDialog))); - delegated("click", div_83, () => { - set(hiddenEditDialog, null); + var div_86 = child(div_85); + var text_35 = child(div_86); + var div_87 = sibling(div_86, 2); + var button_62 = child(div_87); + template_effect(() => set_text(text_35, `Could not use the ${get(tool) === "stamp" ? "clone stamp" : "healing brush"} because the area to sample from has not been defined. `)); + delegated("click", div_84, () => { + set(healDialog, false); }); - delegated("click", button_55, () => { - set(hiddenEditDialog, null); + delegated("click", button_62, () => { + set(healDialog, false); }); - append($$anchor3, div_83); + append($$anchor3, div_84); }; - if_block(node_73, ($$render) => { - if (get(hiddenEditDialog)) $$render(consequent_59); + if_block(node_78, ($$render) => { + if (get(healDialog)) $$render(consequent_64); }); } - var div_87 = sibling(node_73, 2); - var div_88 = child(div_87); - var node_74 = sibling(child(div_88), 2); - SavePathInput(node_74, { + var node_79 = sibling(node_78, 2); + { + var consequent_65 = ($$anchor3) => { + var div_88 = root_86$1(); + var div_89 = child(div_88); + var div_90 = child(div_89); + var text_36 = child(div_90); + var div_91 = sibling(div_90, 2); + var button_63 = child(div_91); + template_effect(() => set_text(text_36, get(hiddenEditDialog))); + delegated("click", div_88, () => { + set(hiddenEditDialog, null); + }); + delegated("click", button_63, () => { + set(hiddenEditDialog, null); + }); + append($$anchor3, div_88); + }; + if_block(node_79, ($$render) => { + if (get(hiddenEditDialog)) $$render(consequent_65); + }); + } + var div_92 = sibling(node_79, 2); + var div_93 = child(div_92); + var node_80 = sibling(child(div_93), 2); + SavePathInput(node_80, { get value() { return get(savePrefix); }, @@ -12290,21 +13129,21 @@ function EditModal($$anchor, $$props) { return fetchApi(); } }); - var node_75 = sibling(div_88, 2); + var node_81 = sibling(div_93, 2); { - var consequent_60 = ($$anchor3) => { - var span_22 = root_79$1(); - append($$anchor3, span_22); + var consequent_66 = ($$anchor3) => { + var span_24 = root_87$1(); + append($$anchor3, span_24); }; - if_block(node_75, ($$render) => { - if (get(editsSaved)) $$render(consequent_60); + if_block(node_81, ($$render) => { + if (get(editsSaved)) $$render(consequent_66); }); } - var button_56 = sibling(node_75, 2); - var button_57 = sibling(button_56, 2); - var text_32 = child(button_57); - var button_58 = sibling(button_57, 2); - var text_33 = child(button_58); + var button_64 = sibling(node_81, 2); + var button_65 = sibling(button_64, 2); + var text_37 = child(button_65); + var button_66 = sibling(button_65, 2); + var text_38 = child(button_66); action(div, ($$node) => { var _a; return (_a = portal) == null ? void 0 : _a($$node); @@ -12321,7 +13160,7 @@ function EditModal($$anchor, $$props) { classes_5 = set_class(button_20, 1, "pcr-ed-strip-color svelte-1ozzqkm", null, classes_5, { open: get(colorPopout) }); set_style(button_20, `background: ${get(hexColor) ?? ""};`); set_style(button_21, `background: ${$0 ?? ""};`); - classes_6 = set_class(div_38, 1, "pcr-ed-viewport svelte-1ozzqkm", null, classes_6, { + classes_6 = set_class(div_39, 1, "pcr-ed-viewport svelte-1ozzqkm", null, classes_6, { eyedrop: get(tool) === "eyedrop" || get(colorPopout), "select-mode": get(tool) === "select" || get(tool) === "lasso" || get(tool) === "objsel" || get(tool) === "smooth" || get(tool) === "crop" || get(tool) === "bucket", "move-mode": get(tool) === "move" || get(mods).ctrl && !get(mods).shift && !get(mods).alt, @@ -12329,43 +13168,47 @@ function EditModal($$anchor, $$props) { "no-edit": get(hiddenEditBlocked), "drag-over": get(dragActive) }); - styles = set_style(div_38, "", styles, { - cursor: get(transforming) && get(overRotateZone) ? ROTATE_CURSOR : null + styles = set_style(div_39, "", styles, { + cursor: get(spaceDown) ? "grab" : get(transforming) && get(overRotateZone) ? ROTATE_CURSOR : null }); - set_style(div_39, `transform: translate(${get(panX) ?? ""}px, ${get(panY) ?? ""}px) scale(${get(zoom) ?? ""}); width:${get(width) ?? ""}px; height:${get(height) ?? ""}px;`); + set_style(div_41, `transform: translate(${get(panX) ?? ""}px, ${get(panY) ?? ""}px) scale(${get(zoom) ?? ""}); width:${get(width) ?? ""}px; height:${get(height) ?? ""}px;`); set_attribute(img_1, "src", get(sourceUrl)); set_attribute(img_1, "width", get(width)); set_attribute(img_1, "height", get(height)); set_attribute(canvas, "width", get(width)); set_attribute(canvas, "height", get(height)); + styles_1 = set_style(canvas, "", styles_1, { display: get(hasCustomBlend) ? "none" : null }); set_attribute(canvas_1, "width", get(width)); set_attribute(canvas_1, "height", get(height)); - styles_1 = set_style(canvas_1, "", styles_1, $1); + styles_2 = set_style(canvas_1, "", styles_2, $1); + set_attribute(canvas_3, "width", get(width)); + set_attribute(canvas_3, "height", get(height)); + styles_4 = set_style(canvas_3, "", styles_4, { display: get(hasCustomBlend) ? null : "none" }); set_text(text_17, `${$2 ?? ""}%`); - set_style(div_63, `width: ${get(navTw) ?? ""}px; height: ${get(navTh) ?? ""}px;`); + set_style(div_65, `width: ${get(navTw) ?? ""}px; height: ${get(navTh) ?? ""}px;`); set_attribute(img_2, "src", get(sourceUrl)); set_style(img_2, `width: ${get(navTw) ?? ""}px; height: ${get(navTh) ?? ""}px;`); set_value(input, $3); - classes_8 = set_class(button_31, 1, "pcr-ed-lh-tab svelte-1ozzqkm", null, classes_8, { active: get(lhTab) === "layers" }); - classes_9 = set_class(button_32, 1, "pcr-ed-lh-tab svelte-1ozzqkm", null, classes_9, { active: get(lhTab) === "history" }); - button_57.disabled = !editDocHash() || !get(histIndex) || get(savingEdits) || get(saving); - set_attribute(button_57, "title", editDocHash() ? "Save your layers back onto this image — no new file, keeps the thumbnail" : "This image can't store edits in place — use Save new image"); - set_text(text_32, get(savingEdits) ? "Saving…" : "Save edits"); - button_58.disabled = !get(histIndex) || get(saving) || get(savingEdits); - set_attribute(button_58, "title", get(histIndex) ? "Export the result as a new image (keeps the original)" : "Paint something first"); - set_text(text_33, get(saving) ? "Saving…" : "Save new image"); + classes_8 = set_class(button_32, 1, "pcr-ed-lh-tab svelte-1ozzqkm", null, classes_8, { active: get(lhTab) === "layers" }); + classes_9 = set_class(button_33, 1, "pcr-ed-lh-tab svelte-1ozzqkm", null, classes_9, { active: get(lhTab) === "history" }); + button_65.disabled = !editDocHash() || !get(histIndex) || get(savingEdits) || get(saving); + set_attribute(button_65, "title", editDocHash() ? "Save your layers back onto this image — no new file, keeps the thumbnail" : "This image can't store edits in place — use Save new image"); + set_text(text_37, get(savingEdits) ? "Saving…" : "Save edits"); + button_66.disabled = !get(histIndex) || get(saving) || get(savingEdits); + set_attribute(button_66, "title", get(histIndex) ? "Export the result as a new image (keeps the original)" : "Paint something first"); + set_text(text_38, get(saving) ? "Saving…" : "Save new image"); }, [ () => rgbCss(get(bgRgb)), () => { - var _a, _b, _c, _d, _e; + var _a, _b, _c; return { - display: ((_a = activeLayer()) == null ? void 0 : _a.visible) ? null : "none", - opacity: ((_b = activeLayer()) == null ? void 0 : _b.opacity) ?? 1, - "mix-blend-mode": layerBlendCss(activeLayer()), - "mask-image": ((_c = activeLayer()) == null ? void 0 : _c.maskUrl) ? `url(${activeLayer().maskUrl})` : null, + display: get(hasCustomBlend) || !layerShown(activeLayer()) ? "none" : null, + opacity: layerEffOpacity(activeLayer()), + "mix-blend-mode": get(maskEdit) ? null : layerBlendCss(activeLayer()), + "mask-image": !get(maskEdit) && ((_a = activeLayer()) == null ? void 0 : _a.maskUrl) ? `url(${activeLayer().maskUrl})` : null, "mask-repeat": "no-repeat", - "mask-position": `${((_d = activeLayer()) == null ? void 0 : _d.ox) || 0}px ${((_e = activeLayer()) == null ? void 0 : _e.oy) || 0}px` + "mask-position": `${((_b = activeLayer()) == null ? void 0 : _b.ox) || 0}px ${((_c = activeLayer()) == null ? void 0 : _c.oy) || 0}px` }; }, () => Math.round(get(zoom) * 100), @@ -12383,47 +13226,47 @@ function EditModal($$anchor, $$props) { delegated("click", button_21, () => { set(bgRgb, [...get(rgb)], true); }); - event("dragover", div_38, onCanvasDragOver); - event("dragleave", div_38, () => { + event("dragover", div_39, onCanvasDragOver); + event("dragleave", div_39, () => { set(dragActive, false); }); - event("drop", div_38, onCanvasDrop); - delegated("pointerdown", div_38, onPointerDown); - delegated("pointermove", div_38, onPointerMove); - delegated("pointerup", div_38, onPointerUp); - event("pointercancel", div_38, onPointerUp); - event("pointerleave", div_38, () => { + event("drop", div_39, onCanvasDrop); + delegated("pointerdown", div_39, onPointerDown); + delegated("pointermove", div_39, onPointerMove); + delegated("pointerup", div_39, onPointerUp); + event("pointercancel", div_39, onPointerUp); + event("pointerleave", div_39, () => { set(cursorPos, null); }); - delegated("dblclick", div_38, () => { + delegated("dblclick", div_39, () => { if (get(tool) === "crop" && get(cropBox)) commitCrop(); }); - event("wheel", div_38, onWheel); - delegated("contextmenu", div_38, (e) => e.preventDefault()); - event("dragstart", div_38, (e) => e.preventDefault()); - event("selectstart", div_38, (e) => e.preventDefault()); + event("wheel", div_39, onWheel); + delegated("contextmenu", div_39, (e) => e.preventDefault()); + event("dragstart", div_39, (e) => e.preventDefault()); + event("selectstart", div_39, (e) => e.preventDefault()); event("load", img_1, () => { var _a, _b; update(imgLoadTick); if (((_a = activeLayer()) == null ? void 0 : _a.isBackground) && !((_b = activeLayer()) == null ? void 0 : _b.bitmap)) loadActiveBitmap(); }); - delegated("pointerdown", div_63, navDrag); - delegated("click", button_25, () => zoomStep(-1)); + delegated("pointerdown", div_65, navDrag); + delegated("click", button_26, () => zoomStep(-1)); delegated("input", input, (e) => setZoomCentered(Math.exp(parseFloat(e.target.value)))); - delegated("click", button_26, () => zoomStep(1)); - delegated("click", button_31, () => { + delegated("click", button_27, () => zoomStep(1)); + delegated("click", button_32, () => { set(lhTab, "layers"); }); - delegated("click", button_32, () => { + delegated("click", button_33, () => { set(lhTab, "history"); }); - delegated("click", button_56, close); - delegated("click", button_57, saveEdits); - delegated("click", button_58, save); + delegated("click", button_64, close); + delegated("click", button_65, saveEdits); + delegated("click", button_66, save); append($$anchor2, div); }; if_block(node, ($$render) => { - if ($$props.open) $$render(consequent_61); + if ($$props.open) $$render(consequent_67); }); } append($$anchor, fragment); diff --git a/js/lib/svelte/select-Dp4ExMMc.js b/js/lib/svelte/select-Bp4FHWwK.js similarity index 95% rename from js/lib/svelte/select-Dp4ExMMc.js rename to js/lib/svelte/select-Bp4FHWwK.js index 9b587f8..e5a0a40 100644 --- a/js/lib/svelte/select-Dp4ExMMc.js +++ b/js/lib/svelte/select-Bp4FHWwK.js @@ -1,4 +1,4 @@ -import { a2 as listen_to_event_and_reset_event, a3 as current_batch, a4 as effect, a5 as is_array, a6 as select_multiple_invalid_value, a7 as is, a8 as teardown } from "./disclose-version-uq4tn5Y6.js"; +import { a2 as listen_to_event_and_reset_event, a3 as current_batch, a4 as effect, a5 as is_array, a6 as select_multiple_invalid_value, a7 as is, a8 as teardown } from "./disclose-version-et9wt-4m.js"; function select_option(select, value, mounting = false) { if (select.multiple) { if (value == void 0) { @@ -97,4 +97,4 @@ export { init_select as i, select_option as s }; -//# sourceMappingURL=select-Dp4ExMMc.js.map +//# sourceMappingURL=select-Bp4FHWwK.js.map diff --git a/js/lib/svelte/style-Boi27oOu.js b/js/lib/svelte/style-B3hsaAru.js similarity index 90% rename from js/lib/svelte/style-Boi27oOu.js rename to js/lib/svelte/style-B3hsaAru.js index 7c0e249..3a24d23 100644 --- a/js/lib/svelte/style-Boi27oOu.js +++ b/js/lib/svelte/style-B3hsaAru.js @@ -1,4 +1,4 @@ -import { ac as to_style } from "./disclose-version-uq4tn5Y6.js"; +import { ac as to_style } from "./disclose-version-et9wt-4m.js"; function update_styles(dom, prev = {}, next, priority) { for (var key in next) { var value = next[key]; @@ -36,4 +36,4 @@ function set_style(dom, value, prev_styles, next_styles) { export { set_style as s }; -//# sourceMappingURL=style-Boi27oOu.js.map +//# sourceMappingURL=style-B3hsaAru.js.map diff --git a/js/lib/upscale-from-image.js b/js/lib/upscale-from-image.js index a26b37a..055b89c 100644 --- a/js/lib/upscale-from-image.js +++ b/js/lib/upscale-from-image.js @@ -813,32 +813,57 @@ function ensureDepthBranch(graph, LG, usNode, depthSource, loadNode) { const poser = graph._nodes.find((n) => n.comfyClass === "PromptChain_PoseStudio"); const sourceNode = depthSource === "pose" && poser ? poser : loadNode; const sourceSlot = sourceNode === poser ? (poser.outputs?.findIndex((o) => o.name === "IMAGE") ?? 0) : 0; + // The Poser feeds its IMAGE straight into Apply ControlNet when it ALREADY renders + // true depth — its uploaded control map is the depth map itself, so DepthAnything + // is skipped. A legacy clay-mode Poser keeps the estimator: nothing re-captures a + // depth render at queue time, so pointing a clay render at a depth net would be wrong. + const poseDirect = sourceNode === poser && + (poser.properties?.pcrOutputMode === "depth" || poser.properties?.pcrTrueDepth === true); const existing = findDepthApply(graph, usNode); if (existing) { - if (existing.pre) { + if (poseDirect) { + // Hint the apply straight from the Poser; retire any DepthAnything in between. + sourceNode.connect(sourceSlot, existing.apply, inputIdx(existing.apply, "image", 3)); + if (existing.pre) existing.pre.mode = 4; + } else if (existing.pre) { const cur = traceInput(graph, existing.pre, "image"); if (cur?.node !== sourceNode) { sourceNode.connect(sourceSlot, existing.pre, inputIdx(existing.pre, "image", 0)); } + } else { + // The apply was built pose-direct (no preprocessor) but the new source isn't + // a true-depth render — a clay-mode Poser or an image. Its raw pixels aren't a + // depth map, so splice a DepthAnything estimator back in between source and apply. + const pre = LG.createNode(resolvePre(CONTROLNET_TYPES.depth.pre)); + if (pre) { + pre.pos = [existing.apply.pos[0] - 340, existing.apply.pos[1]]; + graph.add(pre); + pre.size = [291, pre.size?.[1] ?? 130]; + const resW = pre.widgets?.find((w) => w.name === "resolution"); + if (resW) { resW.value = 1024; resW.callback?.(1024); } + sourceNode.connect(sourceSlot, pre, inputIdx(pre, "image", 0)); + pre.connect(0, existing.apply, inputIdx(existing.apply, "image", 3)); + } } return true; } const cfg = CONTROLNET_TYPES.depth; - const pre = LG.createNode(resolvePre(cfg.pre)); + const pre = poseDirect ? null : LG.createNode(resolvePre(cfg.pre)); const loader = LG.createNode("ControlNetLoader"); const setUnion = LG.createNode("SetUnionControlNetType"); const applyNode = LG.createNode("ControlNetApplyAdvanced"); - if (!pre || !loader || !setUnion || !applyNode) return false; + if ((!poseDirect && !pre) || !loader || !setUnion || !applyNode) return false; const ax = usNode.pos[0]; const ay = usNode.pos[1]; - pre.pos = [ax - 700, ay + 420]; + if (pre) pre.pos = [ax - 700, ay + 420]; loader.pos = [ax - 700, ay + 640]; setUnion.pos = [ax - 700, ay + 780]; applyNode.pos = [ax - 360, ay + 420]; - graph.add(pre); graph.add(loader); graph.add(setUnion); graph.add(applyNode); + if (pre) graph.add(pre); + graph.add(loader); graph.add(setUnion); graph.add(applyNode); applyNode.title = `ControlNet — Depth${sourceNode === poser ? " (pose)" : ""}`; applyNode.properties = { ...(applyNode.properties || {}), pcrControlType: "depth" }; @@ -850,15 +875,20 @@ function ensureDepthBranch(graph, LG, usNode, depthSource, loadNode) { else if (w.name === "start_percent") { w.value = 0.0; w.callback?.(0.0); } else if (w.name === "end_percent") { w.value = cfg.end; w.callback?.(cfg.end); } } - const resW = pre.widgets?.find((w) => w.name === "resolution"); + const resW = pre?.widgets?.find((w) => w.name === "resolution"); if (resW) { resW.value = 1024; resW.callback?.(1024); } const posSrc = traceInput(graph, usNode, "positive"); const negSrc = traceInput(graph, usNode, "negative"); if (!posSrc?.node || !negSrc?.node) return false; - sourceNode.connect(sourceSlot, pre, inputIdx(pre, "image", 0)); - pre.connect(0, applyNode, inputIdx(applyNode, "image", 3)); + if (pre) { + sourceNode.connect(sourceSlot, pre, inputIdx(pre, "image", 0)); + pre.connect(0, applyNode, inputIdx(applyNode, "image", 3)); + } else { + // Pose-direct: the Poser's true-depth IMAGE is the control map itself. + sourceNode.connect(sourceSlot, applyNode, inputIdx(applyNode, "image", 3)); + } loader.connect(0, setUnion, 0); setUnion.connect(0, applyNode, inputIdx(applyNode, "control_net", 2)); posSrc.node.connect(posSrc.slot, applyNode, inputIdx(applyNode, "positive", 0)); diff --git a/js/lib/viewer-bridge.js b/js/lib/viewer-bridge.js index 2eea896..8b4c0ea 100644 --- a/js/lib/viewer-bridge.js +++ b/js/lib/viewer-bridge.js @@ -306,7 +306,15 @@ export async function openViewer(images, startIndex = 0, workflowId = "", onDele onInpaintInstallPack: (injectable) => installInpaintPack(injectable), onMountPromptEditor: mountPromptEditor, onEditPrepare: (entry) => prepareEdit(entry), - onEditSave: (prepared, blob, prefix) => saveEditedImage(prepared, blob, prefix), + onEditSave: async (prepared, blob, prefix) => { + const entry = await saveEditedImage(prepared, blob, prefix); + // Opened from a node (e.g. LoadImage) that wants the result wired back. + if (entry && typeof opts.onAfterEditSave === "function") { + try { await opts.onAfterEditSave(entry); } + catch (e) { console.error("[PromptChain] onAfterEditSave failed", e); } + } + return entry; + }, onInpaintRegion: (data, args) => inpaintRegion(data, args), onReposeCaps: () => fetchReposeCaps(), onReposeRun: (opts) => runReposeInBackground(opts), diff --git a/js/loadimage-editor.js b/js/loadimage-editor.js new file mode 100644 index 0000000..a3f7e8c --- /dev/null +++ b/js/loadimage-editor.js @@ -0,0 +1,123 @@ +// Right-click a LoadImage node -> "Open in PromptChain editor": opens the +// node's currently-loaded input image in the PromptChain editor. A saved edit +// becomes a lineage child (parent = the loaded image) and re-points THIS node +// to the edited result — a NEW input file, not an overwrite of the source. +// +// A SEPARATE extension (own registerExtension) so a JS error here can never +// take down the prompt editor / poser / region box. The menu item rides +// getExtraMenuOptions, which fires in both the legacy and Vue node renderers — +// the only trigger that works uniformly (a bare click on the on-node image is +// not interceptable in Vue mode), mirroring core's "Open in MaskEditor". + +import { app } from "../../scripts/app.js"; +import { api } from "../../scripts/api.js"; +import { openViewer } from "./lib/viewer-bridge.js"; + +const TARGET_NODES = new Set(["LoadImage"]); + +// The loaded file's identity. node.imgs[i].src is the authoritative +// /view?filename=&subfolder=&type= URL; the 'image' widget value is the +// fallback (it can carry a trailing " [input]" annotation, which we strip). +function readLoadedImage(node) { + const src = node.imgs?.[node.imageIndex ?? 0]?.src; + if (src) { + try { + const u = new URL(src, window.location.origin); + const filename = u.searchParams.get("filename") || ""; + if (filename) { + return { + filename, + subfolder: u.searchParams.get("subfolder") || "", + type: u.searchParams.get("type") || "input", + }; + } + } catch {} + } + const widget = node.widgets?.find((w) => w.name === "image"); + const raw = typeof widget?.value === "string" ? widget.value : ""; + const val = raw.replace(/\s*\[(input|output|temp)\]\s*$/, ""); + if (!val) return null; + const parts = val.split("/"); + const filename = parts.pop(); + return { filename, subfolder: parts.join("/"), type: "input" }; +} + +function findImageWidget(node) { + return node.widgets?.find( + (w) => w.options?.image_upload === true || (w.type === "combo" && w.name === "image") + ) || null; +} + +// The edit is saved to the OUTPUT tree, but LoadImage reads the INPUT root. +// image-workflow?hash= stages a content-addressed promptchain_source_ +// copy into input/ and hands back input_ref — the same primitive prepareEdit +// uses for the source — which is then a valid LoadImage combo value. +async function attachEditedToNode(node, entry) { + if (!entry?.hash) return; + let inputRef = ""; + try { + const res = await api.fetchApi(`/promptchain/image-workflow?hash=${encodeURIComponent(entry.hash)}`); + if (res.ok) inputRef = (await res.json())?.input_ref || ""; + } catch (e) { + console.error("[PromptChain] LoadImage attach-back staging failed", e); + } + if (!inputRef) return; + + const widget = findImageWidget(node); + if (!widget) return; + // The full re-point: add the option, set the value, fire the combo callback + // (Comfy's installed refresh: node.imgs=undefined -> reload -> setDirtyCanvas). + // Setting .value alone does NOT reload the preview. + if (widget.options?.values && !widget.options.values.includes(inputRef)) { + widget.options.values.push(inputRef); + } + widget.value = inputRef; + widget.callback?.(inputRef); + node.setDirtyCanvas(true, true); +} + +async function openLoadImageInEditor(node) { + const img = readLoadedImage(node); + if (!img?.filename) { + app.extensionManager?.toast?.add?.({ + severity: "warn", + summary: "PromptChain", + detail: "This node has no loaded image to edit.", + life: 5000, + }); + return; + } + // A browse-style entry (no DB hash) — identical shape to the sidebar's, so + // prepareEdit resolves the input file as the lineage parent. + const scope = img.type === "output" ? "output" : "input"; + const path = img.subfolder ? `${img.subfolder}/${img.filename}` : img.filename; + const entry = { + hash: path, + filename: img.filename, + subfolder: "", + _directUrl: api.apiURL(`/promptchain/browse/preview?scope=${scope}&path=${encodeURIComponent(path)}`), + _browseScope: scope, + _browsePath: path, + }; + openViewer([entry], 0, "", null, { + autoEdit: true, + onAfterEditSave: (saved) => attachEditedToNode(node, saved), + }); +} + +app.registerExtension({ + name: "PromptChain.LoadImageEditor", + async beforeRegisterNodeDef(nodeType, nodeData) { + if (!TARGET_NODES.has(nodeData?.name)) return; + const original = nodeType.prototype.getExtraMenuOptions; + nodeType.prototype.getExtraMenuOptions = function (canvas, options) { + const result = original?.apply(this, arguments); + const node = this; + options.push({ + content: "Open in PromptChain editor", + callback: () => openLoadImageInEditor(node), + }); + return result; + }; + }, +}); diff --git a/js/main.js b/js/main.js index 1ff2bce..8dd96f2 100644 --- a/js/main.js +++ b/js/main.js @@ -367,20 +367,6 @@ function setupImagePreviewListeners() { // here describe the user's active graph, not that prompt's. if (detail.prompt_id && externallyTrackedPrompts.has(detail.prompt_id)) return; const nodeId = Number(detail.node); - let pcNodes = _outputToPC.get(nodeId); - let wfId = _executionWorkflowId; - if (!pcNodes?.length) { - // The SaveImage didn't map to a PromptChain node via the execution_start - // graph scan (trace miss / subgraph / workflow-id drift). Don't silently - // drop the generation — diagnose, then attribute it to the active graph's - // PromptChain node so the generation panel still catches it. - if (!wfId) wfId = getWorkflowId(); - const fallbackPC = (app.graph?._nodes || []).find((n) => n.comfyClass === NODE_TYPE); - console.warn(`[PromptChain][record] unmapped output node=${detail.node}->${nodeId} mapKeys=[${[..._outputToPC.keys()]}] wfId=${wfId} graphNodes=${app.graph?._nodes?.length} rootGraph=${!!app.rootGraph} fallbackPC=${fallbackPC?.id ?? "none"}`); - if (!fallbackPC || !wfId) return; - pcNodes = [fallbackPC]; - } - const img = detail.output.images[detail.output.images.length - 1]; const params = new URLSearchParams({ filename: img.filename, @@ -389,6 +375,39 @@ function setupImagePreviewListeners() { }); const imageUrl = api.apiURL(`/view?${params}&rand=${Math.random()}`); + // A PreviewImage emits type "temp" — a transient guide map (e.g. the 3D + // Poser depth control map), not a saved render. It still flashes in the + // node panel to guide the user, but must never be recorded: the Generated + // gallery holds saved outputs (SaveImage → "output") only. + const isSavedOutput = (img.type || "output") === "output"; + + let pcNodes = _outputToPC.get(nodeId); + let wfId = _executionWorkflowId; + if (!pcNodes?.length) { + // The SaveImage didn't map to a PromptChain node via the execution_start + // graph scan (trace miss / subgraph / workflow-id drift). Don't silently + // drop the generation — diagnose, then attribute it to the active graph's + // PromptChain node so the generation panel still catches it. A side-branch + // preview (the pose depth map) is EXPECTED to be unmapped — don't warn. + if (!wfId) wfId = getWorkflowId(); + const fallbackPC = (app.graph?._nodes || []).find((n) => n.comfyClass === NODE_TYPE); + if (isSavedOutput) { + console.warn(`[PromptChain][record] unmapped output node=${detail.node}->${nodeId} mapKeys=[${[..._outputToPC.keys()]}] wfId=${wfId} graphNodes=${app.graph?._nodes?.length} rootGraph=${!!app.rootGraph} fallbackPC=${fallbackPC?.id ?? "none"}`); + } + if (!fallbackPC || (isSavedOutput && !wfId)) return; + pcNodes = [fallbackPC]; + } + + // Transient preview (PreviewImage / non-"output"): flash in the panel to + // guide the user, then stop — no metadata, no gallery record. + if (!isSavedOutput) { + for (const pc of pcNodes) { + pc._pcrImagePanel?.updateImage(imageUrl, null); + pc._pcrImagePanel?.hideProgress(); + } + return; + } + // metadata from graph-determined nearest KSampler const entry = _saveNodeGraph.get(nodeId); let meta = entry?.ksampleId != null ? { ..._executionMeta.get(entry.ksampleId) } : {}; diff --git a/js/pose-studio.js b/js/pose-studio.js index 125b3e0..94fb5a4 100644 --- a/js/pose-studio.js +++ b/js/pose-studio.js @@ -113,9 +113,13 @@ function installPoseStyles() { // Stop pointer/key events from bubbling to LiteGraph (which would drag the node // or pop its context menu), but only in the BUBBLE phase — OrbitControls listens // on the canvas in the target phase, which runs first, so it still gets to orbit. +// `mousedown` is isolated too: when the viewport is docked inside the PromptChain +// fullscreen overlay, that overlay preventDefaults stray mousedowns to keep focus +// in the editor — which silently suppresses the popup of every native
+
+ From + +
click the image to pick a color · hold to preview · Screen pick grabs from anywhere {/if}
@@ -4458,7 +4911,7 @@ class:transform-mode={transforming} class:no-edit={hiddenEditBlocked} class:drag-over={dragActive} - style:cursor={transforming && overRotateZone ? ROTATE_CURSOR : null} + style:cursor={spaceDown ? "grab" : (transforming && overRotateZone ? ROTATE_CURSOR : null)} bind:this={viewportEl} ondragover={onCanvasDragOver} ondragleave={() => { dragActive = false; }} @@ -4474,6 +4927,14 @@ ondragstart={(e) => e.preventDefault()} onselectstart={(e) => e.preventDefault()} > + {#if maskEdit} + +
e.stopPropagation()}> + ✎ Editing layer mask — paint white to reveal, black to hide + +
+ {/if}
- + + A layer mask applies as a CSS mask so the canvas pixels stay raw. + Hidden (but still painted on) when the custom-blend composite is shown. --> {#each layers as L, i (L.id)} {#if i > activeIndex} {/if} {/each} + + {#if (tool === "heal" || tool === "stamp") && healSource && !healPaintingUi}
@@ -4704,48 +5168,83 @@ {/each}
- {#each [...layers].reverse() as L (L.id)} - {@const i = layers.indexOf(L)} - - -
clickLayer(i, e)} - oncontextmenu={(e) => { e.preventDefault(); if (!selectedIds.includes(L.id)) clickLayer(i, e); layerMenu = { x: e.clientX, y: e.clientY, i }; }} - ondragover={(e) => { e.preventDefault(); e.dataTransfer.dropEffect = "move"; dragOverId = L.id; }} - ondragleave={() => { if (dragOverId === L.id) dragOverId = null; }} - ondrop={(e) => { e.preventDefault(); dropLayer(L.id); }}> - { dragId = L.id; e.dataTransfer.effectAllowed = "move"; e.dataTransfer.setData("text/plain", String(L.id)); }} - ondragend={() => { dragId = null; dragOverId = null; }}>⠿ - { e.stopPropagation(); setLayerVisible(i, !L.visible); }}>{L.visible ? "👁" : "—"} - {#if renamingId === L.id} - - e.stopPropagation()} - onkeydown={(e) => { - e.stopPropagation(); - if (e.key === "Enter") commitRename(); - else if (e.key === "Escape") renamingId = null; - }} - onblur={commitRename} /> - {:else} - { e.stopPropagation(); startRename(L); }}>{L.name}{#if L.mask}▦{/if}{#if layerBlendCss(L)}{L.blend}{/if} - {/if} - { opacityDragStart = L.opacity; }} - oninput={(e) => setLayerOpacity(i, +e.currentTarget.value)} - onchange={(e) => { if (opacityDragStart === null || +e.currentTarget.value !== opacityDragStart) commitAction("Opacity"); opacityDragStart = null; }} - onclick={(e) => e.stopPropagation()} title="Opacity" /> -
+ {#each panelRows as row (row.type === "group" ? "g" + row.group.id : row.L.id)} + {#if row.type === "group"} + {@const g = row.group} + +
+ toggleGroupCollapsed(g.id)}>{g.collapsed ? "▸" : "▾"} + setGroupVisible(g.id, !g.visible)}>{g.visible ? "👁" : "—"} + {#if renamingGroupId === g.id} + + e.stopPropagation()} + onkeydown={(e) => { e.stopPropagation(); if (e.key === "Enter") commitGroupRename(); else if (e.key === "Escape") renamingGroupId = null; }} + onblur={commitGroupRename} /> + {:else} + { e.stopPropagation(); startGroupRename(g); }}>📁 {g.name} + {/if} + { opacityDragStart = g.opacity; }} + oninput={(e) => setGroupOpacity(g.id, +e.currentTarget.value)} + onchange={(e) => { if (opacityDragStart === null || +e.currentTarget.value !== opacityDragStart) commitAction("Group Opacity"); opacityDragStart = null; }} + onclick={(e) => e.stopPropagation()} title="Group opacity" /> + +
+ {:else} + {@const L = row.L} + {@const i = row.i} + + +
clickLayer(i, e)} + oncontextmenu={(e) => { e.preventDefault(); if (!selectedIds.includes(L.id)) clickLayer(i, e); layerMenu = { x: e.clientX, y: e.clientY, i, flip: e.clientX > window.innerWidth - 340 }; }} + ondragover={(e) => { e.preventDefault(); e.dataTransfer.dropEffect = "move"; dragOverId = L.id; }} + ondragleave={() => { if (dragOverId === L.id) dragOverId = null; }} + ondrop={(e) => { e.preventDefault(); dropLayer(L.id); }}> + { dragId = L.id; e.dataTransfer.effectAllowed = "move"; e.dataTransfer.setData("text/plain", String(L.id)); }} + ondragend={() => { dragId = null; dragOverId = null; }}>⠿ + { e.stopPropagation(); setLayerVisible(i, !L.visible); }}>{L.visible ? "👁" : "—"} + {#if L.mask} + + { e.stopPropagation(); toggleMaskEdit(i); }} /> + {/if} + {#if renamingId === L.id} + + e.stopPropagation()} + onkeydown={(e) => { + e.stopPropagation(); + if (e.key === "Enter") commitRename(); + else if (e.key === "Escape") renamingId = null; + }} + onblur={commitRename} /> + {:else} + { e.stopPropagation(); startRename(L); }}>{L.name}{#if L.blend && L.blend !== "normal"}{L.blend}{/if} + {/if} + { opacityDragStart = L.opacity; }} + oninput={(e) => setLayerOpacity(i, +e.currentTarget.value)} + onchange={(e) => { if (opacityDragStart === null || +e.currentTarget.value !== opacityDragStart) commitAction("Opacity"); opacityDragStart = null; }} + onclick={(e) => e.stopPropagation()} title="Opacity" /> +
+ {/if} {/each}
{:else} @@ -4760,23 +5259,19 @@
{#if layerMenu} + {@const Lm = layers[layerMenu.i]} + {@const multi = selectedIds.length > 1} + {@const groupable = selectedIds.filter((id) => !layers.find((ly) => ly.id === id)?.isBackground).length}
+ - {#if selectedIds.length > 1} + + {#if multi} {:else if layerMenu.i > 0} {/if} - - {#if !layers[layerMenu.i]?.isBackground} - {#if !layers[layerMenu.i]?.mask} - - {:else} - - - {/if} - {/if} - {#if layerMenu.i > 0 && layers[layerMenu.i]?.bitmap} + {#if layerMenu.i > 0 && Lm?.bitmap} {/if} + + + {#if groupable || Lm?.groupId} +
+ {#if groupable} + + {/if} + {#if Lm?.groupId} + + {/if} + {/if} + + + {#if !Lm?.isBackground} +
+ {#if !Lm?.mask} + + {:else} + +
+ +
+ + + + +
+ + +
+
+ {/if} + {/if} + + +
{#if canDeleteSelected()} - + {/if}
{/if} @@ -4795,11 +5326,11 @@
{#if selActive} - - + + {/if} - + {#if selActive} @@ -4938,6 +5469,7 @@ display: flex; align-items: center; gap: 8px; padding: 5px 7px; border-radius: 4px; cursor: pointer; background: #1c1c1c; border: 1px solid #2e2e2e; + -webkit-user-select: none; user-select: none; /* the name text must not hijack a press-drag */ } .pcr-ed-layer-row:hover { border-color: #3d3d3d; } .pcr-ed-layer-row.selected { background: rgba(200, 89, 9, 0.10); border-color: #5a4630; } @@ -4955,6 +5487,20 @@ } .pcr-ed-layer-menu button:hover { background: #c85909; color: #fff; } .pcr-ed-layer-menu button.danger:hover { background: #b23b3b; } + .pcr-ed-menu-sep { height: 1px; margin: 4px 6px; background: #3a3a3a; } + /* Mask actions collapse into a hover flyout (CSS-only, no timers). The flyout is + a DOM descendant of .pcr-ed-layer-menu so the outside-click dismiss still works. */ + .pcr-ed-submenu-host { position: relative; display: flex; flex-direction: column; } + .pcr-ed-submenu-trigger { display: flex; align-items: center; justify-content: space-between; } + .pcr-ed-submenu-caret { color: #888; font-size: 11px; margin-left: 8px; } + .pcr-ed-submenu { + position: absolute; left: 100%; top: -5px; min-width: 160px; + background: #232323; border: 1px solid #444; border-radius: 6px; padding: 4px; + box-shadow: 0 6px 22px rgba(0, 0, 0, 0.5); display: none; flex-direction: column; z-index: 1; + } + .pcr-ed-submenu-host.flip .pcr-ed-submenu { left: auto; right: 100%; } + .pcr-ed-submenu-host:hover .pcr-ed-submenu, .pcr-ed-submenu:hover { display: flex; } + .pcr-ed-submenu-host:hover .pcr-ed-submenu-trigger { background: #c85909; color: #fff; } .pcr-ed-layer-bg { cursor: default; opacity: 0.85; } .pcr-ed-layer-actions { display: flex; gap: 4px; } .pcr-ed-layer-btn { @@ -4964,8 +5510,8 @@ .pcr-ed-layer-btn:hover:not(:disabled) { color: #fff; border-color: #555; } .pcr-ed-layer-btn:disabled { opacity: 0.4; cursor: default; } .pcr-ed-layer-grip { - flex: none; width: 12px; text-align: center; - font-size: 11px; line-height: 1; color: #555; + flex: none; width: 16px; text-align: center; + font-size: 12px; line-height: 1; color: #666; cursor: grab; user-select: none; } .pcr-ed-layer-grip:hover { color: #aaa; } @@ -4976,11 +5522,19 @@ user-select: none; color: #ccc; } .pcr-ed-eye.off { color: #666; } + /* Photoshop-style layer-mask thumbnail (shows the mask shape; white glow = active paint target). */ + .pcr-ed-mask-thumb { + flex: none; width: 18px; height: 18px; border-radius: 3px; + object-fit: cover; background: #000; border: 1px solid #555; cursor: pointer; + } + .pcr-ed-mask-thumb:hover { border-color: #888; } + .pcr-ed-mask-thumb.editing { border-color: #fff; box-shadow: 0 0 0 1px #fff, 0 0 0 3px rgba(255, 255, 255, 0.30); } .pcr-ed-layer-name { flex: 1; font-size: 12px; color: #ddd; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .pcr-ed-layer-blendtag { margin-left: 5px; font-size: 10px; color: #8b96a5; } .pcr-ed-layer-rename { flex: 1; min-width: 0; font-size: 12px; color: #eee; background: #1b1b1b; border: 1px solid #c85909; border-radius: 3px; padding: 1px 4px; + -webkit-user-select: text; user-select: text; /* stays editable under the row's user-select:none */ } .pcr-ed-blend-select { width: 100%; margin: 0 0 4px; padding: 3px 6px; font-size: 12px; @@ -4988,6 +5542,13 @@ } .pcr-ed-blend-select:disabled { opacity: 0.45; } .pcr-ed-layer-opacity { width: 70px; accent-color: #c85909; } + .pcr-ed-group-row { display: flex; align-items: center; gap: 4px; padding: 3px 5px; border: 1px solid #333; border-radius: 4px; + background: rgba(120, 130, 150, 0.12); -webkit-user-select: none; user-select: none; } + .pcr-ed-group-row .pcr-ed-eye { cursor: pointer; } + .pcr-ed-group-name { flex: 1; font-size: 12px; font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; cursor: default; } + .pcr-ed-group-x { background: none; border: none; color: #999; cursor: pointer; font-size: 13px; padding: 0 2px; } + .pcr-ed-group-x:hover { color: #fff; } + .pcr-ed-layer-row.in-group { margin-left: 14px; } .pcr-ed-hist { display: flex; flex-direction: column; max-height: 170px; overflow-y: auto; diff --git a/svelte-src/components/sidebar/AssetBrowser.svelte b/svelte-src/components/sidebar/AssetBrowser.svelte index 41f038e..ce1585e 100644 --- a/svelte-src/components/sidebar/AssetBrowser.svelte +++ b/svelte-src/components/sidebar/AssetBrowser.svelte @@ -456,7 +456,13 @@ const item = ctxMenu?.item; if (item) { const root = scopeRoots[nav.scope] || ""; - const full = root ? `${root}/${item.path}` : item.path; + let full = root ? `${root}/${item.path}` : item.path; + // The backend normalizes root/paths to forward slashes for web use; restore + // native separators when the path is Windows-rooted (drive letter or UNC) so + // it pastes correctly into Explorer and Windows apps. + if (/^[a-zA-Z]:[/\\]/.test(full) || full.startsWith("//")) { + full = full.replace(/\//g, "\\"); + } await navigator.clipboard.writeText(full); toast?.("info", "Path copied to clipboard"); }