diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..c5412fa
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,4 @@
+[*]
+tab_width = 4
+indent_size = 4
+trim_trailing_whitespace = true
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..f760286
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,6 @@
+.vs/
+.vscode/
+.cache/
+obj/
+node_modules/
+System Volume Information/
\ No newline at end of file
diff --git a/.npmignore b/.npmignore
new file mode 100644
index 0000000..affc1fa
--- /dev/null
+++ b/.npmignore
@@ -0,0 +1,10 @@
+.vs/
+.vscode/
+tools/
+
+.editorconfig
+.eslintignore
+.eslintrc.json
+.gitignore
+.prettierrc.json
+.babelrc.json
\ No newline at end of file
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..174ac3b
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2020 Kevin Hill
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/Properties/Resources.Designer.cs b/Properties/Resources.Designer.cs
new file mode 100644
index 0000000..6cc827f
--- /dev/null
+++ b/Properties/Resources.Designer.cs
@@ -0,0 +1,84 @@
+//------------------------------------------------------------------------------
+//
+// This code was generated by a tool.
+// Runtime Version:4.0.30319.42000
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+//
+//------------------------------------------------------------------------------
+
+namespace SSPM.Properties {
+ using System;
+
+
+ ///
+ /// A strongly-typed resource class, for looking up localized strings, etc.
+ ///
+ // This class was auto-generated by the StronglyTypedResourceBuilder
+ // class via a tool like ResGen or Visual Studio.
+ // To add or remove a member, edit your .ResX file then rerun ResGen
+ // with the /str option, or rebuild your VS project.
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+ public class Resources {
+
+ private static global::System.Resources.ResourceManager resourceMan;
+
+ private static global::System.Globalization.CultureInfo resourceCulture;
+
+ [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+ internal Resources() {
+ }
+
+ ///
+ /// Returns the cached ResourceManager instance used by this class.
+ ///
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ public static global::System.Resources.ResourceManager ResourceManager {
+ get {
+ if (object.ReferenceEquals(resourceMan, null)) {
+ global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("SSPM.Properties.Resources", typeof(Resources).Assembly);
+ resourceMan = temp;
+ }
+ return resourceMan;
+ }
+ }
+
+ ///
+ /// Overrides the current thread's CurrentUICulture property for all
+ /// resource lookups using this strongly typed resource class.
+ ///
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ public static global::System.Globalization.CultureInfo Culture {
+ get {
+ return resourceCulture;
+ }
+ set {
+ resourceCulture = value;
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to (function(spGlobal) {
+ /// const notify = (message, title) => {
+ /// const textInfo = new StrokesPlus.Types.Internal.DisplayTextInfo();
+ ///
+ /// textInfo.UsePrimaryScreen = true;
+ /// textInfo.Message = message;
+ /// textInfo.Title = title;
+ /// textInfo.TitleFont = new Font("Segoe UI", 18, host.flags(FontStyle.Bold));
+ /// textInfo.TitleAlignment = "Right";
+ /// textInfo.MessageFont = new Font("Segoe UI Semibold", 14);
+ /// textInfo.MessageAlignment = "Right";
+ /// textInfo.Padding = 10;
+ /// textInfo. [rest of string was truncated]";.
+ ///
+ public static string sppm {
+ get {
+ return ResourceManager.GetString("sppm", resourceCulture);
+ }
+ }
+ }
+}
diff --git a/Properties/Resources.resx b/Properties/Resources.resx
new file mode 100644
index 0000000..f94ec78
--- /dev/null
+++ b/Properties/Resources.resx
@@ -0,0 +1,124 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+
+ ..\src\sppm.js;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;Windows-1252
+
+
\ No newline at end of file
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..6ef0062
--- /dev/null
+++ b/README.md
@@ -0,0 +1,62 @@
+## ScriptsPlus, a StrokesPlus.net Plugin
+
+### Enhance the scripting capabilities of S+ with a rich module library.
+Inspired by the simplicity of jQuery, there are 40+ modules that extend and enhance the scripting capabilities within action scripts.
+Many common `sp.xxxx` methods are wrapped to simplify their use and some wrapped together to create new tools.
+
+# Install
+Download `ScriptsPlusPlugin.dll` from here and place it in `C:\Program Files\StrokesPlus.net\Plug-Ins` to be automatically picked up by S+
+You can also add it manually, or place it in your own plugins folder and add that path.
+
+# Loading
+ - Head to `Global Actions`
+ - Open the `Load/Unload` tab
+ - Check the box to enable the load script
+ - Paste in this snippet: `var $ = ScriptsPlus();`
+ - Profit!
+
+# Examples
+
+### Wrapped Methods
+```javascript
+// wraps `sp.MessageBox()`
+$.alert("Hello World!");
+
+// wraps `new DisplayTextInfo()`
+$.toast("Hello World!");
+
+// wraps `sp.sp.ShowBalloonTip()`
+$.balloon("Hello World!");
+```
+
+### Dialogs
+```javascript
+function getUserInput() {
+ var modal = $.dialog.create("Text Input Demo");
+
+ modal.show(input => $.alert(input, "Input"));
+}
+
+getUserInput();
+```
+
+### Popup Menus
+```javascript
+var { addToMenu, addToSubMenu, menuItem, show } = $.popup;
+
+var popup = $.popup.create();
+var addToMainMenu = $.popup.addToMenu(popup);
+
+var mastercam = menuItem("Mastercam");
+var addToDemo = $.popup.addToSubMenu(mastercam);
+
+addToDemo(menuItem("Hello World", `$.alert('Hello World')`));
+addToDemo(menuItem("Taco Bell", `$.alert('Taco Bell')`));
+addToDemo(menuItem("Is Good", `$.alert('Is Good')`));
+
+addToMainMenu(mastercam);
+addToMainMenu($.popup.spacer);
+addToMainMenu($.popup.cancel);
+
+show(popup);
+```
\ No newline at end of file
diff --git a/bin/Release/sspm.dll b/bin/Release/sspm.dll
new file mode 100644
index 0000000..29a59aa
Binary files /dev/null and b/bin/Release/sspm.dll differ
diff --git a/bin/Release/sspm.pdb b/bin/Release/sspm.pdb
new file mode 100644
index 0000000..85e5652
Binary files /dev/null and b/bin/Release/sspm.pdb differ
diff --git a/sppm.csproj b/sppm.csproj
new file mode 100644
index 0000000..f6dc6a9
--- /dev/null
+++ b/sppm.csproj
@@ -0,0 +1,74 @@
+
+
+
+
+ Debug
+ AnyCPU
+ {0000CF33-0000-0000-0000-000000000000}
+ Library
+ SSPM
+ sspm
+ v4.8
+ 512
+ true
+ true
+
+
+
+
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ pdbonly
+ true
+ bin\Release\
+ prompt
+ 4
+ false
+
+
+
+ False
+ C:\Program Files\StrokesPlus.net\ClearScript.Core.dll
+ False
+
+
+ C:\Program Files\StrokesPlus.net\ClearScript.V8.dll
+ False
+
+
+
+
+
+
+
+
+ True
+ True
+ Resources.resx
+
+
+
+
+
+ PublicResXFileCodeGenerator
+ Resources.Designer.cs
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/sppm.sln b/sppm.sln
new file mode 100644
index 0000000..d01b1a2
--- /dev/null
+++ b/sppm.sln
@@ -0,0 +1,25 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 16
+VisualStudioVersion = 16.0.31321.278
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "sppm", "sppm.csproj", "{0000CF33-0000-0000-0000-000000000000}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {0000CF33-0000-0000-0000-000000000000}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {0000CF33-0000-0000-0000-000000000000}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {0000CF33-0000-0000-0000-000000000000}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {0000CF33-0000-0000-0000-000000000000}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {EDD7BDD0-0D99-4905-B067-282AFA10DD39}
+ EndGlobalSection
+EndGlobal
diff --git a/src/sppm.cs b/src/sppm.cs
new file mode 100644
index 0000000..d3c95c8
--- /dev/null
+++ b/src/sppm.cs
@@ -0,0 +1,22 @@
+using Microsoft.ClearScript;
+using System.Windows.Forms;
+
+namespace SSPM
+{
+ public static class SSPM
+ {
+ public static string jsSource { get; } = Properties.Resources.sppm;
+
+ public static void StrokesPlusInitStaticPlugin(ScriptEngine e)
+ {
+ try
+ {
+ e.Execute(jsSource);
+ }
+ catch (System.Exception err)
+ {
+ MessageBox.Show(err.Message, "SSPM Plugin Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
+ }
+ }
+ }
+}
diff --git a/src/sppm.js b/src/sppm.js
new file mode 100644
index 0000000..bb6b72d
--- /dev/null
+++ b/src/sppm.js
@@ -0,0 +1,133 @@
+(function(spGlobal) {
+ const notify = (message, title) => {
+ const textInfo = new StrokesPlus.Types.Internal.DisplayTextInfo();
+
+ textInfo.UsePrimaryScreen = true;
+ textInfo.Message = message;
+ textInfo.Title = title;
+ textInfo.TitleFont = new Font("Segoe UI", 18, host.flags(FontStyle.Bold));
+ textInfo.TitleAlignment = "Right";
+ textInfo.MessageFont = new Font("Segoe UI Semibold", 14);
+ textInfo.MessageAlignment = "Right";
+ textInfo.Padding = 10;
+ textInfo.Duration = 2000;
+ textInfo.Location = "bottomright";
+ textInfo.Opacity = 0.9;
+ textInfo.ForeColor = "white";
+ textInfo.BackColor = "SteelBlue";
+
+ return StrokesPlus.UI.TextOverlay.Show(textInfo);
+ }
+
+ const alert = StrokesPlus.UI.Alert;
+
+ const APPDATA = StrokesPlus.OS.Shell.ExpandEnvironmentVariables('%APPDATA%');
+ const SP_APPDATA = System.IO.Path.Combine(APPDATA, 'StrokesPlus.net');
+ const NODE_MODULES = System.IO.Path.Combine(SP_APPDATA, 'node_modules');
+ const PKG_JSON = "package.json";
+
+ function npm(input) {
+ const NPM = 'C:\\Program Files\\nodejs\\npm.cmd';
+ const CMD = `cd "${SP_APPDATA}" && "${NPM}" ${input}`;
+
+ try {
+ StrokesPlus.OS.Shell.RunProgram('cmd.exe', '/C ' + CMD, '', 'hidden', true, true, true);
+ } catch (err) {
+ alert(err.toString(), "Error");
+ }
+ }
+
+ function resolve(id) {
+ const pkgPath = System.IO.Path.Combine(NODE_MODULES, id, PKG_JSON);
+
+ if (System.IO.File.Exists(pkgPath)) {
+ return pkgPath;
+ } else {
+ alert(`${pkgPath} was not found`, "Error");
+ }
+ }
+
+ function readPackage(abspath) {
+ try {
+ return JSON.parse(System.IO.File.ReadAllText(abspath));
+ } catch (error) {
+ alert(error, "error");
+ }
+ }
+
+ class Plugin {
+ static load(id) {
+ const pkgPath = resolve(id);
+ const plugin = new Plugin(pkgPath);
+ const src = plugin.source;
+ __spEngineWrapper.Engine.Evaluate(src);
+ }
+ static fromNpm(id) {
+ if (typeof id === "string") {
+ notify(id, `Installing from npm...`);
+ npm(`install --save ${id}`);
+ } else {
+ npm(`install`);
+ }
+ }
+ constructor(abspath) {
+ this.abspath = abspath;
+ this.resolvedPkg = this.abspath.replace("/", "\\").replace("\\\\", "\\");
+ }
+ get pkgExists() {
+ return System.IO.File.Exists(this.resolvedPkg);
+ }
+ get pkg() {
+ const contents = System.IO.File.ReadAllText(this.resolvedPkg);
+ try {
+ return JSON.parse(contents);
+ } catch (error) {
+ return {};
+ }
+ }
+ get mainEntry() {
+ const path = this.resolvedPkg.replace(PKG_JSON, "");
+ return System.IO.Path.Combine(path, this.pkg.main);
+ }
+ get source() {
+ try {
+ return System.IO.File.ReadAllText(this.mainEntry);
+ } catch (error) {
+ return alert(abspath, 'File Read Error');
+ }
+ }
+ }
+
+ const SSPM = class {
+ constructor() {
+ //
+ }
+
+ install(id) {
+ if (typeof id === "string") {
+ notify(`Installing "${id}" from npmjs`, "StrokesPlus Package Manager");
+ npm(`install --save ${id}`);
+ } else {
+ npm(`install`);
+ }
+ }
+ }
+
+ function once(fn) {
+ if (__spEngineWrapper.Engine.Name == StrokesPlus.ScriptEngine.List.Last().Engine.Name) {
+ fn();
+ }
+ }
+
+ spGlobal["SPPM"] = SPPM;
+ spGlobal["Plugin"] = Plugin;
+ spGlobal["sppm"] = new spGlobal["SPPM"]();
+})(this);
+//
+////////////////////////////////////////////////////////////////////
+//
+//sppm.install('@spcsp/explore-settings-json');
+//Plugin.load('@spcsp/explore-settings-json');
+
+//Plugin.load('@spcsp/osd-toast');
+//toast("hi");