Merge pull request #1 from dnwSilver/feature/command-line

Feature/command line
This commit is contained in:
Kolosov Alexandr 2024-11-01 18:59:03 +05:00 committed by GitHub
commit d01491f5cd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
117 changed files with 3248 additions and 749 deletions

18
TLD.sln
View File

@ -2,6 +2,12 @@
Microsoft Visual Studio Solution File, Format Version 12.00
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TUI", "src\TUI\TUI.csproj", "{F92C03F7-2A65-4D0A-9736-13E749AF6903}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TUI.Engine.Tests", "tests\TUI.Engine.Tests\TUI.Engine.Tests.csproj", "{2F0611D2-073F-4E26-BD1B-ACC433FC6F4E}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TUI.Engine", "src\TUI.Engine\TUI.Engine.csproj", "{38E7E2DD-40C1-4B7C-9A7A-E3677AD55431}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TUI.Controls.Tests", "tests\TUI.Controls.Tests\TUI.Controls.Tests.csproj", "{59A0E843-8664-45B9-AD0C-52B8FE38986A}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -16,5 +22,17 @@ Global
{F92C03F7-2A65-4D0A-9736-13E749AF6903}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F92C03F7-2A65-4D0A-9736-13E749AF6903}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F92C03F7-2A65-4D0A-9736-13E749AF6903}.Release|Any CPU.Build.0 = Release|Any CPU
{2F0611D2-073F-4E26-BD1B-ACC433FC6F4E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2F0611D2-073F-4E26-BD1B-ACC433FC6F4E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2F0611D2-073F-4E26-BD1B-ACC433FC6F4E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2F0611D2-073F-4E26-BD1B-ACC433FC6F4E}.Release|Any CPU.Build.0 = Release|Any CPU
{38E7E2DD-40C1-4B7C-9A7A-E3677AD55431}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{38E7E2DD-40C1-4B7C-9A7A-E3677AD55431}.Debug|Any CPU.Build.0 = Debug|Any CPU
{38E7E2DD-40C1-4B7C-9A7A-E3677AD55431}.Release|Any CPU.ActiveCfg = Release|Any CPU
{38E7E2DD-40C1-4B7C-9A7A-E3677AD55431}.Release|Any CPU.Build.0 = Release|Any CPU
{59A0E843-8664-45B9-AD0C-52B8FE38986A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{59A0E843-8664-45B9-AD0C-52B8FE38986A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{59A0E843-8664-45B9-AD0C-52B8FE38986A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{59A0E843-8664-45B9-AD0C-52B8FE38986A}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal

76
docs/Features.txt Normal file
View File

@ -0,0 +1,76 @@
󰘳 1 󱓟 releases  select prev
󰘳 2 󰧑 competencies  select next
󰘳 3  dependencies 󰬌 toggle head
󰘳 4 󰬘 quit
󰎔 too new  Auth  package
 so good 󰞉 WWW 󰡨 image
 be nice 󰚩 SEO  site
󰬟 too old  VCS  api
  󱓟 releases  Fact  2023
  󱓟 releases  Fact
  󱓟 releases  Planned
  󱓟 releases  Planned  2024-12-31 xfirm
  󰧑 competencies
  󰶼 growth zone
   dependencies
  󱁤 tools
󰦖 wait 󰲽 build
󱔢 reopen 󱞈 testing
󱞇 in progress 󰦕 done
󱞇 review 󱄊 cancel
┌──────────────────────────────────────────────────────────────────────── Planned release ─────────────────────────────────────────────────────────────────┐
│ 󱓟 Planned 󱜜 󰲌 application  Tasks
│ 2024-12-31 1d elk-site 4/11 󰦕 󰦕 󰦕 󰦕 󱞇 󱞇 󱞇 󱞈 󱞈 󱔢 󰦖 󰦖 󰦖
│ elk-site 2/5 󰦕 󰦕 󰦖 󰦖 󰦖
│ elk-site 22/22 󰦕 󰦕 󰦕 󰦕 󰦕 󰦕 󰦕 󰦕 󰦕 󰦕 󰦕 󰦕 󰦕 󰦕 󰦕 󰦕 󱞇 󱞇 󱞇 󱞈 󱞈 󱞈 󱔢 󰦖 󰦖 󰦖
│ 2024-12-31 12d xfirm 4/12 󰦕 󰦕 󰦕 󰦕 󱞇 󱞇 󱞇 󱞈 󱞈 󱞈 󱔢 󰦖 󰦖 󰦖
└──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
󰨑 show stopper 󰦖 wait 󰲽 build  feature
󰨑 first of all 󱔢 reopen 󱞈 testing  bug
󰨑 just do it 󱞇 in progress 󰦕 done
󰨑 doesn't rush 󱞇 review 󱄊 cancel
┌──────────────────────────────────────────────────────────────────────── checkperson-site ────────────────────────────────────────────────────────────────┐
│ 󰨑 󰦖  ELK-3628 Create new menu.  Anna P. 󰵮 Petrov A. 󰙨 Ivanov I.
│ 󰨑 󰦕  XF-12 Change input hover.  Alex K. 󰵮 Ivanov I. 󰙨 Petrov A., Petrov B.
│ 󰨑 󱞇  ELK-3628 Crete new project with menu, profile, issues and #  Anna P. 󰵮 Ivanov I. 󰙨 Petrov A., Petrov B.
└──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
 publish
󱓞 release
󰶯 hotfix
󱓞 release candidate
┌─────────────────────────────────────────────────────────────────────────── Actual releases 2023 ─────────────────────────────────────────────────────────┐
│ Jan Feb Mar Apl May Jun Jul Aug Sep Oct Nov Dec
│ ├─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤
│ LK 󱓞 󱓞  󱓞 󱓞  󱓞 󱓞  󱓞 󱓞  󱓞 󱓞  󱓞 󱓞  󱓞 󱓞 󱓞  󱓞 󱓞  󱓞 󱓞  󱓞 󱓞  󱓞 󱓞
│ CP 󱓞  󱓞  󱓞  󱓞    󱓞  󱓞  󱓞  󱓞  󱓞  󱓞  󱓞
│ RR 󰶯 󰶯  󰶯 󰶯  󰶯 󰶯  󰶯 󰶯  󰶯 󰶯  󰶯 󰶯  󰶯 󰶯  󰶯 󰶯  󰶯 󰶯  󰶯 󰶯  󰶯 󰶯  󰶯 󰶯
└──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
 GA4  Jaeger 󰡾 Fallback  Screenshots  Connected  Auth
Я YM  Prometheus 󰉼 Renovate  End2End  Disconnected 󰖟 WWW
󰩃 Husky 󱎴 Sentry 󱉕 Lighthouse  Unit  Not implemented 󰚩 SEO
 Probs  Grafana 󱂅 Kibana  Coverage  No need
┌─────────────────────────────────────────────────────────────────────────── Tools ──────────────────────────────────┐
│   Я  󰉼 󰩃 󱎴 󱉕   󱂅     󰖟 󰚩  󰡾
│ elk-site                   
└────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘

51
docs/RENDER.md Normal file
View File

@ -0,0 +1,51 @@
# RENDER
```mermaid
classDiagram
%% Application layer
class ViewPort{
+ GetCanvas()
+ string: Content
}
%% Хранит данные
class Store {
Reload()
}
%% Хранит разметку
class View {
Store
SetWidgets()
SetCanvas()
Render()
}
%% Подгатавливает текст
class Painter {
+ SetGrid()
+ SetView()
}
%% Composition
class Widget {
+ State
~ Bind()
~ Render()
}
class WidgetState
<<Enum>> WidgetState
WidgetState : Loading
WidgetState : Mounted
WidgetState : BindingError
WidgetState : RenderError
```
```mermaid
flowchart LR
Change_Store --> Binding_Widgets
Binding_Widgets --> Render_Widgets
Render_Widgets --> Create_View
Create_View --> Fill_View_Port
```

View File

@ -0,0 +1,3 @@
namespace TUI.Engine.Attributes.Alignments;
public record Alignment(Horizontal Horizontal, Vertical Vertical);

View File

@ -0,0 +1,8 @@
namespace TUI.Engine.Attributes.Alignments;
public enum Horizontal
{
Left = 0,
Center = 1,
Right = 2,
}

View File

@ -0,0 +1,10 @@
namespace TUI.Engine.Attributes.Alignments;
public interface IWithAlignment
{
internal Alignment Alignment { get; }
void SetAlignment(Vertical vertical);
void SetAlignment(Horizontal horizontal);
}

View File

@ -0,0 +1,8 @@
namespace TUI.Engine.Attributes.Alignments;
public enum Vertical
{
Top,
Center,
Bottom,
}

View File

@ -0,0 +1,10 @@
namespace TUI.Engine.Attributes.Orientations;
public interface IWithOrientation
{
internal Orientation Orientation { get; }
public void SetOrientationHorizontal();
public void SetOrientationVertical();
}

View File

@ -0,0 +1,7 @@
namespace TUI.Engine.Attributes.Orientations;
public enum Orientation
{
Horizontal,
Vertical,
}

View File

@ -0,0 +1,14 @@
using TUI.Engine.Theme;
namespace TUI.Engine.Attributes.Paddings;
public interface IWithPadding
{
Padding Padding { get; }
void SetPadding(Level level);
void SetPaddingLeft(Level level);
void SetPaddingTop(Level level);
void SetPaddingBottom(Level level);
void SetPaddingRight(Level level);
}

View File

@ -0,0 +1,15 @@
using TUI.Engine.Theme;
namespace TUI.Engine.Attributes.Paddings;
public record Padding(
Level Left = Level.None,
Level Top = Level.None,
Level Right = Level.None,
Level Bottom = Level.None
)
{
public Padding(Level padding) : this(Left: padding, Top: padding, Right: padding, Bottom: padding)
{
}
}

View File

@ -0,0 +1,16 @@
using TUI.Engine.Attributes.Orientations;
namespace TUI.Engine.Attributes.Resizings;
public interface IResizable
{
internal Resizing ResizingHorizontal { get; }
internal Resizing ResizingVertical { get; }
internal Size GetFixedSize();
void SetAdaptive(Orientation orientation);
void SetFixed(Orientation orientation, int value);
}

View File

@ -0,0 +1,7 @@
namespace TUI.Engine.Attributes.Resizings;
public enum Resizing
{
Adaptive,
Fixed
}

View File

@ -0,0 +1,60 @@
using TUI.Engine.Attributes.Orientations;
using TUI.Engine.Containers;
namespace TUI.Engine.Attributes.Resizings;
internal static class ResizingExtensions
{
internal static int GetHeight(this IResizable node, IContainer container, int maxHeight, int nodeIndex)
{
if (node.ResizingVertical == Resizing.Fixed)
{
return node.GetFixedSize().Height;
}
if (container.Orientation == Orientation.Horizontal)
{
return maxHeight;
}
var fixedNodes = container.GetFixedNodes().ToArray();
var absoluteNodes = container.GetAbsoluteNodes().ToArray();
var fixedHeight = fixedNodes.Sum(s => s.GetFixedSize().Height);
var allowableHeight = maxHeight - fixedHeight;
var allowableCount = container.GetNodes().Count - fixedNodes.Length - absoluteNodes.Length;
var nodeHeight = (allowableHeight / allowableCount).Min(1);
var nodeNumber = nodeIndex + 1 - container.GetFixedNodes(nodeIndex).Count() -
container.GetAbsoluteNodes(nodeIndex).Count();
if (allowableHeight - nodeNumber * nodeHeight < nodeHeight)
{
return allowableHeight + nodeHeight - nodeNumber * nodeHeight;
}
return nodeHeight;
}
internal static int GetWidth(this IResizable node, IContainer container, int maxWidth)
{
if (node.ResizingHorizontal == Resizing.Fixed)
{
return node.GetFixedSize().Width;
}
if (container.Orientation == Orientation.Vertical)
{
return maxWidth;
}
var fixedNodes = container
.GetNodes()
.Where(n => n.ResizingHorizontal == Resizing.Fixed).ToArray();
var allowableWidth = maxWidth - fixedNodes.Sum(s => s.GetFixedSize().Width);
var allowableCount = container.GetNodes().Count - fixedNodes.Length;
return allowableWidth / allowableCount;
}
}

View File

@ -0,0 +1,10 @@
using TUI.Engine.Nodes;
namespace TUI.Engine.Attributes;
public readonly record struct Size(int Width, int Height)
{
public static Size operator -(Size a, Position b) => new(a.Width - b.Left, a.Height - b.Top);
public override string ToString() => $"W[{Width}] H[{Height}]";
}

View File

@ -0,0 +1,56 @@
using TUI.Engine.Attributes;
using TUI.Engine.Attributes.Alignments;
using TUI.Engine.Attributes.Paddings;
using TUI.Engine.Nodes;
using TUI.Engine.Theme;
namespace TUI.Engine.Components;
public abstract class ComponentBase : NodeBase, IComponent
{
protected abstract Sketch DrawComponent(Size minSize);
public bool IsRelative { get; private set; } = true;
public void SetRelative()
{
IsRelative = true;
}
public void SetAbsolute()
{
IsRelative = false;
}
Sketch IComponent.MakeSketch(Size minSize) => DrawComponent(minSize);
#region Alignments
Alignment IWithAlignment.Alignment => Alignment;
internal Alignment Alignment { get; private set; } = new(Defaults.HorizontalAlignment, Defaults.VerticalAlignment);
public void SetAlignment(Vertical vertical) => Alignment = Alignment with { Vertical = vertical };
public void SetAlignment(Horizontal horizontal) => Alignment = Alignment with { Horizontal = horizontal };
#endregion
#region Paddings
Padding IWithPadding.Padding => Padding;
internal Padding Padding { get; private set; } = new(Defaults.Padding);
public void SetPadding(Level level) => Padding = new Padding(level);
public void SetPaddingTop(Level level) => Padding = Padding with { Top = level };
public void SetPaddingRight(Level level) => Padding = Padding with { Right = level };
public void SetPaddingBottom(Level level) => Padding = Padding with { Bottom = level };
public void SetPaddingLeft(Level level) => Padding = Padding with { Left = level };
#endregion
}

View File

@ -0,0 +1,40 @@
using TUI.Engine.Attributes;
using TUI.Engine.Attributes.Alignments;
using TUI.Engine.Nodes;
namespace TUI.Engine.Components;
internal static class ComponentExtensions
{
internal static Position CorrectContentPosition(this IComponent component,
Position pencil,
Size maxSize,
Size sketchSize)
{
var padding = component.Padding;
var alignment = component.Alignment;
var alignmentCompensationLeft = GetAlignmentCompensationLeft(alignment.Horizontal, maxSize, sketchSize);
var alignmentCompensationTop = GetAlignmentCompensationTop(alignment.Vertical, maxSize, sketchSize);
var left = pencil.Left + (int)padding.Left + alignmentCompensationLeft;
var top = pencil.Top + (int)padding.Top + alignmentCompensationTop;
return new Position(left, top);
}
private static int GetAlignmentCompensationLeft(Horizontal alignment, Size maxSize, Size sketchSize) =>
alignment switch
{
Horizontal.Left => 0,
Horizontal.Center => (maxSize.Width - sketchSize.Width) / 2,
Horizontal.Right => maxSize.Width - sketchSize.Width,
_ => 0
};
private static int GetAlignmentCompensationTop(Vertical alignment, Size maxSize, Size sketchSize) =>
alignment switch
{
Vertical.Top => 0,
Vertical.Center => (maxSize.Height - sketchSize.Height) / 2,
Vertical.Bottom => maxSize.Height - sketchSize.Height,
_ => 0
};
}

View File

@ -0,0 +1,17 @@
using TUI.Engine.Attributes;
using TUI.Engine.Attributes.Alignments;
using TUI.Engine.Attributes.Paddings;
using TUI.Engine.Nodes;
namespace TUI.Engine.Components;
public interface IComponent : INode, IWithAlignment, IWithPadding
{
internal bool IsRelative { get; }
public void SetRelative();
public void SetAbsolute();
internal Sketch MakeSketch(Size minSize);
}

View File

@ -0,0 +1,31 @@
using System.Text;
using TUI.Engine.Attributes;
namespace TUI.Engine.Components;
public sealed class Sketch : IEnumerable<string>
{
private IEnumerable<string> ContentRows { get; }
public Sketch(string content) => ContentRows = content.Split(Symbols.LineBreak);
public Sketch(StringBuilder builder) => ContentRows = builder.ToString().Split(Symbols.LineBreak);
public IEnumerator<string> GetEnumerator() => ContentRows.GetEnumerator();
public IEnumerable<string> Crop(Size maxSize) => ContentRows
.Select(row => maxSize.Width < row.GetWidth() ? row[..maxSize.Width] : row)
.Take(maxSize.Height)
.ToArray();
public Size GetSize()
{
var width = ContentRows.Select(row => row.GetWidth()).DefaultIfEmpty(0).Max();
var height = ContentRows.Count();
return new Size(width, height);
}
public override string ToString() => string.Join(Symbols.LineBreak, ContentRows);
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() => GetEnumerator();
}

View File

@ -0,0 +1,27 @@
using System.Text;
using TUI.Engine.Attributes;
namespace TUI.Engine.Components;
public abstract class StaticComponentBase : ComponentBase
{
private Sketch? _cache;
protected abstract void RenderWithCache(StringBuilder builder);
protected override Sketch DrawComponent(Size sketchMinSize)
{
if (_cache is not null)
{
return _cache;
}
var builder = new StringBuilder();
RenderWithCache(builder);
_cache = new Sketch(builder.ToString());
return _cache;
}
}

View File

@ -0,0 +1,24 @@
using TUI.Engine.Attributes.Orientations;
using TUI.Engine.Nodes;
using TUI.Engine.Theme;
namespace TUI.Engine.Containers;
public abstract class ContainerBase : NodeBase, IContainer
{
private Orientation _orientation = Defaults.Orientation;
Orientation IWithOrientation.Orientation => _orientation;
public void SetOrientationHorizontal()
{
_orientation = Orientation.Horizontal;
}
public void SetOrientationVertical()
{
_orientation = Orientation.Vertical;
}
public abstract Nodes.Nodes GetNodes();
}

View File

@ -0,0 +1,42 @@
using TUI.Engine.Attributes.Resizings;
using TUI.Engine.Components;
using TUI.Engine.Nodes;
namespace TUI.Engine.Containers;
internal static class ContainerExtensions
{
private static readonly Func<INode, bool> AbsoluteNodes = node => node is IComponent { IsRelative: false };
private static readonly Func<INode, bool> FixedNodes = node => node.ResizingVertical == Resizing.Fixed;
internal static IEnumerable<INode> GetAbsoluteNodes(this IContainer container, int? takeNodeNumber = null)
{
if (takeNodeNumber is not null)
{
return container
.GetNodes()
.Take(takeNodeNumber.Value + 1)
.Where(AbsoluteNodes);
}
return container
.GetNodes()
.Where(AbsoluteNodes);
}
internal static IEnumerable<INode> GetFixedNodes(this IContainer container, int? takeNodeNumber = null)
{
if (takeNodeNumber is not null)
{
return container
.GetNodes()
.Take(takeNodeNumber.Value + 1)
.Where(FixedNodes);
}
return container
.GetNodes()
.Where(FixedNodes);
}
}

View File

@ -0,0 +1,9 @@
using TUI.Engine.Attributes.Orientations;
using TUI.Engine.Nodes;
namespace TUI.Engine.Containers;
public interface IContainer : INode, IWithOrientation
{
public Nodes.Nodes GetNodes();
}

View File

@ -0,0 +1,51 @@
using System.Globalization;
using System.Text;
using System.Text.RegularExpressions;
namespace TUI.Engine;
public static class Extensions
{
public static int Max(this int value, int maxValue)
{
return value <= maxValue ? value : maxValue;
}
public static int Min(this int value, int minValue)
{
return value > minValue ? value : minValue;
}
public static bool Have(this IEnumerable<string> array, string findValue)
{
return array.Any(item => item == findValue);
}
public static string Repeat(this string value, int count)
{
return count < 0 ? string.Empty : new StringBuilder(value.Length * count).Insert(0, value, count).ToString();
}
public static string RemoveColors(this string text)
{
return Regex.Replace(text, @"\S\[(\d{0,3}[;m]_?){0,5}", "");
}
public static int GetWidth(this string text)
{
if (string.IsNullOrEmpty(text)) return 0;
var clearText = text.RemoveColors();
var stringInfo = new StringInfo(clearText);
return stringInfo.LengthInTextElements;
}
public static Version ToVersion(this string textVersion)
{
var version = textVersion.Replace("^", "").Replace("~", "").Split(".");
var major = Convert.ToInt32(version[0]);
var minor = Convert.ToInt32(version[1]);
var patch = Convert.ToInt32(version[2].Split('-')[0]);
return new Version(major, minor, patch);
}
}

55
src/TUI.Engine/Helper.cs Normal file
View File

@ -0,0 +1,55 @@
using System.Runtime.CompilerServices;
using Pastel;
using TUI.Engine.Attributes;
using TUI.Engine.Nodes;
[assembly: InternalsVisibleTo("TUI.Engine.Tests", AllInternalsVisible = true)]
namespace TUI.Engine;
public static class Helper
{
private static readonly Queue<ConsoleColor> Colors = new();
private static void Init()
{
Colors.Enqueue(ConsoleColor.DarkYellow);
Colors.Enqueue(ConsoleColor.DarkMagenta);
Colors.Enqueue(ConsoleColor.DarkGreen);
Colors.Enqueue(ConsoleColor.DarkCyan);
Colors.Enqueue(ConsoleColor.DarkBlue);
Colors.Enqueue(ConsoleColor.DarkRed);
Colors.Enqueue(ConsoleColor.Cyan);
Colors.Enqueue(ConsoleColor.Yellow);
}
public static void ShowBackground(Position position, Size size)
{
return;
if (!Colors.Any())
{
Init();
}
var color = Colors.Dequeue();
var top = position.Top;
var height = 0;
while (height < size.Height)
{
var left = position.Left;
var width = 0;
while (width < size.Width)
{
Console.SetCursorPosition(left, top);
Console.Write(Symbols.Space.ToString().PastelBg(color));
width++;
left++;
}
height++;
top++;
}
}
}

View File

@ -0,0 +1,15 @@
using TUI.Engine.Attributes;
using TUI.Engine.Attributes.Resizings;
using TUI.Engine.Rendering.Canvas;
namespace TUI.Engine.Nodes;
public interface INode : IResizable
{
DrawContext DrawContext { get; set; }
StyleContext StyleContext { get; set; }
}
public record DrawContext(ICanvas Canvas, Position Pencil, Size MaxSize);
public record StyleContext(string? Foreground, string? Background = null);

View File

@ -0,0 +1,62 @@
using TUI.Engine.Attributes;
using TUI.Engine.Attributes.Orientations;
using TUI.Engine.Attributes.Resizings;
using TUI.Engine.Theme;
namespace TUI.Engine.Nodes;
public abstract class NodeBase : INode
{
private int _fixedWidth;
private int _fixedHeight;
Size IResizable.GetFixedSize() => new(_fixedWidth, _fixedHeight);
#region Resizing
private Resizing ResizingHorizontal { get; set; } = Defaults.HorizontalResizing;
Resizing IResizable.ResizingHorizontal => ResizingHorizontal;
private Resizing ResizingVertical { get; set; } = Defaults.VerticalResizing;
Resizing IResizable.ResizingVertical => ResizingVertical;
public void SetAdaptive(Orientation orientation)
{
switch (orientation)
{
case Orientation.Horizontal:
ResizingHorizontal = Resizing.Adaptive;
break;
case Orientation.Vertical:
ResizingVertical = Resizing.Adaptive;
break;
default:
throw new ArgumentOutOfRangeException(nameof(orientation), orientation, null);
}
}
public void SetFixed(Orientation orientation, int value)
{
switch (orientation)
{
case Orientation.Horizontal:
ResizingHorizontal = Resizing.Fixed;
_fixedWidth = value;
break;
case Orientation.Vertical:
ResizingVertical = Resizing.Fixed;
_fixedHeight = value;
break;
default:
throw new ArgumentOutOfRangeException(nameof(orientation), orientation, null);
}
}
#endregion Resizing
public DrawContext? DrawContext { get; set; }
public StyleContext? StyleContext { get; set; }
}

View File

@ -0,0 +1,15 @@
using TUI.Engine.Attributes;
using TUI.Engine.Attributes.Resizings;
using TUI.Engine.Containers;
namespace TUI.Engine.Nodes;
internal static class NodeExtensions
{
internal static Size GetSize(this INode node, IContainer parentContainer, int nodeNumber, Size allowableSize)
{
var width = node.GetWidth(parentContainer, allowableSize.Width);
var height = node.GetHeight(parentContainer, allowableSize.Height, nodeNumber);
return new Size(width, height);
}
}

View File

@ -0,0 +1,5 @@
namespace TUI.Engine.Nodes;
public class Nodes : List<INode>
{
}

View File

@ -0,0 +1,8 @@
namespace TUI.Engine.Nodes;
public record Position(int Left, int Top)
{
public static readonly Position Default = new(0, 0);
public override string ToString() => $"L[{Left}] T[{Top}]";
}

View File

@ -0,0 +1,35 @@
using TUI.Engine.Attributes;
using TUI.Engine.Nodes;
namespace TUI.Engine.Rendering.Canvas;
public class ConsoleCanvas : ICanvas
{
private readonly DrawCraftsman _drawCraftsman;
public Size Size { get; } = new(Console.WindowWidth, Console.WindowHeight);
public ConsoleCanvas()
{
var componentCraftsman = new ComponentCraftsman(this);
var containerCraftsman = new ContainerCraftsman(componentCraftsman);
_drawCraftsman = new DrawCraftsman(componentCraftsman, containerCraftsman);
}
public void SetPencil(Position pencilPosition)
{
Console.SetCursorPosition(pencilPosition.Left, pencilPosition.Top);
}
public void Paint(string value) => Console.Write(value);
public void Draw(INode node)
{
_drawCraftsman.Draw(node, Position.Default, Size);
}
public void Draw(INode node, Position pencil, Size maxSize)
{
_drawCraftsman.Draw(node, pencil, maxSize);
}
}

View File

@ -0,0 +1,17 @@
using TUI.Engine.Attributes;
using TUI.Engine.Nodes;
namespace TUI.Engine.Rendering.Canvas;
public interface ICanvas
{
Size Size { get; }
void SetPencil(Position pencilPosition);
void Paint(string value);
void Draw(INode node);
void Draw(INode node, Position pencil, Size maxSize);
}

View File

@ -0,0 +1,42 @@
using Pastel;
using TUI.Engine.Attributes;
using TUI.Engine.Components;
using TUI.Engine.Nodes;
using TUI.Engine.Rendering.Canvas;
namespace TUI.Engine.Rendering;
internal sealed class ComponentCraftsman : CraftsmanBase, IDrawable<IComponent>
{
private readonly ICanvas _canvas;
public ComponentCraftsman(ICanvas canvas)
{
_canvas = canvas;
}
public Size Draw(IComponent component, Position pencil, Size maxSize)
{
var sketch = component.MakeSketch(maxSize);
var sketchSize = sketch.GetSize();
var correctedPencil = component.CorrectContentPosition(pencil, maxSize, sketchSize);
Debug(pencil, maxSize);
foreach (var line in sketch.Crop(maxSize))
{
component.DrawContext = new DrawContext(_canvas, pencil, maxSize);
_canvas.SetPencil(correctedPencil);
_canvas.Paint(component.StyleContext is not null
? line.RemoveColors().Pastel(component.StyleContext.Foreground)
: line);
correctedPencil = correctedPencil with { Top = correctedPencil.Top + 1 };
}
return sketchSize;
}
}

View File

@ -0,0 +1,86 @@
using TUI.Engine.Attributes;
using TUI.Engine.Attributes.Orientations;
using TUI.Engine.Attributes.Resizings;
using TUI.Engine.Components;
using TUI.Engine.Containers;
using TUI.Engine.Nodes;
namespace TUI.Engine.Rendering;
internal sealed class ContainerCraftsman : CraftsmanBase, IDrawable<IContainer>
{
private readonly IDrawable<IComponent> _componentCraftsman;
public ContainerCraftsman(IDrawable<IComponent> componentCraftsman)
{
_componentCraftsman = componentCraftsman;
}
public Size Draw(IContainer container, Position pencil, Size maxSize)
{
var nodeNumber = 0;
var nextNodePosition = pencil;
var nodes = container.GetNodes();
Debug(nextNodePosition, maxSize);
while (nodeNumber < nodes.Count)
{
var node = nodes[nodeNumber];
var nodeSize = node.GetSize(container, nodeNumber, maxSize);
nextNodePosition = DrawNode(node, container, nextNodePosition, nodeSize);
nodeNumber++;
}
return maxSize;
}
private Position DrawNode(INode node, IContainer container, Position nodePosition, Size maxSize)
{
switch (node)
{
case IContainer childContainer:
var containerSize = Draw(childContainer, nodePosition, maxSize);
return GetNextNodePosition(container, containerSize, nodePosition);
case IComponent childComponent:
var componentSize = _componentCraftsman.Draw(childComponent, nodePosition, maxSize);
return childComponent.IsRelative
? GetNextNodePosition(container, maxSize, nodePosition, componentSize)
: nodePosition;
default:
throw new InvalidCastException();
}
}
private static Position GetNextNodePosition(
IContainer container,
Size defaultSize,
Position position,
Size? componentSize = null)
{
switch (container.Orientation)
{
case Orientation.Horizontal:
var componentWidth = container.ResizingHorizontal switch
{
Resizing.Adaptive => defaultSize.Width,
Resizing.Fixed => componentSize?.Width.Max(container.GetFixedSize().Width) ??
container.GetFixedSize().Width,
_ => 0
};
return position with { Left = position.Left + componentWidth };
case Orientation.Vertical:
var componentHeight = container.ResizingVertical switch
{
Resizing.Adaptive => defaultSize.Height,
Resizing.Fixed => componentSize?.Height.Max(container.GetFixedSize().Height) ??
container.GetFixedSize().Height,
_ => 0
};
return position with { Top = position.Top + componentHeight };
default:
throw new InvalidCastException();
}
}
}

View File

@ -0,0 +1,14 @@
using System.Diagnostics;
using TUI.Engine.Attributes;
using TUI.Engine.Nodes;
namespace TUI.Engine.Rendering;
public abstract class CraftsmanBase
{
protected void Debug(Position pencilPosition, Size allowableSize)
{
Debugger.Log(0, "Draw", $"{pencilPosition}{GetType().Name}.\n");
Helper.ShowBackground(pencilPosition, allowableSize);
}
}

View File

@ -0,0 +1,9 @@
using TUI.Engine.Attributes;
using TUI.Engine.Nodes;
namespace TUI.Engine.Rendering;
public interface IDrawable<in TItem> where TItem : INode
{
Size Draw(TItem item, Position pencil, Size maxSize);
}

View File

@ -0,0 +1,28 @@
using TUI.Engine.Attributes;
using TUI.Engine.Components;
using TUI.Engine.Containers;
using TUI.Engine.Nodes;
namespace TUI.Engine.Rendering;
internal sealed class DrawCraftsman : IDrawable<INode>
{
private readonly IDrawable<IComponent> _componentCraftsman;
private readonly IDrawable<IContainer> _containerCraftsman;
public DrawCraftsman(
IDrawable<IComponent> componentCraftsman,
IDrawable<IContainer> containerCraftsman)
{
_componentCraftsman = componentCraftsman;
_containerCraftsman = containerCraftsman;
}
public Size Draw(INode node, Position pencil, Size maxSize) =>
node switch
{
IContainer container => _containerCraftsman.Draw(container, pencil, maxSize),
IComponent component => _componentCraftsman.Draw(component, pencil, maxSize),
_ => throw new InvalidCastException("Unknown node type.")
};
}

View File

@ -0,0 +1,24 @@
using Pastel;
using TUI.Engine.Theme;
namespace TUI.Engine;
public static class SymbolExtensions
{
public static string Colorized(this string symbol) =>
!SymbolColors.ContainsKey(symbol) ? symbol.Hint() : symbol.Pastel(SymbolColors[symbol]);
private static readonly Dictionary<string, string> SymbolColors = new()
{
{ Symbols.Git, "F14E32" },
{ Symbols.Site, "BF40BF" },
{ Symbols.GitLab, "E24329" },
{ Symbols.GitHub, "ADBAC7" },
{ Symbols.NetworkPublic, "00FFFF" },
{ Symbols.Api, "7F52FF" },
{ Symbols.DockerImage, "086DD7" },
{ Symbols.NpmPackage, "CB0000" },
{ Symbols.Seo, "4285F4" },
{ Symbols.Auth, "FFD700" },
};
}

37
src/TUI.Engine/Symbols.cs Normal file
View File

@ -0,0 +1,37 @@
namespace TUI.Engine;
public static class Symbols
{
public const string Space = " ";
public const string Copyright = "©";
public const string GitLab = "";
public const string GitHub = "";
public const string Git = "";
public const string LineBreak = "\n";
public const string NetworkPublic = "󰞉";
public const string NetworkPrivate = "󰕑";
public const string Undefined = "";
public const string Site = "";
public const string Api = "";
public const string DockerImage = "";
public const string NpmPackage = "";
public const string Seo = "󰚩";
public const string Auth = "";
public const string NotFound = "";
public const string Error = "";
public const string Download = "";
public static class Lines
{
public const string Vertical = "│";
public const string Horizontal = "─";
}
public static class Angles
{
public const string RightTop = "┐";
public const string LeftBottom = "└";
public const string LeftTop = "┌";
public const string RightBottom = "┘";
}
}

View File

@ -0,0 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Pastel" Version="4.1.0"/>
</ItemGroup>
<ItemGroup>
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo">
<_Parameter1>$(MSBuildProjectName).Tests</_Parameter1>
</AssemblyAttribute>
</ItemGroup>
</Project>

View File

@ -0,0 +1,20 @@
using TUI.Engine.Attributes.Alignments;
using TUI.Engine.Attributes.Orientations;
using TUI.Engine.Attributes.Resizings;
namespace TUI.Engine.Theme;
public static class Defaults
{
public const Horizontal HorizontalAlignment = Horizontal.Center;
public const Vertical VerticalAlignment = Vertical.Top;
public const Level Padding = Level.None;
public const Resizing HorizontalResizing = Resizing.Adaptive;
public const Resizing VerticalResizing = Resizing.Adaptive;
public const Orientation Orientation = TUI.Engine.Attributes.Orientations.Orientation.Horizontal;
}

View File

@ -0,0 +1,7 @@
namespace TUI.Engine.Theme;
public static class Indentation
{
public const Level Default = Level.Normal;
public const Level BorderWidth = Level.Normal;
}

View File

@ -0,0 +1,7 @@
namespace TUI.Engine.Theme;
public enum Level
{
None = 0,
Normal = 1
}

View File

@ -1,21 +1,35 @@
using Pastel;
namespace TUI.UserInterface;
namespace TUI.Engine.Theme;
public static class Palette
{
public const string HoverColor = "292928";
public const string PrimaryColor = "84BA64";
public const string HintColor = "71797E";
public const string DisableColor = "303030";
public const string ErrorColor = "CA3433";
public const string WarningColor = "EC9706";
public const string InfoColor = "25799F";
public static string Primary(this string currentText) => currentText.Pastel(PrimaryColor);
public static string Main(this string currentText, bool isFocused = true) =>
isFocused
? currentText.Pastel(PrimaryColor)
: Hint(currentText);
public static string Hint(this string currentText) => currentText.Pastel(HintColor);
public static string Hint(this char currentText) => currentText.ToString().Pastel(HintColor);
public static string Disable(this string currentText) => currentText.RemoveColors().Pastel(HintColor);
public static string Warning(this string currentText) => currentText.Pastel(WarningColor);
public static string Error(this string currentText) => currentText.Pastel(ErrorColor);
public static string Info(this string currentText) => currentText.Pastel(InfoColor);
public static string Info(this char currentText) => currentText.ToString().Pastel(InfoColor);
public static string Info(this int currentText) => currentText.ToString().Pastel(InfoColor);
}

View File

@ -0,0 +1,40 @@
using System.Text;
using TUI.Engine;
using TUI.Engine.Attributes;
using TUI.Engine.Attributes.Orientations;
using TUI.Engine.Components;
namespace TUI.Controls.Common;
public class StubComponent : ComponentBase
{
private readonly Size _size;
private readonly string? _text;
public StubComponent(Size size, string? text = null)
{
_size = size;
_text = text;
SetFixed(Orientation.Horizontal, size.Width);
SetFixed(Orientation.Vertical, size.Height);
}
protected override Sketch DrawComponent(Size minSize)
{
var builder = new StringBuilder();
var height = 0;
while (_size.Height > height)
{
builder.Append(Symbols.Space.Repeat(_size.Width - (_text?.GetWidth() ?? 0)));
height++;
}
if (_text is not null)
{
builder.Append(_text);
}
return new Sketch(builder.ToString());
}
}

View File

@ -0,0 +1,20 @@
using TUI.Engine.Attributes;
using TUI.Engine.Components;
using TUI.Engine.Theme;
namespace TUI.Controls.Components;
public class BreadCrumbsComponent : ComponentBase
{
private readonly List<string> _crumbs = new() { " " };
public BreadCrumbsComponent(params string[] crumbs)
{
_crumbs.AddRange(crumbs);
}
protected override Sketch DrawComponent(Size minSize)
{
return new Sketch(string.Join("  ".Hint(), _crumbs).Hint());
}
}

View File

@ -0,0 +1,36 @@
using System.Text;
using TUI.Engine;
using TUI.Engine.Attributes;
using TUI.Engine.Attributes.Alignments;
using TUI.Engine.Components;
namespace TUI.Controls.Components;
public class CellsComponentBase : ComponentBase, IComponent
{
private const int MaxCellWidth = 10;
private readonly IEnumerable<string> _cells;
public CellsComponentBase(IEnumerable<string> cells)
{
_cells = cells;
}
public void Render(Horizontal horizontal, Size size)
{
var content = new StringBuilder();
foreach (var cell in _cells)
{
content.Append(Symbols.Space.Repeat(MaxCellWidth - cell.GetWidth()));
content.Append(cell);
}
// base.Render(content, position, size);
}
protected override Sketch DrawComponent(Size minSize)
{
throw new NotImplementedException();
}
}

View File

@ -0,0 +1,67 @@
using System.Text;
using TUI.Engine;
using TUI.Engine.Attributes;
using TUI.Engine.Components;
using TUI.Engine.Theme;
using static TUI.Engine.Symbols;
namespace TUI.Controls.Components;
public class PanelComponent : ComponentBase, IComponent
{
private readonly string _title;
public PanelComponent(string title)
{
_title = title;
SetAbsolute();
}
private static void RenderTopLine(StringBuilder builder, Size size, string title)
{
var halfWidth = (size.Width - title.GetWidth() - (int)Indentation.BorderWidth * 2 -
(int)Indentation.Default * 2) / 2;
builder.Append(Angles.LeftTop);
builder.Append(Lines.Horizontal.Repeat(halfWidth));
builder.AppendFormat("{0}{1}{0}", Space.Repeat(Convert.ToInt32(Indentation.Default)), title);
builder.Append(Lines.Horizontal.Repeat(halfWidth + halfWidth % 2));
builder.Append(Angles.RightTop);
builder.Append(LineBreak);
}
private static void RenderMiddleLine(StringBuilder builder, Size size)
{
var dashboardHeight = size.Height - (int)Indentation.BorderWidth * 2;
while (dashboardHeight > 0)
{
var bodyWidth = size.Width - (int)Indentation.BorderWidth * 2;
builder.Append(Lines.Vertical);
builder.Append(Space.Repeat(bodyWidth));
builder.Append(Lines.Vertical);
builder.Append(LineBreak);
dashboardHeight--;
}
}
private static void RenderBottomLine(StringBuilder builder, Size size)
{
var width = size.Width - (int)Indentation.BorderWidth * 2;
builder.Append(Angles.LeftBottom);
builder.Append(Lines.Horizontal.Repeat(width));
builder.Append(Angles.RightBottom);
builder.Append(LineBreak);
}
protected override Sketch DrawComponent(Size minSize)
{
var builder = new StringBuilder();
RenderTopLine(builder, minSize, _title);
RenderMiddleLine(builder, minSize);
RenderBottomLine(builder, minSize);
return new Sketch(builder.ToString().Main());
}
}

View File

@ -0,0 +1,47 @@
using System.Text;
using TUI.Domain;
using TUI.Engine;
using TUI.Engine.Attributes;
using TUI.Engine.Components;
using TUI.Engine.Theme;
using TUI.UserInterface;
using static TUI.Engine.Symbols;
namespace TUI.Controls.Components;
public class ProjectTitle : ComponentBase
{
private readonly Project _project;
public ProjectTitle(Project project)
{
_project = project;
}
protected override Sketch DrawComponent(Size minSize)
{
var builder = new StringBuilder();
builder.Append(GetHub().Colorized());
builder.Append(Space);
builder.Append((_project.IsPublicNetwork ? NetworkPublic : NetworkPrivate).Colorized());
builder.Append(Space);
builder.Append(_project.SeoDependent ? Seo.Colorized() : Seo.Disable());
builder.Append(Space);
builder.Append(_project.HasAuth ? Auth.Colorized() : Auth.Disable());
builder.Append(Space);
builder.Append(GetApplicationType().Colorized());
builder.Append(Space);
builder.Append(_project.Name.Disable());
return new Sketch(builder);
}
private string GetHub() => _project.Hub.Type == "gitlab" ? GitLab : GitHub;
private string GetApplicationType()
{
foreach (var application in Icons.Applications.Where(application => _project.Tags.Have(application.Value)))
return application.Key;
return Undefined.Hint();
}
}

View File

@ -0,0 +1,50 @@
using TUI.Engine;
using TUI.Engine.Attributes;
using TUI.Engine.Components;
using TUI.Engine.Nodes;
namespace TUI.Controls.Components;
public class SpeakerComponent : ComponentBase
{
private string _message = "";
private Position? _pencil;
private SpeakerComponent()
{
}
public static SpeakerComponent Instance { get; } = new();
protected override Sketch DrawComponent(Size minSize)
{
return new Sketch(_message);
}
private void Clear()
{
_message = new string(' ', DrawContext.MaxSize.Width);
DrawContext.Canvas.Draw(Instance, _pencil, DrawContext.MaxSize);
}
public void Shout(string emoji, string message)
{
if (DrawContext is null)
{
return;
}
_pencil ??= DrawContext.Pencil with { };
Clear();
_message = emoji + Symbols.Space + message;
DrawContext.Canvas.Draw(Instance, _pencil, DrawContext.MaxSize);
Task.Delay(2000).ContinueWith(_ =>
{
Clear();
return Task.CompletedTask;
});
}
}

View File

@ -0,0 +1,44 @@
using System.Text;
using TUI.Domain;
using TUI.Engine;
using TUI.Engine.Attributes;
using TUI.Engine.Components;
using TUI.Engine.Theme;
namespace TUI.Controls.Components;
public class VersionComponent : ComponentBase
{
private readonly VersionType _type;
private readonly string _version;
private readonly Brand? _brand;
private readonly VersionStatus _status;
public VersionComponent(string version, Brand brand, VersionStatus status = VersionStatus.SoGood,
VersionType type = VersionType.Release)
{
_version = version;
_brand = brand;
_status = status;
_type = type;
}
protected override Sketch DrawComponent(Size minSize)
{
var builder = new StringBuilder();
if (_brand is not null)
{
builder.Append(_brand.ColorLogo());
}
builder.Append(_type.ToImage().Warning());
builder.Append(Symbols.Space);
builder.Append(_version);
var sketch = builder.ToString();
return new Sketch(_status.Colorize(sketch));
}
}

View File

@ -0,0 +1,28 @@
using TUI.Engine.Theme;
namespace TUI.Controls.Components;
public static class VersionExtensions
{
public static string ToImage(this VersionType versionType)
=>
versionType switch
{
VersionType.Alpha => "󰀫",
VersionType.Beta => "󰂡",
VersionType.Candidate => "󰑣",
VersionType.Canary => "󱗆",
VersionType.Next => "󰒭",
_ => ""
};
public static string Colorize(this VersionStatus versionStatus, string value) =>
versionStatus switch
{
VersionStatus.TooOld => value.Warning(),
VersionStatus.ToNew => value.Info(),
VersionStatus.SoGood => value.Hint(),
VersionStatus.BeNice => value.Main(),
_ => value
};
}

View File

@ -0,0 +1,10 @@
namespace TUI.Controls.Components;
public enum VersionStatus
{
NotFound,
ToNew,
SoGood,
BeNice,
TooOld,
}

View File

@ -0,0 +1,12 @@
namespace TUI.Controls.Components;
public enum VersionType
{
Convention,
Release,
Candidate,
Canary,
Alpha,
Beta,
Next,
}

View File

@ -0,0 +1,19 @@
using TUI.Engine.Containers;
using TUI.Engine.Nodes;
namespace TUI.Controls.Containers;
public class ContentContainer : ContainerBase
{
private readonly Nodes _children = new();
public void AddChildren(INode node)
{
_children.Add(node);
}
public override Nodes GetNodes()
{
return _children;
}
}

View File

@ -0,0 +1,34 @@
using TUI.Controls.Components;
using TUI.Engine.Attributes.Orientations;
using TUI.Engine.Containers;
using TUI.Engine.Nodes;
using TUI.Engine.Theme;
namespace TUI.Controls.Containers;
public class DashboardContainer : ContainerBase
{
private readonly Nodes _children = new();
private readonly ContentContainer _content;
public DashboardContainer()
{
var panel = new PanelComponent(" ".Info() + " Dependencies".Main());
_content = new ContentContainer();
_content.SetOrientationVertical();
SetOrientationVertical();
_children.Add(panel);
_children.Add(_content);
}
public void AddChildren(IContainer node)
{
node.SetFixed(Orientation.Vertical, 1);
_content.AddChildren(node);
}
public override Nodes GetNodes() => _children;
public Nodes GetContent() => _content.GetNodes();
}

View File

@ -0,0 +1,105 @@
using TUI.Controls.Common;
using TUI.Controls.Components;
using TUI.Domain;
using TUI.Engine;
using TUI.Engine.Attributes;
using TUI.Engine.Attributes.Alignments;
using TUI.Engine.Attributes.Orientations;
using TUI.Engine.Components;
using TUI.Engine.Containers;
using TUI.Engine.Nodes;
using TUI.Engine.Theme;
namespace TUI.Controls.Containers;
public class DependenciesContainer : ContainerBase
{
public readonly Project? Project;
private const int VersionColumnWidth = 11;
private const int TitleColumnWidth = 25;
private readonly Nodes _dependencies = new();
public DependenciesContainer()
{
}
public DependenciesContainer(Project project)
{
Project = project;
}
public void AddTitleStub()
{
var size = new Size(TitleColumnWidth, 1);
var title = new StubComponent(size);
title.SetPadding(Level.Normal);
_dependencies.Add(title);
}
public void AddTitle(IComponent title)
{
title.SetPadding(Level.Normal);
title.SetFixed(Orientation.Horizontal, TitleColumnWidth);
title.SetAlignment(Horizontal.Left);
if (Project is not null && Project.Legacy)
{
title.StyleContext = new StyleContext(Palette.DisableColor);
}
_dependencies.Add(title);
}
public void AddDependencyStub()
{
var size = new Size(VersionColumnWidth, 1);
var stub = new StubComponent(size, Symbols.NotFound.Hint());
stub.SetPadding(Level.Normal);
stub.SetAlignment(Horizontal.Right);
stub.SetFixed(Orientation.Horizontal, VersionColumnWidth);
if (Project is not null && Project.Legacy)
{
stub.StyleContext = new StyleContext(Palette.DisableColor);
}
_dependencies.Add(stub);
}
public void AddError()
{
var size = new Size(25, 1);
var stub = new StubComponent(size, (Symbols.Error + Symbols.Space + " Something went wrong").Error());
stub.SetPadding(Level.Normal);
stub.SetAlignment(Horizontal.Right);
stub.SetFixed(Orientation.Horizontal, 25);
if (Project is not null && Project.Legacy)
{
stub.StyleContext = new StyleContext(Palette.DisableColor);
}
_dependencies.Add(stub);
}
public void AddDependency(Dependency dependency, VersionStatus status = VersionStatus.BeNice)
{
var version = new VersionComponent(dependency.Version, dependency.Brand, status, dependency.Type);
version.SetPadding(Level.Normal);
version.SetAlignment(Horizontal.Right);
version.SetFixed(Orientation.Horizontal, VersionColumnWidth);
if (Project is not null && Project.Legacy)
{
version.StyleContext = new StyleContext(Palette.DisableColor);
}
_dependencies.Add(version);
}
public override Nodes GetNodes() => _dependencies;
}

View File

@ -0,0 +1,32 @@
using TUI.Controls.Components;
using TUI.Controls.Statics;
using TUI.Engine.Attributes.Alignments;
using TUI.Engine.Components;
using TUI.Engine.Containers;
using TUI.Engine.Nodes;
using TUI.Engine.Theme;
namespace TUI.Controls.Containers;
public class FooterContainer : ContainerBase
{
private readonly INode _breadcrumbs;
private readonly INode _speaker;
public FooterContainer(IComponent? breadcrumbs)
{
breadcrumbs.SetAlignment(Horizontal.Left);
breadcrumbs.SetPaddingLeft(Level.Normal);
_breadcrumbs = breadcrumbs;
_speaker = SpeakerComponent.Instance;
}
public override Nodes GetNodes()
{
var copyright = new CopyrightComponent();
copyright.SetAlignment(Horizontal.Right);
copyright.SetPaddingRight(Level.Normal);
return new Nodes { _breadcrumbs, _speaker, copyright };
}
}

View File

@ -0,0 +1,38 @@
using TUI.Controls.Statics;
using TUI.Controls.Statics.Hints;
using TUI.Engine.Attributes.Alignments;
using TUI.Engine.Containers;
using TUI.Engine.Nodes;
using TUI.Engine.Theme;
namespace TUI.Controls.Containers;
public class HeaderContainer : ContainerBase, IContainer
{
public override Nodes GetNodes()
{
var versionHints = new VersionHints();
versionHints.SetPadding(Indentation.Default);
versionHints.SetAlignment(Horizontal.Left);
var tagsHints = new TagHints();
tagsHints.SetPadding(Indentation.Default);
tagsHints.SetAlignment(Horizontal.Left);
var appTypeHints = new AppTypeHints();
appTypeHints.SetPadding(Indentation.Default);
appTypeHints.SetAlignment(Horizontal.Left);
var hotkeysHints = new HotkeysHint();
hotkeysHints.SetPadding(Indentation.Default);
hotkeysHints.SetAlignment(Horizontal.Left);
var logo = new LogoComponent();
logo.SetAlignment(Horizontal.Right);
logo.SetPaddingLeft(Indentation.Default);
logo.SetPaddingBottom(Indentation.Default);
logo.SetPaddingRight(Indentation.Default);
return new Nodes { versionHints, tagsHints, appTypeHints, hotkeysHints, logo };
}
}

View File

@ -1,16 +0,0 @@
using TUI.UserInterface;
namespace TUI.Controls;
public class Copyright : IControl<string>
{
public void Render(string author, Position position)
{
const string icon = "© ";
Console.SetCursorPosition(position.Left - icon.Width(), position.Top);
var copyright = $"{icon}{author}".Hint();
Console.Write(copyright);
}
}

View File

@ -1,60 +0,0 @@
using System.Text;
using TUI.UserInterface;
namespace TUI.Controls;
public class Dashboard : IControl<string>
{
public void Render(string title, Position position)
{
Console.SetCursorPosition(position.Left, position.Top);
RenderTopLine(title);
var marginTop = Theme.BorderWidth + Theme.Padding + position.Top;
var dashboardHeight = Console.WindowHeight - Theme.BorderWidth;
for (var top = marginTop; top < dashboardHeight; top++)
{
RenderMiddleLine();
}
RenderBottomLine();
}
private static void RenderMiddleLine()
{
Console.Write("│".Primary());
Console.Write(new string(' ', Console.WindowWidth - Theme.BorderWidth * 2));
Console.WriteLine("│".Primary());
}
private static void RenderBottomLine()
{
var lineWidth = Console.WindowWidth - Theme.BorderWidth * 2;
Console.Write("└".Primary());
Console.Write('─'.Repeat(lineWidth).Primary());
Console.WriteLine("┘".Primary());
}
private static void RenderTopLine(string title)
{
var lineWidth =
(Console.WindowWidth - title.Width() - Theme.BorderWidth * 2 - Theme.Padding * 2) /
2;
var topLine = new StringBuilder();
topLine.Append("┌");
topLine.Append('─'.Repeat(lineWidth));
topLine.AppendFormat("{0}{1}{0}", ' '.Repeat(Theme.Padding), title);
if (title.Width() % 2 == 1)
{
topLine.Append('─');
}
topLine.Append('─'.Repeat(lineWidth));
topLine.Append("┐");
Console.WriteLine(topLine.ToString().Primary());
}
}

View File

@ -1,73 +0,0 @@
using TUI.Dashboards;
using TUI.Domain;
using TUI.UserInterface;
namespace TUI.Controls;
public class Display
{
private bool _headerInDisplay = true;
public readonly Header Header;
public readonly Copyright Copyright;
public readonly DependencyDashboard DependencyDashboard;
private Project _currentProject;
public Display()
{
Header = new Header();
Copyright = new Copyright();
DependencyDashboard = new DependencyDashboard();
Render();
}
public void OpenDeps(Project project)
{
_currentProject = project;
var dashboardPosition = new Position(0, Header.Height);
DependencyDashboard.Render(_currentProject, dashboardPosition);
}
private void ResizeDependencies(bool full)
{
var dashboardPosition = new Position(0, full ? 0 : Header.Height);
DependencyDashboard.Render(_currentProject, dashboardPosition);
}
public void Render()
{
var headerPosition = new Position(0, 0);
Header.Render(headerPosition);
const string copyrightText = "Kolosov Aleksandr";
var copyrightPosition = new Position(
Console.WindowWidth - copyrightText.Width(),
Console.WindowHeight);
Copyright.Render(copyrightText, copyrightPosition);
}
public void ToggleHeader()
{
_headerInDisplay = !_headerInDisplay;
if (_headerInDisplay)
{
var headerPosition = new Position(0, 0);
Header.Render(headerPosition);
}
ResizeDependencies(!_headerInDisplay);
}
public void Next()
{
DependencyDashboard.Next();
}
public void Previous()
{
DependencyDashboard.Previous();
}
}

View File

@ -1,11 +0,0 @@
namespace TUI.Controls;
public interface IControl
{
void Render(Position position);
}
public interface IControl<in TProps>
{
void Render(TProps props, Position position);
}

View File

@ -0,0 +1,32 @@
using TUI.Engine.Attributes.Orientations;
using TUI.Engine.Containers;
using TUI.Engine.Nodes;
namespace TUI.Controls.Layouts;
public class DashboardLayout : ContainerBase, IContainer
{
private readonly INode _header;
private readonly INode _footer;
private readonly INode _dashboard;
public DashboardLayout(INode header, INode dashboard, INode footer)
{
SetOrientationVertical();
SetAdaptive(Orientation.Horizontal);
SetAdaptive(Orientation.Vertical);
header.SetFixed(Orientation.Vertical, 6);
footer.SetFixed(Orientation.Vertical, 1);
_header = header;
_footer = footer;
_dashboard = dashboard;
}
public override Nodes GetNodes() =>
new()
{
_header, _dashboard, _footer
};
}

View File

@ -1,3 +0,0 @@
namespace TUI.Controls;
public record Position(int Left, int Top);

View File

@ -0,0 +1,18 @@
using System.Text;
using TUI.Engine;
using TUI.Engine.Components;
using TUI.Engine.Theme;
namespace TUI.Controls.Statics;
public class CopyrightComponent : StaticComponentBase
{
protected override void RenderWithCache(StringBuilder builder)
{
builder.Append(Symbols.Copyright.Info());
builder.Append(Symbols.Space);
builder.Append("Kolosov A. aka \"dnwSilver\"".Hint());
builder.Append(Symbols.Space);
builder.Append(DateTime.Now.Year.Info());
}
}

View File

@ -0,0 +1,27 @@
using System.Text;
using TUI.Engine;
using TUI.Engine.Components;
using TUI.Engine.Theme;
namespace TUI.Controls.Statics.Hints;
public class AppTypeHints : StaticComponentBase
{
private readonly Dictionary<string, string> _hints = new()
{
{ Symbols.NpmPackage, "package" },
{ Symbols.DockerImage, "image" },
{ Symbols.Site, "site" },
{ Symbols.Api, "api" }
};
protected override void RenderWithCache(StringBuilder builder)
{
foreach (var hint in _hints)
{
builder.Append(hint.Key);
builder.Append(Symbols.Space);
builder.AppendLine(hint.Value.Hint());
}
}
}

View File

@ -0,0 +1,27 @@
using System.Text;
using TUI.Engine;
using TUI.Engine.Components;
using TUI.Engine.Theme;
namespace TUI.Controls.Statics.Hints;
public class HotkeysHint : StaticComponentBase
{
private readonly Dictionary<string, string> _hints = new()
{
{ "", "select prev" },
{ "", "select next" },
{ "󰬌", "toggle head" },
{ "󰬘", "quit" }
};
protected override void RenderWithCache(StringBuilder builder)
{
foreach (var hint in _hints)
{
builder.Append(hint.Key.Hint());
builder.Append(Symbols.Space);
builder.AppendLine(hint.Value.Hint());
}
}
}

View File

@ -0,0 +1,27 @@
using System.Text;
using TUI.Engine;
using TUI.Engine.Components;
using TUI.Engine.Theme;
namespace TUI.Controls.Statics.Hints;
public class TagHints : StaticComponentBase
{
private readonly Dictionary<string, string> _hints = new()
{
{ Symbols.Auth, "Auth" },
{ Symbols.NetworkPublic, "WWW" },
{ Symbols.Seo, "SEO" },
{ Symbols.Git, "VCS" }
};
protected override void RenderWithCache(StringBuilder builder)
{
foreach (var hint in _hints)
{
builder.Append(hint.Key);
builder.Append(Symbols.Space);
builder.AppendLine(hint.Value.Hint());
}
}
}

View File

@ -0,0 +1,28 @@
using System.Text;
using TUI.Controls.Components;
using TUI.Engine;
using TUI.Engine.Components;
using TUI.Engine.Theme;
namespace TUI.Controls.Statics.Hints;
public class VersionHints : StaticComponentBase
{
private readonly Dictionary<string, string> _hints = new()
{
{ "󰎔", VersionStatus.ToNew.Colorize("too new") },
{ "", VersionStatus.SoGood.Colorize("so good") },
{ "", VersionStatus.BeNice.Colorize("be nice") },
{ "󰬟", VersionStatus.TooOld.Colorize("too old") }
};
protected override void RenderWithCache(StringBuilder builder)
{
foreach (var hint in _hints)
{
builder.Append(hint.Key.Hint());
builder.Append(Symbols.Space);
builder.AppendLine(hint.Value);
}
}
}

View File

@ -0,0 +1,20 @@
using System.Text;
using TUI.Engine;
using TUI.Engine.Components;
using TUI.Engine.Theme;
namespace TUI.Controls.Statics;
public class LogoComponent : StaticComponentBase
{
protected override void RenderWithCache(StringBuilder builder)
{
builder.Append($" {"".Main()}{"".Hint()}{"".Main()}").Append(Symbols.LineBreak);
builder.Append($" {"".Main()}{"".Hint()}{"".Main()}").Append(Symbols.LineBreak);
builder.Append($" {"".Main()}{"".Hint()}{"".Main()}").Append(Symbols.LineBreak);
builder.Append($" {"".Hint()}{"".Main()}{"".Hint()}{"".Main()}{"".Hint()}{"".Main()}")
.Append(Symbols.LineBreak);
builder.Append($" {"".Hint()}{"".Main()}{"".Hint()}{"".Main()}").Append(Symbols.LineBreak);
builder.Append($"{"".Hint()}{"".Main()}{"".Hint()}{"".Main()}").Append(Symbols.LineBreak);
}
}

View File

@ -1,74 +0,0 @@
using Pastel;
using TUI.UserInterface;
namespace TUI.Controls;
public record TableProps(IEnumerable<string> HeaderCells,
IEnumerable<string> Rows,
int TitleWidth,
int ColumnWidth);
public class Table : IControl<TableProps>
{
private Position _position;
private int _selectedRowId;
public void Render(TableProps props, Position position)
{
_position = position;
Console.SetCursorPosition(_position.Left, _position.Top);
Console.Write(' '.Repeat(props.TitleWidth));
foreach (var headerCell in props.HeaderCells)
{
Console.Write(' '.Repeat(props.ColumnWidth - headerCell.Width()) + headerCell);
}
}
private readonly Dictionary<int, string> _rows = new();
public void RenderRow(int rowId, string rowText, string? bgColor = default)
{
var padRight = ' '.Repeat(Console.WindowWidth - rowText.Width() - Theme.BorderWidth * 2);
_rows[rowId] = rowText + padRight;
Console.SetCursorPosition(_position.Left, _position.Top + rowId);
Console.Write(string.IsNullOrEmpty(bgColor) ? rowText : rowText.PastelBg(bgColor));
}
public void Next()
{
if (_selectedRowId >= _rows.Count)
{
return;
}
RemoveHoverFromCurrentRow();
RenderRow(++_selectedRowId, _rows[_selectedRowId], Palette.HoverColor);
}
private void RemoveHoverFromCurrentRow()
{
if (_rows.TryGetValue(_selectedRowId, out var row))
{
RenderRow(_selectedRowId, row);
}
}
public void Previous()
{
if (_selectedRowId == 0)
{
Next();
}
if (_selectedRowId == 1)
{
return;
}
RemoveHoverFromCurrentRow();
RenderRow(--_selectedRowId, _rows[_selectedRowId], Palette.HoverColor);
}
}

View File

@ -1,215 +0,0 @@
using System.Diagnostics;
using System.Net;
using System.Text.Json;
using Pastel;
using TUI.Controls;
using TUI.Domain;
using TUI.Settings;
using TUI.UserInterface;
namespace TUI.Dashboards;
public class DependencyDashboard : IControl<Project>
{
private const int TitleWidth = 25;
private const int ColumnWidth = 10;
private readonly Table _table = new();
public void Render(Project project, Position position)
{
var dashboard = new Dashboard();
dashboard.Render(project.Icon, position);
var header = project.Dependencies.Select(GetConventionVersion).ToArray();
var rows = project.Sources.Select(GetTitle).ToArray();
var tablePosition = new Position(
position.Left + Theme.BorderWidth,
position.Top + Theme.BorderWidth);
var tableProps = new TableProps(header, rows, TitleWidth, ColumnWidth);
_table.Render(tableProps, tablePosition);
for (var rowId = 0; rowId < rows.Length; rowId++)
{
var actualDependencies = GetDependencies(project.Sources[rowId], project.Dependencies);
_table.RenderRow(rowId + 1, rows[rowId] + actualDependencies);
}
}
private static string GetDependencies(SourceDto sourceDto, IEnumerable<DependencyDto> conventionDependencies)
{
try
{
var package = DownloadPackage(sourceDto);
return string.Join("",
conventionDependencies
.Select(dependency => GetVersion(dependency, package))
.Select(RenderCurrentVersion));
}
catch (HttpRequestException exception)
{
switch (exception.StatusCode)
{
case HttpStatusCode.BadRequest:
return " Request have errors.".Pastel(Palette.ErrorColor);
case HttpStatusCode.Forbidden:
return " Not enough rights.".Pastel(Palette.ErrorColor);
case HttpStatusCode.NotFound:
return " Repository or branch master not found.".Pastel(Palette.ErrorColor);
}
throw;
}
catch (Exception exception)
{
Debugger.Break();
return "󰋔 We tried to send a request but couldn't. Check your configuration.".Pastel(Palette.ErrorColor);
}
}
private static string GetVersion(DependencyDto dependency, Package package)
{
var currentVersion = package.ParseVersion(dependency.Name);
if (currentVersion == null)
{
return Icons.NotFound;
}
var conventionVersion = dependency.Version?.ToVersion();
return PaintingVersion(currentVersion, conventionVersion);
}
private static string PaintingVersion(Version current, Version? convention)
{
var textVersion = current.ToString();
if (current > convention)
{
return textVersion.Info();
}
if (current < convention)
{
return current.Major == convention.Major ? textVersion.Primary() : textVersion.Warning();
}
return textVersion.Hint();
}
private readonly static Dictionary<string, Package> Packages = new();
private static Package DownloadPackage(SourceDto sourceDto)
{
if (Packages.TryGetValue(sourceDto.Repo, out var downloadPackage))
{
return downloadPackage;
}
using HttpClient client = new();
var endpoint = sourceDto.Tags.Have("gitlab") ? GetGitlabEndpoint(sourceDto) : sourceDto.Repo;
var json = client.GetStringAsync(endpoint).GetAwaiter().GetResult();
var package = JsonSerializer.Deserialize<Package>(json);
Packages.Add(endpoint, package);
return package;
}
private static string GetGitlabEndpoint(SourceDto sourceDto)
{
var token = Environment.GetEnvironmentVariable("TLD_GITLAB_PAT");
return $"{sourceDto.Repo}/api/v4/projects/{sourceDto.ProjectId}/repository/files/package.json/raw?" +
$"private_token={token}&ref=master";
}
private static string GetConventionVersion(DependencyDto dependencyDto)
{
return dependencyDto.Icon.Pastel(dependencyDto.Color) + dependencyDto.Version.Primary();
}
private static string RenderCurrentVersion(string version)
{
var versionWidth = version.Width();
if (versionWidth == 0)
{
return ' '.Repeat(ColumnWidth - 1) + Icons.NotFound.Hint();
}
return ' '.Repeat(ColumnWidth - versionWidth) + version;
}
private static string GetTitle(SourceDto sourceDto)
{
var title = "";
title += RenderPadding();
title += RenderTags(sourceDto);
if (title.Width() + sourceDto.Name.Length + Theme.Padding <= TitleWidth)
{
title += sourceDto.Name;
}
else
{
var maxNameWidth = TitleWidth - title.Width() - Theme.Padding;
title += $"{sourceDto.Name[..(maxNameWidth - 1)]}{"#".Hint()}";
}
title += RenderPadding();
return $"{title}{' '.Repeat(TitleWidth - title.Width())}";
}
private static string RenderPadding()
{
return new string(' ', Theme.Padding);
}
private static string RenderTags(SourceDto sourceDto)
{
var tags = "";
tags += GetGitApplication(sourceDto);
tags += " ";
tags += sourceDto.Tags.Have("public") ? Icons.NetworkPublic : Icons.NetworkPrivate;
tags += " ";
tags += sourceDto.Tags.Have("seo") ? Icons.SEO : Icons.SEO.Disable();
tags += " ";
tags += sourceDto.Tags.Have("auth") ? Icons.Auth : Icons.Auth.Disable();
tags += " ";
tags += GetApplicationType(sourceDto);
tags += " ";
return tags;
}
private static string GetApplicationType(SourceDto sourceDto)
{
foreach (var application in Icons.Applications)
{
if (sourceDto.Tags.Have(application.Value))
return application.Key;
}
return Icons.Undefined;
}
private static string GetGitApplication(SourceDto sourceDto) => sourceDto.Repo switch
{
{ } url when url.Contains("gitlab") => Icons.GitLab,
{ } url when url.Contains("github") => Icons.GitHub,
_ => Icons.Git
};
public void Next()
{
_table.Next();
}
public void Previous()
{
_table.Previous();
}
}

8
src/TUI/Domain/Brand.cs Normal file
View File

@ -0,0 +1,8 @@
using Pastel;
namespace TUI.Domain;
public record Brand(string Name, string? Logo = null, string? Color = null)
{
public string ColorLogo() => Logo?.Pastel(Color) ?? string.Empty;
};

View File

@ -0,0 +1,67 @@
using TUI.Controls.Components;
namespace TUI.Domain;
public record Dependency()
{
private readonly Version _current;
public readonly Brand Brand;
public VersionType Type { get; private set; }
public string Version => $"{_current.Major}.{_current.Minor}.{_current.Patch}";
public Dependency(string version, Brand brand) : this()
{
_current = new Version(version);
Brand = brand;
}
public VersionStatus Comparison(Dependency outerDependency)
{
// if (string.IsNullOrEmpty(Version) || string.IsNullOrEmpty(outerDependency.Version))
// {
// return VersionStatus.NotFound;
// }
var outer = outerDependency._current;
Type = _current.Type;
if (_current.Major < outer.Major)
{
return VersionStatus.TooOld;
}
if (_current.Major > outer.Major)
{
return VersionStatus.ToNew;
}
if (_current.Minor < outer.Minor)
{
return VersionStatus.BeNice;
}
if (_current.Minor > outer.Minor)
{
return VersionStatus.ToNew;
}
if (_current.Patch < outer.Patch)
{
return VersionStatus.BeNice;
}
if (_current.Patch > outer.Patch)
{
return VersionStatus.ToNew;
}
if (outer.Type != VersionType.Release)
{
return VersionStatus.ToNew;
}
return VersionStatus.SoGood;
}
};

3
src/TUI/Domain/Hub.cs Normal file
View File

@ -0,0 +1,3 @@
namespace TUI.Domain;
public record Hub(string Origin, string Type);

View File

@ -1,21 +1,14 @@
using TUI.Settings;
using YamlDotNet.Serialization;
namespace TUI.Domain;
[YamlSerializable]
public class Project
public record Project(int Id, string Name, IEnumerable<string> Tags, Hub Hub)
{
[YamlMember]
public string Icon { get; set; }
private IEnumerable<Dependency> Dependencies => new List<Dependency>();
[YamlMember]
public string Name { get; set; }
public bool IsPublicNetwork => Tags.Contains("public");
[YamlMember]
public DependencyDto[] Dependencies { get; set; }
public bool HasAuth => Tags.Contains("auth");
[YamlMember]
public IList<SourceDto> Sources { get; set; }
public bool SeoDependent => Tags.Contains("seo");
public bool Legacy => Tags.Contains("legacy");
}

View File

@ -1,22 +0,0 @@
using YamlDotNet.Serialization;
using YamlDotNet.Serialization.NamingConventions;
namespace TUI.Domain;
[YamlSerializable]
public class Settings
{
public static Settings Init()
{
var deserializer = new DeserializerBuilder()
.WithNamingConvention(UnderscoredNamingConvention.Instance)
.Build();
using var sr = new StreamReader("settings.yaml");
return deserializer.Deserialize<Settings>(sr.ReadToEnd());
}
[YamlMember]
public Project[] Projects { get; set; }
}

32
src/TUI/Domain/Version.cs Normal file
View File

@ -0,0 +1,32 @@
using TUI.Controls.Components;
namespace TUI.Domain;
public class Version
{
public readonly int Major;
public readonly int Minor;
public readonly int Patch;
public readonly VersionType Type;
public Version(string version)
{
var parts = version.Split('.');
Major = Convert.ToInt32(parts[0].Replace("^", "").Replace("~", ""));
Minor = Convert.ToInt32(parts[1]);
Patch = Convert.ToInt32(string.Join("", parts[2].TakeWhile(char.IsDigit)));
var extension = parts[2].Replace(Patch.ToString(), "");
Type = extension switch
{
not null when extension.Contains("rc") => VersionType.Candidate,
not null when extension.Contains("beta") => VersionType.Beta,
not null when extension.Contains("alpha") => VersionType.Alpha,
not null when extension.Contains("canary") => VersionType.Canary,
not null when extension.Contains("next") => VersionType.Next,
_ => VersionType.Release
};
}
}

View File

@ -1,45 +0,0 @@
using System.Text.RegularExpressions;
namespace TUI;
public static class Extensions
{
public static bool Have(this IEnumerable<string> array, string findValue)
{
return array.Any(item => item == findValue);
}
public static string Repeat(this char symbol, int repeatCount)
{
return repeatCount < 0 ? "" : new string(symbol, repeatCount);
}
public static string RemoveColors(this string text)
{
return Regex.Replace(text, @"\S\[(\d{0,3}[;m][_]?){0,5}", "");
}
public static int Width(this string text)
{
if (string.IsNullOrEmpty(text))
{
return 0;
}
var clearText = text.RemoveColors();
var stringInfo = new System.Globalization.StringInfo(clearText);
return stringInfo.LengthInTextElements;
}
public static Version? ToVersion(this string textVersion)
{
var version = textVersion.Replace("^", "").Replace("~", "").Split(".");
if (version.Length != 3)
return null;
var major = Convert.ToInt32(version[0]);
var minor = Convert.ToInt32(version[1]);
var patch = Convert.ToInt32(version[2].Split('-')[0]);
return new Version(major, minor, patch);
}
}

View File

@ -0,0 +1,178 @@
using TUI.Controls.Components;
using TUI.Controls.Containers;
using TUI.Controls.Layouts;
using TUI.Engine.Rendering.Canvas;
using TUI.Store;
namespace TUI.Pages;
public record DependenciesState(HeaderContainer Header, DashboardContainer Dashboard, FooterContainer Footer);
public class DependenciesPage : PageBase
{
private DependenciesPage()
{
}
public static DependenciesPage Instance { get; } = new();
private DependenciesStore _store;
private DependenciesState _state;
public override void Initial()
{
var header = new HeaderContainer();
var dashboard = new DashboardContainer();
var dependenciesHeader = new DependenciesContainer();
dependenciesHeader.AddTitleStub();
foreach (var conventionDependency in _store.ConventionDependencies)
{
dependenciesHeader.AddDependency(conventionDependency);
}
dashboard.AddChildren(dependenciesHeader);
foreach (var project in _store.Projects)
{
var projectDependencies = new DependenciesContainer(project);
projectDependencies.AddTitle(new ProjectTitle(project));
dashboard.AddChildren(projectDependencies);
}
var breadCrumbs = new BreadCrumbsComponent(" Dependencies", "JavaScript");
var footer = new FooterContainer(breadCrumbs);
_state = new DependenciesState(header, dashboard, footer);
}
public override void Render()
{
ICanvas canvas = new ConsoleCanvas();
var layout = new DashboardLayout(_state.Header, _state.Dashboard, _state.Footer);
canvas.Draw(layout);
}
public override void Load()
{
Initial();
var projects = _state.Dashboard.GetContent();
foreach (var projectDependencies in projects.Cast<DependenciesContainer?>().Skip(1))
{
if (projectDependencies is null)
{
continue;
}
var project = projectDependencies.Project;
var actualDependencies = _store.ActualDependencies(project).ToArray();
if (!actualDependencies.Any())
{
projectDependencies.AddError();
}
else
{
foreach (var conventionDependency in _store.ConventionDependencies)
{
var actualDependency = actualDependencies.SingleOrDefault(
dependency => string.Equals(dependency.Brand.Name, conventionDependency.Brand.Name,
StringComparison.CurrentCultureIgnoreCase));
if (actualDependency is null)
{
projectDependencies.AddDependencyStub();
continue;
}
var versionType = actualDependency.Comparison(conventionDependency);
projectDependencies.AddDependency(actualDependency, versionType);
}
}
Render();
}
}
public override void Bind()
{
_store = new DependenciesStore();
_store.Bind();
}
// private bool _commandLineInDisplay;
// private ProjectDto _currentProjectDto;
// private bool _headerInDisplay = true;
// public string FocusedElement { get; set; } = "";
// public void OpenDeps(ProjectDto projectDto)
// {
// _currentProjectDto = projectDto;
// var dashboardPosition = new ControlPosition(0, Header.Height);
// // DependencyDashboard.IsFocused = true;
// // CommandLine.IsFocused = false;
// DependenciesView.Render(_currentProjectDto, dashboardPosition);
// }
//
// private void ResizeDependencies()
// {
// var topPosition = 0;
// topPosition += _commandLineInDisplay ? CommandLine.Height : 0;
// topPosition += _headerInDisplay ? Header.Height : 0;
// var dashboardPosition = new ControlPosition(0, topPosition);
// DependenciesView.Render(_currentProjectDto, dashboardPosition);
// }
//
// public void Render()
// {
// var headerPosition = new ControlPosition(0, 0);
// Header.Render(headerPosition);
//
// const string copyrightText = "Kolosov Aleksandr";
// var copyrightPosition = new ControlPosition(
// Console.WindowWidth - copyrightText.Width(),
// Console.WindowHeight);
// CopyrightControl.Render(copyrightText, copyrightPosition);
// }
//
// public void ToggleHeader()
// {
// _headerInDisplay = !_headerInDisplay;
// if (_headerInDisplay)
// {
// var headerPosition = new ControlPosition(0, 0);
// Header.Render(headerPosition);
// }
//
// ResizeDependencies();
// }
//
// public void Next()
// {
// DependenciesView.Next();
// }
//
// public void Previous()
// {
// DependenciesView.Previous();
// }
//
// public void OpenCommandLine()
// {
// var commandLinePosition = new ControlPosition(0, _headerInDisplay ? Header.Height : 0);
// // CommandLine.IsFocused = true;
// // DependencyDashboard.IsFocused = false;
// FocusedElement = nameof(CommandLine);
// CommandLine.Render(commandLinePosition);
// _commandLineInDisplay = true;
// ResizeDependencies();
// Console.SetCursorPosition(commandLinePosition.Left + Theme.Padding + Theme.BorderWidth + 2,
// commandLinePosition.Top + Theme.BorderWidth);
// Console.CursorVisible = true;
// }
}

10
src/TUI/Pages/IPage.cs Normal file
View File

@ -0,0 +1,10 @@
namespace TUI.Pages;
interface IPage
{
void Open();
void Initial();
void Render();
void Bind();
void Load();
}

22
src/TUI/Pages/PageBase.cs Normal file
View File

@ -0,0 +1,22 @@
using System.Diagnostics;
namespace TUI.Pages;
public abstract class PageBase : IPage
{
public void Open()
{
Debugger.Log(0, "Event", $"Open page ${GetType().UnderlyingSystemType.Name}\n");
Bind();
Initial();
Render();
}
public abstract void Load();
public abstract void Initial();
public abstract void Render();
public abstract void Bind();
}

View File

@ -0,0 +1,47 @@
using TUI.Controls.Common;
using TUI.Controls.Components;
using TUI.Controls.Containers;
using TUI.Controls.Layouts;
using TUI.Controls.Statics;
using TUI.Engine.Attributes;
using TUI.Engine.Rendering.Canvas;
namespace TUI.Pages;
public class WelcomePage : PageBase
{
private WelcomePage()
{
}
public static WelcomePage Instance { get; } = new();
public override void Initial()
{
}
public override void Render()
{
ICanvas canvas = new ConsoleCanvas();
var header = new StubComponent(new Size(1, 1));
var logo = new LogoComponent();
var breadCrumbs = new BreadCrumbsComponent();
var footer = new FooterContainer(breadCrumbs);
var layout = new DashboardLayout(header, logo, footer);
canvas.Draw(layout);
}
public override void Load()
{
}
public override void Bind()
{
}
}

View File

@ -1,32 +1,48 @@
using TUI.Controls;
using Settings = TUI.Domain.Settings;
using TUI.Pages;
Console.Clear();
Console.CursorVisible = false;
var settings = Settings.Init();
var welcomePage = WelcomePage.Instance;
welcomePage.Open();
Thread.Sleep(500);
var display = new Display();
display.OpenDeps(settings.Projects[0]);
IPage currentPage = DependenciesPage.Instance;
currentPage.Open();
var hotKey = ConsoleKey.NoName;
ConsoleKeyInfo? key = null;
var waitCommand = true;
do
{
switch (hotKey)
switch (key?.Key)
{
case ConsoleKey.DownArrow:
display.Next();
case ConsoleKey.Q:
waitCommand = false;
break;
case ConsoleKey.UpArrow:
display.Previous();
case ConsoleKey.R:
key = null;
currentPage.Load();
break;
case ConsoleKey.E:
display.ToggleHeader();
case ConsoleKey.D1:
key = null;
currentPage = DependenciesPage.Instance;
Console.Clear();
currentPage.Render();
break;
case ConsoleKey.D0:
key = null;
currentPage = WelcomePage.Instance;
Console.Clear();
currentPage.Render();
break;
default:
key = Console.ReadKey(true);
break;
}
hotKey = Console.ReadKey(intercept: true).Key;
} while (hotKey != ConsoleKey.Q);
} while (waitCommand);
Console.Clear();
Console.CursorVisible = true;

View File

@ -0,0 +1,11 @@
using System.Runtime.Serialization;
using YamlDotNet.Serialization;
namespace TUI.Providers.Dependencies;
[DataContract]
[YamlSerializable]
public class DependenciesDto
{
[DataMember] [YamlMember] public StackDto[] Stacks { get; set; }
}

View File

@ -1,8 +1,7 @@
using System.Runtime.Serialization;
using YamlDotNet.Serialization;
namespace TUI.Settings;
namespace TUI.Providers.Dependencies;
[DataContract]
[YamlSerializable]

View File

@ -0,0 +1,126 @@
using System.Text.Json;
using System.Text.Json.Nodes;
using TUI.Domain;
using YamlDotNet.Serialization;
using YamlDotNet.Serialization.NamingConventions;
namespace TUI.Providers.Dependencies;
public class DependencyRepository
{
private DependenciesDto? _dependenciesDto;
private DependenciesDto DependenciesDto
{
get
{
if (_dependenciesDto is not null)
{
return _dependenciesDto;
}
var deserializer = new DeserializerBuilder()
.WithNamingConvention(UnderscoredNamingConvention.Instance)
.Build();
using var sr = new StreamReader("dependencies.yaml");
_dependenciesDto = deserializer.Deserialize<DependenciesDto>(sr.ReadToEnd());
return _dependenciesDto;
}
}
public IEnumerable<Dependency> ReadConventions(string stackName)
{
return DependenciesDto.Stacks
.Single(stack => stack.Name == stackName)
.Conventions
.Select(convention =>
{
var brand = new Brand(convention.Name, convention.Icon, convention.Color);
return new Dependency(convention.Version, brand);
});
}
public IEnumerable<Project> ReadProjects(string stackName)
{
var projects = new List<Project>();
var hubs = DependenciesDto.Stacks
.Single(stack => stack.Name == stackName)
.Hubs;
foreach (var hub in hubs)
{
projects.AddRange(hub
.Projects
.Select(proj => new Project(proj.Id, proj.Name, proj.Tags, new Hub(hub.Origin, hub.Type))));
}
return projects;
}
public IEnumerable<Dependency> ReadActual(Project project)
{
var dependencies = new List<Dependency>();
if (project.Hub.Type == "gitlab")
{
var endpoint = GetGitlabEndpoint(project.Hub.Origin, project.Id);
using HttpClient client = new();
var json = client.GetStringAsync(endpoint).GetAwaiter().GetResult();
var packageJson = JsonSerializer.Deserialize<PackageJson>(json);
dependencies.AddRange(Map(packageJson?.Dependencies));
dependencies.AddRange(Map(packageJson?.DevDependencies));
dependencies.AddRange(Map(packageJson?.Engines));
}
return dependencies;
}
private static string GetGitlabEndpoint(string origin, int projectId)
{
var token = Environment.GetEnvironmentVariable("TLD_GITLAB_PAT");
return $"{origin}/api/v4/projects/{projectId}/repository/files/package.json/raw?" +
$"private_token={token}&ref=dev";
}
private static IEnumerable<Dependency> Map(JsonObject? dependencies)
{
if (dependencies is null)
{
yield break;
}
foreach (var dependency in dependencies)
{
var actualVersion = dependency.Value?.ToString();
var brand = new Brand(dependency.Key);
if (actualVersion is null)
{
continue;
}
yield return new Dependency(actualVersion, brand);
}
}
}
// private static Package DownloadPackage(ProjectDto project)
// {
// // var endpoint = projectDto.Tags.Have("gitlab") ? GetGitlabEndpoint(projectDto) : projectDto.Repo;
// var endpoint = "";
// if (Packages.TryGetValue(endpoint, out var downloadPackage)) return downloadPackage;
//
// using HttpClient client = new();
// var json = client.GetStringAsync(endpoint).GetAwaiter().GetResult();
//
// Packages.Add(endpoint, package);
// return package;
// }
//

View File

@ -0,0 +1,15 @@
using System.Runtime.Serialization;
using YamlDotNet.Serialization;
namespace TUI.Providers.Dependencies;
[DataContract]
[YamlSerializable]
public class HubDto
{
[YamlMember] [DataMember] public string Origin { get; set; }
[YamlMember] [DataMember] public string Type { get; set; }
[YamlMember] [DataMember] public IEnumerable<ProjectDto> Projects { get; set; }
}

View File

@ -1,10 +1,10 @@
using System.Text.Json.Nodes;
using System.Text.Json.Serialization;
using TUI.Engine;
namespace TUI.Providers.Dependencies;
namespace TUI.Domain;
public class Package
public class PackageJson
{
[JsonPropertyName("dependencies")]
public JsonObject? Dependencies { get; set; }
@ -17,25 +17,16 @@ public class Package
public Version? ParseVersion(string? dependencyName)
{
if (dependencyName == null)
{
return null;
}
if (dependencyName == null) return null;
JsonNode? version = null;
var lowerDependencyName = dependencyName.ToLower();
Dependencies?.TryGetPropertyValue(lowerDependencyName, out version);
if (version == null)
{
Engines?.TryGetPropertyValue(lowerDependencyName, out version);
}
if (version == null) Engines?.TryGetPropertyValue(lowerDependencyName, out version);
if (version == null)
{
DevDependencies?.TryGetPropertyValue(lowerDependencyName, out version);
}
if (version == null) DevDependencies?.TryGetPropertyValue(lowerDependencyName, out version);
return version?.GetValue<string>().ToVersion();
}

View File

@ -0,0 +1,16 @@
using System.Runtime.Serialization;
using YamlDotNet.Serialization;
namespace TUI.Providers.Dependencies;
[YamlSerializable]
public class ProjectDto
{
[DataMember] [YamlMember] public int Id { get; set; }
[DataMember] [YamlMember] public string Name { get; set; }
[DataMember] [YamlMember] public string Deps { get; set; }
[DataMember] [YamlMember] public IEnumerable<string> Tags { get; set; }
}

View File

@ -0,0 +1,17 @@
using System.Runtime.Serialization;
using YamlDotNet.Serialization;
namespace TUI.Providers.Dependencies;
[DataContract]
[YamlSerializable]
public class StackDto
{
[YamlMember] [DataMember] public string Name { get; set; }
[YamlMember] [DataMember] public string Icon { get; set; }
[YamlMember] [DataMember] public DependencyDto[] Conventions { get; set; }
[YamlMember] [DataMember] public IEnumerable<HubDto> Hubs { get; set; }
}

View File

@ -1,20 +0,0 @@
using YamlDotNet.Serialization;
namespace TUI.Settings;
[YamlSerializable]
public class SourceDto
{
[YamlMember]
public string[] Tags { get; set; }
[YamlMember]
public string Name { get; set; }
[YamlMember]
public int ProjectId { get; set; } = 0;
[YamlMember]
public string Repo { get; set; }
}

View File

@ -0,0 +1,43 @@
using System.Diagnostics;
using TUI.Controls.Components;
using TUI.Domain;
using TUI.Engine;
using TUI.Engine.Theme;
using TUI.Providers.Dependencies;
namespace TUI.Store;
public class DependenciesStore
{
public IEnumerable<Dependency> ConventionDependencies;
public IEnumerable<Project> Projects;
private DependencyRepository Repository = new();
public IEnumerable<Dependency> ActualDependencies(Project project)
{
SpeakerComponent.Instance.Shout(Symbols.Download.Info(),
$"Fetch actual dependencies for project {project.Name.Main()}");
try
{
return Repository.ReadActual(project);
}
catch(Exception ex)
{
Debugger.Log(0, "error", ex.Message);
SpeakerComponent.Instance.Shout(Symbols.Error.Error(), $"Fetch failed for project{project.Name}");
return new List<Dependency>();
}
}
public void Bind()
{
SpeakerComponent.Instance.Shout("🤔", "Prepare javascript conventions");
ConventionDependencies = Repository.ReadConventions("javascript");
SpeakerComponent.Instance.Shout("🤩", "Prepare javascript projects");
Projects = Repository.ReadProjects("javascript");
}
}

View File

@ -9,14 +9,26 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Pastel" Version="4.1.0" />
<PackageReference Include="YamlDotNet" Version="13.1.1" />
<PackageReference Include="Pastel" Version="4.1.0"/>
<PackageReference Include="YamlDotNet" Version="13.1.1"/>
</ItemGroup>
<ItemGroup>
<None Update="settings.yaml">
<None Update="dependencies.yaml">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup>
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo">
<_Parameter1>$(MSBuildProjectName).Tests</_Parameter1>
</AssemblyAttribute>
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo">
<_Parameter1>$(MSBuildProjectName).Controls.Tests</_Parameter1>
</AssemblyAttribute>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\TUI.Engine\TUI.Engine.csproj"/>
</ItemGroup>
</Project>

View File

@ -0,0 +1,19 @@
// using TUI.Components.Controls;
// using TUI.Engine;
// using TUI.Engine.Controls;
//
//
// namespace TUI.UserInterface;
// //
// // public class CommandLine : DashboardControl
// // {
// // public const int Height = 3;
// //
// // public void Render(ControlPosition position)
// // {
// // base.Render("Command", position, Height);
// //
// // Console.SetCursorPosition(position.Left + Theme.BorderWidth + Theme.Padding, position.Top + Theme.BorderWidth);
// // Console.Write(">");
// // }
// // }

View File

@ -1,82 +0,0 @@
using TUI.Controls;
namespace TUI.UserInterface;
public class Header : IControl
{
public const int LogoWidth = 16;
public const int Height = 6;
public const int MaxHeaderBlocksWidth = 16;
private readonly Dictionary<string, string> _hotKeys = new()
{
{ "", "select prev" },
{ "", "select next" },
{ "󰬌", "toggle head" },
{ "󰬘", "quit" },
};
private readonly Dictionary<string, string> _hints = new()
{
{ "󰎔", "too new".Info() },
{ "", "so good" },
{ "", "be nice".Primary() },
{ "󰬟", "too old".Warning() }
};
private readonly Dictionary<string, string> _tags = new()
{
{ Icons.Auth, "Auth" },
{ Icons.NetworkPublic, "WWW" },
{ Icons.SEO, "SEO" },
{ Icons.GitLab, "VCS" },
};
public void Render(Position position)
{
Console.SetCursorPosition(position.Left, position.Top);
for (var i = 1; i <= Height; i++)
{
Console.WriteLine(new string(' ', Console.WindowWidth - LogoWidth));
}
RenderBlock(0, _hints);
RenderBlock(1, _tags);
RenderBlock(2, Icons.Applications);
RenderBlock(3, _hotKeys);
RenderLogo();
}
private static void RenderBlock(int blockNumber, Dictionary<string, string> items)
{
var leftPadding = Theme.Padding + blockNumber * MaxHeaderBlocksWidth;
var hotKeyNumber = 0;
foreach (var item in items)
{
Console.SetCursorPosition(leftPadding, Theme.Padding + hotKeyNumber++);
Console.Write((item.Key + " " + item.Value).Hint());
}
}
private static void RenderLogo()
{
Console.SetCursorPosition(Console.WindowWidth - LogoWidth - Theme.Padding, 0);
Console.WriteLine(" ╭━━━━┳╮".Primary() + "".Hint() + "╭━━━╮ ".Primary());
Console.SetCursorPosition(Console.WindowWidth - LogoWidth - Theme.Padding, 1);
Console.WriteLine(" ┃╭╮╭╮┃┃".Primary() + "".Hint() + "╰╮╭╮┃ ".Primary());
Console.SetCursorPosition(Console.WindowWidth - LogoWidth - Theme.Padding, 2);
Console.WriteLine(" ╰╯┃┃╰┫┃".Primary() + "".Hint() + "┃┃┃┃ ".Primary());
Console.SetCursorPosition(Console.WindowWidth - LogoWidth - Theme.Padding, 3);
Console.WriteLine(" ".Hint() + "┃┃".Primary() + "".Hint() + "┃┃".Primary() + "".Hint() +
"╭╮┃┃┃┃ ".Primary());
Console.SetCursorPosition(Console.WindowWidth - LogoWidth - Theme.Padding, 4);
Console.WriteLine(" ".Hint() + "┃┃".Primary() + "".Hint() + "┃╰━╯┣╯╰╯┃ ".Primary());
Console.SetCursorPosition(Console.WindowWidth - LogoWidth - Theme.Padding, 5);
Console.WriteLine("".Hint() + "╰╯".Primary() + "".Hint() + "╰━━━┻━━━╯ ".Primary());
}
}

View File

@ -1,32 +1,15 @@
using Pastel;
using TUI.Engine;
namespace TUI.UserInterface;
public static class Icons
{
public static string GitLab => GetIcon("", "E24329");
public static string GitHub => GetIcon("", "ADBAC7");
public static string Git => GetIcon("", "F14E32");
public static string NetworkPublic => GetIcon("󰞉", "00FFFF");
public static string NetworkPrivate => GetIcon("󰕑");
public static string Undefined => GetIcon("");
public static string Site => GetIcon("", "BF40BF");
public static string Api => GetIcon("", "7F52FF");
public static string DockerImage => GetIcon("󰡨", "086DD7");
public static string NpmPackage => GetIcon("", "CB0000");
public static string SEO => GetIcon("󰚩", "4285F4");
public static string Auth => GetIcon("", "FFD700");
public static string NotFound => GetIcon("");
public readonly static Dictionary<string, string> Applications = new()
public static readonly Dictionary<string, string> Applications = new()
{
{ NpmPackage, "package" },
{ DockerImage, "image" },
{ Site, "site" },
{ Api, "api" },
{ Symbols.NpmPackage.Colorized(), "package" },
{ Symbols.DockerImage.Colorized(), "image" },
{ Symbols.Site.Colorized(), "site" },
{ Symbols.Api, "api" }
};
private static string GetIcon(string icon, string? activeColor = null) =>
icon.Pastel(activeColor ?? Palette.HintColor);
}

View File

@ -1,4 +1,5 @@
using TUI.Settings;
using TUI.Engine.Theme;
using TUI.Providers.Dependencies;
namespace TUI.UserInterface;
@ -11,13 +12,16 @@ public static class Panel
private const int TagCount = 5;
private const int TagWidth = 2;
public static void RenderRows(SourceDto[] sources, int selectedRowNumber)
private static int _marginTop;
public static void RenderRows(ProjectDto[] sources, int selectedRowNumber)
{
for (var index = 0; index < sources.Length; index++)
{
Console.SetCursorPosition(Theme.Padding,
Console.SetCursorPosition(Convert.ToInt32(Indentation.Default),
6 + index + _marginTop + BorderWidth +
Theme.Padding);
Convert.ToInt32(Indentation.Default));
if (selectedRowNumber == index + 1)
{
@ -28,9 +32,8 @@ public static class Panel
}
for (var index = 0; index < sources.Length; index++)
{
Console.SetCursorPosition(TitleWidth,
6 + index + _marginTop + BorderWidth + Theme.Padding);
6 + index + _marginTop + BorderWidth + Convert.ToInt32(Indentation.Default));
// var source = sources[index];
// var package = DownloadPackage(source);
// var resultText = package.Dependencies.React;
@ -41,7 +44,6 @@ public static class Panel
// }
//
// Console.Write(resultText);
}
// for (var index = 0; index < sources.Length; index++)
// {
// var loading = true;
@ -74,9 +76,6 @@ public static class Panel
// }
}
private static int _marginTop;
// private static Package DownloadPackage(Source source)
// {
// if (Packages.TryGetValue(source.Repo, out var downloadPackage))

View File

@ -1,7 +0,0 @@
namespace TUI.UserInterface;
public static class Theme
{
public static int Padding = 1;
public static int BorderWidth = 1;
}

Some files were not shown because too many files have changed in this diff Show More