1
0
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:
chylex 2016-06-25 18:52:37 +02:00
parent 5557f79fe7
commit 13d0e10dcd
11 changed files with 229 additions and 30 deletions

View File

@ -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();

View File

@ -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));
}
}

View File

@ -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;
}

View File

@ -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){

View 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;
}
}
}
}

View File

@ -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;

View 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();
}
}
}

View File

@ -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],{

View File

@ -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);

View 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;

View File

@ -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>