mirror of
https://github.com/chylex/TweetDuck.git
synced 2025-04-20 21:15:48 +02:00
Implement analytics (#176)
* Implement analytics report generation w/ user config and basic system info * Add HW and plugin info to analytics report generation * Add a way of displaying the full analytics report * Fix issues in analytics report and include design theme * Ensure tab config is saved when switching tabs * Fix compilation error in TabSettingsFeedback and safeguard nulls * Add locale to analytics report * Work on analytics (utils, last collection label, dependency refactoring) * Add analytics state file and implement sending reports every week * Send an analytics report after each update
This commit is contained in:
parent
4e26fd9d56
commit
108cf8923e
Core
Program.csResources/Plugins/edit-design
TweetDuck.csproj@ -8,6 +8,7 @@
|
||||
using TweetDuck.Core.Notification;
|
||||
using TweetDuck.Core.Notification.Screenshot;
|
||||
using TweetDuck.Core.Other;
|
||||
using TweetDuck.Core.Other.Analytics;
|
||||
using TweetDuck.Core.Other.Management;
|
||||
using TweetDuck.Core.Other.Settings;
|
||||
using TweetDuck.Core.Utils;
|
||||
@ -47,10 +48,11 @@ public bool IsWaiting{
|
||||
|
||||
private bool isLoaded;
|
||||
private FormWindowState prevState;
|
||||
|
||||
|
||||
private TweetScreenshotManager notificationScreenshotManager;
|
||||
private SoundNotification soundNotification;
|
||||
private VideoPlayer videoPlayer;
|
||||
private AnalyticsManager analytics;
|
||||
|
||||
public FormBrowser(UpdaterSettings updaterSettings){
|
||||
InitializeComponent();
|
||||
@ -77,6 +79,7 @@ public FormBrowser(UpdaterSettings updaterSettings){
|
||||
notificationScreenshotManager?.Dispose();
|
||||
soundNotification?.Dispose();
|
||||
videoPlayer?.Dispose();
|
||||
analytics?.Dispose();
|
||||
};
|
||||
|
||||
this.trayIcon.ClickRestore += trayIcon_ClickRestore;
|
||||
@ -108,6 +111,10 @@ private void RestoreWindow(){
|
||||
Config.BrowserWindow.Restore(this, true);
|
||||
prevState = WindowState;
|
||||
isLoaded = true;
|
||||
|
||||
if (Config.AllowDataCollection){
|
||||
analytics = new AnalyticsManager(this, plugins, Program.AnalyticsFilePath);
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateTrayIcon(){
|
||||
@ -321,6 +328,10 @@ public void OnIntroductionClosed(bool showGuide, bool allowDataCollection){
|
||||
Config.FirstRun = false;
|
||||
Config.AllowDataCollection = allowDataCollection;
|
||||
Config.Save();
|
||||
|
||||
if (allowDataCollection && analytics == null){
|
||||
analytics = new AnalyticsManager(this, plugins, Program.AnalyticsFilePath);
|
||||
}
|
||||
}
|
||||
|
||||
if (showGuide){
|
||||
@ -340,7 +351,7 @@ public void OpenSettings(Type startTab){
|
||||
if (!FormManager.TryBringToFront<FormSettings>()){
|
||||
bool prevEnableUpdateCheck = Config.EnableUpdateCheck;
|
||||
|
||||
FormSettings form = new FormSettings(this, plugins, updates, startTab);
|
||||
FormSettings form = new FormSettings(this, plugins, updates, analytics, startTab);
|
||||
|
||||
form.FormClosed += (sender, args) => {
|
||||
if (!prevEnableUpdateCheck && Config.EnableUpdateCheck){
|
||||
@ -355,6 +366,16 @@ public void OpenSettings(Type startTab){
|
||||
}
|
||||
|
||||
browser.RefreshMemoryTracker();
|
||||
|
||||
if (Config.AllowDataCollection){
|
||||
if (analytics == null){
|
||||
analytics = new AnalyticsManager(this, plugins, Program.AnalyticsFilePath);
|
||||
}
|
||||
}
|
||||
else if (analytics != null){
|
||||
analytics.Dispose();
|
||||
analytics = null;
|
||||
}
|
||||
|
||||
if (form.ShouldReloadBrowser){
|
||||
FormManager.TryFind<FormPlugins>()?.Close();
|
||||
|
56
Core/Other/Analytics/AnalyticsFile.cs
Normal file
56
Core/Other/Analytics/AnalyticsFile.cs
Normal file
@ -0,0 +1,56 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using TweetDuck.Core.Utils;
|
||||
using TweetDuck.Data.Serialization;
|
||||
|
||||
namespace TweetDuck.Core.Other.Analytics{
|
||||
sealed class AnalyticsFile{
|
||||
private static readonly FileSerializer<AnalyticsFile> Serializer = new FileSerializer<AnalyticsFile>();
|
||||
|
||||
static AnalyticsFile(){
|
||||
Serializer.RegisterTypeConverter(typeof(DateTime), new SingleTypeConverter<DateTime>{
|
||||
ConvertToString = value => value.ToBinary().ToString(),
|
||||
ConvertToObject = value => DateTime.FromBinary(long.Parse(value))
|
||||
});
|
||||
}
|
||||
|
||||
// STATE PROPERTIES
|
||||
|
||||
public DateTime LastDataCollection { get; set; } = DateTime.MinValue;
|
||||
public string LastCollectionVersion { get; set; } = null;
|
||||
public string LastCollectionMessage { get; set; } = null;
|
||||
|
||||
// END OF DATA
|
||||
|
||||
private readonly string file;
|
||||
|
||||
private AnalyticsFile(string file){
|
||||
this.file = file;
|
||||
}
|
||||
|
||||
public bool Save(){
|
||||
try{
|
||||
WindowsUtils.CreateDirectoryForFile(file);
|
||||
Serializer.Write(file, this);
|
||||
return true;
|
||||
}catch(Exception e){
|
||||
Program.Reporter.HandleException("Analytics File Error", "Could not save the analytics file.", true, e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static AnalyticsFile Load(string file){
|
||||
AnalyticsFile config = new AnalyticsFile(file);
|
||||
|
||||
try{
|
||||
Serializer.Read(file, config);
|
||||
}catch(FileNotFoundException){
|
||||
}catch(DirectoryNotFoundException){
|
||||
}catch(Exception e){
|
||||
Program.Reporter.HandleException("Analytics File Error", "Could not open the analytics file.", true, e);
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
||||
}
|
||||
}
|
110
Core/Other/Analytics/AnalyticsManager.cs
Normal file
110
Core/Other/Analytics/AnalyticsManager.cs
Normal file
@ -0,0 +1,110 @@
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
using System.Timers;
|
||||
using TweetDuck.Core.Controls;
|
||||
using TweetDuck.Core.Utils;
|
||||
using TweetDuck.Plugins;
|
||||
|
||||
namespace TweetDuck.Core.Other.Analytics{
|
||||
sealed class AnalyticsManager : IDisposable{
|
||||
private static readonly TimeSpan CollectionInterval = TimeSpan.FromDays(7);
|
||||
private static readonly Uri CollectionUrl = new Uri("https://tweetduck.chylex.com/breadcrumb/report");
|
||||
|
||||
public string LastCollectionMessage => file.LastCollectionMessage;
|
||||
|
||||
private readonly FormBrowser browser;
|
||||
private readonly PluginManager plugins;
|
||||
private readonly AnalyticsFile file;
|
||||
private readonly Timer currentTimer;
|
||||
|
||||
public AnalyticsManager(FormBrowser browser, PluginManager plugins, string file){
|
||||
this.browser = browser;
|
||||
this.plugins = plugins;
|
||||
this.file = AnalyticsFile.Load(file);
|
||||
|
||||
this.currentTimer = new Timer{ SynchronizingObject = browser };
|
||||
this.currentTimer.Elapsed += currentTimer_Elapsed;
|
||||
|
||||
if (this.file.LastCollectionVersion != Program.VersionTag){
|
||||
ScheduleReportIn(TimeSpan.FromHours(12), string.Empty);
|
||||
}
|
||||
else{
|
||||
RestartTimer();
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose(){
|
||||
currentTimer.Dispose();
|
||||
}
|
||||
|
||||
private void ScheduleReportIn(TimeSpan delay, string message = null){
|
||||
SetLastDataCollectionTime(DateTime.Now.Subtract(CollectionInterval).Add(delay), message);
|
||||
}
|
||||
|
||||
private void SetLastDataCollectionTime(DateTime dt, string message = null){
|
||||
file.LastDataCollection = new DateTime(dt.Year, dt.Month, dt.Day, dt.Hour, dt.Minute, 0, dt.Kind);
|
||||
file.LastCollectionVersion = Program.VersionTag;
|
||||
file.LastCollectionMessage = message ?? dt.ToString("g", Program.Culture);
|
||||
|
||||
file.Save();
|
||||
RestartTimer();
|
||||
}
|
||||
|
||||
private void RestartTimer(){
|
||||
TimeSpan diff = DateTime.Now.Subtract(file.LastDataCollection);
|
||||
int minutesTillNext = (int)(CollectionInterval.TotalMinutes-Math.Floor(diff.TotalMinutes));
|
||||
|
||||
currentTimer.Interval = Math.Max(minutesTillNext, 1)*60000;
|
||||
currentTimer.Start();
|
||||
}
|
||||
|
||||
private void currentTimer_Elapsed(object sender, ElapsedEventArgs e){
|
||||
currentTimer.Stop();
|
||||
|
||||
TimeSpan diff = DateTime.Now.Subtract(file.LastDataCollection);
|
||||
|
||||
if (Math.Floor(diff.TotalMinutes) >= CollectionInterval.TotalMinutes){
|
||||
SendReport();
|
||||
}
|
||||
else{
|
||||
RestartTimer();
|
||||
}
|
||||
}
|
||||
|
||||
private void SendReport(){
|
||||
AnalyticsReportGenerator.ExternalInfo info = AnalyticsReportGenerator.ExternalInfo.From(browser);
|
||||
|
||||
Task.Factory.StartNew(() => {
|
||||
AnalyticsReport report = AnalyticsReportGenerator.Create(info, plugins);
|
||||
BrowserUtils.CreateWebClient().UploadValues(CollectionUrl, "POST", report.ToNameValueCollection());
|
||||
}).ContinueWith(task => browser.InvokeAsyncSafe(() => {
|
||||
if (task.Status == TaskStatus.RanToCompletion){
|
||||
SetLastDataCollectionTime(DateTime.Now);
|
||||
}
|
||||
else if (task.Exception != null){
|
||||
string message = null;
|
||||
|
||||
if (task.Exception.InnerException is WebException e){
|
||||
switch(e.Status){
|
||||
case WebExceptionStatus.ConnectFailure:
|
||||
message = "Connection Error";
|
||||
break;
|
||||
|
||||
case WebExceptionStatus.NameResolutionFailure:
|
||||
message = "DNS Error";
|
||||
break;
|
||||
|
||||
case WebExceptionStatus.ProtocolError:
|
||||
HttpWebResponse response = e.Response as HttpWebResponse;
|
||||
message = "HTTP Error "+(response != null ? $"{(int)response.StatusCode} ({response.StatusDescription})" : "(unknown code)");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ScheduleReportIn(TimeSpan.FromHours(4), message ?? "Error: "+(task.Exception.InnerException?.Message ?? task.Exception.Message));
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
57
Core/Other/Analytics/AnalyticsReport.cs
Normal file
57
Core/Other/Analytics/AnalyticsReport.cs
Normal file
@ -0,0 +1,57 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Specialized;
|
||||
using System.Text;
|
||||
|
||||
namespace TweetDuck.Core.Other.Analytics{
|
||||
sealed class AnalyticsReport : IEnumerable{
|
||||
private OrderedDictionary data = new OrderedDictionary(32);
|
||||
private int separators;
|
||||
|
||||
public void Add(int ignored){ // adding separators to pretty print
|
||||
data.Add((++separators).ToString(), null);
|
||||
}
|
||||
|
||||
public void Add(string key, string value){
|
||||
data.Add(key, value);
|
||||
}
|
||||
|
||||
public AnalyticsReport FinalizeReport(){
|
||||
if (!data.IsReadOnly){
|
||||
data = data.AsReadOnly();
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public IEnumerator GetEnumerator(){
|
||||
return data.GetEnumerator();
|
||||
}
|
||||
|
||||
public NameValueCollection ToNameValueCollection(){
|
||||
NameValueCollection collection = new NameValueCollection();
|
||||
|
||||
foreach(DictionaryEntry entry in data){
|
||||
if (entry.Value != null){
|
||||
collection.Add((string)entry.Key, (string)entry.Value);
|
||||
}
|
||||
}
|
||||
|
||||
return collection;
|
||||
}
|
||||
|
||||
public override string ToString(){
|
||||
StringBuilder build = new StringBuilder();
|
||||
|
||||
foreach(DictionaryEntry entry in data){
|
||||
if (entry.Value == null){
|
||||
build.AppendLine();
|
||||
}
|
||||
else{
|
||||
build.AppendLine(entry.Key+": "+entry.Value);
|
||||
}
|
||||
}
|
||||
|
||||
return build.ToString();
|
||||
}
|
||||
}
|
||||
}
|
280
Core/Other/Analytics/AnalyticsReportGenerator.cs
Normal file
280
Core/Other/Analytics/AnalyticsReportGenerator.cs
Normal file
@ -0,0 +1,280 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.IO;
|
||||
using System.Windows.Forms;
|
||||
using Microsoft.Win32;
|
||||
using TweetDuck.Configuration;
|
||||
using System.Linq;
|
||||
using System.Management;
|
||||
using System.Text.RegularExpressions;
|
||||
using TweetDuck.Core.Notification;
|
||||
using TweetDuck.Core.Utils;
|
||||
using TweetDuck.Plugins;
|
||||
|
||||
namespace TweetDuck.Core.Other.Analytics{
|
||||
static class AnalyticsReportGenerator{
|
||||
public static AnalyticsReport Create(ExternalInfo info, PluginManager plugins){
|
||||
Dictionary<string, string> editLayoutDesign = EditLayoutDesignPluginData;
|
||||
|
||||
return new AnalyticsReport{
|
||||
{ "App Version" , Program.VersionTag },
|
||||
{ "App Type" , Program.IsPortable ? "portable" : "installed" },
|
||||
0,
|
||||
{ "System Name" , SystemName },
|
||||
{ "System Edition" , SystemEdition },
|
||||
{ "System Environment" , Environment.Is64BitOperatingSystem ? "64-bit" : "32-bit" },
|
||||
{ "System Build" , SystemBuild },
|
||||
{ "System Locale" , Program.Culture.Name.ToLower() },
|
||||
0,
|
||||
{ "RAM" , Exact(RamSize) },
|
||||
{ "GPU" , GpuVendor },
|
||||
0,
|
||||
{ "Screen Count" , Exact(Screen.AllScreens.Length) },
|
||||
{ "Screen Resolution" , info.Resolution ?? "(unknown)" },
|
||||
{ "Screen DPI" , info.DPI != null ? Exact(info.DPI.Value) : "(unknown)" },
|
||||
0,
|
||||
{ "Hardware Acceleration" , Bool(SysConfig.HardwareAcceleration) },
|
||||
{ "Browser GC Reload" , Bool(SysConfig.EnableBrowserGCReload) },
|
||||
{ "Browser GC Threshold" , Exact(SysConfig.BrowserMemoryThreshold) },
|
||||
0,
|
||||
{ "Expand Links" , Bool(UserConfig.ExpandLinksOnHover) },
|
||||
{ "Switch Account Selectors" , Bool(UserConfig.SwitchAccountSelectors) },
|
||||
{ "Search In First Column" , Bool(UserConfig.OpenSearchInFirstColumn) },
|
||||
{ "Best Image Quality" , Bool(UserConfig.BestImageQuality) },
|
||||
{ "Spell Check" , Bool(UserConfig.EnableSpellCheck) },
|
||||
{ "Zoom" , Exact(UserConfig.ZoomLevel) },
|
||||
0,
|
||||
{ "Updates" , Bool(UserConfig.EnableUpdateCheck) },
|
||||
{ "Update Dismissed" , Bool(!string.IsNullOrEmpty(UserConfig.DismissedUpdate)) },
|
||||
0,
|
||||
{ "Tray" , TrayMode },
|
||||
{ "Tray Highlight" , Bool(UserConfig.EnableTrayHighlight) },
|
||||
0,
|
||||
{ "Notification Position" , NotificationPosition },
|
||||
{ "Notification Size" , NotificationSize },
|
||||
{ "Notification Timer" , NotificationTimer },
|
||||
{ "Notification Timer Speed" , RoundUp(UserConfig.NotificationDurationValue, 5) },
|
||||
{ "Notification Scroll Speed" , Exact(UserConfig.NotificationScrollSpeed) },
|
||||
{ "Notification Column Title" , Bool(UserConfig.DisplayNotificationColumn) },
|
||||
{ "Notification Media Previews" , Bool(UserConfig.NotificationMediaPreviews) },
|
||||
{ "Notification Link Skip" , Bool(UserConfig.NotificationSkipOnLinkClick) },
|
||||
{ "Notification Non-Intrusive" , Bool(UserConfig.NotificationNonIntrusiveMode) },
|
||||
{ "Notification Idle Pause" , Exact(UserConfig.NotificationIdlePauseSeconds) },
|
||||
{ "Custom Sound Notification" , string.IsNullOrEmpty(UserConfig.NotificationSoundPath) ? "off" : Path.GetExtension(UserConfig.NotificationSoundPath) },
|
||||
0,
|
||||
{ "Program Arguments" , List(ProgramArguments) },
|
||||
{ "Custom CEF Arguments" , RoundUp((UserConfig.CustomCefArgs ?? string.Empty).Length, 10) },
|
||||
{ "Custom Browser CSS" , RoundUp((UserConfig.CustomBrowserCSS ?? string.Empty).Length, 50) },
|
||||
{ "Custom Notification CSS" , RoundUp((UserConfig.CustomNotificationCSS ?? string.Empty).Length, 50) },
|
||||
0,
|
||||
{ "Plugins All" , List(plugins.Plugins.Select(plugin => plugin.Identifier)) },
|
||||
{ "Plugins Enabled" , List(plugins.Plugins.Where(plugin => plugins.Config.IsEnabled(plugin)).Select(plugin => plugin.Identifier)) },
|
||||
0,
|
||||
{ "Theme" , Dict(editLayoutDesign, "_theme", "light/def") },
|
||||
{ "Column Width" , Dict(editLayoutDesign, "columnWidth", "310px/def") },
|
||||
{ "Font Size" , Dict(editLayoutDesign, "fontSize", "12px/def") },
|
||||
{ "Large Quote Font" , Dict(editLayoutDesign, "increaseQuoteTextSize", "false/def") },
|
||||
{ "Small Compose Font" , Dict(editLayoutDesign, "smallComposeTextSize", "false/def") },
|
||||
{ "Avatar Radius" , Dict(editLayoutDesign, "avatarRadius", "2/def") },
|
||||
{ "Hide Tweet Actions" , Dict(editLayoutDesign, "hideTweetActions", "true/def") },
|
||||
{ "Move Tweet Actions" , Dict(editLayoutDesign, "moveTweetActionsToRight", "true/def") },
|
||||
{ "Theme Color Tweaks" , Dict(editLayoutDesign, "themeColorTweaks", "true/def") },
|
||||
{ "Revert Icons" , Dict(editLayoutDesign, "revertIcons", "true/def") },
|
||||
{ "Optimize Animations" , Dict(editLayoutDesign, "optimizeAnimations", "true/def") },
|
||||
{ "Reply Account Mode" , ReplyAccountConfigFromPlugin },
|
||||
{ "Template Count" , Exact(TemplateCountFromPlugin) },
|
||||
}.FinalizeReport();
|
||||
}
|
||||
|
||||
private static UserConfig UserConfig => Program.UserConfig;
|
||||
private static SystemConfig SysConfig => Program.SystemConfig;
|
||||
|
||||
private static string Bool(bool value) => value ? "on" : "off";
|
||||
private static string Exact(int value) => value.ToString();
|
||||
private static string RoundUp(int value, int multiple) => (multiple*(int)Math.Ceiling((double)value/multiple)).ToString();
|
||||
private static string Dict(Dictionary<string, string> dict, string key, string def = "(unknown)") => dict.TryGetValue(key, out string value) ? value : def;
|
||||
private static string List(IEnumerable<string> list) => string.Join("|", list.DefaultIfEmpty("(none)"));
|
||||
|
||||
private static string SystemName { get; }
|
||||
private static string SystemEdition { get; }
|
||||
private static string SystemBuild { get; }
|
||||
private static int RamSize { get; }
|
||||
private static string GpuVendor { get; }
|
||||
private static string[] ProgramArguments { get; }
|
||||
|
||||
static AnalyticsReportGenerator(){
|
||||
string osName, osEdition, osBuild;
|
||||
|
||||
try{
|
||||
using(RegistryKey key = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Windows NT\CurrentVersion", false)){
|
||||
// ReSharper disable once PossibleNullReferenceException
|
||||
osName = key.GetValue("ProductName") as string;
|
||||
osEdition = key.GetValue("EditionID") as string;
|
||||
osBuild = key.GetValue("CurrentBuild") as string;
|
||||
|
||||
if (osName != null && osEdition != null){
|
||||
osName = osName.Replace(osEdition, "").TrimEnd();
|
||||
}
|
||||
}
|
||||
}catch{
|
||||
osName = osEdition = osBuild = null;
|
||||
}
|
||||
|
||||
SystemName = osName ?? "Windows (unknown)";
|
||||
SystemEdition = osEdition ?? "(unknown)";
|
||||
SystemBuild = osBuild ?? "(unknown)";
|
||||
|
||||
try{
|
||||
using(ManagementObjectSearcher searcher = new ManagementObjectSearcher("SELECT Capacity FROM Win32_PhysicalMemory")){
|
||||
foreach(ManagementBaseObject obj in searcher.Get()){
|
||||
RamSize += (int)((ulong)obj["Capacity"]/(1024L*1024L));
|
||||
}
|
||||
}
|
||||
}catch{
|
||||
RamSize = 0;
|
||||
}
|
||||
|
||||
string gpu = null;
|
||||
|
||||
try{
|
||||
using(ManagementObjectSearcher searcher = new ManagementObjectSearcher("SELECT Caption FROM Win32_VideoController")){
|
||||
foreach(ManagementBaseObject obj in searcher.Get()){
|
||||
string vendor = obj["Caption"] as string;
|
||||
|
||||
if (!string.IsNullOrEmpty(vendor)){
|
||||
gpu = vendor;
|
||||
}
|
||||
}
|
||||
}
|
||||
}catch{
|
||||
// rip
|
||||
}
|
||||
|
||||
GpuVendor = gpu ?? "(unknown)";
|
||||
|
||||
Dictionary<string, string> args = new Dictionary<string, string>();
|
||||
Arguments.GetCurrentClean().ToDictionary(args);
|
||||
ProgramArguments = args.Keys.Select(key => key.TrimStart('-')).ToArray();
|
||||
}
|
||||
|
||||
private static string TrayMode{
|
||||
get{
|
||||
switch(UserConfig.TrayBehavior){
|
||||
case TrayIcon.Behavior.DisplayOnly: return "icon";
|
||||
case TrayIcon.Behavior.MinimizeToTray: return "minimize";
|
||||
case TrayIcon.Behavior.CloseToTray: return "close";
|
||||
case TrayIcon.Behavior.Combined: return "combined";
|
||||
default: return "off";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static string NotificationPosition{
|
||||
get{
|
||||
switch(UserConfig.NotificationPosition){
|
||||
case TweetNotification.Position.TopLeft: return "top left";
|
||||
case TweetNotification.Position.TopRight: return "top right";
|
||||
case TweetNotification.Position.BottomLeft: return "bottom left";
|
||||
case TweetNotification.Position.BottomRight: return "bottom right";
|
||||
default: return "custom";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static string NotificationSize{
|
||||
get{
|
||||
switch(UserConfig.NotificationSize){
|
||||
case TweetNotification.Size.Auto: return "auto";
|
||||
default: return RoundUp(UserConfig.CustomNotificationSize.Width, 20)+"x"+RoundUp(UserConfig.CustomNotificationSize.Height, 20);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static string NotificationTimer{
|
||||
get{
|
||||
if (!UserConfig.DisplayNotificationTimer){
|
||||
return "off";
|
||||
}
|
||||
else{
|
||||
return UserConfig.NotificationTimerCountDown ? "count down" : "count up";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static Dictionary<string, string> EditLayoutDesignPluginData{
|
||||
get{
|
||||
Dictionary<string, string> dict = new Dictionary<string, string>();
|
||||
|
||||
try{
|
||||
string data = File.ReadAllText(Path.Combine(Program.PluginDataPath, "official", "edit-design", "config.json"));
|
||||
|
||||
foreach(Match match in Regex.Matches(data, "\"(\\w+?)\":(.*?)[,}]")){
|
||||
dict[match.Groups[1].Value] = match.Groups[2].Value.Trim('"');
|
||||
}
|
||||
}catch{
|
||||
// rip
|
||||
}
|
||||
|
||||
return dict;
|
||||
}
|
||||
}
|
||||
|
||||
private static int TemplateCountFromPlugin{
|
||||
get{
|
||||
try{
|
||||
string data = File.ReadAllText(Path.Combine(Program.PluginDataPath, "official", "templates", "config.json"));
|
||||
return Math.Min(StringUtils.CountOccurrences(data, "{\"name\":"), StringUtils.CountOccurrences(data, ",\"contents\":"));
|
||||
}catch{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static string ReplyAccountConfigFromPlugin{
|
||||
get{
|
||||
try{
|
||||
string data = File.ReadAllText(Path.Combine(Program.PluginDataPath, "official", "reply-account", "configuration.js")).Replace(" ", "");
|
||||
|
||||
Match matchType = Regex.Match(data, "defaultAccount:\"([#@])(.*?)\"(?:,|$)");
|
||||
Match matchAdvanced = Regex.Match(data, "useAdvancedSelector:(.*?)(?:,|$)", RegexOptions.Multiline);
|
||||
|
||||
if (!matchType.Success){
|
||||
return "(unknown)";
|
||||
}
|
||||
|
||||
string accType = matchType.Groups[1].Value == "#" ? matchType.Groups[2].Value : "account";
|
||||
return matchAdvanced.Success && !matchAdvanced.Value.Contains("false") ? "advanced/"+accType : accType;
|
||||
}catch{
|
||||
return "(unknown)";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class ExternalInfo{
|
||||
public static ExternalInfo From(Form form){
|
||||
if (form == null){
|
||||
return new ExternalInfo();
|
||||
}
|
||||
else{
|
||||
Screen screen = Screen.FromControl(form);
|
||||
int dpi;
|
||||
|
||||
using(Graphics graphics = form.CreateGraphics()){
|
||||
dpi = (int)graphics.DpiY;
|
||||
}
|
||||
|
||||
return new ExternalInfo{
|
||||
Resolution = screen.Bounds.Width+"x"+screen.Bounds.Height,
|
||||
DPI = dpi
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public string Resolution { get; private set; }
|
||||
public int? DPI { get; private set; }
|
||||
|
||||
private ExternalInfo(){}
|
||||
}
|
||||
}
|
||||
}
|
@ -4,6 +4,7 @@
|
||||
using System.Windows.Forms;
|
||||
using TweetDuck.Core.Controls;
|
||||
using TweetDuck.Core.Notification.Example;
|
||||
using TweetDuck.Core.Other.Analytics;
|
||||
using TweetDuck.Core.Other.Settings;
|
||||
using TweetDuck.Core.Other.Settings.Dialogs;
|
||||
using TweetDuck.Core.Utils;
|
||||
@ -22,7 +23,7 @@ sealed partial class FormSettings : Form{
|
||||
|
||||
public bool ShouldReloadBrowser { get; private set; }
|
||||
|
||||
public FormSettings(FormBrowser browser, PluginManager plugins, UpdateHandler updates, Type startTab){
|
||||
public FormSettings(FormBrowser browser, PluginManager plugins, UpdateHandler updates, AnalyticsManager analytics, Type startTab){
|
||||
InitializeComponent();
|
||||
|
||||
Text = Program.BrandName+" Options";
|
||||
@ -38,16 +39,17 @@ public FormSettings(FormBrowser browser, PluginManager plugins, UpdateHandler up
|
||||
AddButton("System Tray", () => new TabSettingsTray());
|
||||
AddButton("Notifications", () => new TabSettingsNotifications(new FormNotificationExample(browser, plugins)));
|
||||
AddButton("Sounds", () => new TabSettingsSounds());
|
||||
AddButton("Feedback", () => new TabSettingsFeedback());
|
||||
AddButton("Advanced", () => new TabSettingsAdvanced(browser.ReinjectCustomCSS));
|
||||
AddButton("Feedback", () => new TabSettingsFeedback(analytics, AnalyticsReportGenerator.ExternalInfo.From(this.browser), this.plugins));
|
||||
AddButton("Advanced", () => new TabSettingsAdvanced(this.browser.ReinjectCustomCSS));
|
||||
|
||||
SelectTab(tabs[startTab ?? typeof(TabSettingsGeneral)]);
|
||||
}
|
||||
|
||||
private void FormSettings_FormClosing(object sender, FormClosingEventArgs e){
|
||||
currentTab.Control.OnClosing();
|
||||
|
||||
foreach(SettingsTab tab in tabs.Values){
|
||||
if (tab.IsInitialized){
|
||||
tab.Control.OnClosing();
|
||||
tab.Control.Dispose();
|
||||
}
|
||||
}
|
||||
@ -57,11 +59,7 @@ private void FormSettings_FormClosing(object sender, FormClosingEventArgs e){
|
||||
}
|
||||
|
||||
private void btnManageOptions_Click(object sender, EventArgs e){
|
||||
foreach(SettingsTab tab in tabs.Values){
|
||||
if (tab.IsInitialized){
|
||||
tab.Control.OnClosing();
|
||||
}
|
||||
}
|
||||
currentTab.Control.OnClosing();
|
||||
|
||||
using(DialogSettingsManage dialog = new DialogSettingsManage(plugins)){
|
||||
if (dialog.ShowDialog() == DialogResult.OK){
|
||||
@ -114,6 +112,7 @@ private void SelectTab<T>() where T : BaseTabSettings{
|
||||
private void SelectTab(SettingsTab tab){
|
||||
if (currentTab != null){
|
||||
currentTab.Button.BackColor = SystemColors.Control;
|
||||
currentTab.Control.OnClosing();
|
||||
}
|
||||
|
||||
tab.Button.BackColor = tab.Button.FlatAppearance.MouseDownBackColor;
|
||||
|
90
Core/Other/Settings/Dialogs/DialogSettingsAnalytics.Designer.cs
generated
Normal file
90
Core/Other/Settings/Dialogs/DialogSettingsAnalytics.Designer.cs
generated
Normal file
@ -0,0 +1,90 @@
|
||||
namespace TweetDuck.Core.Other.Settings.Dialogs {
|
||||
partial class DialogSettingsAnalytics {
|
||||
/// <summary>
|
||||
/// Required designer variable.
|
||||
/// </summary>
|
||||
private System.ComponentModel.IContainer components = null;
|
||||
|
||||
/// <summary>
|
||||
/// Clean up any resources being used.
|
||||
/// </summary>
|
||||
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
|
||||
protected override void Dispose(bool disposing) {
|
||||
if (disposing && (components != null)) {
|
||||
components.Dispose();
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
#region Windows Form Designer generated code
|
||||
|
||||
/// <summary>
|
||||
/// Required method for Designer support - do not modify
|
||||
/// the contents of this method with the code editor.
|
||||
/// </summary>
|
||||
private void InitializeComponent() {
|
||||
this.textBoxReport = new System.Windows.Forms.TextBox();
|
||||
this.btnClose = new System.Windows.Forms.Button();
|
||||
this.labelInfo = new System.Windows.Forms.Label();
|
||||
this.SuspendLayout();
|
||||
//
|
||||
// textBoxReport
|
||||
//
|
||||
this.textBoxReport.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
|
||||
| System.Windows.Forms.AnchorStyles.Left)
|
||||
| System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.textBoxReport.Font = new System.Drawing.Font("Courier New", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238)));
|
||||
this.textBoxReport.Location = new System.Drawing.Point(12, 41);
|
||||
this.textBoxReport.Multiline = true;
|
||||
this.textBoxReport.Name = "textBoxReport";
|
||||
this.textBoxReport.ReadOnly = true;
|
||||
this.textBoxReport.ScrollBars = System.Windows.Forms.ScrollBars.Both;
|
||||
this.textBoxReport.Size = new System.Drawing.Size(460, 480);
|
||||
this.textBoxReport.TabIndex = 1;
|
||||
//
|
||||
// btnClose
|
||||
//
|
||||
this.btnClose.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.btnClose.Location = new System.Drawing.Point(416, 527);
|
||||
this.btnClose.Name = "btnClose";
|
||||
this.btnClose.Padding = new System.Windows.Forms.Padding(3, 0, 3, 0);
|
||||
this.btnClose.Size = new System.Drawing.Size(56, 23);
|
||||
this.btnClose.TabIndex = 2;
|
||||
this.btnClose.Text = "Close";
|
||||
this.btnClose.UseVisualStyleBackColor = true;
|
||||
this.btnClose.Click += new System.EventHandler(this.btnClose_Click);
|
||||
//
|
||||
// labelInfo
|
||||
//
|
||||
this.labelInfo.Location = new System.Drawing.Point(12, 9);
|
||||
this.labelInfo.Margin = new System.Windows.Forms.Padding(3, 0, 3, 3);
|
||||
this.labelInfo.Name = "labelInfo";
|
||||
this.labelInfo.Size = new System.Drawing.Size(460, 26);
|
||||
this.labelInfo.TabIndex = 0;
|
||||
this.labelInfo.Text = "When enabled, this data will be sent over a secure network roughly once every wee" +
|
||||
"k.\r\nSome numbers in the report were made imprecise on purpose.";
|
||||
//
|
||||
// DialogSettingsAnalytics
|
||||
//
|
||||
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
|
||||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
||||
this.ClientSize = new System.Drawing.Size(484, 562);
|
||||
this.Controls.Add(this.labelInfo);
|
||||
this.Controls.Add(this.btnClose);
|
||||
this.Controls.Add(this.textBoxReport);
|
||||
this.MinimumSize = new System.Drawing.Size(450, 340);
|
||||
this.Name = "DialogSettingsAnalytics";
|
||||
this.ShowIcon = false;
|
||||
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
|
||||
this.ResumeLayout(false);
|
||||
this.PerformLayout();
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private System.Windows.Forms.TextBox textBoxReport;
|
||||
private System.Windows.Forms.Button btnClose;
|
||||
private System.Windows.Forms.Label labelInfo;
|
||||
}
|
||||
}
|
24
Core/Other/Settings/Dialogs/DialogSettingsAnalytics.cs
Normal file
24
Core/Other/Settings/Dialogs/DialogSettingsAnalytics.cs
Normal file
@ -0,0 +1,24 @@
|
||||
using System;
|
||||
using System.Windows.Forms;
|
||||
using TweetDuck.Core.Controls;
|
||||
using TweetDuck.Core.Other.Analytics;
|
||||
|
||||
namespace TweetDuck.Core.Other.Settings.Dialogs{
|
||||
sealed partial class DialogSettingsAnalytics : Form{
|
||||
public string CefArgs => textBoxReport.Text;
|
||||
|
||||
public DialogSettingsAnalytics(AnalyticsReport report){
|
||||
InitializeComponent();
|
||||
|
||||
Text = Program.BrandName+" Options - Analytics Report";
|
||||
|
||||
textBoxReport.EnableMultilineShortcuts();
|
||||
textBoxReport.Text = report.ToString().TrimEnd();
|
||||
textBoxReport.Select(0, 0);
|
||||
}
|
||||
|
||||
private void btnClose_Click(object sender, EventArgs e){
|
||||
Close();
|
||||
}
|
||||
}
|
||||
}
|
56
Core/Other/Settings/TabSettingsFeedback.Designer.cs
generated
56
Core/Other/Settings/TabSettingsFeedback.Designer.cs
generated
@ -25,12 +25,14 @@ protected override void Dispose(bool disposing) {
|
||||
private void InitializeComponent() {
|
||||
this.components = new System.ComponentModel.Container();
|
||||
this.panelFeedback = new System.Windows.Forms.Panel();
|
||||
this.labelDataCollectionMessage = new System.Windows.Forms.Label();
|
||||
this.btnViewReport = new System.Windows.Forms.Button();
|
||||
this.btnSendFeedback = new System.Windows.Forms.Button();
|
||||
this.labelDataCollectionLink = new System.Windows.Forms.LinkLabel();
|
||||
this.checkDataCollection = new System.Windows.Forms.CheckBox();
|
||||
this.labelDataCollection = new System.Windows.Forms.Label();
|
||||
this.labelFeedback = new System.Windows.Forms.Label();
|
||||
this.toolTip = new System.Windows.Forms.ToolTip(this.components);
|
||||
this.btnSendFeedback = new System.Windows.Forms.Button();
|
||||
this.panelFeedback.SuspendLayout();
|
||||
this.SuspendLayout();
|
||||
//
|
||||
@ -38,15 +40,48 @@ private void InitializeComponent() {
|
||||
//
|
||||
this.panelFeedback.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
|
||||
| System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.panelFeedback.Controls.Add(this.labelDataCollectionMessage);
|
||||
this.panelFeedback.Controls.Add(this.btnViewReport);
|
||||
this.panelFeedback.Controls.Add(this.btnSendFeedback);
|
||||
this.panelFeedback.Controls.Add(this.labelDataCollectionLink);
|
||||
this.panelFeedback.Controls.Add(this.checkDataCollection);
|
||||
this.panelFeedback.Controls.Add(this.labelDataCollection);
|
||||
this.panelFeedback.Location = new System.Drawing.Point(9, 31);
|
||||
this.panelFeedback.Name = "panelFeedback";
|
||||
this.panelFeedback.Size = new System.Drawing.Size(322, 80);
|
||||
this.panelFeedback.Size = new System.Drawing.Size(322, 188);
|
||||
this.panelFeedback.TabIndex = 1;
|
||||
//
|
||||
// labelDataCollectionMessage
|
||||
//
|
||||
this.labelDataCollectionMessage.Location = new System.Drawing.Point(6, 114);
|
||||
this.labelDataCollectionMessage.Margin = new System.Windows.Forms.Padding(6);
|
||||
this.labelDataCollectionMessage.Name = "labelDataCollectionMessage";
|
||||
this.labelDataCollectionMessage.Size = new System.Drawing.Size(310, 67);
|
||||
this.labelDataCollectionMessage.TabIndex = 5;
|
||||
//
|
||||
// btnViewReport
|
||||
//
|
||||
this.btnViewReport.AutoSize = true;
|
||||
this.btnViewReport.Location = new System.Drawing.Point(6, 82);
|
||||
this.btnViewReport.Margin = new System.Windows.Forms.Padding(5, 3, 3, 3);
|
||||
this.btnViewReport.Name = "btnViewReport";
|
||||
this.btnViewReport.Size = new System.Drawing.Size(144, 23);
|
||||
this.btnViewReport.TabIndex = 4;
|
||||
this.btnViewReport.Text = "View My Analytics Report";
|
||||
this.btnViewReport.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// btnSendFeedback
|
||||
//
|
||||
this.btnSendFeedback.AutoSize = true;
|
||||
this.btnSendFeedback.Location = new System.Drawing.Point(5, 3);
|
||||
this.btnSendFeedback.Margin = new System.Windows.Forms.Padding(5, 3, 3, 3);
|
||||
this.btnSendFeedback.Name = "btnSendFeedback";
|
||||
this.btnSendFeedback.Padding = new System.Windows.Forms.Padding(3, 0, 3, 0);
|
||||
this.btnSendFeedback.Size = new System.Drawing.Size(164, 23);
|
||||
this.btnSendFeedback.TabIndex = 0;
|
||||
this.btnSendFeedback.Text = "Send Feedback / Bug Report";
|
||||
this.btnSendFeedback.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// labelDataCollectionLink
|
||||
//
|
||||
this.labelDataCollectionLink.AutoSize = true;
|
||||
@ -60,7 +95,6 @@ private void InitializeComponent() {
|
||||
this.labelDataCollectionLink.TabStop = true;
|
||||
this.labelDataCollectionLink.Text = "(learn more)";
|
||||
this.labelDataCollectionLink.UseCompatibleTextRendering = true;
|
||||
this.labelDataCollectionLink.LinkClicked += new System.Windows.Forms.LinkLabelLinkClickedEventHandler(this.labelDataCollectionLink_LinkClicked);
|
||||
//
|
||||
// checkDataCollection
|
||||
//
|
||||
@ -94,18 +128,6 @@ private void InitializeComponent() {
|
||||
this.labelFeedback.TabIndex = 0;
|
||||
this.labelFeedback.Text = "Feedback";
|
||||
//
|
||||
// btnSendFeedback
|
||||
//
|
||||
this.btnSendFeedback.AutoSize = true;
|
||||
this.btnSendFeedback.Location = new System.Drawing.Point(5, 3);
|
||||
this.btnSendFeedback.Margin = new System.Windows.Forms.Padding(5, 3, 3, 3);
|
||||
this.btnSendFeedback.Name = "btnSendFeedback";
|
||||
this.btnSendFeedback.Padding = new System.Windows.Forms.Padding(3, 0, 3, 0);
|
||||
this.btnSendFeedback.Size = new System.Drawing.Size(164, 23);
|
||||
this.btnSendFeedback.TabIndex = 0;
|
||||
this.btnSendFeedback.Text = "Send Feedback / Bug Report";
|
||||
this.btnSendFeedback.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// TabSettingsFeedback
|
||||
//
|
||||
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
|
||||
@ -113,7 +135,7 @@ private void InitializeComponent() {
|
||||
this.Controls.Add(this.panelFeedback);
|
||||
this.Controls.Add(this.labelFeedback);
|
||||
this.Name = "TabSettingsFeedback";
|
||||
this.Size = new System.Drawing.Size(340, 122);
|
||||
this.Size = new System.Drawing.Size(340, 230);
|
||||
this.panelFeedback.ResumeLayout(false);
|
||||
this.panelFeedback.PerformLayout();
|
||||
this.ResumeLayout(false);
|
||||
@ -130,5 +152,7 @@ private void InitializeComponent() {
|
||||
private System.Windows.Forms.ToolTip toolTip;
|
||||
private System.Windows.Forms.LinkLabel labelDataCollectionLink;
|
||||
private System.Windows.Forms.Button btnSendFeedback;
|
||||
private System.Windows.Forms.Button btnViewReport;
|
||||
private System.Windows.Forms.Label labelDataCollectionMessage;
|
||||
}
|
||||
}
|
||||
|
@ -1,18 +1,34 @@
|
||||
using System;
|
||||
using System.Windows.Forms;
|
||||
using TweetDuck.Core.Other.Analytics;
|
||||
using TweetDuck.Core.Other.Settings.Dialogs;
|
||||
using TweetDuck.Core.Utils;
|
||||
using TweetDuck.Plugins;
|
||||
|
||||
namespace TweetDuck.Core.Other.Settings{
|
||||
sealed partial class TabSettingsFeedback : BaseTabSettings{
|
||||
public TabSettingsFeedback(){
|
||||
private readonly AnalyticsReportGenerator.ExternalInfo analyticsInfo;
|
||||
private readonly PluginManager plugins;
|
||||
|
||||
public TabSettingsFeedback(AnalyticsManager analytics, AnalyticsReportGenerator.ExternalInfo analyticsInfo, PluginManager plugins){
|
||||
InitializeComponent();
|
||||
|
||||
this.analyticsInfo = analyticsInfo;
|
||||
this.plugins = plugins;
|
||||
|
||||
checkDataCollection.Checked = Config.AllowDataCollection;
|
||||
|
||||
if (analytics != null){
|
||||
string collectionTime = analytics.LastCollectionMessage;
|
||||
labelDataCollectionMessage.Text = string.IsNullOrEmpty(collectionTime) ? "No collection yet" : "Last collection: "+collectionTime;
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnReady(){
|
||||
btnSendFeedback.Click += btnSendFeedback_Click;
|
||||
checkDataCollection.CheckedChanged += checkDataCollection_CheckedChanged;
|
||||
labelDataCollectionLink.LinkClicked += labelDataCollectionLink_LinkClicked;
|
||||
btnViewReport.Click += btnViewReport_Click;
|
||||
}
|
||||
|
||||
private void btnSendFeedback_Click(object sender, EventArgs e){
|
||||
@ -26,5 +42,11 @@ private void checkDataCollection_CheckedChanged(object sender, EventArgs e){
|
||||
private void labelDataCollectionLink_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e){
|
||||
BrowserUtils.OpenExternalBrowser("https://github.com/chylex/TweetDuck/wiki/Send-anonymous-data");
|
||||
}
|
||||
|
||||
private void btnViewReport_Click(object sender, EventArgs e){
|
||||
using(DialogSettingsAnalytics dialog = new DialogSettingsAnalytics(AnalyticsReportGenerator.Create(analyticsInfo, plugins))){
|
||||
dialog.ShowDialog();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -95,9 +95,14 @@ public static string GetErrorName(CefErrorCode code){
|
||||
return StringUtils.ConvertPascalCaseToScreamingSnakeCase(Enum.GetName(typeof(CefErrorCode), code) ?? string.Empty);
|
||||
}
|
||||
|
||||
public static WebClient DownloadFileAsync(string url, string target, Action onSuccess, Action<Exception> onFailure){
|
||||
public static WebClient CreateWebClient(){
|
||||
WebClient client = new WebClient{ Proxy = null };
|
||||
client.Headers[HttpRequestHeader.UserAgent] = HeaderUserAgent;
|
||||
return client;
|
||||
}
|
||||
|
||||
public static WebClient DownloadFileAsync(string url, string target, Action onSuccess, Action<Exception> onFailure){
|
||||
WebClient client = CreateWebClient();
|
||||
|
||||
client.DownloadFileCompleted += (sender, args) => {
|
||||
if (args.Cancelled){
|
||||
|
@ -18,5 +18,16 @@ public static int[] ParseInts(string str, char separator){
|
||||
public static string ConvertPascalCaseToScreamingSnakeCase(string str){
|
||||
return Regex.Replace(str, @"(\p{Ll})(\P{Ll})|(\P{Ll})(\P{Ll}\p{Ll})", "$1$3_$2$4").ToUpper();
|
||||
}
|
||||
|
||||
public static int CountOccurrences(string source, string substring){
|
||||
int count = 0, index = 0;
|
||||
|
||||
while((index = source.IndexOf(substring, index)) != -1){
|
||||
index += substring.Length;
|
||||
++count;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -36,6 +36,7 @@ static class Program{
|
||||
public static string UserConfigFilePath => Path.Combine(StoragePath, "TD_UserConfig.cfg");
|
||||
public static string SystemConfigFilePath => Path.Combine(StoragePath, "TD_SystemConfig.cfg");
|
||||
public static string PluginConfigFilePath => Path.Combine(StoragePath, "TD_PluginConfig.cfg");
|
||||
public static string AnalyticsFilePath => Path.Combine(StoragePath, "TD_Analytics.cfg");
|
||||
|
||||
private static string ErrorLogFilePath => Path.Combine(StoragePath, "TD_Log.txt");
|
||||
private static string ConsoleLogFilePath => Path.Combine(StoragePath, "TD_Console.txt");
|
||||
|
@ -6,6 +6,7 @@ enabled(){
|
||||
this.config = null;
|
||||
|
||||
this.defaultConfig = {
|
||||
_theme: "light",
|
||||
columnWidth: "310px",
|
||||
fontSize: "12px",
|
||||
hideTweetActions: true,
|
||||
@ -38,11 +39,13 @@ enabled(){
|
||||
this.currentStage = 1;
|
||||
}
|
||||
else if (this.tmpConfig !== null){
|
||||
let needsResave = !("_theme" in this.tmpConfig);
|
||||
|
||||
this.config = $.extend(this.defaultConfig, this.tmpConfig);
|
||||
this.tmpConfig = null;
|
||||
this.reinjectAll();
|
||||
|
||||
if (this.firstTimeLoad){
|
||||
if (this.firstTimeLoad || needsResave){
|
||||
$TDP.writeFile(this.$token, configFile, JSON.stringify(this.config));
|
||||
}
|
||||
}
|
||||
@ -60,6 +63,8 @@ enabled(){
|
||||
}
|
||||
else{
|
||||
$(document).one("dataSettingsValues", () => {
|
||||
this.defaultConfig._theme = TD.settings.getTheme();
|
||||
|
||||
switch(TD.settings.getColumnWidth()){
|
||||
case "wide": this.defaultConfig.columnWidth = "350px"; break;
|
||||
case "narrow": this.defaultConfig.columnWidth = "270px"; break;
|
||||
@ -234,9 +239,11 @@ enabled(){
|
||||
modal.find("[data-td-theme='"+TD.settings.getTheme()+"']").prop("checked", true);
|
||||
|
||||
modal.find("[data-td-theme]").change(function(){
|
||||
me.config._theme = $(this).attr("data-td-theme");
|
||||
|
||||
setTimeout(function(){
|
||||
TD.settings.setTheme($(this).attr("data-td-theme"));
|
||||
$(document).trigger("uiToggleTheme");
|
||||
me.saveConfig();
|
||||
me.reinjectAll();
|
||||
}, 1);
|
||||
});
|
||||
|
@ -131,6 +131,10 @@
|
||||
</Compile>
|
||||
<Compile Include="Core\Notification\SoundNotification.cs" />
|
||||
<Compile Include="Core\Notification\TweetNotification.cs" />
|
||||
<Compile Include="Core\Other\Analytics\AnalyticsFile.cs" />
|
||||
<Compile Include="Core\Other\Analytics\AnalyticsManager.cs" />
|
||||
<Compile Include="Core\Other\Analytics\AnalyticsReport.cs" />
|
||||
<Compile Include="Core\Other\Analytics\AnalyticsReportGenerator.cs" />
|
||||
<Compile Include="Core\Other\FormAbout.cs">
|
||||
<SubType>Form</SubType>
|
||||
</Compile>
|
||||
@ -156,6 +160,12 @@
|
||||
<DependentUpon>FormPlugins.cs</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Core\Other\Management\VideoPlayer.cs" />
|
||||
<Compile Include="Core\Other\Settings\Dialogs\DialogSettingsAnalytics.cs">
|
||||
<SubType>Form</SubType>
|
||||
</Compile>
|
||||
<Compile Include="Core\Other\Settings\Dialogs\DialogSettingsAnalytics.Designer.cs">
|
||||
<DependentUpon>DialogSettingsAnalytics.cs</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Core\Other\Settings\Dialogs\DialogSettingsCSS.cs">
|
||||
<SubType>Form</SubType>
|
||||
</Compile>
|
||||
|
Loading…
Reference in New Issue
Block a user