mirror of
https://github.com/chylex/TweetDuck.git
synced 2025-05-10 17:34:07 +02:00
Implement basic plugin loading and management
This commit is contained in:
parent
5557f79fe7
commit
13d0e10dcd
@ -35,7 +35,7 @@ private static UserConfig Config{
|
||||
|
||||
private FormWindowState prevState;
|
||||
|
||||
public FormBrowser(PluginManager plugins){
|
||||
public FormBrowser(PluginManager pluginManager){
|
||||
InitializeComponent();
|
||||
|
||||
Text = Program.BrandName;
|
||||
@ -61,14 +61,15 @@ public FormBrowser(PluginManager plugins){
|
||||
|
||||
UpdateTrayIcon();
|
||||
|
||||
plugins = pluginManager;
|
||||
plugins.Config.PluginChangedState += plugins_PluginChangedState;
|
||||
|
||||
notification = CreateNotificationForm(true);
|
||||
notification.CanMoveWindow = () => false;
|
||||
notification.Show();
|
||||
|
||||
updates = new UpdateHandler(browser,this);
|
||||
updates.UpdateAccepted += updates_UpdateAccepted;
|
||||
|
||||
this.plugins = plugins;
|
||||
}
|
||||
|
||||
private void ShowChildForm(Form form){
|
||||
@ -82,7 +83,7 @@ private void ForceClose(){
|
||||
}
|
||||
|
||||
public FormNotification CreateNotificationForm(bool autoHide){
|
||||
return new FormNotification(this,bridge,trayIcon,autoHide);
|
||||
return new FormNotification(this,bridge,plugins,trayIcon,autoHide);
|
||||
}
|
||||
|
||||
// window setup
|
||||
@ -122,6 +123,8 @@ private void Browser_FrameLoadEnd(object sender, FrameLoadEndEventArgs e){
|
||||
foreach(string js in ScriptLoader.LoadResources("code.js").Where(js => js != null)){
|
||||
browser.ExecuteScriptAsync(js);
|
||||
}
|
||||
|
||||
browser.ExecuteScriptAsync(plugins.GenerateScript(PluginEnvironment.Browser));
|
||||
}
|
||||
}
|
||||
|
||||
@ -188,6 +191,10 @@ private void trayIcon_ClickClose(object sender, EventArgs e){
|
||||
ForceClose();
|
||||
}
|
||||
|
||||
private void plugins_PluginChangedState(object sender, PluginChangedStateEventArgs e){
|
||||
browser.ExecuteScriptAsync(PluginScriptGenerator.GenerateSetPluginState(e.Plugin,e.IsEnabled));
|
||||
}
|
||||
|
||||
private void updates_UpdateAccepted(object sender, UpdateAcceptedEventArgs e){
|
||||
Hide();
|
||||
|
||||
|
@ -8,12 +8,14 @@
|
||||
using TweetDck.Core.Handling;
|
||||
using TweetDck.Resources;
|
||||
using TweetDck.Core.Utils;
|
||||
using TweetDck.Plugins;
|
||||
|
||||
namespace TweetDck.Core{
|
||||
sealed partial class FormNotification : Form{
|
||||
public Func<bool> CanMoveWindow = () => true;
|
||||
|
||||
private readonly Form owner;
|
||||
private readonly PluginManager plugins;
|
||||
private readonly TrayIcon trayIcon;
|
||||
private readonly ChromiumWebBrowser browser;
|
||||
|
||||
@ -47,12 +49,13 @@ private static int BaseClientHeight{
|
||||
}
|
||||
}
|
||||
|
||||
public FormNotification(Form owner, TweetDeckBridge bridge, TrayIcon trayIcon, bool autoHide){
|
||||
public FormNotification(Form owner, TweetDeckBridge bridge, PluginManager plugins, TrayIcon trayIcon, bool autoHide){
|
||||
InitializeComponent();
|
||||
|
||||
Text = Program.BrandName;
|
||||
|
||||
this.owner = owner;
|
||||
this.plugins = plugins;
|
||||
this.trayIcon = trayIcon;
|
||||
this.autoHide = autoHide;
|
||||
|
||||
@ -112,6 +115,7 @@ private void Config_MuteToggled(object sender, EventArgs e){
|
||||
private void Browser_FrameLoadEnd(object sender, FrameLoadEndEventArgs e){
|
||||
if (e.Frame.IsMain && notificationJS != null && browser.Address != "about:blank"){
|
||||
browser.ExecuteScriptAsync(notificationJS);
|
||||
browser.ExecuteScriptAsync(plugins.GenerateScript(PluginEnvironment.Notification));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -6,11 +6,6 @@
|
||||
|
||||
namespace TweetDck.Plugins{
|
||||
class Plugin{
|
||||
[Flags]
|
||||
public enum Environment{
|
||||
None, Browser, Notification
|
||||
}
|
||||
|
||||
public string Identifier { get { return identifier; } }
|
||||
public string Name { get { return metadata["NAME"]; } }
|
||||
public string Description { get { return metadata["DESCRIPTION"]; } }
|
||||
@ -18,7 +13,7 @@ public enum Environment{
|
||||
public string Version { get { return metadata["VERSION"]; } }
|
||||
public string Website { get { return metadata["WEBSITE"]; } }
|
||||
public PluginGroup Group { get; private set; }
|
||||
public Environment Environments { get; private set; }
|
||||
public PluginEnvironment Environments { get; private set; }
|
||||
|
||||
private readonly string path;
|
||||
private readonly string identifier;
|
||||
@ -32,9 +27,19 @@ public enum Environment{
|
||||
|
||||
private Plugin(string path, PluginGroup group){
|
||||
this.path = path;
|
||||
this.identifier = group.GetIdentifierPrefix()+Path.GetDirectoryName(path);
|
||||
this.identifier = group.GetIdentifierPrefix()+Path.GetFileName(path);
|
||||
this.Group = group;
|
||||
this.Environments = Environment.None;
|
||||
this.Environments = PluginEnvironment.None;
|
||||
}
|
||||
|
||||
public string GetScriptPath(PluginEnvironment environment){
|
||||
if (Environments.HasFlag(environment)){
|
||||
string file = environment.GetScriptFile();
|
||||
return file != null ? Path.Combine(path,file) : string.Empty;
|
||||
}
|
||||
else{
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
public override int GetHashCode(){
|
||||
@ -63,11 +68,10 @@ public static Plugin CreateFromFolder(string path, PluginGroup group, out string
|
||||
|
||||
private static bool LoadEnvironments(string path, Plugin plugin, out string error){
|
||||
foreach(string file in Directory.EnumerateFiles(path,"*.js",SearchOption.TopDirectoryOnly).Select(Path.GetFileName)){
|
||||
if (file.Equals("browser.js",StringComparison.Ordinal)){
|
||||
plugin.Environments |= Environment.Browser;
|
||||
}
|
||||
else if (file.Equals("notification.js",StringComparison.Ordinal)){
|
||||
plugin.Environments |= Environment.Notification;
|
||||
PluginEnvironment environment = PluginEnvironmentExtensions.Values.FirstOrDefault(env => file.Equals(env.GetScriptFile(),StringComparison.Ordinal));
|
||||
|
||||
if (environment != PluginEnvironment.None){
|
||||
plugin.Environments |= environment;
|
||||
}
|
||||
else{
|
||||
error = "Unknown script file: "+file;
|
||||
@ -75,7 +79,7 @@ private static bool LoadEnvironments(string path, Plugin plugin, out string erro
|
||||
}
|
||||
}
|
||||
|
||||
if (plugin.Environments == Environment.None){
|
||||
if (plugin.Environments == PluginEnvironment.None){
|
||||
error = "Plugin has no script files.";
|
||||
return false;
|
||||
}
|
||||
|
@ -7,6 +7,18 @@ class PluginConfig{
|
||||
[field:NonSerialized]
|
||||
public event EventHandler<PluginChangedStateEventArgs> PluginChangedState;
|
||||
|
||||
public IEnumerable<string> DisabledPlugins{
|
||||
get{
|
||||
return Disabled;
|
||||
}
|
||||
}
|
||||
|
||||
public bool AnyDisabled{
|
||||
get{
|
||||
return Disabled.Count > 0;
|
||||
}
|
||||
}
|
||||
|
||||
private readonly HashSet<string> Disabled = new HashSet<string>();
|
||||
|
||||
public void SetEnabled(Plugin plugin, bool enabled){
|
||||
|
36
Plugins/PluginEnvironment.cs
Normal file
36
Plugins/PluginEnvironment.cs
Normal file
@ -0,0 +1,36 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace TweetDck.Plugins{
|
||||
[Flags]
|
||||
enum PluginEnvironment{
|
||||
None = 0,
|
||||
Browser = 1,
|
||||
Notification = 2
|
||||
}
|
||||
|
||||
static class PluginEnvironmentExtensions{
|
||||
public static IEnumerable<PluginEnvironment> Values{
|
||||
get{
|
||||
yield return PluginEnvironment.Browser;
|
||||
yield return PluginEnvironment.Notification;
|
||||
}
|
||||
}
|
||||
|
||||
public static string GetScriptFile(this PluginEnvironment environment){
|
||||
switch(environment){
|
||||
case PluginEnvironment.Browser: return "browser.js";
|
||||
case PluginEnvironment.Notification: return "notification.js";
|
||||
default: return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static string GetScriptVariables(this PluginEnvironment environment){
|
||||
switch(environment){
|
||||
case PluginEnvironment.Browser: return "$,$TD,TD";
|
||||
case PluginEnvironment.Notification: return "$TD";
|
||||
default: return string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -2,6 +2,8 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using TweetDck.Resources;
|
||||
|
||||
namespace TweetDck.Plugins{
|
||||
class PluginManager{
|
||||
@ -44,6 +46,31 @@ public void Reload(){
|
||||
}
|
||||
}
|
||||
|
||||
public string GenerateScript(PluginEnvironment environment){
|
||||
string mainScript = ScriptLoader.LoadResource("plugins.js");
|
||||
if (mainScript == null)return string.Empty;
|
||||
|
||||
StringBuilder build = new StringBuilder((1+plugins.Count)*512);
|
||||
|
||||
PluginScriptGenerator.AppendStart(build,environment);
|
||||
build.Append(mainScript);
|
||||
PluginScriptGenerator.AppendConfig(build,Config);
|
||||
|
||||
foreach(Plugin plugin in Plugins){
|
||||
string path = plugin.GetScriptPath(environment);
|
||||
if (string.IsNullOrEmpty(path))continue;
|
||||
|
||||
try{
|
||||
PluginScriptGenerator.AppendPlugin(build,plugin.Identifier,File.ReadAllText(path));
|
||||
}catch{
|
||||
// TODO
|
||||
}
|
||||
}
|
||||
|
||||
PluginScriptGenerator.AppendEnd(build,environment);
|
||||
return build.ToString();
|
||||
}
|
||||
|
||||
private IEnumerable<Plugin> LoadPluginsFrom(string path, PluginGroup group){
|
||||
foreach(string fullDir in Directory.EnumerateDirectories(path,"*",SearchOption.TopDirectoryOnly)){
|
||||
string error;
|
||||
|
31
Plugins/PluginScriptGenerator.cs
Normal file
31
Plugins/PluginScriptGenerator.cs
Normal file
@ -0,0 +1,31 @@
|
||||
using System.Text;
|
||||
|
||||
namespace TweetDck.Plugins{
|
||||
static class PluginScriptGenerator{
|
||||
public static void AppendStart(StringBuilder build, PluginEnvironment environment){
|
||||
build.Append("(function(").Append(environment.GetScriptVariables()).Append("){");
|
||||
}
|
||||
|
||||
public static void AppendConfig(StringBuilder build, PluginConfig config){
|
||||
if (config.AnyDisabled){
|
||||
build.Append("PLUGINS.disabled = [\"").Append(string.Join("\",\"",config.DisabledPlugins)).Append("\"];");
|
||||
}
|
||||
}
|
||||
|
||||
public static void AppendPlugin(StringBuilder build, string pluginIdentifier, string pluginContents){
|
||||
build.Append("PLUGINS.installed.push({");
|
||||
build.Append("id: \"").Append(pluginIdentifier).Append("\",");
|
||||
build.Append("obj: new class extends PluginBase{ ").Append(pluginContents).Append(" }");
|
||||
build.Append("});");
|
||||
}
|
||||
|
||||
public static void AppendEnd(StringBuilder build, PluginEnvironment environment){
|
||||
build.Append("PLUGINS.load();");
|
||||
build.Append("})(").Append(environment.GetScriptVariables()).Append(");");
|
||||
}
|
||||
|
||||
public static string GenerateSetPluginState(Plugin plugin, bool enabled){
|
||||
return new StringBuilder().Append("window.TD_PLUGINS.setState(\"").Append(plugin.Identifier).Append("\",").Append(enabled ? "true" : "false").Append(");").ToString();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,9 +1,4 @@
|
||||
(function($,$TD,TD){
|
||||
//
|
||||
// Variable: Says whether TweetD*ck events was initialized.
|
||||
//
|
||||
var isInitialized = false;
|
||||
|
||||
//
|
||||
// Variable: Current highlighted column jQuery object.
|
||||
//
|
||||
@ -58,11 +53,15 @@
|
||||
}
|
||||
});
|
||||
|
||||
// Finish init
|
||||
// Finish init and load plugins
|
||||
$TD.loadFontSizeClass(TD.settings.getFontSize());
|
||||
$TD.loadNotificationHeadContents(getNotificationHeadContents());
|
||||
|
||||
isInitialized = true;
|
||||
window.TD_APP_READY = true;
|
||||
|
||||
if (window.TD_PLUGINS){
|
||||
window.TD_PLUGINS.onReady();
|
||||
}
|
||||
};
|
||||
|
||||
//
|
||||
@ -127,10 +126,10 @@
|
||||
var app = $("body").children(".js-app");
|
||||
|
||||
new MutationObserver(function(){
|
||||
if (isInitialized && app.hasClass("is-hidden")){
|
||||
isInitialized = false;
|
||||
if (window.TD_APP_READY && app.hasClass("is-hidden")){
|
||||
window.TD_APP_READY = false;
|
||||
}
|
||||
else if (!isInitialized && !app.hasClass("is-hidden")){
|
||||
else if (!window.TD_APP_READY && !app.hasClass("is-hidden")){
|
||||
initializeTweetDck();
|
||||
}
|
||||
}).observe(app[0],{
|
||||
|
@ -116,4 +116,13 @@
|
||||
|
||||
$TD.setNotificationTweetEmbedded(account[0].getAttribute("href")+"/status/"+tweetId);
|
||||
})();
|
||||
|
||||
//
|
||||
// Block: Load plugins.
|
||||
//
|
||||
window.TD_APP_READY = true;
|
||||
|
||||
if (window.TD_PLUGINS){
|
||||
window.TD_PLUGINS.onReady();
|
||||
}
|
||||
})($TD);
|
62
Resources/Scripts/plugins.js
Normal file
62
Resources/Scripts/plugins.js
Normal file
@ -0,0 +1,62 @@
|
||||
class PluginBase{
|
||||
constructor(pluginSettings){
|
||||
this.$pluginSettings = pluginSettings || {};
|
||||
}
|
||||
|
||||
enabled(){}
|
||||
ready(){}
|
||||
disabled(){}
|
||||
}
|
||||
|
||||
var PLUGINS = {
|
||||
installed: [],
|
||||
disabled: [],
|
||||
waiting: [],
|
||||
|
||||
isDisabled: plugin => PLUGINS.disabled.includes(plugin.id),
|
||||
findObject: identifier => PLUGINS.installed.find(plugin => plugin.id === identifier),
|
||||
|
||||
load: function(){
|
||||
PLUGINS.installed.forEach(plugin => {
|
||||
if (!PLUGINS.isDisabled(plugin)){
|
||||
plugin.obj.enabled();
|
||||
PLUGINS.runWhenReady(plugin);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
onReady: function(){
|
||||
PLUGINS.waiting.forEach(plugin => plugin.obj.ready());
|
||||
PLUGINS.waiting = [];
|
||||
},
|
||||
|
||||
runWhenReady: function(plugin){
|
||||
if (window.TD_APP_READY){
|
||||
plugin.obj.ready();
|
||||
}
|
||||
else{
|
||||
PLUGINS.waiting.push(plugin);
|
||||
}
|
||||
},
|
||||
|
||||
setState: function(identifier, enable){
|
||||
var plugin = PLUGINS.findObject(identifier);
|
||||
|
||||
if (enable && PLUGINS.isDisabled(plugin)){
|
||||
PLUGINS.disabled.splice(PLUGINS.disabled.indexOf(identifier),1);
|
||||
plugin.obj.enabled();
|
||||
PLUGINS.runWhenReady(plugin);
|
||||
}
|
||||
else if (!enable && !PLUGINS.isDisabled(plugin)){
|
||||
PLUGINS.disabled.push(identifier);
|
||||
plugin.obj.disabled();
|
||||
}
|
||||
else return;
|
||||
|
||||
if (plugin.obj.$pluginSettings.requiresPageReload){
|
||||
window.location.reload();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
window.TD_PLUGINS = PLUGINS;
|
@ -1,4 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="packages\CefSharp.WinForms.49.0.0-pre02\build\CefSharp.WinForms.props" Condition="Exists('packages\CefSharp.WinForms.49.0.0-pre02\build\CefSharp.WinForms.props')" />
|
||||
<Import Project="packages\CefSharp.Common.49.0.0-pre02\build\CefSharp.Common.props" Condition="Exists('packages\CefSharp.Common.49.0.0-pre02\build\CefSharp.Common.props')" />
|
||||
@ -159,9 +159,11 @@
|
||||
<Compile Include="Plugins\Plugin.cs" />
|
||||
<Compile Include="Plugins\PluginChangedStateEventArgs.cs" />
|
||||
<Compile Include="Plugins\PluginConfig.cs" />
|
||||
<Compile Include="Plugins\PluginEnvironment.cs" />
|
||||
<Compile Include="Plugins\PluginGroup.cs" />
|
||||
<Compile Include="Plugins\PluginLoadErrorEventArgs.cs" />
|
||||
<Compile Include="Plugins\PluginManager.cs" />
|
||||
<Compile Include="Plugins\PluginScriptGenerator.cs" />
|
||||
<Compile Include="Updates\FormUpdateDownload.cs">
|
||||
<SubType>Form</SubType>
|
||||
</Compile>
|
||||
@ -275,6 +277,12 @@
|
||||
<TargetPath>notification.js</TargetPath>
|
||||
</ContentWithTargetPath>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ContentWithTargetPath Include="Resources\Scripts\plugins.js">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
<TargetPath>plugins.js</TargetPath>
|
||||
</ContentWithTargetPath>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ContentWithTargetPath Include="Resources\Scripts\update.js">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
|
Loading…
Reference in New Issue
Block a user