Иконка ресурса

FREE LiveCommits : 1.0.0 1.0.0

Нет прав для скачивания
C#:
using System;
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json;
using Oxide.Core;
using Oxide.Core.Libraries.Covalence;
using Oxide.Game.Rust.Cui;
using UnityEngine;

namespace Oxide.Plugins
{
    [Info("LiveCommits", "MediocratyItself", "1.0.0")]
    [Description("View Facepunch commits history in real-time")]
    class LiveCommits : RustPlugin
    {
        #region Configuration
        private Configuration config;

        public class Configuration
        {
            [JsonProperty("Show button")]
            public bool ShowButton { get; set; } = true;

            [JsonProperty("Refresh rate (seconds)")]
            public int RefreshRate { get; set; } = 10;
        }

        protected override void LoadConfig()
        {
            base.LoadConfig();
            try
            {
                config = Config.ReadObject<Configuration>();
                if (config == null)
                {
                    throw new JsonException();
                }
            }
            catch
            {
                PrintWarning("Creating new configuration file");
                LoadDefaultConfig();
            }
            SaveConfig();
        }

        protected override void LoadDefaultConfig()
        {
            config = new Configuration();
        }

        protected override void SaveConfig()
        {
            Config.WriteObject(config, true);
        }
        #endregion

        #region Fields
        private Dictionary<ulong, int> playerPages = new Dictionary<ulong, int>();
        private Dictionary<ulong, int> playerScrollPos = new Dictionary<ulong, int>();
        private Dictionary<ulong, int> playerViewingCommit = new Dictionary<ulong, int>();
        private Dictionary<ulong, string> playerViewingDev = new Dictionary<ulong, string>();
        private Dictionary<ulong, int> playerDevPages = new Dictionary<ulong, int>();
        private Dictionary<ulong, string> playerFilterMode = new Dictionary<ulong, string>();
        private HashSet<ulong> playersWithUIOpen = new HashSet<ulong>();
        private Dictionary<int, CommitData> pageCache = new Dictionary<int, CommitData>();
        private Dictionary<string, Dictionary<int, CommitData>> devPageCache = new Dictionary<string, Dictionary<int, CommitData>>();
        private DateTime lastCacheUpdate = DateTime.MinValue;
        private const int CacheExpiryMinutes = 5;
        private int totalCommits = 0;
        private Timer refreshTimer;
        private Dictionary<string, Timer> uiTimers = new Dictionary<string, Timer>();
        private int lastCommitId = 0;
        
        private SteamBuildInfo cachedSteamBuild;
        private DateTime lastSteamBuildCheck = DateTime.MinValue;
        private const int SteamBuildCacheMinutes = 5;
        #endregion

        #region Data Classes
        public class CommitData
        {
            public int total { get; set; }
            public int skip { get; set; }
            public int take { get; set; }
            public List<CommitResult> results { get; set; }
        }

        public class CommitResult
        {
            public int id { get; set; }
            public string repo { get; set; }
            public string branch { get; set; }
            public string changeset { get; set; }
            public DateTime created { get; set; }
            public int likes { get; set; }
            public int dislikes { get; set; }
            public string message { get; set; }
            public CommitUser user { get; set; }
        }

        public class CommitUser
        {
            public string name { get; set; }
            public string avatar { get; set; }
            public string id { get; set; }
            public string login { get; set; }
            public string username { get; set; }
        }
        
        public class SteamBuildInfo
        {
            public string Branch { get; set; }
            public string BuildId { get; set; }
            public string TimeUpdated { get; set; }
            public DateTime LastChecked { get; set; }
        }
        #endregion

        #region Oxide Hooks
        void OnServerInitialized()
        {
            if (config.ShowButton)
            {
                foreach (var player in BasePlayer.activePlayerList)
                {
                    CreateToggleButton(player);
                }
            }
            
            FetchCommitData(null, 1);
            
            if (config.RefreshRate > 0)
            {
                refreshTimer = timer.Every(config.RefreshRate, () => CheckForNewCommits());
            }
        }

        void Unload()
        {
            foreach (var player in BasePlayer.activePlayerList)
            {
                DestroyAllUI(player);
            }
            
            refreshTimer?.Destroy();
            
            foreach (var uiTimer in uiTimers.Values)
            {
                uiTimer?.Destroy();
            }
            uiTimers.Clear();
        }
        #endregion

        #region Commands
        [ChatCommand("commits")]
        void CommitsCommand(BasePlayer player, string command, string[] args)
        {
            if (!playerPages.ContainsKey(player.userID))
                playerPages[player.userID] = 1;
            if (!playerScrollPos.ContainsKey(player.userID))
                playerScrollPos[player.userID] = 0;
            if (!playerFilterMode.ContainsKey(player.userID))
                playerFilterMode[player.userID] = "all";
            playerViewingCommit.Remove(player.userID);
            playerViewingDev.Remove(player.userID);

            ShowCommitUI(player);
        }

        [ConsoleCommand("livecommits.close")]
        void CloseUICommand(ConsoleSystem.Arg arg)
        {
            var player = arg.Player();
            if (player == null) return;
            DestroyUI(player);
            playersWithUIOpen.Remove(player.userID);
            playerViewingCommit.Remove(player.userID);
            playerViewingDev.Remove(player.userID);
            StopUIUpdateTimer(player);
        }

        [ConsoleCommand("livecommits.page")]
        void PageCommand(ConsoleSystem.Arg arg)
        {
            var player = arg.Player();
            if (player == null) return;

            int page = arg.GetInt(0, 1);
            if (page < 1) page = 1;
            
            int totalPages = (int)Math.Ceiling(totalCommits / 100.0);
            if (page > totalPages) page = totalPages;
            
            playerPages[player.userID] = page;
            playerScrollPos[player.userID] = 0;
            playerViewingCommit.Remove(player.userID);
            playerViewingDev.Remove(player.userID);
            
            FetchCommitData(player, page);
        }

        [ConsoleCommand("livecommits.goto")]
        void GotoPageCommand(ConsoleSystem.Arg arg)
        {
            var player = arg.Player();
            if (player == null) return;

            string input = arg.GetString(0, "1");
            int page;
            if (!int.TryParse(input, out page)) page = 1;
            
            if (page < 1) page = 1;
            
            int totalPages = (int)Math.Ceiling(totalCommits / 100.0);
            if (page > totalPages) page = totalPages;
            
            playerPages[player.userID] = page;
            playerScrollPos[player.userID] = 0;
            playerViewingCommit.Remove(player.userID);
            playerViewingDev.Remove(player.userID);
            
            FetchCommitData(player, page);
        }

        [ConsoleCommand("livecommits.view")]
        void ViewCommitCommand(ConsoleSystem.Arg arg)
        {
            var player = arg.Player();
            if (player == null) return;

            int commitIndex = arg.GetInt(0, -1);
            if (commitIndex < 0) return;

            int currentPage = playerPages.ContainsKey(player.userID) ? playerPages[player.userID] : 1;
            
            if (playerViewingDev.ContainsKey(player.userID))
            {
                string devKey = playerViewingDev[player.userID];
                int devPage = playerDevPages.ContainsKey(player.userID) ? playerDevPages[player.userID] : 1;
                
                if (devPageCache.ContainsKey(devKey) && devPageCache[devKey].ContainsKey(devPage))
                {
                    var devData = devPageCache[devKey][devPage];
                    if (commitIndex < devData.results.Count)
                    {
                        playerViewingCommit[player.userID] = commitIndex;
                        DrawCommitDetailUI(player, devData.results[commitIndex], devPage, true, commitIndex);
                    }
                }
                return;
            }
            
            if (!pageCache.ContainsKey(currentPage)) return;
            if (commitIndex >= pageCache[currentPage].results.Count) return;

            playerViewingCommit[player.userID] = commitIndex;
            DrawCommitDetailUI(player, pageCache[currentPage].results[commitIndex], currentPage, false, commitIndex);
        }

        [ConsoleCommand("livecommits.back")]
        void BackCommand(ConsoleSystem.Arg arg)
        {
            var player = arg.Player();
            if (player == null) return;

            CuiHelper.DestroyUi(player, "LiveCommitsDetailOverlay");
            playerViewingCommit.Remove(player.userID);
        }

        [ConsoleCommand("livecommits.backtoall")]
        void BackToAllCommand(ConsoleSystem.Arg arg)
        {
            var player = arg.Player();
            if (player == null) return;

            playerViewingDev.Remove(player.userID);
            playerViewingCommit.Remove(player.userID);
            
            int currentPage = playerPages.ContainsKey(player.userID) ? playerPages[player.userID] : 1;
            
            if (pageCache.ContainsKey(currentPage))
            {
                DrawCommitUI(player, pageCache[currentPage], currentPage);
            }
            else
            {
                FetchCommitData(player, currentPage);
            }
        }

        [ConsoleCommand("livecommits.viewdev")]
        void ViewDevCommand(ConsoleSystem.Arg arg)
        {
            var player = arg.Player();
            if (player == null) return;

            int commitIndex = arg.GetInt(0, -1);
            if (commitIndex < 0) return;
            
            int currentPage = playerPages.ContainsKey(player.userID) ? playerPages[player.userID] : 1;
            
            CommitData data = null;
            
            if (playerViewingDev.ContainsKey(player.userID))
            {
                string devKey = playerViewingDev[player.userID];
                int devPage = playerDevPages.ContainsKey(player.userID) ? playerDevPages[player.userID] : 1;
                
                if (devPageCache.ContainsKey(devKey) && devPageCache[devKey].ContainsKey(devPage))
                {
                    data = devPageCache[devKey][devPage];
                }
            }
            else if (pageCache.ContainsKey(currentPage))
            {
                data = pageCache[currentPage];
            }
            
            if (data == null || commitIndex >= data.results.Count) return;
            
            var commit = data.results[commitIndex];
            string devName = commit.user?.name ?? "Unknown";
            string repo = commit.repo ?? "rust_reboot";

            playerViewingDev[player.userID] = $"{devName}/{repo}";
            playerDevPages[player.userID] = 1;
            playerViewingCommit.Remove(player.userID);
            
            FetchDevCommitData(player, devName, repo, 1);
        }

        [ConsoleCommand("livecommits.devpage")]
        void DevPageCommand(ConsoleSystem.Arg arg)
        {
            var player = arg.Player();
            if (player == null) return;

            if (!playerViewingDev.ContainsKey(player.userID)) return;

            int page = arg.GetInt(0, 1);
            if (page < 1) page = 1;

            string devKey = playerViewingDev[player.userID];
            string[] parts = devKey.Split('/');
            if (parts.Length != 2) return;

            playerDevPages[player.userID] = page;
            playerViewingCommit.Remove(player.userID);
            
            FetchDevCommitData(player, parts[0], parts[1], page);
        }

        [ConsoleCommand("livecommits.toggle")]
        void ToggleCommand(ConsoleSystem.Arg arg)
        {
            var player = arg.Player();
            if (player == null) return;

            if (playersWithUIOpen.Contains(player.userID))
            {
                DestroyUI(player);
                playersWithUIOpen.Remove(player.userID);
                playerViewingCommit.Remove(player.userID);
                playerViewingDev.Remove(player.userID);
                StopUIUpdateTimer(player);
                return;
            }

            if (!playerPages.ContainsKey(player.userID))
                playerPages[player.userID] = 1;
            if (!playerScrollPos.ContainsKey(player.userID))
                playerScrollPos[player.userID] = 0;
            if (!playerFilterMode.ContainsKey(player.userID))
                playerFilterMode[player.userID] = "all";
            playerViewingCommit.Remove(player.userID);
            playerViewingDev.Remove(player.userID);

            ShowCommitUI(player);
        }

        [ConsoleCommand("livecommits.filter")]
        void FilterCommand(ConsoleSystem.Arg arg)
        {
            var player = arg.Player();
            if (player == null) return;

            string mode = arg.GetString(0, "all");
            playerFilterMode[player.userID] = mode;
            playerPages[player.userID] = 1;
            playerViewingCommit.Remove(player.userID);
            playerViewingDev.Remove(player.userID);

            if (mode == "updates")
            {
                ShowUpdatesView(player);
            }
            else
            {
                int currentPage = 1;
                if (pageCache.ContainsKey(currentPage))
                {
                    DrawCommitUI(player, pageCache[currentPage], currentPage);
                }
                else
                {
                    FetchCommitData(player, currentPage);
                }
            }
        }
        #endregion

        #region UI Creation
        void CreateToggleButton(BasePlayer player)
        {
            CuiHelper.DestroyUi(player, "LiveCommitsToggleBtn");
            
            var container = new CuiElementContainer();

            container.Add(new CuiPanel
            {
                Image = { Color = "0.12 0.12 0.12 0.97" },
                RectTransform = { AnchorMin = "0.885 0.94", AnchorMax = "0.998 0.998" }
            }, "Overlay", "LiveCommitsToggleBtn");

            container.Add(new CuiElement
            {
                Parent = "LiveCommitsToggleBtn",
                Components = {
                    new CuiRawImageComponent { Url = "https://www.playrust.nl/wp-content/uploads/2017/02/Rust-Logo.jpg", Color = "1 1 1 1" },
                    new CuiRectTransformComponent { AnchorMin = "0.03 0.12", AnchorMax = "0.22 0.88" }
                }
            });

            container.Add(new CuiLabel
            {
                Text = { Text = "FACEPUNCH", FontSize = 12, Align = TextAnchor.MiddleLeft, Color = "1 1 1 1", Font = "robotocondensed-bold.ttf" },
                RectTransform = { AnchorMin = "0.25 0.52", AnchorMax = "0.97 0.95" }
            }, "LiveCommitsToggleBtn");

            container.Add(new CuiLabel
            {
                Text = { Text = "COMMITS", FontSize = 11, Align = TextAnchor.MiddleLeft, Color = "0.55 0.8 0.35 1", Font = "robotocondensed-bold.ttf" },
                RectTransform = { AnchorMin = "0.25 0.08", AnchorMax = "0.97 0.52" }
            }, "LiveCommitsToggleBtn");

            container.Add(new CuiButton
            {
                Button = { Command = "livecommits.toggle", Color = "0 0 0 0" },
                RectTransform = { AnchorMin = "0 0", AnchorMax = "1 1" },
                Text = { Text = "" }
            }, "LiveCommitsToggleBtn");

            CuiHelper.AddUi(player, container);
        }

        void ShowCommitUI(BasePlayer player)
        {
            DestroyUI(player);
            
            int currentPage = playerPages.ContainsKey(player.userID) ? playerPages[player.userID] : 1;

            if (pageCache.ContainsKey(currentPage) && (DateTime.Now - lastCacheUpdate).TotalMinutes < CacheExpiryMinutes)
            {
                DrawCommitUI(player, pageCache[currentPage], currentPage);
                return;
            }

            FetchCommitData(player, currentPage);
        }

        void DrawCommitUI(BasePlayer player, CommitData data, int currentPage)
        {
            CuiHelper.DestroyUi(player, "LiveCommitsOverlay");
            CuiHelper.DestroyUi(player, "LiveCommitsDetailOverlay");
            CuiHelper.DestroyUi(player, "HeaderAvatar");
            CuiHelper.DestroyUi(player, "LiveCommitsHeader");
            CuiHelper.DestroyUi(player, "LiveCommitsMain");
            
            var container = new CuiElementContainer();

            container.Add(new CuiPanel
            {
                Image = { Color = "0 0 0 0.9" },
                RectTransform = { AnchorMin = "0 0", AnchorMax = "1 1" },
                CursorEnabled = true
            }, "Overlay", "LiveCommitsOverlay");

            container.Add(new CuiButton
            {
                Button = { Command = "livecommits.close", Color = "0 0 0 0" },
                RectTransform = { AnchorMin = "0 0", AnchorMax = "1 1" },
                Text = { Text = "" }
            }, "LiveCommitsOverlay", "LiveCommitsBgClose");

            container.Add(new CuiPanel
            {
                Image = { Color = "0.1 0.1 0.1 0.99" },
                RectTransform = { AnchorMin = "0.1 0.06", AnchorMax = "0.9 0.92" }
            }, "LiveCommitsOverlay", "LiveCommitsMain");

            container.Add(new CuiButton
            {
                Button = { Command = "", Color = "0 0 0 0" },
                RectTransform = { AnchorMin = "0 0", AnchorMax = "1 1" },
                Text = { Text = "" }
            }, "LiveCommitsMain");

            container.Add(new CuiPanel
            {
                Image = { Color = "0.4 0.7 0.3 1" },
                RectTransform = { AnchorMin = "0 0.9975", AnchorMax = "1 1" }
            }, "LiveCommitsMain");

            container.Add(new CuiPanel
            {
                Image = { Color = "0.13 0.13 0.13 1" },
                RectTransform = { AnchorMin = "0 0.935", AnchorMax = "1 0.9975" }
            }, "LiveCommitsMain", "LiveCommitsHeader");

            bool isDevView = playerViewingDev.ContainsKey(player.userID);
            
            if (isDevView && data.results.Count > 0 && !string.IsNullOrEmpty(data.results[0].user?.avatar))
            {
                string avatarUrl = data.results[0].user.avatar;
                if (!avatarUrl.Contains("?"))
                    avatarUrl += $"?t={DateTime.Now.Ticks}";
                else
                    avatarUrl += $"&t={DateTime.Now.Ticks}";
                    
                container.Add(new CuiElement
                {
                    Parent = "LiveCommitsHeader",
                    Name = "HeaderAvatar",
                    Components = {
                        new CuiRawImageComponent { Url = avatarUrl, Color = "1 1 1 1" },
                        new CuiRectTransformComponent { AnchorMin = "0.01 0.1", AnchorMax = "0.04 0.9" }
                    }
                });
            }
            else
            {
                container.Add(new CuiElement
                {
                    Parent = "LiveCommitsHeader",
                    Name = "HeaderAvatar",
                    Components = {
                        new CuiRawImageComponent { Url = "https://pbs.twimg.com/profile_images/1247149194093133825/N_9ul0cD_400x400.png", Color = "1 1 1 1" },
                        new CuiRectTransformComponent { AnchorMin = "0.01 0.1", AnchorMax = "0.04 0.9" }
                    }
                });
            }

            if (isDevView)
            {
                string devName = playerViewingDev[player.userID].Split('/')[0].ToUpper();
                container.Add(new CuiLabel
                {
                    Text = { Text = devName, FontSize = 17, Align = TextAnchor.MiddleLeft, Color = "1 1 1 1", Font = "robotocondensed-bold.ttf" },
                    RectTransform = { AnchorMin = "0.05 0.5", AnchorMax = "0.4 1" }
                }, "LiveCommitsHeader");
                
                container.Add(new CuiLabel
                {
                    Text = { Text = "COMMITS", FontSize = 15, Align = TextAnchor.MiddleLeft, Color = "0.55 0.8 0.35 1", Font = "robotocondensed-bold.ttf" },
                    RectTransform = { AnchorMin = "0.05 0", AnchorMax = "0.4 0.5" }
                }, "LiveCommitsHeader");
            }
            else
            {
                container.Add(new CuiLabel
                {
                    Text = { Text = "FACEPUNCH RUST COMMITS", FontSize = 17, Align = TextAnchor.MiddleLeft, Color = "1 1 1 1", Font = "robotocondensed-bold.ttf" },
                    RectTransform = { AnchorMin = "0.05 0", AnchorMax = "0.4 1" }
                }, "LiveCommitsHeader");
            }

            if (isDevView)
            {
                container.Add(new CuiButton
                {
                    Button = { Command = "livecommits.backtoall", Color = "0.25 0.25 0.25 1" },
                    RectTransform = { AnchorMin = "0.41 0.15", AnchorMax = "0.52 0.85" },
                    Text = { Text = "◀ All Commits", FontSize = 11, Align = TextAnchor.MiddleCenter, Color = "0.9 0.9 0.9 1", Font = "robotocondensed-regular.ttf" }
                }, "LiveCommitsHeader");
            }

            int totalDays = (int)(DateTime.Now - new DateTime(2013, 1, 1)).TotalDays;
            double cph = data.total / (double)totalDays / 24.0;
            
            container.Add(new CuiLabel
            {
                Text = { Text = $"<color=#8BC34A>{data.total:N0}</color> Commits", FontSize = 12, Align = TextAnchor.MiddleRight, Color = "0.75 0.75 0.75 1", Font = "robotocondensed-regular.ttf" },
                RectTransform = { AnchorMin = "0.53 0", AnchorMax = "0.92 1" }
            }, "LiveCommitsHeader");

            container.Add(new CuiButton
            {
                Button = { Command = "livecommits.close", Color = "0.75 0.22 0.18 1" },
                RectTransform = { AnchorMin = "0.955 0.15", AnchorMax = "0.99 0.85" },
                Text = { Text = "✕", FontSize = 18, Align = TextAnchor.MiddleCenter, Color = "1 1 1 1", Font = "robotocondensed-bold.ttf" }
            }, "LiveCommitsHeader");

            if (!isDevView)
            {
                DrawFilterSidebar(container, player);
            }

            string contentAnchorMin = isDevView ? "0.008 0.075" : "0.128 0.075";
            
            string filterMode = playerFilterMode.ContainsKey(player.userID) ? playerFilterMode[player.userID] : "all";
            List<CommitResult> filteredCommits = FilterCommits(data.results, filterMode);
            
            if (!isDevView && (filterMode == "merges" || filterMode == "undo"))
            {
                container.Add(new CuiPanel
                {
                    Image = { Color = "0.15 0.15 0.15 1" },
                    RectTransform = { AnchorMin = contentAnchorMin, AnchorMax = "0.992 0.105" }
                }, "LiveCommitsMain", "TimeRangeBar");
                
                if (filteredCommits.Count > 0)
                {
                    DateTime earliest = filteredCommits.Max(c => c.created);
                    DateTime latest = filteredCommits.Min(c => c.created);
                    
                    string timeRangeText = $"Time Range: {earliest:MM/dd/yyyy HH:mm} - {latest:MM/dd/yyyy HH:mm}";
                    
                    container.Add(new CuiLabel
                    {
                        Text = { Text = timeRangeText, FontSize = 11, Align = TextAnchor.MiddleCenter, Color = "0.7 0.7 0.7 1", Font = "robotocondensed-regular.ttf" },
                        RectTransform = { AnchorMin = "0 0", AnchorMax = "1 1" }
                    }, "TimeRangeBar");
                }
                else
                {
                    string filterName = filterMode == "merges" ? "Merges" : "Undos";
                    container.Add(new CuiLabel
                    {
                        Text = { Text = $"No {filterName} on this page", FontSize = 12, Align = TextAnchor.MiddleCenter, Color = "0.9 0.5 0.3 1", Font = "robotocondensed-bold.ttf" },
                        RectTransform = { AnchorMin = "0 0", AnchorMax = "1 1" }
                    }, "TimeRangeBar");
                }
                
                contentAnchorMin = isDevView ? "0.008 0.075" : "0.128 0.11";
            }
            
            container.Add(new CuiPanel
            {
                Image = { Color = "0.08 0.08 0.08 1" },
                RectTransform = { AnchorMin = contentAnchorMin, AnchorMax = "0.992 0.93" }
            }, "LiveCommitsMain", "LiveCommitsContent");

            Dictionary<int, int> filterIndexMapping = new Dictionary<int, int>();
            if (filterMode != "all")
            {
                int filteredIndex = 0;
                for (int i = 0; i < data.results.Count; i++)
                {
                    if (filteredCommits.Contains(data.results[i]))
                    {
                        filterIndexMapping[filteredIndex] = i;
                        filteredIndex++;
                    }
                }
            }

            container.Add(new CuiElement
            {
                Parent = "LiveCommitsContent",
                Name = "LiveCommitsScrollView",
                Components = {
                    new CuiScrollViewComponent {
                        Vertical = true,
                        Horizontal = false,
                        MovementType = UnityEngine.UI.ScrollRect.MovementType.Clamped,
                        Elasticity = 0.1f,
                        Inertia = true,
                        DecelerationRate = 0.2f,
                        ScrollSensitivity = 30f,
                        ContentTransform = new CuiRectTransform {
                            AnchorMin = "0 1",
                            AnchorMax = "1 1",
                            OffsetMin = $"0 -{filteredCommits.Count * 65}",
                            OffsetMax = "0 0"
                        },
                        VerticalScrollbar = new CuiScrollbar {
                            AutoHide = false,
                            HandleColor = "0.4 0.6 0.3 0.8",
                            HighlightColor = "0.5 0.7 0.4 1",
                            TrackColor = "0.15 0.15 0.15 1",
                            Size = 12
                        }
                    },
                    new CuiRectTransformComponent { AnchorMin = "0 0", AnchorMax = "1 1" }
                }
            });

            float itemHeight = 65f;
            float yOffset = 0;

            for (int i = 0; i < filteredCommits.Count; i++)
            {
                var commit = filteredCommits[i];
                int originalIndex = filterMode != "all" ? filterIndexMapping[i] : i;
                DrawCommitItemScrollable(container, "LiveCommitsScrollView", commit, yOffset, itemHeight, originalIndex, isDevView);
                yOffset += itemHeight;
            }

            DrawPagination(container, data, currentPage, isDevView);
            DrawDisclaimer(container);

            CuiHelper.AddUi(player, container);
            playersWithUIOpen.Add(player.userID);
            
            StartUIUpdateTimer(player);
        }

        void DrawFilterSidebar(CuiElementContainer container, BasePlayer player)
        {
            container.Add(new CuiPanel
            {
                Image = { Color = "0.12 0.12 0.12 1" },
                RectTransform = { AnchorMin = "0.008 0.075", AnchorMax = "0.118 0.93" }
            }, "LiveCommitsMain", "FilterSidebar");

            container.Add(new CuiLabel
            {
                Text = { Text = "FILTER", FontSize = 13, Align = TextAnchor.MiddleCenter, Color = "0.55 0.8 0.35 1", Font = "robotocondensed-bold.ttf" },
                RectTransform = { AnchorMin = "0 0.95", AnchorMax = "1 0.99" }
            }, "FilterSidebar");

            container.Add(new CuiPanel
            {
                Image = { Color = "0.4 0.7 0.3 1" },
                RectTransform = { AnchorMin = "0.05 0.945", AnchorMax = "0.95 0.948" }
            }, "FilterSidebar");

            string currentFilter = playerFilterMode.ContainsKey(player.userID) ? playerFilterMode[player.userID] : "all";

            string allColor = currentFilter == "all" ? "0.4 0.7 0.3 1" : "0.18 0.18 0.18 1";
            string allTextColor = currentFilter == "all" ? "1 1 1 1" : "0.7 0.7 0.7 1";
            container.Add(new CuiButton
            {
                Button = { Command = "livecommits.filter all", Color = allColor },
                RectTransform = { AnchorMin = "0.08 0.87", AnchorMax = "0.92 0.93" },
                Text = { Text = "All Commits", FontSize = 12, Align = TextAnchor.MiddleCenter, Color = allTextColor, Font = "robotocondensed-bold.ttf" }
            }, "FilterSidebar");

            string mergesColor = currentFilter == "merges" ? "0.4 0.7 0.3 1" : "0.18 0.18 0.18 1";
            string mergesTextColor = currentFilter == "merges" ? "1 1 1 1" : "0.7 0.7 0.7 1";
            container.Add(new CuiButton
            {
                Button = { Command = "livecommits.filter merges", Color = mergesColor },
                RectTransform = { AnchorMin = "0.08 0.8", AnchorMax = "0.92 0.86" },
                Text = { Text = "Merges", FontSize = 12, Align = TextAnchor.MiddleCenter, Color = mergesTextColor, Font = "robotocondensed-bold.ttf" }
            }, "FilterSidebar");

            string undoColor = currentFilter == "undo" ? "0.4 0.7 0.3 1" : "0.18 0.18 0.18 1";
            string undoTextColor = currentFilter == "undo" ? "1 1 1 1" : "0.7 0.7 0.7 1";
            container.Add(new CuiButton
            {
                Button = { Command = "livecommits.filter undo", Color = undoColor },
                RectTransform = { AnchorMin = "0.08 0.73", AnchorMax = "0.92 0.79" },
                Text = { Text = "Undo", FontSize = 12, Align = TextAnchor.MiddleCenter, Color = undoTextColor, Font = "robotocondensed-bold.ttf" }
            }, "FilterSidebar");

            string updatesColor = currentFilter == "updates" ? "0.4 0.7 0.3 1" : "0.18 0.18 0.18 1";
            string updatesTextColor = currentFilter == "updates" ? "1 1 1 1" : "0.7 0.7 0.7 1";
            container.Add(new CuiButton
            {
                Button = { Command = "livecommits.filter updates", Color = updatesColor },
                RectTransform = { AnchorMin = "0.08 0.66", AnchorMax = "0.92 0.72" },
                Text = { Text = "Updates", FontSize = 12, Align = TextAnchor.MiddleCenter, Color = updatesTextColor, Font = "robotocondensed-bold.ttf" }
            }, "FilterSidebar");
        }

        List<CommitResult> FilterCommits(List<CommitResult> commits, string filterMode)
        {
            if (filterMode == "all")
            {
                return commits;
            }
            else if (filterMode == "merges")
            {
                return commits.Where(c => 
                    !string.IsNullOrEmpty(c.message) && 
                    c.message.ToLower().Contains("merge")
                ).ToList();
            }
            else if (filterMode == "undo")
            {
                return commits.Where(c => 
                    !string.IsNullOrEmpty(c.message) && 
                    c.message.ToLower().Contains("undo")
                ).ToList();
            }
            else if (filterMode == "updates")
            {
                return new List<CommitResult>();
            }

            return commits;
        }

        void DrawCommitDetailUI(BasePlayer player, CommitResult commit, int returnPage, bool isDevView, int commitIndex)
        {
            CuiHelper.DestroyUi(player, "LiveCommitsDetailOverlay");
            
            var container = new CuiElementContainer();

            container.Add(new CuiPanel
            {
                Image = { Color = "0 0 0 0.85", Material = "assets/content/ui/uibackgroundblur.mat" },
                RectTransform = { AnchorMin = "0 0", AnchorMax = "1 1" },
                CursorEnabled = true
            }, "Overlay", "LiveCommitsDetailOverlay");

            container.Add(new CuiButton
            {
                Button = { Command = "livecommits.back", Color = "0 0 0 0" },
                RectTransform = { AnchorMin = "0 0", AnchorMax = "1 1" },
                Text = { Text = "" }
            }, "LiveCommitsDetailOverlay", "DetailBgClose");

            container.Add(new CuiPanel
            {
                Image = { Color = "0.1 0.1 0.1 0.99" },
                RectTransform = { AnchorMin = "0.2 0.15", AnchorMax = "0.8 0.85" }
            }, "LiveCommitsDetailOverlay", "LiveCommitsDetail");

            container.Add(new CuiButton
            {
                Button = { Command = "", Color = "0 0 0 0" },
                RectTransform = { AnchorMin = "0 0", AnchorMax = "1 1" },
                Text = { Text = "" }
            }, "LiveCommitsDetail");

            container.Add(new CuiPanel
            {
                Image = { Color = "0.4 0.7 0.3 1" },
                RectTransform = { AnchorMin = "0 0.995", AnchorMax = "1 1" }
            }, "LiveCommitsDetail");

            container.Add(new CuiPanel
            {
                Image = { Color = "0.13 0.13 0.13 1" },
                RectTransform = { AnchorMin = "0 0.92", AnchorMax = "1 0.995" }
            }, "LiveCommitsDetail", "DetailHeader");

            container.Add(new CuiButton
            {
                Button = { Command = "livecommits.back", Color = "0.25 0.25 0.25 1" },
                RectTransform = { AnchorMin = "0.01 0.15", AnchorMax = "0.08 0.85" },
                Text = { Text = "◀ Back", FontSize = 12, Align = TextAnchor.MiddleCenter, Color = "0.9 0.9 0.9 1", Font = "robotocondensed-regular.ttf" }
            }, "DetailHeader");

            container.Add(new CuiLabel
            {
                Text = { Text = "COMMIT DETAILS", FontSize = 16, Align = TextAnchor.MiddleLeft, Color = "1 1 1 1", Font = "robotocondensed-bold.ttf" },
                RectTransform = { AnchorMin = "0.1 0", AnchorMax = "0.5 1" }
            }, "DetailHeader");

            container.Add(new CuiLabel
            {
                Text = { Text = $"#{commit.changeset}", FontSize = 14, Align = TextAnchor.MiddleRight, Color = "0.4 0.6 0.85 1", Font = "robotocondensed-bold.ttf" },
                RectTransform = { AnchorMin = "0.5 0", AnchorMax = "0.92 1" }
            }, "DetailHeader");

            container.Add(new CuiButton
            {
                Button = { Command = "livecommits.back", Color = "0.75 0.22 0.18 1" },
                RectTransform = { AnchorMin = "0.94 0.15", AnchorMax = "0.99 0.85" },
                Text = { Text = "✕", FontSize = 16, Align = TextAnchor.MiddleCenter, Color = "1 1 1 1", Font = "robotocondensed-bold.ttf" }
            }, "DetailHeader");

            container.Add(new CuiPanel
            {
                Image = { Color = "0.08 0.08 0.08 1" },
                RectTransform = { AnchorMin = "0.015 0.04", AnchorMax = "0.985 0.91" }
            }, "LiveCommitsDetail", "DetailContent");

            if (!string.IsNullOrEmpty(commit.user?.avatar))
            {
                container.Add(new CuiElement
                {
                    Parent = "DetailContent",
                    Name = "DetailAvatar",
                    Components = {
                        new CuiRawImageComponent { Url = commit.user.avatar, Color = "1 1 1 1" },
                        new CuiRectTransformComponent { AnchorMin = "0.03 0.7", AnchorMax = "0.15 0.95" }
                    }
                });

                container.Add(new CuiButton
                {
                    Button = { Command = $"livecommits.viewdev {commitIndex}", Color = "0 0 0 0" },
                    RectTransform = { AnchorMin = "0 0", AnchorMax = "1 1" },
                    Text = { Text = "" }
                }, "DetailAvatar");
            }

            string userName = commit.user?.name ?? "Unknown";
            container.Add(new CuiPanel
            {
                Image = { Color = "0 0 0 0" },
                RectTransform = { AnchorMin = "0.17 0.82", AnchorMax = "0.7 0.95" }
            }, "DetailContent", "UserNamePanel");

            container.Add(new CuiLabel
            {
                Text = { Text = userName, FontSize = 22, Align = TextAnchor.MiddleLeft, Color = "0.3 0.55 0.2 1", Font = "robotocondensed-bold.ttf" },
                RectTransform = { AnchorMin = "0 0", AnchorMax = "1 1" }
            }, "UserNamePanel");

            container.Add(new CuiButton
            {
                Button = { Command = $"livecommits.viewdev {commitIndex}", Color = "0 0 0 0" },
                RectTransform = { AnchorMin = "0 0", AnchorMax = "1 1" },
                Text = { Text = "" }
            }, "UserNamePanel");

            string exactTime = commit.created.ToString("dddd, MMMM dd, yyyy 'at' HH:mm:ss 'UTC'");
            container.Add(new CuiLabel
            {
                Text = { Text = exactTime, FontSize = 13, Align = TextAnchor.MiddleLeft, Color = "0.6 0.6 0.6 1", Font = "robotocondensed-regular.ttf" },
                RectTransform = { AnchorMin = "0.17 0.74", AnchorMax = "0.7 0.82" }
            }, "DetailContent");

            string timeAgo = GetTimeAgoDetailed(commit.created);
            container.Add(new CuiLabel
            {
                Text = { Text = $"({timeAgo})", FontSize = 12, Align = TextAnchor.MiddleLeft, Color = "0.5 0.5 0.5 1", Font = "robotocondensed-regular.ttf" },
                RectTransform = { AnchorMin = "0.17 0.67", AnchorMax = "0.7 0.74" }
            }, "DetailContent");

            container.Add(new CuiLabel
            {
                Text = { Text = $"<color=#6B9E3A>▲</color> {commit.likes}  <color=#C75050>▼</color> {commit.dislikes}", FontSize = 16, Align = TextAnchor.MiddleRight, Color = "0.7 0.7 0.7 1", Font = "robotocondensed-regular.ttf" },
                RectTransform = { AnchorMin = "0.7 0.82", AnchorMax = "0.97 0.95" }
            }, "DetailContent");

            container.Add(new CuiPanel
            {
                Image = { Color = "0.2 0.2 0.2 1" },
                RectTransform = { AnchorMin = "0.03 0.64", AnchorMax = "0.97 0.645" }
            }, "DetailContent");

            string repoName = commit.repo ?? "";
            string branchName = commit.branch ?? "";
            container.Add(new CuiLabel
            {
                Text = { Text = $"<color=#5A9E3A>{repoName}/</color><color=#D4883A>{branchName}</color>", FontSize = 14, Align = TextAnchor.MiddleLeft, Color = "1 1 1 1", Font = "robotocondensed-regular.ttf" },
                RectTransform = { AnchorMin = "0.03 0.56", AnchorMax = "0.97 0.63" }
            }, "DetailContent");

            container.Add(new CuiLabel
            {
                Text = { Text = "Commit Message:", FontSize = 11, Align = TextAnchor.MiddleLeft, Color = "0.5 0.5 0.5 1", Font = "robotocondensed-regular.ttf" },
                RectTransform = { AnchorMin = "0.03 0.48", AnchorMax = "0.97 0.54" }
            }, "DetailContent");

            string message = FormatCommitMessage(commit.message ?? "");
            container.Add(new CuiPanel
            {
                Image = { Color = "0.08 0.08 0.08 1" },
                RectTransform = { AnchorMin = "0.03 0.08", AnchorMax = "0.97 0.47" }
            }, "DetailContent", "MessageBox");

            container.Add(new CuiLabel
            {
                Text = { Text = message, FontSize = 13, Align = TextAnchor.UpperLeft, Color = "0.75 0.75 0.75 1", Font = "robotocondensed-regular.ttf" },
                RectTransform = { AnchorMin = "0.02 0.02", AnchorMax = "0.98 0.98" }
            }, "MessageBox");

            container.Add(new CuiPanel
            {
                Image = { Color = "0.12 0.12 0.12 1" },
                RectTransform = { AnchorMin = "0 0", AnchorMax = "1 0.035" }
            }, "LiveCommitsDetail", "DetailDisclaimer");

            container.Add(new CuiLabel
            {
                Text = { Text = "This plugin is unofficial and not affiliated with or endorsed by Facepunch Studios.", FontSize = 9, Align = TextAnchor.MiddleCenter, Color = "0.7 0.4 0.4 1", Font = "robotocondensed-regular.ttf" },
                RectTransform = { AnchorMin = "0 0", AnchorMax = "1 1" }
            }, "DetailDisclaimer");

            CuiHelper.AddUi(player, container);
        }

        void DrawCommitItemScrollable(CuiElementContainer container, string parent, CommitResult commit, float yOffset, float height, int index, bool isDevView)
        {
            string panelName = $"CommitItem{index}";
            string bgColor = index % 2 == 0 ? "0.09 0.09 0.09 1" : "0.07 0.07 0.07 1";
            
            container.Add(new CuiPanel
            {
                Image = { Color = bgColor },
                RectTransform = { 
                    AnchorMin = "0 1", 
                    AnchorMax = "1 1",
                    OffsetMin = $"0 -{yOffset + height}",
                    OffsetMax = $"0 -{yOffset}"
                }
            }, parent, panelName);

            container.Add(new CuiPanel
            {
                Image = { Color = "0.35 0.6 0.25 1" },
                RectTransform = { AnchorMin = "0 0", AnchorMax = "0.004 1" }
            }, panelName);

            string userName = commit.user?.name ?? "Unknown";
            string repoName = commit.repo ?? "";
            
            if (isDevView)
            {
                string timeAgo = GetTimeAgoDetailed(commit.created);
                container.Add(new CuiLabel
                {
                    Text = { Text = timeAgo, FontSize = 12, Align = TextAnchor.MiddleLeft, Color = "0.6 0.6 0.6 1", Font = "robotocondensed-bold.ttf" },
                    RectTransform = { AnchorMin = "0.02 0.3", AnchorMax = "0.2 0.8" }
                }, panelName, $"DevTimeLabel{index}");
            }
            else
            {
                if (!string.IsNullOrEmpty(commit.user?.avatar))
                {
                    container.Add(new CuiElement
                    {
                        Parent = panelName,
                        Name = $"Avatar{index}",
                        Components = {
                            new CuiRawImageComponent { Url = commit.user.avatar, Color = "1 1 1 1" },
                            new CuiRectTransformComponent { AnchorMin = "0.012 0.15", AnchorMax = "0.052 0.85" }
                        }
                    });
                }

                container.Add(new CuiLabel
                {
                    Text = { Text = userName, FontSize = 11, Align = TextAnchor.MiddleLeft, Color = "0.3 0.55 0.2 1", Font = "robotocondensed-bold.ttf" },
                    RectTransform = { AnchorMin = "0.06 0.6", AnchorMax = "0.18 0.95" }
                }, panelName);

                string timeAgo = GetTimeAgoDetailed(commit.created);
                container.Add(new CuiLabel
                {
                    Text = { Text = timeAgo, FontSize = 9, Align = TextAnchor.MiddleLeft, Color = "0.45 0.45 0.45 1", Font = "robotocondensed-regular.ttf" },
                    RectTransform = { AnchorMin = "0.06 0.3", AnchorMax = "0.18 0.6" }
                }, panelName, $"TimeLabel{index}");
            }

            string branchName = commit.branch ?? "";
            container.Add(new CuiLabel
            {
                Text = { Text = $"<color=#5A9E3A>{repoName}/</color><color=#D4883A>{branchName}</color>", FontSize = 12, Align = TextAnchor.MiddleLeft, Color = "1 1 1 1", Font = "robotocondensed-regular.ttf" },
                RectTransform = { AnchorMin = "0.185 0.6", AnchorMax = "0.7 0.95" }
            }, panelName);

            container.Add(new CuiLabel
            {
                Text = { Text = $"#{commit.changeset}", FontSize = 10, Align = TextAnchor.MiddleLeft, Color = "0.4 0.6 0.85 1", Font = "robotocondensed-bold.ttf" },
                RectTransform = { AnchorMin = "0.7 0.6", AnchorMax = "0.82 0.95" }
            }, panelName);

            string message = FormatCommitMessage(commit.message ?? "");
            if (message.Length > 90) message = message.Substring(0, 90) + "...";
            container.Add(new CuiLabel
            {
                Text = { Text = message, FontSize = 11, Align = TextAnchor.MiddleLeft, Color = "0.7 0.7 0.7 1", Font = "robotocondensed-regular.ttf" },
                RectTransform = { AnchorMin = "0.185 0.08", AnchorMax = "0.82 0.55" }
            }, panelName);

            container.Add(new CuiLabel
            {
                Text = { Text = $"<color=#6B9E3A>▲</color> {commit.likes}", FontSize = 10, Align = TextAnchor.MiddleCenter, Color = "0.6 0.6 0.6 1", Font = "robotocondensed-regular.ttf" },
                RectTransform = { AnchorMin = "0.85 0.5", AnchorMax = "0.92 0.92" }
            }, panelName);

            container.Add(new CuiLabel
            {
                Text = { Text = $"<color=#C75050>▼</color> {commit.dislikes}", FontSize = 10, Align = TextAnchor.MiddleCenter, Color = "0.6 0.6 0.6 1", Font = "robotocondensed-regular.ttf" },
                RectTransform = { AnchorMin = "0.92 0.5", AnchorMax = "0.99 0.92" }
            }, panelName);

            container.Add(new CuiButton
            {
                Button = { Command = $"livecommits.view {index}", Color = "0 0 0 0" },
                RectTransform = { AnchorMin = "0.19 0", AnchorMax = "1 1" },
                Text = { Text = "" }
            }, panelName);
            
            if (!isDevView)
            {
                if (!string.IsNullOrEmpty(commit.user?.avatar))
                {
                    container.Add(new CuiButton
                    {
                        Button = { Command = $"livecommits.viewdev {index}", Color = "0 0 0 0" },
                        RectTransform = { AnchorMin = "0.012 0.15", AnchorMax = "0.052 0.85" },
                        Text = { Text = "" }
                    }, panelName, $"AvatarBtn{index}");
            
// ... [Code preview truncated due to length. Full source in attached file] ...

Автор
15FPS
Скачиваний
0
Просмотры
1
Первый выпуск
Обновление
Оценка
0.00 звёзд 0 оценок
Сверху