feat: initial commit

This commit is contained in:
alphafetus committed 2025-09-12 21:08:49 -04:00
commit d0b7e83609
15 files changed
+449

No files matched your search

+5
View File
@@ -0,0 +1,5 @@
bin/
obj/
/packages/
riderModule.iml
/_ReSharper.Caches/
+16
View File
@@ -0,0 +1,16 @@
Microsoft Visual Studio Solution File, Format Version 12.00
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PantherByte", "PantherByte\PantherByte.csproj", "{92A66B7A-D045-450D-AF19-1DC36FD599F7}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{92A66B7A-D045-450D-AF19-1DC36FD599F7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{92A66B7A-D045-450D-AF19-1DC36FD599F7}.Debug|Any CPU.Build.0 = Debug|Any CPU
{92A66B7A-D045-450D-AF19-1DC36FD599F7}.Release|Any CPU.ActiveCfg = Release|Any CPU
{92A66B7A-D045-450D-AF19-1DC36FD599F7}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal
+15
View File
@@ -0,0 +1,15 @@
<Application xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="PantherByte.App"
xmlns:local="using:PantherByte"
RequestedThemeVariant="Dark">
<!-- "Default" ThemeVariant follows system theme variant. "Dark" or "Light" are other available options. -->
<Application.DataTemplates>
<local:ViewLocator/>
</Application.DataTemplates>
<Application.Styles>
<FluentTheme />
</Application.Styles>
</Application>
+56
View File
@@ -0,0 +1,56 @@
using System;
using System.ComponentModel.Design;
using Avalonia;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Data.Core;
using Avalonia.Data.Core.Plugins;
using System.Linq;
using Avalonia.Markup.Xaml;
using Microsoft.Extensions.DependencyInjection;
using PantherByte.Services;
using PantherByte.ViewModels;
using PantherByte.Views;
namespace PantherByte;
public partial class App : Application {
/// <summary>
/// The services that are available for the application.
/// </summary>
public IServiceProvider? Services { get; private set; }
public new static App? Current => Application.Current as App;
public override void Initialize() {
AvaloniaXamlLoader.Load(this);
}
public override void OnFrameworkInitializationCompleted() {
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) {
// Avoid duplicate validations from both Avalonia and the CommunityToolkit.
// More info: https://docs.avaloniaui.net/docs/guides/development-guides/data-validation#manage-validationplugins
DisableAvaloniaDataAnnotationValidation();
desktop.MainWindow = new MainWindow {
DataContext = new MainWindowViewModel(),
};
var services = new ServiceCollection();
services.AddSingleton<IFileSaveService>(x => new FileService(desktop.MainWindow));
Services = services.BuildServiceProvider();
}
base.OnFrameworkInitializationCompleted();
}
private void DisableAvaloniaDataAnnotationValidation() {
// Get an array of plugins to remove
var dataValidationPluginsToRemove =
BindingPlugins.DataValidators.OfType<DataAnnotationsValidationPlugin>().ToArray();
// remove each entry found
foreach (var plugin in dataValidationPluginsToRemove) {
BindingPlugins.DataValidators.Remove(plugin);
}
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 172 KiB

+31
View File
@@ -0,0 +1,31 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net9.0</TargetFramework>
<Nullable>enable</Nullable>
<BuiltInComInteropSupport>true</BuiltInComInteropSupport>
<ApplicationManifest>app.manifest</ApplicationManifest>
<AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>
</PropertyGroup>
<ItemGroup>
<Folder Include="Models\" />
<AvaloniaResource Include="Assets\**" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Avalonia" Version="11.3.6" />
<PackageReference Include="Avalonia.Desktop" Version="11.3.6" />
<PackageReference Include="Avalonia.ReactiveUI" Version="11.3.6" />
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.3.6" />
<PackageReference Include="Avalonia.Fonts.Inter" Version="11.3.6" />
<!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.-->
<PackageReference Include="Avalonia.Diagnostics" Version="11.3.6">
<IncludeAssets Condition="'$(Configuration)' != 'Debug'">None</IncludeAssets>
<PrivateAssets Condition="'$(Configuration)' != 'Debug'">All</PrivateAssets>
</PackageReference>
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.2.1" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.9" />
<PackageReference Include="ReactiveUI" Version="21.0.1" />
</ItemGroup>
</Project>
+20
View File
@@ -0,0 +1,20 @@
using Avalonia;
using System;
namespace PantherByte;
sealed class Program {
// Initialization code. Don't use any Avalonia, third-party APIs or any
// SynchronizationContext-reliant code before AppMain is called: things aren't initialized
// yet and stuff might break.
[STAThread]
public static void Main(string[] args) => BuildAvaloniaApp()
.StartWithClassicDesktopLifetime(args);
// Avalonia configuration, don't remove; also used by visual designer.
public static AppBuilder BuildAvaloniaApp()
=> AppBuilder.Configure<App>()
.UsePlatformDetect()
.WithInterFont()
.LogToTrace();
}
+25
View File
@@ -0,0 +1,25 @@
using System.Threading.Tasks;
using Avalonia.Controls;
using Avalonia.Platform.Storage;
namespace PantherByte.Services;
public class FileService : IFileSaveService {
private readonly Window _target;
public FileService(Window target) {
_target = target;
}
/// <summary>
/// Opens a file picker for the user and returns the path of the file they'd like to create.
/// </summary>
/// <returns>A filepath to the user's selected save location.</returns>
public async Task<IStorageFolder?> ChooseFolderAsync() {
var folderList = await _target.StorageProvider.OpenFolderPickerAsync(new FolderPickerOpenOptions() {
Title = "Choose Download Location",
});
return folderList[0];
}
}
@@ -0,0 +1,9 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Avalonia.Platform.Storage;
namespace PantherByte;
public interface IFileSaveService {
public Task<IStorageFolder?> ChooseFolderAsync();
}
+26
View File
@@ -0,0 +1,26 @@
using System;
using Avalonia.Controls;
using Avalonia.Controls.Templates;
using PantherByte.ViewModels;
namespace PantherByte;
public class ViewLocator : IDataTemplate {
public Control? Build(object? param) {
if (param is null)
return null;
var name = param.GetType().FullName!.Replace("ViewModel", "View", StringComparison.Ordinal);
var type = Type.GetType(name);
if (type != null) {
return (Control)Activator.CreateInstance(type)!;
}
return new TextBlock { Text = "Not Found: " + name };
}
public bool Match(object? data) {
return data is ViewModelBase;
}
}
@@ -0,0 +1,130 @@
using System;
using System.Collections.Generic;
using System.IO;
using CommunityToolkit.Mvvm.Input;
using System.Threading.Tasks;
using PantherByte.Services;
using ReactiveUI;
namespace PantherByte.ViewModels;
public partial class MainWindowViewModel : ViewModelBase {
private string _link = string.Empty;
private string _saveLocation = string.Empty;
private string _format = "mp3";
private bool _canDownload = true;
/// <summary>
/// The link to the video to download. For now, only use YouTube links.
/// </summary>
public string Link {
get => _link;
set => this.RaiseAndSetIfChanged(ref _link, value);
}
/// <summary>
/// The format to download the video in. Currently supported options are MP3, MP4, and WAV. This will become the
/// argument passed to the "-t" argument in <see cref="GenerateCmdStringAsync"/>.
/// </summary>
public string Format {
get => _format;
set => this.RaiseAndSetIfChanged(ref _format, value);
}
/// <summary>
/// The folder to save the files in. This will become the argument passed to the "-P" argument in
/// <see cref="GenerateCmdStringAsync"/>.
/// </summary>
public string SaveLocation {
get => _saveLocation;
set => this.RaiseAndSetIfChanged(ref _saveLocation, value);
}
/// <summary>
/// Indicates if the file can be downloaded. If not, the download button is disabled.
/// </summary>
public bool CanDownload {
get => _canDownload;
set => this.RaiseAndSetIfChanged(ref _canDownload, value);
}
/// <summary>
/// Constructor. Mainly used for subscribing properties to the changes from other properties.
/// </summary>
public MainWindowViewModel() {
// Subscribe the CanDownload bool to the results of the Link, SaveLocation, and Format property changes.
this.WhenAnyValue(
x => x.Link,
x => x.SaveLocation,
x => x.Format,
(link, saveLocation, format) => !string.IsNullOrWhiteSpace(link)
&& !string.IsNullOrWhiteSpace(format)
&& !string.IsNullOrWhiteSpace(saveLocation)
)
.Subscribe(value => CanDownload = value);
}
/// <summary>
/// Gets the save location for the generated file. We are only allowing users to download one file at a time for now.
/// </summary>
/// <exception cref="NullReferenceException">
/// Thrown when there are no available services that implement <see cref="IFileSaveService"/>, or when the
/// <see cref="Avalonia.Platform.Storage.IStorageFolder"/> returned from the <see cref="IFileSaveService.ChooseFolderAsync"/>
/// method is null.
/// </exception>
/// <exception cref="DirectoryNotFoundException">
/// Thrown when the selected directory path does not exist in the current filesystem.
/// </exception>
[RelayCommand]
private async Task GetSaveLocationAsync() {
try {
var fileService = App.Current?.Services?.GetService(typeof(IFileSaveService));
if (fileService is FileService service) {
var filePath = await service.ChooseFolderAsync();
if (filePath is not null) {
var tempPath = filePath.Path.AbsolutePath;
if (Directory.Exists(tempPath)) {
SaveLocation = tempPath;
Console.WriteLine("Successfully set the file path.");
} else {
throw new DirectoryNotFoundException($"Directory does not exist: {tempPath}");
}
} else {
throw new NullReferenceException("The resulting storage folder was null.");
}
} else {
throw new NullReferenceException("Missing a FileSaveService instance");
}
} catch (Exception ex) {
// Exceptions are caught locally for now, but will eventually require a modal error window.
Console.WriteLine(ex.ToString());
}
}
/// <summary>
/// Generates the arguments used for the main command string.
/// </summary>
[RelayCommand]
private async Task<List<string>> GenerateCmdArgsAsync() {
// The command string format to use: yt-dlp <link> -t <mp3|mp4|wav> -P <path> --no-playlist
// yt-dlp requires usage of ffmpeg as well.
// Code will be executed when the button is clicked.
return await Task.Run(() => new List<string>([Link, "-t", Format, "-P", SaveLocation, "--no-playlist"]));
}
/// <summary>
/// Handles the download process of the file. Generates the command and runs it.
/// </summary>
[RelayCommand]
private async Task DownloadFileAsync() {
// Build the command and run the yt-dlp program by using the generated string.
// Somehow we'll need to package the program in with the application during distribution.
var args = await GenerateCmdArgsAsync();
// Run the command.
try {
System.Diagnostics.Process.Start("yt-dlp", args);
} catch (Exception ex) {
Console.WriteLine(ex.ToString());
}
}
}
+5
View File
@@ -0,0 +1,5 @@
using ReactiveUI;
namespace PantherByte.ViewModels;
public class ViewModelBase : ReactiveObject { }
+84
View File
@@ -0,0 +1,84 @@
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="using:PantherByte.ViewModels"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="PantherByte.Views.MainWindow"
x:DataType="vm:MainWindowViewModel"
Icon="/Assets/avalonia-logo.ico"
Title="PantherByte"
TransparencyLevelHint="AcrylicBlur"
Background="Transparent"
SizeToContent="WidthAndHeight"
CanMaximize="False">
<Design.DataContext>
<!-- This only sets the DataContext for the previewer in an IDE,
to set the actual DataContext for runtime, set the DataContext property in code (look at App.axaml.cs) -->
<vm:MainWindowViewModel/>
</Design.DataContext>
<!-- <TextBlock Text="{Binding Greeting}" HorizontalAlignment="Center" VerticalAlignment="Center"/> -->
<Panel>
<!-- Creates background theme. -->
<ExperimentalAcrylicBorder IsHitTestVisible="False">
<ExperimentalAcrylicBorder.Material>
<ExperimentalAcrylicMaterial
BackgroundSource="Digger"
TintColor="Black"
TintOpacity="1"
MaterialOpacity="0.65"/>
</ExperimentalAcrylicBorder.Material>
</ExperimentalAcrylicBorder>
<Grid
Margin="20"
ShowGridLines="False"
Width="500"
RowDefinitions="Auto, Auto, Auto, Auto, Auto, Auto"
RowSpacing="10">
<TextBlock
Grid.Row="0"
Text="Youtube Video Downloads"
FontSize="30"/>
<TextBlock
Grid.Row="1"
Text="Enter your video link and save location."
FontSize="20"></TextBlock>
<Border Grid.Row="2" BorderBrush="White" BorderThickness="0,0,0,1"/>
<StackPanel Grid.Row="3" Spacing="5">
<TextBlock Text="Save location:"/>
<Grid
ColumnDefinitions="*, 30"
ColumnSpacing="5">
<TextBox
Grid.Column="0"
Text="{Binding SaveLocation}"
Watermark="Choose a folder..."/>
<Button
Grid.Column="1"
Content=">"
HorizontalContentAlignment="Center"
VerticalContentAlignment="Center"
Command="{Binding GetSaveLocationCommand}"/>
</Grid>
</StackPanel>
<StackPanel Grid.Row="4" Spacing="5">
<TextBlock Text="Video link:"/>
<TextBox
Text="{Binding Link}"
Watermark="Paste link here..."/>
</StackPanel>
<Button
Grid.Row="5"
Content="Download"
HorizontalAlignment="Center"
VerticalAlignment="Center"
IsEnabled="{Binding CanDownload}"
Command="{Binding DownloadFileCommand}"/>
</Grid>
</Panel>
</Window>
+9
View File
@@ -0,0 +1,9 @@
using Avalonia.Controls;
namespace PantherByte.Views;
public partial class MainWindow : Window {
public MainWindow() {
InitializeComponent();
}
}
+18
View File
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
<!-- This manifest is used on Windows only.
Don't remove it as it might cause problems with window transparency and embedded controls.
For more details visit https://learn.microsoft.com/en-us/windows/win32/sbscs/application-manifests -->
<assemblyIdentity version="1.0.0.0" name="AvaloniaApplication1.Desktop"/>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<!-- A list of the Windows versions that this application has been tested on
and is designed to work with. Uncomment the appropriate elements
and Windows will automatically select the most compatible environment. -->
<!-- Windows 10 -->
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
</application>
</compatibility>
</assembly>