Modifiche varie alla soluzione
This commit is contained in:
21
Ganimede/App.config
Normal file
21
Ganimede/App.config
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8" ?>
|
||||||
|
<configuration>
|
||||||
|
<configSections>
|
||||||
|
<sectionGroup name="userSettings" type="System.Configuration.UserSettingsGroup, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" >
|
||||||
|
<section name="Ganimede.Properties.Settings" type="System.Configuration.ClientSettingsSection, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" allowExeDefinition="MachineToLocalUser" requirePermission="false" />
|
||||||
|
</sectionGroup>
|
||||||
|
</configSections>
|
||||||
|
<userSettings>
|
||||||
|
<Ganimede.Properties.Settings>
|
||||||
|
<setting name="LastOutputFolder" serializeAs="String">
|
||||||
|
<value />
|
||||||
|
</setting>
|
||||||
|
<setting name="LastVideoPath" serializeAs="String">
|
||||||
|
<value />
|
||||||
|
</setting>
|
||||||
|
<setting name="FFmpegBinFolder" serializeAs="String">
|
||||||
|
<value>C:\Users\alber\source\repos\Ganimede\Ganimede\FFMpeg</value>
|
||||||
|
</setting>
|
||||||
|
</Ganimede.Properties.Settings>
|
||||||
|
</userSettings>
|
||||||
|
</configuration>
|
||||||
@@ -1,14 +1,11 @@
|
|||||||
using System.Configuration;
|
using System.Windows;
|
||||||
using System.Data;
|
|
||||||
using System.Windows;
|
|
||||||
|
|
||||||
namespace Ganimede
|
namespace Ganimede
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Interaction logic for App.xaml
|
/// Interaction logic for App.xaml
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public partial class App : Application
|
public partial class App : System.Windows.Application
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,30 @@
|
|||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<UseWPF>true</UseWPF>
|
<UseWPF>true</UseWPF>
|
||||||
|
<UseWindowsForms>true</UseWindowsForms>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="FFMpegCore" Version="5.2.0" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Reference Include="System.Windows.Forms" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Compile Update="Properties\Settings.Designer.cs">
|
||||||
|
<DesignTimeSharedInput>True</DesignTimeSharedInput>
|
||||||
|
<AutoGen>True</AutoGen>
|
||||||
|
<DependentUpon>Settings.settings</DependentUpon>
|
||||||
|
</Compile>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<None Update="Properties\Settings.settings">
|
||||||
|
<Generator>SettingsSingleFileGenerator</Generator>
|
||||||
|
<LastGenOutput>Settings.Designer.cs</LastGenOutput>
|
||||||
|
</None>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -5,8 +5,44 @@
|
|||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
xmlns:local="clr-namespace:Ganimede"
|
xmlns:local="clr-namespace:Ganimede"
|
||||||
mc:Ignorable="d"
|
mc:Ignorable="d"
|
||||||
Title="MainWindow" Height="450" Width="800">
|
Title="Frame Extractor" Height="600" Width="900"
|
||||||
<Grid>
|
Background="#222">
|
||||||
|
<Grid Margin="40">
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
<RowDefinition Height="*"/>
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="*"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
|
<!-- Drag & Drop Area -->
|
||||||
|
<Border x:Name="DragDropArea" Grid.Row="0" Height="180" CornerRadius="16" BorderBrush="#444" BorderThickness="2" Background="#282828" AllowDrop="True" Drop="DragDropArea_Drop" MouseEnter="DragDropArea_MouseEnter" MouseLeave="DragDropArea_MouseLeave">
|
||||||
|
<TextBlock Text="Drag and drop video here" Foreground="#AAA" FontSize="22" HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
<!-- Buttons -->
|
||||||
|
<StackPanel Grid.Row="1" Orientation="Horizontal" HorizontalAlignment="Center" Margin="0,24,0,0" >
|
||||||
|
<Button x:Name="BrowseVideoButton" Content="Browse Video" Width="160" Height="48" Margin="0,0,16,0" Click="BrowseVideoButton_Click"/>
|
||||||
|
<Button x:Name="SelectOutputFolderButton" Content="Select Output Folder" Width="160" Height="48" Click="SelectOutputFolderButton_Click"/>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<!-- Extract Frames Button -->
|
||||||
|
<Button Grid.Row="2" x:Name="ExtractFramesButton" Content="Extract Frames" Width="220" Height="54" HorizontalAlignment="Center" Margin="0,24,0,0" Click="ExtractFramesButton_Click"/>
|
||||||
|
|
||||||
|
<!-- Progress & Thumbnails -->
|
||||||
|
<StackPanel Grid.Row="3" Margin="0,32,0,0">
|
||||||
|
<ProgressBar x:Name="ProgressBar" Height="24" Minimum="0" Maximum="100" Value="0" Background="#333" Foreground="#4FC3F7"/>
|
||||||
|
<TextBlock x:Name="StatusText" Foreground="#AAA" FontSize="16" Margin="0,12,0,0"/>
|
||||||
|
<ItemsControl x:Name="ThumbnailsPanel" Margin="0,24,0,0" Height="120" HorizontalAlignment="Center">
|
||||||
|
<ItemsControl.ItemsPanel>
|
||||||
|
<ItemsPanelTemplate>
|
||||||
|
<WrapPanel Orientation="Horizontal"/>
|
||||||
|
</ItemsPanelTemplate>
|
||||||
|
</ItemsControl.ItemsPanel>
|
||||||
|
</ItemsControl>
|
||||||
|
</StackPanel>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Window>
|
</Window>
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
using System.Text;
|
using System;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using System.IO;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
using System.Windows.Controls;
|
using System.Windows.Controls;
|
||||||
using System.Windows.Data;
|
|
||||||
using System.Windows.Documents;
|
|
||||||
using System.Windows.Input;
|
|
||||||
using System.Windows.Media;
|
|
||||||
using System.Windows.Media.Imaging;
|
using System.Windows.Media.Imaging;
|
||||||
using System.Windows.Navigation;
|
using Microsoft.Win32;
|
||||||
using System.Windows.Shapes;
|
using FFMpegCore;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using Ganimede.Properties;
|
||||||
|
|
||||||
namespace Ganimede
|
namespace Ganimede
|
||||||
{
|
{
|
||||||
@@ -16,9 +17,173 @@ namespace Ganimede
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public partial class MainWindow : Window
|
public partial class MainWindow : Window
|
||||||
{
|
{
|
||||||
|
private string? videoPath;
|
||||||
|
private string? outputFolder;
|
||||||
|
private ObservableCollection<BitmapImage> thumbnails = new();
|
||||||
|
|
||||||
public MainWindow()
|
public MainWindow()
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
ThumbnailsPanel.ItemsSource = thumbnails;
|
||||||
|
Debug.WriteLine("[INIT] MainWindow initialized.");
|
||||||
|
|
||||||
|
// Carica i percorsi salvati
|
||||||
|
outputFolder = Settings.Default.LastOutputFolder;
|
||||||
|
videoPath = Settings.Default.LastVideoPath;
|
||||||
|
if (!string.IsNullOrEmpty(outputFolder))
|
||||||
|
StatusText.Text = $"Last output folder: {outputFolder}";
|
||||||
|
if (!string.IsNullOrEmpty(videoPath))
|
||||||
|
StatusText.Text += $"\nLast video: {System.IO.Path.GetFileName(videoPath)}";
|
||||||
|
|
||||||
|
// Configura FFMpegCore con percorso binari
|
||||||
|
var ffmpegBin = Settings.Default.FFmpegBinFolder;
|
||||||
|
if (!string.IsNullOrEmpty(ffmpegBin) && Directory.Exists(ffmpegBin))
|
||||||
|
{
|
||||||
|
FFMpegCore.GlobalFFOptions.Configure(options => options.BinaryFolder = ffmpegBin);
|
||||||
|
Debug.WriteLine($"[CONFIG] FFMpeg bin folder set: {ffmpegBin}");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
StatusText.Text += "\n[WARNING] ffmpeg/ffprobe path not set. Configure in settings.";
|
||||||
|
Debug.WriteLine("[WARNING] ffmpeg/ffprobe path not set or invalid.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void BrowseVideoButton_Click(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
Debug.WriteLine("[UI] BrowseVideoButton_Click invoked.");
|
||||||
|
var dialog = new Microsoft.Win32.OpenFileDialog { Filter = "Video files (*.mp4;*.avi;*.mov)|*.mp4;*.avi;*.mov|All files (*.*)|*.*" };
|
||||||
|
if (dialog.ShowDialog() == true)
|
||||||
|
{
|
||||||
|
videoPath = dialog.FileName;
|
||||||
|
StatusText.Text = $"Selected video: {Path.GetFileName(videoPath)}";
|
||||||
|
Settings.Default.LastVideoPath = videoPath;
|
||||||
|
Settings.Default.Save();
|
||||||
|
Debug.WriteLine($"[INFO] Video selected: {videoPath}");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Debug.WriteLine("[INFO] Video selection cancelled.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SelectOutputFolderButton_Click(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
Debug.WriteLine("[UI] SelectOutputFolderButton_Click invoked.");
|
||||||
|
using (var dialog = new System.Windows.Forms.FolderBrowserDialog())
|
||||||
|
{
|
||||||
|
if (dialog.ShowDialog() == System.Windows.Forms.DialogResult.OK)
|
||||||
|
{
|
||||||
|
outputFolder = dialog.SelectedPath;
|
||||||
|
StatusText.Text = $"Selected output folder: {outputFolder}";
|
||||||
|
Settings.Default.LastOutputFolder = outputFolder;
|
||||||
|
Settings.Default.Save();
|
||||||
|
Debug.WriteLine($"[INFO] Output folder selected: {outputFolder}");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Debug.WriteLine("[INFO] Output folder selection cancelled.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void ExtractFramesButton_Click(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
Debug.WriteLine("[UI] ExtractFramesButton_Click invoked.");
|
||||||
|
if (string.IsNullOrEmpty(videoPath) || string.IsNullOrEmpty(outputFolder))
|
||||||
|
{
|
||||||
|
StatusText.Text = "Please select a video and output folder.";
|
||||||
|
Debug.WriteLine("[ERROR] Video path or output folder not set.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ExtractFramesButton.IsEnabled = false;
|
||||||
|
ProgressBar.Value = 0;
|
||||||
|
thumbnails.Clear();
|
||||||
|
StatusText.Text = "Analyzing video...";
|
||||||
|
Debug.WriteLine($"[PROCESS] Starting analysis for video: {videoPath}");
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var mediaInfo = await FFProbe.AnalyseAsync(videoPath);
|
||||||
|
Debug.WriteLine($"[INFO] Video duration: {mediaInfo.Duration}, FrameRate: 24");
|
||||||
|
int frameRate = 24;
|
||||||
|
int frameCount = (int)mediaInfo.Duration.TotalSeconds * frameRate;
|
||||||
|
Debug.WriteLine($"[INFO] Total frames to extract: {frameCount}");
|
||||||
|
for (int i = 0; i < frameCount; i++)
|
||||||
|
{
|
||||||
|
var frameTime = TimeSpan.FromSeconds((double)i / frameRate);
|
||||||
|
string framePath = Path.Combine(outputFolder, $"frame_{i}.png");
|
||||||
|
Debug.WriteLine($"[PROCESS] Extracting frame {i + 1}/{frameCount} at {frameTime}");
|
||||||
|
await FFMpegArguments
|
||||||
|
.FromFileInput(videoPath)
|
||||||
|
.OutputToFile(framePath, false, options => options
|
||||||
|
.Seek(frameTime)
|
||||||
|
.WithFrameOutputCount(1)
|
||||||
|
.ForceFormat("png")
|
||||||
|
.Resize(320, 180))
|
||||||
|
.ProcessAsynchronously();
|
||||||
|
if (File.Exists(framePath))
|
||||||
|
{
|
||||||
|
var bitmap = new BitmapImage();
|
||||||
|
bitmap.BeginInit();
|
||||||
|
bitmap.UriSource = new Uri(framePath);
|
||||||
|
bitmap.CacheOption = BitmapCacheOption.OnLoad;
|
||||||
|
bitmap.EndInit();
|
||||||
|
thumbnails.Add(bitmap);
|
||||||
|
Debug.WriteLine($"[INFO] Frame saved and loaded: {framePath}");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Debug.WriteLine($"[ERROR] Frame file not found: {framePath}");
|
||||||
|
}
|
||||||
|
ProgressBar.Value = (i + 1) * 100 / frameCount;
|
||||||
|
StatusText.Text = $"Extracting frames {i + 1}/{frameCount} ({ProgressBar.Value}%) - Processing frame {i + 1}.";
|
||||||
|
}
|
||||||
|
StatusText.Text = "Extraction complete!";
|
||||||
|
Debug.WriteLine("[SUCCESS] Extraction complete.");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
StatusText.Text = $"Error: {ex.Message}";
|
||||||
|
Debug.WriteLine($"[EXCEPTION] {ex.GetType()}: {ex.Message}\n{ex.StackTrace}");
|
||||||
|
}
|
||||||
|
ExtractFramesButton.IsEnabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DragDropArea_Drop(object sender, System.Windows.DragEventArgs e)
|
||||||
|
{
|
||||||
|
Debug.WriteLine("[UI] DragDropArea_Drop invoked.");
|
||||||
|
if (e.Data.GetDataPresent(System.Windows.DataFormats.FileDrop))
|
||||||
|
{
|
||||||
|
var files = (string[])e.Data.GetData(System.Windows.DataFormats.FileDrop);
|
||||||
|
if (files.Length > 0)
|
||||||
|
{
|
||||||
|
videoPath = files[0];
|
||||||
|
StatusText.Text = $"Selected video: {Path.GetFileName(videoPath)}";
|
||||||
|
Settings.Default.LastVideoPath = videoPath;
|
||||||
|
Settings.Default.Save();
|
||||||
|
Debug.WriteLine($"[INFO] Video selected via drag & drop: {videoPath}");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Debug.WriteLine("[WARN] Drag & drop did not contain files.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Debug.WriteLine("[WARN] Drag & drop did not contain file drop format.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DragDropArea_MouseEnter(object sender, System.Windows.Input.MouseEventArgs e)
|
||||||
|
{
|
||||||
|
DragDropArea.BorderBrush = new System.Windows.Media.SolidColorBrush(System.Windows.Media.Color.FromRgb(79, 195, 247));
|
||||||
|
Debug.WriteLine("[UI] DragDropArea_MouseEnter");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DragDropArea_MouseLeave(object sender, System.Windows.Input.MouseEventArgs e)
|
||||||
|
{
|
||||||
|
DragDropArea.BorderBrush = new System.Windows.Media.SolidColorBrush(System.Windows.Media.Color.FromRgb(68, 68, 68));
|
||||||
|
Debug.WriteLine("[UI] DragDropArea_MouseLeave");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
4
Ganimede/Models/.keep
Normal file
4
Ganimede/Models/.keep
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
namespace Ganimede.Models
|
||||||
|
{
|
||||||
|
// Modelli futuri (es. VideoInfo, FrameInfo)
|
||||||
|
}
|
||||||
62
Ganimede/Properties/Settings.Designer.cs
generated
Normal file
62
Ganimede/Properties/Settings.Designer.cs
generated
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// <auto-generated>
|
||||||
|
// Il codice è stato generato da uno strumento.
|
||||||
|
// Versione runtime:4.0.30319.42000
|
||||||
|
//
|
||||||
|
// Le modifiche apportate a questo file possono provocare un comportamento non corretto e andranno perse se
|
||||||
|
// il codice viene rigenerato.
|
||||||
|
// </auto-generated>
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
namespace Ganimede.Properties {
|
||||||
|
|
||||||
|
|
||||||
|
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||||
|
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "17.14.0.0")]
|
||||||
|
internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
|
||||||
|
|
||||||
|
private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
|
||||||
|
|
||||||
|
public static Settings Default {
|
||||||
|
get {
|
||||||
|
return defaultInstance;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[global::System.Configuration.UserScopedSettingAttribute()]
|
||||||
|
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||||
|
[global::System.Configuration.DefaultSettingValueAttribute("")]
|
||||||
|
public string LastOutputFolder {
|
||||||
|
get {
|
||||||
|
return ((string)(this["LastOutputFolder"]));
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
this["LastOutputFolder"] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[global::System.Configuration.UserScopedSettingAttribute()]
|
||||||
|
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||||
|
[global::System.Configuration.DefaultSettingValueAttribute("")]
|
||||||
|
public string LastVideoPath {
|
||||||
|
get {
|
||||||
|
return ((string)(this["LastVideoPath"]));
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
this["LastVideoPath"] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[global::System.Configuration.UserScopedSettingAttribute()]
|
||||||
|
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||||
|
[global::System.Configuration.DefaultSettingValueAttribute("C:\\Users\\alber\\source\\repos\\Ganimede\\Ganimede\\FFMpeg")]
|
||||||
|
public string FFmpegBinFolder {
|
||||||
|
get {
|
||||||
|
return ((string)(this["FFmpegBinFolder"]));
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
this["FFmpegBinFolder"] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
15
Ganimede/Properties/Settings.settings
Normal file
15
Ganimede/Properties/Settings.settings
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
<?xml version='1.0' encoding='iso-8859-1'?>
|
||||||
|
<SettingsFile xmlns="http://schemas.microsoft.com/VisualStudio/2004/01/settings" CurrentProfile="(Default)" GeneratedClassNamespace="Ganimede.Properties" GeneratedClassName="Settings">
|
||||||
|
<Profiles />
|
||||||
|
<Settings>
|
||||||
|
<Setting Name="LastOutputFolder" Type="System.String" Scope="User">
|
||||||
|
<Value Profile="(Default)" />
|
||||||
|
</Setting>
|
||||||
|
<Setting Name="LastVideoPath" Type="System.String" Scope="User">
|
||||||
|
<Value Profile="(Default)" />
|
||||||
|
</Setting>
|
||||||
|
<Setting Name="FFmpegBinFolder" Type="System.String" Scope="User">
|
||||||
|
<Value Profile="(Default)">C:\Users\alber\source\repos\Ganimede\Ganimede\FFMpeg</Value>
|
||||||
|
</Setting>
|
||||||
|
</Settings>
|
||||||
|
</SettingsFile>
|
||||||
4
Ganimede/ViewModels/.keep
Normal file
4
Ganimede/ViewModels/.keep
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
namespace Ganimede.ViewModels
|
||||||
|
{
|
||||||
|
// ViewModel principale e futuri ViewModel
|
||||||
|
}
|
||||||
4
Ganimede/Views/.keep
Normal file
4
Ganimede/Views/.keep
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
namespace Ganimede.Views
|
||||||
|
{
|
||||||
|
// Views aggiuntive se necessario
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user