Collapse single group of layers

Engaged ,
Aug 09, 2022 Aug 09, 2022

Copy link to clipboard

Copied

Is it possible to collapse only currently selected group of layers in PS using javascript?
I have the problem that after completing my script, selected group stay expanded, even it is collapsed when i run script.
I found some solutions where all groups are collapsed,  but is possible to do it for a single group?

Thanks in advance

TOPICS
Actions and scripting , macOS

Views

213

Likes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines

correct answers 2 Correct answers

Community Expert , Aug 09, 2022 Aug 09, 2022

You can try the script that I created here:

 

https://community.adobe.com/t5/photoshop-ecosystem-discussions/action-or-script-to-expand-unfold-layer-groups/m-p/13077014#U13088415

 

I just updated it, it has not had exhaustive testing so it may or may not work as intended with your unique layer structure.

 

Feedback (good or bad) would be appreciated!

 

Likes

Translate

Translate
Community Expert , Aug 10, 2022 Aug 10, 2022

@milevic & @Signfeld 

 

Please try the following script, I have extended it with some of the ideas from this thread. Thank you @Signfeld for the discussion and bouncing ideas back and forth!

 

I believe that it is a better option than my script linked in the other topic.

 

This is still a hack to overcome missing features of Photoshop, which only allows one to:

Collapse All Groups...

But why not collapse the active group? Why not expand all groups? Why not expand the active group? ...However, I

...

Likes

Translate

Translate
Community Expert ,
Aug 09, 2022 Aug 09, 2022

Copy link to clipboard

Copied

You can try the script that I created here:

 

https://community.adobe.com/t5/photoshop-ecosystem-discussions/action-or-script-to-expand-unfold-lay...

 

I just updated it, it has not had exhaustive testing so it may or may not work as intended with your unique layer structure.

 

Feedback (good or bad) would be appreciated!

 

Likes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Enthusiast ,
Aug 09, 2022 Aug 09, 2022

Copy link to clipboard

Copied

I didn't try it but came across that code recently.

Wouldn't it be easier to move all layers out of the Group, delete the Group, and regroup the layers?

Maybe it's what you're doing in a dupe; haven't checked it out line by line...

Likes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Aug 09, 2022 Aug 09, 2022

Copy link to clipboard

Copied


@Signfeld wrote:

I didn't try it but came across that code recently.

Wouldn't it be easier to move all layers out of the Group, delete the Group, and regroup the layers?

Maybe it's what you're doing in a dupe; haven't checked it out line by line...


 

@Signfeld  Please do try it, I'd appreciate the feedback.

 

What is easy for one person may not be easy for another, I did it the way that was easiest for me which produced the required end result, while trying to keep the solution robust and maintaining all properties of the original parent group and child layers.

 

I'd be interested in seeing your solution, always keen to find another and or better way to do the same thing. It's a shame that a hack like this is required.

 

Likes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Enthusiast ,
Aug 09, 2022 Aug 09, 2022

Copy link to clipboard

Copied

I tend to need a whole day to get two lines of code working sometimes... 😉

I'm just basing my idea on how an action can close a folder, which AFAIK is by grouping layers.

So in a script, the first thing I would try is...

- select the (parent) Group and save its name

- select everything inside the group

- Group these and use the saved name (I expect this to be a closed group)

- Move the group outside of the original group (let's say we move it up: Ctrl + ] )

- Go one layer down and delete original group

- Move one layer up

I'm not sure if it would work like that in a script and haven't considered nested groups and stuff...  Just my initial idea.

I'll try yours later today  🙂

Likes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Aug 09, 2022 Aug 09, 2022

Copy link to clipboard

Copied


@Signfeld wrote:

I tend to need a whole day to get two lines of code working sometimes... 😉

 

I hear you! Been there too many times to count, it is super frustrating, knowing that potentially one character too few or one too many or in the wrong place is breaking things!

 

So in a script, the first thing I would try is...

- select the (parent) Group and save its name

- select everything inside the group

- Group these and use the saved name (I expect this to be a closed group)

- Move the group outside of the original group (let's say we move it up: Ctrl + ] )

- Go one layer down and delete original group

 

The original group name is preserved, however, how would you maintain the original groups:

* Opacity and or Fill values

* Blending mode

* Advanced blending options (layer option blend-if sliders)

* Anything else that may be lost with the re-creation of the original layer (colour label etc)

 

I of course hit many dead ends in what looked to be a promising approach, only to find out that there was an unforeseen issue that then forced me to go back to the drawing board.

 

Likes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Enthusiast ,
Aug 09, 2022 Aug 09, 2022

Copy link to clipboard

Copied

Aha, didn't think about all of those features I would lose...

Seems those could all be restored or copied as well, but my code could become much longer than yours 🙂

Likes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Aug 09, 2022 Aug 09, 2022

Copy link to clipboard

Copied


@Signfeld wrote:

Seems those could all be restored or copied as well, but my code could become much longer than yours 🙂


 

In theory yes, but in practice, how? That is where I was stuck which lead me to where I am now!

 

Code length isn't an issue as long as the execution is timely enough.

 

Likes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Enthusiast ,
Aug 09, 2022 Aug 09, 2022

Copy link to clipboard

Copied

I've seen scripts that I think copy/paste the Blend Ifs (or anything related) as a layer style; or maybe save and load it back. I mean, I've seen a script that does that, I don't have the code.

Indeed, we haven't found a way to read some stuff from a mask on a Group (like you mentioned recently). I had not even considered a mask being on the group... Sorry to give false hope... 😜  It always looks so easy initially...

Likes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Enthusiast ,
Aug 09, 2022 Aug 09, 2022

Copy link to clipboard

Copied

I did notice recently that when you use the ScriptListener code for dragging a mask to another layer, it preserves Density and Feather settings, so you wouldn't need to read/restore these parameters that way.

Doing this manually moves the mask, but doing this by action or script still leaves the original mask in place too, so in many cases you have to delete that.

Likes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Enthusiast ,
Aug 09, 2022 Aug 09, 2022

Copy link to clipboard

Copied

To recap — if my method would be used...

- Saving/restoring or copy/pasting the layer style is certainly possible, and this also includes Blend Mode, Opacity, and Fill.

- You can check if the original Group has a mask and copy that over safely (preserving Density/Opacity)

- That leaves the color coding (no idea if that's easy), and to check how many layers were initially clipped and have to be clipped back on.
Unless I'm forgetting something, it feels doable.

Likes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Enthusiast ,
Aug 09, 2022 Aug 09, 2022

Copy link to clipboard

Copied

Tested yours on a few cases and seems to work fine.

However, when a to be collapsed Group has opened Folders in it, it also collapses those. It might be good to have a parameter to not do that.  With my proposed method, it doesn't touch that and leaves them opened.

Both options are useful of course.

Likes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Aug 09, 2022 Aug 09, 2022

Copy link to clipboard

Copied


@Signfeld wrote:

Tested yours on a few cases and seems to work fine.

However, when a to be collapsed Group has opened Folders in it, it also collapses those. It might be good to have a parameter to not do that.  With my proposed method, it doesn't touch that and leaves them opened.

Both options are useful of course.


 

Thank you for the feedback, this is valuable. I of course only tested with a single group with no nested groups... I should know by now to create a more complex test file! I'm sure that is the result of the collapse all groups command used in the temp. duped file.

Likes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Enthusiast ,
Aug 09, 2022 Aug 09, 2022

Copy link to clipboard

Copied

Also, if your Group has a layer clipped onto it, it becomes unclipped... 😉 (in either method)

Likes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Aug 09, 2022 Aug 09, 2022

Copy link to clipboard

Copied


@Signfeld wrote:

Also, if your Group has a layer clipped onto it, it becomes unclipped... 😉 (in either method)


 

Damn, something else I didn't consider!

Likes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Engaged ,
Aug 10, 2022 Aug 10, 2022

Copy link to clipboard

Copied

Thanks @Stephen_A_Marsh

Great, as always

Likes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Aug 10, 2022 Aug 10, 2022

Copy link to clipboard

Copied

@milevic – as you would have seen in the ongoing discussion, it depends on the layer structure on how successful this will be for a given layer stack. YMMV so use with care/caution.

Likes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Aug 10, 2022 Aug 10, 2022

Copy link to clipboard

Copied

@milevic & @Signfeld 

 

Please try the following script, I have extended it with some of the ideas from this thread. Thank you @Signfeld for the discussion and bouncing ideas back and forth!

 

I believe that it is a better option than my script linked in the other topic.

 

This is still a hack to overcome missing features of Photoshop, which only allows one to:

Collapse All Groups...

But why not collapse the active group? Why not expand all groups? Why not expand the active group? ...However, I digress!

 

Note: Script updated 17th August 2022

 

/*
Collapse Active Group.jsx
Modified by Stephen Marsh, 17th August 2022
https://gist.github.com/MarshySwamp/a39e3e1efee41174b3b92ee68c40fcd8
https://community.adobe.com/t5/photoshop-ecosystem-discussions/collapse-single-group-of-layers/m-p/13132297

Based on:
https://community.adobe.com/t5/photoshop-ecosystem/expand-collapse-a-group-via-javascript/m-p/7286298
By Vova_p, 16th February 2016

*** KNOWN LIMITATIONS & GOTCHAS ***
This script is a hack to overcome a shortcoming of the original implementation of layer groups/sets:
+ Layers clipped to the group will lose their clipping group property
+ Default layer names such as "Layer 6" may be auto-renamed by Photoshop to "Layer 10" etc.
*/

#target photoshop

if (app.documents.length > 0) {

    function main() {

        if (activeDocument.activeLayer.typename === "LayerSet") {

            // Set the original document
            var origDoc = app.activeDocument;

            // Doc name variable
            var origDocName = activeDocument.name;

            // Store the name of the group
            var groupname = activeDocument.activeLayer.name;

            // Copy the active layer properties to a layer style (opacity, fill, blend mode, blend-if etc)
            var idcopyEffects = stringIDToTypeID("copyEffects");
            executeAction(idcopyEffects, undefined, DialogModes.NO);

            // Toggle the active layer visibility
            toggleLayerVisibility(true);

            // Create the temp doc from active layer group
            newDocFromLayer("_tempDoc");

            // Set the "destination" group variable
            var destinationGroup = activeDocument.activeLayer.name;

            // Dupe the set into a temp group
            activeDocument.activeLayer.duplicate();

            // Set the duped "source" group variable
            var sourceGroup = activeDocument.layers[0].name;

            // Ungroup the group to select all the child elements
            unGroup();

            // Group the selected layers to collapse the group
            groupSelectedLayers();

            // Recall the original group name
            activeDocument.activeLayer.name = groupname;

            // Copy the layer mask with all properties from the temp group
            copyLayerMask();

            // Restore the layer style to the new collapsed group
            pasteEffects(true);

            // Select and remove the temp group
            activeDocument.activeLayer = activeDocument.layers[0];
            activeDocument.activeLayer.remove();

            // Dupe the group back to the original doc
            dupeLayer();

            // Close the temp doc
            activeDocument.close(SaveOptions.DONOTSAVECHANGES);

            // Return to the original document
            app.activeDocument = origDoc;

            // Select the visible backward layer
            selectForwardORBackwardLayer(false, "backwardEnum");

            // Delete the original expanded group
            removeGroup();

            // Select the visible forward layer
            selectForwardORBackwardLayer(false, "forwardEnum");

            // Toggle the original active layer visibility
            toggleLayerVisibility(true);

        } else {
            alert("Only layer groups can be collapsed!");
        }

        /***** Functions *****/

        function dupeLayer() {
            function s2t(s) {
                return app.stringIDToTypeID(s);
            }
            var descriptor = new ActionDescriptor();
            var list = new ActionList();
            var reference = new ActionReference();
            var reference2 = new ActionReference();
            reference.putEnumerated(s2t("layer"), s2t("ordinal"), s2t("targetEnum"));
            descriptor.putReference(s2t("null"), reference);
            reference2.putName(s2t("document"), origDocName);
            descriptor.putReference(s2t("to"), reference2);
            descriptor.putInteger(s2t("version"), 5);
            list.putInteger(0);
            descriptor.putList(s2t("ID"), list);
            executeAction(s2t("duplicate"), descriptor, DialogModes.NO);
        }

        function toggleLayerVisibility(toggleOptionsPalette) {
            function s2t(s) {
                return app.stringIDToTypeID(s);
            }
            var descriptor = new ActionDescriptor();
            var list = new ActionList();
            var reference = new ActionReference();
            reference.putEnumerated(s2t("layer"), s2t("ordinal"), s2t("targetEnum"));
            list.putReference(reference);
            descriptor.putList(s2t("null"), list);
            descriptor.putBoolean(s2t("toggleOptionsPalette"), toggleOptionsPalette);
            executeAction(s2t("show"), descriptor, DialogModes.NO);
        }

        function newDocFromLayer(docName) {
            function s2t(s) {
                return app.stringIDToTypeID(s);
            }
            var descriptor = new ActionDescriptor();
            var reference = new ActionReference();
            var reference2 = new ActionReference();
            reference.putClass(s2t("document"));
            descriptor.putReference(s2t("null"), reference);
            descriptor.putString(s2t("name"), docName);
            reference2.putEnumerated(s2t("layer"), s2t("ordinal"), s2t("targetEnum"));
            descriptor.putReference(s2t("using"), reference2);
            descriptor.putInteger(s2t("version"), 0);
            executeAction(s2t("make"), descriptor, DialogModes.NO);
        }

        function unGroup() {
            function c2t(s) {
                return app.charIDToTypeID(s);
            }
            var s2t = function (s) {
                return app.stringIDToTypeID(s);
            };
            var descriptor = new ActionDescriptor();
            var reference = new ActionReference();
            reference.putEnumerated(s2t("layer"), s2t("ordinal"), s2t("targetEnum"));
            descriptor.putReference(c2t("null"), reference);
            executeAction(s2t("ungroupLayersEvent"), descriptor, DialogModes.NO);
        }

        function groupSelectedLayers() {
            function c2t(s) {
                return app.charIDToTypeID(s);
            }
            var s2t = function (s) {
                return app.stringIDToTypeID(s);
            };
            var descriptor = new ActionDescriptor();
            var reference = new ActionReference();
            var reference2 = new ActionReference();
            reference.putClass(s2t("layerSection"));
            descriptor.putReference(c2t("null"), reference);
            reference2.putEnumerated(s2t("layer"), s2t("ordinal"), s2t("targetEnum"));
            descriptor.putReference(s2t("from"), reference2);
            executeAction(s2t("make"), descriptor, DialogModes.NO);
        }

        function copyLayerMask() {
            // Copy layer mask - preserving feather, density
            function s2t(s) {
                return app.stringIDToTypeID(s);
            }
            var descriptor = new ActionDescriptor();
            var reference = new ActionReference();
            var reference2 = new ActionReference();
            descriptor.putClass(s2t("new"), s2t("channel"));
            reference.putEnumerated(s2t("channel"), s2t("channel"), s2t("mask"));
            reference.putName(s2t("layer"), destinationGroup); // Destination group name
            descriptor.putReference(s2t("at"), reference);
            reference2.putEnumerated(s2t("channel"), s2t("channel"), s2t("mask"));
            reference2.putName(s2t("layer"), sourceGroup); // Source group name
            descriptor.putReference(s2t("using"), reference2);
            executeAction(s2t("make"), descriptor, DialogModes.NO);
        }

        function selectForwardORBackwardLayer(makeVisible, forwardORbackward) {
            function s2t(s) {
                return app.stringIDToTypeID(s);
            }
            var descriptor = new ActionDescriptor();
            var list = new ActionList();
            var reference = new ActionReference();
            // "forwardEnum" or "backwardEnum"
            reference.putEnumerated(s2t("layer"), s2t("ordinal"), s2t(forwardORbackward));
            descriptor.putReference(s2t("null"), reference);
            // true or false
            descriptor.putBoolean(s2t("makeVisible"), makeVisible);
            list.putInteger(15);
            descriptor.putList(s2t("layerID"), list);
            executeAction(s2t("select"), descriptor, DialogModes.NO);
        }

        function removeGroup() {
            function s2t(s) {
                return app.stringIDToTypeID(s);
            }
            var descriptor = new ActionDescriptor();
            var reference = new ActionReference();
            reference.putEnumerated(s2t("layer"), s2t("ordinal"), s2t("targetEnum"));
            descriptor.putReference(s2t("null"), reference);
            executeAction(s2t("delete"), descriptor, DialogModes.NO);
        }

        function pasteEffects(allowPasteFXOnLayerSet) {
            function s2t(s) {
                return app.stringIDToTypeID(s);
            }
            var descriptor = new ActionDescriptor();
            descriptor.putBoolean(s2t("allowPasteFXOnLayerSet"), allowPasteFXOnLayerSet);
            executeAction(s2t("pasteEffects"), descriptor, DialogModes.NO);
        }
    }
    app.activeDocument.suspendHistory("Collapse Active Group.jsx", "main()");

} else {
    alert('You must have a document open!');
}

 

Likes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Enthusiast ,
Aug 10, 2022 Aug 10, 2022

Copy link to clipboard

Copied

Cool. I agree this is a better direction and I suspect you can get there all the way.

This one still loses the mask as well...

I PM'd you yesterday what I use to copy a mask, preserving Feather and Density. If you want to save the mask as a channel, you run into the problem that you mentioned of possibly not being able to read some specs of a Group mask (which you need, as making it a channel loses these specs).

-

I think I would first rename the Group to a failsafe name, as I'd want that for the function restoring the mask. If possible, I might then go up and count how many layers are clipped to the Group (and reselect the failsafe group name). At the end, I'd use this number in a loop to reclip layers above the Group.

-

(I haven't been getting many notifications either, but a few today.)

Likes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Enthusiast ,
Aug 10, 2022 Aug 10, 2022

Copy link to clipboard

Copied

Also, if layers inside had colors, these are currently lost...

Signfeld_0-1660162434350.png

 

Likes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Aug 10, 2022 Aug 10, 2022

Copy link to clipboard

Copied

@Signfeld – I added code to capture and restore the original group label colour, which works, however when it is applied it is also inadvertently applied to child groups/sets. Back to the drawing board...

Likes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Engaged ,
Aug 11, 2022 Aug 11, 2022

Copy link to clipboard

Copied

I agree there are a bunch of different scenarios but for my needs this script works perfectly,  thanks to both of you for your dedication and thanks @Stephen_A_Marsh for code

Likes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Aug 12, 2022 Aug 12, 2022

Copy link to clipboard

Copied


@milevic wrote:

I agree there are a bunch of different scenarios but for my needs this script works perfectly,  thanks to both of you for your dedication and thanks @Stephen_A_Marsh for code


 

Agreed, it works for your use case, which is great! I'd like to make this bullet-proof, however, this is proving to be a challenge. At least some of the limitations are known.

Likes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Enthusiast ,
Aug 12, 2022 Aug 12, 2022

Copy link to clipboard

Copied

I see you've added code for the mask, but it loses Feather and Density settings.

I keep recommending this code that copies the mask preserving both.

This is ScriptListener code produced by moving a mask manually. The code, however, leaves a copy in place (that you sometimes have to delete).

I guess for your current method, you may have to put the mask on a temporary new layer, copy it back, remove the temp layer...

 

// preserves Feather and Density
function copyMaskToLayer(layerName) {
	var idMk = charIDToTypeID( "Mk  " );
	var desc4142 = new ActionDescriptor();
	var idNw = charIDToTypeID( "Nw  " );
	var idChnl = charIDToTypeID( "Chnl" );
	desc4142.putClass( idNw, idChnl );
	var idAt = charIDToTypeID( "At  " );
	var ref822 = new ActionReference();
	var idChnl = charIDToTypeID( "Chnl" );
	var idChnl = charIDToTypeID( "Chnl" );
	var idMsk = charIDToTypeID( "Msk " );
	ref822.putEnumerated( idChnl, idChnl, idMsk );
	var idLyr = charIDToTypeID( "Lyr " );
	ref822.putName( idLyr, layerName );
	desc4142.putReference( idAt, ref822 );
	var idUsng = charIDToTypeID( "Usng" );
	var ref823 = new ActionReference();
	var idChnl = charIDToTypeID( "Chnl" );
	var idChnl = charIDToTypeID( "Chnl" );
	var idMsk = charIDToTypeID( "Msk " );
	ref823.putEnumerated( idChnl, idChnl, idMsk );
	var idLyr = charIDToTypeID( "Lyr " );
	var idOrdn = charIDToTypeID( "Ordn" );
	var idTrgt = charIDToTypeID( "Trgt" );
	ref823.putEnumerated( idLyr, idOrdn, idTrgt );
	desc4142.putReference( idUsng, ref823 );
	executeAction( idMk, desc4142, DialogModes.NO );
};

 

 

Likes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Aug 12, 2022 Aug 12, 2022

Copy link to clipboard

Copied

LATEST

@Signfeld wrote:

I see you've added code for the mask, but it loses Feather and Density settings.

I keep recommending this code that copies the mask preserving both.

 

I'm taking it in baby steps as time permits. Thanks for sticking with me!

 

Edit: 17th August 2022 - Script updated, colour label and mask density/feather properties are now maintained!

 

Looks like I'm still forgetting things and reinventing the wheel again:

Likes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines