diff --git a/src/DynamoCore/Configuration/IPathResolver.cs b/src/DynamoCore/Configuration/IPathResolver.cs
index 5273787238a..a2d7b854bbd 100644
--- a/src/DynamoCore/Configuration/IPathResolver.cs
+++ b/src/DynamoCore/Configuration/IPathResolver.cs
@@ -156,6 +156,11 @@ public interface IPathManager
///
string SamplesDirectory { get; }
+ ///
+ /// The root directory where all template files are stored
+ ///
+ string TemplatesDirectory { get; }
+
///
/// The directory where the automatically saved files will be stored.
///
diff --git a/src/DynamoCore/Configuration/PathManager.cs b/src/DynamoCore/Configuration/PathManager.cs
index 6a794e8ab04..1e3649483b1 100644
--- a/src/DynamoCore/Configuration/PathManager.cs
+++ b/src/DynamoCore/Configuration/PathManager.cs
@@ -66,6 +66,7 @@ internal static Lazy
public const string ViewExtensionsDirectoryName = "viewExtensions";
public const string DefinitionsDirectoryName = "definitions";
public const string SamplesDirectoryName = "samples";
+ public const string TemplateDirectoryName = "templates";
public const string BackupDirectoryName = "backup";
public const string PreferenceSettingsFileName = "DynamoSettings.xml";
public const string PythonTemplateFileName = "PythonTemplate.py";
@@ -82,6 +83,7 @@ internal static Lazy
private string commonPackages;
private string logDirectory;
private string samplesDirectory;
+ private string templatesDirectory;
private string backupDirectory;
private string defaultBackupDirectory;
private string preferenceFilePath;
@@ -240,6 +242,14 @@ public string SamplesDirectory
get { return samplesDirectory; }
}
+ ///
+ /// Dynamo Templates folder
+ ///
+ public string TemplatesDirectory
+ {
+ get { return templatesDirectory; }
+ }
+
public string BackupDirectory
{
get { return backupDirectory; }
@@ -572,6 +582,7 @@ private void BuildCommonDirectories()
commonDefinitions = Path.Combine(commonDataDir, DefinitionsDirectoryName);
commonPackages = Path.Combine(commonDataDir, PackagesDirectoryName);
samplesDirectory = GetSamplesFolder(commonDataDir);
+ templatesDirectory = GetTemplateFolder(commonDataDir);
rootDirectories = new List { userDataDir };
@@ -715,7 +726,51 @@ private static string GetSamplesFolder(string dataRootDirectory)
return sampleDirectory;
}
-
+
+ ///
+ /// Get template folder path from common data directory
+ ///
+ ///
+ ///
+ private string GetTemplateFolder(string dataRootDirectory)
+ {
+ var versionedDirectory = dataRootDirectory;
+ if (!Directory.Exists(versionedDirectory))
+ {
+ // Try to see if folder "%ProgramData%\{...}\{major}.{minor}" exists, if it
+ // does not, then root directory would be "%ProgramData%\{...}".
+ //
+ dataRootDirectory = Directory.GetParent(versionedDirectory).FullName;
+ }
+ else if (!Directory.Exists(Path.Combine(versionedDirectory, TemplateDirectoryName)))
+ {
+ // If the folder "%ProgramData%\{...}\{major}.{minor}" exists, then try to see
+ // if the folder "%ProgramData%\{...}\{major}.{minor}\templates" exists. If it
+ // doesn't exist, then root directory would be "%ProgramData%\{...}".
+ //
+ dataRootDirectory = Directory.GetParent(versionedDirectory).FullName;
+ }
+
+ var uiCulture = CultureInfo.CurrentUICulture.Name;
+ var templateDirectory = Path.Combine(dataRootDirectory, TemplateDirectoryName, uiCulture);
+
+ // If the localized template directory does not exist then fall back
+ // to using the en-US template folder. Do an additional check to see
+ // if the localized folder is available but is empty.
+ //
+ var di = new DirectoryInfo(templateDirectory);
+ if (!Directory.Exists(templateDirectory) ||
+ !di.GetDirectories().Any() ||
+ !di.GetFiles("*.dyn", SearchOption.AllDirectories).Any())
+ {
+ var neturalCommonTemplates = Path.Combine(dataRootDirectory, TemplateDirectoryName, "en-US");
+ if (Directory.Exists(neturalCommonTemplates))
+ templateDirectory = neturalCommonTemplates;
+ }
+
+ return templateDirectory;
+ }
+
private IEnumerable LibrarySearchPaths(string library)
{
// Strip out possible directory from library path.
diff --git a/src/DynamoCore/Models/DynamoModel.cs b/src/DynamoCore/Models/DynamoModel.cs
index ced6d3402f5..46293ee6158 100644
--- a/src/DynamoCore/Models/DynamoModel.cs
+++ b/src/DynamoCore/Models/DynamoModel.cs
@@ -1957,6 +1957,29 @@ public void OpenFileFromPath(string filePath, bool forceManualExecutionMode = fa
}
}
+ ///
+ /// Opens a Dynamo workspace from a path to a template on disk.
+ ///
+ /// Path to file
+ /// Set this to true to discard
+ /// execution mode specified in the file and set manual mode
+ public void OpenTemplateFromPath(string filePath, bool forceManualExecutionMode = false)
+ {
+
+ if (DynamoUtilities.PathHelper.isValidJson(filePath, out string fileContents, out Exception ex))
+ {
+ OpenJsonFileFromPath(fileContents, filePath, forceManualExecutionMode, true);
+ }
+ else
+ {
+ // These kind of exceptions indicate that file is not accessible
+ if (ex is IOException || ex is UnauthorizedAccessException || ex is JsonReaderException)
+ {
+ throw ex;
+ }
+ }
+ }
+
///
/// Inserts a Dynamo graph or Custom Node inside the current workspace from a file path
///
@@ -2028,8 +2051,9 @@ static private DynamoPreferencesData DynamoPreferencesDataFromJson(string json)
/// Path to file
/// Set this to true to discard
/// execution mode specified in the file and set manual mode
+ /// Set this to true to indicate that the file is a template
/// True if workspace was opened successfully
- private bool OpenJsonFileFromPath(string fileContents, string filePath, bool forceManualExecutionMode)
+ private bool OpenJsonFileFromPath(string fileContents, string filePath, bool forceManualExecutionMode, bool isTemplate = false)
{
try
{
@@ -2040,7 +2064,7 @@ private bool OpenJsonFileFromPath(string fileContents, string filePath, bool for
if (true) //MigrationManager.ProcessWorkspace(dynamoPreferences.Version, xmlDoc, IsTestMode, NodeFactory))
{
WorkspaceModel ws;
- if (OpenJsonFile(filePath, fileContents, dynamoPreferences, forceManualExecutionMode, out ws))
+ if (OpenJsonFile(filePath, fileContents, dynamoPreferences, forceManualExecutionMode, isTemplate, out ws))
{
OpenWorkspace(ws);
//Raise an event to deserialize the view parameters before
@@ -2076,7 +2100,7 @@ private bool InsertJsonFileFromPath(string fileContents, string filePath, bool f
{
if (true) //MigrationManager.ProcessWorkspace(dynamoPreferences.Version, xmlDoc, IsTestMode, NodeFactory))
{
- if (OpenJsonFile(filePath, fileContents, dynamoPreferences, forceManualExecutionMode, out WorkspaceModel ws))
+ if (OpenJsonFile(filePath, fileContents, dynamoPreferences, forceManualExecutionMode, false, out WorkspaceModel ws))
{
ExtraWorkspaceViewInfo viewInfo = ExtraWorkspaceViewInfo.ExtraWorkspaceViewInfoFromJson(fileContents);
@@ -2268,6 +2292,7 @@ private bool OpenJsonFile(
string fileContents,
DynamoPreferencesData dynamoPreferences,
bool forceManualExecutionMode,
+ bool isTemplate,
out WorkspaceModel workspace)
{
if (!string.IsNullOrEmpty(filePath))
@@ -2295,8 +2320,8 @@ private bool OpenJsonFile(
CustomNodeManager,
this.LinterManager);
- workspace.FileName = string.IsNullOrEmpty(filePath) ? "" : filePath;
- workspace.FromJsonGraphId = string.IsNullOrEmpty(filePath) ? WorkspaceModel.ComputeGraphIdFromJson(fileContents) : "";
+ workspace.FileName = string.IsNullOrEmpty(filePath) || isTemplate? string.Empty : filePath;
+ workspace.FromJsonGraphId = string.IsNullOrEmpty(filePath) ? WorkspaceModel.ComputeGraphIdFromJson(fileContents) : string.Empty;
workspace.ScaleFactor = dynamoPreferences.ScaleFactor;
if (!IsTestMode && !IsHeadless)
diff --git a/src/DynamoCore/Models/DynamoModelCommands.cs b/src/DynamoCore/Models/DynamoModelCommands.cs
index b2259ab6ca7..eae0f04c19a 100644
--- a/src/DynamoCore/Models/DynamoModelCommands.cs
+++ b/src/DynamoCore/Models/DynamoModelCommands.cs
@@ -46,12 +46,20 @@ protected virtual void OpenFileImpl(OpenFileCommand command)
{
string filePath = command.FilePath;
bool forceManualMode = command.ForceManualExecutionMode;
+ bool isTemplate = command.IsTemplate;
OpenFileFromPath(filePath, forceManualMode);
//clear the clipboard to avoid copying between dyns
//ClipBoard.Clear();
}
+ protected virtual void OpenTemplateImpl(OpenFileCommand command)
+ {
+ string filePath = command.FilePath;
+ bool forceManualMode = command.ForceManualExecutionMode;
+ OpenTemplateFromPath(filePath, forceManualMode);
+ }
+
protected virtual void OpenFileFromJsonImpl(OpenFileFromJsonCommand command)
{
string fileContents = command.FileContents;
diff --git a/src/DynamoCore/Models/RecordableCommands.cs b/src/DynamoCore/Models/RecordableCommands.cs
index 44355e6cf15..af0232dd2e4 100644
--- a/src/DynamoCore/Models/RecordableCommands.cs
+++ b/src/DynamoCore/Models/RecordableCommands.cs
@@ -458,14 +458,16 @@ public class OpenFileCommand : RecordableCommand
#region Public Class Methods
///
- ///
+ /// Constructor
///
/// The path to the file.
/// Should the file be opened in manual execution mode?
- public OpenFileCommand(string filePath, bool forceManualExecutionMode = false)
+ /// Is Dynamo opening a template file?
+ public OpenFileCommand(string filePath, bool forceManualExecutionMode = false, bool isTemplate = false)
{
FilePath = filePath;
ForceManualExecutionMode = forceManualExecutionMode;
+ IsTemplate = isTemplate;
}
private static string TryFindFile(string xmlFilePath, string uriString = null)
@@ -507,6 +509,7 @@ internal static OpenFileCommand DeserializeCore(XmlElement element)
[DataMember]
internal string FilePath { get; private set; }
internal bool ForceManualExecutionMode { get; private set; }
+ internal bool IsTemplate { get; private set; }
private DynamoModel dynamoModel;
#endregion
@@ -516,7 +519,14 @@ internal static OpenFileCommand DeserializeCore(XmlElement element)
protected override void ExecuteCore(DynamoModel dynamoModel)
{
this.dynamoModel = dynamoModel;
- dynamoModel.OpenFileImpl(this);
+ if (IsTemplate)
+ {
+ dynamoModel.OpenTemplateImpl(this);
+ }
+ else
+ {
+ dynamoModel.OpenFileImpl(this);
+ }
}
protected override void SerializeCore(XmlElement element)
diff --git a/src/DynamoCoreWpf/Properties/Resources.Designer.cs b/src/DynamoCoreWpf/Properties/Resources.Designer.cs
index b0c93817cad..4a2d17e80b2 100644
--- a/src/DynamoCoreWpf/Properties/Resources.Designer.cs
+++ b/src/DynamoCoreWpf/Properties/Resources.Designer.cs
@@ -1801,6 +1801,24 @@ public static string DynamoViewFileMenuOpen {
}
}
+ ///
+ /// Looks up a localized string similar to _File.
+ ///
+ public static string DynamoViewFileMenuOpenFile {
+ get {
+ return ResourceManager.GetString("DynamoViewFileMenuOpenFile", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to _Template.
+ ///
+ public static string DynamoViewFileMenuOpenTemplate {
+ get {
+ return ResourceManager.GetString("DynamoViewFileMenuOpenTemplate", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Open _Recent Files.
///
@@ -10143,6 +10161,24 @@ public static string WebView2RequiredTitle {
}
}
+ ///
+ /// Looks up a localized string similar to Workspaces cannot be saved to the Templates folder. Please choose a different folder to save your file..
+ ///
+ public static string WorkspaceSaveTemplateDirectoryBlockMsg {
+ get {
+ return ResourceManager.GetString("WorkspaceSaveTemplateDirectoryBlockMsg", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Invalid Save Path.
+ ///
+ public static string WorkspaceSaveTemplateDirectoryBlockTitle {
+ get {
+ return ResourceManager.GetString("WorkspaceSaveTemplateDirectoryBlockTitle", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to You haven't saved this file yet..
///
diff --git a/src/DynamoCoreWpf/Properties/Resources.en-US.resx b/src/DynamoCoreWpf/Properties/Resources.en-US.resx
index f1c98329dca..02c8c5c4e8a 100644
--- a/src/DynamoCoreWpf/Properties/Resources.en-US.resx
+++ b/src/DynamoCoreWpf/Properties/Resources.en-US.resx
@@ -3900,4 +3900,16 @@ In certain complex graphs or host program scenarios, Automatic mode may cause in
Your changes will be lost if you proceed.
-
+
+ _File
+
+
+ _Template
+
+
+ Workspaces cannot be saved to the Templates folder. Please choose a different folder to save your file.
+
+
+ Invalid Save Path
+
+
\ No newline at end of file
diff --git a/src/DynamoCoreWpf/Properties/Resources.resx b/src/DynamoCoreWpf/Properties/Resources.resx
index 2192e865b97..7179ad5159b 100644
--- a/src/DynamoCoreWpf/Properties/Resources.resx
+++ b/src/DynamoCoreWpf/Properties/Resources.resx
@@ -3887,4 +3887,16 @@ In certain complex graphs or host program scenarios, Automatic mode may cause in
Your changes will be lost if you proceed.
+
+ _File
+
+
+ _Template
+
+
+ Workspaces cannot be saved to the Templates folder. Please choose a different folder to save your file.
+
+
+ Invalid Save Path
+
\ No newline at end of file
diff --git a/src/DynamoCoreWpf/ViewModels/Core/DynamoViewModel.cs b/src/DynamoCoreWpf/ViewModels/Core/DynamoViewModel.cs
index 0f0b706213a..a66dde38597 100644
--- a/src/DynamoCoreWpf/ViewModels/Core/DynamoViewModel.cs
+++ b/src/DynamoCoreWpf/ViewModels/Core/DynamoViewModel.cs
@@ -1686,8 +1686,9 @@ private void OpenFromJson(object parameters)
/// Open a definition or workspace.
/// For most cases, parameters variable refers to the file path to open
/// However, when this command is used in OpenFileDialog, the variable is
- /// a Tuple{string, bool} instead. The boolean flag is used to override the
- /// RunSetting of the workspace.
+ /// a Tuple{string, bool} instead. When this command is used in OpenTemplateDialog,
+ /// the variable is a Tuple{string, bool} instead. The second boolean flag is
+ /// used to override the RunSetting of the workspace.
///
///
private void Open(object parameters)
@@ -1696,7 +1697,8 @@ private void Open(object parameters)
// that can't be handled reliably
filePath = string.Empty;
fileContents = string.Empty;
- bool forceManualMode = false;
+ bool forceManualMode = false;
+ bool isTemplate = false;
try
{
if (parameters is Tuple packedParams)
@@ -1704,6 +1706,12 @@ private void Open(object parameters)
filePath = packedParams.Item1;
forceManualMode = packedParams.Item2;
}
+ else if (parameters is Tuple tupleParams)
+ {
+ filePath = tupleParams.Item1;
+ forceManualMode = tupleParams.Item2;
+ isTemplate = tupleParams.Item3;
+ }
else
{
filePath = parameters as string;
@@ -1719,7 +1727,7 @@ private void Open(object parameters)
&& FileTrustViewModel != null;
RunSettings.ForceBlockRun = displayTrustWarning;
// Execute graph open command
- ExecuteCommand(new DynamoModel.OpenFileCommand(filePath, forceManualMode));
+ ExecuteCommand(new DynamoModel.OpenFileCommand(filePath, forceManualMode, isTemplate));
// Only show trust warning popop when current opened workspace is homeworkspace and not custom node workspace
if (displayTrustWarning && (currentWorkspaceViewModel?.IsHomeSpace ?? false))
{
@@ -1920,6 +1928,8 @@ private void ShowOpenDialogAndOpenResult(object parameter)
return;
}
+ bool isTemplate = (parameter as string).Equals("Template");
+
DynamoOpenFileDialog _fileDialog = new DynamoOpenFileDialog(this)
{
Filter = string.Format(Resources.FileDialogDynamoDefinitions,
@@ -1928,8 +1938,18 @@ private void ShowOpenDialogAndOpenResult(object parameter)
Title = string.Format(Resources.OpenDynamoDefinitionDialogTitle,BrandingResourceProvider.ProductName)
};
- // if you've got the current space path, use it as the inital dir
- if (!string.IsNullOrEmpty(Model.CurrentWorkspace.FileName))
+ // If opening a template, use templates dir as the initial dir
+ if (isTemplate && !string.IsNullOrEmpty(Model.PathManager.TemplatesDirectory))
+ {
+ string path = Model.PathManager.TemplatesDirectory;
+ if (Directory.Exists(path))
+ {
+ var di = new DirectoryInfo(Model.PathManager.TemplatesDirectory);
+ _fileDialog.InitialDirectory = di.FullName;
+ }
+ }
+ // otherwise, if you've got the current space path, use it as the initial dir
+ else if (!string.IsNullOrEmpty(Model.CurrentWorkspace.FileName))
{
string path = Model.CurrentWorkspace.FileName;
if (File.Exists(path))
@@ -1952,19 +1972,26 @@ private void ShowOpenDialogAndOpenResult(object parameter)
if (Directory.Exists(path))
_fileDialog.InitialDirectory = path;
}
-
+
if (_fileDialog.ShowDialog() == DialogResult.OK)
{
if (CanOpen(_fileDialog.FileName))
{
- Open(new Tuple(_fileDialog.FileName, _fileDialog.RunManualMode));
+ if (isTemplate)
+ {
+ // File opening API which does not modify the original template file
+ Open(new Tuple(_fileDialog.FileName, _fileDialog.RunManualMode, true));
+ }
+ else
+ {
+ Open(new Tuple(_fileDialog.FileName, _fileDialog.RunManualMode));
+ }
}
}
}
private bool CanShowOpenDialogAndOpenResultCommand(object parameter) => CanRunGraph;
-
///
/// Present the open dialog and open the workspace that is selected.
///
@@ -2118,7 +2145,17 @@ private void InternalSaveAs(string path, SaveContext saveContext, bool isBackup
try
{
Model.Logger.Log(string.Format(Properties.Resources.SavingInProgress, path));
- var hasSaved = CurrentSpaceViewModel.Save(path, isBackup, Model.EngineController, saveContext);
+ var hasSaved = false;
+ if (path.Contains(Model.PathManager.TemplatesDirectory))
+ {
+ // Give user notifications
+ DynamoMessageBox.Show(WpfResources.WorkspaceSaveTemplateDirectoryBlockMsg, WpfResources.WorkspaceSaveTemplateDirectoryBlockTitle,
+ MessageBoxButton.OK, MessageBoxImage.Warning);
+ }
+ else
+ {
+ hasSaved = CurrentSpaceViewModel.Save(path, isBackup, Model.EngineController, saveContext);
+ }
if (!isBackup && hasSaved)
{
diff --git a/src/DynamoCoreWpf/ViewModels/Core/DynamoViewModelDelegateCommands.cs b/src/DynamoCoreWpf/ViewModels/Core/DynamoViewModelDelegateCommands.cs
index b8b4a4d1e6b..56faceb04df 100644
--- a/src/DynamoCoreWpf/ViewModels/Core/DynamoViewModelDelegateCommands.cs
+++ b/src/DynamoCoreWpf/ViewModels/Core/DynamoViewModelDelegateCommands.cs
@@ -18,6 +18,7 @@ private void InitializeDelegateCommands()
SaveCommand = new DelegateCommand(Save, CanSave);
SaveAsCommand = new DelegateCommand(SaveAs, CanSaveAs);
ShowOpenDialogAndOpenResultCommand = new DelegateCommand(ShowOpenDialogAndOpenResult, CanShowOpenDialogAndOpenResultCommand);
+ ShowOpenTemplateDialogCommand = new DelegateCommand(ShowOpenDialogAndOpenResult, CanShowOpenDialogAndOpenResultCommand);
ShowInsertDialogAndInsertResultCommand = new DelegateCommand(ShowInsertDialogAndInsertResult, CanShowInsertDialogAndInsertResultCommand);
ShowSaveDialogAndSaveResultCommand = new DelegateCommand(ShowSaveDialogAndSaveResult, CanShowSaveDialogAndSaveResult);
ShowSaveDialogIfNeededAndSaveResultCommand = new DelegateCommand(ShowSaveDialogIfNeededAndSaveResult, CanShowSaveDialogIfNeededAndSaveResultCommand);
@@ -98,6 +99,7 @@ private void InitializeDelegateCommands()
public DelegateCommand OpenIfSavedCommand { get; set; }
public DelegateCommand OpenCommand { get; set; }
public DelegateCommand ShowOpenDialogAndOpenResultCommand { get; set; }
+ public DelegateCommand ShowOpenTemplateDialogCommand { get; set; }
public DelegateCommand ShowInsertDialogAndInsertResultCommand { get; set; }
public DelegateCommand WriteToLogCmd { get; set; }
public DelegateCommand PostUiActivationCommand { get; set; }
diff --git a/src/DynamoCoreWpf/Views/Core/DynamoView.xaml b/src/DynamoCoreWpf/Views/Core/DynamoView.xaml
index c7611fce55b..889c07a2ca8 100644
--- a/src/DynamoCoreWpf/Views/Core/DynamoView.xaml
+++ b/src/DynamoCoreWpf/Views/Core/DynamoView.xaml
@@ -112,6 +112,9 @@
+
@@ -334,9 +337,18 @@