diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..d08d9a9 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,166 @@ +[*] +charset=utf-8 +end_of_line=lf +indent_size=4 +indent_style=space +insert_final_newline=false +max_line_length=120 +tab_width=4 +trim_trailing_whitespace=false +ij_continuation_indent_size=8 +ij_formatter_off_tag=@formatter:off +ij_formatter_on_tag=@formatter:on +ij_formatter_tags_enabled=false +ij_smart_tabs=false +ij_visual_guides=none +ij_wrap_on_typing=true + +[*.properties] +ij_properties_align_group_field_declarations=false +ij_properties_keep_blank_lines=false +ij_properties_key_value_delimiter=equals +ij_properties_spaces_around_key_value_delimiter=false + +[*.proto] +indent_size=2 +tab_width=2 +ij_continuation_indent_size=4 +ij_protobuf_keep_blank_lines_in_code=2 +ij_protobuf_keep_indents_on_empty_lines=false +ij_protobuf_keep_line_breaks=true +ij_protobuf_space_after_comma=true +ij_protobuf_space_before_comma=false +ij_protobuf_spaces_around_assignment_operators=true +ij_protobuf_spaces_within_braces=false +ij_protobuf_spaces_within_brackets=false + +[.editorconfig] +ij_editorconfig_align_group_field_declarations=false +ij_editorconfig_space_after_colon=false +ij_editorconfig_space_after_comma=false +ij_editorconfig_space_before_colon=false +ij_editorconfig_space_before_comma=false +ij_editorconfig_spaces_around_assignment_operators=false + +[{*.ant,*.appxmanifest,*.axml,*.cscfg,*.csdef,*.disco,*.filelayout,*.fxml,*.jhm,*.jnlp,*.jrxml,*.manifest,*.myapp,*.nuspec,*.rng,*.stylecop,*.svcmap,*.tld,*.wadcfgx,*.webref,*.wsdl,*.xml,*.xsd,*.xsl,*.xslt,*.xul,StyleCop.Cache}] +ij_xml_align_attributes=true +ij_xml_align_text=false +ij_xml_attribute_wrap=normal +ij_xml_block_comment_add_space=false +ij_xml_block_comment_at_first_column=true +ij_xml_keep_blank_lines=2 +ij_xml_keep_indents_on_empty_lines=false +ij_xml_keep_line_breaks=true +ij_xml_keep_line_breaks_in_text=true +ij_xml_keep_whitespaces=false +ij_xml_keep_whitespaces_around_cdata=preserve +ij_xml_keep_whitespaces_inside_cdata=false +ij_xml_line_comment_at_first_column=true +ij_xml_space_after_tag_name=false +ij_xml_space_around_equals_in_attribute=false +ij_xml_space_inside_empty_tag=false +ij_xml_text_wrap=normal +max_line_length=off +trim_trailing_whitespace=true +insert_final_newline=true +indent_size=2 + +[{*.bash,*.sh,*.zsh}] +indent_size=2 +tab_width=2 +ij_shell_binary_ops_start_line=false +ij_shell_keep_column_alignment_padding=false +ij_shell_minify_program=false +ij_shell_redirect_followed_by_space=false +ij_shell_switch_cases_indented=false +ij_shell_use_unix_line_separator=true + +[{*.har,*.json}] +indent_size=2 +tab_width=2 +ij_json_keep_blank_lines_in_code=0 +ij_json_keep_indents_on_empty_lines=false +ij_json_keep_line_breaks=true +ij_json_space_after_colon=true +ij_json_space_after_comma=true +ij_json_space_before_colon=true +ij_json_space_before_comma=false +ij_json_spaces_within_braces=false +ij_json_spaces_within_brackets=false +ij_json_wrap_long_lines=false + +[{*.htm,*.html,*.sht,*.shtm,*.shtml}] +ij_html_add_new_line_before_tags=body,div,p,form,h1,h2,h3 +ij_html_align_attributes=true +ij_html_align_text=false +ij_html_attribute_wrap=normal +ij_html_block_comment_add_space=false +ij_html_block_comment_at_first_column=true +ij_html_do_not_align_children_of_min_lines=0 +ij_html_do_not_break_if_inline_tags=title,h1,h2,h3,h4,h5,h6,p +ij_html_do_not_indent_children_of_tags=html,body,thead,tbody,tfoot +ij_html_enforce_quotes=false +ij_html_inline_tags=a,abbr,acronym,b,basefont,bdo,big,br,cite,cite,code,dfn,em,font,i,img,input,kbd,label,q,s,samp,select,small,span,strike,strong,sub,sup,textarea,tt,u,var +ij_html_keep_blank_lines=2 +ij_html_keep_indents_on_empty_lines=false +ij_html_keep_line_breaks=true +ij_html_keep_line_breaks_in_text=true +ij_html_keep_whitespaces=false +ij_html_keep_whitespaces_inside=span,pre,textarea +ij_html_line_comment_at_first_column=true +ij_html_new_line_after_last_attribute=never +ij_html_new_line_before_first_attribute=never +ij_html_quote_style=double +ij_html_remove_new_line_before_tags=br +ij_html_space_after_tag_name=false +ij_html_space_around_equality_in_attribute=false +ij_html_space_inside_empty_tag=false +ij_html_text_wrap=normal + +[{*.markdown,*.md}] +indent_size=2 +tab_width=2 +ij_continuation_indent_size=4 +ij_visual_guides=120 +ij_markdown_force_one_space_after_blockquote_symbol=true +ij_markdown_force_one_space_after_header_symbol=true +ij_markdown_force_one_space_after_list_bullet=true +ij_markdown_force_one_space_between_words=true +ij_markdown_insert_quote_arrows_on_wrap=true +ij_markdown_keep_indents_on_empty_lines=false +ij_markdown_keep_line_breaks_inside_text_blocks=true +ij_markdown_max_lines_around_block_elements=1 +ij_markdown_max_lines_around_header=1 +ij_markdown_max_lines_between_paragraphs=1 +ij_markdown_min_lines_around_block_elements=1 +ij_markdown_min_lines_around_header=1 +ij_markdown_min_lines_between_paragraphs=1 +ij_markdown_wrap_text_if_long=true +ij_markdown_wrap_text_inside_blockquotes=true + +[{*.pb,*.textproto}] +indent_size=2 +tab_width=2 +ij_continuation_indent_size=4 +ij_prototext_keep_blank_lines_in_code=2 +ij_prototext_keep_indents_on_empty_lines=false +ij_prototext_keep_line_breaks=true +ij_prototext_space_after_colon=true +ij_prototext_space_after_comma=true +ij_prototext_space_before_colon=false +ij_prototext_space_before_comma=false +ij_prototext_spaces_within_braces=true +ij_prototext_spaces_within_brackets=false + +[{*.yaml,*.yml}] +indent_size=2 +ij_yaml_align_values_properties=do_not_align +ij_yaml_autoinsert_sequence_marker=true +ij_yaml_block_mapping_on_new_line=false +ij_yaml_indent_sequence_value=true +ij_yaml_keep_indents_on_empty_lines=false +ij_yaml_keep_line_breaks=true +ij_yaml_sequence_on_new_line=false +ij_yaml_space_before_colon=false +ij_yaml_spaces_within_braces=true +ij_yaml_spaces_within_brackets=true diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7f24a60 --- /dev/null +++ b/.gitignore @@ -0,0 +1,345 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore +*.yaml + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- Backup*.rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# JetBrains Rider +.idea/ +*.sln.iml + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# BeatPulse healthcheck temp database +healthchecksdb +*.DotSettings +*.DS_Store +**/*.Production.json +Logs/ \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..402e5b1 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,37 @@ +# Changelog +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +### 🆕 Added + +- Add dashboard for dependencies +- Add header with toggle +- Add hotkey for exit (q) + +### 🛠 Changed + +_Список изменившейся функциональности._ + +### 📜 Deprecated + +_Список устаревшей функциональности._ + +### 🗑 Removed + +_Список удаленной функциональности._ + +### 🪲 Fixed + +_Список исправлений багов._ + +### 🔐 Security + +_Список правок для обеспечения безопасности._ + +### 📦 Support + +_Список правок для обеспечения технической поддержки._ diff --git a/README.md b/README.md index feaff64..cf6f367 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,7 @@ -# tld +# Team Lead Dashboards + Team Lead Dashboard CLI To Manage Your Work In Style! + +## Dependencies + +![](docs/Dependencies.png) \ No newline at end of file diff --git a/TLD.sln b/TLD.sln new file mode 100644 index 0000000..245bd53 --- /dev/null +++ b/TLD.sln @@ -0,0 +1,20 @@ + +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 +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {BF07B744-015F-4904-B1B6-CBDE266A07B0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BF07B744-015F-4904-B1B6-CBDE266A07B0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BF07B744-015F-4904-B1B6-CBDE266A07B0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BF07B744-015F-4904-B1B6-CBDE266A07B0}.Release|Any CPU.Build.0 = Release|Any CPU + {F92C03F7-2A65-4D0A-9736-13E749AF6903}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {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 + EndGlobalSection +EndGlobal diff --git a/docs/Dependencies.png b/docs/Dependencies.png new file mode 100644 index 0000000..e4504f9 Binary files /dev/null and b/docs/Dependencies.png differ diff --git a/src/TUI/Controls/Copyright.cs b/src/TUI/Controls/Copyright.cs new file mode 100644 index 0000000..0100952 --- /dev/null +++ b/src/TUI/Controls/Copyright.cs @@ -0,0 +1,16 @@ +using TUI.UserInterface; + + +namespace TUI.Controls; + +public class Copyright : IControl +{ + 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); + } +} \ No newline at end of file diff --git a/src/TUI/Controls/Dashboard.cs b/src/TUI/Controls/Dashboard.cs new file mode 100644 index 0000000..69ae5bb --- /dev/null +++ b/src/TUI/Controls/Dashboard.cs @@ -0,0 +1,60 @@ +using System.Text; +using TUI.UserInterface; + + +namespace TUI.Controls; + +public class Dashboard : IControl +{ + 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()); + } +} \ No newline at end of file diff --git a/src/TUI/Controls/Display.cs b/src/TUI/Controls/Display.cs new file mode 100644 index 0000000..bd819a1 --- /dev/null +++ b/src/TUI/Controls/Display.cs @@ -0,0 +1,73 @@ +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(); + } +} \ No newline at end of file diff --git a/src/TUI/Controls/IControl.cs b/src/TUI/Controls/IControl.cs new file mode 100644 index 0000000..6fda601 --- /dev/null +++ b/src/TUI/Controls/IControl.cs @@ -0,0 +1,11 @@ +namespace TUI.Controls; + +public interface IControl +{ + void Render(Position position); +} + +public interface IControl +{ + void Render(TProps props, Position position); +} \ No newline at end of file diff --git a/src/TUI/Controls/Position.cs b/src/TUI/Controls/Position.cs new file mode 100644 index 0000000..d9200b9 --- /dev/null +++ b/src/TUI/Controls/Position.cs @@ -0,0 +1,3 @@ +namespace TUI.Controls; + +public record Position(int Left, int Top); \ No newline at end of file diff --git a/src/TUI/Controls/Table.cs b/src/TUI/Controls/Table.cs new file mode 100644 index 0000000..353ca3e --- /dev/null +++ b/src/TUI/Controls/Table.cs @@ -0,0 +1,74 @@ +using Pastel; +using TUI.UserInterface; + + +namespace TUI.Controls; + +public record TableProps(IEnumerable HeaderCells, + IEnumerable Rows, + int TitleWidth, + int ColumnWidth); + +public class Table : IControl +{ + 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 _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); + } +} \ No newline at end of file diff --git a/src/TUI/Dashboards/DependencyDashboard.cs b/src/TUI/Dashboards/DependencyDashboard.cs new file mode 100644 index 0000000..a6ec1dc --- /dev/null +++ b/src/TUI/Dashboards/DependencyDashboard.cs @@ -0,0 +1,156 @@ +using System.Text; +using System.Text.Json; +using Pastel; +using TUI.Controls; +using TUI.Domain; +using TUI.UserInterface; + + +namespace TUI.Dashboards; + +public class DependencyDashboard : IControl +{ + private int _selectedRowNumber = 0; + + private const int TitleWidth = 35; + private const int ColumnWidth = 10; + + private 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); + } + + // Panel.RenderRows(project.Sources.ToArray(), _selectedRowNumber); + } + + private string GetDependencies(Source source, IEnumerable conventionDependencies) + { + var package = DownloadPackage(source); + + return string.Join("", + conventionDependencies + .Select(package.Dependencies.GetVersion) + .Select(GetCurrentVersion)); + } + + private readonly static Dictionary Packages = new(); + + private static Package DownloadPackage(Source source) + { + if (Packages.TryGetValue(source.Repo, out var downloadPackage)) + { + return downloadPackage; + } + + using HttpClient client = new(); + var json = client.GetStringAsync(source.Repo).GetAwaiter().GetResult(); + var package = JsonSerializer.Deserialize(json); + Packages.Add(source.Repo, package); + return package; + } + + // private string GetVersions(string title) + // { + // // var source = sources[index]; + // // var package = DownloadPackage(source); + // // var resultText = package.Dependencies.React; + // // resultText = new string(' ', ColumnWidth - resultText.Width()) + resultText; + // // if (selectedRowNumber == index + 1) + // // { + // // resultText = resultText.PastelBg("292928"); + // // } + // + // return resultText; + // } + + private static string GetConventionVersion(Dependency dependency) + { + return dependency.Icon.Pastel(dependency.Color) + dependency.Version.Primary(); + } + + private static string GetCurrentVersion(string version) + { + return ' '.Repeat(ColumnWidth - version.Width()) + version; + } + + private static string GetTitle(Source source) + { + var rowText = new StringBuilder(); + + RenderPadding(rowText); + RenderTags(rowText, source); + rowText.Append(source.Name); + RenderPadding(rowText); + var text = rowText.ToString(); + return $"{text}{' '.Repeat(TitleWidth - text.Width())}"; + } + + private static void RenderPadding(StringBuilder rowText) + { + rowText.Append(new string(' ', Theme.Padding)); + } + + private static void RenderTags(StringBuilder rowText, Source source) + { + rowText.Append(GetGitApplication(source)); + rowText.Append(source.Tags.Have("public") + ? GetIcon("󰞉", "00FFFF") + : GetIcon("󰕑", "AFE1AF")); + rowText.Append(GetIcon("󰚩", "4285F4", source.Tags.Have("seo"))); + rowText.Append(GetIcon("", "FFD700", source.Tags.Have("auth"))); + rowText.Append(GetApplicationType(source)); + } + + private static string GetApplicationType(Source source) + { + if (source.Tags.Have("site")) + return GetIcon("", "BF40BF"); + if (source.Tags.Have("api")) + return GetIcon("", "7F52FF"); + if (source.Tags.Have("package")) + return GetIcon("", "CB0000"); + if (source.Tags.Have("image")) + return GetIcon("󰡨", "086DD7"); + + return GetIcon("", "CB0000"); + } + + private static string GetGitApplication(Source source) => source.Repo switch + { + { } url when url.Contains("gitlab") => GetIcon("", "E24329"), + { } url when url.Contains("github") => GetIcon("", "ADBAC7"), + _ => GetIcon("", "F14E32") + }; + + private static string GetIcon(string icon, string activeColor, bool enabled = true) => + (icon.Pastel(enabled ? activeColor : "71797E") + " ").PadLeft(2); + + public void Next() + { + _table.Next(); + } + + public void Previous() + { + _table.Previous(); + } +} \ No newline at end of file diff --git a/src/TUI/Domain/Dependency.cs b/src/TUI/Domain/Dependency.cs new file mode 100644 index 0000000..e1e3961 --- /dev/null +++ b/src/TUI/Domain/Dependency.cs @@ -0,0 +1,26 @@ +using YamlDotNet.Serialization; + + +namespace TUI.Domain; + +[YamlSerializable] +public class Dependency +{ + private string _icon; + + [YamlMember] + public string Name { get; set; } + + [YamlMember] + public string Icon + { + get => $" {_icon} "; + set => _icon = value; + } + + [YamlMember] + public string Version { get; set; } + + [YamlMember] + public string Color { get; set; } +} \ No newline at end of file diff --git a/src/TUI/Domain/Package.cs b/src/TUI/Domain/Package.cs new file mode 100644 index 0000000..88218f9 --- /dev/null +++ b/src/TUI/Domain/Package.cs @@ -0,0 +1,26 @@ +using System.Text.Json.Serialization; + + +namespace TUI.Domain; + +public class Package +{ + [JsonPropertyName("dependencies")] + public Dependencies Dependencies { get; set; } +} + +public class Dependencies +{ + [JsonPropertyName("react")] + public string React { get; set; } + + [JsonPropertyName("typesciprt")] + public string TypeScript { get; set; } + + public string GetVersion(Dependency dependency) => dependency.Name.ToLower() switch + { + "react" => React, + "typescipt" => TypeScript, + _ => "-" + }; +} \ No newline at end of file diff --git a/src/TUI/Domain/Project.cs b/src/TUI/Domain/Project.cs new file mode 100644 index 0000000..12a06c1 --- /dev/null +++ b/src/TUI/Domain/Project.cs @@ -0,0 +1,21 @@ +using System.Runtime.CompilerServices; +using YamlDotNet.Serialization; + + +namespace TUI.Domain; + +[YamlSerializable] +public class Project +{ + [YamlMember] + public string Icon { get; set; } + + [YamlMember] + public string Name { get; set; } + + [YamlMember] + public Dependency[] Dependencies { get; set; } + + [YamlMember] + public IList Sources { get; set; } +} \ No newline at end of file diff --git a/src/TUI/Domain/Settings.cs b/src/TUI/Domain/Settings.cs new file mode 100644 index 0000000..0a4c676 --- /dev/null +++ b/src/TUI/Domain/Settings.cs @@ -0,0 +1,22 @@ +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(sr.ReadToEnd()); + } + + [YamlMember] + public Project[] Projects { get; set; } +} \ No newline at end of file diff --git a/src/TUI/Domain/Source.cs b/src/TUI/Domain/Source.cs new file mode 100644 index 0000000..e8f154a --- /dev/null +++ b/src/TUI/Domain/Source.cs @@ -0,0 +1,17 @@ +using YamlDotNet.Serialization; + + +namespace TUI.Domain; + +[YamlSerializable] +public class Source +{ + [YamlMember] + public string[] Tags { get; set; } + + [YamlMember] + public string Name { get; set; } + + [YamlMember] + public string Repo { get; set; } +} \ No newline at end of file diff --git a/src/TUI/Extensions.cs b/src/TUI/Extensions.cs new file mode 100644 index 0000000..a24d1c7 --- /dev/null +++ b/src/TUI/Extensions.cs @@ -0,0 +1,24 @@ +using System.Text.RegularExpressions; + + +namespace TUI; + +public static class Extensions +{ + public static bool Have(this IEnumerable array, string findValue) + { + return array.Any(item => item == findValue); + } + + public static string Repeat(this char symbol, int repeatCount) + { + return new string(symbol, repeatCount); + } + + public static int Width(this string text) + { + var clearText = Regex.Replace(text, @"\S\[(\d{0,3}[;m][_]?){0,5}", ""); + var stringInfo = new System.Globalization.StringInfo(clearText); + return stringInfo.LengthInTextElements; + } +} \ No newline at end of file diff --git a/src/TUI/Program.cs b/src/TUI/Program.cs new file mode 100644 index 0000000..ed56ff6 --- /dev/null +++ b/src/TUI/Program.cs @@ -0,0 +1,32 @@ +using TUI.Controls; +using Settings = TUI.Domain.Settings; + + +Console.Clear(); +Console.CursorVisible = false; + +var settings = Settings.Init(); + +var display = new Display(); +display.OpenDeps(settings.Projects[0]); + +var hotKey = ConsoleKey.NoName; +do +{ + switch (hotKey) + { + case ConsoleKey.DownArrow: + display.Next(); + break; + case ConsoleKey.UpArrow: + display.Previous(); + break; + case ConsoleKey.E: + display.ToggleHeader(); + break; + } + + hotKey = Console.ReadKey(intercept: true).Key; +} while (hotKey != ConsoleKey.Q); + +Console.Clear(); \ No newline at end of file diff --git a/src/TUI/TUI.csproj b/src/TUI/TUI.csproj new file mode 100644 index 0000000..c3c1043 --- /dev/null +++ b/src/TUI/TUI.csproj @@ -0,0 +1,21 @@ + + + + Exe + net7.0 + enable + enable + + + + + + + + + + Always + + + + diff --git a/src/TUI/UserInterface/Header.cs b/src/TUI/UserInterface/Header.cs new file mode 100644 index 0000000..b547e97 --- /dev/null +++ b/src/TUI/UserInterface/Header.cs @@ -0,0 +1,39 @@ +using TUI.Controls; + + +namespace TUI.UserInterface; + +public class Header : IControl +{ + public const int LogoWidth = 16; + public const int Height = 6; + + 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)); + } + + RenderLogo(); + } + + private 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()); + } +} \ No newline at end of file diff --git a/src/TUI/UserInterface/Palette.cs b/src/TUI/UserInterface/Palette.cs new file mode 100644 index 0000000..27dcdd7 --- /dev/null +++ b/src/TUI/UserInterface/Palette.cs @@ -0,0 +1,13 @@ +using Pastel; + + +namespace TUI.UserInterface; + +public static class Palette +{ + public const string HoverColor = "292928"; + public const string PrimaryColor = "84BA64"; + public const string HintColor = "71797E"; + public static string Primary(this string currentText) => currentText.Pastel(PrimaryColor); + public static string Hint(this string currentText) => currentText.Pastel(HintColor); +} \ No newline at end of file diff --git a/src/TUI/UserInterface/Panel.cs b/src/TUI/UserInterface/Panel.cs new file mode 100644 index 0000000..d1813ac --- /dev/null +++ b/src/TUI/UserInterface/Panel.cs @@ -0,0 +1,96 @@ +using System.Text; +using System.Text.Json; +using Pastel; +using TUI.Domain; + + +namespace TUI.UserInterface; + +public static class Panel +{ + private const int BorderWidth = 1; + private const int ColumnWidth = 10; + private const int TitleWidth = 35; + private const int TagCount = 5; + private const int TagWidth = 2; + + public static void RenderRows(Source[] sources, int selectedRowNumber) + { + for (var index = 0; index < sources.Length; index++) + { + Console.SetCursorPosition(Theme.Padding, + 6 + index + _marginTop + BorderWidth + + Theme.Padding); + + if (selectedRowNumber == index + 1) + { + // resultText = resultText.PastelBg("292928"); + } + + // Console.Write(resultText); + } + + for (var index = 0; index < sources.Length; index++) + { + Console.SetCursorPosition(TitleWidth, + 6 + index + _marginTop + BorderWidth + Theme.Padding); + // var source = sources[index]; + // var package = DownloadPackage(source); + // var resultText = package.Dependencies.React; + // resultText = new string(' ', ColumnWidth - resultText.Width()) + resultText; + // if (selectedRowNumber == index + 1) + // { + // resultText = resultText.PastelBg("292928"); + // } + // + // Console.Write(resultText); + } + // for (var index = 0; index < sources.Length; index++) + // { + // var loading = true; + // var braille = new[] { "⠿", "⠧", "⠏", "⠛", "⠹", "⠼", "⠶" }; + // var braileNumber = 0; + // do + // { + // var resultText = braille[braileNumber]; + // if (selectedRowNumber == index + 1) + // { + // resultText = resultText.PastelBg("292928"); + // } + // + // Console.SetCursorPosition(ColumnWidth + TagCountInLeftPanel * 2, index + 2); + // Console.Write(resultText); + // Thread.Sleep(100); + // if (braileNumber == braille.Length - 1) + // { + // braileNumber = 0; + // loading = false; + // } + // else + // { + // braileNumber++; + // } + // } while (loading); + // + // Console.SetCursorPosition(ColumnWidth + TagCountInLeftPanel * 2, index + 2); + // Console.Write(braille[0]); + // } + } + + + private static int _marginTop; + + // private static Package DownloadPackage(Source source) + // { + // if (Packages.TryGetValue(source.Repo, out var downloadPackage)) + // { + // return downloadPackage; + // } + // + // using HttpClient client = new(); + // var json = client.GetStringAsync(source.Repo).GetAwaiter().GetResult(); + // var package = JsonSerializer.Deserialize(json); + // Packages.Add(source.Repo, package); + // return package; + // } +} \ No newline at end of file diff --git a/src/TUI/UserInterface/Theme.cs b/src/TUI/UserInterface/Theme.cs new file mode 100644 index 0000000..dec24e8 --- /dev/null +++ b/src/TUI/UserInterface/Theme.cs @@ -0,0 +1,7 @@ +namespace TUI.UserInterface; + +public static class Theme +{ + public static int Padding = 1; + public static int BorderWidth = 1; +} \ No newline at end of file