• Global community
    • Language:
      • Deutsch
      • English
      • Español
      • Français
      • Português
  • 日本語コミュニティ
    Dedicated community for Japanese speakers
  • 한국 커뮤니티
    Dedicated community for Korean speakers
Exit
1

Best way to save user prefs in UXP plugin for Photoshop?

Explorer ,
Dec 29, 2020 Dec 29, 2020

Copy link to clipboard

Copied

I'm writing a UXP plugin for Photoshop and I'm trying to find the best way to save user preferences and allow them to create presets for the plugin via the available API.  If I was writing a "normal" application the data would be saved to a local database or flat file...but I don't see anything exposed in the API to allow for this.  

 

Honestly, the documentation on this API set is HORRIBLY confusing and incomplete:  some stuff is supported, other stuff isn't...but there's a workaround, and everything else I have no freaking clue and it's a matter of trying different stuff until I get it working.  Some interfaces are documented that they exist, but not what the property/accessor is actually called in the API.  I mean, writing these plugins shouldn't be this much guesswork.

 

<rantConcluded/>

 

I'm starting to see the light at the end of the tunnel with this plugin but I'm still not sure how to give users the ability to save their preferences and create presets with the settings in my control so they can work faster in the future.  Is there a good way of doing this?  Maybe some documentation somewhere that I haven't stumbled upon yet?  My preference would be to save/load a JSON document to the user's drive, if that's supported.

 

Many thanks in advance!  This platform look like it is going to be SUPER cool...one day, when it's finally finished and all growed-up!

TOPICS
Actions and scripting , SDK

Views

1.8K

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 1 Correct answer

Explorer , Jan 09, 2021 Jan 09, 2021

Okay, so unfortunately your solution doesn't work in UXP.  The objects/classes aren't recognized.  BUT I did find a REALLY easy way to do it that does work.

 

My presets are saved as a JSON object in my code and I can save them to the local storage with: window.localStorage.setItem and then retrieve them with: window.localStorage.getItem.  The only gotcha is that you have stringify/parse the JSON object when you do it.

 

You can do a simple test of making this work with the following code:

window.loc
...

Votes

Translate

Translate
Adobe
LEGEND ,
Dec 29, 2020 Dec 29, 2020

Copy link to clipboard

Copied

Votes

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 ,
Dec 30, 2020 Dec 30, 2020

Copy link to clipboard

Copied

I've been avoiding working with UXP for the reasons you stated. For the old jsx u, I have been using XML files to save preferences. I created a recursive script that reads all the control values of the UI, so they can be saved and reapplied. The tick is to give each control a name, which is the same as the variable. Don't know if it will still work with UXP.

Votes

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
LEGEND ,
Dec 30, 2020 Dec 30, 2020

Copy link to clipboard

Copied

Votes

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
Explorer ,
Dec 30, 2020 Dec 30, 2020

Copy link to clipboard

Copied

I'm finding that UXP is something that can only be partially implemented in my plugin so my panel is using a blend of UXP with other React components and structures.  It might be possible to massage how you are saving your XML files into what I'm doing.  If you can share how you are making it work in the legacy code, I can let you know if there's a way to port it over to your UXP project when you're ready to make that jump.  Deal?

Votes

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
Explorer ,
Dec 30, 2020 Dec 30, 2020

Copy link to clipboard

Copied

I actually just ran across this way of saving user custom options using app.getCustomOptions and app.putCustomOptions, which I know for sure will work in the model that my plugin uses.  I'll need to look closer at it to see how to create multiple saved versions of the app settings, but this is definitely a start!

 

https://community.adobe.com/t5/photoshop/photoshop-ui-save-user-input-options/m-p/11114193?page=1

 

Thought I would share with you since you were looking for a similar solution.

Votes

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 ,
Dec 30, 2020 Dec 30, 2020

Copy link to clipboard

Copied

Here's a short example of I save my presets. The nice thing about using a recursive function to capture the values, is that you can change the UI and not have to recode how the preferences are saved. Adobe used this approach the Deco fill scripts. You can also look at C:\Program Files\Adobe\Adobe Photoshop 2021\Presets\Deco__Deco Menu.jsx to see it. The below code is actually a little backwards. the UI should come up first, with the controls, then the button to save is selected, but this was done to for someone else, but hopefully you can get the jist of it.

 

 

///////////Preference and XML vars  
var startNodes = new XML('<root><presets/></root>');//creates a start for addin info to the XML preference file.  
//var startNodes = new XML('<root><presets><preset presetName ="Current Default"/></presets></root>');//creates a start for addin info to the XML preference file.  
var prefXML = new XML();// the actual XML file that holds the preferences until written to a file.  
//var prefXML = new XML();// the actual XML file that holds the preferences until written to a file.  
var presetList = new Array(  );//Blank array to be populated with the users preferences stored in an XML file  
var saveName;//Name for the saved preset  
  
var xmlFile = new File('~/Desktop/xmlTest.xml');  
if(xmlFile.exists){  
    prefXML = new XML(readXMLFile(xmlFile))  
    };// see if a preference file exists and load it.  
else {prefXML = startNodes}  
  
var dlg = new Window('dialog','XML Test')  

        
        /////////All elements separated in a separate window
        ///////////////////////////////////////////////////////////////////////////////////////////
       var dlg2 = new Window('dialog','XML Test')  
       var namePreset = dlg2.add('edittext',undefined,'Name of the preset'); 
       namePreset.size = [150,16] 
       namePreset.active = true;
       
        
        var myCheckBox = dlg2.add('checkbox',undefined,'Check Box'); myCheckBox.name='myCheckBox';  
        var myRadio1 = dlg2.add('radiobutton',undefined,'Radio 1'); myRadio1.name='myRadio1';  
        var myRadio2 = dlg2.add('radiobutton',undefined,'Radio 2'); myRadio2.name='myRadio2';  
        myRadio1.value = true;  
        var myEditT = dlg2.add('edittext',undefined,'Edit Test '); myEditT.name='myEditT';  
        var myStaticT = dlg2.add('statictext',undefined,'Static Test '); myStaticT.name='myStaticT';  
        var mySlider = dlg2.add('slider',undefined,25,0,100); mySlider.name='mySlider';  
        var myDropList = dlg2.add('dropdownlist',undefined,['one','two','three']); myDropList.name='myDropList';  
         
         var AddnPreset = dlg2.add('button',undefined,'Add New Preset')  
         ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
         ///xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

     
/////////////////////////////////// This needs to be in any dialog box for the presets to work///////////////////////////////////////////////////////////  
/////////////////////////////////// The name of the dialog has to match whatever is in the current UI///////////////////////////////////////////  
dlg.gp = dlg.add('group');  
dlg.gp.orientation = 'row';  
    var presetListDrop = dlg.gp.add('dropdownlist',undefined, ['No Presets']); //presetListDrop.name = 'presetListDrop';  
        presetListDrop.size = [200,16]  
        presetListDrop.selection = 0;  
          
        presetListDrop.onChange = function(){//updates UI when new preset is selected  
              if(prefXML.presets.children().length()>0){//loads presets into UI                       
                 setUIvar(prefXML,parseInt(presetListDrop.selection),dlg);  
              };                  
        };          
    var saveP = dlg.gp.add('button',undefined,'Create New Preset')  
    var deleteP = dlg.gp.add('button',undefined,'Delete Current Preset')  
  
        saveP.onClick = function(){  
            var goodName = true;  
            //saveName = prompt ('Enter a name for the preset', '', 'Preset Save');  
            
            dlg2.show();  // Load the window 2 (Settings)
            ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////   
            
            for(var i=0;i<presetListDrop.items.length;i++){  
                if(presetListDrop.items[i].text==saveName){  
                    goodName = false;  
                    alert(saveName +' is already in use. Choose another name.')  
                    }   
                }  
             
            if(saveName && goodName){                
                storePrefs()  
                presetListDrop.selection = presetListDrop.items.length -1  
                }  
            };  
          
        deleteP.onClick = function(){  
            if(isNaN(parseInt(presetListDrop.selection))){alert('You must select a preset to delete first')}  
            else{  
                var delPre = confirm ('Do you want to delete the preset "' + presetListDrop.selection.text +'"?', 'Yes', 'Delete Preset')  
                if(delPre){  
                    delete prefXML.presets.preset[parseInt(presetListDrop.selection)];  
                    setPresetList();  
                    writeXMLFile(xmlFile,prefXML);  
                    presetListDrop.selection = 0  
                }//end if  
            };//end else  
        };//end function      
  
  
      if(prefXML.presets.children().length()>0){//loads presets into UI  
            setPresetList()      
            setUIvar(prefXML,0,dlg)  
            //presetListDrop.selection = 0  
            }  
  
    ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////  
      
    var myOkButton = dlg.add('button',undefined,'Close');  
  
    myOkButton.onClick = function(){dlg.close()};  
  
  
dlg.show();  
  
  
////////////////////////////////Presets//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////  
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////  
//function to add a new preset  
function storePrefs(){  
    var tempXML  
        tempXML = new XML('<root><presets><preset presetName ="' + saveName + '"/></presets></root>');  
        setXML(tempXML,0,dlg);  
        prefXML.presets.appendChild (XML(tempXML.presets.preset[0]))  
        setPresetList();               
        writeXMLFile(xmlFile,prefXML);        
      };//end function storePrefs  
    
  
/////////////////////////////////////////////////////////////////////////////////////////////////////  
//function loops through the ui object and if the control items have been assigned a name it stores the name and the value to an XML file.  
 function setXML(x,n,d){//x = xml file, n = starting node, d = dialog.   
    for(var i = 0;i<d.children.length;i++){  
       if(d.children[i].type == 'panel' || d.children[i].type == 'group' || d.children[i].type == 'tabbedpanel' || d.children[i].type == 'tab'){setXML(x,n,d.children[i])}//loops though UI and restarts function if it comes to a container that might have more children  
        else{  
            if(d.children[i].name){//check to make sure the control has a name assigned so that it only records those with name.  
                switch(d.children[i].type){  
                    case 'radiobutton':  
                        x.presets.child(n).appendChild(XML('<' + d.children[i].name +' type="' + d.children[i].type + '">' + d.children[i].value + '</' + d.children[i].name + '>'));                          
                        break;  
                    case 'checkbox':  
                        x.presets.child(n).appendChild(XML('<' + d.children[i].name +' type="' + d.children[i].type + '">' + d.children[i].value + '</' + d.children[i].name + '>'));                          
                        break;  
                    case 'slider':  
                        x.presets.child(n).appendChild(XML('<' + d.children[i].name +' type="' + d.children[i].type + '">' + d.children[i].value + '</' + d.children[i].name + '>'));                          
                        break;  
                    case 'edittext':                      
                        x.presets.child(n).appendChild(XML('<' + d.children[i].name +' type="' + d.children[i].type + '"><![CDATA[' + d.children[i].text + ']]\></' + d.children[i].name + '>'));                          
                        break;                      
                    case 'dropdownlist':  
                        if(d.children[i].selection){varHold = d.children[i].selection.text}  
                        else{varHold = 'null'};  
                        x.presets.child(n).appendChild(XML('<' + d.children[i].name +' selecIndex="' + d.children[i].selection + '" type="' + d.children[i].type + '"><![CDATA[' + varHold + ']]\></' + d.children[i].name + '>'));                          
                        break;  
                  };//end switch  
                }//end if for child having name  
            };//end else  
        };//end for loop  
 }//end function setXML  
  
////////////////////////////////////////////////////////////////////////////////////////////////  
//function loops through the ui object and if a control item has been assigned a name uses that name to look up the preset value in the XML.  
function setUIvar(x,n,d){//x= xml file; n = node number (0 is default node), d = UI dialog  
  
    var currentXMLVal;//used to store values from XML file.  When this value is assigned, it checks to see if value from XML exist  
    var noMatch = false  
      
    for(var i = 0;i<d.children.length;i++){  
        noMatch = false;  
          
        if(d.children[i].type == 'panel' || d.children[i].type == 'group' || d.children[i].type == 'tab' || d.children[i].type == 'tabbedpanel'){setUIvar(x,n,d.children[i])};//reruns function if child is container and not control item.      
        else{  
            if(d.children[i].name){//Checks to see if child has a name assigned so only will reset those control items that have a name will be stored.  
                try{  
                    //Assigns all variables from presets node.  The "n" tells which preset to select.   
                    currentXMLVal = x.presets.preset[n].child(d.children[i].name);     
                    if(currentXMLVal == 'null'){currentXMLVal = null};  
                    }//end try  
                //catch assigns 'no_good' to current XMLVal so that if there is no value in the XML file, it will not try to assign a bad value to the UI controls.  
                catch(e){currentXMLVal = 'no_good'};//end catch  
                //switch makes sure proper type of value is reassigned back to UI controls.  
                if(x.presets.preset[n].child(d.children[i].name).length() > 0 || d.children[i].type == 'button'){  
                      
                    switch(d.children[i].type){  
                        case 'radiobutton':  
                            d.children[i].value = returnBoolean(currentXMLVal);  
                            break;  
                         case 'checkbox':  
                            d.children[i].value = returnBoolean(currentXMLVal);                          
                            break;  
                        case 'edittext':  
                            d.children[i].text = currentXMLVal;  
                            break;  
                        case 'slider':  
                            d.children[i].value = parseFloat(currentXMLVal);  
                            break;  
                        case 'dropdownlist':  
                            varHold = false;  
  
                          if(x.presets.preset[n].child(d.children[i].name).@selecIndex.toString() == 'null'){d.children[i].selection = null}  
                          else{d.children[i].selection = parseInt(x.presets.preset[n].child(d.children[i].name).@selecIndex)};  
  
                          break;  
                          };//end switch else  
  
                   };//end if to see if there is a good value from the XML  
                 
                };//end if for UI control having name  
            };//end else for if child is container or control  
        };//end for loop   
  
 };//end function setUIvar  
  
 //function returns a boolean value.  Values stored in XML are returned as strings, so they need to be converted back to boolean values.  
function returnBoolean(b){  
    if(b == 'true'){return true}  
    else{return false}  
    };  
  
//Function resets the values of the preset list when items are added or deleted.  
function setPresetList(){  
    presetList = new Array();  
    presetListDrop.removeAll();  
    for(var i=0;i<prefXML.presets.children().length();i++){presetListDrop.add('item',prefXML.presets.children()[i].@presetName)}  
};//end function setPresetList  
  
function readXMLFile(file) {  
    if (!file.exists) {  
        alert( "Cannot find file: " + deodeURI(file.absoluteURI));  
        }  
    else{  
        file.encoding = "UTF8";  
        file.lineFeed = "unix";  
        file.open("r", "TEXT", "????");  
        var str = file.read();  
        file.close();  
  
        return new XML(str);  
        };  
};  
  
function writeXMLFile(file, xml) {  
    file.encoding = "UTF8";  
    file.open("w", "TEXT", "????");  
    //unicode signature, this is UTF16 but will convert to UTF8 "EF BB BF"  
    file.write("\uFEFF");  
    file.lineFeed = "unix";  
    if (!(xml instanceof XML)) {  
        for(var g=0;g<scriptArray.length;g++){  
            try{  
                file.writeln (scriptArray[g].toString())  
                }  
            catch(e){}  
         };//end for loop              
        }  
    else{file.write(xml.toXMLString())};  
    file.close();  
    };

 

Votes

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
Explorer ,
Jan 06, 2021 Jan 06, 2021

Copy link to clipboard

Copied

Thank you for that.  It really helps!  Obviously writing the file to the desktop is not a good idea...does Adobe have a particular place that they want us saving these files to or is your solution more of a situation where the user loads the files for the preset they want to use instead of it presenting a list of available ones for them to choose from?  I assumed that Adobe wouldn't give us access to their file system for security reasons.  If they do, that's actually pretty scary!

Votes

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
Explorer ,
Jan 09, 2021 Jan 09, 2021

Copy link to clipboard

Copied

Okay, so unfortunately your solution doesn't work in UXP.  The objects/classes aren't recognized.  BUT I did find a REALLY easy way to do it that does work.

 

My presets are saved as a JSON object in my code and I can save them to the local storage with: window.localStorage.setItem and then retrieve them with: window.localStorage.getItem.  The only gotcha is that you have stringify/parse the JSON object when you do it.

 

You can do a simple test of making this work with the following code:

window.localStorage.setItem('test', JSON.stringify({ name: "Test", description: "Attention, this is a test", default: true ));
var myOjbect = JSON.parse(window.localStorage.getItem('test'));

 

I figured I would put the answer here for anyone down the road who is looking how to do something similar because the UXP stuff is HORRIBLY documented and (as of yet) only partially supported!  Ugh!

 

You can read more about this way of doing it here.

Votes

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
LEGEND ,
Dec 30, 2020 Dec 30, 2020

Copy link to clipboard

Copied

I was writing prefs to XML files with my Bridge Utility Script Pack before I switched to using the built-in preferences functions. Extendscript does a decent job on XML although documentation is terrible so its lots of trial and error.

Votes

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 ,
Jan 06, 2021 Jan 06, 2021

Copy link to clipboard

Copied

In my example, I just saved it to the desktop because it was easy, but the file can be saved anywhere. It would probably be best to save in a location where other presets reside.

Votes

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
Explorer ,
Dec 07, 2021 Dec 07, 2021

Copy link to clipboard

Copied

LATEST

A bit late to the party, but UXP has a dedicated dataFolder exactly for this purpose:

https://www.adobe.io/photoshop/uxp/2022/uxp/reference-js/Modules/uxp/Persistent%20File%20Storage/Fil...

Votes

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