diff --git a/appveyor.yml b/appveyor.yml index c3c548432db..e54ed98753b 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -4,14 +4,17 @@ platform: Any CPU configuration: Release +image: Visual Studio 2017 + before_build: - nuget restore .\src\Dynamo.All.sln environment: dynamo_solution: src/Dynamo.All.sln - image: Visual Studio 2017 + build_script: + - where msbuild - msbuild %dynamo_solution% diff --git a/src/Dynamo.All.sln b/src/Dynamo.All.sln index 7adf54bec0b..539f9ceaa55 100644 --- a/src/Dynamo.All.sln +++ b/src/Dynamo.All.sln @@ -227,6 +227,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Extensions", "Extensions", EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WorkspaceDependencyViewExtension", "WorkspaceDependencyViewExtension\WorkspaceDependencyViewExtension.csproj", "{5E76AAB3-6302-473E-9655-081B53FB1419}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LibraryViewExtensionMSWebBrowser", "LibraryViewExtensionMSWebBrowser\LibraryViewExtensionMSWebBrowser.csproj", "{1A5DC90A-477E-4D4A-87BD-0BFB01F056CE}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -811,6 +813,14 @@ Global {5E76AAB3-6302-473E-9655-081B53FB1419}.Release|Any CPU.Build.0 = Release|Any CPU {5E76AAB3-6302-473E-9655-081B53FB1419}.Release|x64.ActiveCfg = Release|Any CPU {5E76AAB3-6302-473E-9655-081B53FB1419}.Release|x64.Build.0 = Release|Any CPU + {1A5DC90A-477E-4D4A-87BD-0BFB01F056CE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1A5DC90A-477E-4D4A-87BD-0BFB01F056CE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1A5DC90A-477E-4D4A-87BD-0BFB01F056CE}.Debug|x64.ActiveCfg = Debug|Any CPU + {1A5DC90A-477E-4D4A-87BD-0BFB01F056CE}.Debug|x64.Build.0 = Debug|Any CPU + {1A5DC90A-477E-4D4A-87BD-0BFB01F056CE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1A5DC90A-477E-4D4A-87BD-0BFB01F056CE}.Release|Any CPU.Build.0 = Release|Any CPU + {1A5DC90A-477E-4D4A-87BD-0BFB01F056CE}.Release|x64.ActiveCfg = Release|Any CPU + {1A5DC90A-477E-4D4A-87BD-0BFB01F056CE}.Release|x64.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -883,6 +893,7 @@ Global {C0D6DEE5-5532-4345-9C66-4C00D7FDB8BE} = {FA7BE306-A3B0-45FA-9D87-0C69E6932C13} {47D2166C-5261-4093-9660-E72B7035E666} = {88D45B00-E564-41DB-B57C-9509646CAA49} {5E76AAB3-6302-473E-9655-081B53FB1419} = {88D45B00-E564-41DB-B57C-9509646CAA49} + {1A5DC90A-477E-4D4A-87BD-0BFB01F056CE} = {88D45B00-E564-41DB-B57C-9509646CAA49} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {89CB19C6-BF0A-4E6A-BFDA-79D143EAB59D} diff --git a/src/DynamoCore/Properties/AssemblyInfo.cs b/src/DynamoCore/Properties/AssemblyInfo.cs index eb12d6ab109..874b1b21080 100644 --- a/src/DynamoCore/Properties/AssemblyInfo.cs +++ b/src/DynamoCore/Properties/AssemblyInfo.cs @@ -34,3 +34,5 @@ // Internals are visible to the Package Manager extension // For workspace package dependency collection [assembly: InternalsVisibleTo("WorkspaceDependencyViewExtension")] + +[assembly: InternalsVisibleTo("LibraryViewExtensionMSWebBrowser")] \ No newline at end of file diff --git a/src/LibraryViewExtensionMSWebBrowser/EventObserver.cs b/src/LibraryViewExtensionMSWebBrowser/EventObserver.cs new file mode 100644 index 00000000000..9c0fb2fd46c --- /dev/null +++ b/src/LibraryViewExtensionMSWebBrowser/EventObserver.cs @@ -0,0 +1,167 @@ +using System; +using System.Threading; + +namespace Dynamo.LibraryViewExtensionMSWebBrowser +{ + /// + /// A class that observes an event and notifies the clients with a transformed + /// data as configured using reduce function. + /// + /// Type of value passed when event is raised + /// Type of transformed data to notify to the client + class EventObserver : IDisposable + { + private Timer throttler = null; + private TimeSpan duetime; + private bool accumulate = false; + private bool disposed = false; + + private TResult result; + private Func reducer; + private Action observer; + + private object root = new object(); + + /// + /// This event is fired when this object is disposed, clients can + /// register a callback for Dispose. + /// + public event Action Disposed; + + /// + /// Creates the event observer with a registered observe action and + /// a reduce function for notification. + /// + /// This action will be invoked to notify the client + /// This function will be invoked to reduce the + /// actual event parameter together with last reduced value returned by this reducer. + public EventObserver(Action observe, Func reduce) + { + observer = observe; + reducer = reduce; + accumulate = true; + result = default(TResult); + } + + /// + /// Creates the event observer with a registered observe action and + /// a reduce function for notification. + /// + /// This action will be invoked to notify the client + /// This function will be invoked to transform the + /// actual event parameter. + public EventObserver(Action observe, Func transform) + { + observer = observe; + reducer = (x, y) => transform(y); + result = default(TResult); + } + + public static TValue Identity(TValue last, TValue current) + { + return current; + } + + /// + /// The client need to register this method as an event callback for + /// the event that needs to be observed. + /// + /// Event parameter passed + public void OnEvent(TValue value) + { + if (disposed) return; + + lock (root) { result = reducer(result, value); } + + OnEventCore(value); + } + + /// + /// The core implementation for OnEvent, if throttle is active it will + /// update the throttle timer with the due time, so that if no subsequent + /// event is raised within the given throttle due time, the client will + /// be notified. + /// + /// + protected virtual void OnEventCore(TValue value) + { + if (throttler != null) + { + throttler.Change(duetime, TimeSpan.FromMilliseconds(0)); + } + else + { + Notify(this); + } + } + + /// + /// Creates an observer that throttle the stream of notification + /// for a given duetime i.e. notification will be fired only if duetime + /// is ellapsed from the last event trigger, if the event trigger is + /// sooner then the observer will again wait for the given duetime + /// to notify the client. + /// + /// Due time for throttle + /// The modified event Observer which throttle the events + public EventObserver Throttle(TimeSpan duetime) + { + if (disposed) throw new ObjectDisposedException("EventObserver"); + + this.duetime = duetime; + this.throttler = new Timer(Notify, this, Timeout.Infinite, Timeout.Infinite); + //when throttled, reset the result after notification is fired. + accumulate = false; + return this; + } + + /// + /// Notify the client, could be triggered from throttle timer or + /// called directly by passing this object as state. + /// + /// + protected void Notify(object state) + { + if (disposed) throw new ObjectDisposedException("EventObserver"); + + TResult r; + lock (root) + { + r = result; + if (!accumulate) result = default(TResult); + } + observer(r); + } + + public void Dispose() + { + if (!disposed) + { + disposed = true; + if (Disposed != null) { Disposed(); } + Disposed = null; + observer = null; + reducer = null; + } + } + } + + /// + /// Implements IDisposable to invoke a given dispose action on Dispose(). + /// + class AnonymousDisposable : IDisposable + { + private Action dispose; + + public AnonymousDisposable(Action dispose) + { + this.dispose = dispose; + } + + public void Dispose() + { + if(dispose != null) { dispose(); } + dispose = null; + } + } +} diff --git a/src/LibraryViewExtensionMSWebBrowser/Handlers/DllResourceProvider.cs b/src/LibraryViewExtensionMSWebBrowser/Handlers/DllResourceProvider.cs new file mode 100644 index 00000000000..d8e1c642434 --- /dev/null +++ b/src/LibraryViewExtensionMSWebBrowser/Handlers/DllResourceProvider.cs @@ -0,0 +1,86 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; + +namespace Dynamo.LibraryViewExtensionMSWebBrowser.Handlers +{ + /// + /// A simple DllResourceProvider, which provides embedded resources from the dll. + /// + public class DllResourceProvider : ResourceProviderBase + { + /// + /// Creates a resource provider to get embedded resources from a given assembly. + /// + /// The base url from where the request is served. + /// Root namespace for this resource provider. + /// Assembly from where the resources are to be obtained. + public DllResourceProvider(string baseUrl, string rootNamespace, Assembly assembly = null) + { + RootNamespace = rootNamespace; + BaseUrl = baseUrl; + Assembly = assembly; + if (Assembly == null) + Assembly = Assembly.GetExecutingAssembly(); + } + + /// + /// Root namespace for this resource provider. For an example, if this provider + /// serves resources at "Dynamo.LibraryUI.Web.Xxxx" (where "Xxxx" is the actual + /// name of the resource), then root namespace would be "Dynamo.LibraryUI.Web". + /// + public string RootNamespace { get; private set; } + + /// + /// The base url from where the request is served. For example, if this provider + /// serves requests at http://localhost/dist/v0.0.1/Xxxx (where "Xxxx" is the name + /// of the request), then the base url is "http://localhost/dist/v0.0.1". + /// + public string BaseUrl { get; private set; } + + /// + /// Assembly from where the resources are to be obtained. + /// + public Assembly Assembly { get; private set; } + + /// + /// Call this method to get the stream for a given requested resource. + /// + /// The requested url. + /// Output parameter whose value is the extension + /// of the requested resource. This extension does not contain "." character. + /// Returns the stream if the requested resource can be found, or null + /// otherwise. + public override Stream GetResource(string url, out string extension) + { + extension = "txt"; + var uri = new Uri(url); + string resourceName; + var assembly = GetResourceAssembly(uri, out resourceName); + if (null == assembly) + { + return null; + } + + var stream = assembly.GetManifestResourceStream(resourceName); + var idx = resourceName.LastIndexOf('.'); + if (idx > 0) + { + extension = resourceName.Substring(idx + 1); + } + + return stream; + } + + protected virtual Assembly GetResourceAssembly(Uri url, out string resourceName) + { + var path = url.AbsoluteUri.Replace(BaseUrl, "").Replace('/', '.'); + resourceName = RootNamespace + path; + return this.Assembly; + } + } +} diff --git a/src/LibraryViewExtensionMSWebBrowser/Handlers/IconResourceProvider.cs b/src/LibraryViewExtensionMSWebBrowser/Handlers/IconResourceProvider.cs new file mode 100644 index 00000000000..d2005eb1fda --- /dev/null +++ b/src/LibraryViewExtensionMSWebBrowser/Handlers/IconResourceProvider.cs @@ -0,0 +1,281 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Resources; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using Dynamo.Engine; +using Dynamo.Interfaces; +using Dynamo.Models; + +namespace Dynamo.LibraryViewExtensionMSWebBrowser.Handlers +{ + /// + /// Implements resource provider for icons + /// + class IconResourceProvider : ResourceProviderBase + { + private const string imagesSuffix = "Images"; + private IPathManager pathManager; + private string defaultIconDataString; + private string defaultIconName; + private DllResourceProvider embeddedDllResourceProvider; + //TODO remove these at some point in future after Dynamo 2.6 release. + private MethodInfo getForAssemblyMethodInfo; + private PropertyInfo LibraryCustomizationResourceAssemblyProperty; + private Type LibraryCustomizationType; + + private LibraryViewCustomization customization; + //internal cache of url to base64 encoded image data. (url,tuple) + private Dictionary> urlToBase64DataCache = new Dictionary>(); + + /// + /// Default constructor for the IconResourceProvider + /// + /// Path manager instance to resolve the + /// customization resource path + /// Name of the default icon (including + /// extension) to be used when it can't find the requested icon + public IconResourceProvider(IPathManager pathManager, string defaultIcon = "default-icon.svg") : base(true) + { + this.pathManager = pathManager; + defaultIconName = defaultIcon; + var dynCore = typeof(DynamoModel).Assembly; + //TODO replace with direct calls after Dynamo 2.6 is released. + this.getForAssemblyMethodInfo = dynCore.GetType("Dynamo.Engine.LibraryCustomizationServices").GetMethod("GetForAssembly", BindingFlags.Static | BindingFlags.Public); + this.LibraryCustomizationType = dynCore.GetType("Dynamo.Engine.LibraryCustomization"); + this.LibraryCustomizationResourceAssemblyProperty = LibraryCustomizationType.GetProperty("ResourceAssembly", BindingFlags.Instance|BindingFlags.Public); + } + + /// + /// Additional constructor used to access customization resources and dll resource provider directly during icon lookup. + /// + /// + /// + /// + /// + public IconResourceProvider(IPathManager pathManager, DllResourceProvider dllResourceProvider,LibraryViewCustomization customization, string defaultIcon = "default-icon.svg") : + this(pathManager, defaultIcon) + { + this.customization = customization; + this.embeddedDllResourceProvider = dllResourceProvider; + } + + + /// + /// Retrieves the resource for a given url as a base64 encoded string. + /// ie data:image/png;base64, stringxxxyyyzzz + /// + /// url for the requested icon + /// Returns the extension to describe the type of resource. + /// + public string GetResourceAsString(string url, out string extension) + { + //sometimes the urls have "about:" added to them - remove this + //and do it before checking cache. + //https://en.wikipedia.org/wiki/About_URI_scheme + if (url.StartsWith("about:")) + { + url = url?.Replace("about:", string.Empty); + } + + if (!String.IsNullOrEmpty(url) && urlToBase64DataCache.ContainsKey(url)) + { + var cachedData = urlToBase64DataCache[url]; + extension = cachedData.Item2; + return cachedData.Item1; + } + + //because we modify the spec the icon urls may have been replaced by base64 encoded images + //if thats the case, no need to look them up again from disk, just return the string. + if (!String.IsNullOrEmpty(url) && url.Contains("data:image/")) + { + var match = Regex.Match(url, @"data:(?.+?);base64,(?.+)"); + var base64Data = match.Groups["data"].Value; + var contentType = match.Groups["type"].Value; + //image/png -> png + extension = contentType.Split('/').Skip(1).FirstOrDefault(); + urlToBase64DataCache.Add(url, Tuple.Create(base64Data, extension)); + return base64Data; + } + + + var base64String = string.Empty; + extension = "png"; + //Create IconUrl to parse the request.Url to icon name and path. + if (String.IsNullOrEmpty(url)) + { + return string.Empty; + } + + //before trying to create a uri we have to handle resources that might + //be embedded into the resources.dlls + //these paths will start with ./dist + if (url.StartsWith(@"./dist")) + { + //make relative url a full uri + var urlAbs = url.Replace(@"./dist", @"http://localhost/dist"); + var ext = string.Empty; + var stream = embeddedDllResourceProvider.GetResource(urlAbs, out ext); + if (stream != null) + { + extension = ext; + base64String = GetIconAsBase64(stream, ext); + } + } + else + { + //TODO check if absolute instead of using try/catch + try + { + var icon = new IconUrl(new Uri(url)); + base64String = GetIconAsBase64(icon, out extension); + + } + catch (Exception e) + { + //look in resources for registered path and just use the stream directly + var resourceDict = this.customization.Resources.ToDictionary(kvp => kvp.Key, kvp => kvp.Value); + if (resourceDict.ContainsKey(url)) + { + var fileExtension = System.IO.Path.GetExtension(url); + extension = fileExtension; + base64String = GetIconAsBase64(resourceDict[url], fileExtension); + } + } + } + if (base64String == null) + { + base64String = GetDefaultIconBase64(out extension); + + } + urlToBase64DataCache.Add(url, Tuple.Create(base64String, extension)); + return base64String; + } + + /// + /// Do not use this, in most cases you want to call GetResourceAsString() directly. + /// + /// + /// + /// + public override Stream GetResource(string url, out string extension) + { + var stream = new MemoryStream(); + var writer = new StreamWriter(stream); + writer.Write(GetResourceAsString(url, out extension)); + writer.Flush(); + stream.Position = 0; + return stream; + } + + /// + /// Gets the string for a default icon, to be used when no icon found + /// for a given request. This keeps a cache of the string to reuse next + /// time. + /// + /// + /// Returns the extension to describe the type of resource. + /// A valid Stream if the icon resource found successfully else null. + private string GetDefaultIconBase64(out string extension) + { + extension = Path.GetExtension(defaultIconName).Replace(".", ""); + if (defaultIconDataString == null) + { + var assembly = Assembly.GetExecutingAssembly(); + var resource = assembly.GetManifestResourceNames().FirstOrDefault(s => s.Contains(defaultIconName)); + + if (string.IsNullOrEmpty(resource)) return null; + + defaultIconName = resource; + var reader = new StreamReader(assembly.GetManifestResourceStream(defaultIconName)); + defaultIconDataString = Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(reader.ReadToEnd())); + reader.Dispose(); + } + + return defaultIconDataString; + } + + /// + /// Gets the base64 encoded string for the given icon resource url. This method has the potential to be very slow + /// as it will create a new resourceManager for a given assembly and search that assembly, which may require disk access. + /// + /// Icon Url + /// Returns the extension to describe the type of resource. + /// a base64 encoded string version of an image if found else null. + private string GetIconAsBase64(IconUrl icon, out string extension) + { + extension = "png"; + + var path = Path.GetFullPath(icon.Path); //Get full path if it's a relative path. + var libraryCustomization = getForAssemblyMethodInfo.Invoke(null,new object[] { path, pathManager, true }); + if (libraryCustomization == null) + return null; + + var assembly = LibraryCustomizationResourceAssemblyProperty.GetValue(libraryCustomization) as Assembly; + if (assembly == null) + return null; + + // "Name" can be "Some.Assembly.Name.customization" with multiple dots, + // we are interested in removal of the "customization" part and the middle dots. + var temp = assembly.GetName().Name.Split('.'); + var assemblyName = String.Join("", temp.Take(temp.Length - 1)); + var rm = new ResourceManager(assemblyName + imagesSuffix, assembly); + + using (var image = (Bitmap)rm.GetObject(icon.Name)) + { + if (image == null) return null; + + var tempstream = new MemoryStream(); + + image.Save(tempstream, image.RawFormat); + byte[] imageBytes = tempstream.ToArray(); + tempstream.Dispose(); + string base64String = Convert.ToBase64String(imageBytes); + return base64String; + } + } + + /// + /// Gets icon as base64 string from a given stream which points to some image (or font) data. + /// + /// + /// + /// + private string GetIconAsBase64(Stream stream, string extension) + { + string base64String = string.Empty; + if (extension.ToLower().Contains("svg")) + { + var reader = new BinaryReader(stream); + var imageBytes = reader.ReadBytes((int)stream.Length); + base64String = Convert.ToBase64String(imageBytes); + reader.Dispose(); + } + + else if (extension.ToLower().Contains("png")) + { + var reader = new BinaryReader(stream); + var imageBytes = reader.ReadBytes((int)stream.Length); + base64String = Convert.ToBase64String(imageBytes); + reader.Dispose(); + } + + else if (extension.ToLower().Contains("ttf") || extension.ToLower().Contains("woff")) + { + var reader = new BinaryReader(stream); + var fontBytes = reader.ReadBytes((int)stream.Length); + base64String = Convert.ToBase64String(fontBytes); + reader.Dispose(); + } + + return base64String; + + } + + } +} diff --git a/src/LibraryViewExtensionMSWebBrowser/Handlers/IconUrl.cs b/src/LibraryViewExtensionMSWebBrowser/Handlers/IconUrl.cs new file mode 100644 index 00000000000..248d352c919 --- /dev/null +++ b/src/LibraryViewExtensionMSWebBrowser/Handlers/IconUrl.cs @@ -0,0 +1,80 @@ +using System; +using System.IO; +using System.Linq; +using System.Net; + +namespace Dynamo.LibraryViewExtensionMSWebBrowser.Handlers +{ + class IconUrl + { + public const string ServiceEndpoint = "/icons"; + private const string query = @"?path="; + + internal const string DefaultPath = "DynamoCore.dll"; + internal const string DefaultIcon = "DefaultCustomNode.Small"; + + /// + /// Creates IconUrl object by extracting parameters from the given Uri + /// + /// Uri representing the url string + public IconUrl(Uri url) + { + Url = url.AbsoluteUri; + if(string.IsNullOrEmpty(url.Query)) + { + Path = DefaultPath; + } + else + { + var assemblyPath = url.Query.Substring(query.Length); + Path = WebUtility.UrlDecode(assemblyPath); //Decode the path to normal path + } + + Name = url.Segments.Last(); + } + + /// + /// Creates IconUrl object from given icon name and resource path + /// + public IconUrl(string iconName, string resourcePath, bool customNode = false) + { + Path = resourcePath; + Name = iconName + ".Small"; + if (customNode) + { + string customizationPath = null; + if (Path != null && Path != String.Empty) + { + customizationPath = System.IO.Path.GetDirectoryName(Path); + customizationPath = Directory.GetParent(customizationPath).FullName; + } + + Path = DefaultPath; + Name = DefaultIcon; + + if (customizationPath != null && + File.Exists(System.IO.Path.Combine(customizationPath, "bin", "Package.customization.dll"))) + { + Path = System.IO.Path.Combine(customizationPath, "bin", "Package.dll"); + } + } + + Url = string.Format(@"http://localhost{0}/{1}{2}{3}", ServiceEndpoint, Name, query, Path); + } + + /// + /// Path for the image resource assembly + /// + public string Path { get; private set; } + + /// + /// Image resource name + /// + public string Name { get; private set; } + + /// + /// A simple url string representation + /// + public string Url { get; private set; } + } +} diff --git a/src/LibraryViewExtensionMSWebBrowser/Handlers/LayoutSpecProvider.cs b/src/LibraryViewExtensionMSWebBrowser/Handlers/LayoutSpecProvider.cs new file mode 100644 index 00000000000..b30a4b0290c --- /dev/null +++ b/src/LibraryViewExtensionMSWebBrowser/Handlers/LayoutSpecProvider.cs @@ -0,0 +1,92 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; +using Dynamo.Wpf.Interfaces; + +namespace Dynamo.LibraryViewExtensionMSWebBrowser.Handlers +{ + /// + /// Implements LayoutSpec resource provider, by default it reads the spec + /// from a given json resource. It also allows to update certain specific + /// sections from the layout spec for a given set of NodeSearchElements + /// + class LayoutSpecProvider : ResourceProviderBase + { + private Stream resourceStream; + private readonly ILibraryViewCustomization customization; + private readonly IconResourceProvider iconProvider; + + /// + /// Creates the LayoutSpecProvider + /// + /// The resource name of json resource in + /// the given assembly. + /// Assembly which contains the specified resource + public LayoutSpecProvider(ILibraryViewCustomization customization, string resource, Assembly assembly = null) : base(false) + { + assembly = assembly == null ? Assembly.GetExecutingAssembly() : assembly; + var stream = assembly.GetManifestResourceStream(resource); + + //Get the spec from the stream + var spec = LayoutSpecification.FromJSONStream(stream); + customization.AddSections(spec.sections); + this.customization = customization; + this.customization.SpecificationUpdated += OnSpecificationUpdate; + } + + /// + /// Creates a layoutSpecProvider with access to the IconResourceProvider + /// so that icon urls can be replaced with base64 encoded image data. + /// + /// + /// + /// + /// + public LayoutSpecProvider(ILibraryViewCustomization customization, IconResourceProvider iconProvider, string resource, Assembly assembly = null) : + this(customization, resource, assembly) + { + this.iconProvider = iconProvider; + } + + private void OnSpecificationUpdate(object sender, EventArgs e) + { + DisposeResourceStream(); + } + + /// + /// Gets the resource for the given request + /// + public override Stream GetResource(string url, out string extension) + { + extension = "json"; + try + { + if (resourceStream == null || resourceStream.CanRead == false || resourceStream.Position == resourceStream.Length) + { + //passing false here - does not replace urls with base64 strings + resourceStream = (customization as LibraryViewCustomization).ToJSONStream(false, iconProvider); + } + } + catch + { + resourceStream = (customization as LibraryViewCustomization).ToJSONStream(false, iconProvider); + } + + return resourceStream; + } + + private void DisposeResourceStream() + { + if (resourceStream != null) + { + resourceStream.Dispose(); + resourceStream = null; + } + } + + } +} diff --git a/src/LibraryViewExtensionMSWebBrowser/Handlers/NodeItemDataProvider.cs b/src/LibraryViewExtensionMSWebBrowser/Handlers/NodeItemDataProvider.cs new file mode 100644 index 00000000000..7d72c335cc3 --- /dev/null +++ b/src/LibraryViewExtensionMSWebBrowser/Handlers/NodeItemDataProvider.cs @@ -0,0 +1,185 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Dynamo.Search; +using Dynamo.Search.SearchElements; +using Dynamo.LibraryViewExtensionMSWebBrowser.Handlers; +using Newtonsoft.Json; + +// TODO there are many clases in this file which are duplicates in LibraryViewExtension - +// these can be refactored out into a shared core. + +namespace Dynamo.LibraryViewExtensionMSWebBrowser +{ + class NodeData + { + public string creationName { get; set; } + public string module { get; set; } + } + + class LoadedTypeItem + { + public string fullyQualifiedName { get; set; } + public string iconUrl { get; set; } + public string contextData { get; set; } + public string parameters { get; set; } + public string itemType { get; set; } + public string description { get; set; } + public string keywords { get; set; } + } + + class LoadedTypeData where T : LoadedTypeItem + { + public List loadedTypes { get; set; } + } + + /// + /// Provides json resource data for all the loaded nodes + /// + class NodeItemDataProvider : ResourceProviderBase + { + protected NodeSearchModel model; + private IconResourceProvider iconProvider; + /// + /// Constructor + /// + /// + public NodeItemDataProvider(NodeSearchModel model) : base(false) + { + this.model = model; + } + + public NodeItemDataProvider(NodeSearchModel model, IconResourceProvider iconProvider) + { + this.model = model; + this.iconProvider = iconProvider; + } + + public override Stream GetResource(string url, out string extension) + { + extension = "json"; + //pass false to keep original icon urls + return GetNodeItemDataStream(model.SearchEntries, false); + } + + /// + /// main nodeItem data lookup method. + /// + /// + /// the option to replace the nodeItems iconUrl property with the base64Data + /// imagedata before it gets sent to the library UI. If this parameter is false, the urls will remain unchanged + /// and the librayUI will need to resolve them. + /// + protected Stream GetNodeItemDataStream(IEnumerable searchEntries, bool replaceIconURLWithData) + { + var ms = new MemoryStream(); + var sw = new StreamWriter(ms); + var serializer = new JsonSerializer(); + var stringBuilder = new StringBuilder(); + var data = CreateObjectForSerialization(searchEntries); + + if (replaceIconURLWithData) + { + var ext = string.Empty; + IEnumerable loadedTypes = new List(); + if (data is LoadedTypeData) + { + loadedTypes = (data as LoadedTypeData).loadedTypes; + + } + else if (data is LoadedTypeData) + { + loadedTypes = (data as LoadedTypeData).loadedTypes; + } + //lookup each loaded type and gets its icon, and embed that string in the type + foreach (var item in loadedTypes) + { + var iconAsBase64 = iconProvider.GetResourceAsString(item.iconUrl, out ext); + if (ext == "svg") + { + ext = "svg+xml"; + } + stringBuilder.Append("data:image/"); + stringBuilder.Append(ext); + stringBuilder.Append(";base64, "); + stringBuilder.Append(iconAsBase64); + item.iconUrl = stringBuilder.ToString(); + stringBuilder.Clear(); + //item.iconUrl = $"data:image/{ext};base64, {iconAsBase64}"; + } + } + serializer.Serialize(sw, data); + + sw.Flush(); + ms.Position = 0; + return ms; + } + + /// + /// Create a LoadedTypeData object for serialization + /// + /// + /// + protected virtual object CreateObjectForSerialization(IEnumerable searchEntries) + { + var data = new LoadedTypeData(); + + // Converting searchEntries to another list so as to avoid modifying the actual searchEntries list when iterating through it. + data.loadedTypes = searchEntries.ToList().Select(e => CreateLoadedTypeItem(e)).ToList(); + return data; + } + + /// Gets fully qualified name for the given node search element + /// + public static string GetFullyQualifiedName(NodeSearchElement element) + { + //If the node search element is part of a package, then we need to prefix pkg:// for it + if (element.ElementType.HasFlag(ElementTypes.Packaged)) + { + //Use FullCategory and name as read from _customization.xml file + return string.Format("{0}{1}.{2}", "pkg://", element.FullCategoryName, element.Name); + } + else if (element.ElementType.HasFlag(ElementTypes.CustomNode)) + { + //Use FullCategory and name as read from _customization.xml file + return string.Format("{0}{1}", "dyf://", element.FullName); + } + return element.FullName; + } + + /// + /// Creates LoadedTypeItem from given node search element + /// + /// + /// + internal T CreateLoadedTypeItem(NodeSearchElement element) where T : LoadedTypeItem, new() + { + var item = new T() + { + fullyQualifiedName = GetFullyQualifiedName(element), + contextData = element.CreationName, + iconUrl = new IconUrl(element.IconName, element.Assembly).Url, + parameters = element.Parameters, + itemType = element.Group.ToString().ToLower(), + description = element.Description, + keywords = element.SearchKeywords.Any() + ? element.SearchKeywords.Where(s => !string.IsNullOrEmpty(s)).Aggregate((x, y) => string.Format("{0}, {1}", x, y)) + : string.Empty + }; + + //If this element is not a custom node then we are done. The icon url for custom node is different + if (!element.ElementType.HasFlag(ElementTypes.CustomNode)) return item; + + var customNode = element as CustomNodeSearchElement; + if (customNode != null) + { + item.iconUrl = new IconUrl(customNode.IconName, customNode.Path, true).Url; + } + + return item; + } + } +} diff --git a/src/LibraryViewExtensionMSWebBrowser/Handlers/ResourceProviderBase.cs b/src/LibraryViewExtensionMSWebBrowser/Handlers/ResourceProviderBase.cs new file mode 100644 index 00000000000..23b5cd89c9e --- /dev/null +++ b/src/LibraryViewExtensionMSWebBrowser/Handlers/ResourceProviderBase.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Dynamo.LibraryViewExtensionMSWebBrowser.Handlers +{ + /// + /// The abstract base class for Resource provider + /// + public abstract class ResourceProviderBase + { + protected ResourceProviderBase(bool isStatic = true, string scheme = "http") + { + IsStaticResource = isStatic; + Scheme = scheme; + } + public bool IsStaticResource { get; private set; } + + public string Scheme { get; private set; } + + public abstract Stream GetResource(string url, out string extension); + } +} diff --git a/src/LibraryViewExtensionMSWebBrowser/Handlers/SearchResultDataProvider.cs b/src/LibraryViewExtensionMSWebBrowser/Handlers/SearchResultDataProvider.cs new file mode 100644 index 00000000000..d734b00661b --- /dev/null +++ b/src/LibraryViewExtensionMSWebBrowser/Handlers/SearchResultDataProvider.cs @@ -0,0 +1,78 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; +using Dynamo.Search; +using Dynamo.Search.SearchElements; + +namespace Dynamo.LibraryViewExtensionMSWebBrowser.Handlers +{ + class LoadedTypeItemExtended : LoadedTypeItem + { + public int weight { get; set; } + public LoadedTypeItemExtended setWeight(int w) + { + this.weight = w; + return this; + } + } + + /// + /// Provides json resource data for all the loaded nodes + /// + class SearchResultDataProvider : NodeItemDataProvider + { + public const string serviceIdentifier = "/nodeSearch"; + private MethodInfo searchMethod = typeof(NodeSearchModel).GetMethod("Search", BindingFlags.Instance|BindingFlags.NonPublic); + + /// + /// Constructor + /// + /// + public SearchResultDataProvider(NodeSearchModel model) : base(model) + { + } + + /// + /// Constructor + /// + /// + public SearchResultDataProvider(NodeSearchModel model, IconResourceProvider iconProvider) : base(model, iconProvider) + { + } + + /// + /// Get the results of a search. + /// + /// + /// + /// + public override Stream GetResource(string searchText, out string extension) + { + + var text = Uri.UnescapeDataString(searchText); + //TODO replace this with direct call. + var elements = searchMethod.Invoke(model,new object[] { text,0 }) as IEnumerable; + + extension = "json"; + return GetNodeItemDataStream(elements, true); + } + + /// + /// Create a LoadedTypeData object for serialization + /// + /// + /// + protected override object CreateObjectForSerialization(IEnumerable searchEntries) + { + int w = 0; //represents the weight + var data = new LoadedTypeData(); + data.loadedTypes = searchEntries + .Select(e => CreateLoadedTypeItem(e).setWeight(w++)).ToList(); + return data; + } + } +} diff --git a/src/LibraryViewExtensionMSWebBrowser/LibraryViewController.cs b/src/LibraryViewExtensionMSWebBrowser/LibraryViewController.cs new file mode 100644 index 00000000000..54007279ec8 --- /dev/null +++ b/src/LibraryViewExtensionMSWebBrowser/LibraryViewController.cs @@ -0,0 +1,597 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Runtime.InteropServices; +using System.Security.Permissions; +using System.Windows; +using System.Windows.Controls; +using Dynamo.Extensions; +using Dynamo.LibraryViewExtensionMSWebBrowser.Handlers; +using Dynamo.LibraryViewExtensionMSWebBrowser.ViewModels; +using Dynamo.LibraryViewExtensionMSWebBrowser.Views; +using Dynamo.Logging; +using Dynamo.Models; +using Dynamo.Search; +using Dynamo.Search.SearchElements; +using Dynamo.ViewModels; +using Dynamo.Wpf.Interfaces; +using Dynamo.Wpf.ViewModels; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace Dynamo.LibraryViewExtensionMSWebBrowser +{ + /// + /// This object is exposed to the browser to pass data back and forth between contexts. + /// + [PermissionSet(SecurityAction.Demand, Name = "FullTrust")] + [ComVisibleAttribute(true)] + public class scriptingObject + { + private LibraryViewController controller; + + public scriptingObject(LibraryViewController controller) + { + this.controller = controller; + } + + /// + /// Main callback which libraryJS uses to notify the extension to perform some action. + /// + /// + public void notify(string data) + { + scriptNotifyHandler(data); + } + + /// + /// Used to get access to the iconResourceProvider and return a base64encoded string version of an icon. + /// + /// + public void GetBase64StringFromPath(string iconurl) + { + + string ext; + var iconAsBase64 = controller.iconProvider.GetResourceAsString(iconurl, out ext); + if (string.IsNullOrEmpty(iconAsBase64)) + { + controller.browser.InvokeScript("completeReplaceImages", ""); + } + if (ext.Contains("svg")) + { + ext = "svg+xml"; + } + //send back result. + controller.browser.InvokeScript("completeReplaceImages", $"data:image/{ext};base64, {iconAsBase64}"); + } + + private void scriptNotifyHandler(string dataFromjs) + { + + if (string.IsNullOrEmpty(dataFromjs)) + { + return; + } + + try + { + //a simple refresh of the libary is requested from js context. + if (dataFromjs == "requestUpdateLibrary") + { + controller.RefreshLibraryView(controller.browser); + return; + } + //a more complex action needs to be taken on the c# side. + /*dataFromjs will be an object like: + + {func:funcName, + data:"string" | data:object[] | bool} + */ + + var simpleRPCPayload = JsonConvert.DeserializeObject>(dataFromjs); + var funcName = simpleRPCPayload["func"] as string; + if (funcName == "createNode") + { + var data = simpleRPCPayload["data"] as string; + controller.CreateNode(data); + } + else if (funcName == "showNodeTooltip") + { + var data = (simpleRPCPayload["data"] as JArray).Children(); + controller.ShowNodeTooltip(data.ElementAt(0).Value(), data.ElementAt(1).Value()); + } + else if (funcName == "closeNodeTooltip") + { + var data = (bool)simpleRPCPayload["data"]; + controller.CloseNodeTooltip(data); + } + else if (funcName == "importLibrary") + { + controller.ImportLibrary(); + } + else if (funcName == "logEventsToInstrumentation") + { + var data = (simpleRPCPayload["data"] as JArray).Children(); + controller.LogEventsToInstrumentation(data.ElementAt(0).Value(), data.ElementAt(1).Value()); + } + else if (funcName == "performSearch") + { + var data = simpleRPCPayload["data"] as string; + var extension = string.Empty; + var searchStream = controller.searchResultDataProvider.GetResource(data, out extension); + var searchReader = new StreamReader(searchStream); + var results = searchReader.ReadToEnd(); + //send back results to libjs + controller.browser. + InvokeScript("completeSearch", results); + searchReader.Dispose(); + } + } + catch (Exception e) + { + this.controller.LogToDynamoConsole($"Error while parsing command data from javascript{Environment.NewLine}{e.Message}"); + } + } + } + + /// + /// This class acts as the entry point to create the browser view as well + /// as implementing the methods which are called from the javascript context. + /// + public class LibraryViewController : IDisposable + { + private Window dynamoWindow; + private ICommandExecutive commandExecutive; + private DynamoViewModel dynamoViewModel; + private FloatingLibraryTooltipPopup libraryViewTooltip; + // private ResourceHandlerFactory resourceFactory; + private IDisposable observer; + internal WebBrowser browser; + private const string CreateNodeInstrumentationString = "Search-NodeAdded"; + // TODO remove this when we can control the library state from Dynamo more precisely. + private bool disableObserver = false; + + private LayoutSpecProvider layoutProvider; + private NodeItemDataProvider nodeProvider; + internal SearchResultDataProvider searchResultDataProvider; + internal IconResourceProvider iconProvider; + private LibraryViewCustomization customization; + private scriptingObject interop; + + /// + /// Creates a LibraryViewController. + /// + /// DynamoView hosting library component + /// Command executive to run dynamo commands + internal LibraryViewController(Window dynamoView, ICommandExecutive commandExecutive, LibraryViewCustomization customization) + { + this.dynamoWindow = dynamoView; + dynamoViewModel = dynamoView.DataContext as DynamoViewModel; + this.customization = customization; + libraryViewTooltip = CreateTooltipControl(); + + this.commandExecutive = commandExecutive; + InitializeResourceProviders(dynamoViewModel.Model, customization); + dynamoWindow.StateChanged += DynamoWindowStateChanged; + dynamoWindow.SizeChanged += DynamoWindow_SizeChanged; + interop = new scriptingObject(this); + } + + //if the window is resized toggle visibility of browser to force redraw + private void DynamoWindow_SizeChanged(object sender, SizeChangedEventArgs e) + { + if (browser != null) + { + browser.InvalidateVisual(); + } + } + + //if the dynamo window is minimized and then restored, force a layout update. + private void DynamoWindowStateChanged(object sender, EventArgs e) + { + if (browser != null) + { + browser.InvalidateVisual(); + } + } + + #region methods exposed to JS + /// + /// Call this method to create a new node in Dynamo canvas. + /// + /// Node creation name + public void CreateNode(string nodeName) + { + dynamoWindow.Dispatcher.BeginInvoke(new Action(() => + { + //if the node we're trying to create is a customNode, lets disable the eventObserver. + // this will stop the libraryController from refreshing the libraryView on custom node creation. + var resultGuid = Guid.Empty; + if (Guid.TryParse(nodeName, out resultGuid)) + { + this.disableObserver = true; + } + //Create the node of given item name + var cmd = new DynamoModel.CreateNodeCommand(Guid.NewGuid().ToString(), nodeName, -1, -1, true, false); + commandExecutive.ExecuteCommand(cmd, Guid.NewGuid().ToString(), LibraryViewExtensionMSWebBrowser.ExtensionName); + LogEventsToInstrumentation(CreateNodeInstrumentationString, nodeName); + + this.disableObserver = false; + })); + } + + /// Call this method to import a zero touch library. It will prompt + /// user to select the zero touch dll. + /// + public void ImportLibrary() + { + dynamoWindow.Dispatcher.BeginInvoke(new Action(() => + dynamoViewModel.ImportLibraryCommand.Execute(null) + )); + } + /// + /// This function logs events to instrumentation if it matches a set of known events + /// + /// Event Name that gets logged to instrumentation + /// Data that gets logged to instrumentation + public void LogEventsToInstrumentation(string eventName, string data) + { + if (eventName == "Search" || eventName == "Filter-Categories" || eventName == "Search-NodeAdded") + { + Analytics.LogPiiInfo(eventName, data); + } + } + internal void RefreshLibraryView(WebBrowser browser) + { + dynamoWindow.Dispatcher.BeginInvoke( + new Action(() => + { + + var ext1 = string.Empty; + var ext2 = string.Empty; + //read the full loadedTypes and LayoutSpec json strings. + var reader = new StreamReader(nodeProvider.GetResource(null, out ext1)); + var loadedTypes = reader.ReadToEnd(); + + var reader2 = new StreamReader(layoutProvider.GetResource(null, out ext2)); + var layoutSpec = reader2.ReadToEnd(); + + try + { + browser.InvokeScript + ("refreshLibViewFromData", loadedTypes, layoutSpec); + } + catch (Exception e) + { + this.dynamoViewModel.Model.Logger.Log(e); + } + reader.Dispose(); + reader2.Dispose(); + + })); + + } + + #endregion + + private string ReplaceUrlWithBase64Image(string html, string minifiedURL, bool magicreplace = true) + { + var ext = string.Empty; + // this depends on librariejs minification producing the same results - + // longterm this is fragile. We should intercept these requests and handle them instead. + if (magicreplace) + { + minifiedURL = $"n.p+\"{minifiedURL}\""; + } + var searchString = minifiedURL.Replace("n.p+", @"./dist").Replace("\"", ""); + var base64 = iconProvider.GetResourceAsString(searchString, out ext); + //replace some urls to svg icons with base64 data. + if (ext == "svg") + { + ext = "svg+xml"; + base64 = $"data:image/{ext};base64, {base64}"; + } + + + if (ext == "ttf" || ext == "woff" || ext == "woff2" || ext == "eot") + { + + base64 = $"data:application/x-font-{ext};charset=utf-8;base64,{base64}"; + } + + html = html.Replace(minifiedURL, '"' + base64 + '"'); + return html; + } + + //list of resources which have paths embedded directly into the source. + private readonly Tuple[] dynamicResourcePaths = new Tuple[15] + { + Tuple.Create("/resources/library-create.svg",true), + Tuple.Create("/resources/default-icon.svg",true), + Tuple.Create("/resources/fontawesome-webfont.eot",true), + Tuple.Create("/resources/fontawesome-webfont.ttf",true), + Tuple.Create("/resources/fontawesome-webfont.woff2",true), + Tuple.Create("/resources/fontawesome-webfont.woff",true), + Tuple.Create("/resources/library-action.svg",true), + Tuple.Create("/resources/library-query.svg",true), + Tuple.Create("/resources/indent-arrow-down-wo-lines.svg",true), + Tuple.Create("/resources/indent-arrow-down.svg",true), + Tuple.Create("/resources/indent-arrow-right-last.svg",true), + Tuple.Create("/resources/indent-arrow-right-wo-lines.svg",true), + Tuple.Create("/resources/indent-arrow-right.svg",true), + Tuple.Create("/resources/ArtifaktElement-Bold.woff",true), + Tuple.Create("/resources/ArtifaktElement-Regular.woff",true) + }; + + /// + /// Creates and adds the library view to the WPF visual tree. + /// Also loads the library.html and js files. + /// + /// LibraryView control + internal void AddLibraryView() + { + LibraryViewModel model = new LibraryViewModel(); + LibraryView view = new LibraryView(model); + + var lib_min_template = "LIBPLACEHOLDER"; + var libHTMLURI = "Dynamo.LibraryViewExtensionMSWebBrowser.web.library.library.html"; + var stream = LoadResource(libHTMLURI); + + var libMinURI = "Dynamo.LibraryViewExtensionMSWebBrowser.web.library.librarie.min.js"; + var libminstream = LoadResource(libMinURI); + var libminstring = "LIBJS"; + var libraryHTMLPage = "LIBRARY HTML WAS NOT FOUND"; + + + using (var reader = new StreamReader(libminstream)) + { + libminstring = reader.ReadToEnd(); + // replace some resources and their paths so that no requests are generated + // instead the base64 data is already embedded. This list includes common icons and fonts. + dynamicResourcePaths.ToList().ForEach(path => + { + libminstring = ReplaceUrlWithBase64Image(libminstring, path.Item1, path.Item2); + }); + + } + + using (var reader = new StreamReader(stream)) + { + libraryHTMLPage = reader.ReadToEnd().Replace(lib_min_template, libminstring); + } + + var sidebarGrid = dynamoWindow.FindName("sidebarGrid") as Grid; + sidebarGrid.Children.Add(view); + //register the interop object into the browser. + view.Browser.ObjectForScripting = interop; + //open the library ui page. + view.Browser.NavigateToString(libraryHTMLPage); + + browser = view.Browser; + browser.Loaded += Browser_Loaded; + browser.SizeChanged += Browser_SizeChanged; + LibraryViewController.SetupSearchModelEventsObserver(browser, dynamoViewModel.Model.SearchModel, this, this.customization); + } + + + + private void Browser_Loaded(object sender, RoutedEventArgs e) + { + string msg = "Browser Loaded"; + LogToDynamoConsole(msg); + } + + //if the browser window itself is resized, toggle visibility to force redraw. + private void Browser_SizeChanged(object sender, SizeChangedEventArgs e) + { + if (browser != null) + { + browser.InvalidateVisual(); + } + } + + #region Tooltip + + /// + /// Call this method to create a new node in Dynamo canvas. + /// + /// Node creation name + /// The y position + public void ShowNodeTooltip(string nodeName, double y) + { + dynamoWindow.Dispatcher.BeginInvoke(new Action(() => + { + ShowTooltip(nodeName, y); + })); + } + + /// + /// Call this method to create a new node in Dynamo canvas. + /// + public void CloseNodeTooltip(bool closeImmediately) + { + dynamoWindow.Dispatcher.BeginInvoke(new Action(() => + { + CloseTooltip(closeImmediately); + })); + } + + private FloatingLibraryTooltipPopup CreateTooltipControl() + { + var sidebarGrid = dynamoWindow.FindName("sidebarGrid") as Grid; + + var tooltipPopup = new FloatingLibraryTooltipPopup(200) + { + Name = "libraryToolTipPopup", + StaysOpen = true, + AllowsTransparency = true, + PlacementTarget = sidebarGrid + }; + sidebarGrid.Children.Add(tooltipPopup); + + return tooltipPopup; + } + + private NodeSearchElementViewModel FindTooltipContext(String nodeName) + { + return dynamoViewModel.CurrentSpaceViewModel.InCanvasSearchViewModel.FindViewModelForNode(nodeName); + } + + private void ShowTooltip(String nodeName, double y) + { + var nseViewModel = FindTooltipContext(nodeName); + if (nseViewModel == null) + { + return; + } + + libraryViewTooltip.UpdateYPosition(y); + libraryViewTooltip.SetDataContext(nseViewModel); + } + + private void CloseTooltip(bool closeImmediately) + { + libraryViewTooltip.SetDataContext(null, closeImmediately); + } + + #endregion + + private Stream LoadResource(string url) + { + var assembly = Assembly.GetExecutingAssembly(); + var textStream = assembly.GetManifestResourceStream(url); + return textStream; + } + + internal static IDisposable SetupSearchModelEventsObserver(WebBrowser webview, NodeSearchModel model, LibraryViewController controller, ILibraryViewCustomization customization, int throttleTime = 200) + { + customization.SpecificationUpdated += (o, e) => + { + controller.RefreshLibraryView(webview); + controller.CloseNodeTooltip(true); + }; + + + var observer = new EventObserver>( + elements => NotifySearchModelUpdate(customization, elements), CollectToList + ).Throttle(TimeSpan.FromMilliseconds(throttleTime)); + + Action handler = (searchElement) => + { + var libraryViewController = (controller as LibraryViewController); + if ((libraryViewController != null) && (libraryViewController.disableObserver)) + { + return; + } + + observer.OnEvent(searchElement); + }; + Action onRemove = e => handler(null); + + //Set up the event callback + model.EntryAdded += handler; + model.EntryRemoved += onRemove; + model.EntryUpdated += handler; + + //Set up the dispose event handler + observer.Disposed += () => + { + model.EntryAdded -= handler; + model.EntryRemoved -= onRemove; + model.EntryUpdated -= handler; + }; + + return observer; + } + + /// + /// Returns a new list by adding the given element to the given list + /// + /// + /// This old list of elements + /// The element to add to the list + /// The new list containing all items from input list and the given element + private static IEnumerable CollectToList(IEnumerable list, T element) + { + //Accumulate all non-null element into a list. + if (list == null) list = Enumerable.Empty(); + return element != null ? list.Concat(Enumerable.Repeat(element, 1)) : list; + } + + /// + /// Notifies the SearchModel update event with list of updated elements + /// + /// ILibraryViewCustomization to update the + /// specification and raise specification changed event. + /// List of updated elements + private static void NotifySearchModelUpdate(ILibraryViewCustomization customization, IEnumerable elements) + { + //elements might be null if we have removed an element. + if (elements != null) + { + var includes = elements + .Select(NodeItemDataProvider.GetFullyQualifiedName) + .Select(name => name.Split('.').First()) + .Distinct() + .SkipWhile(s => s.Contains("://")) + .Select(p => new LayoutIncludeInfo() { path = p }); + + customization.AddIncludeInfo(includes, "Add-ons"); + + } + } + + /// + /// creates the resource providers that are used throughout the extensions lifetime + /// to retrieve images and other resource files from disk. + /// + /// + /// + private void InitializeResourceProviders(DynamoModel model, LibraryViewCustomization customization) + { + var dllProvider = new DllResourceProvider("http://localhost/dist", "Dynamo.LibraryViewExtensionMSWebBrowser.web.library"); + iconProvider = new IconResourceProvider(model.PathManager, dllProvider, customization); + nodeProvider = new NodeItemDataProvider(model.SearchModel, iconProvider); + searchResultDataProvider = new SearchResultDataProvider(model.SearchModel, iconProvider); + layoutProvider = new LayoutSpecProvider(customization, iconProvider, "Dynamo.LibraryViewExtensionMSWebBrowser.web.library.layoutSpecs.json"); + } + + /// + /// Convenience method for logging to Dynamo Console. + /// + /// + internal void LogToDynamoConsole(string message) + { + this.dynamoViewModel.Model.Logger.Log(message); + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + protected void Dispose(bool disposing) + { + if (!disposing) return; + + if (observer != null) observer.Dispose(); + observer = null; + if (this.dynamoWindow != null) + { + dynamoWindow.StateChanged -= DynamoWindowStateChanged; + dynamoWindow.SizeChanged -= DynamoWindow_SizeChanged; + dynamoWindow = null; + } + if (this.browser != null) + { + browser.SizeChanged -= Browser_SizeChanged; + browser.Loaded -= Browser_Loaded; + browser.Dispose(); + browser = null; + } + } + } +} diff --git a/src/LibraryViewExtensionMSWebBrowser/LibraryViewCustomization.cs b/src/LibraryViewExtensionMSWebBrowser/LibraryViewCustomization.cs new file mode 100644 index 00000000000..788a568f14d --- /dev/null +++ b/src/LibraryViewExtensionMSWebBrowser/LibraryViewCustomization.cs @@ -0,0 +1,264 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Dynamo.Wpf.Interfaces; +using Dynamo.LibraryViewExtensionMSWebBrowser.Handlers; +using Newtonsoft.Json; + +namespace Dynamo.LibraryViewExtensionMSWebBrowser +{ + class LibraryViewCustomization : ILibraryViewCustomization, IDisposable + { + public const string DefaultSectionName = "default"; + private LayoutSpecification root = new LayoutSpecification(); + private Dictionary resources = new Dictionary(); + + /// + /// Returns a list of key value pair of all the registered resources + /// + public IEnumerable> Resources { get { return resources; } } + + private void RaiseSepcificationUpdated() + { + var handler = SpecificationUpdated; + if (handler != null) handler(this, EventArgs.Empty); + } + + /// + /// This event is raised when the specification is updated + /// + public event EventHandler SpecificationUpdated; + + /// + /// Allows clients to add a new section to the layout specification. This + /// operation will fail if the current specification already contains a + /// section with the same text as of the given section. The clients must + /// call AddElements method to add layout elements to a given section. + /// + public bool AddSections(IEnumerable sections) + { + var spec = GetSpecification(); + spec.sections.AddRange(sections); + var count = spec.sections.Count; + if (spec.sections.GroupBy(s => s.text).Count() < count) return false; //some duplicate sections exist + + //Update the specification + SetSpecification(spec); + return true; + } + + /// + /// Allows clients to add a list of elements to a given section. This + /// operation will fail if the text property of any of the child elements + /// of the given section matches with the text property of the given + /// elements. + /// + public bool AddElements(IEnumerable elements, string sectionText = "") + { + return UpdateSpecification(elements, s => s.childElements, e => e.text, sectionText); + } + + /// + /// Allows clients to add a list of include path to a given section. This + /// operation will fail if the path property of any of the includes + /// of the given section conflicts with the path property of the given + /// includes. + /// + public bool AddIncludeInfo(IEnumerable includes, string sectionText = "") + { + return UpdateSpecification(includes, s => s.include, info => info.path, sectionText); + } + + /// + /// Updates the specification with given list of items. + /// + /// Type of items LayoutElement or LayoutIncludeInfo + /// List of items to add + /// Function to get the list of a given section to update + /// Function to select key for the given item to identify it uniquely. + /// Name of the section where the given items will be added + /// + private bool UpdateSpecification(IEnumerable items, Func> getlist, Func keyselector, string sectionText) + { + var text = DefaultSectionName; + if (!string.IsNullOrEmpty(sectionText)) + { + text = sectionText; + } + var spec = GetSpecification(); + var section = spec.sections.FirstOrDefault(x => string.Equals(x.text, text)); + if (section == null) + { + section = new LayoutSection(text); + spec.sections.Add(section); + } + + //obtain the list to update + var list = getlist(section); + list.AddRange(items); //add items to the list + int count = list.Count; + var groups = list.GroupBy(keyselector).ToList(); + if (groups.Count < count) + { + var duplicates = string.Join(", ", groups.Where(g => g.Count() > 1).Select(g => g.Key)); + throw new InvalidOperationException(string.Format("Duplicate entries found, ex: {0}", duplicates)); + } + + //Update the specification + SetSpecification(spec); + return true; + } + + /// + /// Gets a copy of the current library layout specification + /// + public LayoutSpecification GetSpecification() + { + return root.Clone(); //returns clone + } + + /// + /// Sets the given specification as the current library layout + /// specification by overwriting the current one. + /// + public void SetSpecification(LayoutSpecification specification) + { + root = specification; + RaiseSepcificationUpdated(); + } + + /// + /// Serializes the current specification to JSON stream + /// + public Stream ToJSONStream() + { + return root.ToJSONStream(); + } + + /// + /// Serializes the current specification to JSON stream and optionally replaces icon urls with images as base64 data + /// + public Stream ToJSONStream(bool replaceIconURLWithData, IconResourceProvider iconResourceProvider = null) + { + return ToJSONStream(root, replaceIconURLWithData, iconResourceProvider); + } + + + /// + /// Notifies when a resource stream is registered. This event is + /// used by ResourceHandlerFactory to register the resource handler + /// for provided url path. + /// + public event Action ResourceStreamRegistered; + + /// + /// Registers a given resource stream for a given url. If registered + /// successfully the requested resource is returned from the given stream. + /// + /// relative path of url including + /// extension of the resource, e.g. /myicons/xicon.png + /// resource data stream + /// True if the operation was successful + public bool RegisterResourceStream(string urlpath, Stream resource) + { + var extension = Path.GetExtension(urlpath); + if (string.IsNullOrEmpty(extension)) return false; + + resources.Add(urlpath, resource); + + var handler = ResourceStreamRegistered; + if (handler != null) + { + handler(urlpath, resource); + } + return true; + } + + /// + /// To be called by host application to request CEF shutdown + /// + public void OnAppShutdown() + { + + } + + /// + /// IDisposable implementation + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + private void Dispose(bool disposing) + { + if (disposing) + { + foreach (var item in resources) + { + item.Value.Dispose(); + } + resources.Clear(); + } + } + + //layoutSpec helpers + /// + /// Serializes the layout specification to json stream and + /// optionally replaces icon urls with image data as base64 encoded strings + /// + /// + private Stream ToJSONStream(LayoutSpecification spec, bool replaceIconURLWithData, IconResourceProvider iconProvider = null) + { + var ms = new MemoryStream(); + var sw = new StreamWriter(ms); + if (replaceIconURLWithData && iconProvider != null) + { + //foreach section update all nested children and nested includes, and root iconurls to image data. + spec.sections.ForEach(section => { + section.iconUrl = getBase64ImageString(section, iconProvider); + section.EnumerateChildren().ToList().ForEach(child => child.iconUrl = getBase64ImageString(child, iconProvider)); + section.EnumerateIncludes().ToList().ForEach(child => child.iconUrl = getBase64ImageString(child, iconProvider)); + }); + } + + var serializer = new JsonSerializer() { Formatting = Formatting.Indented, NullValueHandling = NullValueHandling.Ignore }; + serializer.Serialize(sw, spec); + + sw.Flush(); + ms.Position = 0; + return ms; + } + + private string getBase64ImageString(LayoutElement item, IconResourceProvider iconProvider) + { + var ext = string.Empty; + var iconAsBase64 = iconProvider.GetResourceAsString(item.iconUrl, out ext); + if (string.IsNullOrEmpty(iconAsBase64)) + { + return ""; + } + if (ext.Contains("svg")) + { + ext = "svg+xml"; + } + return $"data:image/{ext};base64, {iconAsBase64}"; + } + private string getBase64ImageString(LayoutIncludeInfo item, IconResourceProvider iconProvider) + { + var ext = string.Empty; + var iconAsBase64 = iconProvider.GetResourceAsString(item.iconUrl, out ext); + if (string.IsNullOrEmpty(iconAsBase64)) + { + return ""; + } + if (ext.Contains("svg")) + { + ext = "svg+xml"; + } + return $"data:image/{ext};base64, {iconAsBase64}"; + } + } +} diff --git a/src/LibraryViewExtensionMSWebBrowser/LibraryViewExtensionMSWebBrowser.csproj b/src/LibraryViewExtensionMSWebBrowser/LibraryViewExtensionMSWebBrowser.csproj new file mode 100644 index 00000000000..d0b1be1e1f0 --- /dev/null +++ b/src/LibraryViewExtensionMSWebBrowser/LibraryViewExtensionMSWebBrowser.csproj @@ -0,0 +1,323 @@ + + + + + Debug + AnyCPU + {1A5DC90A-477E-4D4A-87BD-0BFB01F056CE} + Library + Properties + Dynamo.LibraryViewExtensionMSWebBrowser + LibraryViewExtensionMSWebBrowser + v4.7 + + false + 512 + false + + + + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\..\extern\prism\Microsoft.Practices.Prism.dll + False + + + ..\packages\Newtonsoft.Json.8.0.3\lib\net45\Newtonsoft.Json.dll + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + LibraryView.xaml + + + + + {51BB6014-43F7-4F31-B8D3-E3C37EBEDAF4} + DynamoCoreWpf + False + + + {7858FA8C-475F-4B8E-B468-1F8200778CF8} + DynamoCore + False + + + {EF879A10-041D-4C68-83E7-3192685F1BAE} + DynamoServices + False + + + + + Designer + MSBuild:Compile + + + + + + + + + + + + web\library\resources\ArtifaktElement-Bold.woff + + + web\library\resources\ArtifaktElement-Regular.woff + + + web\library\resources\fontawesome-webfont.eot + + + web\library\resources\fontawesome-webfont.ttf + + + web\library\resources\fontawesome-webfont.woff + + + web\library\resources\fontawesome-webfont.woff2 + + + + + web\library\resources\Autodesk.DesignScript.Geometry.Arc.png + + + web\library\resources\Autodesk.DesignScript.Geometry.BoundingBox.png + + + web\library\resources\Autodesk.DesignScript.Geometry.Circle.png + + + web\library\resources\Autodesk.DesignScript.Geometry.Cone.png + + + web\library\resources\Autodesk.DesignScript.Geometry.CoordinateSystem.png + + + web\library\resources\Autodesk.DesignScript.Geometry.Cuboid.png + + + web\library\resources\Autodesk.DesignScript.Geometry.Curve.png + + + web\library\resources\Autodesk.DesignScript.Geometry.Cylinder.png + + + web\library\resources\Autodesk.DesignScript.Geometry.Edge.png + + + web\library\resources\Autodesk.DesignScript.Geometry.Ellipse.png + + + web\library\resources\Autodesk.DesignScript.Geometry.EllipseArc.png + + + web\library\resources\Autodesk.DesignScript.Geometry.Face.png + + + web\library\resources\Autodesk.DesignScript.Geometry.Helix.png + + + web\library\resources\Autodesk.DesignScript.Geometry.IndexGroup.png + + + web\library\resources\Autodesk.DesignScript.Geometry.Line.png + + + web\library\resources\Autodesk.DesignScript.Geometry.Mesh.png + + + web\library\resources\Autodesk.DesignScript.Geometry.NurbsCurve.png + + + web\library\resources\Autodesk.DesignScript.Geometry.NurbsSurface.png + + + web\library\resources\Autodesk.DesignScript.Geometry.Plane.png + + + web\library\resources\Autodesk.DesignScript.Geometry.Point.png + + + web\library\resources\Autodesk.DesignScript.Geometry.PolyCurve.png + + + web\library\resources\Autodesk.DesignScript.Geometry.Polygon.png + + + web\library\resources\Autodesk.DesignScript.Geometry.PolySurface.png + + + web\library\resources\Autodesk.DesignScript.Geometry.Rectangle.png + + + web\library\resources\Autodesk.DesignScript.Geometry.Solid.png + + + web\library\resources\Autodesk.DesignScript.Geometry.Sphere.png + + + web\library\resources\Autodesk.DesignScript.Geometry.Surface.png + + + web\library\resources\Autodesk.DesignScript.Geometry.Topology.png + + + web\library\resources\Autodesk.DesignScript.Geometry.UV.png + + + web\library\resources\Autodesk.DesignScript.Geometry.Vector.png + + + web\library\resources\Autodesk.DesignScript.Geometry.Vertex.png + + + web\library\resources\Category.Dictionary.svg + + + web\library\resources\Category.Display.svg + + + web\library\resources\Category.Geometry.svg + + + web\library\resources\Category.ImportExport.svg + + + web\library\resources\Category.Input.svg + + + web\library\resources\Category.List.svg + + + web\library\resources\Category.Math.svg + + + web\library\resources\Category.Revit.svg + + + web\library\resources\Category.Script.svg + + + web\library\resources\Category.String.svg + + + web\library\resources\default-icon.svg + + + web\library\resources\DSCore.DayOfWeek.png + + + web\library\resources\Dynamo.svg + + + web\library\resources\fontawesome-webfont.svg + + + web\library\resources\indent-arrow-down-without-line.svg + + + web\library\resources\indent-arrow-down-wo-lines.svg + + + web\library\resources\indent-arrow-down.svg + + + web\library\resources\indent-arrow-right-last.svg + + + web\library\resources\indent-arrow-right-wo-lines.svg + + + web\library\resources\indent-arrow-right.svg + + + web\library\resources\library-action.svg + + + web\library\resources\library-create.svg + + + web\library\resources\library-query.svg + + + web\library\resources\plus-symbol.svg + + + web\library\resources\Tessellation.ConvexHull.png + + + web\library\resources\Tessellation.Delaunay.png + + + web\library\resources\Tessellation.Voronoi.png + + + + + Always + + + + + + + + + + + \ No newline at end of file diff --git a/src/LibraryViewExtensionMSWebBrowser/LibraryViewToolTip.cs b/src/LibraryViewExtensionMSWebBrowser/LibraryViewToolTip.cs new file mode 100644 index 00000000000..92336a92c7c --- /dev/null +++ b/src/LibraryViewExtensionMSWebBrowser/LibraryViewToolTip.cs @@ -0,0 +1,51 @@ +using System.Windows; +using System.Windows.Controls.Primitives; +using Dynamo.UI.Controls; + +namespace Dynamo.LibraryViewExtensionMSWebBrowser +{ + public class FloatingLibraryTooltipPopup : LibraryToolTipPopup + { + private double overridenTargetY = 0; + private CustomPopupPlacementCallback basePlacementCallback = null; + + public FloatingLibraryTooltipPopup(double y) + { + overridenTargetY = y; + + // Store the old callback for getting the placement from the base class. + // As such, later in the callback in this class, it is only needed to adjust the + // placement. + basePlacementCallback = CustomPopupPlacementCallback; + CustomPopupPlacementCallback = PlacementCallback; + } + + /// + /// Set the y position for the tooltip popup which will override the y position returned + /// from the placement callback from the base class. + /// + /// + internal void UpdateYPosition(double y) + { + overridenTargetY = y; + } + + /// + /// The callback method which will return the placement for the popup. + /// This will get the placement by calling the callback method from the base class and + /// then update the y postion. + /// + /// + /// + /// + /// + private CustomPopupPlacement[] PlacementCallback(Size popup, Size target, Point offset) + { + var placements = basePlacementCallback(popup, target, offset); + var point = placements[0].Point; + point.Y = overridenTargetY; + placements[0].Point = point; + return placements; + } + } +} diff --git a/src/LibraryViewExtensionMSWebBrowser/Library_ViewExtensionDefinition.xml b/src/LibraryViewExtensionMSWebBrowser/Library_ViewExtensionDefinition.xml new file mode 100644 index 00000000000..98e9591a3a0 --- /dev/null +++ b/src/LibraryViewExtensionMSWebBrowser/Library_ViewExtensionDefinition.xml @@ -0,0 +1,4 @@ + + librarymsWebBrowser\LibraryViewExtensionMSWebBrowser.dll + Dynamo.LibraryViewExtensionMSWebBrowser.LibraryViewExtensionMSWebBrowser + \ No newline at end of file diff --git a/src/LibraryViewExtensionMSWebBrowser/Properties/AssemblyInfo.cs b/src/LibraryViewExtensionMSWebBrowser/Properties/AssemblyInfo.cs new file mode 100644 index 00000000000..641ac776495 --- /dev/null +++ b/src/LibraryViewExtensionMSWebBrowser/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("LibraryViewExtensionMSWebBrowser")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("LibraryViewExtensionMSWebBrowser")] +[assembly: AssemblyCopyright("Copyright © 2019")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("1a5dc90a-477e-4d4a-87bd-0bfb01f056ce")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.*")] +//[assembly: AssemblyFileVersion("1.0.*")] diff --git a/src/LibraryViewExtensionMSWebBrowser/ViewExtension.cs b/src/LibraryViewExtensionMSWebBrowser/ViewExtension.cs new file mode 100644 index 00000000000..481fdef00c9 --- /dev/null +++ b/src/LibraryViewExtensionMSWebBrowser/ViewExtension.cs @@ -0,0 +1,100 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using Dynamo.Models; +using Dynamo.ViewModels; +using Dynamo.Wpf.Extensions; +using Dynamo.Wpf.Interfaces; + +namespace Dynamo.LibraryViewExtensionMSWebBrowser +{ + /// + /// This extension duplicates many of the types in the CEF based LibraryViewExtension + /// but is based on MSWebBrowser control from system.windows to avoid conflicts with CEF and CEFSharp. + /// + public class LibraryViewExtensionMSWebBrowser : IViewExtension + { + private ViewLoadedParams viewLoadedParams; + private ViewStartupParams viewStartupParams; + private LibraryViewCustomization customization = new LibraryViewCustomization(); + private LibraryViewController controller; + + public string UniqueId + { + get { return "63cd0755-4a36-4670-ae89-b68e772633c4"; } + } + + public static readonly string ExtensionName = "LibraryUI2"; + + public string Name + { + get { return ExtensionName; } + } + + public void Startup(ViewStartupParams p) + { + viewStartupParams = p; + p.ExtensionManager.RegisterService(customization); + } + + public void Loaded(ViewLoadedParams p) + { + if (!DynamoModel.IsTestMode) + { + viewLoadedParams = p; + controller = new LibraryViewController(p.DynamoWindow, p.CommandExecutive, customization); + controller.AddLibraryView(); + (viewLoadedParams.DynamoWindow.DataContext as DynamoViewModel).PropertyChanged += handleDynamoViewPropertyChanges; + } + + } + + //hide browser directly when startpage is shown to deal with air space problem. + //https://github.com/dotnet/wpf/issues/152 + private void handleDynamoViewPropertyChanges(object sender, System.ComponentModel.PropertyChangedEventArgs e) + { + DynamoViewModel senderDVM = sender as DynamoViewModel; + + if (senderDVM!= null && e.PropertyName == nameof(senderDVM.ShowStartPage)) + { + var sp = senderDVM.ShowStartPage; + var vis = sp == true ? Visibility.Hidden : Visibility.Visible; + if(controller.browser != null) + { + controller.browser.Visibility = vis; + } + } + } + + public void Shutdown() + { + Dispose(); + } + + public void Dispose() + { + Dispose(true); + System.GC.SuppressFinalize(this); + } + + protected void Dispose(bool disposing) + { + if (!disposing) return; + + if (controller != null) controller.Dispose(); + if (customization != null) customization.Dispose(); + if(viewLoadedParams != null && viewLoadedParams.DynamoWindow.DataContext as DynamoViewModel != null) + { + (viewLoadedParams.DynamoWindow.DataContext as DynamoViewModel).PropertyChanged -= handleDynamoViewPropertyChanges; + } + + + customization = null; + controller = null; + } + + } +} diff --git a/src/LibraryViewExtensionMSWebBrowser/ViewModels/LibraryViewModel.cs b/src/LibraryViewExtensionMSWebBrowser/ViewModels/LibraryViewModel.cs new file mode 100644 index 00000000000..01d171ec7d1 --- /dev/null +++ b/src/LibraryViewExtensionMSWebBrowser/ViewModels/LibraryViewModel.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using Dynamo.Core; + +namespace Dynamo.LibraryViewExtensionMSWebBrowser.ViewModels +{ + /// + /// Library View Loader + /// + public class LibraryViewModel : NotificationObject + { + /// + /// Checks and updates the visibility property + /// + public bool IsVisible + { + get { return Visibility == Visibility.Visible; } + set { Visibility = value ? Visibility.Visible : Visibility.Collapsed; } + } + + /// + /// Controls visibilty of the view + /// + public Visibility Visibility + { + get { return visibility; } + set + { + visibility = value; + RaisePropertyChanged("Visibility"); + } + } + + private Visibility visibility = Visibility.Visible; + } +} diff --git a/src/LibraryViewExtensionMSWebBrowser/Views/LibraryView.xaml b/src/LibraryViewExtensionMSWebBrowser/Views/LibraryView.xaml new file mode 100644 index 00000000000..2f571a579e4 --- /dev/null +++ b/src/LibraryViewExtensionMSWebBrowser/Views/LibraryView.xaml @@ -0,0 +1,13 @@ + + + + + diff --git a/src/LibraryViewExtensionMSWebBrowser/Views/LibraryView.xaml.cs b/src/LibraryViewExtensionMSWebBrowser/Views/LibraryView.xaml.cs new file mode 100644 index 00000000000..7fd0739ddf4 --- /dev/null +++ b/src/LibraryViewExtensionMSWebBrowser/Views/LibraryView.xaml.cs @@ -0,0 +1,20 @@ + +using System.Windows.Controls; + +using Dynamo.LibraryViewExtensionMSWebBrowser.ViewModels; + +namespace Dynamo.LibraryViewExtensionMSWebBrowser.Views +{ + /// + /// Interaction logic for LibraryView.xaml + /// + public partial class LibraryView : UserControl + { + public LibraryView(LibraryViewModel viewModel) + { + this.DataContext = viewModel; + InitializeComponent(); + + } + } +} diff --git a/src/LibraryViewExtensionMSWebBrowser/packages.config b/src/LibraryViewExtensionMSWebBrowser/packages.config new file mode 100644 index 00000000000..6920beab476 --- /dev/null +++ b/src/LibraryViewExtensionMSWebBrowser/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/src/LibraryViewExtensionMSWebBrowser/readme.md b/src/LibraryViewExtensionMSWebBrowser/readme.md new file mode 100644 index 00000000000..2f00fa6e712 --- /dev/null +++ b/src/LibraryViewExtensionMSWebBrowser/readme.md @@ -0,0 +1,30 @@ +## LibraryViewExtension(MSWebBrowser) + +### what is this: + A version of the LibraryViewExtension which displays the Librarie.JS component (Dynamo node library) using the System.Windows.Controls.WebBrowser control. This uses the Trident rendering engine. + +### why is this: + Some applications which integrate Dynamo have inflexible dependencies on CEF or CEFSharp of various versions. As an alternative solution to falling back to the older Dynamo 1.x WPF based library this extension gives users a similar experience and leverages the same librarie.js codebase with minimal changes. + +### Architecture vs CEFSharp. + +* unlike in CEFSharp there are no resourceHandlers registered which the browser control will call to resolve local requests. Instead all local resources are either resolved by calling c# methods directly or by embedding them as base64 or js/html strings into the html passed to the browser. +* similar to CEFSharp - an `ObjectForScripting` is registered with the browser which lets js code call c# methods. +* to allow data to be passed back and forth everything is serialized to strings. If data needs to move between c# to js - then the c# side must call `InvokeScript` and pass the data as an argument - this means that sometimes function calls are split into two halves. ie - The js side calls some c# method `firstHalf()` - c# context computes some result and then `invokeScript(("secondHalf", resultData)` which returns the result into the js context by calling a second function. Local variables are saved between the function calls on the js side by declaring them outside of either function scope. + +### modifications necessary to librarie.js + +* compiled with an import for `core-js` +* cache of originalUrl on the HTMLImage elements when they fail to load and .src is set to DefaultIcon. +* the version of librarie.min.js embedded into this repo has these two changes. (https://github.com/DynamoDS/librarie.js/pull/133) + +### known issues + +* shrink use of `core-js` to just replace the required polyfills or remove completely and use a standard polyfill if we only need to replace a few functions. +* no tests. +* pull duplicated classes out into libraryExtensionCore. +* minimal manual testing compared LibraryViewExtension which has been in production for 1.5 years. + +### how to use this: + +copy the extension manifest and bin directory of this project to the `Dynamo/viewExtensions/` folder of Dynamo. The manifest expects the extension binary to live in `./librarymsWebBrowser/LibraryViewExtensionMSWebBrowser.dll`. \ No newline at end of file diff --git a/src/LibraryViewExtensionMSWebBrowser/web/library/layoutSpecs.json b/src/LibraryViewExtensionMSWebBrowser/web/library/layoutSpecs.json new file mode 100644 index 00000000000..040253bacd7 --- /dev/null +++ b/src/LibraryViewExtensionMSWebBrowser/web/library/layoutSpecs.json @@ -0,0 +1,1401 @@ +{ + "sections": [ + { + "text": "default", + "iconUrl": "", + "elementType": "section", + "showHeader": false, + "include": [ ], + "childElements": [ + { + "text": "List", + "iconUrl": "./dist/resources/Category.List.svg", + "elementType": "category", + "include": [ ], + "childElements": [ + { + "text": "Inspect", + "iconUrl": "", + "elementType": "group", + "include": [ + { + "path": "DSCoreNodes.DSCore.List.AllFalse" + }, + { + "path": "DSCoreNodes.DSCore.List.AllTrue" + }, + { + "path": "DSCoreNodes.DSCore.List.SetIntersection" + }, + { + "path": "DSCoreNodes.DSCore.List.SetUnion" + }, + { + "path": "DSCoreNodes.DSCore.List.SetDifference" + }, + { + "path": "DSCoreNodes.DSCore.List.Contains" + }, + { + "path": "BuiltIn.List.Rank" + }, + { + "path": "DSCoreNodes.DSCore.List.IsHomogeneous" + }, + { + "path": "DSCoreNodes.DSCore.List.IsUniformDepth" + }, + { + "path": "DSCoreNodes.DSCore.List.IsRectangular" + }, + { + "path": "BuiltIn.List.TrueForAll" + }, + { + "path": "BuiltIn.List.TrueForAny" + }, + { + "path": "DSCoreNodes.DSCore.List.IsEmpty" + }, + { + "path": "DSCoreNodes.DSCore.List.IndexOf" + }, + { + "path": "DSCoreNodes.DSCore.List.FirstIndexOf" + }, + { + "path": "BuiltIn.List.Equals" + }, + { + "path": "DSCoreNodes.DSCore.List.CountTrue" + }, + { + "path": "DSCoreNodes.DSCore.List.CountFalse" + }, + { + "path": "DSCoreNodes.DSCore.List.MinimumItem" + }, + { + "path": "DSCoreNodes.DSCore.List.MaximumItem" + }, + { + "path": "DSCoreNodes.DSCore.List.DiagonalRight" + }, + { + "path": "DSCoreNodes.DSCore.List.DiagonalLeft" + }, + { + "path": "DSCoreNodes.DSCore.List.FirstItem" + }, + { + "path": "DSCoreNodes.DSCore.List.GetItemAtIndex" + }, + { + "path": "DSCoreNodes.DSCore.List.LastItem" + }, + { + "path": "DSCoreNodes.DSCore.List.AllIndicesOf" + }, + { + "path": "DSCoreNodes.DSCore.List.Count" + }, + { + "path": "DSCoreNodes.DSCore.List.UniqueItems" + } + ], + "childElements": [ ] + }, + { + "text": "Modify", + "iconUrl": "", + "elementType": "group", + "include": [ + { + "path": "BuiltIn.List.RemoveIfNot" + }, + { + "path": "Core.List.List.Reduce" + }, + { + "path": "Core.List.List.Scan" + }, + { + "path": "DSCoreNodes.DSCore.List.RemoveItemAtIndex" + }, + { + "path": "DSCoreNodes.DSCore.List.RestOfItems" + }, + { + "path": "DSCoreNodes.DSCore.List.Flatten" + }, + { + "path": "DSCoreNodes.DSCore.List.Deconstruct" + }, + { + "path": "DSCoreNodes.DSCore.List.Sublists" + }, + { + "path": "DSCoreNodes.DSCore.List.Chop" + }, + { + "path": "DSCoreNodes.DSCore.List.Slice" + }, + { + "path": "DSCoreNodes.DSCore.List.TakeItems" + }, + { + "path": "DSCoreNodes.DSCore.List.Clean" + }, + { + "path": "DSCoreNodes.DSCore.List.DropEveryNthItem" + }, + { + "path": "DSCoreNodes.DSCore.List.DropItems" + }, + { + "path": "Core.List.List.Filter" + }, + { + "path": "DSCoreNodes.DSCore.List.FilterByBoolMask" + }, + { + "path": "DSCoreNodes.DSCore.List.TakeEveryNthItem" + }, + { + "path": "Core.List.ReplaceByCondition" + }, + { + "path": "DSCoreNodes.DSCore.List.ReplaceItemAtIndex" + }, + { + "path": "DSCoreNodes.DSCore.List.Insert" + }, + { + "path": "DSCoreNodes.DSCore.List.AddItemToEnd" + }, + { + "path": "DSCoreNodes.DSCore.List.AddItemToFront" + } + ], + "childElements": [ ] + }, + { + "text": "Organize", + "iconUrl": "", + "elementType": "group", + "include": [ + { + "path": "DSCoreNodes.DSCore.List.GroupByKey" + }, + { + "path": "BuiltIn.List.GroupByFunction" + }, + { + "path": "BuiltIn.List.MaximumItemByKey" + }, + { + "path": "BuiltIn.List.MinimumItemByKey" + }, + { + "path": "DSCoreNodes.DSCore.List.NormalizeDepth" + }, + { + "path": "DSCoreNodes.DSCore.List.Reverse" + }, + { + "path": "DSCoreNodes.DSCore.List.Reorder" + }, + { + "path": "BuiltIn.List.SortByFunction" + }, + { + "path": "DSCoreNodes.DSCore.List.SortByKey" + }, + { + "path": "DSCoreNodes.DSCore.List.SortIndexByValue" + }, + { + "path": "DSCoreNodes.DSCore.List.ShiftIndices" + }, + { + "path": "DSCoreNodes.DSCore.List.Shuffle" + }, + { + "path": "DSCoreNodes.DSCore.List.Sort" + }, + { + "path": "DSCoreNodes.DSCore.List.Transpose" + } + ], + "childElements": [ ] + }, + { + "text": "Generate", + "iconUrl": "", + "elementType": "group", + "include": [ + { + "path": "Core.List.List Create" + }, + { + "path": "DSCoreNodes.DSCore.List.Join" + }, + { + "path": "Core.List.Range" + }, + { + "path": "Core.List.Sequence" + }, + { + "path": "DSCoreNodes.DSCore.List.Combinations" + }, + { + "path": "DSCoreNodes.DSCore.List.Permutations" + }, + { + "path": "DSCoreNodes.DSCore.List.Empty" + }, + { + "path": "DSCoreNodes.DSCore.List.OfRepeatedItem" + }, + { + "path": "DSCoreNodes.DSCore.List.Cycle" + } + ], + "childElements": [ ] + }, + { + "text": "Match", + "iconUrl": "", + "elementType": "group", + "include": [ + { + "path": "Core.List.List.LaceShortest" + }, + { + "path": "Core.List.List.LaceLongest" + }, + { + "path": "Core.List.List.CartesianProduct" + }, + { + "path": "Core.List.List.Map" + }, + { + "path": "Core.List.List.Combine" + } + ], + "childElements": [ ] + } + ] + }, + { + "text": "Dictionary", + "iconUrl": "./dist/resources/Category.Dictionary.svg", + "elementType": "category", + "include": [ + { + "path": "DesignScriptBuiltin.DesignScript.Builtin.Dictionary.ByKeysValues" + }, + { + "path": "DesignScriptBuiltin.DesignScript.Builtin.Dictionary.Components" + }, + { + "path": "DesignScriptBuiltin.DesignScript.Builtin.Dictionary.RemoveKeys" + }, + { + "path": "DesignScriptBuiltin.DesignScript.Builtin.Dictionary.SetValueAtKeys" + }, + { + "path": "DesignScriptBuiltin.DesignScript.Builtin.Dictionary.ValueAtKey" + }, + { + "path": "DesignScriptBuiltin.DesignScript.Builtin.Dictionary.Count" + }, + { + "path": "DesignScriptBuiltin.DesignScript.Builtin.Dictionary.Keys" + }, + { + "path": "DesignScriptBuiltin.DesignScript.Builtin.Dictionary.Values" + } + ], + "childElements": [ ] + }, + { + "text": "Input", + "iconUrl": "./dist/resources/Category.Input.svg", + "elementType": "category", + "include": [ ], + "childElements": [ + { + "text": "DateTime", + "iconUrl": "", + "elementType": "group", + "include": [ + { + "path": "Core.Input.Date Time" + }, + { + "path": "DSCoreNodes.DSCore.DateTime.MinValue" + }, + { + "path": "DSCoreNodes.DSCore.DateTime.MaxValue" + }, + { + "path": "DSCoreNodes.DSCore.DateTime.Now" + }, + { + "path": "DSCoreNodes.DSCore.DateTime.Today" + }, + { + "path": "DSCoreNodes.DSCore.DateTime.ByDate" + }, + { + "path": "DSCoreNodes.DSCore.DateTime.ByDateAndTime" + }, + { + "path": "DSCoreNodes.DSCore.DateTime.SubtractTimeSpan" + }, + { + "path": "DSCoreNodes.DSCore.DateTime.AddTimeSpan" + }, + { + "path": "DSCoreNodes.DSCore.DateTime.DaysInMonth" + }, + { + "path": "DSCoreNodes.DSCore.DateTime.IsDaylightSavingsTime" + }, + { + "path": "DSCoreNodes.DSCore.DateTime.IsLeapYear" + }, + { + "path": "DSCoreNodes.DSCore.DateTime.FromString" + }, + { + "path": "DSCoreNodes.DSCore.DateTime.Date" + }, + { + "path": "DSCoreNodes.DSCore.DateTime.Components" + }, + { + "path": "DSCoreNodes.DSCore.DateTime.DayOfWeek" + }, + { + "path": "DSCoreNodes.DSCore.DateTime.DayOfYear" + }, + { + "path": "DSCoreNodes.DSCore.DateTime.TimeOfDay" + }, + { + "path": "DSCoreNodes.DSCore.DateTime.Format" + } + ], + "childElements": [ ] + }, + { + "text": "Location", + "iconUrl": "", + "elementType": "group", + "include": [ + { + "path": "DynamoUnits.DynamoUnits.Location.Name" + }, + { + "path": "DynamoUnits.DynamoUnits.Location.Latitude" + }, + { + "path": "DynamoUnits.DynamoUnits.Location.Longitude" + }, + { + "path": "DynamoUnits.DynamoUnits.Location.ByLatitudeAndLongitude" + } + ], + "childElements": [ ] + }, + { + "text": "Basic", + "iconUrl": "", + "elementType": "group", + "include": [ + { + "path": "Core.Input.Boolean" + }, + { + "path": "Core.Input.Integer Slider" + }, + { + "path": "Core.Input.Number Slider" + }, + { + "path": "Core.Input.String" + }, + { + "path": "Core.Input.Number" + }, + { + "path": "Core.Input.Input" + }, + { + "path": "Core.Input.Output" + } + ], + "childElements": [ ] + }, + { + "text": "TimeSpan", + "iconUrl": "", + "elementType": "group", + "include": [ + { + "path": "DSCoreNodes.DSCore.TimeSpan.ByDateDifference" + }, + { + "path": "DSCoreNodes.DSCore.TimeSpan.Zero" + }, + { + "path": "DSCoreNodes.DSCore.TimeSpan.MaxValue" + }, + { + "path": "DSCoreNodes.DSCore.TimeSpan.MinValue" + }, + { + "path": "DSCoreNodes.DSCore.TimeSpan.Create" + }, + { + "path": "DSCoreNodes.DSCore.TimeSpan.Scale" + }, + { + "path": "DSCoreNodes.DSCore.TimeSpan.Negate" + }, + { + "path": "DSCoreNodes.DSCore.TimeSpan.Add" + }, + { + "path": "DSCoreNodes.DSCore.TimeSpan.Subtract" + }, + { + "path": "DSCoreNodes.DSCore.TimeSpan.FromString" + }, + { + "path": "DSCoreNodes.DSCore.TimeSpan.Components" + }, + { + "path": "DSCoreNodes.DSCore.TimeSpan.TotalDays" + }, + { + "path": "DSCoreNodes.DSCore.TimeSpan.TotalHours" + }, + { + "path": "DSCoreNodes.DSCore.TimeSpan.TotalMinutes" + }, + { + "path": "DSCoreNodes.DSCore.TimeSpan.TotalSeconds" + }, + { + "path": "DSCoreNodes.DSCore.TimeSpan.TotalMilliseconds" + } + ], + "childElements": [ ] + }, + { + "text": "Object", + "iconUrl": "", + "elementType": "group", + "include": [ + { + "path": "DSCoreNodes.DSCore.Object.IsNull" + }, + { + "path": "DSCoreNodes.DSCore.Object.Identity" + }, + { + "path": "DSCoreNodes.DSCore.Object.Type" + } + ], + "childElements": [ ] + } + ] + }, + { + "text": "Math", + "iconUrl": "./dist/resources/Category.Math.svg", + "elementType": "category", + "include": [ ], + "childElements": [ + { + "text": "Functions", + "iconUrl": "", + "elementType": "group", + "include": [ + { + "path": "DSCoreNodes.DSCore.Math.Random" + }, + { + "path": "DSCoreNodes.DSCore.Math.RandomList" + }, + { + "path": "DSCoreNodes.DSCore.Math.PiTimes2" + }, + { + "path": "DSCoreNodes.DSCore.Math.Average" + }, + { + "path": "DSCoreNodes.DSCore.Math.RemapRange" + }, + { + "path": "DSCoreNodes.DSCore.Math.PI" + }, + { + "path": "DSCoreNodes.DSCore.Math.E" + }, + { + "path": "DSCoreNodes.DSCore.Math.GoldenRatio" + }, + { + "path": "DSCoreNodes.DSCore.Math.Abs" + }, + { + "path": "DSCoreNodes.DSCore.Math.Acos" + }, + { + "path": "DSCoreNodes.DSCore.Math.Asin" + }, + { + "path": "DSCoreNodes.DSCore.Math.Atan" + }, + { + "path": "DSCoreNodes.DSCore.Math.Atan2" + }, + { + "path": "DSCoreNodes.DSCore.Math.Ceiling" + }, + { + "path": "DSCoreNodes.DSCore.Math.Cos" + }, + { + "path": "DSCoreNodes.DSCore.Math.Cosh" + }, + { + "path": "DSCoreNodes.DSCore.Math.DivRem" + }, + { + "path": "DSCoreNodes.DSCore.Math.Exp" + }, + { + "path": "DSCoreNodes.DSCore.Math.Floor" + }, + { + "path": "DSCoreNodes.DSCore.Math.Log" + }, + { + "path": "DSCoreNodes.DSCore.Math.Log10" + }, + { + "path": "DSCoreNodes.DSCore.Math.Max" + }, + { + "path": "DSCoreNodes.DSCore.Math.Min" + }, + { + "path": "DSCoreNodes.DSCore.Math.Pow" + }, + { + "path": "DSCoreNodes.DSCore.Math.Rand" + }, + { + "path": "DSCoreNodes.DSCore.Math.Round" + }, + { + "path": "DSCoreNodes.DSCore.Math.Sign" + }, + { + "path": "DSCoreNodes.DSCore.Math.Sin" + }, + { + "path": "DSCoreNodes.DSCore.Math.Sinh" + }, + { + "path": "DSCoreNodes.DSCore.Math.Sqrt" + }, + { + "path": "DSCoreNodes.DSCore.Math.Tan" + }, + { + "path": "DSCoreNodes.DSCore.Math.Tanh" + }, + { + "path": "DSCoreNodes.DSCore.Math.Sum" + }, + { + "path": "DSCoreNodes.DSCore.Math.Factorial" + }, + { + "path": "DSCoreNodes.DSCore.Math.Map" + }, + { + "path": "DSCoreNodes.DSCore.Math.MapTo" + }, + { + "path": "Core.Scripting.Formula" + }, + { + "path": "DSCoreNodes.DSCore.Math.EvaluateFormula" + } + ], + "childElements": [ ] + }, + { + "text": "Units", + "iconUrl": "", + "elementType": "group", + "include": [ + { + "path": "Core.Units.Convert Between Units" + }, + { + "path": "Core.Units.Number From Feet and Inches" + }, + { + "path": "Core.Units.Unit Types" + }, + { + "path": "DSCoreNodes.DSCore.Math.RadiansToDegrees" + }, + { + "path": "DSCoreNodes.DSCore.Math.DegreesToRadians" + } + ], + "childElements": [ ] + }, + { + "text": "Operators", + "iconUrl": "./dist/resources/Category.Operators.svg", + "elementType": "group", + "include": [ + { + "path": "Operators.+" + }, + { + "path": "Operators.-" + }, + { + "path": "Operators.*" + }, + { + "path": "Operators./" + }, + { + "path": "Operators.==" + }, + { + "path": "Core.Math.==" + }, + { + "path": "Operators.>=" + }, + { + "path": "Operators.>" + }, + { + "path": "Operators.%" + }, + { + "path": "Operators.<=" + }, + { + "path": "Operators.<" + }, + { + "path": "Operators.&&" + }, + { + "path": "Operators.||" + }, + { + "path": "Operators.!=" + }, + { + "path": "Operators.Not" + } + ], + "childElements": [ ] + }, + { + "text": "Logic", + "iconUrl": "", + "elementType": "group", + "include": [ + { + "path": "Core.Math.And" + }, + { + "path": "Core.Math.Or" + }, + { + "path": "DSCoreNodes.DSCore.Math.Xor" + } + ], + "childElements": [ ] + } + ] + }, + { + "text": "Script", + "iconUrl": "./dist/resources/Category.Script.svg", + "elementType": "category", + "include": [ ], + "childElements": [ + { + "text": "Evaluate", + "iconUrl": "", + "elementType": "group", + "include": [ + { + "path": "Core.Evaluate.Function Apply" + }, + { + "path": "Core.Evaluate.Function Compose" + } + ], + "childElements": [ ] + }, + { + "text": "Control Flow", + "iconUrl": "", + "elementType": "group", + "include": [ + { + "path": "BuiltIn.LoopWhile" + }, + { + "path": "Core.Logic.ScopeIf" + }, + { + "path": "Core.Logic.If" + }, + { + "path": "DSCoreNodes.DSCore.Thread.Pause" + } + ], + "childElements": [ ] + }, + { + "text": "Editor", + "iconUrl": "", + "elementType": "group", + "include": [ + { + "path": "Core.Scripting.Python Script" + }, + { + "path": "Core.Scripting.Python Script From String" + }, + { + "path": "Core.Input.Code Block" + } + ], + "childElements": [ ] + } + ] + }, + { + "text": "Display", + "iconUrl": "./dist/resources/Category.Display.svg", + "elementType": "category", + "include": [], + "childElements": [ + { + "text": "Color", + "iconUrl": "", + "elementType": "group", + "include": [ + { + "path": "DSCoreNodes.DSCore.Color.Red" + }, + { + "path": "DSCoreNodes.DSCore.Color.Green" + }, + { + "path": "DSCoreNodes.DSCore.Color.Blue" + }, + { + "path": "DSCoreNodes.DSCore.Color.Alpha" + }, + { + "path": "DSCoreNodes.DSCore.Color.ByARGB" + }, + { + "path": "DSCoreNodes.DSCore.Color.Brightness" + }, + { + "path": "DSCoreNodes.DSCore.Color.Saturation" + }, + { + "path": "DSCoreNodes.DSCore.Color.Hue" + }, + { + "path": "DSCoreNodes.DSCore.Color.Components" + }, + { + "path": "DSCoreNodes.DSCore.Color.Add" + }, + { + "path": "DSCoreNodes.DSCore.Color.Multiply" + }, + { + "path": "DSCoreNodes.DSCore.Color.Divide" + }, + { + "path": "Core.Color.Color Palette" + } + ], + "childElements": [ ] + }, + { + "text": "Color Range", + "iconUrl": "", + "elementType": "group", + "include": [ + { + "path": "Core.Color.Color Range" + }, + { + "path": "DSCoreNodes.DSCore.ColorRange.ByColorsAndParameters" + }, + { + "path": "DSCoreNodes.DSCore.ColorRange.GetColorAtParameter" + } + ], + "childElements": [ ] + }, + { + "text": "Watch", + "iconUrl": "", + "elementType": "group", + "include": [ + { + "path": "Core.View.Watch" + }, + { + "path": "Core.View.Watch Image" + }, + { + "path": "Core.View.Watch 3D" + } + ], + "childElements": [ ] + } + ] + }, + { + "text": "ImportExport", + "iconUrl": "./dist/resources/Category.ImportExport.svg", + "elementType": "category", + "include": [ ], + "childElements": [ + { + "text": "CAD", + "iconUrl": "", + "elementType": "group", + "include": [ + { + "path": "Dynamo.Translation.CAD.Import" + }, + { + "path": "Dynamo.Translation.CAD.Export" + }, + { + "path": "Dynamo.Translation.CAD.ConvertToGeometries" + }, + { + "path": "Dynamo.Translation.Models.Filter CAD Objects" + }, + { + "path": "Dynamo.Translation.Models.Select CAD Objects" + } + ], + "childElements": [ ] + }, + { + "text": "File System", + "iconUrl": "", + "elementType": "group", + "include": [ + { + "path": "Core.Input.File Path" + }, + { + "path": "Core.Input.Directory Path" + }, + { + "path": "Core.File.File From Path" + }, + { + "path": "Core.File.Directory From Path" + }, + { + "path": "DSCoreNodes.DSCore.IO.FileSystem.CombinePath" + }, + { + "path": "DSCoreNodes.DSCore.IO.FileSystem.FileExtension" + }, + { + "path": "DSCoreNodes.DSCore.IO.FileSystem.ChangePathExtension" + }, + { + "path": "DSCoreNodes.DSCore.IO.FileSystem.DirectoryName" + }, + { + "path": "DSCoreNodes.DSCore.IO.FileSystem.FileName" + }, + { + "path": "DSCoreNodes.DSCore.IO.FileSystem.FileHasExtension" + }, + { + "path": "DSCoreNodes.DSCore.IO.FileSystem.ReadText" + }, + { + "path": "DSCoreNodes.DSCore.IO.FileSystem.MoveFile" + }, + { + "path": "DSCoreNodes.DSCore.IO.FileSystem.DeleteFile" + }, + { + "path": "DSCoreNodes.DSCore.IO.FileSystem.CopyFile" + }, + { + "path": "DSCoreNodes.DSCore.IO.FileSystem.FileExists" + }, + { + "path": "DSCoreNodes.DSCore.IO.FileSystem.WriteText" + }, + { + "path": "DSCoreNodes.DSCore.IO.FileSystem.AppendText" + }, + { + "path": "DSCoreNodes.DSCore.IO.FileSystem.MoveDirectory" + }, + { + "path": "DSCoreNodes.DSCore.IO.FileSystem.CopyDirectory" + }, + { + "path": "DSCoreNodes.DSCore.IO.FileSystem.DeleteDirectory" + }, + { + "path": "DSCoreNodes.DSCore.IO.FileSystem.GetDirectoryContents" + }, + { + "path": "DSCoreNodes.DSCore.IO.FileSystem.DirectoryExists" + } + ], + "childElements": [ ] + }, + { + "text": "Image", + "iconUrl": "", + "elementType": "group", + "include": [ + { + "path": "DSCoreNodes.DSCore.IO.Image.ReadFromFile" + }, + { + "path": "DSCoreNodes.DSCore.IO.Image.Pixels" + }, + { + "path": "DSCoreNodes.DSCore.IO.Image.FromPixels" + }, + { + "path": "DSCoreNodes.DSCore.IO.Image.Dimensions" + }, + { + "path": "DSCoreNodes.DSCore.IO.Image.WriteToFile" + } + ], + "childElements": [ ] + }, + { + "text": "Data", + "iconUrl": "", + "elementType": "group", + "include": [ + { + "path": "DSOffice.DSOffice.Data.ImportExcel" + }, + { + "path": "DSOffice.DSOffice.Data.ExportExcel" + }, + { + "path": "DSOffice.DSOffice.Data.ImportCSV" + }, + { + "path": "DSOffice.DSOffice.Data.ExportCSV" + }, + { + "path": "DSCoreNodes.DSCore.Data.ParseJSON" + }, + { + "path": "DSCoreNodes.DSCore.Data.StringifyJSON" + } + ], + "childElements": [ ] + }, + { + "text": "Web", + "iconUrl": "", + "elementType": "group", + "include": [ + { + "path": "Core.Web.Web Request" + } + ], + "childElements": [ ] + } + ] + }, + { + "text": "String", + "iconUrl": "./dist/resources/Category.String.svg", + "elementType": "category", + "include": [ ], + "childElements": [ + { + "text": "Inspect", + "iconUrl": "", + "elementType": "group", + "include": [ + { + "path": "DSCoreNodes.DSCore.String.Contains" + }, + { + "path": "DSCoreNodes.DSCore.String.CountOccurrences" + }, + { + "path": "DSCoreNodes.DSCore.String.Length" + }, + { + "path": "DSCoreNodes.DSCore.String.IndexOf" + }, + { + "path": "DSCoreNodes.DSCore.String.AllIndicesOf" + }, + { + "path": "DSCoreNodes.DSCore.String.LastIndexOf" + }, + { + "path": "DSCoreNodes.DSCore.String.EndsWith" + }, + { + "path": "DSCoreNodes.DSCore.String.StartsWith" + } + ], + "childElements": [ ] + }, + { + "text": "Modify", + "iconUrl": "", + "elementType": "group", + "include": [ + { + "path": "DSCoreNodes.DSCore.String.TrimWhitespace" + }, + { + "path": "DSCoreNodes.DSCore.String.TrimLeadingWhitespace" + }, + { + "path": "DSCoreNodes.DSCore.String.TrimTrailingWhitespace" + }, + { + "path": "DSCoreNodes.DSCore.String.Replace" + }, + { + "path": "DSCoreNodes.DSCore.String.PadLeft" + }, + { + "path": "DSCoreNodes.DSCore.String.PadRight" + }, + { + "path": "DSCoreNodes.DSCore.String.Center" + }, + { + "path": "DSCoreNodes.DSCore.String.Insert" + }, + { + "path": "DSCoreNodes.DSCore.String.Remove" + }, + { + "path": "DSCoreNodes.DSCore.String.Substring" + }, + { + "path": "DSCoreNodes.DSCore.String.Split" + }, + { + "path": "DSCoreNodes.DSCore.String.ToUpper" + }, + { + "path": "DSCoreNodes.DSCore.String.ToLower" + }, + { + "path": "DSCoreNodes.DSCore.String.ToNumber" + }, + { + "path": "DSCoreNodes.DSCore.String.ChangeCase" + } + ], + "childElements": [ ] + }, + { + "text": "Generate", + "iconUrl": "", + "elementType": "group", + "include": [ + { + "path": "Core.String.String from Object" + }, + { + "path": "Core.String.String from Array" + }, + { + "path": "DSCoreNodes.DSCore.String.Concat" + }, + { + "path": "DSCoreNodes.DSCore.String.Join" + } + ], + "childElements": [ ] + } + ] + }, + { + "text": "Geometry", + "iconUrl": "./dist/resources/Category.Geometry.svg", + "elementType": "category", + "include": [ ], + "childElements": [ + { + "text": "Abstract", + "iconUrl": "", + "elementType": "group", + "include": [ + { + "path": "ProtoGeometry.Autodesk.DesignScript.Geometry.BoundingBox" + }, + { + "path": "ProtoGeometry.Autodesk.DesignScript.Geometry.CoordinateSystem" + }, + { + "path": "ProtoGeometry.Autodesk.DesignScript.Geometry.Vector" + }, + { + "path": "ProtoGeometry.Autodesk.DesignScript.Geometry.Edge" + }, + { + "path": "ProtoGeometry.Autodesk.DesignScript.Geometry.Face" + }, + { + "path": "ProtoGeometry.Autodesk.DesignScript.Geometry.Plane" + }, + { + "path": "ProtoGeometry.Autodesk.DesignScript.Geometry.Vertex" + }, + { + "path": "ProtoGeometry.Autodesk.DesignScript.Geometry.Topology" + } + ], + "childElements": [ ] + }, + { + "text": "Points", + "iconUrl": "", + "elementType": "group", + "include": [ + { + "path": "ProtoGeometry.Autodesk.DesignScript.Geometry.Point" + }, + { + "path": "ProtoGeometry.Autodesk.DesignScript.Geometry.UV" + } + ], + "childElements": [ ] + }, + { + "text": "Curves", + "iconUrl": "", + "elementType": "group", + "include": [ + { + "path": "ProtoGeometry.Autodesk.DesignScript.Geometry.Arc" + }, + { + "path": "ProtoGeometry.Autodesk.DesignScript.Geometry.Circle" + }, + { + "path": "ProtoGeometry.Autodesk.DesignScript.Geometry.Curve" + }, + { + "path": "ProtoGeometry.Autodesk.DesignScript.Geometry.Ellipse" + }, + { + "path": "ProtoGeometry.Autodesk.DesignScript.Geometry.EllipseArc" + }, + { + "path": "ProtoGeometry.Autodesk.DesignScript.Geometry.Helix" + }, + { + "path": "ProtoGeometry.Autodesk.DesignScript.Geometry.Line" + }, + { + "path": "ProtoGeometry.Autodesk.DesignScript.Geometry.NurbsCurve" + }, + { + "path": "ProtoGeometry.Autodesk.DesignScript.Geometry.PolyCurve" + }, + { + "path": "ProtoGeometry.Autodesk.DesignScript.Geometry.Polygon" + }, + { + "path": "ProtoGeometry.Autodesk.DesignScript.Geometry.Rectangle" + } + ], + "childElements": [ ] + }, + { + "text": "Surfaces", + "iconUrl": "", + "elementType": "group", + "include": [ + { + "path": "ProtoGeometry.Autodesk.DesignScript.Geometry.NurbsSurface" + }, + { + "path": "ProtoGeometry.Autodesk.DesignScript.Geometry.PolySurface" + }, + { + "path": "ProtoGeometry.Autodesk.DesignScript.Geometry.Surface" + } + ], + "childElements": [ ] + }, + { + "text": "Solids", + "iconUrl": "", + "elementType": "group", + "include": [ + { + "path": "ProtoGeometry.Autodesk.DesignScript.Geometry.Cone" + }, + { + "path": "ProtoGeometry.Autodesk.DesignScript.Geometry.Cuboid" + }, + { + "path": "ProtoGeometry.Autodesk.DesignScript.Geometry.Cylinder" + }, + { + "path": "ProtoGeometry.Autodesk.DesignScript.Geometry.Solid" + }, + { + "path": "ProtoGeometry.Autodesk.DesignScript.Geometry.Sphere" + } + ], + "childElements": [ ] + }, + { + "text": "Meshes", + "iconUrl": "", + "elementType": "group", + "include": [ + { + "path": "ProtoGeometry.Autodesk.DesignScript.Geometry.IndexGroup" + }, + { + "path": "ProtoGeometry.Autodesk.DesignScript.Geometry.Mesh" + } + ], + "childElements": [ ] + }, + { + "text": "Modifiers", + "iconUrl": "", + "elementType": "group", + "include": [ ], + "childElements": [ + { + "text": "Geometry", + "iconUrl": "", + "elementType": "group", + "include": [ + { + "path": "ProtoGeometry.Autodesk.DesignScript.Geometry.Geometry", + "inclusive": false + }, + { + "path": "Geometry.Geometry.ExportToSAT" + } + ], + "childElements": [ ] + }, + { + "text": "Geometry Color", + "iconUrl": "", + "elementType": "group", + "include": [ + { + "path": "GeometryColor.Modifiers.GeometryColor.ByGeometryColor" + }, + { + "path": "GeometryColor.Modifiers.GeometryColor.BySurfaceColors" + } + ], + "childElements": [ ] + } + ] + }, + { + "text": "Tessellation", + "iconUrl": "", + "elementType": "group", + "include": [ + { + "path": "Tessellation.Tessellation.ConvexHull" + }, + { + "path": "Tessellation.Tessellation.Delaunay" + }, + { + "path": "Tessellation.Tessellation.Voronoi" + } + ], + "childElements": [ ] + }, + { + "text": "TSpline", + "iconUrl": "", + "elementType": "group", + "include": [ + { + "path": "ProtoGeometry.Autodesk.DesignScript.Geometry.TSpline", + "inclusive": false + } + ], + "childElements": [ ] + } + ] + } + ] + }, + { + "text": "Add-ons", + "iconUrl": "./dist/resources/plus-symbol.svg", + "elementType": "section", + "showHeader": true, + "include": [ + { + "path": "pkg://" + }, + { + "path": "dyf://" + } + ], + "childElements": [ ] + } + ] +} \ No newline at end of file diff --git a/src/LibraryViewExtensionMSWebBrowser/web/library/librarie.min.js b/src/LibraryViewExtensionMSWebBrowser/web/library/librarie.min.js new file mode 100644 index 00000000000..80fae0870a9 --- /dev/null +++ b/src/LibraryViewExtensionMSWebBrowser/web/library/librarie.min.js @@ -0,0 +1,21 @@ +!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.LibraryEntryPoint=t():e.LibraryEntryPoint=t()}(this,function(){return function(e){function t(r){if(n[r])return n[r].exports;var o=n[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,t),o.l=!0,o.exports}var n={};return t.m=e,t.c=n,t.i=function(e){return e},t.d=function(e,n,r){t.o(e,n)||Object.defineProperty(e,n,{configurable:!1,enumerable:!0,get:r})},t.n=function(e){var n=e&&e.__esModule?function(){return e.default}:function(){return e};return t.d(n,"a",n),n},t.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},t.p="./dist",t(t.s=746)}([function(e,t,n){var r=n(5),o=n(28).f,i=n(20),a=n(29),s=n(147),c=n(187),u=n(88);e.exports=function(e,t){var n,l,f,p,d,h=e.target,v=e.global,g=e.stat;if(n=v?r:g?r[h]||s(h,{}):(r[h]||{}).prototype)for(l in t){if(p=t[l],e.noTargetGet?(d=o(n,l),f=d&&d.value):f=n[l],!u(v?l:h+(g?".":"#")+l,e.forced)&&void 0!==f){if(typeof p==typeof f)continue;c(p,f)}(e.sham||f&&f.sham)&&i(p,"sham",!0),a(n,l,p,e)}}},function(e,t,n){var r=n(9);e.exports=function(e){if(!r(e))throw TypeError(String(e)+" is not an object");return e}},function(e,t){e.exports=function(e){try{return!!e()}catch(e){return!0}}},function(e,t){e.exports=function(e){if("function"!=typeof e)throw TypeError(String(e)+" is not a function");return e}},function(e,t){e.exports=!1},function(e,t,n){var r=function(e){return e&&e.Math==Math&&e};e.exports=r("object"==typeof globalThis&&globalThis)||r("object"==typeof window&&window)||r("object"==typeof self&&self)||r(n.i({}))||Function("return this")()},function(e,t,n){var r=n(1),o=n(136),i=n(12),a=n(22),s=n(54),c=n(82),u=function(e,t){this.stopped=e,this.result=t};(e.exports=function(e,t,n,l,f){var p,d,h,v,g,m,y,b=a(t,n,l?2:1);if(f)p=e;else{if("function"!=typeof(d=s(e)))throw TypeError("Target is not iterable");if(o(d)){for(h=0,v=i(e.length);v>h;h++)if((g=l?b(r(y=e[h])[0],y[1]):b(e[h]))&&g instanceof u)return g;return new u(!1)}p=d.call(e)}for(m=p.next;!(y=m.call(p)).done;)if("object"==typeof(g=c(p,b,y.value,l))&&g&&g instanceof u)return g;return new u(!1)}).stop=function(e){return new u(!0,e)}},function(e,t,n){var r=n(5),o=n(118),i=n(18),a=n(93),s=n(140),c=n(216),u=o("wks"),l=r.Symbol,f=c?l:a;e.exports=function(e){return i(u,e)||(s&&i(l,e)?u[e]=l[e]:u[e]=f("Symbol."+e)),u[e]}},function(e,t,n){"use strict";function r(e,t,n,r,i,a,s,c){if(o(t),!e){var u;if(void 0===t)u=new Error("Minified exception occurred; use the non-minified dev environment for the full error message and additional helpful warnings.");else{var l=[n,r,i,a,s,c],f=0;u=new Error(t.replace(/%s/g,function(){return l[f++]})),u.name="Invariant Violation"}throw u.framesToPop=1,u}}var o=function(e){};e.exports=r},function(e,t){e.exports=function(e){return"object"==typeof e?null!==e:"function"==typeof e}},function(e,t,n){var r=n(2);e.exports=!r(function(){return 7!=Object.defineProperty({},"a",{get:function(){return 7}}).a})},function(e,t,n){"use strict";var r,o=n(10),i=n(5),a=n(9),s=n(18),c=n(83),u=n(20),l=n(29),f=n(13).f,p=n(30),d=n(63),h=n(7),v=n(93),g=i.DataView,m=g&&g.prototype,y=i.Int8Array,b=y&&y.prototype,x=i.Uint8ClampedArray,w=x&&x.prototype,C=y&&p(y),E=b&&p(b),S=Object.prototype,F=S.isPrototypeOf,I=h("toStringTag"),_=v("TYPED_ARRAY_TAG"),k=!(!i.ArrayBuffer||!g),A=k&&!!d&&"Opera"!==c(i.opera),T=!1,M={Int8Array:1,Uint8Array:1,Uint8ClampedArray:1,Int16Array:2,Uint16Array:2,Int32Array:4,Uint32Array:4,Float32Array:4,Float64Array:8},O=function(e){var t=c(e);return"DataView"===t||s(M,t)},N=function(e){return a(e)&&s(M,c(e))},R=function(e){if(N(e))return e;throw TypeError("Target is not a typed array")},P=function(e){if(d){if(F.call(C,e))return e}else for(var t in M)if(s(M,r)){var n=i[t];if(n&&(e===n||F.call(n,e)))return e}throw TypeError("Target is not a typed array constructor")},D=function(e,t,n){if(o){if(n)for(var r in M){var a=i[r];a&&s(a.prototype,e)&&delete a.prototype[e]}E[e]&&!n||l(E,e,n?t:A&&b[e]||t)}},L=function(e,t,n){var r,a;if(o){if(d){if(n)for(r in M)(a=i[r])&&s(a,e)&&delete a[e];if(C[e]&&!n)return;try{return l(C,e,n?t:A&&y[e]||t)}catch(e){}}for(r in M)!(a=i[r])||a[e]&&!n||l(a,e,t)}};for(r in M)i[r]||(A=!1);if((!A||"function"!=typeof C||C===Function.prototype)&&(C=function(){throw TypeError("Incorrect invocation")},A))for(r in M)i[r]&&d(i[r],C);if((!A||!E||E===S)&&(E=C.prototype,A))for(r in M)i[r]&&d(i[r].prototype,E);if(A&&p(w)!==E&&d(w,E),o&&!s(E,I)){T=!0,f(E,I,{get:function(){return a(this)?this[_]:void 0}});for(r in M)i[r]&&u(i[r],_,r)}k&&d&&p(m)!==S&&d(m,S),e.exports={NATIVE_ARRAY_BUFFER:k,NATIVE_ARRAY_BUFFER_VIEWS:A,TYPED_ARRAY_TAG:T&&_,aTypedArray:R,aTypedArrayConstructor:P,exportTypedArrayMethod:D,exportTypedArrayStaticMethod:L,isView:O,isTypedArray:N,TypedArray:C,TypedArrayPrototype:E}},function(e,t,n){var r=n(36),o=Math.min;e.exports=function(e){return e>0?o(r(e),9007199254740991):0}},function(e,t,n){var r=n(10),o=n(194),i=n(1),a=n(39),s=Object.defineProperty;t.f=r?s:function(e,t,n){if(i(e),t=a(t,!0),i(n),o)try{return s(e,t,n)}catch(e){}if("get"in n||"set"in n)throw TypeError("Accessors not supported");return"value"in n&&(e[t]=n.value),e}},function(e,t,n){"use strict";function r(e){for(var t=arguments.length-1,n="Minified React error #"+e+"; visit http://facebook.github.io/react/docs/error-decoder.html?invariant="+e,r=0;rE;E++)if((p||E in x)&&(m=x[E],y=w(m,E,b),e))if(t)F[E]=y;else if(y)switch(e){case 3:return!0;case 5:return m;case 6:return E;case 2:c.call(F,m)}else if(l)return!1;return f?-1:u||l?l:F}};e.exports={forEach:u(0),map:u(1),filter:u(2),some:u(3),every:u(4),find:u(5),findIndex:u(6)}},function(e,t){e.exports=function(e){if(void 0==e)throw TypeError("Can't call method on "+e);return e}},function(e,t,n){var r=n(10),o=n(112),i=n(47),a=n(35),s=n(39),c=n(18),u=n(194),l=Object.getOwnPropertyDescriptor;t.f=r?l:function(e,t){if(e=a(e),t=s(t,!0),u)try{return l(e,t)}catch(e){}if(c(e,t))return i(!o.f.call(e,t),e[t])}},function(e,t,n){var r=n(5),o=n(20),i=n(18),a=n(147),s=n(135),c=n(21),u=c.get,l=c.enforce,f=String(String).split("String");(e.exports=function(e,t,n,s){var c=!!s&&!!s.unsafe,u=!!s&&!!s.enumerable,p=!!s&&!!s.noTargetGet;if("function"==typeof n&&("string"!=typeof t||i(n,"name")||o(n,"name",t),l(n).source=f.join("string"==typeof t?t:"")),e===r)return void(u?e[t]=n:a(t,n));c?!p&&e[t]&&(u=!0):delete e[t],u?e[t]=n:o(e,t,n)})(Function.prototype,"toString",function(){return"function"==typeof this&&u(this).source||s(this)})},function(e,t,n){var r=n(18),o=n(16),i=n(117),a=n(130),s=i("IE_PROTO"),c=Object.prototype;e.exports=a?Object.getPrototypeOf:function(e){return e=o(e),r(e,s)?e[s]:"function"==typeof e.constructor&&e instanceof e.constructor?e.constructor.prototype:e instanceof Object?c:null}},function(e,t,n){var r,o=n(1),i=n(142),a=n(133),s=n(86),c=n(193),u=n(132),l=n(117),f=l("IE_PROTO"),p=function(){},d=function(e){return" + + + + + + \ No newline at end of file