Move files for better organization
This commit is contained in:
+128
@@ -0,0 +1,128 @@
|
||||
<Application xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
x:Class="ReceiptPDFBuilder.App"
|
||||
xmlns:viewModels="clr-namespace:ReceiptPDFBuilder.ViewModels"
|
||||
xmlns:views="clr-namespace:ReceiptPDFBuilder.Views"
|
||||
xmlns:dialogHostAvalonia="clr-namespace:DialogHostAvalonia;assembly=DialogHost.Avalonia"
|
||||
xmlns:behaviors="clr-namespace:DialogHostAvalonia;assembly=DialogHost.Avalonia"
|
||||
xmlns:helpers="clr-namespace:ReceiptPDFBuilder.Helpers"
|
||||
RequestedThemeVariant="Default"
|
||||
Name="MayShow">
|
||||
<Application.Styles>
|
||||
<FluentTheme />
|
||||
<StyleInclude Source="avares://AvaloniaProgressRing/Styles/ProgressRing.xaml"/>
|
||||
<StyleInclude Source="avares://Avalonia.Controls.DataGrid/Themes/Fluent.xaml"/>
|
||||
<dialogHostAvalonia:DialogHostStyles />
|
||||
<StyleInclude Source="avares://Xaml.Behaviors.Interactions.DragAndDrop/Styles.axaml" />
|
||||
<StyleInclude Source="avares://Xaml.Behaviors.Interactions.DragAndDrop.DataGrid/Styles.axaml" />
|
||||
|
||||
<Style Selector="DataGrid.DragAndDrop">
|
||||
<Style.Resources>
|
||||
<helpers:DataGridDropHandler x:Key="DataGridDropHandler" />
|
||||
</Style.Resources>
|
||||
<Setter
|
||||
Property="RowHeaderWidth"
|
||||
Value="24" />
|
||||
<Setter Property="(Interaction.Behaviors)">
|
||||
<BehaviorCollectionTemplate>
|
||||
<BehaviorCollection>
|
||||
<ContextDropBehavior Handler="{StaticResource DataGridDropHandler}" />
|
||||
</BehaviorCollection>
|
||||
</BehaviorCollectionTemplate>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<!-- This makes only the DataGridRowHeader available for dragging, instead of making the entire row draggable -->
|
||||
<!-- Which prevents a conflict between text selection in a cell and drag-and-drop -->
|
||||
<Style Selector="DataGrid.DragAndDrop DataGridRowHeader">
|
||||
<Setter Property="(Interaction.Behaviors)">
|
||||
<BehaviorCollectionTemplate>
|
||||
<BehaviorCollection>
|
||||
<ContextDragBehavior HorizontalDragThreshold="3" VerticalDragThreshold="3" />
|
||||
</BehaviorCollection>
|
||||
</BehaviorCollectionTemplate>
|
||||
</Setter>
|
||||
<Setter Property="Content">
|
||||
<Template>
|
||||
<Image
|
||||
Margin="12,0,12,0"
|
||||
Width="12"
|
||||
Height="12"
|
||||
VerticalAlignment="Center"
|
||||
HorizontalAlignment="Center">
|
||||
<Image.Source>
|
||||
<!-- Use your own image here, I used this: https://www.svgrepo.com/svg/347759/grabber -->
|
||||
<DrawingImage Drawing="{StaticResource IconGrabber}" />
|
||||
</Image.Source>
|
||||
</Image>
|
||||
</Template>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style Selector="DataGrid.ItemsDragAndDrop">
|
||||
<Style.Resources>
|
||||
<helpers:DataGridDropHandler x:Key="DataGridDropHandler" />
|
||||
</Style.Resources>
|
||||
<Setter Property="(Interaction.Behaviors)">
|
||||
<BehaviorCollectionTemplate>
|
||||
<BehaviorCollection>
|
||||
<ContextDropBehavior Handler="{StaticResource DataGridDropHandler}" />
|
||||
</BehaviorCollection>
|
||||
</BehaviorCollectionTemplate>
|
||||
</Setter>
|
||||
</Style>
|
||||
<Style Selector="Button.Danger">
|
||||
<Setter Property="Foreground" Value="White"/>
|
||||
<Setter Property="Background" Value="#b00202"/>
|
||||
<Style Selector="^:pointerover /template/ ContentPresenter#PART_ContentPresenter">
|
||||
<Setter Property="Foreground" Value="White"/>
|
||||
<Setter Property="Background" Value="#bd0000"/>
|
||||
</Style>
|
||||
<Style Selector="^:pressed /template/ ContentPresenter#PART_ContentPresenter">
|
||||
<Setter Property="Foreground" Value="White"/>
|
||||
<Setter Property="Background" Value="#820202"/>
|
||||
</Style>
|
||||
</Style>
|
||||
</Application.Styles>
|
||||
<Application.DataTemplates>
|
||||
<DataTemplate DataType="{x:Type viewModels:MainViewModel}">
|
||||
<views:MainView/>
|
||||
</DataTemplate>
|
||||
<DataTemplate DataType="{x:Type viewModels:WarningDeleteItemModel}">
|
||||
<views:WarningDeleteItem/>
|
||||
</DataTemplate>
|
||||
<DataTemplate DataType="{x:Type viewModels:EditFileViewModel}">
|
||||
<views:EditFile/>
|
||||
</DataTemplate>
|
||||
<DataTemplate DataType="{x:Type viewModels:AboutViewModel}">
|
||||
<views:AboutView/>
|
||||
</DataTemplate>
|
||||
<DataTemplate DataType="{x:Type viewModels:WarningViewModel}">
|
||||
<views:WarningView/>
|
||||
</DataTemplate>
|
||||
</Application.DataTemplates>
|
||||
<Application.Resources>
|
||||
<ResourceDictionary>
|
||||
<helpers:DataGridDropHandler x:Key="DataGridDropHandler" />
|
||||
<ResourceDictionary.ThemeDictionaries>
|
||||
<ResourceDictionary x:Key='Light'>
|
||||
<SolidColorBrush x:Key='DragDropBrush'>Black</SolidColorBrush>
|
||||
</ResourceDictionary>
|
||||
<ResourceDictionary x:Key='Dark'>
|
||||
<SolidColorBrush x:Key='DragDropBrush'>White</SolidColorBrush>
|
||||
</ResourceDictionary>
|
||||
</ResourceDictionary.ThemeDictionaries>
|
||||
<!-- IconGrabber, MIT License, author: Primer, taken from: https://www.svgrepo.com/svg/347759/grabber -->
|
||||
<PathGeometry x:Key="IconGrabberGeometry">M15 18a1 1 0 100-2 1 1 0 000 2zm1-6a1 1 0 11-2 0 1 1 0 012 0zm-7 6a1 1 0 100-2 1 1 0 000 2zm0-5a1 1 0 100-2 1 1 0 000 2zm7-6a1 1 0 11-2 0 1 1 0 012 0zM9 8a1 1 0 100-2 1 1 0 000 2z</PathGeometry>
|
||||
<GeometryDrawing x:Key="IconGrabber" Brush="{DynamicResource DragDropBrush}" Geometry="{StaticResource IconGrabberGeometry}" />
|
||||
|
||||
<FontFamily x:Key="FontAwesomeRegular">avares://MayShow/Assets/Fonts/FontAwesome/Font Awesome 7 Free-Regular-400.otf#Font Awesome 7 Free Regular</FontFamily>
|
||||
<FontFamily x:Key="FontAwesomeSolid">avares://MayShow/Assets/Fonts/FontAwesome/Font Awesome 7 Free-Solid-900.otf#Font Awesome 7 Free Solid</FontFamily>
|
||||
</ResourceDictionary>
|
||||
</Application.Resources>
|
||||
<NativeMenu.Menu>
|
||||
<NativeMenu>
|
||||
<NativeMenuItem Header="About ReceiptBuilder" Click="AboutOnClick" />
|
||||
</NativeMenu>
|
||||
</NativeMenu.Menu>
|
||||
</Application>
|
||||
@@ -0,0 +1,33 @@
|
||||
using System;
|
||||
using Avalonia;
|
||||
using Avalonia.Controls.ApplicationLifetimes;
|
||||
using Avalonia.Interactivity;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using DialogHostAvalonia;
|
||||
using ReceiptPDFBuilder;
|
||||
using ReceiptPDFBuilder.ViewModels;
|
||||
|
||||
namespace ReceiptPDFBuilder;
|
||||
|
||||
public partial class App : Application
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
|
||||
public override void OnFrameworkInitializationCompleted()
|
||||
{
|
||||
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
||||
{
|
||||
desktop.MainWindow = new MainWindow();
|
||||
}
|
||||
|
||||
base.OnFrameworkInitializationCompleted();
|
||||
}
|
||||
|
||||
public void AboutOnClick(object? sender, EventArgs args)
|
||||
{
|
||||
DialogHost.Show(new AboutViewModel());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<AvaloniaVersion>11.3.12</AvaloniaVersion>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,165 @@
|
||||
Fonticons, Inc. (https://fontawesome.com)
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
Font Awesome Free License
|
||||
|
||||
Font Awesome Free is free, open source, and GPL friendly. You can use it for
|
||||
commercial projects, open source projects, or really almost whatever you want.
|
||||
Full Font Awesome Free license: https://fontawesome.com/license/free.
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
# Icons: CC BY 4.0 License (https://creativecommons.org/licenses/by/4.0/)
|
||||
|
||||
The Font Awesome Free download is licensed under a Creative Commons
|
||||
Attribution 4.0 International License and applies to all icons packaged
|
||||
as SVG and JS file types.
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
# Fonts: SIL OFL 1.1 License
|
||||
|
||||
In the Font Awesome Free download, the SIL OFL license applies to all icons
|
||||
packaged as web and desktop font files.
|
||||
|
||||
Copyright (c) 2026 Fonticons, Inc. (https://fontawesome.com)
|
||||
with Reserved Font Name: "Font Awesome".
|
||||
|
||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||
This license is copied below, and is also available with a FAQ at:
|
||||
http://scripts.sil.org/OFL
|
||||
|
||||
SIL OPEN FONT LICENSE
|
||||
Version 1.1 - 26 February 2007
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font creation
|
||||
efforts of academic and linguistic communities, and to provide a free and
|
||||
open framework in which fonts may be shared and improved in partnership
|
||||
with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply
|
||||
to any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software components as
|
||||
distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||
or substituting — in part or in whole — any of the components of the
|
||||
Original Version, by changing formats or by porting the Font Software to a
|
||||
new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||
redistribute, and sell modified and unmodified copies of the Font
|
||||
Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components,
|
||||
in Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the corresponding
|
||||
Copyright Holder. This restriction only applies to the primary font name as
|
||||
presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created
|
||||
using the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
# Code: MIT License (https://opensource.org/licenses/MIT)
|
||||
|
||||
In the Font Awesome Free download, the MIT license applies to all non-font and
|
||||
non-icon files.
|
||||
|
||||
Copyright 2026 Fonticons, Inc.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in the
|
||||
Software without restriction, including without limitation the rights to use, copy,
|
||||
modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
and to permit persons to whom the Software is furnished to do so, subject to the
|
||||
following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
||||
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
# Attribution
|
||||
|
||||
Attribution is required by MIT, SIL OFL, and CC BY licenses. Downloaded Font
|
||||
Awesome Free files already contain embedded comments with sufficient
|
||||
attribution, so you shouldn't need to do anything additional when using these
|
||||
files normally.
|
||||
|
||||
We've kept attribution comments terse, so we ask that you do not actively work
|
||||
to remove them from files, especially code. They're a great way for folks to
|
||||
learn about Font Awesome.
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
# Brand Icons
|
||||
|
||||
All brand icons are trademarks of their respective owners. The use of these
|
||||
trademarks does not indicate endorsement of the trademark holder by Font
|
||||
Awesome, nor vice versa. **Please do not use brand logos for any purpose except
|
||||
to represent the company, product, or service to which they refer.**
|
||||
@@ -0,0 +1,93 @@
|
||||
Copyright 2022 The Noto Project Authors (https://github.com/notofonts/latin-greek-cyrillic)
|
||||
|
||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||
This license is copied below, and is also available with a FAQ at:
|
||||
https://openfontlicense.org
|
||||
|
||||
|
||||
-----------------------------------------------------------
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
-----------------------------------------------------------
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font creation
|
||||
efforts of academic and linguistic communities, and to provide a free and
|
||||
open framework in which fonts may be shared and improved in partnership
|
||||
with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply
|
||||
to any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software components as
|
||||
distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||
or substituting -- in part or in whole -- any of the components of the
|
||||
Original Version, by changing formats or by porting the Font Software to a
|
||||
new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||
redistribute, and sell modified and unmodified copies of the Font
|
||||
Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components,
|
||||
in Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the corresponding
|
||||
Copyright Holder. This restriction only applies to the primary font name as
|
||||
presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created
|
||||
using the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||
@@ -0,0 +1,136 @@
|
||||
Noto Sans Variable Font
|
||||
=======================
|
||||
|
||||
This download contains Noto Sans as both variable fonts and static fonts.
|
||||
|
||||
Noto Sans is a variable font with these axes:
|
||||
wdth
|
||||
wght
|
||||
|
||||
This means all the styles are contained in these files:
|
||||
Noto_Sans/NotoSans-VariableFont_wdth,wght.ttf
|
||||
Noto_Sans/NotoSans-Italic-VariableFont_wdth,wght.ttf
|
||||
|
||||
If your app fully supports variable fonts, you can now pick intermediate styles
|
||||
that aren’t available as static fonts. Not all apps support variable fonts, and
|
||||
in those cases you can use the static font files for Noto Sans:
|
||||
Noto_Sans/static/NotoSans_ExtraCondensed-Thin.ttf
|
||||
Noto_Sans/static/NotoSans_ExtraCondensed-ExtraLight.ttf
|
||||
Noto_Sans/static/NotoSans_ExtraCondensed-Light.ttf
|
||||
Noto_Sans/static/NotoSans_ExtraCondensed-Regular.ttf
|
||||
Noto_Sans/static/NotoSans_ExtraCondensed-Medium.ttf
|
||||
Noto_Sans/static/NotoSans_ExtraCondensed-SemiBold.ttf
|
||||
Noto_Sans/static/NotoSans_ExtraCondensed-Bold.ttf
|
||||
Noto_Sans/static/NotoSans_ExtraCondensed-ExtraBold.ttf
|
||||
Noto_Sans/static/NotoSans_ExtraCondensed-Black.ttf
|
||||
Noto_Sans/static/NotoSans_Condensed-Thin.ttf
|
||||
Noto_Sans/static/NotoSans_Condensed-ExtraLight.ttf
|
||||
Noto_Sans/static/NotoSans_Condensed-Light.ttf
|
||||
Noto_Sans/static/NotoSans_Condensed-Regular.ttf
|
||||
Noto_Sans/static/NotoSans_Condensed-Medium.ttf
|
||||
Noto_Sans/static/NotoSans_Condensed-SemiBold.ttf
|
||||
Noto_Sans/static/NotoSans_Condensed-Bold.ttf
|
||||
Noto_Sans/static/NotoSans_Condensed-ExtraBold.ttf
|
||||
Noto_Sans/static/NotoSans_Condensed-Black.ttf
|
||||
Noto_Sans/static/NotoSans_SemiCondensed-Thin.ttf
|
||||
Noto_Sans/static/NotoSans_SemiCondensed-ExtraLight.ttf
|
||||
Noto_Sans/static/NotoSans_SemiCondensed-Light.ttf
|
||||
Noto_Sans/static/NotoSans_SemiCondensed-Regular.ttf
|
||||
Noto_Sans/static/NotoSans_SemiCondensed-Medium.ttf
|
||||
Noto_Sans/static/NotoSans_SemiCondensed-SemiBold.ttf
|
||||
Noto_Sans/static/NotoSans_SemiCondensed-Bold.ttf
|
||||
Noto_Sans/static/NotoSans_SemiCondensed-ExtraBold.ttf
|
||||
Noto_Sans/static/NotoSans_SemiCondensed-Black.ttf
|
||||
Noto_Sans/static/NotoSans-Thin.ttf
|
||||
Noto_Sans/static/NotoSans-ExtraLight.ttf
|
||||
Noto_Sans/static/NotoSans-Light.ttf
|
||||
Noto_Sans/static/NotoSans-Regular.ttf
|
||||
Noto_Sans/static/NotoSans-Medium.ttf
|
||||
Noto_Sans/static/NotoSans-SemiBold.ttf
|
||||
Noto_Sans/static/NotoSans-Bold.ttf
|
||||
Noto_Sans/static/NotoSans-ExtraBold.ttf
|
||||
Noto_Sans/static/NotoSans-Black.ttf
|
||||
Noto_Sans/static/NotoSans_ExtraCondensed-ThinItalic.ttf
|
||||
Noto_Sans/static/NotoSans_ExtraCondensed-ExtraLightItalic.ttf
|
||||
Noto_Sans/static/NotoSans_ExtraCondensed-LightItalic.ttf
|
||||
Noto_Sans/static/NotoSans_ExtraCondensed-Italic.ttf
|
||||
Noto_Sans/static/NotoSans_ExtraCondensed-MediumItalic.ttf
|
||||
Noto_Sans/static/NotoSans_ExtraCondensed-SemiBoldItalic.ttf
|
||||
Noto_Sans/static/NotoSans_ExtraCondensed-BoldItalic.ttf
|
||||
Noto_Sans/static/NotoSans_ExtraCondensed-ExtraBoldItalic.ttf
|
||||
Noto_Sans/static/NotoSans_ExtraCondensed-BlackItalic.ttf
|
||||
Noto_Sans/static/NotoSans_Condensed-ThinItalic.ttf
|
||||
Noto_Sans/static/NotoSans_Condensed-ExtraLightItalic.ttf
|
||||
Noto_Sans/static/NotoSans_Condensed-LightItalic.ttf
|
||||
Noto_Sans/static/NotoSans_Condensed-Italic.ttf
|
||||
Noto_Sans/static/NotoSans_Condensed-MediumItalic.ttf
|
||||
Noto_Sans/static/NotoSans_Condensed-SemiBoldItalic.ttf
|
||||
Noto_Sans/static/NotoSans_Condensed-BoldItalic.ttf
|
||||
Noto_Sans/static/NotoSans_Condensed-ExtraBoldItalic.ttf
|
||||
Noto_Sans/static/NotoSans_Condensed-BlackItalic.ttf
|
||||
Noto_Sans/static/NotoSans_SemiCondensed-ThinItalic.ttf
|
||||
Noto_Sans/static/NotoSans_SemiCondensed-ExtraLightItalic.ttf
|
||||
Noto_Sans/static/NotoSans_SemiCondensed-LightItalic.ttf
|
||||
Noto_Sans/static/NotoSans_SemiCondensed-Italic.ttf
|
||||
Noto_Sans/static/NotoSans_SemiCondensed-MediumItalic.ttf
|
||||
Noto_Sans/static/NotoSans_SemiCondensed-SemiBoldItalic.ttf
|
||||
Noto_Sans/static/NotoSans_SemiCondensed-BoldItalic.ttf
|
||||
Noto_Sans/static/NotoSans_SemiCondensed-ExtraBoldItalic.ttf
|
||||
Noto_Sans/static/NotoSans_SemiCondensed-BlackItalic.ttf
|
||||
Noto_Sans/static/NotoSans-ThinItalic.ttf
|
||||
Noto_Sans/static/NotoSans-ExtraLightItalic.ttf
|
||||
Noto_Sans/static/NotoSans-LightItalic.ttf
|
||||
Noto_Sans/static/NotoSans-Italic.ttf
|
||||
Noto_Sans/static/NotoSans-MediumItalic.ttf
|
||||
Noto_Sans/static/NotoSans-SemiBoldItalic.ttf
|
||||
Noto_Sans/static/NotoSans-BoldItalic.ttf
|
||||
Noto_Sans/static/NotoSans-ExtraBoldItalic.ttf
|
||||
Noto_Sans/static/NotoSans-BlackItalic.ttf
|
||||
|
||||
Get started
|
||||
-----------
|
||||
|
||||
1. Install the font files you want to use
|
||||
|
||||
2. Use your app's font picker to view the font family and all the
|
||||
available styles
|
||||
|
||||
Learn more about variable fonts
|
||||
-------------------------------
|
||||
|
||||
https://developers.google.com/web/fundamentals/design-and-ux/typography/variable-fonts
|
||||
https://variablefonts.typenetwork.com
|
||||
https://medium.com/variable-fonts
|
||||
|
||||
In desktop apps
|
||||
|
||||
https://theblog.adobe.com/can-variable-fonts-illustrator-cc
|
||||
https://helpx.adobe.com/nz/photoshop/using/fonts.html#variable_fonts
|
||||
|
||||
Online
|
||||
|
||||
https://developers.google.com/fonts/docs/getting_started
|
||||
https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Fonts/Variable_Fonts_Guide
|
||||
https://developer.microsoft.com/en-us/microsoft-edge/testdrive/demos/variable-fonts
|
||||
|
||||
Installing fonts
|
||||
|
||||
MacOS: https://support.apple.com/en-us/HT201749
|
||||
Linux: https://www.google.com/search?q=how+to+install+a+font+on+gnu%2Blinux
|
||||
Windows: https://support.microsoft.com/en-us/help/314960/how-to-install-or-remove-a-font-in-windows
|
||||
|
||||
Android Apps
|
||||
|
||||
https://developers.google.com/fonts/docs/android
|
||||
https://developer.android.com/guide/topics/ui/look-and-feel/downloadable-fonts
|
||||
|
||||
License
|
||||
-------
|
||||
Please read the full license text (OFL.txt) to understand the permissions,
|
||||
restrictions and requirements for usage, redistribution, and modification.
|
||||
|
||||
You can use them in your products & projects – print or digital,
|
||||
commercial or otherwise.
|
||||
|
||||
This isn't legal advice, please consider consulting a lawyer and see the full
|
||||
license for all details.
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,93 @@
|
||||
Copyright 2014-2021 Adobe (http://www.adobe.com/), with Reserved Font Name 'Source'
|
||||
|
||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||
This license is copied below, and is also available with a FAQ at:
|
||||
https://openfontlicense.org
|
||||
|
||||
|
||||
-----------------------------------------------------------
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
-----------------------------------------------------------
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font creation
|
||||
efforts of academic and linguistic communities, and to provide a free and
|
||||
open framework in which fonts may be shared and improved in partnership
|
||||
with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply
|
||||
to any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software components as
|
||||
distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||
or substituting -- in part or in whole -- any of the components of the
|
||||
Original Version, by changing formats or by porting the Font Software to a
|
||||
new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||
redistribute, and sell modified and unmodified copies of the Font
|
||||
Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components,
|
||||
in Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the corresponding
|
||||
Copyright Holder. This restriction only applies to the primary font name as
|
||||
presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created
|
||||
using the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||
@@ -0,0 +1,71 @@
|
||||
Noto Sans JP Variable Font
|
||||
==========================
|
||||
|
||||
This download contains Noto Sans JP as both a variable font and static fonts.
|
||||
|
||||
Noto Sans JP is a variable font with this axis:
|
||||
wght
|
||||
|
||||
This means all the styles are contained in a single file:
|
||||
Noto_Sans_JP/NotoSansJP-VariableFont_wght.ttf
|
||||
|
||||
If your app fully supports variable fonts, you can now pick intermediate styles
|
||||
that aren’t available as static fonts. Not all apps support variable fonts, and
|
||||
in those cases you can use the static font files for Noto Sans JP:
|
||||
Noto_Sans_JP/static/NotoSansJP-Thin.ttf
|
||||
Noto_Sans_JP/static/NotoSansJP-ExtraLight.ttf
|
||||
Noto_Sans_JP/static/NotoSansJP-Light.ttf
|
||||
Noto_Sans_JP/static/NotoSansJP-Regular.ttf
|
||||
Noto_Sans_JP/static/NotoSansJP-Medium.ttf
|
||||
Noto_Sans_JP/static/NotoSansJP-SemiBold.ttf
|
||||
Noto_Sans_JP/static/NotoSansJP-Bold.ttf
|
||||
Noto_Sans_JP/static/NotoSansJP-ExtraBold.ttf
|
||||
Noto_Sans_JP/static/NotoSansJP-Black.ttf
|
||||
|
||||
Get started
|
||||
-----------
|
||||
|
||||
1. Install the font files you want to use
|
||||
|
||||
2. Use your app's font picker to view the font family and all the
|
||||
available styles
|
||||
|
||||
Learn more about variable fonts
|
||||
-------------------------------
|
||||
|
||||
https://developers.google.com/web/fundamentals/design-and-ux/typography/variable-fonts
|
||||
https://variablefonts.typenetwork.com
|
||||
https://medium.com/variable-fonts
|
||||
|
||||
In desktop apps
|
||||
|
||||
https://theblog.adobe.com/can-variable-fonts-illustrator-cc
|
||||
https://helpx.adobe.com/nz/photoshop/using/fonts.html#variable_fonts
|
||||
|
||||
Online
|
||||
|
||||
https://developers.google.com/fonts/docs/getting_started
|
||||
https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Fonts/Variable_Fonts_Guide
|
||||
https://developer.microsoft.com/en-us/microsoft-edge/testdrive/demos/variable-fonts
|
||||
|
||||
Installing fonts
|
||||
|
||||
MacOS: https://support.apple.com/en-us/HT201749
|
||||
Linux: https://www.google.com/search?q=how+to+install+a+font+on+gnu%2Blinux
|
||||
Windows: https://support.microsoft.com/en-us/help/314960/how-to-install-or-remove-a-font-in-windows
|
||||
|
||||
Android Apps
|
||||
|
||||
https://developers.google.com/fonts/docs/android
|
||||
https://developer.android.com/guide/topics/ui/look-and-feel/downloadable-fonts
|
||||
|
||||
License
|
||||
-------
|
||||
Please read the full license text (OFL.txt) to understand the permissions,
|
||||
restrictions and requirements for usage, redistribution, and modification.
|
||||
|
||||
You can use them in your products & projects – print or digital,
|
||||
commercial or otherwise.
|
||||
|
||||
This isn't legal advice, please consider consulting a lawyer and see the full
|
||||
license for all details.
|
||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,19 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
|
||||
namespace ReceiptPDFBuilder.Helpers
|
||||
{
|
||||
|
||||
// https://docs.microsoft.com/en-us/dotnet/api/system.componentmodel.inotifypropertychanged?view=netframework-4.7.2
|
||||
class ChangeNotifier : INotifyPropertyChanged
|
||||
{
|
||||
public event PropertyChangedEventHandler? PropertyChanged;
|
||||
protected void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
|
||||
{
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
|
||||
using System;
|
||||
|
||||
namespace ReceiptPDFBuilder.Helpers;
|
||||
|
||||
class Constants
|
||||
{
|
||||
public static string AppVersion = "1.1.0";
|
||||
|
||||
public static string[] GetQuotes()
|
||||
{
|
||||
// sources:
|
||||
// https://www.reddit.com/r/dadjokes/comments/1ehid23/dads_of_reddit_whats_a_short_clean_joke_that/
|
||||
// https://www.thepioneerwoman.com/home-lifestyle/a35617884/best-dad-jokes/
|
||||
// https://www.today.com/life/dad-jokes-rcna27325
|
||||
// https://www.microscooters.com.au/blogs/family/100-dad-jokes-that-are-the-best-worst-in-the-book?srsltid=AfmBOopcTDq26iDYUsqaTjvUcVW6yxE-u942tatHC7Arns85unMMNfEO
|
||||
return [
|
||||
"When in the crucible of life, always remember to take your friends with you.",
|
||||
"What do you call a paper airplane that won't fly? Stationary.",
|
||||
"I used to be addicted to dad jokes, but now I'm all groan up.",
|
||||
"I used to have a phobia about speed bumps. But I'm slowly getting over it.",
|
||||
"Be careful trusting stairs. They're always up to something.",
|
||||
"What do you call a fish with no eye? A fsh",
|
||||
"How do you throw a party in outer space? You planet.",
|
||||
"Why don't eggs tell jokes? They might crack up!",
|
||||
"What do you call a snowman with a six-pack? An abdominal snowman!",
|
||||
"Why did the math book look sad? Because it had too many problems!",
|
||||
"I was going to tell you a joke about time travel, but you didn't like it.",
|
||||
"I'm writing a book about glue, but I'm stuck on the first chapter.",
|
||||
"If two vegetarians get in an argument, is it still called beef?",
|
||||
"How do you stop a bull from charging? Cancel its credit card.",
|
||||
"Why do seagulls fly over the sea? If they flew over the bay, they would be bagels.",
|
||||
"What vegetable is cool, but not *that* cool? Radish.",
|
||||
"How you fix a broken pumpkin? With a pumpkin patch.",
|
||||
"Where do boats go when they're sick? To the dock.",
|
||||
"Why was the broom late to class? It over-swept.",
|
||||
"Wanna hear a joke about construction? I'm still workin' on it!",
|
||||
"Which state has the most streets? Rhode Island.",
|
||||
"What kind of car does a sheep like to drive? A lamborghini.",
|
||||
"Where do surfers go for an education? Boarding school.",
|
||||
"What do you get when you cross a fish with an elephant? Swimming trunks.",
|
||||
"What did one eye say to the other? “Between us, something smells.”",
|
||||
"When does a joke become a dad joke? When the punchline becomes apparent.",
|
||||
"What do you call a cold puppy? A chili dog.",
|
||||
"Why did the spider go to school? He wanted to be a web designer.",
|
||||
"I was going to tell a sodium joke, then I thought, “Na.”",
|
||||
"Did you hear about the two rowboats that got into an argument? It was an oar-deal.",
|
||||
"What do you call birds that stick together? Velcrows.",
|
||||
"How do birds learn to fly? They wing it.",
|
||||
"Humpty Dumpty had a great fall. Summer wasn't too bad either.",
|
||||
"How does the moon cut his hair? Eclipse it!",
|
||||
"Why did the candle quit his job? He felt burned out."
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
using System.Collections.ObjectModel;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Input;
|
||||
using Avalonia.VisualTree;
|
||||
using Avalonia.Xaml.Interactions.DragAndDrop;
|
||||
using ReceiptPDFBuilder.Models;
|
||||
using ReceiptPDFBuilder.ViewModels;
|
||||
|
||||
namespace ReceiptPDFBuilder.Helpers;
|
||||
|
||||
class DataGridDropHandler : BaseDataGridDropHandler<ReportFile>
|
||||
{
|
||||
// https://wieslawsoltes.github.io/Xaml.Behaviors/articles/drag-and-drop-datagrid/datagrid-drag-and-drop-overview.html
|
||||
// ...that page's code basically doesn't work in the way you'd expect. so just use the source code sample's code.
|
||||
protected override bool Validate(DataGrid dg, DragEventArgs e, object? sourceContext, object? targetContext, bool execute)
|
||||
{
|
||||
if (sourceContext is not ReportFile sourceItem
|
||||
|| targetContext is not MainViewModel vm
|
||||
|| dg.GetVisualAt(e.GetPosition(dg)) is not Control targetControl
|
||||
|| targetControl.DataContext is not ReportFile targetItem)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var items = dg.ItemsSource as ObservableCollection<ReportFile>;
|
||||
return RunDropAction(dg, e, execute, sourceItem, targetItem, items?? []);
|
||||
}
|
||||
|
||||
protected override ReportFile MakeCopy(ObservableCollection<ReportFile> parentCollection, ReportFile item)
|
||||
{
|
||||
// Return a clone of the item if you support Copy operations
|
||||
return new ReportFile { };
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
|
||||
namespace ReceiptPDFBuilders.Helpers;
|
||||
|
||||
public static class ThreadSafeRandom
|
||||
{
|
||||
[ThreadStatic] private static Random? Local;
|
||||
|
||||
public static Random ThisThreadsRandom
|
||||
{
|
||||
get { return Local ?? (Local = new Random(unchecked(Environment.TickCount * 31 + Thread.CurrentThread.ManagedThreadId))); }
|
||||
}
|
||||
}
|
||||
|
||||
// https://stackoverflow.com/a/1262619/3938401
|
||||
static class ListExtensions
|
||||
{
|
||||
public static void Shuffle<T>(this IList<T> list)
|
||||
{
|
||||
int n = list.Count;
|
||||
while (n > 1)
|
||||
{
|
||||
n--;
|
||||
int k = ThreadSafeRandom.ThisThreadsRandom.Next(n + 1);
|
||||
T value = list[k];
|
||||
list[k] = list[n];
|
||||
list[n] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
using System.Text.Json.Serialization;
|
||||
using ReceiptPDFBuilder.Models;
|
||||
|
||||
namespace ReceiptPDFBuilder.Helpers;
|
||||
|
||||
[JsonSerializable(typeof(Settings))]
|
||||
[JsonSerializable(typeof(ReportFile))]
|
||||
[JsonSerializable(typeof(PDFReport))]
|
||||
internal partial class SourceGenerationContext : JsonSerializerContext { }
|
||||
@@ -0,0 +1,38 @@
|
||||
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace ReceiptPDFBuilders.Helpers;
|
||||
|
||||
class Utilities
|
||||
{
|
||||
public static JsonSerializerOptions GetSerializerOptions()
|
||||
{
|
||||
var opts = new JsonSerializerOptions
|
||||
{
|
||||
WriteIndented = false,
|
||||
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
|
||||
};
|
||||
return opts;
|
||||
}
|
||||
|
||||
public static DateOnly? CheckValidDateInString(string str)
|
||||
{
|
||||
// https://stackoverflow.com/a/14918404/3938401
|
||||
var rgx = new Regex(@"\d{4}-\d{2}-\d{2}");
|
||||
var mat = rgx.Match(str);
|
||||
if (mat.Success)
|
||||
{
|
||||
var dtStr = mat.ToString();
|
||||
string[] formats = ["yyyy-MM-dd"];
|
||||
DateTime parsedDateTime;
|
||||
var didWork = DateTime.TryParseExact(dtStr, formats, CultureInfo.InvariantCulture,
|
||||
DateTimeStyles.None, out parsedDateTime);
|
||||
return didWork ? DateOnly.FromDateTime(parsedDateTime) : null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
using ReceiptPDFBuilder.ViewModels;
|
||||
|
||||
namespace ReceiptPDFBuilder.Interfaces
|
||||
{
|
||||
interface IChangeViewModel
|
||||
{
|
||||
void PushViewModel(BaseViewModel model);
|
||||
void PopViewModel();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
using Avalonia.Controls;
|
||||
|
||||
namespace ReceiptPDFBuilder.Interfaces
|
||||
{
|
||||
interface ITopLevelGrabber
|
||||
{
|
||||
TopLevel GetTopLevel();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<Window xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
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="ReceiptPDFBuilder.MainWindow"
|
||||
Title="MayShow"
|
||||
xmlns:vm="clr-namespace:ReceiptPDFBuilder.ViewModels"
|
||||
xmlns:dialogHost="clr-namespace:DialogHostAvalonia;assembly=DialogHost.Avalonia"
|
||||
x:DataType="vm:MainWindowViewModel"
|
||||
Width="800"
|
||||
MinWidth="550"
|
||||
Height="650"
|
||||
MinHeight="550">
|
||||
<dialogHost:DialogHost CloseOnClickAway="False"
|
||||
Identifier="DialogHost">
|
||||
<dialogHost:DialogHost.DialogContent>
|
||||
<StackPanel/>
|
||||
</dialogHost:DialogHost.DialogContent>
|
||||
<!-- put the content over which the dialog is shown here (e.g. your main window grid)-->
|
||||
<ContentControl Content="{Binding CurrentViewModel}"/>
|
||||
</dialogHost:DialogHost>
|
||||
</Window>
|
||||
@@ -0,0 +1,19 @@
|
||||
using Avalonia.Controls;
|
||||
using ReceiptPDFBuilder.Interfaces;
|
||||
using ReceiptPDFBuilder.ViewModels;
|
||||
|
||||
namespace ReceiptPDFBuilder;
|
||||
|
||||
public partial class MainWindow : Window, ITopLevelGrabber
|
||||
{
|
||||
public MainWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
DataContext = new MainWindowViewModel(this);
|
||||
}
|
||||
|
||||
public TopLevel GetTopLevel()
|
||||
{
|
||||
return this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Threading.Tasks;
|
||||
using ReceiptPDFBuilder.Helpers;
|
||||
|
||||
namespace ReceiptPDFBuilder.Models;
|
||||
|
||||
class PDFReport : ChangeNotifier
|
||||
{
|
||||
private string _baseFolder;
|
||||
private string _title;
|
||||
private List<ReportFile> _files;
|
||||
private DateTime _lastSaved;
|
||||
private DateTime? _lastGenerated;
|
||||
|
||||
public PDFReport()
|
||||
{
|
||||
_baseFolder = "";
|
||||
_title = "";
|
||||
_files = [];
|
||||
_lastSaved = DateTime.Now;
|
||||
_lastGenerated = null;
|
||||
}
|
||||
|
||||
public string BaseFolder
|
||||
{
|
||||
get => _baseFolder;
|
||||
set { _baseFolder = value; NotifyPropertyChanged(); }
|
||||
}
|
||||
|
||||
public string Title
|
||||
{
|
||||
get => _title;
|
||||
set { _title = value; NotifyPropertyChanged(); }
|
||||
}
|
||||
|
||||
public List<ReportFile> Files
|
||||
{
|
||||
get => _files;
|
||||
set { _files = value; NotifyPropertyChanged(); }
|
||||
}
|
||||
|
||||
public DateTime LastSaved
|
||||
{
|
||||
get => _lastSaved;
|
||||
set { _lastSaved = value; NotifyPropertyChanged(); }
|
||||
}
|
||||
|
||||
public DateTime? LastGenerated
|
||||
{
|
||||
get => _lastGenerated;
|
||||
set { _lastGenerated = value; NotifyPropertyChanged(); }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text.Json.Serialization;
|
||||
using ReceiptPDFBuilder.Helpers;
|
||||
|
||||
namespace ReceiptPDFBuilder.Models;
|
||||
|
||||
class ReportFile : ChangeNotifier
|
||||
{
|
||||
private string _title;
|
||||
private DateTime _receiptDateTime;
|
||||
private string _notes;
|
||||
private string _filePath;
|
||||
|
||||
public ReportFile()
|
||||
{
|
||||
_title = "";
|
||||
_receiptDateTime = DateTime.Now;
|
||||
_notes = "";
|
||||
_filePath = "";
|
||||
}
|
||||
|
||||
public ReportFile(ReportFile other)
|
||||
{
|
||||
Title = _title = other.Title;
|
||||
ReceiptDateTime = _receiptDateTime = other.ReceiptDateTime;
|
||||
Notes = _notes = other.Notes;
|
||||
FilePath = _filePath = other.FilePath;
|
||||
}
|
||||
|
||||
public string Title
|
||||
{
|
||||
get => _title;
|
||||
set { _title = value; NotifyPropertyChanged(); }
|
||||
}
|
||||
|
||||
public DateTime ReceiptDateTime
|
||||
{
|
||||
get => _receiptDateTime;
|
||||
set
|
||||
{
|
||||
_receiptDateTime = value;
|
||||
NotifyPropertyChanged();
|
||||
NotifyPropertyChanged(nameof(ReceiptDate));
|
||||
}
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
public DateOnly ReceiptDate
|
||||
{
|
||||
get => DateOnly.FromDateTime(_receiptDateTime);
|
||||
}
|
||||
|
||||
public string Notes
|
||||
{
|
||||
get => _notes;
|
||||
set { _notes = value; NotifyPropertyChanged(); }
|
||||
}
|
||||
|
||||
public string FilePath
|
||||
{
|
||||
get => _filePath;
|
||||
set
|
||||
{
|
||||
_filePath = value;
|
||||
NotifyPropertyChanged();
|
||||
NotifyPropertyChanged(nameof(FileName));
|
||||
NotifyPropertyChanged(nameof(IsFileFoundOnDisk));
|
||||
}
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
public string FileName
|
||||
{
|
||||
get => Path.GetFileName(_filePath);
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
public bool IsFileFoundOnDisk
|
||||
{
|
||||
get => File.Exists(FilePath);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Threading.Tasks;
|
||||
using ReceiptPDFBuilder.Helpers;
|
||||
using ReceiptPDFBuilders.Helpers;
|
||||
|
||||
namespace ReceiptPDFBuilder.Models;
|
||||
|
||||
class Settings : ChangeNotifier
|
||||
{
|
||||
private string _lastUsedPath;
|
||||
|
||||
public Settings()
|
||||
{
|
||||
_lastUsedPath = "";
|
||||
}
|
||||
|
||||
[JsonInclude]
|
||||
public string LastUsedPath
|
||||
{
|
||||
get => _lastUsedPath;
|
||||
set { _lastUsedPath = value; NotifyPropertyChanged(); }
|
||||
}
|
||||
|
||||
public static string GetSettingsFileName()
|
||||
{
|
||||
return "settings.json";
|
||||
}
|
||||
|
||||
public static string GetSettingsPath()
|
||||
{
|
||||
var path = Path.Combine(
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
|
||||
"ReceiptPDFBuilder"
|
||||
);
|
||||
if (!Directory.Exists(path))
|
||||
{
|
||||
Directory.CreateDirectory(path);
|
||||
}
|
||||
return Path.Combine(path, GetSettingsFileName());
|
||||
}
|
||||
|
||||
public async Task<string> SaveSettingsAsync()
|
||||
{
|
||||
var jsonContext = new SourceGenerationContext(Utilities.GetSerializerOptions());
|
||||
using MemoryStream memoryStream = new MemoryStream();
|
||||
await JsonSerializer.SerializeAsync(memoryStream, this, jsonContext.Settings);
|
||||
memoryStream.Position = 0;
|
||||
using var reader = new StreamReader(memoryStream);
|
||||
var json = await reader.ReadToEndAsync();
|
||||
await File.WriteAllTextAsync(GetSettingsPath(), json);
|
||||
return json;
|
||||
}
|
||||
|
||||
public static Settings LoadSettings()
|
||||
{
|
||||
var path = GetSettingsPath();
|
||||
if (!File.Exists(path))
|
||||
{
|
||||
return new Settings();
|
||||
}
|
||||
var json = File.ReadAllText(GetSettingsPath());
|
||||
var jsonContext = new SourceGenerationContext(Utilities.GetSerializerOptions());
|
||||
return JsonSerializer.Deserialize<Settings>(json, jsonContext.Settings) ?? new Settings();
|
||||
}
|
||||
|
||||
public static async Task<Settings> LoadSettingsAsync()
|
||||
{
|
||||
using FileStream fileStream = File.OpenRead(GetSettingsPath());
|
||||
var jsonContext = new SourceGenerationContext(Utilities.GetSerializerOptions());
|
||||
var output = await JsonSerializer.DeserializeAsync<Settings>(fileStream, jsonContext.Settings) ?? new Settings();
|
||||
return output;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
using Avalonia;
|
||||
using System;
|
||||
|
||||
namespace ReceiptPDFBuilder;
|
||||
|
||||
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();
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 401 KiB |
@@ -0,0 +1,55 @@
|
||||
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<BuiltInComInteropSupport>true</BuiltInComInteropSupport>
|
||||
<ApplicationManifest>app.manifest</ApplicationManifest>
|
||||
<AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>
|
||||
<PublishSingleFile>true</PublishSingleFile>
|
||||
<SelfContained>true</SelfContained>
|
||||
<PublishTrimmed>true</PublishTrimmed>
|
||||
<PublishAot>true</PublishAot>
|
||||
<AssemblyName>MayShow</AssemblyName>
|
||||
<AssemblyVersion>1.1.0</AssemblyVersion> <!-- Also update Constants version -->
|
||||
<ApplicationIcon>ReceiptPDFBuilder-icon.ico</ApplicationIcon>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<TrimmerRootAssembly Include="PdfSharp" />
|
||||
<TrimmerRootAssembly Include="Avalonia.Controls.DataGrid" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AvaloniaResource Include="Assets\**" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Content Include="Assets\Fonts\Noto_Sans\*.*">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="Assets\Fonts\Noto_Sans\static\*.*">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="Assets\Fonts\Noto_Sans_JP\*.*">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="Assets\Fonts\Noto_Sans_JP\static\*.*">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Avalonia" Version="$(AvaloniaVersion)" />
|
||||
<PackageReference Include="Avalonia.Desktop" Version="$(AvaloniaVersion)" />
|
||||
<PackageReference Include="Avalonia.Themes.Fluent" Version="$(AvaloniaVersion)" />
|
||||
<PackageReference Include="Avalonia.Fonts.Inter" Version="$(AvaloniaVersion)" />
|
||||
<PackageReference Include="Avalonia.Controls.DataGrid" Version="$(AvaloniaVersion)" />
|
||||
<PackageReference Include="Avalonia.Diagnostics" Version="$(AvaloniaVersion)">
|
||||
<IncludeAssets Condition="'$(Configuration)' != 'Debug'">None</IncludeAssets>
|
||||
<PrivateAssets Condition="'$(Configuration)' != 'Debug'">All</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="PDFsharp-MigraDoc" Version="6.2.3" />
|
||||
<PackageReference Include="Magick.NET-Q16-AnyCPU" Version="14.10.2" />
|
||||
<PackageReference Include="Deadpikle.AvaloniaProgressRing" Version="0.10.11-preview20251127001" />
|
||||
<PackageReference Include="DialogHost.Avalonia" Version="0.10.4" />
|
||||
<PackageReference Include="Xaml.Behaviors.Interactions.DragAndDrop.DataGrid" Version="11.3.9.5" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,33 @@
|
||||
#nullable enable
|
||||
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Avalonia.Platform.Storage;
|
||||
using Avalonia.Themes.Fluent;
|
||||
using DialogHostAvalonia;
|
||||
using ImageMagick;
|
||||
using MigraDoc.DocumentObjectModel;
|
||||
using MigraDoc.Rendering;
|
||||
using PdfSharp.Fonts;
|
||||
using PdfSharp.Pdf.IO;
|
||||
using PdfSharp.Snippets.Font;
|
||||
using ReceiptPDFBuilder.Interfaces;
|
||||
using ReceiptPDFBuilder.Models;
|
||||
|
||||
namespace ReceiptPDFBuilder.ViewModels;
|
||||
|
||||
class AboutViewModel
|
||||
{
|
||||
public AboutViewModel()
|
||||
{
|
||||
}
|
||||
|
||||
public void Close()
|
||||
{
|
||||
DialogHost.Close("DialogHost", null);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
using Avalonia.Controls;
|
||||
using ReceiptPDFBuilder.Helpers;
|
||||
using ReceiptPDFBuilder.Interfaces;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace ReceiptPDFBuilder.ViewModels
|
||||
{
|
||||
class BaseViewModel : ChangeNotifier
|
||||
{
|
||||
IChangeViewModel _viewModelChanger;
|
||||
ITopLevelGrabber? _topLevelGrabber;
|
||||
|
||||
public BaseViewModel(IChangeViewModel viewModelChanger)
|
||||
{
|
||||
_viewModelChanger = viewModelChanger;
|
||||
_topLevelGrabber = null;
|
||||
}
|
||||
|
||||
public ITopLevelGrabber? TopLevelGrabber
|
||||
{
|
||||
get => _topLevelGrabber;
|
||||
set { _topLevelGrabber = value; }
|
||||
}
|
||||
|
||||
public IChangeViewModel ViewModelChanger
|
||||
{
|
||||
get { return _viewModelChanger; }
|
||||
set { _viewModelChanger = value; }
|
||||
}
|
||||
|
||||
#region IChangeViewModel
|
||||
|
||||
public void PopViewModel()
|
||||
{
|
||||
_viewModelChanger?.PopViewModel();
|
||||
}
|
||||
|
||||
public void PushViewModel(BaseViewModel model)
|
||||
{
|
||||
_viewModelChanger?.PushViewModel(model);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
#nullable enable
|
||||
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Avalonia.Platform.Storage;
|
||||
using Avalonia.Themes.Fluent;
|
||||
using DialogHostAvalonia;
|
||||
using ImageMagick;
|
||||
using MigraDoc.DocumentObjectModel;
|
||||
using MigraDoc.Rendering;
|
||||
using PdfSharp.Fonts;
|
||||
using PdfSharp.Pdf.IO;
|
||||
using PdfSharp.Snippets.Font;
|
||||
using ReceiptPDFBuilder.Interfaces;
|
||||
using ReceiptPDFBuilder.Models;
|
||||
|
||||
namespace ReceiptPDFBuilder.ViewModels;
|
||||
|
||||
class EditFileViewModel : BaseViewModel
|
||||
{
|
||||
ReportFile _clonedFile;
|
||||
ReportFile _originalFile;
|
||||
|
||||
public EditFileViewModel(ReportFile file, IChangeViewModel viewModelChanger) : base(viewModelChanger)
|
||||
{
|
||||
_clonedFile = new ReportFile(file);
|
||||
_originalFile = file;
|
||||
}
|
||||
|
||||
public ReportFile OriginalFile
|
||||
{
|
||||
get => _originalFile;
|
||||
}
|
||||
|
||||
public ReportFile ClonedFile
|
||||
{
|
||||
get => _clonedFile;
|
||||
}
|
||||
|
||||
public void Cancel()
|
||||
{
|
||||
DialogHost.Close("DialogHost", null);
|
||||
}
|
||||
|
||||
public void Save()
|
||||
{
|
||||
DialogHost.Close("DialogHost", ClonedFile);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,701 @@
|
||||
#nullable enable
|
||||
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
using Avalonia.Platform.Storage;
|
||||
using Avalonia.Themes.Fluent;
|
||||
using DialogHostAvalonia;
|
||||
using ImageMagick;
|
||||
using MigraDoc.DocumentObjectModel;
|
||||
using MigraDoc.Rendering;
|
||||
using PdfSharp.Fonts;
|
||||
using PdfSharp.Pdf.IO;
|
||||
using PdfSharp.Snippets.Font;
|
||||
using ReceiptPDFBuilder.Helpers;
|
||||
using ReceiptPDFBuilder.Interfaces;
|
||||
using ReceiptPDFBuilder.Models;
|
||||
using ReceiptPDFBuilders.Helpers;
|
||||
|
||||
namespace ReceiptPDFBuilder.ViewModels;
|
||||
|
||||
class MainViewModel : BaseViewModel, IFontResolver
|
||||
{
|
||||
private string _processDir;
|
||||
private bool _isCreatingPDF;
|
||||
private string _createPDFLog;
|
||||
private string _workingFolder;
|
||||
|
||||
private string _reportTitle;
|
||||
private ObservableCollection<ReportFile> _reportFiles;
|
||||
private DateTime? _lastGeneratedTime;
|
||||
|
||||
private Settings _settings;
|
||||
|
||||
public MainViewModel(IChangeViewModel viewModelChanger) : base(viewModelChanger)
|
||||
{
|
||||
_processDir = Path.GetDirectoryName(Environment.ProcessPath) ?? "";
|
||||
Console.WriteLine("Process is running from: {0}", _processDir);
|
||||
_isCreatingPDF = false;
|
||||
var quotes = Constants.GetQuotes();
|
||||
Random random = new Random();
|
||||
var quoteIndex = random.Next(0, quotes.Length);
|
||||
_createPDFLog = "----- MayShow v" + Constants.AppVersion + "------" + Environment.NewLine;
|
||||
_createPDFLog += quotes[quoteIndex] + Environment.NewLine;
|
||||
_createPDFLog += "---------------------------------------" + Environment.NewLine;
|
||||
_createPDFLog += "Ready to create PDF!";
|
||||
_workingFolder = "";
|
||||
_reportFiles = new ObservableCollection<ReportFile>();
|
||||
_reportFiles.CollectionChanged += ( sender, e ) => { NotifyPropertyChanged(nameof(IsCreatePDFButtonEnabled)); };
|
||||
_reportTitle = "";
|
||||
_lastGeneratedTime = null;
|
||||
_settings = Settings.LoadSettings();
|
||||
if (!string.IsNullOrWhiteSpace(_settings.LastUsedPath))
|
||||
{
|
||||
LogInfo("Loading data at last used path of {0}", _settings.LastUsedPath);
|
||||
ScanFolder(_settings.LastUsedPath);
|
||||
}
|
||||
}
|
||||
|
||||
public string ReportTitle
|
||||
{
|
||||
get => _reportTitle;
|
||||
set { _reportTitle = value; NotifyPropertyChanged(); NotifyPropertyChanged(nameof(IsTitleBoxVisible)); }
|
||||
}
|
||||
|
||||
public bool IsTitleBoxVisible
|
||||
{
|
||||
get => !string.IsNullOrWhiteSpace(_workingFolder);
|
||||
}
|
||||
|
||||
public bool IsCreatingPDF
|
||||
{
|
||||
get => _isCreatingPDF;
|
||||
set
|
||||
{
|
||||
_isCreatingPDF = value;
|
||||
NotifyPropertyChanged();
|
||||
NotifyPropertyChanged(nameof(IsCreatePDFButtonEnabled));
|
||||
NotifyPropertyChanged(nameof(HasWorkingFolderAndNotMakingPDF));
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsCreatePDFButtonEnabled
|
||||
{
|
||||
get => !_isCreatingPDF && _reportFiles.Count > 0;
|
||||
}
|
||||
|
||||
public bool HasWorkingFolder
|
||||
{
|
||||
get => !string.IsNullOrWhiteSpace(_workingFolder) && Directory.Exists(_workingFolder);
|
||||
}
|
||||
|
||||
public bool HasWorkingFolderAndNotMakingPDF
|
||||
{
|
||||
get => !string.IsNullOrWhiteSpace(_workingFolder) && Directory.Exists(_workingFolder) && !_isCreatingPDF;
|
||||
}
|
||||
|
||||
public string WorkingFolder
|
||||
{
|
||||
get => _workingFolder;
|
||||
set
|
||||
{
|
||||
_workingFolder = value;
|
||||
NotifyPropertyChanged();
|
||||
NotifyPropertyChanged(nameof(HasWorkingFolder));
|
||||
NotifyPropertyChanged(nameof(HasWorkingFolderAndNotMakingPDF));
|
||||
}
|
||||
}
|
||||
|
||||
public string CreatePDFLog
|
||||
{
|
||||
get => _createPDFLog;
|
||||
set { _createPDFLog = value; NotifyPropertyChanged(); }
|
||||
}
|
||||
|
||||
public ObservableCollection<ReportFile> ReportFiles
|
||||
{
|
||||
get => _reportFiles;
|
||||
set
|
||||
{
|
||||
_reportFiles = value;
|
||||
NotifyPropertyChanged();
|
||||
_reportFiles.CollectionChanged += ( sender, e ) =>
|
||||
{
|
||||
NotifyPropertyChanged(nameof(IsCreatePDFButtonEnabled));
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private void LogInfo(string message, params object[]? arguments)
|
||||
{
|
||||
var timestamp = string.Format("[{0:s}]", DateTime.Now);
|
||||
Console.WriteLine(timestamp + " " + message, arguments);
|
||||
CreatePDFLog += Environment.NewLine + string.Format(message, arguments ?? []);
|
||||
}
|
||||
|
||||
public async void ChooseFolder()
|
||||
{
|
||||
var topLevel = TopLevelGrabber?.GetTopLevel();
|
||||
if (topLevel is not null)
|
||||
{
|
||||
var folders = await topLevel.StorageProvider.OpenFolderPickerAsync(new FolderPickerOpenOptions()
|
||||
{
|
||||
Title = "Pick a folder of files...",
|
||||
AllowMultiple = false,
|
||||
});
|
||||
if (folders.Count == 1)
|
||||
{
|
||||
var folder = folders[0];
|
||||
LogInfo("Clearing existing list and loading items in folder: " + folder.Path.LocalPath);
|
||||
ReportFiles.Clear();
|
||||
ScanFolder(folder.Path.LocalPath);
|
||||
_settings.LastUsedPath = folder.Path.LocalPath;
|
||||
await _settings.SaveSettingsAsync();
|
||||
ResortPDFItemsByDate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ScanFolder(string path)
|
||||
{
|
||||
if (Directory.Exists(path))
|
||||
{
|
||||
WorkingFolder = path;
|
||||
NotifyPropertyChanged(nameof(IsTitleBoxVisible));
|
||||
var reportFilePath = Path.Combine(path, GetReportSavedDataFileName());
|
||||
var successfullyLoadedPriorReport = false;
|
||||
if (File.Exists(reportFilePath))
|
||||
{
|
||||
// load prior report
|
||||
var json = File.ReadAllText(reportFilePath);
|
||||
var jsonContext = new SourceGenerationContext(Utilities.GetSerializerOptions());
|
||||
var report = JsonSerializer.Deserialize<PDFReport>(json, jsonContext.PDFReport);
|
||||
if (report != null && report.Files.Count > 0)
|
||||
{
|
||||
ReportFiles = new ObservableCollection<ReportFile>(report.Files);
|
||||
ReportTitle = report.Title;
|
||||
WorkingFolder = report.BaseFolder;
|
||||
_lastGeneratedTime = report.LastGenerated ?? null;
|
||||
LogInfo("Reloaded report last saved at {0}", report.LastSaved);
|
||||
successfullyLoadedPriorReport = true;
|
||||
}
|
||||
}
|
||||
if (!successfullyLoadedPriorReport)
|
||||
{
|
||||
// Scan folder for files and display in DataGrid
|
||||
ReportFiles.Clear();
|
||||
ReportTitle = "";
|
||||
var filePaths = Directory.GetFiles(_workingFolder);
|
||||
foreach (var filePath in filePaths)
|
||||
{
|
||||
AddFileBasedOnPath(filePath);
|
||||
}
|
||||
ResortPDFItemsByDate();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
LogInfo("Error: The directory {0} does not exist. Please select another folder.", path);
|
||||
}
|
||||
NotifyPropertyChanged(nameof(IsCreatePDFButtonEnabled));
|
||||
}
|
||||
|
||||
public void ShowAbout()
|
||||
{
|
||||
DialogHost.Show(new AboutViewModel());
|
||||
}
|
||||
|
||||
public void RemoveFile(object f) => RemoveFileImpl((ReportFile)f);
|
||||
|
||||
public async void RemoveFileImpl(ReportFile file)
|
||||
{
|
||||
var result = await DialogHost.Show(new WarningDeleteItemModel(file));
|
||||
if (result != null && (bool)result)
|
||||
{
|
||||
var idx = ReportFiles.IndexOf(file);
|
||||
if (idx != -1)
|
||||
{
|
||||
ReportFiles.RemoveAt(idx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// https://github.com/AvaloniaUI/Avalonia/issues/10075
|
||||
public void EditFileProperties(object f) => EditFilePropertiesImpl((ReportFile)f);
|
||||
|
||||
public async void EditFilePropertiesImpl(ReportFile file)
|
||||
{
|
||||
var result = await DialogHost.Show(new EditFileViewModel(file, ViewModelChanger));
|
||||
if (result != null && result is ReportFile updatedData)
|
||||
{
|
||||
file.Title = updatedData.Title;
|
||||
file.ReceiptDateTime = updatedData.ReceiptDateTime;
|
||||
file.Notes = updatedData.Notes;
|
||||
}
|
||||
}
|
||||
|
||||
private string[] GetAllowedFileExtensionPatterns()
|
||||
{
|
||||
return [ "*.png", "*.jpg", "*.jpeg", "*.gif", "*.bmp", "*.webp", "*.pdf", "*.heic", ];
|
||||
}
|
||||
|
||||
private string[] GetAllowedFileExtensionPatternsWithoutStar()
|
||||
{
|
||||
var list = GetAllowedFileExtensionPatterns();
|
||||
return list.Select(x => x.Replace("*.", "")).ToArray();
|
||||
}
|
||||
|
||||
public async void AddItem()
|
||||
{
|
||||
var topLevel = TopLevelGrabber?.GetTopLevel();
|
||||
if (topLevel is not null)
|
||||
{
|
||||
var files = await topLevel.StorageProvider.OpenFilePickerAsync(new FilePickerOpenOptions()
|
||||
{
|
||||
Title = "Choose image or PDF files...",
|
||||
AllowMultiple = true,
|
||||
FileTypeFilter = [
|
||||
new FilePickerFileType("All Types")
|
||||
{
|
||||
Patterns = GetAllowedFileExtensionPatterns(),
|
||||
AppleUniformTypeIdentifiers = [ "public.image", "com.adobe.pdf", "public.heic" ],
|
||||
MimeTypes = [ "image/*", "application/pdf", "image/heic" ]
|
||||
},
|
||||
FilePickerFileTypes.ImageAll,
|
||||
new FilePickerFileType("HEIC Images")
|
||||
{
|
||||
Patterns = [ "*.heic" ],
|
||||
AppleUniformTypeIdentifiers = [ "public.heic" ],
|
||||
MimeTypes = [ "image/heic" ]
|
||||
},
|
||||
FilePickerFileTypes.Pdf,
|
||||
],
|
||||
});
|
||||
if (files.Count > 0)
|
||||
{
|
||||
foreach (var file in files)
|
||||
{
|
||||
var filePath = file.TryGetLocalPath();
|
||||
AddFileBasedOnPath(filePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void AddFileBasedOnPath(string? filePath)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(filePath) && File.Exists(filePath) && !filePath.EndsWith(".DS_Store"))
|
||||
{
|
||||
// make sure extensions are OK
|
||||
var fileExtensions = GetAllowedFileExtensionPatternsWithoutStar();
|
||||
var didMatch = false;
|
||||
foreach (var fileExtension in fileExtensions)
|
||||
{
|
||||
if (filePath.ToLower().EndsWith("." + fileExtension.ToLower()))
|
||||
{
|
||||
didMatch = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!didMatch)
|
||||
{
|
||||
LogInfo("File {0} did not match allowed file extension types, so it was not added.", filePath);
|
||||
}
|
||||
else
|
||||
{
|
||||
var date = Utilities.CheckValidDateInString(filePath);
|
||||
ReportFiles.Add(new ReportFile()
|
||||
{
|
||||
Title = Path.GetFileName(filePath),
|
||||
ReceiptDateTime = date.HasValue ? date.Value.ToDateTime(TimeOnly.MinValue) : File.GetCreationTime(filePath),
|
||||
Notes = "",
|
||||
FilePath = filePath,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void LocateFile(object f) => LocateFileImpl((ReportFile) f);
|
||||
public async void LocateFileImpl(ReportFile reportFile)
|
||||
{
|
||||
var topLevel = TopLevelGrabber?.GetTopLevel();
|
||||
if (topLevel is not null)
|
||||
{
|
||||
var files = await topLevel.StorageProvider.OpenFilePickerAsync(new FilePickerOpenOptions()
|
||||
{
|
||||
Title = "Choose image or PDF file...",
|
||||
AllowMultiple = false,
|
||||
FileTypeFilter = [
|
||||
new FilePickerFileType("All Types")
|
||||
{
|
||||
Patterns = GetAllowedFileExtensionPatterns(),
|
||||
AppleUniformTypeIdentifiers = [ "public.image", "com.adobe.pdf", "public.heic" ],
|
||||
MimeTypes = [ "image/*", "application/pdf", "image/heic" ]
|
||||
},
|
||||
FilePickerFileTypes.ImageAll,
|
||||
new FilePickerFileType("HEIC Images")
|
||||
{
|
||||
Patterns = [ "*.heic" ],
|
||||
AppleUniformTypeIdentifiers = [ "public.heic" ],
|
||||
MimeTypes = [ "image/heic" ]
|
||||
},
|
||||
FilePickerFileTypes.Pdf,
|
||||
],
|
||||
});
|
||||
if (files.Count > 0)
|
||||
{
|
||||
var file = files[0];
|
||||
reportFile.FilePath = file.Path.LocalPath;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// https://github.com/AvaloniaUI/Avalonia/issues/10075
|
||||
public void OpenFile(object f) => OpenFileImpl((ReportFile)f);
|
||||
public void OpenFileImpl(ReportFile file)
|
||||
{
|
||||
var topLevel = TopLevelGrabber?.GetTopLevel();
|
||||
if (topLevel is not null)
|
||||
{
|
||||
var launcher = topLevel.Launcher;
|
||||
launcher.LaunchUriAsync(new Uri(file.FilePath));
|
||||
}
|
||||
}
|
||||
|
||||
public void OpenFileLocation(object f) => OpenFileLocationImpl((ReportFile)f);
|
||||
|
||||
private void OpenFileLocationImpl(ReportFile file)
|
||||
{
|
||||
OpenFolderForFileInFileViewer(file.FilePath);
|
||||
}
|
||||
|
||||
private void OpenFolderForFileInFileViewer(string fullPathToFile)
|
||||
{
|
||||
var topLevel = TopLevelGrabber?.GetTopLevel();
|
||||
var dirName = Path.GetDirectoryName(fullPathToFile);
|
||||
if (topLevel is not null && dirName != null)
|
||||
{
|
||||
var launcher = topLevel.Launcher;
|
||||
launcher.LaunchUriAsync(new Uri(dirName));
|
||||
}
|
||||
}
|
||||
|
||||
public void ResortPDFItemsByDate()
|
||||
{
|
||||
LogInfo("Sorting report files list...");
|
||||
ReportFiles = new ObservableCollection<ReportFile>(ReportFiles.OrderBy(x => x.ReceiptDateTime));
|
||||
}
|
||||
|
||||
public async void BuildPDF()
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(ReportTitle))
|
||||
{
|
||||
await DialogHost.Show(new WarningViewModel("You must provide a report title!"));
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
await Task.Run(() => CreatePDF(_workingFolder));
|
||||
} catch (Exception e)
|
||||
{
|
||||
LogInfo("PDF process failed! Reason: " + e.Message);
|
||||
if (e.StackTrace != null)
|
||||
{
|
||||
LogInfo(e.StackTrace);
|
||||
}
|
||||
if (e.InnerException != null)
|
||||
{
|
||||
LogInfo("Inner exception: " + e.InnerException.Message);
|
||||
if (e.InnerException.StackTrace != null)
|
||||
{
|
||||
LogInfo(e.InnerException.StackTrace);
|
||||
}
|
||||
}
|
||||
LogInfo("Please report this error to a programmer or fix the issue listed above.");
|
||||
IsCreatingPDF = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async void SaveInterimReportInfo()
|
||||
{
|
||||
var report = new PDFReport()
|
||||
{
|
||||
Title = ReportTitle,
|
||||
Files = ReportFiles.ToList(),
|
||||
BaseFolder = _workingFolder,
|
||||
LastSaved = DateTime.Now,
|
||||
LastGenerated = _lastGeneratedTime,
|
||||
};
|
||||
await SavePDFReportDataToDisk(report);
|
||||
}
|
||||
|
||||
private async Task SavePDFReportDataToDisk(PDFReport report)
|
||||
{
|
||||
var jsonContext = new SourceGenerationContext(Utilities.GetSerializerOptions());
|
||||
using var memoryStream = new MemoryStream();
|
||||
await JsonSerializer.SerializeAsync(memoryStream, report, jsonContext.PDFReport);
|
||||
memoryStream.Position = 0;
|
||||
using var reader = new StreamReader(memoryStream);
|
||||
var json = await reader.ReadToEndAsync();
|
||||
var savePath = Path.Combine(_workingFolder, GetReportSavedDataFileName());
|
||||
await File.WriteAllTextAsync(savePath, json);
|
||||
LogInfo("Saved report information to {0}", savePath);
|
||||
}
|
||||
|
||||
private async Task CreateAndSaveReportObjectAfterReportCreation()
|
||||
{
|
||||
var report = new PDFReport()
|
||||
{
|
||||
Title = ReportTitle,
|
||||
Files = ReportFiles.ToList(),
|
||||
BaseFolder = _workingFolder,
|
||||
LastSaved = DateTime.Now,
|
||||
LastGenerated = DateTime.Now,
|
||||
};
|
||||
_lastGeneratedTime = DateTime.Now;
|
||||
await SavePDFReportDataToDisk(report);
|
||||
}
|
||||
|
||||
private string GetReportSavedDataFileName()
|
||||
{
|
||||
return "report_data.json";
|
||||
}
|
||||
|
||||
public byte[]? GetFont(string faceName)
|
||||
{
|
||||
LogInfo(string.Format("Loading font {0}", faceName));
|
||||
if (faceName == "Noto Sans JP")
|
||||
{
|
||||
var path = Path.Combine(_processDir, "Assets/Fonts/Noto_Sans_JP/static/NotoSansJP-Regular.ttf");
|
||||
if (!File.Exists(path))
|
||||
{
|
||||
path = Path.Combine(_processDir, "../Resources/Assets/Fonts/Noto_Sans_JP/static/NotoSansJP-Regular.ttf");
|
||||
}
|
||||
return File.ReadAllBytes(path);
|
||||
}
|
||||
if (faceName == "Noto Sans JP Bold")
|
||||
{
|
||||
var path = Path.Combine(_processDir, "Assets/Fonts/Noto_Sans_JP/static/NotoSansJP-SemiBold.ttf");
|
||||
if (!File.Exists(path))
|
||||
{
|
||||
path = Path.Combine(_processDir, "../Resources/Assets/Fonts/Noto_Sans_JP/static/NotoSansJP-SemiBold.ttf");
|
||||
}
|
||||
return File.ReadAllBytes(path);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public FontResolverInfo? ResolveTypeface(string familyName, bool bold, bool italic)
|
||||
{
|
||||
// LogInfo(string.Format("Resolving font name {0}", familyName));
|
||||
if (familyName == "Noto Sans JP")
|
||||
{
|
||||
if (bold)
|
||||
{
|
||||
return new FontResolverInfo(familyName + " Bold");
|
||||
}
|
||||
return new FontResolverInfo(familyName);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// https://forum.pdfsharp.net/viewtopic.php?f=2&t=1025
|
||||
private async Task CreatePDF(string folderPath)
|
||||
{
|
||||
// TODO: calculate needed width for images based on page width and margins and all that?
|
||||
// TODO: resize (non-HEIC) images for smaller size...?
|
||||
IsCreatingPDF = true;
|
||||
var pdfDoc = new Document();
|
||||
var outputFileName = ReportTitle + ".pdf";
|
||||
var folderName = new DirectoryInfo(folderPath).Name;
|
||||
const int imageWidth = 425;
|
||||
if (folderName.Contains('-'))
|
||||
{
|
||||
// see if year/month format
|
||||
var parts = folderName.Split('-');
|
||||
if (parts[0].Length == 4 &&
|
||||
parts[1].Length <= 2 &&
|
||||
int.TryParse(parts[0], out int year) && int.TryParse(parts[1], out int month))
|
||||
{
|
||||
outputFileName = string.Format("{0} {1} Receipts.pdf",
|
||||
CultureInfo.CurrentCulture.DateTimeFormat.GetMonthName(month),
|
||||
year);
|
||||
LogInfo("Auto-changed output file name to " + outputFileName);
|
||||
}
|
||||
}
|
||||
var section = pdfDoc.AddSection();
|
||||
section.PageSetup.PageFormat = PageFormat.Letter;
|
||||
section.PageSetup.PageWidth = "8.5in";
|
||||
section.PageSetup.PageHeight = "11in";
|
||||
section.PageSetup.TopMargin = "0.5in";
|
||||
section.PageSetup.RightMargin = "0.5in";
|
||||
section.PageSetup.BottomMargin = "0.5in";
|
||||
section.PageSetup.LeftMargin = "0.5in";
|
||||
// setup footer for page number
|
||||
var footerPar = new Paragraph();
|
||||
footerPar.Format.Alignment = ParagraphAlignment.Center;
|
||||
footerPar.Format.Font.Size = 10;
|
||||
footerPar.AddText("--Page ");
|
||||
footerPar.AddPageField();
|
||||
footerPar.AddText(" of ");
|
||||
footerPar.AddNumPagesField();
|
||||
footerPar.AddText("--");
|
||||
footerPar.AddLineBreak();
|
||||
footerPar.AddText("Report generated on " + DateTime.Now.ToString("f"));
|
||||
section.Footers.Primary.Add(footerPar);
|
||||
// add report title
|
||||
var reportTitlePar = section.AddParagraph();
|
||||
reportTitlePar.Format.Alignment = ParagraphAlignment.Center;
|
||||
reportTitlePar.Format.Font.Size = 16;
|
||||
reportTitlePar.Format.Font.Bold = true;
|
||||
reportTitlePar.Format.Font.Name = "Noto Sans JP"; // has english letters in it, too
|
||||
reportTitlePar.AddText(ReportTitle);
|
||||
//
|
||||
GlobalFontSettings.FontResolver = this;
|
||||
GlobalFontSettings.FallbackFontResolver = new FailsafeFontResolver();
|
||||
var hasAddedData = false;
|
||||
for (var i = 0; i < ReportFiles.Count; i++)
|
||||
{
|
||||
var file = ReportFiles[i];
|
||||
var fileName = file.FileName;
|
||||
var filePath = file.FilePath;
|
||||
if (!File.Exists(filePath))
|
||||
{
|
||||
LogInfo("ERROR: File \"{0}\" does not exist at path \"{1}\". Please remove it from the report or re-add it using the Add Item button if you still want it to be in this report.", file.Title, file.FilePath);
|
||||
IsCreatingPDF = false;
|
||||
return;
|
||||
}
|
||||
if (fileName == ".DS_Store" || fileName == outputFileName)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (i > 0 && hasAddedData)
|
||||
{
|
||||
section.AddPageBreak();
|
||||
}
|
||||
var imageTitlePar = section.AddParagraph();
|
||||
imageTitlePar.Format.Alignment = ParagraphAlignment.Center;
|
||||
imageTitlePar.Format.Font.Size = 12;
|
||||
imageTitlePar.Format.Font.Bold = true;
|
||||
imageTitlePar.Format.Font.Name = "Noto Sans JP"; // has english letters in it, too
|
||||
if (string.IsNullOrWhiteSpace(file.Title))
|
||||
{
|
||||
imageTitlePar.AddText(file.FileName);
|
||||
}
|
||||
else
|
||||
{
|
||||
imageTitlePar.AddText(file.Title);
|
||||
}
|
||||
var receiptDatePar = section.AddParagraph();
|
||||
receiptDatePar.Format.Alignment = ParagraphAlignment.Center;
|
||||
receiptDatePar.Format.Font.Size = 12;
|
||||
receiptDatePar.Format.Font.Bold = true;
|
||||
receiptDatePar.Format.Font.Name = "Noto Sans JP"; // has english letters in it, too
|
||||
receiptDatePar.AddText(file.ReceiptDate.ToString("yyyy-MM-dd"));
|
||||
if (!string.IsNullOrWhiteSpace(file.Notes))
|
||||
{
|
||||
var imageNotesPar = section.AddParagraph();
|
||||
imageNotesPar.Format.Alignment = ParagraphAlignment.Center;
|
||||
imageNotesPar.Format.Font.Size = 10;
|
||||
imageNotesPar.Format.Font.Bold = false;
|
||||
imageNotesPar.Format.Font.Name = "Noto Sans JP";
|
||||
imageNotesPar.AddText(file.Notes);
|
||||
}
|
||||
section.AddParagraph(); // add empty line for spacing
|
||||
// now add the image
|
||||
var lowerName = fileName.ToLower();
|
||||
var isPDF = lowerName.EndsWith(".pdf");
|
||||
// convert heic, webp, or png to JPEG for size and ease of use
|
||||
// (and probably compat reasons too, though I haven't tested that...)
|
||||
var isHEIC = lowerName.EndsWith(".heic");
|
||||
var isWebp = lowerName.EndsWith(".webp");
|
||||
var isPNG = lowerName.EndsWith(".png");
|
||||
var info = new FileInfo(file.FilePath);
|
||||
uint loadedImageWidth = 0;
|
||||
uint loadedImageHeight = 0;
|
||||
if (isHEIC || isWebp || isPNG)
|
||||
{
|
||||
// Save image as jpg
|
||||
var convertedDir = Path.Combine(folderPath, "converted");
|
||||
if (!Directory.Exists(convertedDir))
|
||||
{
|
||||
Directory.CreateDirectory(convertedDir);
|
||||
}
|
||||
var outputPath = Path.Combine(convertedDir, info.Name + ".jpg");
|
||||
using var mImage = new MagickImage(info.FullName);
|
||||
loadedImageWidth = mImage.Width;
|
||||
loadedImageHeight = mImage.Height;
|
||||
mImage.Quality = 80;
|
||||
if (mImage.Width >= 400 || mImage.Height >= 400)
|
||||
{
|
||||
loadedImageWidth = (uint)Math.Floor(mImage.Width * 0.5);
|
||||
loadedImageHeight = (uint)Math.Floor(mImage.Height * 0.5);
|
||||
mImage.Scale(loadedImageWidth, loadedImageHeight);
|
||||
}
|
||||
await mImage.WriteAsync(outputPath);
|
||||
filePath = Path.Combine("Converted", info.Name + ".jpg");
|
||||
LogInfo(string.Format("Converted image to JPEG; fileName is now {0}", file.FilePath));
|
||||
}
|
||||
else if (!isPDF)
|
||||
{
|
||||
// load height/width
|
||||
using var mImage = new MagickImage(info.FullName);
|
||||
loadedImageWidth = mImage.Width;
|
||||
loadedImageHeight = mImage.Height;
|
||||
}
|
||||
var paragraph = section.AddParagraph();
|
||||
paragraph.Format.Alignment = ParagraphAlignment.Center;
|
||||
var image = paragraph.AddImage(filePath);
|
||||
image.LockAspectRatio = true;
|
||||
if (!isPDF && loadedImageHeight > 600)
|
||||
{
|
||||
image.Height = 550; // make sure it will fit on one page
|
||||
}
|
||||
else
|
||||
{
|
||||
image.Width = imageWidth; // can't be too wide now...not sure why...maybe due to margins...
|
||||
}
|
||||
LogInfo(string.Format("Added image: {0} ({1})", file.Title, filePath));
|
||||
if (isPDF)
|
||||
{
|
||||
// add other PDF pages
|
||||
// see: https://stackoverflow.com/a/65091204/3938401
|
||||
var pdfFileToAdd = PdfReader.Open(filePath);
|
||||
imageTitlePar.AddText(string.Format(" (PDF with {0} page{1}) ",
|
||||
pdfFileToAdd.PageCount,
|
||||
pdfFileToAdd.PageCount == 1 ? "" : "s"));
|
||||
for (var j = 2; j <= pdfFileToAdd.PageCount; j++)
|
||||
{
|
||||
section.AddPageBreak();
|
||||
paragraph = section.AddParagraph();
|
||||
paragraph.Format.Alignment = ParagraphAlignment.Center;
|
||||
image = paragraph.AddImage(filePath + "#" + j);
|
||||
image.LockAspectRatio = true;
|
||||
image.Width = imageWidth;
|
||||
}
|
||||
}
|
||||
hasAddedData = true;
|
||||
}
|
||||
var pdfRenderer = new PdfDocumentRenderer
|
||||
{
|
||||
Document = pdfDoc,
|
||||
WorkingDirectory = folderPath
|
||||
};
|
||||
LogInfo("Rendering document...");
|
||||
pdfRenderer.RenderDocument();
|
||||
string outputPDFFileName = Path.Join(folderPath, outputFileName);
|
||||
LogInfo("Saving document to disk...");
|
||||
pdfRenderer.PdfDocument.Save(outputPDFFileName);
|
||||
LogInfo("Saved PDF output to: " + outputPDFFileName);
|
||||
await CreateAndSaveReportObjectAfterReportCreation();
|
||||
OpenFolderForFileInFileViewer(outputPDFFileName);
|
||||
IsCreatingPDF = false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
using ReceiptPDFBuilder.Helpers;
|
||||
using ReceiptPDFBuilder.Interfaces;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace ReceiptPDFBuilder.ViewModels
|
||||
{
|
||||
class MainWindowViewModel : ChangeNotifier, IChangeViewModel
|
||||
{
|
||||
BaseViewModel _currentViewModel;
|
||||
Stack<BaseViewModel> _viewModels;
|
||||
|
||||
public MainWindowViewModel(ITopLevelGrabber topLevelGrabber)
|
||||
{
|
||||
_viewModels = new Stack<BaseViewModel>();
|
||||
var initialViewModel = new MainViewModel(this)
|
||||
{
|
||||
TopLevelGrabber = topLevelGrabber
|
||||
};
|
||||
_viewModels.Push(initialViewModel);
|
||||
_currentViewModel = initialViewModel;
|
||||
}
|
||||
|
||||
public BaseViewModel CurrentViewModel
|
||||
{
|
||||
get { return _currentViewModel; }
|
||||
set { _currentViewModel = value; NotifyPropertyChanged(); }
|
||||
}
|
||||
|
||||
#region IChangeViewModel
|
||||
|
||||
public void PushViewModel(BaseViewModel model)
|
||||
{
|
||||
_viewModels.Push(model);
|
||||
CurrentViewModel = model;
|
||||
}
|
||||
|
||||
public void PopViewModel()
|
||||
{
|
||||
if (_viewModels.Count > 1)
|
||||
{
|
||||
_viewModels.Pop();
|
||||
CurrentViewModel = _viewModels.Peek();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
using DialogHostAvalonia;
|
||||
using ReceiptPDFBuilder.Helpers;
|
||||
using ReceiptPDFBuilder.Models;
|
||||
|
||||
namespace ReceiptPDFBuilder.ViewModels
|
||||
{
|
||||
class WarningDeleteItemModel : ChangeNotifier
|
||||
{
|
||||
ReportFile _file;
|
||||
|
||||
public WarningDeleteItemModel(ReportFile file)
|
||||
{
|
||||
_file = file;
|
||||
}
|
||||
|
||||
public ReportFile File
|
||||
{
|
||||
get => _file;
|
||||
}
|
||||
|
||||
public void KeepItem()
|
||||
{
|
||||
DialogHost.Close("DialogHost", false);
|
||||
}
|
||||
|
||||
public void RemoveItem()
|
||||
{
|
||||
DialogHost.Close("DialogHost", true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
#nullable enable
|
||||
|
||||
using DialogHostAvalonia;
|
||||
|
||||
namespace ReceiptPDFBuilder.ViewModels;
|
||||
|
||||
class WarningViewModel
|
||||
{
|
||||
private string _error;
|
||||
|
||||
public WarningViewModel(string error)
|
||||
{
|
||||
_error = error;
|
||||
}
|
||||
|
||||
public string Error
|
||||
{
|
||||
get => _error;
|
||||
}
|
||||
|
||||
public void Close()
|
||||
{
|
||||
DialogHost.Close("DialogHost", null);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
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="ReceiptPDFBuilder.Views.AboutView"
|
||||
xmlns:models="clr-namespace:ReceiptPDFBuilder.Models"
|
||||
xmlns:vm="clr-namespace:ReceiptPDFBuilder.ViewModels"
|
||||
xmlns:dialogHost="clr-namespace:DialogHostAvalonia;assembly=DialogHost.Avalonia"
|
||||
x:DataType="vm:AboutViewModel">
|
||||
<StackPanel Orientation="Vertical"
|
||||
Spacing="4">
|
||||
<TextBlock Text="MayShow"
|
||||
HorizontalAlignment="Center"
|
||||
TextWrapping="Wrap"
|
||||
FontSize="18"
|
||||
FontWeight="Bold"/>
|
||||
<TextBlock Text="MayShow (an intentional misspelling of 明証, pronounced may-shō, a Japanese word meaning proof, evidence, or corroboration) is a PDF report creation tool. It was built by MB for A in 2026. May the quacking of ducks always be in your favor. Thanks for using our software!"
|
||||
MaxWidth="300"
|
||||
TextWrapping="Wrap"
|
||||
FontSize="14"/>
|
||||
<TextBlock Text="App icon made using https://gauger.me/fonticon/ with FontAwesome icon 'file-invoice-dollar' and the macOS software Icon Composer."
|
||||
MaxWidth="300"
|
||||
TextWrapping="Wrap"
|
||||
FontSize="14"/>
|
||||
<TextBlock Text="Copyright 2026 - Quickity Quack Productions"
|
||||
MaxWidth="300"
|
||||
TextWrapping="Wrap"
|
||||
HorizontalAlignment="Center"
|
||||
Margin="0,4,0,4"
|
||||
FontSize="12"/>
|
||||
<Button Command="{Binding Close}"
|
||||
Classes="accent"
|
||||
Content="Close"
|
||||
HorizontalAlignment="Right"
|
||||
Margin="0,0,4,4"/>
|
||||
</StackPanel>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,15 @@
|
||||
using System;
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
|
||||
namespace ReceiptPDFBuilder.Views
|
||||
{
|
||||
public partial class AboutView : UserControl
|
||||
{
|
||||
public AboutView()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
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="ReceiptPDFBuilder.Views.EditFile"
|
||||
xmlns:models="clr-namespace:ReceiptPDFBuilder.Models"
|
||||
xmlns:vm="clr-namespace:ReceiptPDFBuilder.ViewModels"
|
||||
xmlns:dialogHost="clr-namespace:DialogHostAvalonia;assembly=DialogHost.Avalonia"
|
||||
x:DataType="vm:EditFileViewModel">
|
||||
<ScrollViewer AllowAutoHide="False">
|
||||
<StackPanel Orientation="Vertical"
|
||||
Spacing="4"
|
||||
Margin="12,4,12,0">
|
||||
<Label Content="Edit File Details"
|
||||
HorizontalAlignment="Center"
|
||||
FontSize="16"
|
||||
FontWeight="Bold" />
|
||||
<Label Content="Title" />
|
||||
<TextBox Watermark="Title"
|
||||
Text="{Binding ClonedFile.Title}"
|
||||
VerticalAlignment="Stretch"
|
||||
TextWrapping="Wrap" />
|
||||
<Label Content="Notes" />
|
||||
<TextBox Watermark="Notes"
|
||||
Text="{Binding ClonedFile.Notes}"
|
||||
VerticalAlignment="Stretch"
|
||||
AcceptsReturn="True"
|
||||
ScrollViewer.AllowAutoHide="False"
|
||||
Height="75" />
|
||||
<Label Content="Receipt Date" />
|
||||
<Calendar SelectionMode="SingleDate"
|
||||
SelectedDate="{Binding ClonedFile.ReceiptDateTime}"
|
||||
DisplayDate="{Binding ClonedFile.ReceiptDateTime}" />
|
||||
<StackPanel Orientation="Horizontal"
|
||||
Spacing="12"
|
||||
Margin="0,4,0,0"
|
||||
HorizontalAlignment="Right">
|
||||
<Button Command="{Binding Cancel}">
|
||||
<TextBlock>
|
||||
<Run Text=""
|
||||
FontFamily="{StaticResource FontAwesomeSolid}" /> Cancel</TextBlock>
|
||||
</Button>
|
||||
<Button Command="{Binding Save}"
|
||||
Classes="accent">
|
||||
<TextBlock>
|
||||
<Run Text=""
|
||||
FontFamily="{StaticResource FontAwesomeSolid}" /> Save</TextBlock>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,15 @@
|
||||
using System;
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
|
||||
namespace ReceiptPDFBuilder.Views
|
||||
{
|
||||
public partial class EditFile : UserControl
|
||||
{
|
||||
public EditFile()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,274 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
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="ReceiptPDFBuilder.Views.MainView"
|
||||
xmlns:helpers="clr-namespace:ReceiptPDFBuilder.Helpers"
|
||||
xmlns:models="clr-namespace:ReceiptPDFBuilder.Models"
|
||||
xmlns:views="clr-namespace:ReceiptPDFBuilder.Views"
|
||||
xmlns:vm="clr-namespace:ReceiptPDFBuilder.ViewModels"
|
||||
xmlns:progRing="clr-namespace:AvaloniaProgressRing;assembly=AvaloniaProgressRing"
|
||||
x:DataType="vm:MainViewModel">
|
||||
<Grid ColumnDefinitions="*"
|
||||
RowDefinitions="Auto, 2*, Auto, Auto, *">
|
||||
<Button Command="{Binding ShowAbout}"
|
||||
Grid.Row="0"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Top"
|
||||
Margin="0,2,4,0">
|
||||
<TextBlock><Run Text="" FontFamily="{StaticResource FontAwesomeSolid}"/> About</TextBlock>
|
||||
</Button>
|
||||
<StackPanel Orientation="Vertical"
|
||||
Spacing="2">
|
||||
<Label Content="MayShow: Report Builder"
|
||||
FontSize="20"
|
||||
FontWeight="Bold"
|
||||
HorizontalAlignment="Center"/>
|
||||
<Grid ColumnDefinitions="Auto, *"
|
||||
Margin="4,0,0,0">
|
||||
<Button Content="Choose Receipt Folder"
|
||||
Command="{Binding ChooseFolder}"
|
||||
IsEnabled="{Binding !IsCreatingPDF}"
|
||||
Grid.Column="0" />
|
||||
<TextBlock Text="{Binding WorkingFolder}"
|
||||
VerticalAlignment="Center"
|
||||
TextWrapping="NoWrap"
|
||||
Margin="4,0,4,0"
|
||||
TextTrimming="PrefixCharacterEllipsis"
|
||||
Grid.Column="1"/>
|
||||
</Grid>
|
||||
<Label Content="Report Title"
|
||||
IsVisible="{Binding IsTitleBoxVisible}" />
|
||||
<TextBox Text="{Binding ReportTitle}"
|
||||
IsVisible="{Binding IsTitleBoxVisible}"
|
||||
Watermark="Receipts December 2024"
|
||||
Margin="2,0,2,4"
|
||||
Classes="clearButton"
|
||||
Name="TitleTextBox">
|
||||
<TextBox.KeyBindings>
|
||||
<KeyBinding Command="{Binding $parent[views:MainView].UnfocusTextbox}" Gesture="Enter" />
|
||||
</TextBox.KeyBindings>
|
||||
</TextBox>
|
||||
</StackPanel>
|
||||
<DataGrid x:Name="FilesGrid"
|
||||
Classes="DragAndDrop ItemsDragAndDrop"
|
||||
Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
Grid.ColumnSpan="2"
|
||||
Margin="2"
|
||||
ItemsSource="{Binding ReportFiles}"
|
||||
AutoGenerateColumns="False"
|
||||
IsReadOnly="False"
|
||||
GridLinesVisibility="All"
|
||||
CanUserReorderColumns="False"
|
||||
CanUserResizeColumns="True"
|
||||
CanUserSortColumns="False"
|
||||
BorderThickness="1"
|
||||
VerticalScrollBarVisibility="Visible"
|
||||
ScrollViewer.AllowAutoHide="False"
|
||||
HorizontalScrollBarVisibility="Disabled"
|
||||
HeadersVisibility="All"
|
||||
BorderBrush="Gray">
|
||||
<DataGrid.Styles>
|
||||
<Style Selector="TextBlock">
|
||||
<Setter Property="TextWrapping" Value="NoWrap" />
|
||||
<Setter Property="TextTrimming" Value="CharacterEllipsis" />
|
||||
</Style>
|
||||
<Style Selector="TextBox">
|
||||
<Setter Property="TextWrapping" Value="NoWrap" />
|
||||
</Style>
|
||||
<Style Selector="ToolTip">
|
||||
<Setter Property="MaxWidth" Value="1000" />
|
||||
</Style>
|
||||
</DataGrid.Styles>
|
||||
<DataGrid.Columns>
|
||||
<DataGridTemplateColumn Header="Title"
|
||||
IsReadOnly="False"
|
||||
Width="*">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<Grid ColumnDefinitions="Auto, *">
|
||||
<Button Command="{Binding $parent[DataGrid].((vm:MainViewModel)DataContext).LocateFile}"
|
||||
CommandParameter="{Binding}"
|
||||
IsVisible="{Binding !IsFileFoundOnDisk}"
|
||||
Margin="2"
|
||||
Content=""
|
||||
VerticalContentAlignment="Center"
|
||||
Background="Transparent"
|
||||
Grid.Column="0"
|
||||
FontFamily="{StaticResource FontAwesomeSolid}"
|
||||
ToolTip.Tip="File not found; click to locate..."
|
||||
IsEnabled="{Binding !$parent[DataGrid].((vm:MainViewModel)DataContext).IsCreatingPDF}"/>
|
||||
<TextBlock Text="{Binding Title}"
|
||||
TextTrimming="CharacterEllipsis"
|
||||
TextWrapping="NoWrap"
|
||||
ToolTip.Tip="{Binding Title}"
|
||||
Grid.Column="1"
|
||||
Margin="8,0,4,0"
|
||||
VerticalAlignment="Center"/>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
<DataGridTemplateColumn.CellEditingTemplate>
|
||||
<DataTemplate DataType="models:ReportFile">
|
||||
<TextBox Text="{Binding Title}"
|
||||
Watermark="Title"
|
||||
ToolTip.Tip="{Binding Title}"
|
||||
Classes="clearButton">
|
||||
<TextBox.KeyBindings>
|
||||
<KeyBinding Command="{Binding $parent[views:MainView].UnfocusTextbox}" Gesture="Enter" />
|
||||
</TextBox.KeyBindings>
|
||||
</TextBox>
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellEditingTemplate>
|
||||
</DataGridTemplateColumn>
|
||||
<DataGridTemplateColumn Header="Receipt Date"
|
||||
IsReadOnly="False"
|
||||
Width="125">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<Label Content="{Binding ReceiptDate}"
|
||||
VerticalAlignment="Center"
|
||||
Margin="8,0,8,0"
|
||||
HorizontalAlignment="Left"/>
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
<DataGridTemplateColumn.CellEditingTemplate>
|
||||
<DataTemplate DataType="models:ReportFile">
|
||||
<CalendarDatePicker SelectedDate="{Binding ReceiptDateTime}"
|
||||
DisplayDate="{Binding ReceiptDateTime}"/>
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellEditingTemplate>
|
||||
</DataGridTemplateColumn>
|
||||
<DataGridTemplateColumn Header="File Name"
|
||||
IsReadOnly="True"
|
||||
Width="*">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding FileName}"
|
||||
VerticalAlignment="Center"
|
||||
ToolTip.Tip="{Binding FileName}"
|
||||
Margin="8,0,8,0"
|
||||
HorizontalAlignment="Left"/>
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
</DataGridTemplateColumn>
|
||||
<DataGridTemplateColumn Header=""
|
||||
IsReadOnly="True"
|
||||
Width="*">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<StackPanel Orientation="Horizontal"
|
||||
Spacing="4">
|
||||
<Button Command="{Binding $parent[DataGrid].((vm:MainViewModel)DataContext).EditFileProperties}"
|
||||
CommandParameter="{Binding}"
|
||||
Classes="accent"
|
||||
Margin="2"
|
||||
IsEnabled="{Binding !$parent[DataGrid].((vm:MainViewModel)DataContext).IsCreatingPDF}">
|
||||
<Button.Content>
|
||||
<TextBlock><Run Text="" FontFamily="{StaticResource FontAwesomeSolid}"/> Edit</TextBlock>
|
||||
</Button.Content>
|
||||
</Button>
|
||||
<Button Command="{Binding $parent[DataGrid].((vm:MainViewModel)DataContext).RemoveFile}"
|
||||
CommandParameter="{Binding}"
|
||||
Classes="Danger"
|
||||
Margin="2"
|
||||
IsEnabled="{Binding !$parent[DataGrid].((vm:MainViewModel)DataContext).IsCreatingPDF}">
|
||||
<Button.Content>
|
||||
<TextBlock><Run Text="" FontFamily="{StaticResource FontAwesomeSolid}"/> Remove</TextBlock>
|
||||
</Button.Content>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
</DataGridTemplateColumn>
|
||||
</DataGrid.Columns>
|
||||
<DataGrid.RowDetailsTemplate>
|
||||
<DataTemplate x:DataType="models:ReportFile">
|
||||
<Grid ColumnDefinitions="*"
|
||||
RowDefinitions="Auto, Auto, Auto">
|
||||
<TextBlock TextWrapping="Wrap" Margin="4" Grid.Row="0">
|
||||
<Run FontWeight="Bold" Text="File Path"/>: <Run Text="{Binding FilePath}"/>
|
||||
</TextBlock>
|
||||
<TextBlock TextWrapping="Wrap" Margin="4" Grid.Row="1">
|
||||
<Run FontWeight="Bold" Text="Notes"/>: <Run Text="{Binding Notes}"/>
|
||||
</TextBlock>
|
||||
<StackPanel Orientation="Horizontal"
|
||||
Spacing="8"
|
||||
Margin="4"
|
||||
Grid.Row="2">
|
||||
<Button Command="{Binding $parent[DataGrid].((vm:MainViewModel)DataContext).OpenFileLocation}"
|
||||
CommandParameter="{Binding}">
|
||||
<Button.Content>
|
||||
<TextBlock FontSize="12"><Run Text="" FontFamily="{StaticResource FontAwesomeSolid}"/> Open File Location</TextBlock>
|
||||
</Button.Content>
|
||||
</Button>
|
||||
<Button Command="{Binding $parent[DataGrid].((vm:MainViewModel)DataContext).OpenFile}"
|
||||
CommandParameter="{Binding}">
|
||||
<Button.Content>
|
||||
<TextBlock FontSize="12"><Run Text="" FontFamily="{StaticResource FontAwesomeSolid}"/> Open File</TextBlock>
|
||||
</Button.Content>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</DataGrid.RowDetailsTemplate>
|
||||
</DataGrid>
|
||||
<StackPanel Orientation="Vertical"
|
||||
HorizontalAlignment="Center"
|
||||
Spacing="4"
|
||||
Grid.Row="2"
|
||||
Margin="4">
|
||||
<StackPanel Orientation="Horizontal"
|
||||
Spacing="4">
|
||||
<Button Command="{Binding AddItem}"
|
||||
IsEnabled="{Binding !IsCreatingPDF}">
|
||||
<TextBlock><Run Text="+" FontFamily="{StaticResource FontAwesomeSolid}"/> Add Item</TextBlock>
|
||||
</Button>
|
||||
<Button Command="{Binding SaveInterimReportInfo}"
|
||||
IsEnabled="{Binding HasWorkingFolderAndNotMakingPDF}">
|
||||
<TextBlock><Run Text="" FontFamily="{StaticResource FontAwesomeSolid}"/> Save Report Info</TextBlock>
|
||||
</Button>
|
||||
<Button Command="{Binding ResortPDFItemsByDate}"
|
||||
IsEnabled="{Binding IsCreatePDFButtonEnabled}">
|
||||
<TextBlock><Run Text="" FontFamily="{StaticResource FontAwesomeSolid}"/> Re-sort PDF Items</TextBlock>
|
||||
</Button>
|
||||
<Button Command="{Binding BuildPDF}"
|
||||
Classes="accent"
|
||||
IsEnabled="{Binding IsCreatePDFButtonEnabled}">
|
||||
<TextBlock><Run Text="" FontFamily="{StaticResource FontAwesomeSolid}"/> Create Report PDF</TextBlock>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
<StackPanel Orientation="Horizontal"
|
||||
IsVisible="{Binding IsCreatingPDF}"
|
||||
Spacing="6"
|
||||
HorizontalAlignment="Center">
|
||||
<Label Content="Creating PDF..."
|
||||
IsVisible="{Binding IsCreatingPDF}"
|
||||
VerticalAlignment="Center"/>
|
||||
<progRing:ProgressRing Width="30"
|
||||
Height="30"
|
||||
IsActive="{Binding IsCreatingPDF}"
|
||||
Foreground="{DynamicResource SystemAccentColor}"/>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
<StackPanel Orientation="Vertical"
|
||||
HorizontalAlignment="Stretch"
|
||||
Spacing="2"
|
||||
Grid.Row="3">
|
||||
<Rectangle Fill="Gray" Height="3" HorizontalAlignment="Stretch"/>
|
||||
<Label Content="Program Log" FontSize="14" FontWeight="Bold"/>
|
||||
</StackPanel>
|
||||
<ScrollViewer Margin="2"
|
||||
Grid.Row="4"
|
||||
x:Name="LogScrollView"
|
||||
VerticalScrollBarVisibility="Visible"
|
||||
AllowAutoHide="False">
|
||||
<SelectableTextBlock Text="{Binding CreatePDFLog}"
|
||||
Margin="2"
|
||||
TextWrapping="Wrap"
|
||||
x:Name="LogBlock"/>
|
||||
</ScrollViewer>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,31 @@
|
||||
using System;
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Input;
|
||||
using Avalonia.Markup.Xaml;
|
||||
|
||||
namespace ReceiptPDFBuilder.Views
|
||||
{
|
||||
public partial class MainView : UserControl
|
||||
{
|
||||
public MainView()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
LogBlock.PropertyChanged += LogBlock_PropertyChanged;
|
||||
}
|
||||
|
||||
private void LogBlock_PropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e)
|
||||
{
|
||||
if (e.Property.ToString() == "Text")
|
||||
{
|
||||
LogScrollView.ScrollToEnd();
|
||||
}
|
||||
}
|
||||
|
||||
public void UnfocusTextbox()
|
||||
{
|
||||
var topLevel = TopLevel.GetTopLevel(this);
|
||||
topLevel?.FocusManager?.ClearFocus();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
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="ReceiptPDFBuilder.Views.WarningDeleteItem"
|
||||
xmlns:models="clr-namespace:ReceiptPDFBuilder.Models"
|
||||
xmlns:vm="clr-namespace:ReceiptPDFBuilder.ViewModels"
|
||||
xmlns:dialogHost="clr-namespace:DialogHostAvalonia;assembly=DialogHost.Avalonia"
|
||||
x:DataType="vm:WarningDeleteItemModel">
|
||||
<StackPanel HorizontalAlignment="Center"
|
||||
Margin="6"
|
||||
Spacing="8">
|
||||
<TextBlock TextAlignment="Center"
|
||||
FontWeight="Bold"
|
||||
TextWrapping="Wrap"
|
||||
FontSize="16"
|
||||
MaxWidth="350">Are you sure you want to remove the item "<Run Text="{Binding File.Title}"/>"?</TextBlock>
|
||||
<StackPanel Orientation="Horizontal"
|
||||
HorizontalAlignment="Center"
|
||||
Spacing="8">
|
||||
<Button Command="{Binding KeepItem}"
|
||||
Classes="accent">
|
||||
<TextBlock><Run Text="" FontFamily="{StaticResource FontAwesomeSolid}"/> Keep Item</TextBlock>
|
||||
</Button>
|
||||
<Button Command="{Binding RemoveItem}"
|
||||
Classes="Danger">
|
||||
<TextBlock><Run Text="" FontFamily="{StaticResource FontAwesomeSolid}"/> Remove Item</TextBlock>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,15 @@
|
||||
using System;
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
|
||||
namespace ReceiptPDFBuilder.Views
|
||||
{
|
||||
public partial class WarningDeleteItem : UserControl
|
||||
{
|
||||
public WarningDeleteItem()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
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="ReceiptPDFBuilder.Views.WarningView"
|
||||
xmlns:models="clr-namespace:ReceiptPDFBuilder.Models"
|
||||
xmlns:vm="clr-namespace:ReceiptPDFBuilder.ViewModels"
|
||||
xmlns:dialogHost="clr-namespace:DialogHostAvalonia;assembly=DialogHost.Avalonia"
|
||||
x:DataType="vm:WarningViewModel">
|
||||
<StackPanel Orientation="Vertical"
|
||||
Spacing="4">
|
||||
<TextBlock TextAlignment="Center"
|
||||
FontWeight="Bold"
|
||||
FontSize="18"
|
||||
Text="Error!"/>
|
||||
<TextBlock TextAlignment="Center"
|
||||
FontWeight="Bold"
|
||||
TextWrapping="Wrap"
|
||||
FontSize="14"
|
||||
MaxWidth="350"
|
||||
Text="{Binding Error}"/>
|
||||
<Button Command="{Binding Close}"
|
||||
Classes="accent"
|
||||
Content="Close"
|
||||
HorizontalAlignment="Right"
|
||||
Margin="0,4,4,4"/>
|
||||
</StackPanel>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,15 @@
|
||||
using System;
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
|
||||
namespace ReceiptPDFBuilder.Views
|
||||
{
|
||||
public partial class WarningView : UserControl
|
||||
{
|
||||
public WarningView()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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.1.0.0" name="ReceiptPDFBuilder.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>
|
||||
Reference in New Issue
Block a user