Is there an option that shows me a list with all smart objects paths?

5 Votes
Community Beginner ,
Jan 13, 2022 Jan 13, 2022

Copy link to clipboard

Copied

I have several projects that needs to be reskinned (same distribution of assets but different artwork). I normally duplicate an initial project to work on it and change the arwork, but sometimes, duplicated files are linked to the inistial project instead of the duplicated one. (Photoshop takes this linking as correct and doesnt show me a warning, so I need to check all linked layers individually. I would like to know if there is a list of all linked smart objects with its path (just like the initial window that usually warns you when a linked smart object is missing.), but this time I need to see a window like the one I attach but with all linked smart objectsScreen Shot 2022-01-13 at 4.41.11 PM copy.jpg.

Any help? thank you!

Idea No status
TOPICS
Actions and scripting , macOS

Views

534

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
27 Comments
Adobe Community Professional ,
Jan 13, 2022 Jan 13, 2022

Copy link to clipboard

Copied

It would be nice if there was a links panel (there currently is not so it may be worth a feature request).

 

In the meantime, there are two places you can see the links.

1. By clicking in the layers panel on each linked smart object in turn, the path for the linked content is shown in the properties panel.

 

2. File Info - Raw data contains the paths but they are not all together so can be hard to spot.

 

Dave

Votes

Translate

Translate

Report

Report
Adobe Community Professional ,
Jan 14, 2022 Jan 14, 2022

Copy link to clipboard

Copied

Votes

Translate

Translate

Report

Report
Adobe Community Professional ,
Jan 14, 2022 Jan 14, 2022

Copy link to clipboard

Copied

A list of the linked Smart Objects’ paths should not be too hard to get with a Script, but I suspect you do not just want a list, right? 

Votes

Translate

Translate

Report

Report
Adobe Community Professional ,
Jan 14, 2022 Jan 14, 2022

Copy link to clipboard

Copied

Feature request comes under Idea's

2022-01-14 09_36_37-Window.png

Votes

Translate

Translate

Report

Report
Adobe Community Professional ,
Jan 14, 2022 Jan 14, 2022

Copy link to clipboard

Copied

Indeed Ged, it appears that Adobe staff have been moving over some old topics from the uservoice site to this one, but I couldn't find that one moved over here? Yes a new idea/feature request topic could be made, but if the old topic is going to be moved over anyway...

Votes

Translate

Translate

Report

Report
Adobe Community Professional ,
Jan 14, 2022 Jan 14, 2022

Copy link to clipboard

Copied

The closest that I could find was the following, which is only for the active layer:

 

https://stackoverflow.com/questions/63010107/get-a-smart-objects-layers-files-directory-source-in-js...

 

Votes

Translate

Translate

Report

Report
Adobe Community Professional ,
Jan 14, 2022 Jan 14, 2022

Copy link to clipboard

Copied

Apologies Stephen, I misread your initial post

Votes

Translate

Translate

Report

Report
Adobe Community Professional ,
Jan 14, 2022 Jan 14, 2022

Copy link to clipboard

Copied

No worries Ged, I'd be happy to create a new idea post but it seems like extra work if old uservoice posts are being migrated. Should this be discussed in the ACP forum? If there is a topic please send me a private message with the ACP link etc.

Votes

Translate

Translate

Report

Report
Adobe Community Professional ,
Jan 14, 2022 Jan 14, 2022

Copy link to clipboard

Copied

I've moved this thread to "ideas" so that anyone can add their vote at the top of the thread. I've added mine 🙂

 

A panel similar to the re-linking dialogue box but with all links listed would work for me. It would help provide a quick check that SOs are linked to the correct documents.

 

Dave

 

Votes

Translate

Translate

Report

Report
Adobe Community Professional ,
Jan 14, 2022 Jan 14, 2022

Copy link to clipboard

Copied

Well, a list of the linked files’ paths is not hard to get. 

// get list of linked smart objects’ paths;
// 2022, use it at your own risk;
var aaa = collectSmartObjectsLinks ();
alert ("\n"+aaa.join("\n\n"));
////// collect layers //////
function collectSmartObjectsLinks () {
// get number of layers;
var ref = new ActionReference();
ref.putProperty(stringIDToTypeID('property'), stringIDToTypeID('numberOfLayers'));
ref.putEnumerated( charIDToTypeID("Dcmn"), charIDToTypeID("Ordn"), charIDToTypeID("Trgt") ); 
var applicationDesc = executeActionGet(ref);
var theNumber = applicationDesc.getInteger(stringIDToTypeID("numberOfLayers"));
// process the layers;
var theLayers = new Array;
for (var m = 0; m <= theNumber; m++) {
try {
var ref = new ActionReference();
ref.putIndex( charIDToTypeID( "Lyr " ), m);
var layerDesc = executeActionGet(ref);
var layerSet = typeIDToStringID(layerDesc.getEnumerationValue(stringIDToTypeID("layerSection")));
var isBackground = layerDesc.getBoolean(stringIDToTypeID("background"));
var isSmartObject = layerDesc.hasKey(stringIDToTypeID("smartObject"));
// collect smart object links;
if (isSmartObject == true) {
var theSO = layerDesc.getObjectValue(stringIDToTypeID("smartObject"));
if (theSO.getBoolean(stringIDToTypeID("linked")) == true) {
var theLink = theSO.getPath(stringIDToTypeID("link"));
var theCheck = true;
for (var n = 0; n < theLayers.length; n++) {
if (theLayers[n] == String(theLink)) {theCheck = false}
};
if (theCheck == true) {theLayers.push(theLink)};
};
};
}
catch (e) {};
};
return theLayers
};

Votes

Translate

Translate

Report

Report
Adobe Community Professional ,
Jan 14, 2022 Jan 14, 2022

Copy link to clipboard

Copied

@c.pfaffenbichler – excellent stuff, not hard for you! :]

 

I added a couple of extra bits:

1) Copy result to clipboard

2) Write result to text file on desktop

 

// https://community.adobe.com/t5/photoshop-ecosystem-ideas/is-there-an-option-that-shows-me-a-list-with-all-smart-objects-paths/idi-p/12657491
// get list of linked smart objects’ paths;
// 2022, use it at your own risk;
var aaa = collectSmartObjectsLinks();
alert("\n" + aaa.join("\n") + "\n\n" + "(copied to clipboard)");

// SO linked paths to list
var list = aaa.join("\n");

// Copy SO linked paths list to clipboard
var d = new ActionDescriptor();
d.putString(stringIDToTypeID('textData'), list);
executeAction(stringIDToTypeID('textToClipboard'), d, DialogModes.NO);

// Text file platform specific LF options
var os = $.os.toLowerCase().indexOf("mac") >= 0 ? "mac" : "windows";
if (os === "mac") {
    textFileLF = "Unix"; // Legacy = "Macintosh"
} else {
    textFileLF = "Windows";
}
// Create a desktop text file of the SO linked paths
var textFile = new File('~/Desktop' + '/' + 'Linked SO Paths.txt');
if (textFile.exists)
    textFile.remove();
    textFile.open('w');
    textFile.encoding = 'UTF-8';
    textFile.lineFeed = textFileLF;
    textFile.write(list);
    textFile.close();
    
    // Open the text file
    textFile.execute();

////// collect layers //////
function collectSmartObjectsLinks() {
    // get number of layers;
    var ref = new ActionReference();
    ref.putProperty(stringIDToTypeID('property'), stringIDToTypeID('numberOfLayers'));
    ref.putEnumerated(charIDToTypeID("Dcmn"), charIDToTypeID("Ordn"), charIDToTypeID("Trgt"));
    var applicationDesc = executeActionGet(ref);
    var theNumber = applicationDesc.getInteger(stringIDToTypeID("numberOfLayers"));
    // process the layers;
    var theLayers = new Array();
    for (var m = 0; m <= theNumber; m++) {
        try {
            var ref = new ActionReference();
            ref.putIndex(charIDToTypeID("Lyr "), m);
            var layerDesc = executeActionGet(ref);
            //var layerSet = typeIDToStringID(layerDesc.getEnumerationValue(stringIDToTypeID("layerSection")));
            //var isBackground = layerDesc.getBoolean(stringIDToTypeID("background"));
            var isSmartObject = layerDesc.hasKey(stringIDToTypeID("smartObject"));
            // collect smart object links;
            if (isSmartObject === true) {
                var theSO = layerDesc.getObjectValue(stringIDToTypeID("smartObject"));
                if (theSO.getBoolean(stringIDToTypeID("linked")) === true) {
                    var theLink = theSO.getPath(stringIDToTypeID("link"));
                    var theCheck = true;
                    for (var n = 0; n < theLayers.length; n++) {
                        if (theLayers[n] == String(theLink)) {
                            theCheck = false;
                        }
                    }
                    if (theCheck === true) {
                        theLayers.push(theLink);
                    }
                }
            }
        } catch (e) {}
    }
    return theLayers;
}

 

https://prepression.blogspot.com/2017/11/downloading-and-installing-adobe-scripts.html

 

Votes

Translate

Translate

Report

Report
Community Beginner ,
Jan 14, 2022 Jan 14, 2022

Copy link to clipboard

Copied

Exactly, a list where I can relink anything that is not correctly linked
would be expected.

--
Julio Fernández
-Concept artist and illustrator-

Votes

Translate

Translate

Report

Report
Community Beginner ,
Jan 14, 2022 Jan 14, 2022

Copy link to clipboard

Copied

Thank you Dave! I had to do it one by one as you mention with the
properties, but I have hundreds haha. Same with File Info. :/. Adobe
definitely needs that window to be added.

--
Julio Fernández
-Concept artist and illustrator-

Votes

Translate

Translate

Report

Report
Adobe Community Professional ,
Jan 14, 2022 Jan 14, 2022

Copy link to clipboard

Copied

What is correct vs incorrect link? You know and can do this manually, but how can you instruct a computer to automate? What are the rules, criteria etc?


Please post an example of the full path and filename in your template. Also do the same for the replacement image and path.

 

Please provide at least three example of old and new pairs.

 

How would you propose that an automated relinking be performed? Different source folder, but same linked SO image name?

Votes

Translate

Translate

Report

Report
Community Beginner ,
Jan 14, 2022 Jan 14, 2022

Copy link to clipboard

Copied

Hi, sorry I'm not so good at engineering, I'm an artist haha, but let me know if this explains better the situation:

 

When I duplicate a project for an art reskin, sometimes Photoshop do the job and links the files inside that new project, but sometimes it doesnt; when that happens Photoshop does not warn you, cause all smart objects are linked, but what it doesnt know is that some linked smart objectas are linked to the previous assets form the previous project.   I'll try to explain it with the next images:

 

Screen Shot 2022-01-14 at 3.25.46 PM.pngScreen Shot 2022-01-14 at 3.30.52 PM.png

Votes

Translate

Translate

Report

Report
Adobe Community Professional ,
Jan 14, 2022 Jan 14, 2022

Copy link to clipboard

Copied

Photoshop already has a magic wand tool, but I think you wish to wave a different magic wand. 😐

 

So, from your diagram, the linked PSD files in the assets folder always have the same name, same dimensions etc(just different content)?


It is just the top level project folder which has a different name and path?

 

As I originally requested:

Please post an example of the full path and filename in your template. Also do the same for the replacement image and path.

Please provide at least three example of old and new pairs.

 

It may make sense to look at Adobe InDesign for inspiration as it has more mature link handling. Some of the features don't make sense in Photoshop, but mass relinking to a new folder or extension does:

 

id-links.png

 

relink-folder.png

 

relink-extension.png

Votes

Translate

Translate

Report

Report
Community Beginner ,
Jan 14, 2022 Jan 14, 2022

Copy link to clipboard

Copied

"So, from your diagram, the linked PSD files in the assets folder always have the same name, same dimensions etc? It is just the top level project folder which has a different name and path?"   For this, my answer is yes.  This use to be a problem specially cause different people has the whole project downloaded in different Drives across the globe, and we sync it via Resilio sync. So, the top level project folder usually is their own hard Drive.   I cannot share screenshots of my files due to confidentialty company rules :(. And to replicate this It would take a lot of time that I dont have right now. 

I was looking for something exactly like you showed me on indesign, but in photoshop.  It's what I meant with my image example with the list of linked files where we can relink them manually. 

Votes

Translate

Translate

Report

Report
Adobe Community Professional ,
Jan 15, 2022 Jan 15, 2022

Copy link to clipboard

Copied

How important is this for you right now (as opposed to a possible future implementation)? 

 

Creating a dialog or, better yet, a Panel for such operations would seem a somewhat involved task but I suspect that there are people (not me, though) who could handle it. 

Would you be willing to hire someone to write this? 

Votes

Translate

Translate

Report

Report
Adobe Community Professional ,
Jan 15, 2022 Jan 15, 2022

Copy link to clipboard

Copied

A scriptUI dialog, legacy floating CEP panel extension - or even a new style UXP panel would be nice, but perhaps overkill at the simplest level...

 

All one would need for a basic improvement is a script to update all links relative to a new directory path. Just like InDesign's relink to folder option. Same image name, same extension - just a different new path.

 

From a scripting perspective this should be relatively simple to script in a basic form (famous last words).

 

A simple brute force approach would update all files with a new path, blindly assuming that the file exists with the same name at the new directory location* (which is how you have described your project directory structure, same name/format links, same quantity, just with updated content).

 

A more sophisticated script would only update images that had a different path than the new project and warn if missing links were found that didn't exist in the new directory.

 

*Example:

instructions.png

 

Votes

Translate

Translate

Report

Report
Adobe Community Professional ,
Jan 16, 2022 Jan 16, 2022

Copy link to clipboard

Copied

Here is the script to relink to selected folder, it has only had limited testing. I have also added the list from @c.pfaffenbichler so it's a bit of a mashup!

 

// https://community.adobe.com/t5/photoshop-ecosystem-ideas/is-there-an-option-that-shows-me-a-list-with-all-smart-objects-paths/idi-p/12657491
// Relink (linked) smart object layers to new asset folder
// 

#target photoshop

var newPath = Folder.selectDialog('Relink to Folder');
var newPath = newPath.fsName + "/";
$.writeln("New path: " + newPath);

// Loop forward over top level layers
// for (var i = 0; i < app.activeDocument.layers.length; i++) {
// Loop backward over top level layers
for (var i = app.activeDocument.layers.length - 1; i >= 0; i--) {
        app.activeDocument.activeLayer = app.activeDocument.layers[i];
        var activeSO = getSmartObjectReference();
        relinkSmartObject();
}


// Functions

function relinkSmartObject() {
    try {
        if (activeSO.found) {
            var oldPath = activeSO.filePath.fsName;
            var oldPathOnly = oldPath.replace(/(^.+\/)(?:.+$)/, '$1');
            $.writeln("Old path: " + oldPathOnly);
            var fileName = oldPath.replace(/(?:^.+\/)(.+$)/, '$1');
            $.writeln("Filename: " + fileName);
            var relinkToFile = stringIDToTypeID("placedLayerRelinkToFile");
            var AD = new ActionDescriptor();
            var idNull = stringIDToTypeID("null");
            AD.putPath(idNull, new File(newPath + fileName));
            executeAction(relinkToFile, AD, DialogModes.NO);
        }
    } catch (e) {}
}

function getSmartObjectReference()
/* https://stackoverflow.com/questions/63010107/get-a-smart-objects-layers-files-directory-source-in-jsx */
{
    try {
        var smartObject = {
            found: false,
            fileRef: '',
            filePath: '',
            linked: false,
        };
        var ref, so;
        ref = new ActionReference();
        ref.putProperty(charIDToTypeID("Prpr"), stringIDToTypeID("smartObject"));
        ref.putEnumerated(charIDToTypeID("Lyr "), charIDToTypeID("Ordn"), charIDToTypeID("Trgt"));
        so = executeActionGet(ref).getObjectValue(stringIDToTypeID("smartObject"));
        smartObject.found = true;
        smartObject.linked = so.getBoolean(stringIDToTypeID("linked"));
        smartObject.fileRef = so.getString(stringIDToTypeID("fileReference"));
        if (smartObject.linked) {
            smartObject.filePath = so.getPath(stringIDToTypeID("link"));
        }
        return smartObject;
    } catch (e) {
        return smartObject;
    }
}

///////////////////////////

// https://community.adobe.com/t5/photoshop-ecosystem-ideas/is-there-an-option-that-shows-me-a-list-with-all-smart-objects-paths/idi-p/12657491
// get list of linked smart objects’ paths;
// 2022, use it at your own risk;
var aaa = collectSmartObjectsLinks();
alert("\n" + aaa.join("\n\n") + "\n\n" + "(copied to clipboard)");

// SO linked paths to list
var list = aaa.join("\n");

// Copy SO linked paths list to clipboard
var d = new ActionDescriptor();
d.putString(stringIDToTypeID('textData'), list);
executeAction(stringIDToTypeID('textToClipboard'), d, DialogModes.NO);

// Text file platform specific LF options
var os = $.os.toLowerCase().indexOf("mac") >= 0 ? "mac" : "windows";
if (os === "mac") {
    textFileLF = "Unix"; // Legacy = "Macintosh"
} else {
    textFileLF = "Windows";
}
// Create a desktop text file of the SO linked paths
var textFile = new File('~/Desktop' + '/' + 'Linked SO Paths.txt');
if (textFile.exists)
    textFile.remove();
    textFile.open('w');
    textFile.encoding = 'UTF-8';
    textFile.lineFeed = textFileLF;
    textFile.write(list);
    textFile.close();
    
    // Open the text file
    textFile.execute();

////// collect layers //////
function collectSmartObjectsLinks() {
    // get number of layers;
    var ref = new ActionReference();
    ref.putProperty(stringIDToTypeID('property'), stringIDToTypeID('numberOfLayers'));
    ref.putEnumerated(charIDToTypeID("Dcmn"), charIDToTypeID("Ordn"), charIDToTypeID("Trgt"));
    var applicationDesc = executeActionGet(ref);
    var theNumber = applicationDesc.getInteger(stringIDToTypeID("numberOfLayers"));
    // process the layers;
    var theLayers = new Array();
    for (var m = 0; m <= theNumber; m++) {
        try {
            var ref = new ActionReference();
            ref.putIndex(charIDToTypeID("Lyr "), m);
            var layerDesc = executeActionGet(ref);
            var layerSet = typeIDToStringID(layerDesc.getEnumerationValue(stringIDToTypeID("layerSection")));
            var isBackground = layerDesc.getBoolean(stringIDToTypeID("background"));
            var isSmartObject = layerDesc.hasKey(stringIDToTypeID("smartObject"));
            // collect smart object links;
            if (isSmartObject === true) {
                var theSO = layerDesc.getObjectValue(stringIDToTypeID("smartObject"));
                if (theSO.getBoolean(stringIDToTypeID("linked")) === true) {
                    var theLink = theSO.getPath(stringIDToTypeID("link"));
                    var theCheck = true;
                    for (var n = 0; n < theLayers.length; n++) {
                        if (theLayers[n] == String(theLink)) {
                            theCheck = false;
                        }
                    }
                    if (theCheck === true) {
                        theLayers.push(theLink.fsName);
                    }
                }
            }
        } catch (e) {}
    }
    return theLayers;
}

Votes

Translate

Translate

Report

Report
Adobe Community Professional ,
Jan 16, 2022 Jan 16, 2022

Copy link to clipboard

Copied

Now I am curious if and when further functionality will be asked for … 

Votes

Translate

Translate

Report

Report
Adobe Community Professional ,
Jan 18, 2022 Jan 18, 2022

Copy link to clipboard

Copied

I use linked layers in my work, so I decided to add scriptUI dialog. At the same time, I tried to solve all the problems that I face myself. 

Lost files are marked in red, files existing on the disk but located in a different directory are marked in yellow, files in the same directory as the active document are marked in green. The list supports selection of several lines, by double-clicking on a line, you can set the path to a specific file. When replacing links using the "relink" button, the search for suitable files is carried out taking into account subdirectories.

I tried to make the replacement of links as accurate as possible - the script tries to save both the original path (this is only possible on windows) and the original file extension, and only if nothing is found tries to work out other options. 

2022-02-21_09-45-15.png

 

#target photoshop
/*
// BEGIN__HARVEST_EXCEPTION_ZSTRING
<javascriptresource>
<name>Relink files</name>
<category>jazzy</category>
<enableinfo>true</enableinfo>
</javascriptresource>
// END__HARVEST_EXCEPTION_ZSTRING
*/
$.localize = true
//$.locale = 'ru'

const UUID = 'a3e4d053-135c-4225-b741-39bcb6656fd0',
    apl = new AM('application'),
    doc = new AM('document'),
    lr = new AM('layer'),
    fn = new CommonFunctions(),
    str = new Locale(),
    ver = '0.23';
var allowedExtensions = function (s) { var t = {}; for (var i = 0; i < s.length; i++) t[s[i]] = true; return t }(['PSD', 'PDD', 'PSDT', 'PSB', 'BMP', 'RLE', 'DIB', 'GIF', 'EPS', 'IFF', 'TDI', 'JPG', 'JPEG', 'JPE', 'JPF', 'JPX', 'JP2', 'J2C',
    'J2K', 'JPC', 'JPS', 'MPO', 'PCX', 'PDF', 'PDP', 'PXR', 'PNG', 'SCT', 'TGA', 'VDA', 'ICB', 'VST', 'TIFF', 'PBM', 'PGM', 'PPM', 'PNM', 'PFM', 'PAM',
    'DCM', 'DC3', 'DIC', 'TIF', 'CRW', 'NEF', 'RAF', 'ORF', 'MRW', 'MOS', 'SRF', 'PEF', 'DCR', 'CR2', 'DNG', 'ERF', 'X3F', 'RAW', 'ARW', 'CR3', 'KDC', '3FR',
    'MEF', 'MFW', 'NRW', 'RWL', 'RW2', 'SRW', 'GPR', 'IIQ']),
    cfg = (new AM()).getScriptSettings();
main();
function main() {
    try {
        var runMode = version.split('.')[0] < 21 ? false : true;
        if (!runMode) cfg.checkEmbedded = false;
        do {
            var currentSelection = fn.getSelectedLayersIds(),
                smartObjects = fn.buildSmartObjectsTree(),
                filesList = fn.buildFilesList(smartObjects);
            if (filesList.length) {
                if (fn.isDitry) app.refresh()
                var w = new mainWindow(filesList, runMode),
                    result = w.show();
                if (result == 1) {
                    activeDocument.suspendHistory(str.relink.toString(), 'fn.relink(filesList, smartObjects)')
                    if (currentSelection.length) doc.selectLayerByIDList(currentSelection)
                }
            } else {
                alert(str.errNoFiles, str.warning)
                result = 0
            }
            fn.isDitry = false
        } while (result == 3)
    } catch (e) {
        if (confirm(str.errGlobal, true, str.err)) fn.createReport(smartObjects, filesList, e);
    }
}
function AM(target) {
    var s2t = stringIDToTypeID,
        t2s = typeIDToStringID;
    target = target ? s2t(target) : null;
    this.getProperty = function (property, id, idxMode) {
        r = new ActionReference();
        if (property) {
            property = s2t(property);
            r.putProperty(s2t('property'), property);
        }
        id != undefined ? (idxMode ? r.putIndex(target, id) : r.putIdentifier(target, id)) :
            r.putEnumerated(target, s2t('ordinal'), s2t('targetEnum'));
        try { return property ? getDescValue(executeActionGet(r), property) : executeActionGet(r) } catch (e) { return null }
    }
    this.hasProperty = function (property, id, idxMode) {
        property = s2t(property);
        (r = new ActionReference()).putProperty(s2t('property'), property);
        id ? (idxMode ? r.putIndex(target, id) : r.putIdentifier(target, id))
            : r.putEnumerated(target, s2t('ordinal'), s2t('targetEnum'));
        try { return executeActionGet(r).hasKey(property) } catch (e) { return false }
    }
    this.descToObject = function (d, o) {
        if (d) {
            o = o ? o : {}
            for (var i = 0; i < d.count; i++) {
                var k = d.getKey(i)
                o[t2s(k)] = getDescValue(d, k)
            }
            return o
        }
    }
    this.relinkCurrentLayer = function (pth) {
        try {
            (d = new ActionDescriptor()).putPath(s2t('target'), pth);
            executeAction(s2t('placedLayerRelinkToFile'), d, DialogModes.NO);
            return true;
        } catch (e) { return false }
    }
    this.deleteCurrentHistoryState = function () {
        (r = new ActionReference()).putProperty(s2t('historyState'), s2t('currentHistoryState'));
        (d = new ActionDescriptor()).putReference(s2t('target'), r);
        try { executeAction(s2t('delete'), d, DialogModes.NO); } catch (e) { }
    }
    this.convertSmartObjectToLayers = function () {
        try { executeAction(s2t('placedLayerConvertToLayers'), undefined, DialogModes.NO); return true } catch (e) { return false }
    }
    this.selectLayerByIDList = function (IDList) {
        var ref = new ActionReference()
        for (var i = 0; i < IDList.length; i++) {
            ref.putIdentifier(s2t('layer'), IDList[i])
        }
        var desc = new ActionDescriptor()
        desc.putReference(s2t('target'), ref)
        desc.putBoolean(s2t('makeVisible'), false)
        executeAction(s2t('select'), desc, DialogModes.NO)
    }
    this.editSmartObject = function () {
        try {
            executeAction(s2t('placedLayerEditContents'), undefined, DialogModes.NO)
            return true
        } catch (e) { return false }
    }
    this.closeDocument = function (save) {
        save = save != true ? s2t('no') : s2t('yes');
        (d = new ActionDescriptor()).putEnumerated(s2t('saving'), s2t('yesNo'), save);
        executeAction(s2t('close'), d, DialogModes.NO)
    }
    this.applyLocking = function (desc, id, idxMode) {
        if (!desc) {
            desc = new ActionDescriptor();
            desc.putBoolean(s2t('protectNone'), true);
        }
        var r = new ActionReference();
        id ? (idxMode ? r.putIndex(target, id) : r.putIdentifier(target, id))
            : r.putEnumerated(target, s2t('ordinal'), s2t('targetEnum'));
        (d = new ActionDescriptor()).putReference(s2t('null'), r);
        d.putObject(s2t('layerLocking'), s2t('layerLocking'), desc);
        executeAction(s2t('applyLocking'), d, DialogModes.NO);
    }
    function getDescValue(d, p) {
        switch (d.getType(p)) {
            case DescValueType.OBJECTTYPE: return { type: t2s(d.getObjectType(p)), value: d.getObjectValue(p) };
            case DescValueType.LISTTYPE: return d.getList(p);
            case DescValueType.REFERENCETYPE: return d.getReference(p);
            case DescValueType.BOOLEANTYPE: return d.getBoolean(p);
            case DescValueType.STRINGTYPE: return d.getString(p);
            case DescValueType.INTEGERTYPE: return d.getInteger(p);
            case DescValueType.LARGEINTEGERTYPE: return d.getLargeInteger(p);
            case DescValueType.DOUBLETYPE: return d.getDouble(p);
            case DescValueType.ALIASTYPE: return d.getPath(p);
            case DescValueType.CLASSTYPE: return d.getClass(p);
            case DescValueType.UNITDOUBLE: return (d.getUnitDoubleValue(p));
            case DescValueType.ENUMERATEDTYPE: return { type: t2s(d.getEnumerationType(p)), value: t2s(d.getEnumerationValue(p)) };
            default: break;
        };
    }
    this.getScriptSettings = function () {
        var d = null;
        try { d = getCustomOptions(UUID) } catch (e) { }
        return d ? this.descToObject(d, new Config()) : new Config();
    }
    this.putScriptSettings = function (o) {
        var d = objToDesc(o)
        putCustomOptions(UUID, d)
        function objToDesc(o) {
            var d = new ActionDescriptor;
            for (var i = 0; i < o.reflect.properties.length; i++) {
                var k = o.reflect.properties[i].toString();
                if (k == '__proto__' || k == '__count__' || k == '__class__' || k == 'reflect') continue;
                var v = o[k];
                k = s2t(k);
                switch (typeof (v)) {
                    case 'boolean': d.putBoolean(k, v); break;
                    case 'number': d.putInteger(k, v); break;
                    case 'string': d.putString(k, v); break;
                }
            }
            return d;
        }
    }
}
function Config() {
    this.relinkMode = 0
    this.matchExtension = false
    this.collect = false
    this.subfolder = 'assets'
    this.groupByExtension = true
    this.move = false
    this.checkEmbedded = true
}
function CommonFunctions() {
    this.getSelectedLayersIds = function () {
        if (!apl.getProperty('numberOfDocuments')) return []
        var targetLayers = doc.hasProperty('targetLayersIDs') ? doc.getProperty('targetLayersIDs') : [],
            selection = [];
        if (targetLayers) {
            for (var i = 0; i < targetLayers.count; i++) {
                selection.push(targetLayers.getReference(i).getIdentifier(stringIDToTypeID('layerID')))
            }
        }
        return selection;
    }
    this.buildSmartObjectsTree = function () {
        if (!apl.getProperty('numberOfDocuments')) return null
        var smartObjects = getSmartObjectsList(),
            len = 0;
        if (cfg.checkEmbedded) {
            for (var a in smartObjects) if (!(lr.getProperty('smartObject', a).value.getBoolean(stringIDToTypeID('linked')))) len++
        }
        this.isDitry = Boolean(len)
        var progress = progressWindow(str.findEmbedded, '', len)
        if (len && cfg.checkEmbedded) progress.show()
        enumSmartObjects(smartObjects, len, progress)
        progress.close()
        return smartObjects.toSource() == '({})' ? null : smartObjects
    }
    this.buildFilesList = function (smartObjects) {
        var filesList = [];
        if (smartObjects != null) filesList = findEqualFiles(collectFiles(smartObjects));
        function collectFiles(so, filesList) {
            filesList = filesList ? filesList : []
            for (var a in so) {
                if (so[a].fileReference) filesList.push(describeFile(so[a], Number(a))) else collectFiles(so[a], filesList)
            }
            return filesList
        }
        function describeFile(o, id) {
            o.sameFolder = null
            o.missed = null
            o.relink = null

            o.relinked = null
            allowedExtensions[o.link.extension.toUpperCase()] = true
            return o
        }
        filesList.checkFiles = function () {
            for (var i = 0; i < this.length; i++) {
                with (this[i]) {
                    var cur = this[i].relink ? relink : link
                    if (cur instanceof File) {
                        sameFolder = documentFolder != null ? (decodeURI(cur).toUpperCase().indexOf(decodeURI(documentFolder).toUpperCase()) == 0 ? true : false) : null
                        missed = !cur.exists
                    } else {
                        sameFolder = false
                        missed = true
                    }
                }
            }
            return this
        }
        return filesList
    }
    this.splitFilename = function (f) {
        var s;
        if (f) {
            if (f instanceof File) { s = decodeURI(f.name) }
            else {
                s = f.filename ? f.filename : f
                f = {}
            }
            f.filename = s.substr(0, s.lastIndexOf('.'))
            f.extension = (s.substr(s.lastIndexOf('.') + 1, s.length))
        }
        return f
    }
    this.findLinks = function (parentFolder, files, selected) {
        var fileCache = {},
            toDo = {},
            len = selected ? selected.length : files.length;
        for (var i = 0; i < len; i++) {
            var index = selected ? (selected[i]).index : i
            toDo[index] = files[index]
        }
        if (len) {
            for (var a in toDo) {
                if (toDo[a].sameFolder) {
                    with (toDo[a]) {
                        relink = null;
                        var cur = link,//relink ? relink : link,
                            folders = parentFolder != null && cur instanceof File ? (decodeURI(cur.path).replace(new RegExp(decodeURI(parentFolder), 'i'), '').split('/')) : [];
                    }
                    var link = findSimilarFiles(cur.filename, cur.extension, folders, parentFolder);
                    if (link) {
                        files[a].relink = decodeURI(link).toUpperCase() == decodeURI(toDo[a].link).toUpperCase() ? null : link;
                        delete toDo[a]
                    }
                }
            }
            {
                var len = getObjectLength(toDo)
                if (len) {
                    var c = 1;
                    app.updateProgress(c++, len + 1)
                    var allFiles = enumAllFiles(parentFolder)
                    for (var a in toDo) {
                        var cur = toDo[a]
                        app.changeProgressText(str.searchFile + cur.fileReference)
                        app.updateProgress(c++, len + 1)
                        var hash = stringIDToTypeID(cur.link.filename)
                        if (allFiles[hash]) {
                            var cur = allFiles[hash],
                                link = null;
                            for (var i = 0; i < cur.length; i++)
                                if (files[a].link.extension.toUpperCase() == cur[i].extension.toUpperCase()) {
                                    link = cur[i]
                                    break;
                                }
                            if (!link && !cfg.matchExtension) link = cur[0]
                            if (link) {
                                files[a].relink = decodeURI(link).toUpperCase() == decodeURI(toDo[a].link).toUpperCase() ? null : link
                                delete toDo[a]
                            }
                        }
                    }
                }
            }
        }
        function findSimilarFiles(fle, ext, dir, parent) {
            var subPath = '',
                len = dir.length;
            for (var i = 0; i < len; i++) {
                if (dir[i] == '') continue;
                subPath = '/' + dir[i] + '/' + subPath
                var f = checkFile(fle, ext, parent + subPath)
                if (f) return f
            }
            var f = checkFile(fle, ext, parent)
            return f ? f : null

            function checkFile(fle, ext, pth) {
                var f = new File(pth + '/' + fle + '.' + ext)
                if (f.exists) {
                    return splitFilename(f)
                }
                else if (!cfg.matchExtension) {
                    fle = fle.toUpperCase()
                    var p = new Folder(pth)
                    if (p.exists) {
                        if (!fileCache[pth]) fileCache[pth] = p.getFiles()
                        var cur = fileCache[pth];
                        for (var i = 0; i < cur.length; i++) {
                            if (cur[i] instanceof File) {
                                var f = splitFilename(cur[i]);
                                if (f.filename.toUpperCase() == fle && allowedExtensions[f.extension.toUpperCase()]) return f
                            }
                        }
                    }
                }
                return null;
            }
        }
        function enumAllFiles(parent, listOfFiles) {
            app.changeProgressText(str.buildFileList + decodeURI(Folder(parent).name))
            listOfFiles = listOfFiles ? listOfFiles : {};
            var files = Folder(parent).getFiles();
            for (var i = 0; i < files.length; i++) {
                app.updateProgress(i + 1, files.length)
                var cur = files[i];
                if (cur instanceof File) {
                    if (!cur.hidden) {
                        cur = splitFilename(cur)
                        if (allowedExtensions[cur.extension.toUpperCase()]) {
                            var hash = stringIDToTypeID(cur.filename)
                            if (!listOfFiles[hash]) {
                                listOfFiles[hash] = [cur]
                            } else {
                                listOfFiles[hash].push(cur)
                            }
                        }
                    }
                } else if (cur instanceof Folder) {
                    enumAllFiles(cur, listOfFiles, listOfFiles)
                }
            }
            return listOfFiles
        }
    }
    this.collectAssets = function (parentFolder, files) {
        if (!parentFolder) return
        var subfolder = '/' + cfg.subfolder.replace(/[~#%&*{}:<>?|\'-]/g, '_') + '/'
        if (!subfolder.replace(/[\/ .]/g, '').length) subfolder = '/'
        var progress = new progressWindow(str.copyAssets, '', files.length);
        progress.show()
        for (var i = 0; i < files.length; i++) {
            progress.updateProgress(files[i].fileReference)
            if (files[i].missed) continue;
            var cur = files[i].relink ? files[i].relink : files[i].link,
                target = splitFilename(new File(parentFolder + subfolder + (cfg.groupByExtension ? '/' + cur.extension.toUpperCase() + '/' : '') + cur.filename + '.' + cur.extension));
            if (target.exists && (decodeURI(target).toUpperCase() == decodeURI(cur).toUpperCase())) {
                continue;
            } else {
                target = createUniqueFileName(target)
            }
            if (!(Folder(target.path).exists)) Folder(target.path).create()

            if (cfg.collect && cfg.move) {
                if (documentFolder != null && decodeURI(cur).toUpperCase().indexOf(decodeURI(documentFolder).toUpperCase()) == 0) {
                    if (!target.exists) {
                        if (cur.rename(target)) {
                            files[i].relink = target
                        } else {
                            if (cur.copy(target)) {
                                cur.remove()
                                files[i].relink = target
                            }
                        }
                        var f = Folder(cur.path)
                        if (f.getFiles().length == 0) {
                            f.remove()
                            do {
                                f = Folder(f.path)
                                if (f.getFiles().length) break;
                            } while (f.remove())
                        }
                    }
                }
            }
            if (files[i].relink != target && cur.copy(target)) files[i].relink = target

        }
        progress.close()
        function createUniqueFileName(target) {
            var parent = decodeURI(target.path),
                f = target,
                c = 1;
            while (f.exists) {
                f = splitFilename(new File(parent + '/' + target.filename + ' (' + c + ').' + target.extension))
                c++;
            }
            return f
        }
    }
    this.relink = function (files, smartObjects) {
        var toRelink = [],
            linkedObjects = {},
            embeddedObjects = {},
            locked = unlockLayers();
        for (var i = 0; i < files.length; i++) if (files[i].relink) toRelink.push(files[i])
        for (var a in smartObjects) {
            if (smartObjects[a].link) { linkedObjects[a] = smartObjects[a] }
            else { embeddedObjects[a] = flattenObject(smartObjects[a]) }
        }
        if (toRelink.length) {
            var progress = new progressWindow(str.relink, '', toRelink.length)
            progress.show()
            for (var a in linkedObjects) {
                for (var i = 0; i < toRelink.length; i++) {
                    if (isEqualObject(linkedObjects[a].link, toRelink[i].link)) {
                        lr.selectLayerByIDList([Number(a)])
                        progress.updateProgress(lr.getProperty('name', Number(a)))
                        if (lr.relinkCurrentLayer(toRelink[i].relink)) toRelink[i].relinked = true;
                        break;
                    }
                }
            }
            for (var a in embeddedObjects) {
                var toDelete = [],
                    found = false;
                for (var i = 0; i < toRelink.length; i++) {
                    for (var x = 0; x < embeddedObjects[a].length; x++) {
                        if (isEqualObject(embeddedObjects[a][x].link, toRelink[i].link)) {
                            found = true;
                            break;
                        }
                        if (found) break;
                    }
                }
                if (found) {
                    for (var i = 0; i < embeddedObjects[a].length; i++) {
                        if (embeddedObjects[a][i].link instanceof File && embeddedObjects[a][i].link.exists) continue;
                        var f = createDumbFile(documentFolder, embeddedObjects[a][i].fileReference)
                        if (f) toDelete.push(f)
                    }
                    lr.selectLayerByIDList([Number(a)])
                    progress.updateProgress(lr.getProperty('name', Number(a)))
                    if (lr.editSmartObject()) {
                        relinkEmbedded(toRelink)
                        doc.closeDocument(true)
                    }
                    for (var i = 0; i < toDelete.length; i++) {
                        toDelete[i].remove()
                    }
                }
            }
            lockLayers(locked)

        }
        function relinkEmbedded(files) {
            var len = doc.getProperty('numberOfLayers'),
                locked = unlockLayers();
            if (len) {
                for (var i = len; i >= 1; i--) {
                    if (lr.hasProperty('smartObject', i, true)) {
                        var id = lr.getProperty('layerID', i, true),
                            smartObject = lr.descToObject(lr.getProperty('smartObject', i, true).value);
                        if (smartObject.linked) {
                            for (var x = 0; x < files.length; x++) {
                                if (isEqualObject(splitFilename(smartObject.link), files[x].link)) {
                                    doc.selectLayerByIDList([id])
                                    if (lr.relinkCurrentLayer(files[x].relink)) files[x].relinked = true
                                    break;
                                }
                            }
                        } else {
                            doc.selectLayerByIDList([id])
                            if (doc.convertSmartObjectToLayers()) {
                                var cur = flattenObject(enumSmartObjects(getSmartObjectsList(lr.getProperty('itemIndex'), true)))
                                doc.deleteCurrentHistoryState()
                                var found = false;
                                do {
                                    var f = splitFilename((cur.shift()).link)
                                    for (var x = 0; x < files.length; x++) {
                                        if (isEqualObject(f, files[x].link)) {
                                            found = true
                                            break;
                                        }
                                    }
                                    if (found) break;
                                } while (cur.length)
                                if (true) {
                                    doc.selectLayerByIDList([id])
                                    if (lr.editSmartObject()) {
                                        relinkEmbedded(files)
                                        doc.closeDocument(true)
                                    }
                                }
                            }
                        }
                    }
                }
            }
            lockLayers(locked)
        }
        function flattenObject(o, output) {
            output = output ? output : [];
            for (var a in o) {
                if (o[a].link) {
                    output.push(o[a])
                } else {
                    flattenObject(o[a], output)
                }
            }
            return output
        }
        function isEqualObject(a, b) {
            for (var i in a) {
                if (stringIDToTypeID(a[i]) != stringIDToTypeID(b[i])) return false
            }
            return true
        }
    }
    this.createReport = function (smartObjects, filesList, err) {
        var report = [],
            s2t = stringIDToTypeID;
        report.push(new Date + '\nSend this report to jazz-y@ya.ru')
        if (err) report.push('Error:\n' + err.toSource())
        if (smartObjects) report.push('Smart objects:\n' + smartObjects.toSource())
        if (filesList) report.push('Files:\n' + filesList.toSource())
        report.push('Application:')
        try {
            (d = new ActionDescriptor()).putObject(s2t('object'), s2t('object'), apl.getProperty());
            report.push(executeAction(s2t('convertJSONdescriptor'), d).getString(s2t('json')))
        } catch (e) { report.push(e.toSource()) }
        report.push('Documents:')
        if (len = apl.getProperty('numberOfDocuments')) {
            try {
                for (var i = 1; i <= len; i++) {
                    (d = new ActionDescriptor()).putObject(s2t('object'), s2t('object'), doc.getProperty(null, i, true));
                    report.push(executeAction(s2t('convertJSONdescriptor'), d).getString(s2t('json')))
                }
            } catch (e) { report.push(e.toSource()) }
        }
        report.push('Layers of active document:')
        if (len = doc.getProperty('numberOfLayers')) {
            try {
                for (var i = 1; i <= len; i++) {
                    (d = new ActionDescriptor()).putObject(s2t('object'), s2t('object'), lr.getProperty(null, i, true));
                    report.push(executeAction(s2t('convertJSONdescriptor'), d).getString(s2t('json')))
                }
            } catch (e) { report.push(e.toSource()) }
        }
        var f = new File('~/desktop/' + str.scriptName + ' errorLog.txt');
        if (f.saveDlg(str.saveLog)) {
            f.open('w')
            f.write(report.join('\n\n'))
            f.close()
        }
    }
    function unlockLayers() {
        var len = doc.getProperty('numberOfLayers'),
            layers = {};
        if (len) {
            for (var i = len; i >= 1; i--) {
                if (lr.getProperty('layerSection', i, true).value == 'layerSectionEnd') continue;
                var locking = lr.getProperty('layerLocking', i, true).value
                if (checkLocking(locking)) {
                    layers[lr.getProperty('layerID', i, true)] = lr.getProperty('layerLocking', i, true).value;
                    lr.applyLocking(undefined, i, true)
                }
            }
        }
        return layers;
    }
    function lockLayers(o) {
        for (var a in o) {
            lr.applyLocking(o[a], a)
        }
    }
    function checkLocking(d) {
        var o = lr.descToObject(d);
        for (var a in o) if (o[a]) return true
        return false
    }
    function enumSmartObjects(ids, progressLength, w) {
        for (var a in ids) {
            var cur = doc.descToObject(lr.getProperty('smartObject', a).value)
            if (cur.linked && !checkLocking(lr.getProperty('layerLocking', a).value)) {
                if (cur.link && cur.link.type && cur.link.type == 'ccLibrariesElement') {
                    delete ids[a];
                    continue;
                }
                ids[a] = describeSmartObject(cur)
            } else {
                if (cfg.checkEmbedded) {
                    if (progressLength) w.updateProgress(lr.getProperty('name', a))
                    doc.selectLayerByIDList([a])
                    if (doc.convertSmartObjectToLayers()) {
                        ids[a] = enumSmartObjects(getSmartObjectsList(lr.getProperty('itemIndex'), true))
                        doc.deleteCurrentHistoryState()
                    }
                }
                if (ids[a] == null || ids[a].toSource() == '({})') delete ids[a]
            }
        }
        return ids
        function describeSmartObject(o) {
            return {
                link: splitFilename(!o.link || o.link == '' ? o.fileReference : File(o.link)),
                fileReference: o.fileReference,
            }
        }
    }
    function getSmartObjectsList(idx, mode) {
        var indexFrom = idx ? (doc.getProperty('hasBackgroundLayer') ? --idx : idx) : doc.getProperty('numberOfLayers'),
            indexTo = doc.getProperty('hasBackgroundLayer') ? 0 : 1;
        return buildSmartObjectsList(enumLayers(indexFrom, indexTo, mode))
        function enumLayers(from, to, currentSection, parentItem, group) {
            parentItem = parentItem ? parentItem : [];
            var isDirty = false;
            for (var i = from; i >= to; i--) {
                var layerSection = lr.getProperty('layerSection', i, true).value;
                if (layerSection == 'layerSectionStart') {
                    i = enumLayers(i - 1, to, undefined, [], parentItem)
                    isDirty = true;
                    if (currentSection) return parentItem[0]
                    continue;
                }
                if (currentSection && isDirty) return []
                var properties = {};
                properties.layerKind = lr.getProperty('layerKind', i, true)
                properties.id = lr.getProperty('layerID', i, true)
                properties.smartObject = lr.hasProperty('smartObject', i, true)
                if (layerSection == 'layerSectionEnd') {
                    for (o in properties) { parentItem[o] = properties[o] }
                    group.push(parentItem);
                    return i;
                } else {
                    parentItem.push(properties)
                    if (currentSection && !isDirty) return parentItem
                }
            }
            return parentItem
        }
        function buildSmartObjectsList(layers, list) {
            if (!list) list = {}
            for (var i = 0; i < layers.length; i++) {
                var cur = layers[i]
                if (cur.length) {
                    buildSmartObjectsList(cur, list)
                } else {
                    if (cur.smartObject && cur.layerKind == 5) list[cur.id] = null
                }
            }
            return list
        }
    }
    function getObjectLength(o) {
        var i = 0
        for (var a in o) i++
        return i
    }
    function findEqualFiles(files, relinkMode) {
        var o = [], f = files.slice();
        do {
            var cur = f.shift()
            if (cur) {
                for (var i = 0; i < f.length; i++) {
                    if (!relinkMode) {
                        if (f[i] instanceof File) {
                            if (decodeURI(cur.link).toUpperCase() == decodeURI(f[i].link).toUpperCase()) f[i] = null
                        } else {
                            if (f[i] && (cur.link.filename + cur.link.extension).toUpperCase() == (f[i].link.filename + f[i].link.extension).toUpperCase()) f[i] = null
                        }
                    }
                    else {
                        if (f[i] && decodeURI(cur.relink).toUpperCase() == decodeURI(f[i].relink).toUpperCase()) f[i] = null
                    }
                }
                o.push(cur)
            }
        } while (f.length)
        return o
    }
    function createDumbFile(parent, reference) {
        var f = new File(parent + '/' + reference);
        if (f.exists) return null
        f.open('w')
        f.close()
        return f
    }
    function progressWindow(title, message, max) {
        var w = new Window('palette', title),
            bar = w.add('progressbar', undefined, 0, max),
            stProgress = w.add('statictext', undefined, message);
        stProgress.preferredSize = [350, 20]
        stProgress.alignment = 'left'
        bar.preferredSize = [350, 20]
        w.updateProgress = function (message) {
            bar.value++;
            if (message) stProgress.text = bar.value + '/' + max + ': ' + message
            w.update();
        }
        return w;
    }
    splitFilename = this.splitFilename;
    documentFolder = this.documentFolder = doc.hasProperty('fileReference') ? Folder(File(doc.getProperty('fileReference').fsName).path) : null;
    isDitry = this.isDitry = false;
    targetFolder = this.targetFolder = null;
}
function Locale() {
    this.ico = {
        red: "\u0089PNG\r\n\x1A\n\x00\x00\x00\rIHDR\x00\x00\x00\n\x00\x00\x00\n\b\x06\x00\x00\x00\u008D2\u00CF\u00BD\x00\x00\x00;IDAT\x18\u0095c\u00FCci\u00F3\u009F\u0081\b\u00C0\x02R\u00C2((\u0084W\u00E5\u00FF\u00F7\u00EF \n\u00C1\u0080\u0087\x17\u00BB\u00AA/\u009F\u00C1\x14\x131\u00D6\x0E\x15\u0085\b_C}\u0087W!(\u009C\u00F0\x02\x06\x06\x06\x00\x18\u00EF\fO\u0083\b\u00CC\u00FD\x00\x00\x00\x00IEND\u00AEB`\u0082",
        green: "\u0089PNG\r\n\x1A\n\x00\x00\x00\rIHDR\x00\x00\x00\n\x00\x00\x00\n\b\x06\x00\x00\x00\u008D2\u00CF\u00BD\x00\x00\x00=IDAT\x18\u0095c\u00F4\u00DEo\u00F8\u009F\u0081\b\u00C0\x02R\"\u00C6\u00C9\u008FW\u00E5\u00AB\u00EF\x1F!\nA@\u0090\u009D\x1B\u00AB\u00A2\u00F7?\u00BF\u0082i&b\u00AC\x1D*\n\u00E1\u00BE\u0086\u00F9\x0E\u00AFBP8\u00E1\x05\f\f\f\x000\x1F\x0E\x05z4V\u0094\x00\x00\x00\x00IEND\u00AEB`\u0082",
        yellow: "\u0089PNG\r\n\x1A\n\x00\x00\x00\rIHDR\x00\x00\x00\n\x00\x00\x00\n\b\x06\x00\x00\x00\u008D2\u00CF\u00BD\x00\x00\x00;IDAT\x18\u0095c\u00FCp@\u00ED?\x03\x11\u0080\x05\u00A4\u0084\u0089\u0093\x1B\u00AF\u00CA\x7F\u00DF\u00BFB\x14\u0082\x15\u00B3s`W\u00F4\u00F3\x07D\u009E\x18k\u0087\u008AB\u00B8\u00AFa\u00BE\u00C3\u00AB\x10\x14Nx\x01\x03\x03\x03\x00+\u00AA\x0E\u00CA*\u0090\u00C3\u00A2\x00\x00\x00\x00IEND\u00AEB`\u0082",
        all: "\u0089PNG\r\n\x1A\n\x00\x00\x00\rIHDR\x00\x00\x00\n\x00\x00\x00\n\b\x06\x00\x00\x00\u008D2\u00CF\u00BD\x00\x00\x00\u00B5IDAT\x18\u0095\u008D\u0090K\n\u00840\x10D+C\u00FC\u00E5\x06\u00D9\u00B8\u00F7\n\"nt\u00E3\u0089\u00C5\u0095\u00E0!\u00B2\u00CC-\x14\u00F1\u0097\u00A1{\u00B0a\x10\u0086)(:\u00A4_WB\u00AB\u00BE\u00EF\x03\u00FE\u0090&\u00A4\u00AEk!\u0097e\u0081R\nY\u0096\u00C9\u00DD4M\x1F\u0090\u0094\u00A6)\u009Cs\u00F0\u00DE#I\x12\u00E4y\u008E\u00A2(x\x10w\"i]W\u0086\u00BA\u00AE\u00C3\u00BE\u00EF\x18\u00C7\x11\u00D6Z\u00C4q\u00CC\u00FD\u00D7\r\x1E\u00C7\u00C15\u008A\"N$x\u009Egy^\x12\u00E9_\u00D7ua\x18\x06i\u009E\u00E7)gI\f!0\u00DC\u00B6-\u009A\u00A6\u0091\u00E1G\u00A2\u00D6\u009A\u00BDm\x1B\x03dc\u00CC\x13\u00A4\u00C4\u00B2,\u00B9\u0092\u00AB\u00AA\u00E2M|\u0081\u00B4\u00A7\u009F\x02\u00F0\x06\x05\"C\u00AEZ\f\u00AC\x18\x00\x00\x00\x00IEND\u00AEB`\u0082"
    }
    this.scriptName = 'Relink layers'
    this.errNoFiles = { ru: 'Связананные слои не найдены!', en: 'Linked layers not found!' }
    this.findEmbedded = { ru: 'Поиск связанных файлов в смарт-объектах', en: 'Find linked files in smart objects' }
    this.searchFile = { ru: 'Поиск файла: ', en: 'Search file: ' }
    this.buildFileList = { ru: 'Обработка каталога: ', en: 'Build list of files: ' }
    this.copyAssets = { ru: 'Сборка ресурсов', en: 'Collect assets' }
    this.relink = { ru: 'Связать заново', en: 'Relink layers' }
    this.linkedFiles = { ru: 'Связанные файлы', en: 'Linked files' }
    this.findInEmbedded = { ru: 'поиск связанных файлов в смарт-объектах', en: 'find links in smart objects' }
    this.relinkAll = { ru: 'Связать заново', en: 'Relink all' }
    this.extension = { ru: 'учитывать расширение', en: 'match file extension' }
    this.subfolder = { ru: 'собрать ресурсы в папке:', en: 'collect assets in subfolder:' }
    this.groupByExt = { ru: 'группировать по типу', en: 'group by extension' }
    this.move = { ru: 'перемещать', en: 'move files' }
    this.cancel = { ru: 'Отмена', en: 'Cancel' }
    this.currentPath = { ru: 'папка открытого документа', en: 'current document path' }
    this.newPath = { ru: 'новая папка', en: 'new path' }
    this.relinkSelected = { ru: 'Связать выбранные', en: 'Relink selected' }
    this.replaceFor = { ru: 'Заменить связь для ', en: 'Replace link for ' }
    this.strRelinkErr1 = { ru: 'Замена связи не может быть выполнена!', en: 'Relink operation cannot be performed!' }
    this.strRelinkErr2 = { ru: ' тип файлов не поддерживается!', en: ' files does not supported!' }
    this.strRelinkErr3 = { ru: '- открытый документ не сохранен!', en: '- active document has no path!' }
    this.err = { ru: 'Ошибка', en: 'Error' }
    this.warning = { ru: 'Предупреждение', en: 'Warning' }
    this.errGlobal = { ru: 'Произошла ошибка во время выполнения скрипта. Создать файл отчета?', en: 'An error occurred while executing the script. Create a report file? ' }
    this.saveLog = { ru: 'Файл отчета', en: 'Report file' }
    this.select = { ru: 'Быстрый выбор:', en: 'Quick select:' }
    this.target = { ru: 'Путь назначения:', en: 'Target path:' }
}
function mainWindow(fileList, runMode) {
    var w = new Window("dialog {text: '" + str.linkedFiles + " " + ver + "'}"),
        gSelect = w.add("group{alignChildren: ['left','center'], alignment: ['fill','top']}"),
        stSelect = gSelect.add("statictext{text:'" + str.select + "'}"),
        iAll = gSelect.add("iconbutton", undefined, str.ico.all, { style: "toolbutton" }),
        iGreen = gSelect.add("iconbutton", undefined, str.ico.green, { style: "toolbutton" }),
        iYellow = gSelect.add("iconbutton", undefined, str.ico.yellow, { style: "toolbutton" }),
        iRed = gSelect.add("iconbutton", undefined, str.ico.red, { style: "toolbutton" }),
        l = w.add("listbox", [0, 0, 600, 400], undefined, { multiselect: true }),
        gEmbedded = w.add("group{alignChildren: ['left','center'], alignment: ['fill','top']}"),
        chEmbedded = gEmbedded.add("checkbox{text: '" + str.findInEmbedded + "', preferredSize: [70, -1]}"),
        pnRelink = w.add("panel{text: '" + str.linkedFiles + "', alignChildren: ['left','center'], alignment: ['fill','top'], margins:[10,20,10,10]}"),
        gRelink = pnRelink.add("group{alignChildren: ['left','center'], alignment: ['fill','top']}"),
        rl = gRelink.add("button{text: '" + str.relinkAll + "', preferredSize: [170, -1]}"),
        dl = gRelink.add("dropdownlist", undefined, undefined, { items: [str.currentPath, str.newPath] }),
        chMatch = gRelink.add("checkbox{text: '" + str.extension + "', preferredSize: [120, -1]}"),
        gPath = pnRelink.add("group{alignChildren: ['left','center'], alignment: ['fill','top']}"),
        stPathLabel = gPath.add("statictext{text:'" + str.target + "'}"),
        stPath = gPath.add("statictext{preferredSize: [450, -1]}"),
        gCollect = w.add("group{alignChildren: ['left','center'], alignment: ['fill','top']}"),
        chCollect = gCollect.add("checkbox{text: '" + str.subfolder + "', preferredSize: [170, -1]}"),
        gSubCollect = gCollect.add("group{alignChildren: ['left','center'], alignment: ['fill','top']}"),
        et = gSubCollect.add("edittext{preferredSize: [100, -1]}"),
        chGroup = gSubCollect.add("checkbox{text: '" + str.groupByExt + "', preferredSize: [120, -1]}"),
        chMove = gSubCollect.add("checkbox{text: '" + str.move + "', preferredSize: [70, -1]}"),
        gButtons = w.add("group"),
        bnOk = gButtons.add("button {text:'Ok'}", undefined, undefined, { name: "ok" }),
        bnCancel = gButtons.add("button {text:'" + str.cancel + "'}", undefined, undefined, { name: "cancel" });
    l.graphics.font = "dialog:12";
    l.fillLinksList = function (items) {
        var currentSelection = []
        if (this.selection) for (var i = 0; i < this.selection.length; i++) currentSelection.push((this.selection[i]).index)
        this.removeAll()
        for (var i = 0; i < items.length; i++) {
            with (items[i]) {
                var cur = relink ? relink : link
                this.add('item', cur instanceof File ?
                    ((relink ? ' ✎ ' : (missed ? ' :cross_mark: ' : ' ✔ ')) + (relink ? (relink).fsName : link.fsName)) :
                    (' :cross_mark: ' + items[i].fileReference));
                this.items[i].image = items[i].missed ? str.ico.red : (items[i].sameFolder ? str.ico.green : str.ico.yellow)
            }
        }
        if (currentSelection.length) this.selection = currentSelection
    }
    l.onClick = function () {
        rl.text = l.selection != null ? str.relinkSelected + (l.selection.length > 1 ? ' (' + l.selection.length + ')' : '') : str.relinkAll
    }
    l.onDoubleClick = function () {
        if (l.selection != null) {
            if ((l.selection[0]).index >= 0) {
                with (fileList[(l.selection[0]).index]) {
                    var cur = link instanceof File ? link : new File,
                        f = fn.splitFilename(cur.openDlg(str.replaceFor + (link instanceof File ? decodeURI(link.name) : fileReference), '', false));
                    if (f && f.exists) {
                        if (allowedExtensions[f.extension.toUpperCase()]) {
                            relink = decodeURI(link).toUpperCase() == decodeURI(f).toUpperCase() ? null : f;
                            l.fillLinksList(fileList.checkFiles())
                        } else { alert(str.strRelinkErr1 + '\n\n*.' + f.extension + str.strRelinkErr2, str.err, 1) }
                    }
                }
            }
        }
    }
    rl.onClick = function (mode) {
        switch (dl.selection.index) {
            case 0:
                if (fn.documentFolder) {
                    fn.targetFolder = fn.documentFolder;
                } else { alert(str.strRelinkErr1 + '\n\n' + str.strRelinkErr3, str.err, 1) }
                break;
            case 1:
                var p = (new Folder(fn.documentFolder ? fn.documentFolder : null)).selectDlg()
                if (p) { fn.targetFolder = p } else { mode = true }
                break;
        }

        bnOk.enabled = fn.targetFolder
        stPath.text = fn.targetFolder ? fn.targetFolder.fsName : ''
        if (!mode) {
            app.doForcedProgress('', 'fn.findLinks(fn.targetFolder, fileList, l.selection)')
            l.fillLinksList(fileList.checkFiles())
        }
    }
    chCollect.onClick = function () { gSubCollect.enabled = cfg.collect = this.value }
    chGroup.onClick = function () { cfg.groupByExtension = this.value }
    chMove.onClick = function () { cfg.move = this.value }
    bnOk.onClick = function () {
        if (fn.targetFolder == null) { rl.onClick(true) }
        w.close(1);
        (new AM()).putScriptSettings(cfg);
        if (fn.targetFolder && cfg.collect) fn.collectAssets(fn.targetFolder, fileList)
    }
    chEmbedded.onClick = function () {
        cfg.checkEmbedded = this.value
        w.close(3)
    }
    et.onChange = function () {
        cfg.subfolder = this.text
    }
    bnCancel.onClick = function () { fileList = []; w.close(2) }
    chMatch.onClick = function () { cfg.matchExtension = this.value }
    dl.onChange = function () {
        cfg.relinkMode = this.selection.index
        bnOk.enabled = fn.targetFolder = cfg.relinkMode ? null : fn.documentFolder
        stPath.text = fn.targetFolder ? fn.targetFolder.fsName : ''
    }
    iAll.onClick = function () { qickSelect(0) }
    iGreen.onClick = function () { qickSelect(1) }
    iYellow.onClick = function () { qickSelect(2) }
    iRed.onClick = function () { qickSelect(3) }
    w.onShow = function () {
        if (!fn.documentFolder) { dl.items[0].enabled = false; cfg.relinkMode = 1 }
        dl.selection = cfg.relinkMode ? 1 : 0
        chMatch.value = cfg.matchExtension
        gSubCollect.enabled = chCollect.value = cfg.collect
        et.text = cfg.subfolder
        chGroup.value = cfg.groupByExtension
        chMove.value = cfg.move
        chEmbedded.value = cfg.checkEmbedded
        dl.size.width = w.size.width - rl.size.width - chMatch.size.width - 90
        et.size.width = w.size.width - chGroup.size.width - chCollect.size.width - chMove.size.width - 80
        chEmbedded.enabled = runMode
        stPath.text = fn.targetFolder ? fn.targetFolder.fsName : ''
        w.layout.layout(true)
        l.fillLinksList(fileList.checkFiles())
    }
    function qickSelect(mode) {
        var selection = [],
            len = fileList.length;
        l.selection = selection;
        switch (mode) {
            case 1: for (var i = 0; i < len; i++) if (fileList[i].sameFolder && !fileList[i].missed) selection.push(i); break;
            case 2: for (var i = 0; i < len; i++) if (!fileList[i].sameFolder && !fileList[i].missed) selection.push(i); break;
            case 3: for (var i = 0; i < len; i++) if (fileList[i].missed) selection.push(i); break;
            default: for (var i = 0; i < len; i++) selection.push(i);
        }
        l.selection = selection
        l.onClick()
    }
    return w
}

 

Votes

Translate

Translate

Report

Report
Community Beginner ,
Jan 20, 2022 Jan 20, 2022

Copy link to clipboard

Copied

Thank you for all your help! I don't know how to use scripts, I think I'll watch a tutorial about it. Im working on a Mac,  So I`m going to test Stephen's script. :).   Anyway I hope an official window for doing that is add on a future phoshop version!

Votes

Translate

Translate

Report

Report
Adobe Community Professional ,
Jan 21, 2022 Jan 21, 2022

Copy link to clipboard

Copied

Save the code in a txt-file, change the extension from »txt« to »jsx« and put the File in the Presets/Sripts-Folder in the application Folder. 

Then (or at least after restarting Photoshop) the Script should be available under File > Scripts. 

Votes

Translate

Translate

Report

Report
Adobe Community Professional ,
Jan 21, 2022 Jan 21, 2022

Copy link to clipboard

Copied

I posted the link earlier in the topic directly under some script code, however,  more info here on saving and running scripts:

 

https://prepression.blogspot.com/2017/11/downloading-and-installing-adobe-scripts.html

 

Please do provide feedback on my script and on the solution provided by jazz-y.

Votes

Translate

Translate

Report

Report