313 lines
16 KiB
C#
313 lines
16 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.ComponentModel;
|
|
using System.Diagnostics;
|
|
using System.Globalization;
|
|
using System.Linq;
|
|
using SingularityGroup.HotReload.Editor.Localization;
|
|
using UnityEditor;
|
|
using UnityEngine;
|
|
using System.Threading.Tasks;
|
|
using System.IO;
|
|
using SingularityGroup.HotReload.Newtonsoft.Json;
|
|
using SingularityGroup.HotReload.EditorDependencies;
|
|
|
|
namespace SingularityGroup.HotReload.Editor {
|
|
internal struct HotReloadAboutTabState {
|
|
public readonly bool logsFodlerExists;
|
|
public readonly IReadOnlyList<ChangelogVersion> changelog;
|
|
public readonly bool loginRequired;
|
|
public readonly bool hasTrialLicense;
|
|
public readonly bool hasPayedLicense;
|
|
|
|
public HotReloadAboutTabState(
|
|
bool logsFodlerExists,
|
|
IReadOnlyList<ChangelogVersion> changelog,
|
|
bool loginRequired,
|
|
bool hasTrialLicense,
|
|
bool hasPayedLicense
|
|
) {
|
|
this.logsFodlerExists = logsFodlerExists;
|
|
this.changelog = changelog;
|
|
this.loginRequired = loginRequired;
|
|
this.hasTrialLicense = hasTrialLicense;
|
|
this.hasPayedLicense = hasPayedLicense;
|
|
}
|
|
}
|
|
|
|
internal class HotReloadAboutTab : HotReloadTabBase {
|
|
internal static readonly OpenURLButton seeMore = new OpenURLButton(Translations.About.ButtonSeeMore, Constants.ChangelogURL);
|
|
internal static readonly OpenDialogueButton manageLicenseButton = new OpenDialogueButton(Translations.About.ButtonManageLicense, Constants.ManageLicenseURL, Translations.About.ButtonManageLicense, Translations.Dialogs.DialogManageLicenseMessage, Translations.Common.ButtonOpenInBrowser, Translations.Common.ButtonCancel);
|
|
internal static readonly OpenDialogueButton manageAccountButton = new OpenDialogueButton(Translations.About.ButtonManageAccount, Constants.ManageAccountURL, Translations.About.ButtonManageAccount, Translations.Dialogs.DialogManageAccountMessage, Translations.Common.ButtonOpenInBrowser, Translations.Common.ButtonCancel);
|
|
internal static readonly OpenURLButton contactButton = new OpenURLButton(Translations.About.ButtonContact, Constants.ContactURL);
|
|
internal static readonly OpenURLButton discordButton = new OpenURLButton(Translations.About.ButtonJoinDiscord, Constants.DiscordInviteUrl);
|
|
internal static readonly OpenDialogueButton reportIssueButton = new OpenDialogueButton(Translations.About.ButtonReportIssue, Constants.ReportIssueURL, Translations.About.ButtonReportIssue, Translations.Dialogs.DialogReportIssueMessage, Translations.Common.ButtonOpenInBrowser, Translations.Common.ButtonCancel);
|
|
|
|
private Vector2 _changelogScroll;
|
|
private IReadOnlyList<ChangelogVersion> _changelog = new List<ChangelogVersion>();
|
|
private bool _requestedChangelog;
|
|
private int _changelogRequestAttempt;
|
|
private string _changelogDir = Path.Combine(PackageConst.LibraryCachePath, "changelog.json");
|
|
public static string logsPath = Path.Combine(PackageConst.LibraryCachePath, "logs");
|
|
|
|
private static bool LatestChangelogLoaded(IReadOnlyList<ChangelogVersion> changelog) {
|
|
return changelog.Any() && changelog[0].versionNum == PackageUpdateChecker.lastRemotePackageVersion;
|
|
}
|
|
|
|
private async Task FetchChangelog() {
|
|
if(!_changelog.Any()) {
|
|
var file = new FileInfo(_changelogDir);
|
|
if (file.Exists) {
|
|
await Task.Run(() => {
|
|
var bytes = File.ReadAllText(_changelogDir);
|
|
_changelog = JsonConvert.DeserializeObject<List<ChangelogVersion>>(bytes);
|
|
});
|
|
}
|
|
}
|
|
if (_requestedChangelog || LatestChangelogLoaded(_changelog)) {
|
|
return;
|
|
}
|
|
_requestedChangelog = true;
|
|
try {
|
|
do {
|
|
var changelogRequestTimeout = ExponentialBackoff.GetTimeout(_changelogRequestAttempt);
|
|
_changelog = await RequestHelper.FetchChangelog() ?? _changelog;
|
|
if (LatestChangelogLoaded(_changelog)) {
|
|
await Task.Run(() => {
|
|
Directory.CreateDirectory(PackageConst.LibraryCachePath);
|
|
File.WriteAllText(_changelogDir, JsonConvert.SerializeObject(_changelog));
|
|
});
|
|
Repaint();
|
|
return;
|
|
}
|
|
await Task.Delay(changelogRequestTimeout);
|
|
} while (_changelogRequestAttempt++ < 1000 && !LatestChangelogLoaded(_changelog));
|
|
} catch {
|
|
// ignore
|
|
} finally {
|
|
_requestedChangelog = false;
|
|
}
|
|
}
|
|
|
|
public HotReloadAboutTab(HotReloadWindow window) : base(window, Translations.About.AboutTitle, "_Help", Translations.About.AboutDescription) { }
|
|
|
|
string GetRelativeDate(DateTime givenDate) {
|
|
const int second = 1;
|
|
const int minute = 60 * second;
|
|
const int hour = 60 * minute;
|
|
const int day = 24 * hour;
|
|
const int month = 30 * day;
|
|
|
|
var ts = new TimeSpan(DateTime.UtcNow.Ticks - givenDate.Ticks);
|
|
var delta = Math.Abs(ts.TotalSeconds);
|
|
|
|
if (delta < 24 * hour)
|
|
return Translations.About.AboutToday;
|
|
|
|
if (delta < 48 * hour)
|
|
return Translations.About.AboutYesterday;
|
|
|
|
if (delta < 30 * day)
|
|
return string.Format(Translations.About.AboutDaysAgo, ts.Days);
|
|
|
|
if (delta < 12 * month) {
|
|
var months = Convert.ToInt32(Math.Floor((double)ts.Days / 30));
|
|
return months <= 1 ? Translations.About.AboutOneMonthAgo : string.Format(Translations.About.AboutMonthsAgo, months);
|
|
}
|
|
var years = Convert.ToInt32(Math.Floor((double)ts.Days / 365));
|
|
return years <= 1 ? Translations.About.AboutOneYearAgo : string.Format(Translations.About.AboutYearsAgo, years);
|
|
}
|
|
|
|
void RenderVersion(ChangelogVersion version) {
|
|
var tempTextString = "";
|
|
|
|
//version number
|
|
EditorGUILayout.TextArea(version.versionNum, HotReloadWindowStyles.H1TitleStyle);
|
|
|
|
//general info
|
|
if (version.generalInfo != null) {
|
|
EditorGUILayout.TextArea(version.generalInfo, HotReloadWindowStyles.H3TitleStyle);
|
|
}
|
|
|
|
//features
|
|
if (version.features != null) {
|
|
EditorGUILayout.TextArea(Translations.About.AboutFeatures, HotReloadWindowStyles.H2TitleStyle);
|
|
tempTextString = "";
|
|
foreach (var feature in version.features) {
|
|
tempTextString += "• " + feature + "\n";
|
|
}
|
|
EditorGUILayout.TextArea(tempTextString, HotReloadWindowStyles.ChangelogPointerStyle);
|
|
}
|
|
|
|
//improvements
|
|
if (version.improvements != null) {
|
|
EditorGUILayout.TextArea(Translations.About.AboutImprovements, HotReloadWindowStyles.H2TitleStyle);
|
|
tempTextString = "";
|
|
foreach (var improvement in version.improvements) {
|
|
tempTextString += "• " + improvement + "\n";
|
|
}
|
|
EditorGUILayout.TextArea(tempTextString, HotReloadWindowStyles.ChangelogPointerStyle);
|
|
}
|
|
|
|
//fixes
|
|
if (version.fixes != null) {
|
|
EditorGUILayout.TextArea(Translations.About.AboutFixes, HotReloadWindowStyles.H2TitleStyle);
|
|
tempTextString = "";
|
|
foreach (var fix in version.fixes) {
|
|
tempTextString += "• " + fix + "\n";
|
|
}
|
|
EditorGUILayout.TextArea(tempTextString, HotReloadWindowStyles.ChangelogPointerStyle);
|
|
}
|
|
|
|
//date
|
|
DateTime date;
|
|
if (DateTime.TryParseExact(version.date, "dd/MM/yyyy", null, DateTimeStyles.None, out date)) {
|
|
var relativeDate = GetRelativeDate(date);
|
|
GUILayout.TextArea(relativeDate, HotReloadWindowStyles.H3TitleStyle);
|
|
}
|
|
}
|
|
|
|
void RenderChangelog() {
|
|
FetchChangelog().Forget();
|
|
using (new EditorGUILayout.HorizontalScope(HotReloadWindowStyles.SectionInnerBoxWide)) {
|
|
using (new EditorGUILayout.VerticalScope()) {
|
|
HotReloadPrefs.ShowChangeLog = EditorGUILayout.Foldout(HotReloadPrefs.ShowChangeLog, Translations.Miscellaneous.ChangelogTitle, true, HotReloadWindowStyles.FoldoutStyle);
|
|
if (!HotReloadPrefs.ShowChangeLog) {
|
|
return;
|
|
}
|
|
// changelog versions
|
|
var maxChangeLogs = 5;
|
|
var index = 0;
|
|
foreach (var version in currentState.changelog) {
|
|
index++;
|
|
if (index > maxChangeLogs) {
|
|
break;
|
|
}
|
|
|
|
using (new EditorGUILayout.HorizontalScope(HotReloadWindowStyles.ChangelogSectionInnerBox)) {
|
|
using (new EditorGUILayout.VerticalScope()) {
|
|
RenderVersion(version);
|
|
}
|
|
}
|
|
}
|
|
// see more button
|
|
using (new EditorGUILayout.HorizontalScope(HotReloadWindowStyles.ChangelogSectionInnerBox)) {
|
|
seeMore.OnGUI();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private Vector2 _aboutTabScrollPos;
|
|
|
|
HotReloadAboutTabState currentState;
|
|
public override void OnGUI() {
|
|
// HotReloadAboutTabState ensures rendering is consistent between Layout and Repaint calls
|
|
// Without it errors like this happen:
|
|
// ArgumentException: Getting control 2's position in a group with only 2 controls when doing repaint
|
|
// See thread for more context: https://answers.unity.com/questions/17718/argumentexception-getting-control-2s-position-in-a.html
|
|
if (Event.current.type == EventType.Layout) {
|
|
currentState = new HotReloadAboutTabState(
|
|
logsFodlerExists: Directory.Exists(logsPath),
|
|
changelog: _changelog,
|
|
loginRequired: EditorCodePatcher.LoginNotRequired,
|
|
hasTrialLicense: _window.RunTab.TrialLicense,
|
|
hasPayedLicense: _window.RunTab.HasPayedLicense
|
|
);
|
|
}
|
|
using (var scope = new EditorGUILayout.ScrollViewScope(_aboutTabScrollPos, GUI.skin.horizontalScrollbar, GUI.skin.verticalScrollbar, GUILayout.MaxHeight(Math.Max(HotReloadWindowStyles.windowScreenHeight, 800)), GUILayout.MaxWidth(Math.Max(HotReloadWindowStyles.windowScreenWidth, 800)))) {
|
|
_aboutTabScrollPos.x = scope.scrollPosition.x;
|
|
_aboutTabScrollPos.y = scope.scrollPosition.y;
|
|
|
|
using (new EditorGUILayout.VerticalScope(HotReloadWindowStyles.DynamicSectionHelpTab)) {
|
|
using (new EditorGUILayout.VerticalScope()) {
|
|
GUILayout.Space(10);
|
|
RenderLogButtons();
|
|
|
|
EditorGUILayout.Space();
|
|
EditorGUILayout.HelpBox(string.Format(Translations.About.AboutVersionInfo, PackageConst.Version), MessageType.Info);
|
|
EditorGUILayout.Space();
|
|
|
|
RenderHelpButtons();
|
|
|
|
GUILayout.Space(15);
|
|
|
|
try {
|
|
RenderChangelog();
|
|
} catch {
|
|
// ignore
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void RenderHelpButtons() {
|
|
var labelRect = GUILayoutUtility.GetLastRect();
|
|
using (new EditorGUILayout.HorizontalScope()) {
|
|
using (new EditorGUILayout.VerticalScope()) {
|
|
var buttonHeight = 19;
|
|
|
|
var bigButtonRect = new Rect(labelRect.x + 3, labelRect.y + 5, labelRect.width - 6, buttonHeight);
|
|
OpenURLButton.RenderRaw(bigButtonRect, Translations.About.ButtonDocumentation, Constants.DocumentationURL, HotReloadWindowStyles.HelpTabButton);
|
|
|
|
var firstLayerX = bigButtonRect.x;
|
|
var firstLayerY = bigButtonRect.y + buttonHeight + 3;
|
|
var firstLayerWidth = (int)((bigButtonRect.width / 2) - 3);
|
|
|
|
var secondLayerX = firstLayerX + firstLayerWidth + 5;
|
|
var secondLayerY = firstLayerY + buttonHeight + 3;
|
|
var secondLayerWidth = bigButtonRect.width - firstLayerWidth - 5;
|
|
|
|
using (new EditorGUILayout.HorizontalScope()) {
|
|
OpenURLButton.RenderRaw(new Rect { x = firstLayerX, y = firstLayerY, width = firstLayerWidth, height = buttonHeight }, contactButton.text, contactButton.url, HotReloadWindowStyles.HelpTabButton);
|
|
OpenURLButton.RenderRaw(new Rect { x = secondLayerX, y = firstLayerY, width = secondLayerWidth, height = buttonHeight }, Translations.About.ButtonUnityForum, Constants.ForumURL, HotReloadWindowStyles.HelpTabButton);
|
|
}
|
|
using (new EditorGUILayout.HorizontalScope()) {
|
|
OpenDialogueButton.RenderRaw(rect: new Rect { x = firstLayerX, y = secondLayerY, width = firstLayerWidth, height = buttonHeight }, text: reportIssueButton.text, url: reportIssueButton.url, title: reportIssueButton.title, message: reportIssueButton.message, ok: reportIssueButton.ok, cancel: reportIssueButton.cancel, style: HotReloadWindowStyles.HelpTabButton);
|
|
OpenURLButton.RenderRaw(new Rect { x = secondLayerX, y = secondLayerY, width = secondLayerWidth, height = buttonHeight }, discordButton.text, discordButton.url, HotReloadWindowStyles.HelpTabButton);
|
|
}
|
|
}
|
|
}
|
|
GUILayout.Space(80);
|
|
}
|
|
|
|
void RenderLogButtons() {
|
|
if (currentState.logsFodlerExists) {
|
|
EditorGUILayout.Space();
|
|
EditorGUILayout.BeginHorizontal();
|
|
GUILayout.FlexibleSpace();
|
|
if (GUILayout.Button(Translations.Common.ButtonOpenLogFile)) {
|
|
var mostRecentFile = LogsHelper.FindRecentLog(logsPath);
|
|
if (mostRecentFile == null) {
|
|
Log.Info(Translations.About.LogNoLogsFound);
|
|
} else {
|
|
try {
|
|
Process.Start($"\"{Path.Combine(logsPath, mostRecentFile)}\"");
|
|
} catch (Win32Exception e) {
|
|
// TODO: is this the same for chinese?
|
|
if (e.Message.Contains("Application not found")) {
|
|
try {
|
|
Process.Start("notepad.exe", $"\"{Path.Combine(logsPath, mostRecentFile)}\"");
|
|
} catch {
|
|
// Fallback to opening folder with all logs
|
|
Process.Start($"\"{logsPath}\"");
|
|
Log.Info(Translations.About.LogFailedOpeningLogFile);
|
|
}
|
|
}
|
|
} catch {
|
|
// Fallback to opening folder with all logs
|
|
Process.Start($"\"{logsPath}\"");
|
|
Log.Info(Translations.About.LogFailedOpeningLogFile);
|
|
}
|
|
}
|
|
}
|
|
if (GUILayout.Button(Translations.Common.ButtonBrowseAllLogs)) {
|
|
Process.Start($"\"{logsPath}\"");
|
|
}
|
|
EditorGUILayout.EndHorizontal();
|
|
}
|
|
}
|
|
}
|
|
}
|