Esempio n. 1
0
    static void GithubPush(BuildManagerSettings settings, BuildSequence sequence, ChangelogData changelog)
    {
        for (byte i = 0; i < sequence.builds.Count; ++i)
        {
            if (!sequence.builds[i].isEnabled || !sequence.builds[i].needGithubPush)
            {
                continue;
            }

            if (!string.IsNullOrEmpty(buildsPath[i]))
            {
                if (string.IsNullOrEmpty(settings.GithubToken) || string.IsNullOrEmpty(settings.githubUserName) || string.IsNullOrEmpty(settings.githubRepoName))
                {
                    Debug.LogWarning($"Can't push github release. Required data is missing");
                    return;
                }

                PushGithub(settings, sequence, sequence.builds[i]);
            }
            else
            {
                Debug.LogWarning($"[GitHub push] Can't find build for {GetBuildTargetExecutable(sequence.builds[i].target)}");
            }
        }
    }
Esempio n. 2
0
    static void ItchioPush(BuildManagerSettings settings, BuildSequence sequence, ChangelogData changelog)
    {
        for (byte i = 0; i < sequence.builds.Count; ++i)
        {
            if (!sequence.builds[i].isEnabled || !sequence.builds[i].needItchPush)
            {
                continue;
            }

            if (!string.IsNullOrEmpty(buildsPath[i]))
            {
                if (string.IsNullOrEmpty(settings.itchGameLink))
                {
                    Debug.LogWarning($"Can't push itch.io. Required data is missing");
                    return;
                }

                PushItch(settings, sequence, sequence.builds[i]);
            }
            else
            {
                Debug.LogWarning($"[Itch.io push] Can't find build for {GetBuildTargetExecutable(sequence.builds[i].target)}");
            }
        }
    }
Esempio n. 3
0
    public static void RunBuildSequnce(BuildManagerSettings settings, BuildSequence sequence, ChangelogData changelog)
    {
        // Start init
        usedChangelog = changelog;
        ChangelogData.ChangelogVersionEntry usedChangelogEntry = changelog.GetLastVersion();
        buildNameString = usedChangelogEntry.GetVersionHeader();

#if GAME_TEMPLATE
        TemplateGameManager.Instance.buildNameString = buildNameString;
        TemplateGameManager.Instance.productName     = PlayerSettings.productName;
        EditorUtility.SetDirty(TemplateGameManager.Instance);
#endif
        usedDate = DateTime.Now;
        //End init

        Debug.Log("Start building all");
        DateTime startTime = DateTime.Now;

        //Crete release here, because build's not get pushed
        CreateGitHubReleaseIfNeeded(settings, sequence);

        Build(settings, sequence);
        PostBuild(sequence);

        Compress(sequence);

        ItchioPush(settings, sequence, changelog);
        GithubPush(settings, sequence, changelog);

        Debug.Log($"End building all. Elapsed time: {string.Format("{0:mm\\:ss}", DateTime.Now - startTime)}");

#if UNITY_EDITOR_WIN
        ShowExplorer(sequence.builds[sequence.builds.Count - 1].outputRoot);
#endif
    }
    void DrawChangelogInfo()
    {
        bool   isChanged = false;
        string tmpString = "";

        changelogFoldout = EditorGUILayout.BeginFoldoutHeaderGroup(changelogFoldout, "Changelog");
        if (changelogFoldout)
        {
            scrollPosChangelog = EditorGUILayout.BeginScrollView(scrollPosChangelog);
            ++EditorGUI.indentLevel;

            changelog.updateName = DrawStringField("Update name", changelog.updateName);

            --EditorGUI.indentLevel;
            EditorGUILayout.EndScrollView();
        }
        EditorGUILayout.EndFoldoutHeaderGroup();

        if (isChanged)
        {
            ChangelogData.SaveChangelogToFile(changelogPath, changelog);
        }

        string DrawStringField(string label, string text)
        {
            tmpString = EditorGUILayout.TextField(label, text);
            if (text != tmpString)
            {
                isChanged = true;
            }
            return(tmpString);
        }
    }
Esempio n. 5
0
        private void WriteChangelogPart(ChangelogData data)
        {
            try
            {
                var fileName = Path.Combine(
                    _cfg.Value.ChangelogRepo !,
                    "Resources", "Changelog", "Parts", $"pr-{data.Number}.yml");

                _log.LogTrace("Writing changelog part {PartFileName}", fileName);

                var yamlStream = new YamlStream(new YamlDocument(new YamlMappingNode
                {
                    { "author", data.Author },
                    { "time", data.Time.ToString("O") },
                    {
                        "changes",
                        new YamlSequenceNode(
                            data.Changes.Select(c => new YamlMappingNode
                        {
                            { "type", c.Item1.ToString() },
                            { "message", c.Item2 },
                        }))
                    }
                }));

                using var writer = new StreamWriter(fileName);
                yamlStream.Save(writer);
            }
            catch (Exception e)
            {
                _log.LogError(e, "Exception while writing changelog to disk!");
            }
        }
Esempio n. 6
0
        public override void Unload()
        {
            // Unload any data that goes unhandled by tML or contains static data.
            AssetManager.Unload();
            ChangelogData.Unload();
            ILManager.Unload();
            SaveDataManager.Unload();
            LocalizationInitializer.Unload();

            ModLogger = null;
            Instance  = null;
        }
Esempio n. 7
0
        public override void Load()
        {
            Instance  = this;
            ModLogger = Instance.Logger;

            // Initialize all the things!
            AssetManager.Load();
            LocalizationInitializer.Initialize();
            ILManager.Load();
            SaveDataManager.Load();
            SaveDataManager.Save();
            ChangelogData.PopulateChangelogList(this);
        }
Esempio n. 8
0
    public static void SaveChangelogToFile(string usedFileName, ChangelogData data)
    {
        string path = Path.Combine(Application.dataPath, usedFileName);

        string jsonString = JsonUtility.ToJson(data, true);

        if (!File.Exists(path))
        {
            File.Create(path);
        }

        using (FileStream file = new FileStream(path, FileMode.Truncate, FileAccess.Write))
            using (StreamWriter sw = new StreamWriter(file))
                sw.Write(jsonString);
    }
Esempio n. 9
0
        private void btnSelectIP_Click(object sender, EventArgs e)
        {
            var       allChangeLogs = ChangelogData.GetAll();
            ChangeLog changeLog     = allChangeLogs[0];

            if (btnSelectIP.ButtonText == "Show Changelog")
            {
                this.Size = new System.Drawing.Size(685, 277);
                btnSelectIP.ButtonText = "Hide Changelog";
                txtChangelog.Text      = (changeLog.Changelog);
            }
            else if (btnSelectIP.ButtonText == "Hide Changelog")
            {
                this.Size = new System.Drawing.Size(343, 277);
                btnSelectIP.ButtonText = "Show Changelog";
            }
        }
    static void LoadChangelog()
    {
        changelogPath = PlayerPrefs.GetString(CHANGELOG_JSON_PATH_KEY, "");
        changelog     = null;

        //Find path. Try to load settings
        if (!string.IsNullOrEmpty(changelogPath))
        {
            changelog = ChangelogData.LoadChangelogFromFile(changelogPath);
            if (changelog == null)
            {
                changelogPath = null;
            }
        }

        //No path, or cant locate asset at path. Try to find settings in assets.
        if (string.IsNullOrEmpty(changelogPath))
        {
            string[] guids = AssetDatabase.FindAssets("Changelog.json");
            if (guids.Length >= 2)
            {
                Debug.LogError("[BuildManagerWindow]. 2+ Changelog.json exist. Consider on using only 1 changelog. The first on will be used.");
            }

            if (guids.Length != 0)
            {
                changelogPath = AssetDatabase.GUIDToAssetPath(guids[0]);
                PlayerPrefs.SetString(CHANGELOG_JSON_PATH_KEY, changelogPath);
                changelog = ChangelogData.LoadChangelogFromFile(changelogPath);
            }
        }

        //Cant find settings. Create new
        if (changelog == null)
        {
            changelog     = new ChangelogData();
            changelogPath = CHANGELOG_DEFAULT_PATH;
            PlayerPrefs.SetString(CHANGELOG_JSON_PATH_KEY, CHANGELOG_DEFAULT_PATH);

            ChangelogData.SaveChangelogToFile(changelogPath, changelog);
        }
    }
Esempio n. 11
0
    static void ItchioPush(BuildSequence sequence, ChangelogData changelog)
    {
        for (byte i = 0; i < sequence.builds.Count; ++i)
        {
            if (!sequence.builds[i].needItchPush || !sequence.builds[i].isEnabled)
            {
                continue;
            }

            if (!string.IsNullOrEmpty(buildsPath[i]))
            {
                if (sequence.builds[i].itchAddLastChangelogUpdateNameToVerison && !string.IsNullOrEmpty(changelog?.updateName))
                {
                    sequence.builds[i].itchLastChangelogUpdateName = buildNameString;
                }
                PushItch(sequence, sequence.builds[i]);
            }
            else
            {
                Debug.LogWarning($"[Itch.io push] Can't find build for {GetBuildTargetExecutable(sequence.builds[i].target)}");
            }
        }
    }
Esempio n. 12
0
    public static void SaveChangelog(ChangelogData data)
    {
        if (!PlayerPrefs.HasKey(SAVE_FILE_EDITORPREFS))
        {
            string[] allPath = AssetDatabase.FindAssets(SAVE_FILE_NOREZ);
            if (allPath.Length != 0)
            {
                PlayerPrefs.SetString(SAVE_FILE_EDITORPREFS, AssetDatabase.GUIDToAssetPath(allPath[0]).Replace("Assets/", ""));
            }
        }

        string savePath = Path.Combine(Application.dataPath, PlayerPrefs.GetString(SAVE_FILE_EDITORPREFS, SAVE_FILE_EDITORPREFS_DEFAULT));

        string json = JsonUtility.ToJson(data, true);

        if (!File.Exists(savePath))
        {
            FileInfo file = new FileInfo(savePath);
            file.Directory.Create();
        }

        File.WriteAllText(savePath, json);
    }
    public static void RunBuildSequnce(BuildSequence sequence, ChangelogData changelog)
    {
        // Start init
        TemplateGameManager.InstanceEditor.buildNameString = $"{PlayerSettings.bundleVersion} - {changelog.updateName}";
        usedDate = DateTime.Now;
        //End init

        Debug.Log("Start building all");
        DateTime         startTime              = DateTime.Now;
        BuildTarget      targetBeforeStart      = EditorUserBuildSettings.activeBuildTarget;
        BuildTargetGroup targetGroupBeforeStart = BuildPipeline.GetBuildTargetGroup(targetBeforeStart);
        string           definesBeforeStart     = PlayerSettings.GetScriptingDefineSymbolsForGroup(targetGroupBeforeStart);
        bool             isVRSupported          = PlayerSettings.virtualRealitySupported; //TODO: PlayerSettings.virtualRealitySupported is deprecated. Replace with smth new

        string[] buildsPath = new string[sequence.builds.Length];
        for (byte i = 0; i < sequence.builds.Length; ++i)
        {
            BuildData data = sequence.builds[i];

            if (PlayerSettings.virtualRealitySupported != data.isVirtualRealitySupported)
            {
                PlayerSettings.virtualRealitySupported = data.isVirtualRealitySupported;
            }

            buildsPath[i] = BaseBuild(data.targetGroup, data.target, data.options, data.outputRoot + GetPathWithVars(data, data.middlePath), data.scriptingDefinySymbols, data.isPassbyBuild);
        }

        EditorUserBuildSettings.SwitchActiveBuildTarget(targetGroupBeforeStart, targetBeforeStart);
        PlayerSettings.SetScriptingDefineSymbolsForGroup(targetGroupBeforeStart, definesBeforeStart);
        PlayerSettings.virtualRealitySupported = isVRSupported;
        Debug.Log($"End building all. Elapsed time: {string.Format("{0:mm\\:ss}", DateTime.Now - startTime)}");

        startTime = DateTime.Now;
        Debug.Log($"Start compressing all");

        for (byte i = 0; i < sequence.builds.Length; ++i)
        {
            if (!sequence.builds[i].needZip)
            {
                continue;
            }

            if (!string.IsNullOrEmpty(buildsPath[i]))
            {
                BaseCompress(sequence.builds[i].outputRoot + GetPathWithVars(sequence.builds[i], sequence.builds[i].compressDirPath));
            }
            else
            {
                Debug.LogWarning($"[Compressing] Can't find build for {GetBuildTargetExecutable(sequence.builds[i].target)}");
            }
        }

        Debug.Log($"End compressing all. Elapsed time: {string.Format("{0:mm\\:ss}", DateTime.Now - startTime)}");


        for (byte i = 0; i < sequence.builds.Length; ++i)
        {
            if (!sequence.builds[i].needItchPush)
            {
                continue;
            }

            if (!string.IsNullOrEmpty(buildsPath[i]))
            {
                if (sequence.builds[i].itchAddLastChangelogUpdateNameToVerison && !string.IsNullOrEmpty(changelog?.updateName))
                {
                    sequence.builds[i].itchLastChangelogUpdateName = TemplateGameManager.InstanceEditor.buildNameString;
                }
                PushItch(sequence, sequence.builds[i]);
            }
            else
            {
                Debug.LogWarning($"[Itch.io push] Can't find build for {GetBuildTargetExecutable(sequence.builds[i].target)}");
            }
        }
    }
Esempio n. 14
0
    public static void RunBuildSequnce(BuildSequence sequence, ChangelogData changelog)
    {
        // Start init
        GameManager.InstanceEditor.buildNameString = $"{PlayerSettings.bundleVersion} - {changelog.LocalizedUpdate}";
        usedDate = DateTime.Now;
        //End init

        Debug.Log("Start building all");
        DateTime         startTime              = DateTime.Now;
        BuildTarget      targetBeforeStart      = EditorUserBuildSettings.activeBuildTarget;
        BuildTargetGroup targetGroupBeforeStart = BuildPipeline.GetBuildTargetGroup(targetBeforeStart);

        string[] buildsPath = new string[sequence.builds.Length];
        for (byte i = 0; i < sequence.builds.Length; ++i)
        {
            BuildData data = sequence.builds[i];
            buildsPath[i] = BaseBuild(data.targetGroup, data.target, data.options, data.outputRoot + GetPathWithVars(data, data.middlePath));
        }

        EditorUserBuildSettings.SwitchActiveBuildTarget(targetGroupBeforeStart, targetBeforeStart);
        Debug.Log($"End building all. Elapsed time: {string.Format("{0:mm\\:ss}", DateTime.Now - startTime)}");

        startTime = DateTime.Now;
        Debug.Log($"Start compressing all");

        for (byte i = 0; i < sequence.builds.Length; ++i)
        {
            if (!sequence.builds[i].needZip)
            {
                continue;
            }

            if (!string.IsNullOrEmpty(buildsPath[i]))
            {
                BaseCompress(sequence.builds[i].outputRoot + GetPathWithVars(sequence.builds[i], sequence.builds[i].compressDirPath));
            }
            else
            {
                Debug.LogWarning($"[Compressing] Can't find build for {GetBuildTargetExecutable(sequence.builds[i].target)}");
            }
        }

        Debug.Log($"End compressing all. Elapsed time: {string.Format("{0:mm\\:ss}", DateTime.Now - startTime)}");


        for (byte i = 0; i < sequence.builds.Length; ++i)
        {
            if (!sequence.builds[i].needItchPush)
            {
                continue;
            }

            if (!string.IsNullOrEmpty(buildsPath[i]))
            {
                if (sequence.builds[i].itchAddLastChangelogUpdateNameToVerison && !string.IsNullOrEmpty(changelog?.LocalizedUpdate))
                {
                    sequence.builds[i].itchLastChangelogUpdateName = GameManager.InstanceEditor.buildNameString;
                }
                PushItch(sequence, sequence.builds[i]);
            }
            else
            {
                Debug.LogWarning($"[Itch.io push] Can't find build for {GetBuildTargetExecutable(sequence.builds[i].target)}");
            }
        }
    }
Esempio n. 15
0
 public void PushPRChangelog(ChangelogData data)
 {
     _commChannel.Writer.TryWrite(new MsgWritePRChangelog(data));
     _commChannel.Writer.TryWrite(new MsgQueueUpdate());
 }
Esempio n. 16
0
    public static void RunBuildSequnce(BuildManagerSettings settings, BuildSequence sequence, ChangelogData changelog)
    {
        // Start init
        string buildNameString = $"{PlayerSettings.bundleVersion} - {changelog.updateName}";

#if GAME_TEMPLATE
        TemplateGameManager.InstanceEditor.buildNameString = buildNameString;
        TemplateGameManager.InstanceEditor.productName     = PlayerSettings.productName;
#endif
        usedDate = DateTime.Now;
        //End init

        Debug.Log("Start building all");
        DateTime         startTime              = DateTime.Now;
        BuildTarget      targetBeforeStart      = EditorUserBuildSettings.activeBuildTarget;
        BuildTargetGroup targetGroupBeforeStart = BuildPipeline.GetBuildTargetGroup(targetBeforeStart);
        string           definesBeforeStart     = PlayerSettings.GetScriptingDefineSymbolsForGroup(targetGroupBeforeStart);
        bool             isVRSupported          = PlayerSettings.virtualRealitySupported; //TODO: PlayerSettings.virtualRealitySupported is deprecated. Replace with smth new

        string[] buildsPath = new string[sequence.builds.Count];
        for (byte i = 0; i < sequence.builds.Count; ++i)
        {
            BuildData data = sequence.builds[i];

            if (!data.isEnabled)
            {
                continue;
            }

            if (PlayerSettings.virtualRealitySupported != data.isVirtualRealitySupported)
            {
                PlayerSettings.virtualRealitySupported = data.isVirtualRealitySupported;
            }

            buildsPath[i] = BaseBuild(
                data.targetGroup,
                data.target,
                data.options,
                data.outputRoot + GetPathWithVars(data, data.middlePath),
                string.Concat(settings.scriptingDefineSymbols, ";", sequence.scriptingDefineSymbolsOverride, ";", data.scriptingDefineSymbolsOverride),
                data.isPassbyBuild,
                data.isReleaseBuild
                );
        }

        EditorUserBuildSettings.SwitchActiveBuildTarget(targetGroupBeforeStart, targetBeforeStart);
        PlayerSettings.SetScriptingDefineSymbolsForGroup(targetGroupBeforeStart, definesBeforeStart);
        PlayerSettings.virtualRealitySupported = isVRSupported;
        Debug.Log($"End building all. Elapsed time: {string.Format("{0:mm\\:ss}", DateTime.Now - startTime)}");

        for (byte i = 0; i < sequence.builds.Count; ++i)
        {
            if (!sequence.builds[i].needZip || !sequence.builds[i].isEnabled)
            {
                continue;
            }

            if (sequence.builds[i].target == BuildTarget.Android)
            {
                Debug.Log("Skip android build to .zip, because .apk files already compressed");
                continue;
            }

            if (!string.IsNullOrEmpty(buildsPath[i]))
            {
                if (sequence.builds[i].isReleaseBuild)                    //Destroy IL2CPP junk after build
                {
                    string   buildRootPath = Path.GetDirectoryName(buildsPath[i]);
                    string[] dirs          = Directory.GetDirectories(buildRootPath);
                    var      il2cppDirs    = dirs.Where(s => s.Contains("BackUpThisFolder_ButDontShipItWithYourGame"));
                    foreach (var dir in il2cppDirs)
                    {
                        Directory.Delete(dir, true);
                    }
                }
            }
        }

        startTime = DateTime.Now;
        Debug.Log($"Start compressing all");

        for (byte i = 0; i < sequence.builds.Count; ++i)
        {
            if (!sequence.builds[i].needZip || !sequence.builds[i].isEnabled)
            {
                continue;
            }

            if (sequence.builds[i].target == BuildTarget.Android)
            {
                Debug.Log("Skip android build to .zip, because .apk files already compressed");
                continue;
            }

            if (!string.IsNullOrEmpty(buildsPath[i]))
            {
                BaseCompress(sequence.builds[i].outputRoot + GetPathWithVars(sequence.builds[i], sequence.builds[i].compressDirPath));
            }
            else
            {
                Debug.LogWarning($"[Compressing] Can't find build for {GetBuildTargetExecutable(sequence.builds[i].target)}");
            }
        }

        Debug.Log($"End compressing all. Elapsed time: {string.Format("{0:mm\\:ss}", DateTime.Now - startTime)}");


        for (byte i = 0; i < sequence.builds.Count; ++i)
        {
            if (!sequence.builds[i].needItchPush || !sequence.builds[i].isEnabled)
            {
                continue;
            }

            if (!string.IsNullOrEmpty(buildsPath[i]))
            {
                if (sequence.builds[i].itchAddLastChangelogUpdateNameToVerison && !string.IsNullOrEmpty(changelog?.updateName))
                {
                    sequence.builds[i].itchLastChangelogUpdateName = buildNameString;
                }
                PushItch(sequence, sequence.builds[i]);
            }
            else
            {
                Debug.LogWarning($"[Itch.io push] Can't find build for {GetBuildTargetExecutable(sequence.builds[i].target)}");
            }
        }

        ShowExplorer(sequence.builds[sequence.builds.Count - 1].outputRoot);
    }
Esempio n. 17
0
    public static void RunBuildSequnce(BuildManagerSettings settings, BuildSequence sequence, ChangelogData changelog)
    {
        // Start init
        buildNameString = $"{PlayerSettings.bundleVersion} - {changelog.updateName}";
#if GAME_TEMPLATE
        TemplateGameManager.InstanceEditor.buildNameString = buildNameString;
        TemplateGameManager.InstanceEditor.productName     = PlayerSettings.productName;
#endif
        usedDate = DateTime.Now;
        //End init

        Debug.Log("Start building all");
        DateTime startTime = DateTime.Now;

        Build(settings, sequence);
        PostBuild(sequence);
        Compress(sequence);
        ItchioPush(sequence, changelog);

        Debug.Log($"End building all. Elapsed time: {string.Format("{0:mm\\:ss}", DateTime.Now - startTime)}");

#if UNITY_EDITOR_WIN
        ShowExplorer(sequence.builds[sequence.builds.Count - 1].outputRoot);
#endif
    }
 public UIChangelogBox(ChangelogData changelog, int index)
 {
     Changelog = changelog;
     Index     = index;
 }
Esempio n. 19
0
 private sealed record MsgWritePRChangelog(ChangelogData Data) : MsgQueueBase
 static void LoadChangelog()
 {
     changelog = ChangelogData.LoadChangelog();
 }
    void DrawChangelogInfo()
    {
        bool oldChangelogFoldoutValue = changelogFoldout;

        changelogFoldout = EditorGUILayout.BeginFoldoutHeaderGroup(changelogFoldout, "Changelog");
        EditorGUILayout.EndFoldoutHeaderGroup();

        if (changelogFoldout)
        {
            scrollPosChangelog = EditorGUILayout.BeginScrollView(scrollPosChangelog /*, GUILayout.Height(800f)*/);
            ++EditorGUI.indentLevel;

            EditorGUILayout.LabelField("Readme");
            changelog.readme = EditorGUILayout.TextArea(changelog.readme);
            GUILayout.Space(10f);

            EditorGUILayout.BeginHorizontal();
            EditorGUILayout.LabelField("Changelog file:", GUILayout.Width(100));
            if (GUILayout.Button($"Add version"))
            {
                changelog.versions.Add(new ChangelogData.ChangelogVersionEntry());
            }
            EditorGUILayout.EndHorizontal();

            for (int i = changelog.versions.Count - 1; i >= 0; --i)
            {
                ChangelogData.ChangelogVersionEntry version = changelog.versions[i];

                EditorGUILayout.BeginHorizontal();
                version.foldout = EditorGUILayout.BeginFoldoutHeaderGroup(version.foldout, $"{version.version} - {version.date}");
                EditorGUILayout.EndFoldoutHeaderGroup();
                if (GUILayout.Button($"Remove version", GUILayout.Width(100)))
                {
                    changelog.versions.RemoveAt(i);
                    return;
                }
                EditorGUILayout.EndHorizontal();

                if (string.IsNullOrEmpty(version.version))
                {
                    version.version = PlayerSettings.bundleVersion;
                }
                if (string.IsNullOrEmpty(version.date))
                {
                    version.date = System.DateTime.Now.ToShortDateString();
                }

                if (version.foldout)
                {
                    ++EditorGUI.indentLevel;

                    EditorGUILayout.BeginHorizontal();
                    version.version = EditorGUILayout.TextField("Version", version.version);
                    if (GUILayout.Button($"Curr", GUILayout.Width(70)))
                    {
                        version.version = PlayerSettings.bundleVersion;
                    }
                    EditorGUILayout.EndHorizontal();

                    EditorGUILayout.BeginHorizontal();
                    version.date = EditorGUILayout.TextField("Date", version.date);
                    if (GUILayout.Button($"Now", GUILayout.Width(70)))
                    {
                        version.date = System.DateTime.Now.ToShortDateString();
                    }
                    EditorGUILayout.EndHorizontal();

                    version.updateName      = EditorGUILayout.TextField("Update name", version.updateName);
                    version.descriptionText = EditorGUILayout.TextField("Description", version.descriptionText);

                    EditorGUILayout.LabelField("Notes: ");

                    ++EditorGUI.indentLevel;
                    EditorGUILayout.BeginHorizontal();
                    EditorGUILayout.LabelField("Type", GUILayout.Width(150));
                    EditorGUILayout.LabelField("Scope", GUILayout.Width(125));
                    EditorGUILayout.LabelField("Community", GUILayout.Width(100));
                    EditorGUILayout.LabelField("Description");
                    EditorGUILayout.EndHorizontal();

                    for (int j = 0; j < version.notes.Count; ++j)
                    {
                        ChangelogData.ChangelogNoteEntry note = version.notes[j];
                        EditorGUILayout.BeginHorizontal();

                        ChangelogData.ChangelogEntryType  newType  = (ChangelogData.ChangelogEntryType)EditorGUILayout.EnumPopup(note.type, GUILayout.Width(150));
                        ChangelogData.ChangelogEntryScope newScope = (ChangelogData.ChangelogEntryScope)EditorGUILayout.EnumPopup(note.scope, GUILayout.Width(150));
                        note.isCommunityFeedback = EditorGUILayout.Toggle(note.isCommunityFeedback, GUILayout.Width(70));
                        note.text = EditorGUILayout.TextField(note.text);

                        if (note.type != newType || note.scope != newScope)
                        {
                            note.type     = newType;
                            note.scope    = newScope;
                            version.notes = version.notes.OrderBy(_note => _note.type).ThenBy(_note => _note.scope).ToList();
                            return;
                        }

                        if (GUILayout.Button($"-", GUILayout.Width(25)))
                        {
                            version.notes.RemoveAt(j);
                            return;
                        }

                        EditorGUILayout.EndHorizontal();
                    }
                    --EditorGUI.indentLevel;

                    if (GUILayout.Button($"Add note"))
                    {
                        version.notes.Add(new ChangelogData.ChangelogNoteEntry());
                    }

                    --EditorGUI.indentLevel;
                }

                EditorGUILayout.Space(10);
            }

            --EditorGUI.indentLevel;
            EditorGUILayout.EndScrollView();
        }

        if (oldChangelogFoldoutValue != changelogFoldout)
        {
            ChangelogData.SaveChangelog(changelog);

#if GAME_TEMPLATE
            TemplateGameManager.Instance.buildNameString = changelog.GetLastVersion().GetVersionHeader();;
            TemplateGameManager.Instance.productName     = PlayerSettings.productName;
            EditorUtility.SetDirty(TemplateGameManager.Instance);
#endif
        }

        if (changelogFoldout)
        {
            EditorGUILayout.Space(20);
        }
    }