mirror of
https://github.com/chylex/Discord-History-Tracker.git
synced 2025-08-17 19:31:42 +02:00
Compare commits
205 Commits
master-bro
...
wip-viewer
Author | SHA1 | Date | |
---|---|---|---|
b660af4be0
|
|||
3d9d6a454a
|
|||
ee39780928
|
|||
7b58f973a0
|
|||
93fe018343
|
|||
4f5e27f651
|
|||
cbf81ec95a
|
|||
8a80cb8c20
|
|||
865deb356a
|
|||
069ab97196
|
|||
caab038eaa
|
|||
fb837374fc
|
|||
65d935cca1
|
|||
6e64c86d7a
|
|||
8aeb590bb3
|
|||
8dc1adc9f0
|
|||
ddf70b02e7
|
|||
ef59fd992e
|
|||
d044627fac
|
|||
a624745602 | |||
6da3c185e5 | |||
d4d14cab97
|
|||
095c9a061a
|
|||
d01f9ed218
|
|||
dd6f121059
|
|||
8bba33d815
|
|||
9eab8ac92a
|
|||
fe588686fc
|
|||
7392987165
|
|||
492dddb35d
|
|||
d35280a6a6
|
|||
ae8a34f938
|
|||
18f5823f2a
|
|||
37640c97b0
|
|||
3cc5c75c48
|
|||
2424a8ac8d
|
|||
e7705587c2
|
|||
bd7904b88e
|
|||
3db599e901
|
|||
69a68b463c
|
|||
b2389dd521
|
|||
610516de1f
|
|||
3a6b83e0ba
|
|||
22958536e7
|
|||
4db8c302d8
|
|||
7e7d140957
|
|||
8c68438fbb
|
|||
f625a39b4d
|
|||
7fd644449c
|
|||
e4a09515b0
|
|||
9ac9f2246f
|
|||
bbc734ba9b
|
|||
6837b05b0d
|
|||
c94808a15f
|
|||
739e87c5ab
|
|||
d463b407f4
|
|||
cd418f4871
|
|||
176a81e055 | |||
![]() |
1cf3e76644 | ||
33f5ab7cce
|
|||
b9a5664740
|
|||
845ac1b0fa
|
|||
1bead42a0e
|
|||
8f1c91b2cc
|
|||
9ae5ece24b
|
|||
053ab5b091
|
|||
71c628fdf8
|
|||
af621b8d46
|
|||
31fe6aed35
|
|||
c25426af55
|
|||
59129ba20a
|
|||
f7bfe052ca
|
|||
c9bb46c8c7
|
|||
73f4c70325
|
|||
de5a8b690b
|
|||
daa2feb445
|
|||
4e94e788bc
|
|||
133ec532d2
|
|||
3d435d0165
|
|||
3e8151e1f3
|
|||
9f98eba9c1
|
|||
6b54a80be1
|
|||
1e6e5c6f92
|
|||
2459c8ee1a
|
|||
d129a60d1c
|
|||
65ecb0177c
|
|||
d51dcb0a84
|
|||
b13b85dedd
|
|||
15e8b9da63
|
|||
9572f0f002
|
|||
2f3b8b974c
|
|||
bff86b09c7
|
|||
5ca7cf09e8
|
|||
a1c93232d0
|
|||
db5f9d65db
|
|||
4cbf387e2a
|
|||
64cf3c9fbb
|
|||
a4ebd5eed6
|
|||
06716330d6
|
|||
1a6346677e
|
|||
261be50463
|
|||
f93f5c8fdd
|
|||
039c55eb1e
|
|||
a54242de8a
|
|||
578e51dc17
|
|||
8e2ec4dfe2
|
|||
3431f091ad
|
|||
a988003bdd
|
|||
5561f574cf
|
|||
8fd4561721
|
|||
9fe68be3f4
|
|||
90dac674eb
|
|||
1ebf15b039
|
|||
86acef1a22
|
|||
277e241183
|
|||
3b41ea7b5f
|
|||
6ce0ef7d55
|
|||
fd09ac496e
|
|||
9ca56bd910
|
|||
3e891e19c3
|
|||
9341988017
|
|||
461e403733
|
|||
c03e2d328d
|
|||
f3723ee43b
|
|||
8f7b566db7
|
|||
70a2a01ec3
|
|||
c31155738e
|
|||
c23fac465f
|
|||
51a2ac2d66
|
|||
a5e8366f1b
|
|||
3b698dbf33
|
|||
dc2c2d7ce8
|
|||
bb5634adc4
|
|||
d26e16eadf
|
|||
8f5f6065d8
|
|||
ad299bf762
|
|||
f70bbd53d9
|
|||
ae821f738e
|
|||
ab7f5d0a41
|
|||
1bddde7ccd
|
|||
58259c0bb4
|
|||
a84a453990
|
|||
563c644f48
|
|||
f8331a571e
|
|||
1ed26a16ea
|
|||
72c13cbb58
|
|||
e420add646
|
|||
6f1149ad5e
|
|||
b9899922e0
|
|||
6a2933ea0a
|
|||
be5c76c3bd
|
|||
217c1f9e10
|
|||
725ab7accf
|
|||
9a7a2cffc2
|
|||
6d3db23f80
|
|||
4bc9626013
|
|||
8002236c1f
|
|||
c4fe6c4391
|
|||
ebfe972a98
|
|||
20aac4c47a
|
|||
35308e0995
|
|||
f7f32c3f6a
|
|||
4dc781b35c
|
|||
849ef18adb
|
|||
77aa15e557
|
|||
47b106503d
|
|||
bde4cb06f4
|
|||
d772f7ed71
|
|||
0662af9b1a
|
|||
03fd730139
|
|||
d362c96b80
|
|||
9f34c9dffa
|
|||
cacf43d1d8
|
|||
edc23d616d
|
|||
db191f87fd
|
|||
e8677c09b4
|
|||
378c54908e
|
|||
07af4ae00f
|
|||
c262e5aaa4
|
|||
96e125b812
|
|||
c0123b9f91
|
|||
2ec9c7cbc3
|
|||
4cfe19d369
|
|||
f449859ad9
|
|||
ae5630cf96
|
|||
75c8202c77
|
|||
e9e8c95a19
|
|||
879a69608c
|
|||
560c515ecf
|
|||
ec8c00853a
|
|||
5888d13d7c
|
|||
15db0cca8e
|
|||
52998f7d7a
|
|||
c2e302f8bb
|
|||
9f506aceb2
|
|||
bbb50dc50c
|
|||
3f763bbf6b
|
|||
be3a7d6d80
|
|||
bd78051a9d
|
|||
6a24befca1
|
|||
09dce7b062
|
|||
396c622d9c
|
|||
73bf16a21e
|
|||
25071d4323
|
|||
ab7b3532fc
|
8
.idea/.gitignore
generated
vendored
8
.idea/.gitignore
generated
vendored
@@ -1,5 +1,9 @@
|
||||
/inspectionProfiles/
|
||||
/codeStyles
|
||||
/dataSources.local.xml
|
||||
/deployment.xml
|
||||
/httpRequests/
|
||||
/shelf/
|
||||
/inspectionProfiles
|
||||
/misc.xml
|
||||
/shelf/
|
||||
/webServers.xml
|
||||
/workspace.xml
|
||||
|
1
.idea/Discord-History-Tracker.iml
generated
1
.idea/Discord-History-Tracker.iml
generated
@@ -8,6 +8,7 @@
|
||||
<excludeFolder url="file://$MODULE_DIR$/.vscode" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/bld" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/lib" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/app" />
|
||||
</content>
|
||||
<orderEntry type="jdk" jdkName="Python 3.8" jdkType="Python SDK" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
|
4
.idea/php.xml
generated
Normal file
4
.idea/php.xml
generated
Normal file
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="PhpProjectSharedConfiguration" php_language_level="7.4" />
|
||||
</project>
|
52
.vscode/tasks.json
vendored
52
.vscode/tasks.json
vendored
@@ -1,52 +0,0 @@
|
||||
{
|
||||
// See https://go.microsoft.com/fwlink/?LinkId=733558
|
||||
// for the documentation about the tasks.json format
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"label": "Build Raw",
|
||||
"type": "shell",
|
||||
"command": "python ./build.py --nominify --copytracker",
|
||||
"group": {
|
||||
"kind": "build",
|
||||
"isDefault": true
|
||||
},
|
||||
"presentation": {
|
||||
"reveal": "always",
|
||||
"panel": "shared",
|
||||
"echo": true,
|
||||
"focus": false,
|
||||
"showReuseMessage": false
|
||||
},
|
||||
"problemMatcher": []
|
||||
},
|
||||
{
|
||||
"label": "Build Minified",
|
||||
"type": "shell",
|
||||
"command": "python ./build.py --copytracker",
|
||||
"group": "build",
|
||||
"presentation": {
|
||||
"reveal": "always",
|
||||
"panel": "shared",
|
||||
"echo": true,
|
||||
"focus": false,
|
||||
"showReuseMessage": false
|
||||
},
|
||||
"problemMatcher": []
|
||||
},
|
||||
{
|
||||
"label": "Build Website",
|
||||
"type": "shell",
|
||||
"command": "python ./build.py --website",
|
||||
"group": "build",
|
||||
"presentation": {
|
||||
"reveal": "always",
|
||||
"panel": "shared",
|
||||
"echo": true,
|
||||
"focus": false,
|
||||
"showReuseMessage": false
|
||||
},
|
||||
"problemMatcher": []
|
||||
}
|
||||
]
|
||||
}
|
49
README.md
49
README.md
@@ -1,48 +1,45 @@
|
||||
# Welcome
|
||||
|
||||
This branch is dedicated to the browser-only version of **Discord History Tracker**. All you need to use it is either an up-to-date browser, or the [Discord desktop client](https://discord.com/download). Visit the [official website](https://dht.chylex.com/browser-only) for instructions.
|
||||
For instructions on how to **use Discord History Tracker**, visit the [official website](https://dht.chylex.com).
|
||||
|
||||
To **report an issue or suggestion**, first please see the [issues](https://github.com/chylex/Discord-History-Tracker/issues) page and make sure someone else hasn't already created a similar issue report. If you do find an existing issue, comment on it or add a reaction. Otherwise, either click [New Issue](https://github.com/chylex/Discord-History-Tracker/issues/new), or contact me via email [contact@chylex.com](mailto:contact@chylex.com) or Twitter [@chylexmc](https://twitter.com/chylexmc).
|
||||
|
||||
If you are interested in **building from source code**, continue reading the [build instructions](#Build-Instructions) below.
|
||||
|
||||
This branch is dedicated to the Discord History Tracker desktop app. If you are looking for the older browser-only version, visit the [master-browser-only](https://github.com/chylex/Discord-History-Tracker/tree/master-browser-only) branch.
|
||||
|
||||
# Build Instructions
|
||||
|
||||
### Setup
|
||||
|
||||
Fork the repository and clone it to your computer (if you've never used git, you can download the [GitHub Desktop](https://desktop.github.com) client to get started quickly).
|
||||
|
||||
By default, cloning will default to the `master` branch which is dedicated to the desktop app. Make sure to switch to the `master-browser-only` branch.
|
||||
Folder organization:
|
||||
* `app/` contains a Visual Studio solution for the desktop app
|
||||
* `web/` contains source code of the [official website](https://dht.chylex.com), which can be used as a template when making your own website
|
||||
|
||||
Now you can modify the source code:
|
||||
* `src/tracker/` contains JS files that are automatically combined into the **tracker bookmark/script**
|
||||
* `src/viewer/` contains HTML, CSS, JS files that are then combined into the **offline viewer page**
|
||||
* `lib/` contains utilities required to build the project
|
||||
* `web/` contains source code of the [official website](https://dht.chylex.com/browser-only), which can be used as a template when making your own website
|
||||
To start editing source code for the desktop app, install the [.NET 8 SDK](https://dotnet.microsoft.com/en-us/download/dotnet/8.0), and then open `app/DiscordHistoryTracker.sln` in [Visual Studio](https://visualstudio.microsoft.com/downloads/) or [Rider](https://www.jetbrains.com/rider/).
|
||||
|
||||
### Building
|
||||
|
||||
After you've done changes to the source code, you will need to build it. Before that, download and install:
|
||||
* (**required**) [Python 3](https://www.python.org/downloads)
|
||||
* Use to run the build script
|
||||
* (optional) [Node + npm](https://nodejs.org/en) & command line [uglify-js](https://www.npmjs.com/package/uglify-js)
|
||||
* Not required on Windows
|
||||
* Only required for optional [JS minification](#Minification) on Linux/Mac
|
||||
To build a `Debug` version of the desktop app, there are no additional requirements.
|
||||
|
||||
Now open the folder that contains `build.py` in a command line, and run `python build.py` to create a build with default settings. The following files will be created:
|
||||
* `bld/track.js` is the raw tracker script that can be pasted into a browser console
|
||||
* `bld/track.html` is the tracker script but sanitized for inclusion in HTML (see `web/index.php` for examples)
|
||||
* `bld/viewer.html` is the complete offline viewer
|
||||
To build a `Release` version of the desktop app, follow the instructions for your operating system.
|
||||
|
||||
You can tweak the build process using the following flags:
|
||||
* `python build.py --nominify` to disable [minification](#Minification)
|
||||
#### Release – Windows (64-bit)
|
||||
|
||||
### Minification
|
||||
1. Install [Powershell 5](https://docs.microsoft.com/en-us/powershell/scripting/install/installing-powershell-on-windows) or newer (on Windows 10, the included version of Powershell should be enough)
|
||||
|
||||
The build process automatically minifies JS using `UglifyJS@3`, and CSS using a custom minifier.
|
||||
Run the `app/build.bat` script, and read the [Distribution](#distribution) section below.
|
||||
|
||||
* If the `--nominify` flag is used, minification will be completely disabled
|
||||
* If `uglify-js` is not available from the command line, JS minification will be skipped
|
||||
* When building on Windows 64-bit, the build script will use the included Node runner and packages
|
||||
* When building on Windows 32-bit, you will need to download [Node 32-bit](https://nodejs.org/en/download) and replace the included one in `lib/`
|
||||
* When building on Linux/Mac, the build script will attempt to find `uglifyjs` in the command line
|
||||
#### Release – Other Operating Systems
|
||||
|
||||
1. Install the `zip` package from your repository
|
||||
|
||||
Run the `app/build.sh` script, and read the [Distribution](#distribution) section below.
|
||||
|
||||
#### Distribution
|
||||
|
||||
The mentioned build scripts will prepare `Release` builds ready for distribution. Once the script finishes, the `app/bin` folder will contain self-contained executables for each major operating system, and a portable version that works on all other systems but requires .NET 8 to be installed.
|
||||
|
||||
Note that when building on Windows, the generated `.zip` files for Linux and Mac will not have correct file permissions, so it will not be possible to run them by double-clicking the executable. Since .NET 8 fixed several issues with publishing Windows executables on Linux, I recommend using Linux to build the app for all operating systems.
|
||||
|
7
app/.gitignore
vendored
Normal file
7
app/.gitignore
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
.vscode/
|
||||
.vs/
|
||||
|
||||
bin/
|
||||
obj/
|
||||
|
||||
*.user
|
11
app/.idea/.idea.DiscordHistoryTracker/.idea/.gitignore
generated
vendored
Normal file
11
app/.idea/.idea.DiscordHistoryTracker/.idea/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
/.idea.DiscordHistoryTracker.iml
|
||||
/contentModel.xml
|
||||
/dataSources
|
||||
/dataSources.local.xml
|
||||
/dictionaries
|
||||
/httpRequests/
|
||||
/misc.xml
|
||||
/modules.xml
|
||||
/projectSettingsUpdater.xml
|
||||
/shelf/
|
||||
/workspace.xml
|
1
app/.idea/.idea.DiscordHistoryTracker/.idea/.name
generated
Normal file
1
app/.idea/.idea.DiscordHistoryTracker/.idea/.name
generated
Normal file
@@ -0,0 +1 @@
|
||||
DiscordHistoryTracker
|
28
app/.idea/.idea.DiscordHistoryTracker/.idea/avalonia.xml
generated
Normal file
28
app/.idea/.idea.DiscordHistoryTracker/.idea/avalonia.xml
generated
Normal file
@@ -0,0 +1,28 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="AvaloniaProject">
|
||||
<option name="projectPerEditor">
|
||||
<map>
|
||||
<entry key="Desktop/App.axaml" value="Desktop/Desktop.csproj" />
|
||||
<entry key="Desktop/Dialogs/CheckBox/CheckBoxDialog.axaml" value="Desktop/Desktop.csproj" />
|
||||
<entry key="Desktop/Dialogs/Message/MessageDialog.axaml" value="Desktop/Desktop.csproj" />
|
||||
<entry key="Desktop/Dialogs/Progress/ProgressDialog.axaml" value="Desktop/Desktop.csproj" />
|
||||
<entry key="Desktop/Dialogs/TextBox/TextBoxDialog.axaml" value="Desktop/Desktop.csproj" />
|
||||
<entry key="Desktop/Main/AboutWindow.axaml" value="Desktop/Desktop.csproj" />
|
||||
<entry key="Desktop/Main/Controls/AttachmentFilterPanel.axaml" value="Desktop/Desktop.csproj" />
|
||||
<entry key="Desktop/Main/Controls/MessageFilterPanel.axaml" value="Desktop/Desktop.csproj" />
|
||||
<entry key="Desktop/Main/Controls/ServerConfigurationPanel.axaml" value="Desktop/Desktop.csproj" />
|
||||
<entry key="Desktop/Main/Controls/StatusBar.axaml" value="Desktop/Desktop.csproj" />
|
||||
<entry key="Desktop/Main/MainWindow.axaml" value="Desktop/Desktop.csproj" />
|
||||
<entry key="Desktop/Main/Pages/AdvancedPage.axaml" value="Desktop/Desktop.csproj" />
|
||||
<entry key="Desktop/Main/Pages/AttachmentsPage.axaml" value="Desktop/Desktop.csproj" />
|
||||
<entry key="Desktop/Main/Pages/DatabasePage.axaml" value="Desktop/Desktop.csproj" />
|
||||
<entry key="Desktop/Main/Pages/DebugPage.axaml" value="Desktop/Desktop.csproj" />
|
||||
<entry key="Desktop/Main/Pages/TrackingPage.axaml" value="Desktop/Desktop.csproj" />
|
||||
<entry key="Desktop/Main/Pages/ViewerPage.axaml" value="Desktop/Desktop.csproj" />
|
||||
<entry key="Desktop/Main/Screens/MainContentScreen.axaml" value="Desktop/Desktop.csproj" />
|
||||
<entry key="Desktop/Main/Screens/WelcomeScreen.axaml" value="Desktop/Desktop.csproj" />
|
||||
</map>
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
445
app/.idea/.idea.DiscordHistoryTracker/.idea/codeStyles/Project.xml
generated
Normal file
445
app/.idea/.idea.DiscordHistoryTracker/.idea/codeStyles/Project.xml
generated
Normal file
@@ -0,0 +1,445 @@
|
||||
<component name="ProjectCodeStyleConfiguration">
|
||||
<code_scheme name="Project" version="173">
|
||||
<option name="AUTODETECT_INDENTS" value="false" />
|
||||
<option name="OTHER_INDENT_OPTIONS">
|
||||
<value>
|
||||
<option name="INDENT_SIZE" value="2" />
|
||||
<option name="TAB_SIZE" value="2" />
|
||||
</value>
|
||||
</option>
|
||||
<option name="LINE_SEPARATOR" value=" " />
|
||||
<option name="RIGHT_MARGIN" value="999" />
|
||||
<option name="FORMATTER_TAGS_ENABLED" value="true" />
|
||||
<CssCodeStyleSettings>
|
||||
<option name="HEX_COLOR_LOWER_CASE" value="true" />
|
||||
</CssCodeStyleSettings>
|
||||
<DB2CodeStyleSettings version="6">
|
||||
<option name="USE_GENERIC_STYLE" value="true" />
|
||||
</DB2CodeStyleSettings>
|
||||
<DerbyCodeStyleSettings version="6">
|
||||
<option name="USE_GENERIC_STYLE" value="true" />
|
||||
</DerbyCodeStyleSettings>
|
||||
<GoCodeStyleSettings>
|
||||
<option name="MOVE_ALL_STDLIB_IMPORTS_IN_ONE_GROUP" value="true" />
|
||||
<option name="GROUP_STDLIB_IMPORTS" value="true" />
|
||||
<option name="WRAP_COMP_LIT" value="5" />
|
||||
<option name="WRAP_FUNC_PARAMS" value="5" />
|
||||
<option name="WRAP_FUNC_RESULT" value="5" />
|
||||
</GoCodeStyleSettings>
|
||||
<H2CodeStyleSettings version="6">
|
||||
<option name="USE_GENERIC_STYLE" value="true" />
|
||||
</H2CodeStyleSettings>
|
||||
<HSQLCodeStyleSettings version="6">
|
||||
<option name="USE_GENERIC_STYLE" value="true" />
|
||||
</HSQLCodeStyleSettings>
|
||||
<HTMLCodeStyleSettings>
|
||||
<option name="HTML_ALIGN_TEXT" value="true" />
|
||||
<option name="HTML_SPACE_INSIDE_EMPTY_TAG" value="true" />
|
||||
<option name="HTML_ELEMENTS_TO_REMOVE_NEW_LINE_BEFORE" value="" />
|
||||
<option name="HTML_DO_NOT_INDENT_CHILDREN_OF" value="" />
|
||||
</HTMLCodeStyleSettings>
|
||||
<JSCodeStyleSettings version="0">
|
||||
<option name="FORCE_SEMICOLON_STYLE" value="true" />
|
||||
<option name="SPACE_WITHIN_ARRAY_INITIALIZER_BRACKETS" value="true" />
|
||||
<option name="SPACE_BEFORE_FUNCTION_LEFT_PARENTH" value="false" />
|
||||
<option name="FORCE_QUOTE_STYlE" value="true" />
|
||||
<option name="SPACES_WITHIN_OBJECT_LITERAL_BRACES" value="true" />
|
||||
<option name="SPACES_WITHIN_IMPORTS" value="true" />
|
||||
<option name="USE_CHAINED_CALLS_GROUP_INDENTS" value="true" />
|
||||
<option name="SPACE_BEFORE_ASYNC_ARROW_LPAREN" value="false" />
|
||||
</JSCodeStyleSettings>
|
||||
<JSON>
|
||||
<option name="OBJECT_WRAPPING" value="5" />
|
||||
<option name="ARRAY_WRAPPING" value="5" />
|
||||
</JSON>
|
||||
<JavaCodeStyleSettings>
|
||||
<option name="INSERT_INNER_CLASS_IMPORTS" value="true" />
|
||||
<option name="CLASS_COUNT_TO_USE_IMPORT_ON_DEMAND" value="99" />
|
||||
<option name="NAMES_COUNT_TO_USE_IMPORT_ON_DEMAND" value="99" />
|
||||
<option name="PACKAGES_TO_USE_IMPORT_ON_DEMAND">
|
||||
<value />
|
||||
</option>
|
||||
<option name="IMPORT_LAYOUT_TABLE">
|
||||
<value>
|
||||
<package name="" withSubpackages="true" static="false" />
|
||||
<package name="javax" withSubpackages="true" static="false" />
|
||||
<package name="java" withSubpackages="true" static="false" />
|
||||
<package name="" withSubpackages="true" static="true" />
|
||||
</value>
|
||||
</option>
|
||||
</JavaCodeStyleSettings>
|
||||
<JetCodeStyleSettings>
|
||||
<option name="PACKAGES_TO_USE_STAR_IMPORTS">
|
||||
<value>
|
||||
<package name="kotlinx.android.synthetic" alias="false" withSubpackages="true" />
|
||||
</value>
|
||||
</option>
|
||||
<option name="ALIGN_IN_COLUMNS_CASE_BRANCH" value="true" />
|
||||
<option name="NAME_COUNT_TO_USE_STAR_IMPORT" value="2147483647" />
|
||||
<option name="NAME_COUNT_TO_USE_STAR_IMPORT_FOR_MEMBERS" value="2147483647" />
|
||||
<option name="IMPORT_NESTED_CLASSES" value="true" />
|
||||
<option name="WRAP_ELVIS_EXPRESSIONS" value="0" />
|
||||
<option name="ALLOW_TRAILING_COMMA" value="true" />
|
||||
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
|
||||
</JetCodeStyleSettings>
|
||||
<LessCodeStyleSettings>
|
||||
<option name="HEX_COLOR_LOWER_CASE" value="true" />
|
||||
</LessCodeStyleSettings>
|
||||
<MSSQLCodeStyleSettings version="6">
|
||||
<option name="USE_GENERIC_STYLE" value="true" />
|
||||
</MSSQLCodeStyleSettings>
|
||||
<MySQLCodeStyleSettings version="6">
|
||||
<option name="USE_GENERIC_STYLE" value="true" />
|
||||
</MySQLCodeStyleSettings>
|
||||
<Objective-C>
|
||||
<option name="INDENT_DIRECTIVE_AS_CODE" value="true" />
|
||||
<option name="KEEP_STRUCTURES_IN_ONE_LINE" value="true" />
|
||||
<option name="KEEP_CASE_EXPRESSIONS_IN_ONE_LINE" value="true" />
|
||||
<option name="SPACE_BEFORE_INIT_LIST" value="true" />
|
||||
<option name="SPACE_AFTER_DICTIONARY_LITERAL_COLON" value="false" />
|
||||
</Objective-C>
|
||||
<OracleCodeStyleSettings version="6">
|
||||
<option name="USE_GENERIC_STYLE" value="true" />
|
||||
</OracleCodeStyleSettings>
|
||||
<PHPCodeStyleSettings>
|
||||
<option name="ALIGN_KEY_VALUE_PAIRS" value="true" />
|
||||
<option name="CONCAT_SPACES" value="false" />
|
||||
<option name="COMMA_AFTER_LAST_ARRAY_ELEMENT" value="true" />
|
||||
<option name="PHPDOC_BLANK_LINE_BEFORE_TAGS" value="true" />
|
||||
<option name="LOWER_CASE_BOOLEAN_CONST" value="true" />
|
||||
<option name="LOWER_CASE_NULL_CONST" value="true" />
|
||||
<option name="ELSE_IF_STYLE" value="COMBINE" />
|
||||
<option name="VARIABLE_NAMING_STYLE" value="SNAKE_CASE" />
|
||||
<option name="KEEP_BLANK_LINES_AFTER_LBRACE" value="0" />
|
||||
<option name="SPACE_BEFORE_CLOSURE_LEFT_PARENTHESIS" value="false" />
|
||||
<option name="FORCE_SHORT_DECLARATION_ARRAY_STYLE" value="true" />
|
||||
<option name="NEW_LINE_AFTER_PHP_OPENING_TAG" value="true" />
|
||||
<option name="SPACE_AROUND_ASSIGNMENT_IN_DECLARE" value="true" />
|
||||
</PHPCodeStyleSettings>
|
||||
<PostgresCodeStyleSettings version="6">
|
||||
<option name="USE_GENERIC_STYLE" value="true" />
|
||||
</PostgresCodeStyleSettings>
|
||||
<Properties>
|
||||
<option name="KEEP_BLANK_LINES" value="true" />
|
||||
</Properties>
|
||||
<Python>
|
||||
<option name="SPACE_AROUND_EQ_IN_NAMED_PARAMETER" value="true" />
|
||||
<option name="SPACE_AROUND_EQ_IN_KEYWORD_ARGUMENT" value="true" />
|
||||
<option name="NEW_LINE_AFTER_COLON" value="true" />
|
||||
<option name="DICT_WRAPPING" value="5" />
|
||||
<option name="DICT_NEW_LINE_AFTER_LEFT_BRACE" value="true" />
|
||||
<option name="DICT_NEW_LINE_BEFORE_RIGHT_BRACE" value="true" />
|
||||
</Python>
|
||||
<RsCodeStyleSettings>
|
||||
<option name="ALIGN_RET_TYPE" value="false" />
|
||||
<option name="ALIGN_TYPE_PARAMS" value="true" />
|
||||
<option name="ALLOW_ONE_LINE_MATCH" value="true" />
|
||||
<option name="SPACE_AROUND_ASSOC_TYPE_BINDING" value="true" />
|
||||
</RsCodeStyleSettings>
|
||||
<Ruby>
|
||||
<option name="INDENT_PRIVATE_METHODS" value="true" />
|
||||
<option name="INDENT_PROTECTED_METHODS" value="true" />
|
||||
<option name="INDENT_PUBLIC_METHODS" value="true" />
|
||||
<option name="INDENT_WHEN_CASES" value="true" />
|
||||
<option name="CHAIN_CALLS_ALIGNMENT" value="2" />
|
||||
</Ruby>
|
||||
<SQLiteCodeStyleSettings version="6">
|
||||
<option name="USE_GENERIC_STYLE" value="true" />
|
||||
</SQLiteCodeStyleSettings>
|
||||
<ScssCodeStyleSettings>
|
||||
<option name="HEX_COLOR_LOWER_CASE" value="true" />
|
||||
</ScssCodeStyleSettings>
|
||||
<SqlCodeStyleSettings version="6">
|
||||
<option name="KEYWORD_CASE" value="2" />
|
||||
<option name="TYPE_CASE" value="2" />
|
||||
<option name="CUSTOM_TYPE_CASE" value="2" />
|
||||
<option name="SUBQUERY_CONTENT" value="1" />
|
||||
<option name="SUBQUERY_CLOSING" value="1" />
|
||||
<option name="INSERT_TABLE_EL_LINE" value="0" />
|
||||
<option name="INSERT_EL_WRAP" value="2" />
|
||||
<option name="SET_EL_WRAP" value="2" />
|
||||
<option name="SET_ALIGN_EQUAL_SIGN" value="false" />
|
||||
<option name="FROM_EL_WRAP" value="2" />
|
||||
<option name="FROM_ALIGN_JOIN_TABLES" value="true" />
|
||||
<option name="FROM_INDENT_JOIN" value="false" />
|
||||
<option name="FROM_ONLY_JOIN_INDENT" value="2" />
|
||||
<option name="WHERE_EL_WRAP" value="2" />
|
||||
<option name="TABLE_OPENING" value="1" />
|
||||
<option name="TABLE_CONTENT" value="2" />
|
||||
<option name="TABLE_CLOSING" value="3" />
|
||||
<option name="TABLE_DEFAULTS_ALIGN" value="false" />
|
||||
<option name="TABLE_NULLABILITIES_ALIGN" value="false" />
|
||||
<option name="CONSTRAINT_WRAP_1" value="false" />
|
||||
<option name="CONSTRAINT_WRAP_3" value="true" />
|
||||
<option name="CONSTRAINT_WRAP_4" value="true" />
|
||||
<option name="VIEW_INDENT_QUERY" value="true" />
|
||||
<option name="EXPR_CASE_WHEN_WRAP" value="false" />
|
||||
</SqlCodeStyleSettings>
|
||||
<SybaseCodeStyleSettings version="6">
|
||||
<option name="USE_GENERIC_STYLE" value="true" />
|
||||
</SybaseCodeStyleSettings>
|
||||
<TypeScriptCodeStyleSettings version="0">
|
||||
<option name="SPACE_BEFORE_FUNCTION_LEFT_PARENTH" value="false" />
|
||||
<option name="ENFORCE_TRAILING_COMMA" value="WhenMultiline" />
|
||||
<option name="SPACES_WITHIN_IMPORTS" value="true" />
|
||||
<option name="USE_CHAINED_CALLS_GROUP_INDENTS" value="true" />
|
||||
<option name="SPACE_BEFORE_ASYNC_ARROW_LPAREN" value="false" />
|
||||
</TypeScriptCodeStyleSettings>
|
||||
<XML>
|
||||
<option name="XML_SPACE_INSIDE_EMPTY_TAG" value="true" />
|
||||
</XML>
|
||||
<codeStyleSettings language="CMake">
|
||||
<indentOptions>
|
||||
<option name="CONTINUATION_INDENT_SIZE" value="4" />
|
||||
<option name="USE_TAB_CHARACTER" value="true" />
|
||||
<option name="SMART_TABS" value="true" />
|
||||
<option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
|
||||
</indentOptions>
|
||||
</codeStyleSettings>
|
||||
<codeStyleSettings language="CSS">
|
||||
<indentOptions>
|
||||
<option name="INDENT_SIZE" value="2" />
|
||||
<option name="TAB_SIZE" value="2" />
|
||||
<option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
|
||||
</indentOptions>
|
||||
</codeStyleSettings>
|
||||
<codeStyleSettings language="Groovy">
|
||||
<option name="ELSE_ON_NEW_LINE" value="true" />
|
||||
<option name="SPACE_WITHIN_ARRAY_INITIALIZER_BRACES" value="true" />
|
||||
<option name="IF_BRACE_FORCE" value="3" />
|
||||
<option name="DOWHILE_BRACE_FORCE" value="3" />
|
||||
<option name="WHILE_BRACE_FORCE" value="3" />
|
||||
<option name="FOR_BRACE_FORCE" value="3" />
|
||||
<indentOptions>
|
||||
<option name="CONTINUATION_INDENT_SIZE" value="4" />
|
||||
<option name="USE_TAB_CHARACTER" value="true" />
|
||||
<option name="SMART_TABS" value="true" />
|
||||
<option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
|
||||
</indentOptions>
|
||||
</codeStyleSettings>
|
||||
<codeStyleSettings language="HTML">
|
||||
<option name="LINE_COMMENT_AT_FIRST_COLUMN" value="false" />
|
||||
<option name="BLOCK_COMMENT_AT_FIRST_COLUMN" value="false" />
|
||||
<indentOptions>
|
||||
<option name="INDENT_SIZE" value="2" />
|
||||
<option name="TAB_SIZE" value="2" />
|
||||
<option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
|
||||
</indentOptions>
|
||||
</codeStyleSettings>
|
||||
<codeStyleSettings language="JAVA">
|
||||
<option name="RIGHT_MARGIN" value="999" />
|
||||
<option name="BLANK_LINES_AFTER_PACKAGE" value="0" />
|
||||
<option name="BLANK_LINES_BEFORE_IMPORTS" value="0" />
|
||||
<option name="ELSE_ON_NEW_LINE" value="true" />
|
||||
<option name="SPACE_WITHIN_ARRAY_INITIALIZER_BRACES" value="true" />
|
||||
<option name="SPACE_AFTER_TYPE_CAST" value="false" />
|
||||
<option name="SPACE_BEFORE_SYNCHRONIZED_PARENTHESES" value="false" />
|
||||
<option name="KEEP_SIMPLE_METHODS_IN_ONE_LINE" value="true" />
|
||||
<option name="KEEP_SIMPLE_LAMBDAS_IN_ONE_LINE" value="true" />
|
||||
<option name="KEEP_SIMPLE_CLASSES_IN_ONE_LINE" value="true" />
|
||||
<option name="IF_BRACE_FORCE" value="3" />
|
||||
<option name="DOWHILE_BRACE_FORCE" value="3" />
|
||||
<option name="WHILE_BRACE_FORCE" value="3" />
|
||||
<option name="FOR_BRACE_FORCE" value="3" />
|
||||
<indentOptions>
|
||||
<option name="CONTINUATION_INDENT_SIZE" value="4" />
|
||||
<option name="USE_TAB_CHARACTER" value="true" />
|
||||
<option name="SMART_TABS" value="true" />
|
||||
<option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
|
||||
</indentOptions>
|
||||
</codeStyleSettings>
|
||||
<codeStyleSettings language="JSON">
|
||||
<option name="KEEP_BLANK_LINES_IN_CODE" value="1" />
|
||||
<option name="SPACE_WITHIN_BRACKETS" value="true" />
|
||||
<option name="SPACE_WITHIN_BRACES" value="true" />
|
||||
<indentOptions>
|
||||
<option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
|
||||
</indentOptions>
|
||||
</codeStyleSettings>
|
||||
<codeStyleSettings language="JavaScript">
|
||||
<option name="ELSE_ON_NEW_LINE" value="true" />
|
||||
<option name="ALIGN_MULTILINE_TERNARY_OPERATION" value="true" />
|
||||
<option name="ALIGN_MULTILINE_ARRAY_INITIALIZER_EXPRESSION" value="true" />
|
||||
<option name="IF_BRACE_FORCE" value="3" />
|
||||
<option name="DOWHILE_BRACE_FORCE" value="3" />
|
||||
<option name="WHILE_BRACE_FORCE" value="3" />
|
||||
<option name="FOR_BRACE_FORCE" value="3" />
|
||||
<indentOptions>
|
||||
<option name="USE_TAB_CHARACTER" value="true" />
|
||||
<option name="SMART_TABS" value="true" />
|
||||
<option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
|
||||
</indentOptions>
|
||||
</codeStyleSettings>
|
||||
<codeStyleSettings language="LESS">
|
||||
<indentOptions>
|
||||
<option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
|
||||
</indentOptions>
|
||||
</codeStyleSettings>
|
||||
<codeStyleSettings language="Lua">
|
||||
<indentOptions>
|
||||
<option name="INDENT_SIZE" value="2" />
|
||||
<option name="TAB_SIZE" value="2" />
|
||||
<option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
|
||||
</indentOptions>
|
||||
</codeStyleSettings>
|
||||
<codeStyleSettings language="ObjectiveC">
|
||||
<option name="LINE_COMMENT_AT_FIRST_COLUMN" value="false" />
|
||||
<option name="BLOCK_COMMENT_AT_FIRST_COLUMN" value="false" />
|
||||
<option name="LINE_COMMENT_ADD_SPACE" value="true" />
|
||||
<option name="ELSE_ON_NEW_LINE" value="true" />
|
||||
<option name="SPACE_WITHIN_ARRAY_INITIALIZER_BRACES" value="true" />
|
||||
<option name="IF_BRACE_FORCE" value="3" />
|
||||
<option name="DOWHILE_BRACE_FORCE" value="3" />
|
||||
<option name="WHILE_BRACE_FORCE" value="3" />
|
||||
<option name="FOR_BRACE_FORCE" value="3" />
|
||||
<indentOptions>
|
||||
<option name="CONTINUATION_INDENT_SIZE" value="4" />
|
||||
<option name="USE_TAB_CHARACTER" value="true" />
|
||||
<option name="SMART_TABS" value="true" />
|
||||
<option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
|
||||
</indentOptions>
|
||||
</codeStyleSettings>
|
||||
<codeStyleSettings language="PHP">
|
||||
<option name="LINE_COMMENT_AT_FIRST_COLUMN" value="false" />
|
||||
<option name="KEEP_FIRST_COLUMN_COMMENT" value="false" />
|
||||
<option name="KEEP_CONTROL_STATEMENT_IN_ONE_LINE" value="false" />
|
||||
<option name="KEEP_BLANK_LINES_IN_DECLARATIONS" value="1" />
|
||||
<option name="KEEP_BLANK_LINES_IN_CODE" value="1" />
|
||||
<option name="KEEP_BLANK_LINES_BEFORE_RBRACE" value="0" />
|
||||
<option name="CLASS_BRACE_STYLE" value="1" />
|
||||
<option name="METHOD_BRACE_STYLE" value="1" />
|
||||
<option name="ELSE_ON_NEW_LINE" value="true" />
|
||||
<option name="SPECIAL_ELSE_IF_TREATMENT" value="true" />
|
||||
<option name="ALIGN_MULTILINE_CHAINED_METHODS" value="true" />
|
||||
<option name="ALIGN_MULTILINE_PARAMETERS_IN_CALLS" value="true" />
|
||||
<option name="ALIGN_MULTILINE_ARRAY_INITIALIZER_EXPRESSION" value="true" />
|
||||
<option name="SPACE_BEFORE_ARRAY_INITIALIZER_LBRACE" value="true" />
|
||||
<option name="KEEP_SIMPLE_METHODS_IN_ONE_LINE" value="true" />
|
||||
<option name="KEEP_SIMPLE_CLASSES_IN_ONE_LINE" value="true" />
|
||||
<option name="IF_BRACE_FORCE" value="3" />
|
||||
<option name="DOWHILE_BRACE_FORCE" value="3" />
|
||||
<option name="WHILE_BRACE_FORCE" value="3" />
|
||||
<option name="FOR_BRACE_FORCE" value="3" />
|
||||
<indentOptions>
|
||||
<option name="INDENT_SIZE" value="2" />
|
||||
<option name="TAB_SIZE" value="2" />
|
||||
<option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
|
||||
</indentOptions>
|
||||
</codeStyleSettings>
|
||||
<codeStyleSettings language="Puppet">
|
||||
<indentOptions>
|
||||
<option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
|
||||
</indentOptions>
|
||||
</codeStyleSettings>
|
||||
<codeStyleSettings language="Python">
|
||||
<indentOptions>
|
||||
<option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
|
||||
</indentOptions>
|
||||
</codeStyleSettings>
|
||||
<codeStyleSettings language="RHTML">
|
||||
<indentOptions>
|
||||
<option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
|
||||
</indentOptions>
|
||||
</codeStyleSettings>
|
||||
<codeStyleSettings language="Rust">
|
||||
<option name="RIGHT_MARGIN" value="140" />
|
||||
<option name="ALIGN_MULTILINE_CHAINED_METHODS" value="true" />
|
||||
<indentOptions>
|
||||
<option name="USE_TAB_CHARACTER" value="true" />
|
||||
<option name="SMART_TABS" value="true" />
|
||||
<option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
|
||||
</indentOptions>
|
||||
</codeStyleSettings>
|
||||
<codeStyleSettings language="SASS">
|
||||
<indentOptions>
|
||||
<option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
|
||||
</indentOptions>
|
||||
</codeStyleSettings>
|
||||
<codeStyleSettings language="SCSS">
|
||||
<indentOptions>
|
||||
<option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
|
||||
</indentOptions>
|
||||
</codeStyleSettings>
|
||||
<codeStyleSettings language="SQL">
|
||||
<indentOptions>
|
||||
<option name="USE_TAB_CHARACTER" value="true" />
|
||||
<option name="SMART_TABS" value="true" />
|
||||
<option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
|
||||
</indentOptions>
|
||||
</codeStyleSettings>
|
||||
<codeStyleSettings language="Shell Script">
|
||||
<indentOptions>
|
||||
<option name="INDENT_SIZE" value="4" />
|
||||
<option name="TAB_SIZE" value="4" />
|
||||
<option name="USE_TAB_CHARACTER" value="true" />
|
||||
</indentOptions>
|
||||
</codeStyleSettings>
|
||||
<codeStyleSettings language="TOML">
|
||||
<indentOptions>
|
||||
<option name="CONTINUATION_INDENT_SIZE" value="4" />
|
||||
<option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
|
||||
</indentOptions>
|
||||
</codeStyleSettings>
|
||||
<codeStyleSettings language="TypeScript">
|
||||
<option name="ELSE_ON_NEW_LINE" value="true" />
|
||||
<option name="ALIGN_MULTILINE_TERNARY_OPERATION" value="true" />
|
||||
<option name="ALIGN_MULTILINE_ARRAY_INITIALIZER_EXPRESSION" value="true" />
|
||||
<option name="IF_BRACE_FORCE" value="3" />
|
||||
<option name="DOWHILE_BRACE_FORCE" value="3" />
|
||||
<option name="WHILE_BRACE_FORCE" value="3" />
|
||||
<option name="FOR_BRACE_FORCE" value="3" />
|
||||
<indentOptions>
|
||||
<option name="USE_TAB_CHARACTER" value="true" />
|
||||
<option name="SMART_TABS" value="true" />
|
||||
<option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
|
||||
</indentOptions>
|
||||
</codeStyleSettings>
|
||||
<codeStyleSettings language="XML">
|
||||
<indentOptions>
|
||||
<option name="INDENT_SIZE" value="2" />
|
||||
<option name="TAB_SIZE" value="2" />
|
||||
<option name="SMART_TABS" value="true" />
|
||||
<option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
|
||||
</indentOptions>
|
||||
</codeStyleSettings>
|
||||
<codeStyleSettings language="go">
|
||||
<option name="CALL_PARAMETERS_WRAP" value="5" />
|
||||
<indentOptions>
|
||||
<option name="SMART_TABS" value="true" />
|
||||
<option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
|
||||
</indentOptions>
|
||||
</codeStyleSettings>
|
||||
<codeStyleSettings language="kotlin">
|
||||
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
|
||||
<option name="LINE_COMMENT_AT_FIRST_COLUMN" value="false" />
|
||||
<option name="BLOCK_COMMENT_AT_FIRST_COLUMN" value="false" />
|
||||
<option name="LINE_COMMENT_ADD_SPACE" value="true" />
|
||||
<option name="ELSE_ON_NEW_LINE" value="true" />
|
||||
<option name="METHOD_ANNOTATION_WRAP" value="0" />
|
||||
<option name="CLASS_ANNOTATION_WRAP" value="0" />
|
||||
<option name="FIELD_ANNOTATION_WRAP" value="0" />
|
||||
<indentOptions>
|
||||
<option name="CONTINUATION_INDENT_SIZE" value="4" />
|
||||
<option name="USE_TAB_CHARACTER" value="true" />
|
||||
<option name="SMART_TABS" value="true" />
|
||||
<option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
|
||||
</indentOptions>
|
||||
</codeStyleSettings>
|
||||
<codeStyleSettings language="liquid">
|
||||
<indentOptions>
|
||||
<option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
|
||||
</indentOptions>
|
||||
</codeStyleSettings>
|
||||
<codeStyleSettings language="ruby">
|
||||
<option name="SPACE_WITHIN_BRACES" value="true" />
|
||||
<indentOptions>
|
||||
<option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
|
||||
</indentOptions>
|
||||
</codeStyleSettings>
|
||||
<codeStyleSettings language="yaml">
|
||||
<indentOptions>
|
||||
<option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
|
||||
</indentOptions>
|
||||
</codeStyleSettings>
|
||||
</code_scheme>
|
||||
</component>
|
5
app/.idea/.idea.DiscordHistoryTracker/.idea/codeStyles/codeStyleConfig.xml
generated
Normal file
5
app/.idea/.idea.DiscordHistoryTracker/.idea/codeStyles/codeStyleConfig.xml
generated
Normal file
@@ -0,0 +1,5 @@
|
||||
<component name="ProjectCodeStyleConfiguration">
|
||||
<state>
|
||||
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
|
||||
</state>
|
||||
</component>
|
12
app/.idea/.idea.DiscordHistoryTracker/.idea/dataSources.xml
generated
Normal file
12
app/.idea/.idea.DiscordHistoryTracker/.idea/dataSources.xml
generated
Normal file
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="DataSourceManagerImpl" format="xml" multifile-model="true">
|
||||
<data-source source="LOCAL" name="empty.dht" read-only="true" uuid="e01b8deb-489e-470a-9011-f6548927191e">
|
||||
<driver-ref>sqlite.xerial</driver-ref>
|
||||
<synchronize>true</synchronize>
|
||||
<jdbc-driver>org.sqlite.JDBC</jdbc-driver>
|
||||
<jdbc-url>jdbc:sqlite:$PROJECT_DIR$/empty.dht</jdbc-url>
|
||||
<working-dir>$ProjectFileDir$</working-dir>
|
||||
</data-source>
|
||||
</component>
|
||||
</project>
|
6
app/.idea/.idea.DiscordHistoryTracker/.idea/encodings.xml
generated
Normal file
6
app/.idea/.idea.DiscordHistoryTracker/.idea/encodings.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="Encoding" defaultCharsetForPropertiesFiles="UTF-8">
|
||||
<file url="PROJECT" charset="UTF-8" />
|
||||
</component>
|
||||
</project>
|
10
app/.idea/.idea.DiscordHistoryTracker/.idea/indexLayout.xml
generated
Normal file
10
app/.idea/.idea.DiscordHistoryTracker/.idea/indexLayout.xml
generated
Normal file
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="UserContentModel">
|
||||
<attachedFolders>
|
||||
<Path>Resources</Path>
|
||||
</attachedFolders>
|
||||
<explicitIncludes />
|
||||
<explicitExcludes />
|
||||
</component>
|
||||
</project>
|
850
app/.idea/.idea.DiscordHistoryTracker/.idea/inspectionProfiles/Project.xml
generated
Normal file
850
app/.idea/.idea.DiscordHistoryTracker/.idea/inspectionProfiles/Project.xml
generated
Normal file
@@ -0,0 +1,850 @@
|
||||
<component name="InspectionProjectProfileManager">
|
||||
<profile version="1.0">
|
||||
<option name="myName" value="Project" />
|
||||
<inspection_tool class="AbstractMethodCallInConstructor" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="AccessToNonThreadSafeStaticFieldFromInstance" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="nonThreadSafeClasses">
|
||||
<value />
|
||||
</option>
|
||||
<option name="nonThreadSafeTypes" value="" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="AccessToStaticFieldLockedOnInstance" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="AddOperatorModifier" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="AmbiguousFieldAccess" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="AmbiguousMethodCall" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="AnonymousInnerClassMayBeStatic" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ArrayEquality" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="AssertEqualsCalledOnArray" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="AssertsWithoutMessagesTestNG" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="AssignmentOrReturnOfFieldWithMutableType" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="AssignmentToCatchBlockParameter" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="AssignmentToLambdaParameter" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="AssignmentToMethodParameter" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="ignoreTransformationOfOriginalParameter" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="AssignmentToStaticFieldFromInstanceMethod" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="AssignmentToSuperclassField" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="AssignmentUsedAsCondition" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="AutoBoxing" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="ignoreAddedToCollection" value="false" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="AutoCloseableResource" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="ignoredTypes" value="java.util.stream.Stream,java.util.stream.IntStream,java.util.stream.LongStream,java.util.stream.DoubleStream,net.minecraft.client.Minecraft,net.minecraft.client.MainWindow" />
|
||||
<option name="METHOD_MATCHER_CONFIG" value="java.util.Formatter,format,java.io.Writer,append,com.google.common.base.Preconditions,checkNotNull,org.hibernate.Session,close,java.io.PrintWriter,printf,net.minecraft.client.MinecraftClient,getInstance|getWindow" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="AutoUnboxing" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="AwaitNotInLoop" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="AwaitWithoutCorrespondingSignal" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="BadOddness" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="BigDecimalEquals" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="BigDecimalLegacyMethod" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="BooleanExpressionMayBeConditional" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="CallToNativeMethodWhileLocked" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="CallToSimpleGetterInClass" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="ignoreGetterCallsOnOtherObjects" value="false" />
|
||||
<option name="onlyReportPrivateGetter" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="CallToSimpleSetterInClass" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="ignoreSetterCallsOnOtherObjects" value="false" />
|
||||
<option name="onlyReportPrivateSetter" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="CallToStringConcatCanBeReplacedByOperator" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="CallableParameterUseCaseInTypeContextInspection" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="CascadeStringReplacementInspection" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="USE_SHORT_ARRAYS_SYNTAX" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="CastConflictsWithInstanceof" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="CastToIncompatibleInterface" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ChainedEquality" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ChannelResource" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="insideTryAllowed" value="false" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="ClassIndependentOfModule" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ClassLoaderInstantiation" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ClassMayBeInterface" enabled="true" level="WEAK WARNING" enabled_by_default="true">
|
||||
<option name="reportClassesWithNonAbstractMethods" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="ClassMethodNameMatchesFieldNameInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="ClassNameDiffersFromFileName" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ClassNestingDepth" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="m_limit" value="1" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="ClassNewInstance" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ClassOnlyUsedInOneModule" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ClassOnlyUsedInOnePackage" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ClassOverridesFieldOfSuperClassInspection" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="REPORT_PRIVATE_REDEFINITION" value="false" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="ClassReferencesSubclass" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ClassUnconnectedToPackage" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ClassWithOnlyPrivateConstructors" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="CloneCallsConstructors" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="CloneInNonCloneableClass" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="CloneReturnsClassType" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="CloneableClassInSecureContext" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="CloneableImplementsClone" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="m_ignoreCloneableDueToInheritance" value="false" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="CollectionContainsUrl" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="CollectionsFieldAccessReplaceableByMethodCall" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ComparableImplementedButEqualsNotOverridden" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ComparatorNotSerializable" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="CompareToUsesNonFinalVariable" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ComparisonOfShortAndChar" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ComposeMissingKeys" enabled="true" level="ERROR" enabled_by_default="true" />
|
||||
<inspection_tool class="ConditionSignal" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ConditionalExpression" enabled="true" level="INFORMATION" enabled_by_default="true">
|
||||
<option name="ignoreSimpleAssignmentsAndReturns" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="ConditionalExpressionWithIdenticalBranchesJS" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ConfusingElse" enabled="false" level="WEAK WARNING" enabled_by_default="false">
|
||||
<option name="reportWhenNoStatementFollow" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="ConfusingMainMethod" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ConfusingOctalEscape" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ConfusingPlusesOrMinusesJS" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ConstantJUnitAssertArgument" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ConstantMathCall" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ConstantTestNGAssertArgument" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ConstructorCount" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="ignoreDeprecatedConstructors" value="true" />
|
||||
<option name="m_limit" value="5" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="ControlFlowStatementWithoutBraces" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ConvertJavadoc" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ConvertLambdaToReference" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ConvertOldAnnotations" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="CssConvertColorToHexInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="CssConvertColorToRgbInspection" enabled="true" level="INFORMATION" enabled_by_default="true" />
|
||||
<inspection_tool class="CssMissingSemicolon" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="CssReplaceWithShorthandUnsafely" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="CustomClassloader" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="CustomSecurityManager" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="CyclicClassDependency" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="CyclicPackageDependency" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="DateToString" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="DebuggerStatementJS" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="DeclareCollectionAsInterface" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="ignoreLocalVariables" value="false" />
|
||||
<option name="ignorePrivateMethodsAndFields" value="false" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="DefaultNotLastCaseInSwitch" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="DisallowWritingIntoStaticPropertiesInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="DisconnectedForeachInstructionInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="DisjointPackage" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="DivideByZeroJS" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="DocumentWriteJS" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="DoubleBraceInitialization" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="DoubleCheckedLocking" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="ignoreOnVolatileVariables" value="false" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="DriverManagerGetConnection" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="DuplicateBooleanBranch" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="DuplicateConditionJS" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="DynamicallyGeneratedCodeJS" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ES6ConvertIndexedForToForOf" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ES6ConvertLetToConst" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ES6ConvertToForOf" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ES6ShorthandObjectProperty" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ES6TopLevelAwaitExpression" enabled="true" level="ERROR" enabled_by_default="true" />
|
||||
<inspection_tool class="EmptyDirectory" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="EmptyStatementBody" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="m_reportEmptyBlocks" value="true" />
|
||||
<option name="commentsAreContent" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="EmptySynchronizedStatement" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="EnumSwitchStatementWhichMissesCases" enabled="true" level="WEAK WARNING" enabled_by_default="true">
|
||||
<option name="ignoreSwitchStatementsWithDefault" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="EnumerationCanBeIteration" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="EqualsCalledOnEnumConstant" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="EqualsHashCodeCalledOnUrl" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="EqualsUsesNonFinalVariable" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ErrorRethrown" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ExceptionNameDoesntEndWithException" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ExceptionPackage" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ExpectedExceptionNeverThrownTestNG" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ExplicitArgumentCanBeLambda" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ExtendsThread" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ExtendsThrowable" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ExternalizableWithSerializationMethods" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="FallthruInSwitchStatement" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="FieldAccessedSynchronizedAndUnsynchronized" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="countGettersAndSetters" value="false" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="FieldDeclarationSideOnly" enabled="true" level="ERROR" enabled_by_default="true" />
|
||||
<inspection_tool class="FieldHidesSuperclassField" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="m_ignoreInvisibleFields" value="false" />
|
||||
<option name="ignoreStaticFields" value="false" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="FieldMayBeStatic" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="Finalize" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="ignoreTrivialFinalizers" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="FinalizeNotProtected" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="FixedTimeStartWithInspection" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="FloatingPointEquality" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="FoldInitializerAndIfToElvis" enabled="false" level="INFO" enabled_by_default="false" />
|
||||
<inspection_tool class="ForLoopThatDoesntUseLoopVariableJS" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ForgottenDebugOutputInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true">
|
||||
<option name="configuration">
|
||||
<list>
|
||||
<option value="\Codeception\Util\Debug::debug" />
|
||||
<option value="\Codeception\Util\Debug::pause" />
|
||||
<option value="\Doctrine\Common\Util\Debug::dump" />
|
||||
<option value="\Doctrine\Common\Util\Debug::export" />
|
||||
<option value="\Illuminate\Support\Debug\Dumper::dump" />
|
||||
<option value="\Symfony\Component\Debug\Debug::enable" />
|
||||
<option value="\Symfony\Component\Debug\DebugClassLoader::enable" />
|
||||
<option value="\Symfony\Component\Debug\ErrorHandler::register" />
|
||||
<option value="\Symfony\Component\Debug\ExceptionHandler::register" />
|
||||
<option value="\TYPO3\CMS\Core\Utility\DebugUtility::debug" />
|
||||
<option value="\Zend\Debug\Debug::dump" />
|
||||
<option value="\Zend\Di\Display\Console::export" />
|
||||
<option value="dd" />
|
||||
<option value="debug_print_backtrace" />
|
||||
<option value="debug_zval_dump" />
|
||||
<option value="dpm" />
|
||||
<option value="dpq" />
|
||||
<option value="dsm" />
|
||||
<option value="dump" />
|
||||
<option value="dvm" />
|
||||
<option value="error_log" />
|
||||
<option value="kpr" />
|
||||
<option value="phpinfo" />
|
||||
<option value="print_r" />
|
||||
<option value="var_dump" />
|
||||
<option value="var_export" />
|
||||
<option value="wp_die" />
|
||||
<option value="xdebug_break" />
|
||||
<option value="xdebug_call_class" />
|
||||
<option value="xdebug_call_file" />
|
||||
<option value="xdebug_call_function" />
|
||||
<option value="xdebug_call_line" />
|
||||
<option value="xdebug_code_coverage_started" />
|
||||
<option value="xdebug_debug_zval" />
|
||||
<option value="xdebug_debug_zval_stdout" />
|
||||
<option value="xdebug_dump_superglobals" />
|
||||
<option value="xdebug_enable" />
|
||||
<option value="xdebug_get_code_coverage" />
|
||||
<option value="xdebug_get_collected_errors" />
|
||||
<option value="xdebug_get_declared_vars" />
|
||||
<option value="xdebug_get_function_stack" />
|
||||
<option value="xdebug_get_headers" />
|
||||
<option value="xdebug_get_monitored_functions" />
|
||||
<option value="xdebug_get_profiler_filename" />
|
||||
<option value="xdebug_get_stack_depth" />
|
||||
<option value="xdebug_get_tracefile_name" />
|
||||
<option value="xdebug_is_enabled" />
|
||||
<option value="xdebug_memory_usage" />
|
||||
<option value="xdebug_peak_memory_usage" />
|
||||
<option value="xdebug_print_function_stack" />
|
||||
<option value="xdebug_start_code_coverage" />
|
||||
<option value="xdebug_start_error_collection" />
|
||||
<option value="xdebug_start_function_monitor" />
|
||||
<option value="xdebug_start_trace" />
|
||||
<option value="xdebug_stop_code_coverage" />
|
||||
<option value="xdebug_stop_error_collection" />
|
||||
<option value="xdebug_stop_function_monitor" />
|
||||
<option value="xdebug_stop_trace" />
|
||||
<option value="xdebug_time_index" />
|
||||
<option value="xdebug_var_dump" />
|
||||
</list>
|
||||
</option>
|
||||
<option name="migratedIntoUserSpace" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="FunctionNamingConventionJS" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="m_regex" value="[a-z][A-Za-z]*" />
|
||||
<option name="m_minLength" value="0" />
|
||||
<option name="m_maxLength" value="99" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="FunctionWithInconsistentReturnsJS" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="HashCodeUsesNonFinalVariable" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="HibernateResource" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="insideTryAllowed" value="false" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="HtmlFormInputWithoutLabel" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="HtmlMissingClosingTag" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="HtmlPresentationalElement" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="HtmlRequiredTitleAttribute" enabled="true" level="INFORMATION" enabled_by_default="true" />
|
||||
<inspection_tool class="HtmlUnknownTag" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="myValues">
|
||||
<value>
|
||||
<list size="7">
|
||||
<item index="0" class="java.lang.String" itemvalue="nobr" />
|
||||
<item index="1" class="java.lang.String" itemvalue="noembed" />
|
||||
<item index="2" class="java.lang.String" itemvalue="comment" />
|
||||
<item index="3" class="java.lang.String" itemvalue="noscript" />
|
||||
<item index="4" class="java.lang.String" itemvalue="embed" />
|
||||
<item index="5" class="java.lang.String" itemvalue="script" />
|
||||
<item index="6" class="java.lang.String" itemvalue="foreignobject" />
|
||||
</list>
|
||||
</value>
|
||||
</option>
|
||||
<option name="myCustomValuesEnabled" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="IOResource" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="ignoredTypesString" value="java.io.ByteArrayOutputStream,java.io.ByteArrayInputStream,java.io.StringBufferInputStream,java.io.CharArrayWriter,java.io.CharArrayReader,java.io.StringWriter,java.io.StringReader" />
|
||||
<option name="insideTryAllowed" value="false" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="IfStatementWithIdenticalBranchesJS" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ImplicitDefaultCharsetUsage" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="IncompleteDestructuring" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="InconsistentLanguageLevel" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="InconsistentLineSeparators" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="IncrementDecrementOperationEquivalentInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="InnerClassOnInterface" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="m_ignoreInnerInterfaces" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="InnerClassReferencedViaSubclass" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="InnerClassVariableHidesOuterClassVariable" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="m_ignoreInvisibleFields" value="false" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="InsertLiteralUnderscores" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="InstanceofCatchParameter" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="InstanceofIncompatibleInterface" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="InstanceofThis" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="InterfaceMayBeAnnotatedFunctional" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="IsEmptyFunctionUsageInspection" enabled="false" level="WARNING" enabled_by_default="false">
|
||||
<option name="SUGGEST_TO_USE_COUNT_CHECK" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="IsNullFunctionUsageInspection" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="IteratorNextDoesNotThrowNoSuchElementException" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="JDBCExecuteWithNonConstantString" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="JDBCPrepareStatementWithNonConstantString" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="JDBCResource" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="insideTryAllowed" value="false" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="JNDIResource" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="insideTryAllowed" value="false" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="JSArrowFunctionBracesCanBeRemoved" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="JSClassNamingConvention" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="m_minLength" value="0" />
|
||||
<option name="m_maxLength" value="99" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="JSConstructorReturnsPrimitive" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="JSEqualityComparisonWithCoercion.TS" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="mySeverity" value="Always" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="JSJoinVariableDeclarationAndAssignment" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="JSMissingSwitchBranches" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="JSNonASCIINames" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="myAllowOnlyAscii" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="JSNonStrictModeUsed" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="JSOctalInteger" enabled="true" level="ERROR" enabled_by_default="true">
|
||||
<option name="myReportNonStrictEs5" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="JSUndeclaredVariable" enabled="true" level="WEAK WARNING" enabled_by_default="true">
|
||||
<option name="myCheckGlobalDefinitions" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="JSUnusedGlobalSymbols" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="myReportUnusedDefinitions" value="true" />
|
||||
<option name="myReportUnusedProperties" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="JUnitDatapoint" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="JUnitRule" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="JUnitTestNG" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="JavadocHtmlLint" enabled="true" level="ERROR" enabled_by_default="true" />
|
||||
<inspection_tool class="JoinDeclarationAndAssignmentJava" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="LambdaCanBeMethodCall" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="LambdaUnfriendlyMethodOverload" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="LengthOneStringInIndexOf" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="LengthOneStringsInConcatenation" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ListenerMayUseAdapter" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="checkForEmptyMethods" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="LoadLibraryWithNonConstantString" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="LocalCanBeFinal" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="REPORT_VARIABLES" value="true" />
|
||||
<option name="REPORT_PARAMETERS" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="LocalVariableDeclarationSideOnly" enabled="true" level="ERROR" enabled_by_default="true" />
|
||||
<inspection_tool class="LocalVariableNamingConventionJS" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="m_regex" value="[a-z][A-Za-z]*" />
|
||||
<option name="m_minLength" value="0" />
|
||||
<option name="m_maxLength" value="99" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="MalformedSetUpTearDown" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="MapReplaceableByEnumMap" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="MethodCallSideOnly" enabled="true" level="ERROR" enabled_by_default="true" />
|
||||
<inspection_tool class="MethodMayBeStatic" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="m_onlyPrivateOrFinal" value="false" />
|
||||
<option name="m_ignoreEmptyMethods" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="MethodMayBeSynchronized" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="MethodOverloadsParentMethod" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="reportIncompatibleParameters" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="MethodOverridesInaccessibleMethodOfSuper" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="MethodOverridesStaticMethod" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="MethodSideOnly" enabled="true" level="ERROR" enabled_by_default="true" />
|
||||
<inspection_tool class="MisorderedAssertEqualsArguments" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="MisorderedAssertEqualsArgumentsTestNG" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="MisorderedAssertEqualsParameters" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="MisorderedModifiersInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="MissingOverrideAnnotation" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="ignoreObjectMethods" value="false" />
|
||||
<option name="ignoreAnonymousClassMethods" value="false" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="MissortedModifiers" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="m_requireAnnotationsFirst" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="MultipleTopLevelClassesInFile" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="NakedNotify" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="NegatedConditional" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="m_ignoreNegatedNullComparison" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="NestedAssignment" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="NestedClassSideOnly" enabled="true" level="ERROR" enabled_by_default="true" />
|
||||
<inspection_tool class="NestedSwitchStatement" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="NestedSynchronizedStatement" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="NestedTernaryOperatorInspection" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="NewExpressionSideOnly" enabled="true" level="ERROR" enabled_by_default="true" />
|
||||
<inspection_tool class="NonBlockStatementBodyJS" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="NonExceptionNameEndsWithException" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="NonFinalClone" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="NonFinalFieldInEnum" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="NonFinalFieldOfException" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="NonFinalStaticVariableUsedInClassInitialization" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="NonFinalUtilityClass" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="NonPublicClone" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="NonReproducibleMathCall" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="NonSerializableFieldInSerializableClass" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="ignorableAnnotations">
|
||||
<value />
|
||||
</option>
|
||||
<option name="ignoreAnonymousInnerClasses" value="false" />
|
||||
<option name="superClassString" value="java.awt.Component" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="NonSerializableObjectBoundToHttpSession" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="NonSerializableObjectPassedToObjectStream" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="NonSerializableWithSerialVersionUIDField" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="NonSerializableWithSerializationMethods" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="NonShortCircuitBoolean" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="NonSynchronizedMethodOverridesSynchronizedMethod" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="NonThreadSafeLazyInitialization" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="NotOptimalIfConditionsInspection" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="REPORT_DUPLICATE_CONDITIONS" value="false" />
|
||||
<option name="SUGGEST_OPTIMIZING_CONDITIONS" value="false" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="NotifyCalledOnCondition" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="NotifyWithoutCorrespondingWait" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="NullThrown" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="NumericToString" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="OCInconsistentNaming" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ObjectAllocationIgnoredJS" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ObjectInstantiationInEqualsHashCode" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ObjectNotify" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ObjectToString" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ObsoleteCollection" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="ignoreRequiredObsoleteCollectionTypes" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="OctalAndDecimalIntegersMixed" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="OffsetOperationsInspection" enabled="true" level="ERROR" enabled_by_default="true" />
|
||||
<inspection_tool class="OneTimeUseVariablesInspection" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="ALLOW_LONG_STATEMENTS" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="OverloadedVarargsMethod" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="OverridableMethodCallDuringObjectConstruction" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="PackageInMultipleModules" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ParameterHidingMemberVariable" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="m_ignoreInvisibleFields" value="true" />
|
||||
<option name="m_ignoreStaticMethodParametersHidingInstanceFields" value="true" />
|
||||
<option name="m_ignoreForConstructors" value="true" />
|
||||
<option name="m_ignoreForPropertySetters" value="true" />
|
||||
<option name="m_ignoreForAbstractMethods" value="false" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="ParameterNamingConventionJS" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="m_regex" value="[a-z][A-Za-z]*" />
|
||||
<option name="m_minLength" value="0" />
|
||||
<option name="m_maxLength" value="99" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="ParameterizedParametersStaticCollection" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="PhpAssignmentInConditionInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="PhpAssignmentReplaceableWithOperatorAssignmentInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="PhpAssignmentReplaceableWithPrefixExpressionInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="PhpCSValidationInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false">
|
||||
<option name="EXTENSIONS" value="php,js,css,inc" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="PhpClassNamingConventionInspection" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="m_minLength" value="0" />
|
||||
<option name="m_maxLength" value="0" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="PhpClosureCanBeConvertedToShortArrowFunctionInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="PhpCompoundNamespaceDepthInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="PhpConstantNamingConventionInspection" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="m_minLength" value="0" />
|
||||
<option name="m_maxLength" value="0" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="PhpConstantReassignmentInspection" enabled="true" level="ERROR" enabled_by_default="true" />
|
||||
<inspection_tool class="PhpDivisionByZeroInspection" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="PhpDocMissingThrowsInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true">
|
||||
<option name="SKIP_ON_EMPTY_PHPDOC" value="false" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="PhpFunctionNamingConventionInspection" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="m_minLength" value="0" />
|
||||
<option name="m_maxLength" value="0" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="PhpInconsistentReturnPointsInspection" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="ALLOW_RETURN_NULL_IN_VOID" value="false" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="PhpLongTypeFormInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="PhpLoopCanBeConvertedToArrayFillInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="PhpLoopCanBeConvertedToArrayFilterInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="PhpLoopCanBeConvertedToArrayMapInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="PhpMethodNamingConventionInspection" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="m_minLength" value="0" />
|
||||
<option name="m_maxLength" value="0" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="PhpMethodOrClassCallIsNotCaseSensitiveInspection" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="PhpMissingParentCallMagicInspection" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="PhpMissingStrictTypesDeclarationInspection" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="PhpMissingVisibilityInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="PhpNewClassMissingParameterListInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="PhpNonCanonicalElementsOrderInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="PhpOverridingMethodVisibilityInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="PhpPropertyNamingConventionInspection" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="m_regex" value="[a-z][_a-z\d]*" />
|
||||
<option name="m_minLength" value="0" />
|
||||
<option name="m_maxLength" value="0" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="PhpRedundantClosingTagInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="PhpSeparateElseIfInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="PhpShortOpenTagInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="PhpSingleStatementWithBracesInspection" enabled="false" level="INFORMATION" enabled_by_default="false" />
|
||||
<inspection_tool class="PhpStatementHasEmptyBodyInspection" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="myCommentsCountAsContent" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="PhpStatementWithoutBracesInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="PhpStaticAsDynamicMethodCallInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true">
|
||||
<option name="SHOW_FOR_MAGIC" value="false" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="PhpTraditionalSyntaxArrayLiteralInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="PhpTraitsUseListInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="PhpUndefinedCallbackInspection" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="PhpUndefinedClassConstantInspection" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="DOWNGRADE_SEVERITY" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="PhpUndefinedMethodInspection" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="DOWNGRADE_SEVERITY" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="PhpUnnecessaryDoubleQuotesInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="PhpUnused" enabled="true" level="WEAK WARNING" enabled_by_default="true">
|
||||
<option name="SHOW_UNUSED_BY_ENTRIES" value="false" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="PhpUnusedParameterInspection" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="DONT_REPORT_ANONYMOUS" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="PhpVarUsageInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="PhpVariableNamingConventionInspection" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="m_minLength" value="0" />
|
||||
<option name="m_maxLength" value="0" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="PhpVariableVariableInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="PluginXmlDynamicPlugin" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="highlightNonDynamicEPUsages" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="PointlessBitwiseExpressionJS" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="m_ignoreExpressionsContainingConstants" value="false" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="ProblematicVarargsMethodOverride" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="PropertyCanBeStaticInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ProtectedMemberInFinalClass" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="PublicFieldAccessedInSynchronizedContext" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="PublicStaticArrayField" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="PublicStaticCollectionField" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="PyAugmentAssignmentInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="PyClassicStyleClassInspection" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="PyMissingTypeHintsInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true">
|
||||
<option name="m_onlyWhenTypesAreKnown" value="false" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="RawTypeCanBeGeneric" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ReadObjectAndWriteObjectPrivate" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ReadObjectInitialization" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ReadResolveAndWriteReplaceProtected" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="RedundantElseClauseInspection" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="RedundantFieldInitialization" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="onlyWarnOnNull" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="RedundantImplements" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="ignoreSerializable" value="true" />
|
||||
<option name="ignoreCloneable" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="RedundantInnerClassModifier" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="RedundantObjectTypeCheck" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="RedundantSuppression" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="IGNORE_ALL" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="ReferencingObjectsInspection" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="RegExpOctalEscape" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ReplaceAssignmentWithOperatorAssignment" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="ignoreLazyOperators" value="true" />
|
||||
<option name="ignoreObscureOperators" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="ReplaceAssignmentWithOperatorAssignmentJS" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ReplaceCollectionCountWithSize" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ReplaceGuardClauseWithFunctionCall" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ReplaceStringFormatWithLiteral" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ReplaceSubstringWithDropLast" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ReplaceSubstringWithIndexingOperation" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ReplaceSubstringWithSubstringAfter" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ReplaceSubstringWithSubstringBefore" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ReplaceSubstringWithTake" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ResultOfObjectAllocationIgnored" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ResultSetIndexZero" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ReturnOfInnerClass" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="RsSimplifyBooleanExpression" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="RuntimeExec" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="RuntimeExecWithNonConstantString" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="SafeLock" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="SecurityAdvisoriesInspection" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="optionConfiguration">
|
||||
<list>
|
||||
<option value="barryvdh/laravel-debugbar" />
|
||||
<option value="behat/behat" />
|
||||
<option value="brianium/paratest" />
|
||||
<option value="codeception/codeception" />
|
||||
<option value="codedungeon/phpunit-result-printer" />
|
||||
<option value="composer/composer" />
|
||||
<option value="doctrine/coding-standard" />
|
||||
<option value="filp/whoops" />
|
||||
<option value="friendsofphp/php-cs-fixer" />
|
||||
<option value="humbug/humbug" />
|
||||
<option value="infection/infection" />
|
||||
<option value="jakub-onderka/php-parallel-lint" />
|
||||
<option value="johnkary/phpunit-speedtrap" />
|
||||
<option value="kalessil/production-dependencies-guard" />
|
||||
<option value="mikey179/vfsStream" />
|
||||
<option value="mockery/mockery" />
|
||||
<option value="mybuilder/phpunit-accelerator" />
|
||||
<option value="orchestra/testbench" />
|
||||
<option value="pdepend/pdepend" />
|
||||
<option value="phan/phan" />
|
||||
<option value="phing/phing" />
|
||||
<option value="phpcompatibility/php-compatibility" />
|
||||
<option value="phpmd/phpmd" />
|
||||
<option value="phpro/grumphp" />
|
||||
<option value="phpspec/phpspec" />
|
||||
<option value="phpspec/prophecy" />
|
||||
<option value="phpstan/phpstan" />
|
||||
<option value="phpunit/phpunit" />
|
||||
<option value="povils/phpmnd" />
|
||||
<option value="roave/security-advisories" />
|
||||
<option value="satooshi/php-coveralls" />
|
||||
<option value="sebastian/phpcpd" />
|
||||
<option value="slevomat/coding-standard" />
|
||||
<option value="spatie/phpunit-watcher" />
|
||||
<option value="squizlabs/php_codesniffer" />
|
||||
<option value="sstalle/php7cc" />
|
||||
<option value="symfony/debug" />
|
||||
<option value="symfony/maker-bundle" />
|
||||
<option value="symfony/phpunit-bridge" />
|
||||
<option value="symfony/var-dumper" />
|
||||
<option value="vimeo/psalm" />
|
||||
<option value="wimg/php-compatibility" />
|
||||
<option value="wp-coding-standards/wpcs" />
|
||||
<option value="yiisoft/yii2-coding-standards" />
|
||||
<option value="yiisoft/yii2-debug" />
|
||||
<option value="yiisoft/yii2-gii" />
|
||||
<option value="zendframework/zend-coding-standard" />
|
||||
<option value="zendframework/zend-debug" />
|
||||
<option value="zendframework/zend-test" />
|
||||
</list>
|
||||
</option>
|
||||
</inspection_tool>
|
||||
<inspection_tool class="SerialPersistentFieldsWithWrongSignature" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="SerialVersionUIDNotStaticFinal" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="SerializableHasSerialVersionUIDField" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="ignoreAnonymousInnerClasses" value="false" />
|
||||
<option name="superClassString" value="java.awt.Component" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="SerializableHasSerializationMethods" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="ignoreAnonymousInnerClasses" value="false" />
|
||||
<option name="superClassString" value="java.awt.Component" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="SerializableInnerClassHasSerialVersionUIDField" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="ignoreAnonymousInnerClasses" value="false" />
|
||||
<option name="superClassString" value="java.awt.Component" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="SerializableInnerClassWithNonSerializableOuterClass" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="ignoreAnonymousInnerClasses" value="false" />
|
||||
<option name="superClassString" value="java.awt.Component" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="SerializableStoresNonSerializable" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="SerializableWithUnconstructableAncestor" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="SetReplaceableByEnumSet" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="SeveralTargetsMessage" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="SharedThreadLocalRandom" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ShortEchoTagCanBeUsedInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="SignalWithoutCorrespondingAwait" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="SimpleDateFormatWithoutLocale" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="SimplifiableAnnotation" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="SimplifiableIfStatement" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="SingleStatementInBlock" enabled="false" level="INFORMATION" enabled_by_default="false" />
|
||||
<inspection_tool class="SizeReplaceableByIsEmpty" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="SleepWhileHoldingLock" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="SocketResource" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="insideTryAllowed" value="false" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="SpellCheckingInspection" enabled="false" level="TYPO" enabled_by_default="false">
|
||||
<option name="processCode" value="true" />
|
||||
<option name="processLiterals" value="true" />
|
||||
<option name="processComments" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="SqlGotoInspection" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="SqlRedundantOrderingDirectionInspection" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="SqlWithoutWhereInspection" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="myDontWarnForLimit" value="false" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="StackEmpty" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="StaticMethodOnlyUsedInOneClass" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="ignoreTestClasses" value="true" />
|
||||
<option name="ignoreOnConflicts" value="false" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="StrTrUsageAsStrReplaceInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="StringBufferToStringInConcatenation" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="StringConcatenationInFormatCall" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="StringConcatenationInMessageFormatCall" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="StringConcatenationMissingWhitespace" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="StringEqualsEmptyString" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="SUPPRESS_FOR_VALUES_WHICH_COULD_BE_NULL" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="StringLiteralBreaksHTMLJS" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="StringReplaceableByStringBuffer" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="onlyWarnOnLoop" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="StringToUpperWithoutLocale" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="SubStrUsedAsStrPosInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="SubtractionInCompareTo" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="SuspiciousArrayCast" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="SuspiciousIndentAfterControlStatement" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="SuspiciousLiteralUnderscore" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="SwitchStatementWithConfusingDeclaration" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="SynchronizationOnStaticField" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="SynchronizeOnLock" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="SynchronizeOnThis" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="SynchronizedMethod" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="m_includeNativeMethods" value="true" />
|
||||
<option name="ignoreSynchronizedSuperMethods" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="SynchronizedOnLiteralObject" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="SystemGC" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="SystemGetenv" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="SystemProperties" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="SystemRunFinalizersOnExit" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="SystemSetSecurityManager" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="TailRecursionJS" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="TestCaseInProductCode" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="TestCaseWithConstructor" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="TestCaseWithNoTestMethods" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="ignoreSupers" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="TestMethodInProductCode" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="TestMethodWithoutAssertion" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="TestOnlyProblems" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="TextLabelInSwitchStatementJS" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ThisEscapedInConstructor" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ThreadDumpStack" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ThreadLocalNotStaticFinal" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ThreadPriority" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ThreadStartInConstruction" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ThreadStopSuspendResume" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ThreadYield" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ThrowCaughtLocally" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="ignoreRethrownExceptions" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="ThrowRawExceptionInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="ThrowablePrintStackTrace" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="TimeToString" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="TooBroadScope" enabled="true" level="WEAK WARNING" enabled_by_default="true">
|
||||
<option name="m_allowConstructorAsInitializer" value="false" />
|
||||
<option name="m_onlyLookAtBlocks" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="TransientFieldInNonSerializableClass" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="TransientFieldNotInitialized" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="TrivialStringConcatenation" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="UnNecessaryDoubleQuotesInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="UnaryPlus" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="UnclearBinaryExpression" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="UnconditionalWait" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="UnconstrainedVariableType" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="UndeclaredTests" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="UnknownInspectionInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="UnnecessarilyQualifiedStaticUsage" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="m_ignoreStaticFieldAccesses" value="false" />
|
||||
<option name="m_ignoreStaticMethodCalls" value="false" />
|
||||
<option name="m_ignoreStaticAccessFromStaticContext" value="false" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="UnnecessarilyQualifiedStaticallyImportedElement" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="UnnecessaryBoxing" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="onlyReportSuperfluouslyBoxed" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="UnnecessaryConstantArrayCreationExpression" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="UnnecessaryConstructor" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="ignoreAnnotations" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="UnnecessaryExplicitNumericCast" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="UnnecessaryLocalVariable" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="m_ignoreImmediatelyReturnedVariables" value="true" />
|
||||
<option name="m_ignoreAnnotatedVariables" value="false" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="UnnecessaryLocalVariableJS" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="m_ignoreImmediatelyReturnedVariables" value="true" />
|
||||
<option name="m_ignoreAnnotatedVariables" value="false" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="UnnecessaryParentheses" enabled="true" level="INFORMATION" enabled_by_default="true">
|
||||
<option name="ignoreClarifyingParentheses" value="true" />
|
||||
<option name="ignoreParenthesesOnConditionals" value="false" />
|
||||
<option name="ignoreParenthesesOnLambdaParameter" value="false" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="UnnecessarySuperConstructor" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="UnnecessaryUnaryMinus" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="UnnecessaryUnboxing" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="onlyReportSuperfluouslyUnboxed" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="UnsetConstructsCanBeMergedInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="UnterminatedStatementJS" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="ignoreSemicolonAtEndOfBlock" value="false" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="UnusedCatchParameterJS" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="m_ignoreCatchBlocksWithComments" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="UseCouple" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="UseOfAWTPeerClass" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="UseOfJDBCDriverClass" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="UseOfObsoleteAssert" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="UseOfObsoleteDateTimeApi" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="UseOfProcessBuilder" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="UseOfPropertiesAsHashtable" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="UseOfSunClasses" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="UsingInclusionReturnValueInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="UtilityClassWithPublicConstructor" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="UtilityClassWithoutPrivateConstructor" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="ignorableAnnotations">
|
||||
<value />
|
||||
</option>
|
||||
<option name="ignoreClassesWithOnlyMain" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="VariableNotUsedInsideIf" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="VariableUseSideOnly" enabled="true" level="ERROR" enabled_by_default="true" />
|
||||
<inspection_tool class="VoidExpressionJS" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="VolatileArrayField" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="W3CssValidation" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="myCssVersion" value="css3svg" />
|
||||
<option name="myIgnoreVendorSpecificProperties" value="false" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="WaitCalledOnCondition" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="WaitNotInLoop" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="WaitNotifyNotInSynchronizedContext" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="WaitOrAwaitWithoutTimeout" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="WaitWhileHoldingTwoLocks" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="WaitWithoutCorrespondingNotify" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="WhenWithOnlyElse" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="WhileLoopSpinsOnField" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="ignoreNonEmtpyLoops" value="false" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="ZeroLengthArrayInitialization" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
</profile>
|
||||
</component>
|
10
app/.idea/.idea.DiscordHistoryTracker/.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
10
app/.idea/.idea.DiscordHistoryTracker/.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
@@ -0,0 +1,10 @@
|
||||
<component name="InspectionProjectProfileManager">
|
||||
<profile version="1.0">
|
||||
<option name="myName" value="Project Default" />
|
||||
<inspection_tool class="SpellCheckingInspection" enabled="false" level="TYPO" enabled_by_default="false">
|
||||
<option name="processCode" value="true" />
|
||||
<option name="processLiterals" value="true" />
|
||||
<option name="processComments" value="true" />
|
||||
</inspection_tool>
|
||||
</profile>
|
||||
</component>
|
6
app/.idea/.idea.DiscordHistoryTracker/.idea/inspectionProfiles/profiles_settings.xml
generated
Normal file
6
app/.idea/.idea.DiscordHistoryTracker/.idea/inspectionProfiles/profiles_settings.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
||||
<component name="InspectionProjectProfileManager">
|
||||
<settings>
|
||||
<option name="PROJECT_PROFILE" value="Project" />
|
||||
<version value="1.0" />
|
||||
</settings>
|
||||
</component>
|
36
app/.idea/.idea.DiscordHistoryTracker/.idea/jsonSchemas.xml
generated
Normal file
36
app/.idea/.idea.DiscordHistoryTracker/.idea/jsonSchemas.xml
generated
Normal file
@@ -0,0 +1,36 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="JsonSchemaMappingsProjectConfiguration">
|
||||
<state>
|
||||
<map>
|
||||
<entry key="track-channel">
|
||||
<value>
|
||||
<SchemaInfo>
|
||||
<option name="name" value="track-channel" />
|
||||
<option name="relativePathToSchema" value="Resources/Schemas/track-channel.yml" />
|
||||
<option name="schemaVersion" value="JSON Schema version 7" />
|
||||
</SchemaInfo>
|
||||
</value>
|
||||
</entry>
|
||||
<entry key="track-messages">
|
||||
<value>
|
||||
<SchemaInfo>
|
||||
<option name="name" value="track-messages" />
|
||||
<option name="relativePathToSchema" value="Resources/Schemas/track-messages.yml" />
|
||||
<option name="schemaVersion" value="JSON Schema version 7" />
|
||||
</SchemaInfo>
|
||||
</value>
|
||||
</entry>
|
||||
<entry key="track-users">
|
||||
<value>
|
||||
<SchemaInfo>
|
||||
<option name="name" value="track-users" />
|
||||
<option name="relativePathToSchema" value="Resources/Schemas/track-users.yml" />
|
||||
<option name="schemaVersion" value="JSON Schema version 7" />
|
||||
</SchemaInfo>
|
||||
</value>
|
||||
</entry>
|
||||
</map>
|
||||
</state>
|
||||
</component>
|
||||
</project>
|
22
app/.idea/.idea.DiscordHistoryTracker/.idea/libraries/Generated_files.xml
generated
Normal file
22
app/.idea/.idea.DiscordHistoryTracker/.idea/libraries/Generated_files.xml
generated
Normal file
@@ -0,0 +1,22 @@
|
||||
<component name="libraryTable">
|
||||
<library name="Generated files" type="javaScript">
|
||||
<properties>
|
||||
<sourceFilesUrls>
|
||||
<item url="file://$PROJECT_DIR$/Resources/Tracker/scripts.min/discord.js" />
|
||||
<item url="file://$PROJECT_DIR$/Resources/Tracker/scripts.min/dom.js" />
|
||||
<item url="file://$PROJECT_DIR$/Resources/Tracker/scripts.min/gui.js" />
|
||||
<item url="file://$PROJECT_DIR$/Resources/Tracker/scripts.min/settings.js" />
|
||||
<item url="file://$PROJECT_DIR$/Resources/Tracker/scripts.min/state.js" />
|
||||
</sourceFilesUrls>
|
||||
</properties>
|
||||
<CLASSES>
|
||||
<root url="file://$PROJECT_DIR$/Resources/Tracker/scripts.min/discord.js" />
|
||||
<root url="file://$PROJECT_DIR$/Resources/Tracker/scripts.min/dom.js" />
|
||||
<root url="file://$PROJECT_DIR$/Resources/Tracker/scripts.min/gui.js" />
|
||||
<root url="file://$PROJECT_DIR$/Resources/Tracker/scripts.min/settings.js" />
|
||||
<root url="file://$PROJECT_DIR$/Resources/Tracker/scripts.min/state.js" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
||||
</library>
|
||||
</component>
|
20
app/.idea/.idea.DiscordHistoryTracker/.idea/runConfigurations/Desktop.xml
generated
Normal file
20
app/.idea/.idea.DiscordHistoryTracker/.idea/runConfigurations/Desktop.xml
generated
Normal file
@@ -0,0 +1,20 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="Desktop" type="DotNetProject" factoryName=".NET Project">
|
||||
<option name="EXE_PATH" value="$PROJECT_DIR$/.artifacts/bin/Desktop/debug/DiscordHistoryTracker.exe" />
|
||||
<option name="PROGRAM_PARAMETERS" value="" />
|
||||
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/.artifacts/bin/Desktop/debug" />
|
||||
<option name="PASS_PARENT_ENVS" value="1" />
|
||||
<option name="USE_EXTERNAL_CONSOLE" value="0" />
|
||||
<option name="USE_MONO" value="0" />
|
||||
<option name="RUNTIME_ARGUMENTS" value="" />
|
||||
<option name="PROJECT_PATH" value="$PROJECT_DIR$/Desktop/Desktop.csproj" />
|
||||
<option name="PROJECT_EXE_PATH_TRACKING" value="1" />
|
||||
<option name="PROJECT_ARGUMENTS_TRACKING" value="1" />
|
||||
<option name="PROJECT_WORKING_DIRECTORY_TRACKING" value="1" />
|
||||
<option name="PROJECT_KIND" value="DotNetCore" />
|
||||
<option name="PROJECT_TFM" value="net8.0" />
|
||||
<method v="2">
|
||||
<option name="Build" />
|
||||
</method>
|
||||
</configuration>
|
||||
</component>
|
3
app/.idea/.idea.DiscordHistoryTracker/.idea/scopes/Resources.xml
generated
Normal file
3
app/.idea/.idea.DiscordHistoryTracker/.idea/scopes/Resources.xml
generated
Normal file
@@ -0,0 +1,3 @@
|
||||
<component name="DependencyValidationManager">
|
||||
<scope name="Resources" pattern="file:Tracker/scripts/*" />
|
||||
</component>
|
6
app/.idea/.idea.DiscordHistoryTracker/.idea/sqldialects.xml
generated
Normal file
6
app/.idea/.idea.DiscordHistoryTracker/.idea/sqldialects.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="SqlDialectMappings">
|
||||
<file url="PROJECT" dialect="SQLite" />
|
||||
</component>
|
||||
</project>
|
11
app/.idea/.idea.DiscordHistoryTracker/.idea/vcs.xml
generated
Normal file
11
app/.idea/.idea.DiscordHistoryTracker/.idea/vcs.xml
generated
Normal file
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="GitSharedSettings">
|
||||
<option name="FORCE_PUSH_PROHIBITED_PATTERNS">
|
||||
<list />
|
||||
</option>
|
||||
</component>
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$/.." vcs="Git" />
|
||||
</component>
|
||||
</project>
|
136
app/Desktop/App.axaml
Normal file
136
app/Desktop/App.axaml
Normal file
@@ -0,0 +1,136 @@
|
||||
<Application xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:common="clr-namespace:DHT.Desktop.Common"
|
||||
xmlns:system="clr-namespace:System;assembly=System.Runtime"
|
||||
x:Class="DHT.Desktop.App"
|
||||
RequestedThemeVariant="Light">
|
||||
|
||||
<Application.Styles>
|
||||
|
||||
<FluentTheme />
|
||||
<StyleInclude Source="avares://Avalonia.Controls.DataGrid/Themes/Simple.xaml" />
|
||||
|
||||
<Style Selector="Button, CheckBox, RadioButton, Expander /template/ ToggleButton#ExpanderHeader">
|
||||
<Setter Property="Cursor" Value="Hand" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="Button">
|
||||
<Setter Property="Padding" Value="10 7 10 6" />
|
||||
<Setter Property="BorderThickness" Value="1" />
|
||||
<Setter Property="HorizontalContentAlignment" Value="Center" />
|
||||
<Setter Property="VerticalContentAlignment" Value="Center" />
|
||||
</Style>
|
||||
<Style Selector="Button:pressed">
|
||||
<Setter Property="RenderTransform" Value="none" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="TextBox">
|
||||
<Setter Property="Padding" Value="6 0" />
|
||||
<Setter Property="VerticalContentAlignment" Value="Center" />
|
||||
</Style>
|
||||
<Style Selector="TextBox /template/ TextBlock#PART_Watermark">
|
||||
<Setter Property="VerticalAlignment" Value="Center" />
|
||||
</Style>
|
||||
<Style Selector="TextBox:focus /template/ Border#PART_BorderElement">
|
||||
<Setter Property="BorderThickness" Value="1" />
|
||||
</Style>
|
||||
<Style Selector="TextBox:error DataValidationErrors">
|
||||
<Style.Resources>
|
||||
<ControlTemplate x:Key="InlineDataValidationContentTemplate" TargetType="DataValidationErrors">
|
||||
<ContentPresenter Name="PART_ContentPresenter"
|
||||
Padding="{TemplateBinding Padding}"
|
||||
Background="{TemplateBinding Background}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}"
|
||||
CornerRadius="{TemplateBinding CornerRadius}"
|
||||
Content="{TemplateBinding Content}"
|
||||
ContentTemplate="{TemplateBinding ContentTemplate}" />
|
||||
</ControlTemplate>
|
||||
</Style.Resources>
|
||||
<Setter Property="Template" Value="{StaticResource InlineDataValidationContentTemplate}" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="Expander">
|
||||
<Setter Property="MinHeight" Value="40" />
|
||||
<Setter Property="HorizontalAlignment" Value="Stretch" />
|
||||
</Style>
|
||||
|
||||
</Application.Styles>
|
||||
|
||||
<Application.Resources>
|
||||
|
||||
<common:NumberValueConverter x:Key="NumberValueConverter" />
|
||||
<common:BytesValueConverter x:Key="BytesValueConverter" />
|
||||
|
||||
<system:Double x:Key="ControlContentThemeFontSize">14</system:Double>
|
||||
<CornerRadius x:Key="ControlCornerRadius">0</CornerRadius>
|
||||
|
||||
<Color x:Key="SystemAccentColor">#3C5F95</Color>
|
||||
<Color x:Key="SystemAccentColorDark1">#3C5F95</Color>
|
||||
<Color x:Key="SystemAccentColorDark2">#3C5F95</Color>
|
||||
<Color x:Key="SystemAccentColorDark3">#3C5F95</Color>
|
||||
<Color x:Key="SystemAccentColorLight1">#3C5F95</Color>
|
||||
<Color x:Key="SystemAccentColorLight2">#3C5F95</Color>
|
||||
<Color x:Key="SystemAccentColorLight3">#3C5F95</Color>
|
||||
|
||||
<system:Double x:Key="ScrollBarSize">14</system:Double>
|
||||
<TransformOperations x:Key="VerticalSmallScrollThumbScaleTransform">scaleX(0.5) translateX(-3px)</TransformOperations>
|
||||
<TransformOperations x:Key="HorizontalSmallScrollThumbScaleTransform">scaleY(0.5) translateY(-3px)</TransformOperations>
|
||||
<SolidColorBrush x:Key="ScrollBarPanningThumbBackground" Color="#8F8F8F" />
|
||||
|
||||
<SolidColorBrush x:Key="ButtonBorderBrush" Color="#3C5F95" />
|
||||
<SolidColorBrush x:Key="ButtonBorderBrushPointerOver" Color="#0E2B59" />
|
||||
<SolidColorBrush x:Key="ButtonBorderBrushPressed" Color="#061742" />
|
||||
<SolidColorBrush x:Key="ButtonBorderBrushDisabled" Color="#9B9B9B" />
|
||||
<SolidColorBrush x:Key="ButtonBackground" Color="#FFFFFF" />
|
||||
<SolidColorBrush x:Key="ButtonBackgroundPointerOver" Color="#F6F9FD" />
|
||||
<SolidColorBrush x:Key="ButtonBackgroundPressed" Color="#EDF3FD" />
|
||||
<SolidColorBrush x:Key="ButtonBackgroundDisabled" Color="#FFFFFF" />
|
||||
<SolidColorBrush x:Key="ButtonForeground" Color="#112961" />
|
||||
<SolidColorBrush x:Key="ButtonForegroundPointerOver" Color="#050E41" />
|
||||
<SolidColorBrush x:Key="ButtonForegroundPressed" Color="#010320" />
|
||||
<SolidColorBrush x:Key="ButtonForegroundDisabled" Color="#8C8C8C" />
|
||||
|
||||
<SolidColorBrush x:Key="TextControlBorderBrush" Color="#515151" />
|
||||
<SolidColorBrush x:Key="TextControlBorderBrushPointerOver" Color="#000000" />
|
||||
<SolidColorBrush x:Key="TextControlBorderBrushFocused" Color="#3C5F95" />
|
||||
<SolidColorBrush x:Key="TextControlBorderBrushDisabled" Color="#9B9B9B" />
|
||||
<SolidColorBrush x:Key="TextControlBackground" Color="#FFFFFF" />
|
||||
<SolidColorBrush x:Key="TextControlBackgroundPointerOver" Color="#FFFFFF" />
|
||||
<SolidColorBrush x:Key="TextControlBackgroundFocused" Color="#F8FCFF" />
|
||||
<SolidColorBrush x:Key="TextControlBackgroundDisabled" Color="#FFFFFF" />
|
||||
<SolidColorBrush x:Key="TextControlForeground" Color="#000000" />
|
||||
<SolidColorBrush x:Key="TextControlForegroundPointerOver" Color="#000000" />
|
||||
<SolidColorBrush x:Key="TextControlForegroundFocused" Color="#000000" />
|
||||
<SolidColorBrush x:Key="TextControlForegroundDisabled" Color="#8C8C8C" />
|
||||
<SolidColorBrush x:Key="TextControlSelectionHighlightColor" Color="#DEE9F8" />
|
||||
<SolidColorBrush x:Key="TextControlPlaceholderForeground" Color="#AAAAAA" />
|
||||
<SolidColorBrush x:Key="TextControlPlaceholderForegroundPointerOver" Color="#AAAAAA" />
|
||||
<SolidColorBrush x:Key="TextControlPlaceholderForegroundFocused" Color="#AAAAAA" />
|
||||
<SolidColorBrush x:Key="TextControlPlaceholderForegroundDisabled" Color="#AAAAAA" />
|
||||
|
||||
<Thickness x:Key="ExpanderHeaderPadding">15,0</Thickness>
|
||||
<Thickness x:Key="ExpanderContentPadding">12</Thickness>
|
||||
<SolidColorBrush x:Key="ExpanderHeaderBorderBrush" Color="#697DAB" />
|
||||
<SolidColorBrush x:Key="ExpanderHeaderBorderBrushPointerOver" Color="#697DAB" />
|
||||
<SolidColorBrush x:Key="ExpanderHeaderBorderBrushPressed" Color="#697DAB" />
|
||||
<SolidColorBrush x:Key="ExpanderHeaderBorderBrushDisabled" Color="#697DAB" />
|
||||
<SolidColorBrush x:Key="ExpanderHeaderBackground" Color="#697DAB" />
|
||||
<SolidColorBrush x:Key="ExpanderHeaderBackgroundPointerOver" Color="#536794" />
|
||||
<SolidColorBrush x:Key="ExpanderHeaderBackgroundPressed" Color="#47587F" />
|
||||
<SolidColorBrush x:Key="ExpanderHeaderBackgroundDisabled" Color="#697DAB" />
|
||||
<SolidColorBrush x:Key="ExpanderHeaderForeground" Color="#FFFFFF" />
|
||||
<SolidColorBrush x:Key="ExpanderHeaderForegroundPointerOver" Color="#FFFFFF" />
|
||||
<SolidColorBrush x:Key="ExpanderHeaderForegroundPressed" Color="#FFFFFF" />
|
||||
<SolidColorBrush x:Key="ExpanderHeaderForegroundDisabled" Color="#FFFFFF" />
|
||||
<SolidColorBrush x:Key="ExpanderChevronBackground" Color="Transparent" />
|
||||
<SolidColorBrush x:Key="ExpanderChevronBackgroundPointerOver" Color="#536794" />
|
||||
<SolidColorBrush x:Key="ExpanderChevronBackgroundPressed" Color="#47587F" />
|
||||
<SolidColorBrush x:Key="ExpanderChevronBackgroundDisabled" Color="Transparent" />
|
||||
<SolidColorBrush x:Key="ExpanderChevronForeground" Color="#FFFFFF" />
|
||||
<SolidColorBrush x:Key="ExpanderChevronForegroundPointerOver" Color="#FFFFFF" />
|
||||
<SolidColorBrush x:Key="ExpanderChevronForegroundPressed" Color="#FFFFFF" />
|
||||
<SolidColorBrush x:Key="ExpanderChevronForegroundDisabled" Color="#FFFFFF" />
|
||||
|
||||
</Application.Resources>
|
||||
|
||||
</Application>
|
20
app/Desktop/App.axaml.cs
Normal file
20
app/Desktop/App.axaml.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls.ApplicationLifetimes;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using DHT.Desktop.Main;
|
||||
|
||||
namespace DHT.Desktop;
|
||||
|
||||
sealed class App : Application {
|
||||
public override void Initialize() {
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
|
||||
public override void OnFrameworkInitializationCompleted() {
|
||||
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) {
|
||||
desktop.MainWindow = new MainWindow(Program.Arguments);
|
||||
}
|
||||
|
||||
base.OnFrameworkInitializationCompleted();
|
||||
}
|
||||
}
|
72
app/Desktop/Arguments.cs
Normal file
72
app/Desktop/Arguments.cs
Normal file
@@ -0,0 +1,72 @@
|
||||
using System;
|
||||
using DHT.Utils.Logging;
|
||||
|
||||
namespace DHT.Desktop;
|
||||
|
||||
sealed class Arguments {
|
||||
private static readonly Log Log = Log.ForType<Arguments>();
|
||||
|
||||
private const int FirstArgument = 1;
|
||||
|
||||
public static Arguments Empty => new(Array.Empty<string>());
|
||||
|
||||
public bool Console { get; }
|
||||
public string? DatabaseFile { get; }
|
||||
public ushort? ServerPort { get; }
|
||||
public string? ServerToken { get; }
|
||||
|
||||
public Arguments(string[] args) {
|
||||
for (int i = FirstArgument; i < args.Length; i++) {
|
||||
string key = args[i];
|
||||
|
||||
switch (key) {
|
||||
case "-debug":
|
||||
Log.IsDebugEnabled = true;
|
||||
continue;
|
||||
|
||||
case "-console":
|
||||
Console = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
string value;
|
||||
|
||||
if (i == FirstArgument && !key.StartsWith('-')) {
|
||||
value = key;
|
||||
key = "-db";
|
||||
}
|
||||
else if (i >= args.Length - 1) {
|
||||
Log.Warn("Missing value for command line argument: " + key);
|
||||
continue;
|
||||
}
|
||||
else {
|
||||
value = args[++i];
|
||||
}
|
||||
|
||||
switch (key) {
|
||||
case "-db":
|
||||
DatabaseFile = value;
|
||||
continue;
|
||||
|
||||
case "-port": {
|
||||
if (ushort.TryParse(value, out var port)) {
|
||||
ServerPort = port;
|
||||
}
|
||||
else {
|
||||
Log.Warn("Invalid port number: " + value);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
case "-token":
|
||||
ServerToken = value;
|
||||
continue;
|
||||
|
||||
default:
|
||||
Log.Warn("Unknown command line argument: " + key);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
53
app/Desktop/Common/BytesValueConverter.cs
Normal file
53
app/Desktop/Common/BytesValueConverter.cs
Normal file
@@ -0,0 +1,53 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using Avalonia.Data.Converters;
|
||||
|
||||
namespace DHT.Desktop.Common;
|
||||
|
||||
sealed class BytesValueConverter : IValueConverter {
|
||||
private sealed class Unit {
|
||||
private readonly string label;
|
||||
private readonly string numberFormat;
|
||||
|
||||
public Unit(string label, int decimalPlaces) {
|
||||
this.label = label;
|
||||
this.numberFormat = "{0:n" + decimalPlaces + "}";
|
||||
}
|
||||
|
||||
public string Format(double size) {
|
||||
return string.Format(Program.Culture, numberFormat, size) + " " + label;
|
||||
}
|
||||
}
|
||||
|
||||
private static readonly Unit[] Units = {
|
||||
new ("B", decimalPlaces: 0),
|
||||
new ("kB", decimalPlaces: 0),
|
||||
new ("MB", decimalPlaces: 1),
|
||||
new ("GB", decimalPlaces: 1),
|
||||
new ("TB", decimalPlaces: 1)
|
||||
};
|
||||
|
||||
private const int Scale = 1000;
|
||||
|
||||
private static string Convert(ulong size) {
|
||||
int power = size == 0L ? 0 : (int) Math.Log(size, Scale);
|
||||
int unit = power >= Units.Length ? Units.Length - 1 : power;
|
||||
return Units[unit].Format(unit == 0 ? size : size / Math.Pow(Scale, unit));
|
||||
}
|
||||
|
||||
public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture) {
|
||||
if (value is long size and >= 0L) {
|
||||
return Convert((ulong) size);
|
||||
}
|
||||
else if (value is ulong usize) {
|
||||
return Convert(usize);
|
||||
}
|
||||
else {
|
||||
return "-";
|
||||
}
|
||||
}
|
||||
|
||||
public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) {
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
}
|
75
app/Desktop/Common/DatabaseGui.cs
Normal file
75
app/Desktop/Common/DatabaseGui.cs
Normal file
@@ -0,0 +1,75 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Platform.Storage;
|
||||
using Avalonia.Threading;
|
||||
using DHT.Desktop.Dialogs.File;
|
||||
using DHT.Desktop.Dialogs.Message;
|
||||
using DHT.Server.Database;
|
||||
using DHT.Server.Database.Exceptions;
|
||||
using DHT.Server.Database.Sqlite;
|
||||
using DHT.Utils.Logging;
|
||||
|
||||
namespace DHT.Desktop.Common;
|
||||
|
||||
static class DatabaseGui {
|
||||
private static readonly Log Log = Log.ForType(typeof(DatabaseGui));
|
||||
|
||||
private const string DatabaseFileInitialName = "archive.dht";
|
||||
|
||||
private static readonly IReadOnlyList<FilePickerFileType> DatabaseFileDialogFilter = new List<FilePickerFileType> {
|
||||
FileDialogs.CreateFilter("Discord History Tracker Database", new [] { "dht" })
|
||||
};
|
||||
|
||||
public static async Task<string[]> NewOpenDatabaseFilesDialog(Window window, string? suggestedDirectory) {
|
||||
return await window.StorageProvider.OpenFiles(new FilePickerOpenOptions {
|
||||
Title = "Open Database File",
|
||||
FileTypeFilter = DatabaseFileDialogFilter,
|
||||
SuggestedStartLocation = await FileDialogs.GetSuggestedStartLocation(window, suggestedDirectory),
|
||||
AllowMultiple = true
|
||||
});
|
||||
}
|
||||
|
||||
public static async Task<string?> NewOpenOrCreateDatabaseFileDialog(Window window, string? suggestedDirectory) {
|
||||
return await window.StorageProvider.SaveFile(new FilePickerSaveOptions {
|
||||
Title = "Open or Create Database File",
|
||||
FileTypeChoices = DatabaseFileDialogFilter,
|
||||
SuggestedFileName = DatabaseFileInitialName,
|
||||
SuggestedStartLocation = await FileDialogs.GetSuggestedStartLocation(window, suggestedDirectory),
|
||||
ShowOverwritePrompt = false
|
||||
});
|
||||
}
|
||||
|
||||
public static async Task<IDatabaseFile?> TryOpenOrCreateDatabaseFromPath(string path, Window window, ISchemaUpgradeCallbacks schemaUpgradeCallbacks) {
|
||||
var prevSynchronizationContext = SynchronizationContext.Current;
|
||||
SynchronizationContext.SetSynchronizationContext(new AvaloniaSynchronizationContext());
|
||||
var taskScheduler = TaskScheduler.FromCurrentSynchronizationContext();
|
||||
SynchronizationContext.SetSynchronizationContext(prevSynchronizationContext);
|
||||
|
||||
IDatabaseFile? file = null;
|
||||
|
||||
try {
|
||||
file = await SqliteDatabaseFile.OpenOrCreate(path, schemaUpgradeCallbacks, taskScheduler);
|
||||
} catch (InvalidDatabaseVersionException ex) {
|
||||
await Dialog.ShowOk(window, "Database Error", "Database '" + Path.GetFileName(path) + "' appears to be corrupted (invalid version: " + ex.Version + ").");
|
||||
} catch (DatabaseTooNewException ex) {
|
||||
await Dialog.ShowOk(window, "Database Error", "Database '" + Path.GetFileName(path) + "' was opened in a newer version of DHT (database version " + ex.DatabaseVersion + ", app version " + ex.CurrentVersion + ").");
|
||||
} catch (Exception ex) {
|
||||
Log.Error(ex);
|
||||
await Dialog.ShowOk(window, "Database Error", "Database '" + Path.GetFileName(path) + "' could not be opened:" + ex.Message);
|
||||
}
|
||||
|
||||
return file;
|
||||
}
|
||||
|
||||
public static async Task<DialogResult.YesNo> ShowCanUpgradeDatabaseDialog(Window window) {
|
||||
return await Dialog.ShowYesNo(window, "Database Upgrade", "This database was created with an older version of DHT. If you proceed, the database will be upgraded and will no longer open in previous versions of DHT.\n\nPlease ensure you have a backup of the database. Do you want to proceed with the upgrade?");
|
||||
}
|
||||
|
||||
public static async Task<DialogResult.YesNo> ShowCanUpgradeMultipleDatabaseDialog(Window window) {
|
||||
return await Dialog.ShowYesNo(window, "Database Upgrade", "One or more databases were created with an older version of DHT. If you proceed, these databases will be upgraded and will no longer open in previous versions of DHT. Otherwise, these databases will be skipped.\n\nPlease ensure you have a backup of the databases. Do you want to proceed with the upgrade?");
|
||||
}
|
||||
}
|
15
app/Desktop/Common/NumberValueConverter.cs
Normal file
15
app/Desktop/Common/NumberValueConverter.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using Avalonia.Data.Converters;
|
||||
|
||||
namespace DHT.Desktop.Common;
|
||||
|
||||
sealed class NumberValueConverter : IValueConverter {
|
||||
public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture) {
|
||||
return value == null ? "-" : string.Format(Program.Culture, "{0:n0}", value);
|
||||
}
|
||||
|
||||
public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) {
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
}
|
19
app/Desktop/Common/TextFormat.cs
Normal file
19
app/Desktop/Common/TextFormat.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
namespace DHT.Desktop.Common;
|
||||
|
||||
static class TextFormat {
|
||||
public static string Format(this int number) {
|
||||
return number.ToString("N0", Program.Culture);
|
||||
}
|
||||
|
||||
public static string Format(this long number) {
|
||||
return number.ToString("N0", Program.Culture);
|
||||
}
|
||||
|
||||
public static string Pluralize(this int number, string singular) {
|
||||
return number.Format() + "\u00A0" + (number == 1 ? singular : singular + "s");
|
||||
}
|
||||
|
||||
public static string Pluralize(this long number, string singular) {
|
||||
return number.Format() + "\u00A0" + (number == 1 ? singular : singular + "s");
|
||||
}
|
||||
}
|
51
app/Desktop/Desktop.csproj
Normal file
51
app/Desktop/Desktop.csproj
Normal file
@@ -0,0 +1,51 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<RootNamespace>DHT.Desktop</RootNamespace>
|
||||
<AssemblyName>DiscordHistoryTracker</AssemblyName>
|
||||
<PackageId>DiscordHistoryTracker</PackageId>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<ApplicationIcon>./Resources/icon.ico</ApplicationIcon>
|
||||
<AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>
|
||||
<CheckForOverflowUnderflow>true</CheckForOverflowUnderflow>
|
||||
<SatelliteResourceLanguages>en</SatelliteResourceLanguages>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Avalonia" Version="11.0.6" />
|
||||
<PackageReference Include="Avalonia.Controls.DataGrid" Version="11.0.6" />
|
||||
<PackageReference Include="Avalonia.Controls.ItemsRepeater" Version="11.0.6" />
|
||||
<PackageReference Include="Avalonia.Desktop" Version="11.0.6" />
|
||||
<PackageReference Include="Avalonia.Diagnostics" Version="11.0.6" Condition=" '$(Configuration)' == 'Debug' " />
|
||||
<PackageReference Include="Avalonia.Fonts.Inter" Version="11.0.6" />
|
||||
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.0.6" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Server\Server.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Include="..\Version.cs" Link="Version.cs" />
|
||||
<Compile Update="Dialogs\TextBox\TextBoxDialog.axaml.cs">
|
||||
<DependentUpon>CheckBoxDialog.axaml</DependentUpon>
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<AvaloniaResource Include="Resources/icon.ico" />
|
||||
<EmbeddedResource Include="Resources/tracker-loader.js">
|
||||
<LogicalName>tracker-loader.js</LogicalName>
|
||||
</EmbeddedResource>
|
||||
<EmbeddedResource Include="../Resources/Viewer/**">
|
||||
<LogicalName>Viewer\%(RecursiveDir)%(Filename)%(Extension)</LogicalName>
|
||||
<Link>Resources/Viewer/%(RecursiveDir)%(Filename)%(Extension)</Link>
|
||||
<Visible>false</Visible>
|
||||
</EmbeddedResource>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
62
app/Desktop/Dialogs/CheckBox/CheckBoxDialog.axaml
Normal file
62
app/Desktop/Dialogs/CheckBox/CheckBoxDialog.axaml
Normal file
@@ -0,0 +1,62 @@
|
||||
<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"
|
||||
xmlns:namespace="clr-namespace:DHT.Desktop.Dialogs.CheckBox"
|
||||
mc:Ignorable="d" d:DesignWidth="500"
|
||||
x:Class="DHT.Desktop.Dialogs.CheckBox.CheckBoxDialog"
|
||||
x:DataType="namespace:CheckBoxDialogModel"
|
||||
Title="{Binding Title}"
|
||||
Icon="avares://DiscordHistoryTracker/Resources/icon.ico"
|
||||
Width="500" SizeToContent="Height" CanResize="False"
|
||||
WindowStartupLocation="CenterOwner">
|
||||
|
||||
<Window.DataContext>
|
||||
<namespace:CheckBoxDialogModel />
|
||||
</Window.DataContext>
|
||||
|
||||
<Window.Styles>
|
||||
<Style Selector="Panel.buttons">
|
||||
<Setter Property="Margin" Value="0 20 0 0" />
|
||||
</Style>
|
||||
<Style Selector="Panel.buttons > WrapPanel.right">
|
||||
<Setter Property="HorizontalAlignment" Value="Right" />
|
||||
</Style>
|
||||
<Style Selector="Panel.buttons Button">
|
||||
<Setter Property="MinWidth" Value="80" />
|
||||
</Style>
|
||||
<Style Selector="Panel.buttons > WrapPanel.left Button">
|
||||
<Setter Property="Margin" Value="0 0 8 0" />
|
||||
</Style>
|
||||
<Style Selector="Panel.buttons > WrapPanel.right Button">
|
||||
<Setter Property="Margin" Value="8 0 0 0" />
|
||||
</Style>
|
||||
</Window.Styles>
|
||||
|
||||
<StackPanel Margin="20">
|
||||
<ScrollViewer MaxHeight="400">
|
||||
<ItemsRepeater ItemsSource="{Binding Items}">
|
||||
<ItemsRepeater.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<CheckBox IsChecked="{Binding Checked}">
|
||||
<Label>
|
||||
<TextBlock Text="{Binding Title}" TextWrapping="Wrap" />
|
||||
</Label>
|
||||
</CheckBox>
|
||||
</DataTemplate>
|
||||
</ItemsRepeater.ItemTemplate>
|
||||
</ItemsRepeater>
|
||||
</ScrollViewer>
|
||||
<Panel Classes="buttons">
|
||||
<WrapPanel Classes="left">
|
||||
<Button Command="{Binding SelectAll}" IsEnabled="{Binding !AreAllSelected}">Select All</Button>
|
||||
<Button Command="{Binding SelectNone}" IsEnabled="{Binding !AreNoneSelected}">Select None</Button>
|
||||
</WrapPanel>
|
||||
<WrapPanel Classes="right">
|
||||
<Button Click="ClickOk">OK</Button>
|
||||
<Button Click="ClickCancel">Cancel</Button>
|
||||
</WrapPanel>
|
||||
</Panel>
|
||||
</StackPanel>
|
||||
|
||||
</Window>
|
21
app/Desktop/Dialogs/CheckBox/CheckBoxDialog.axaml.cs
Normal file
21
app/Desktop/Dialogs/CheckBox/CheckBoxDialog.axaml.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Interactivity;
|
||||
using DHT.Desktop.Dialogs.Message;
|
||||
|
||||
namespace DHT.Desktop.Dialogs.CheckBox;
|
||||
|
||||
[SuppressMessage("ReSharper", "MemberCanBeInternal")]
|
||||
public sealed partial class CheckBoxDialog : Window {
|
||||
public CheckBoxDialog() {
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
public void ClickOk(object? sender, RoutedEventArgs e) {
|
||||
Close(DialogResult.OkCancel.Ok);
|
||||
}
|
||||
|
||||
public void ClickCancel(object? sender, RoutedEventArgs e) {
|
||||
Close(DialogResult.OkCancel.Cancel);
|
||||
}
|
||||
}
|
70
app/Desktop/Dialogs/CheckBox/CheckBoxDialogModel.cs
Normal file
70
app/Desktop/Dialogs/CheckBox/CheckBoxDialogModel.cs
Normal file
@@ -0,0 +1,70 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using DHT.Utils.Models;
|
||||
|
||||
namespace DHT.Desktop.Dialogs.CheckBox;
|
||||
|
||||
class CheckBoxDialogModel : BaseModel {
|
||||
public string Title { get; init; } = "";
|
||||
|
||||
private IReadOnlyList<CheckBoxItem> items = Array.Empty<CheckBoxItem>();
|
||||
|
||||
public IReadOnlyList<CheckBoxItem> Items {
|
||||
get => items;
|
||||
|
||||
protected set {
|
||||
foreach (var item in items) {
|
||||
item.PropertyChanged -= OnItemPropertyChanged;
|
||||
}
|
||||
|
||||
items = value;
|
||||
|
||||
foreach (var item in items) {
|
||||
item.PropertyChanged += OnItemPropertyChanged;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool pauseCheckEvents = false;
|
||||
|
||||
public bool AreAllSelected => Items.All(static item => item.Checked);
|
||||
public bool AreNoneSelected => Items.All(static item => !item.Checked);
|
||||
|
||||
public void SelectAll() => SetAllChecked(true);
|
||||
public void SelectNone() => SetAllChecked(false);
|
||||
|
||||
private void SetAllChecked(bool isChecked) {
|
||||
pauseCheckEvents = true;
|
||||
|
||||
foreach (var item in Items) {
|
||||
item.Checked = isChecked;
|
||||
}
|
||||
|
||||
pauseCheckEvents = false;
|
||||
UpdateBulkButtons();
|
||||
}
|
||||
|
||||
private void UpdateBulkButtons() {
|
||||
OnPropertyChanged(nameof(AreAllSelected));
|
||||
OnPropertyChanged(nameof(AreNoneSelected));
|
||||
}
|
||||
|
||||
private void OnItemPropertyChanged(object? sender, PropertyChangedEventArgs e) {
|
||||
if (!pauseCheckEvents && e.PropertyName == nameof(CheckBoxItem.Checked)) {
|
||||
UpdateBulkButtons();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sealed class CheckBoxDialogModel<T> : CheckBoxDialogModel {
|
||||
public new IReadOnlyList<CheckBoxItem<T>> Items { get; }
|
||||
|
||||
public IEnumerable<CheckBoxItem<T>> SelectedItems => Items.Where(static item => item.Checked);
|
||||
|
||||
public CheckBoxDialogModel(IEnumerable<CheckBoxItem<T>> items) {
|
||||
this.Items = new List<CheckBoxItem<T>>(items);
|
||||
base.Items = this.Items;
|
||||
}
|
||||
}
|
24
app/Desktop/Dialogs/CheckBox/CheckBoxItem.cs
Normal file
24
app/Desktop/Dialogs/CheckBox/CheckBoxItem.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using DHT.Utils.Models;
|
||||
|
||||
namespace DHT.Desktop.Dialogs.CheckBox;
|
||||
|
||||
class CheckBoxItem : BaseModel {
|
||||
public string Title { get; init; } = "";
|
||||
public object? Item { get; init; } = null;
|
||||
|
||||
private bool isChecked = false;
|
||||
|
||||
public bool Checked {
|
||||
get => isChecked;
|
||||
set => Change(ref isChecked, value);
|
||||
}
|
||||
}
|
||||
|
||||
sealed class CheckBoxItem<T> : CheckBoxItem {
|
||||
public new T Item { get; }
|
||||
|
||||
public CheckBoxItem(T item) {
|
||||
this.Item = item;
|
||||
base.Item = item;
|
||||
}
|
||||
}
|
36
app/Desktop/Dialogs/File/FileDialogs.cs
Normal file
36
app/Desktop/Dialogs/File/FileDialogs.cs
Normal file
@@ -0,0 +1,36 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Platform.Storage;
|
||||
|
||||
namespace DHT.Desktop.Dialogs.File;
|
||||
|
||||
static class FileDialogs {
|
||||
public static async Task<string[]> OpenFiles(this IStorageProvider storageProvider, FilePickerOpenOptions options) {
|
||||
return (await storageProvider.OpenFilePickerAsync(options)).ToLocalPaths();
|
||||
}
|
||||
|
||||
public static async Task<string?> SaveFile(this IStorageProvider storageProvider, FilePickerSaveOptions options) {
|
||||
return (await storageProvider.SaveFilePickerAsync(options))?.ToLocalPath();
|
||||
}
|
||||
|
||||
public static FilePickerFileType CreateFilter(string name, string[] extensions) {
|
||||
return new FilePickerFileType(name) {
|
||||
Patterns = extensions.Select(static ext => "*." + ext).ToArray()
|
||||
};
|
||||
}
|
||||
|
||||
public static Task<IStorageFolder?> GetSuggestedStartLocation(Window window, string? suggestedDirectory) {
|
||||
return suggestedDirectory == null ? Task.FromResult<IStorageFolder?>(null) : window.StorageProvider.TryGetFolderFromPathAsync(suggestedDirectory);
|
||||
}
|
||||
|
||||
private static string ToLocalPath(this IStorageFile file) {
|
||||
return file.TryGetLocalPath() ?? throw new NotSupportedException("Local filesystem is not supported.");
|
||||
}
|
||||
|
||||
private static string[] ToLocalPaths(this IReadOnlyList<IStorageFile> files) {
|
||||
return files.Select(ToLocalPath).ToArray();
|
||||
}
|
||||
}
|
74
app/Desktop/Dialogs/Message/Dialog.cs
Normal file
74
app/Desktop/Dialogs/Message/Dialog.cs
Normal file
@@ -0,0 +1,74 @@
|
||||
using System.Threading.Tasks;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Threading;
|
||||
|
||||
namespace DHT.Desktop.Dialogs.Message;
|
||||
|
||||
static class Dialog {
|
||||
public static async Task ShowOk(Window owner, string title, string message) {
|
||||
if (!Dispatcher.UIThread.CheckAccess()) {
|
||||
await Dispatcher.UIThread.InvokeAsync(() => ShowOk(owner, title, message));
|
||||
return;
|
||||
}
|
||||
|
||||
await new MessageDialog {
|
||||
DataContext = new MessageDialogModel {
|
||||
Title = title,
|
||||
Message = message,
|
||||
IsOkVisible = true
|
||||
}
|
||||
}.ShowDialog<DialogResult.All>(owner);
|
||||
}
|
||||
|
||||
public static async Task<DialogResult.OkCancel> ShowOkCancel(Window owner, string title, string message) {
|
||||
if (!Dispatcher.UIThread.CheckAccess()) {
|
||||
return await Dispatcher.UIThread.InvokeAsync(() => ShowOkCancel(owner, title, message));
|
||||
}
|
||||
|
||||
var result = await new MessageDialog {
|
||||
DataContext = new MessageDialogModel {
|
||||
Title = title,
|
||||
Message = message,
|
||||
IsOkVisible = true,
|
||||
IsCancelVisible = true
|
||||
}
|
||||
}.ShowDialog<DialogResult.All?>(owner);
|
||||
|
||||
return result.ToOkCancel();
|
||||
}
|
||||
|
||||
public static async Task<DialogResult.YesNo> ShowYesNo(Window owner, string title, string message) {
|
||||
if (!Dispatcher.UIThread.CheckAccess()) {
|
||||
return await Dispatcher.UIThread.InvokeAsync(() => ShowYesNo(owner, title, message));
|
||||
}
|
||||
|
||||
var result = await new MessageDialog {
|
||||
DataContext = new MessageDialogModel {
|
||||
Title = title,
|
||||
Message = message,
|
||||
IsYesVisible = true,
|
||||
IsNoVisible = true
|
||||
}
|
||||
}.ShowDialog<DialogResult.All?>(owner);
|
||||
|
||||
return result.ToYesNo();
|
||||
}
|
||||
|
||||
public static async Task<DialogResult.YesNoCancel> ShowYesNoCancel(Window owner, string title, string message) {
|
||||
if (!Dispatcher.UIThread.CheckAccess()) {
|
||||
return await Dispatcher.UIThread.InvokeAsync(() => ShowYesNoCancel(owner, title, message));
|
||||
}
|
||||
|
||||
var result = await new MessageDialog {
|
||||
DataContext = new MessageDialogModel {
|
||||
Title = title,
|
||||
Message = message,
|
||||
IsYesVisible = true,
|
||||
IsNoVisible = true,
|
||||
IsCancelVisible = true
|
||||
}
|
||||
}.ShowDialog<DialogResult.All?>(owner);
|
||||
|
||||
return result.ToYesNoCancel();
|
||||
}
|
||||
}
|
59
app/Desktop/Dialogs/Message/DialogResult.cs
Normal file
59
app/Desktop/Dialogs/Message/DialogResult.cs
Normal file
@@ -0,0 +1,59 @@
|
||||
using System;
|
||||
|
||||
namespace DHT.Desktop.Dialogs.Message;
|
||||
|
||||
static class DialogResult {
|
||||
public enum All {
|
||||
Ok,
|
||||
Yes,
|
||||
No,
|
||||
Cancel
|
||||
}
|
||||
|
||||
public enum OkCancel {
|
||||
Closed,
|
||||
Ok,
|
||||
Cancel
|
||||
}
|
||||
|
||||
public enum YesNo {
|
||||
Closed,
|
||||
Yes,
|
||||
No
|
||||
}
|
||||
|
||||
public enum YesNoCancel {
|
||||
Closed,
|
||||
Yes,
|
||||
No,
|
||||
Cancel
|
||||
}
|
||||
|
||||
public static OkCancel ToOkCancel(this All? result) {
|
||||
return result switch {
|
||||
null => OkCancel.Closed,
|
||||
All.Ok => OkCancel.Ok,
|
||||
All.Cancel => OkCancel.Cancel,
|
||||
_ => throw new ArgumentException("Cannot convert dialog result " + result + " to ok/cancel.")
|
||||
};
|
||||
}
|
||||
|
||||
public static YesNo ToYesNo(this All? result) {
|
||||
return result switch {
|
||||
null => YesNo.Closed,
|
||||
All.Yes => YesNo.Yes,
|
||||
All.No => YesNo.No,
|
||||
_ => throw new ArgumentException("Cannot convert dialog result " + result + " to yes/no.")
|
||||
};
|
||||
}
|
||||
|
||||
public static YesNoCancel ToYesNoCancel(this All? result) {
|
||||
return result switch {
|
||||
null => YesNoCancel.Closed,
|
||||
All.Yes => YesNoCancel.Yes,
|
||||
All.No => YesNoCancel.No,
|
||||
All.Cancel => YesNoCancel.Cancel,
|
||||
_ => throw new ArgumentException("Cannot convert dialog result " + result + " to yes/no/cancel.")
|
||||
};
|
||||
}
|
||||
}
|
43
app/Desktop/Dialogs/Message/MessageDialog.axaml
Normal file
43
app/Desktop/Dialogs/Message/MessageDialog.axaml
Normal file
@@ -0,0 +1,43 @@
|
||||
<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"
|
||||
xmlns:namespace="clr-namespace:DHT.Desktop.Dialogs.Message"
|
||||
mc:Ignorable="d" d:DesignWidth="500"
|
||||
x:Class="DHT.Desktop.Dialogs.Message.MessageDialog"
|
||||
x:DataType="namespace:MessageDialogModel"
|
||||
Title="{Binding Title}"
|
||||
Icon="avares://DiscordHistoryTracker/Resources/icon.ico"
|
||||
Width="500" SizeToContent="Height" CanResize="False"
|
||||
WindowStartupLocation="CenterOwner">
|
||||
|
||||
<Window.DataContext>
|
||||
<namespace:MessageDialogModel />
|
||||
</Window.DataContext>
|
||||
|
||||
<Window.Styles>
|
||||
<Style Selector="TextBlock">
|
||||
<Setter Property="FontSize" Value="15" />
|
||||
<Setter Property="TextWrapping" Value="Wrap" />
|
||||
</Style>
|
||||
<Style Selector="WrapPanel">
|
||||
<Setter Property="HorizontalAlignment" Value="Right" />
|
||||
<Setter Property="Margin" Value="0 8 0 0" />
|
||||
</Style>
|
||||
<Style Selector="Button">
|
||||
<Setter Property="Margin" Value="8 0 0 0" />
|
||||
<Setter Property="MinWidth" Value="80" />
|
||||
</Style>
|
||||
</Window.Styles>
|
||||
|
||||
<StackPanel Margin="20">
|
||||
<TextBlock Text="{Binding Message}" />
|
||||
<WrapPanel>
|
||||
<Button Click="ClickOk" IsVisible="{Binding IsOkVisible}">OK</Button>
|
||||
<Button Click="ClickYes" IsVisible="{Binding IsYesVisible}">Yes</Button>
|
||||
<Button Click="ClickNo" IsVisible="{Binding IsNoVisible}">No</Button>
|
||||
<Button Click="ClickCancel" IsVisible="{Binding IsCancelVisible}">Cancel</Button>
|
||||
</WrapPanel>
|
||||
</StackPanel>
|
||||
|
||||
</Window>
|
28
app/Desktop/Dialogs/Message/MessageDialog.axaml.cs
Normal file
28
app/Desktop/Dialogs/Message/MessageDialog.axaml.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Interactivity;
|
||||
|
||||
namespace DHT.Desktop.Dialogs.Message;
|
||||
|
||||
[SuppressMessage("ReSharper", "MemberCanBeInternal")]
|
||||
public sealed partial class MessageDialog : Window {
|
||||
public MessageDialog() {
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
public void ClickOk(object? sender, RoutedEventArgs e) {
|
||||
Close(DialogResult.All.Ok);
|
||||
}
|
||||
|
||||
public void ClickYes(object? sender, RoutedEventArgs e) {
|
||||
Close(DialogResult.All.Yes);
|
||||
}
|
||||
|
||||
public void ClickNo(object? sender, RoutedEventArgs e) {
|
||||
Close(DialogResult.All.No);
|
||||
}
|
||||
|
||||
public void ClickCancel(object? sender, RoutedEventArgs e) {
|
||||
Close(DialogResult.All.Cancel);
|
||||
}
|
||||
}
|
11
app/Desktop/Dialogs/Message/MessageDialogModel.cs
Normal file
11
app/Desktop/Dialogs/Message/MessageDialogModel.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
namespace DHT.Desktop.Dialogs.Message;
|
||||
|
||||
sealed class MessageDialogModel {
|
||||
public string Title { get; init; } = "";
|
||||
public string Message { get; init; } = "";
|
||||
|
||||
public bool IsOkVisible { get; init; } = false;
|
||||
public bool IsYesVisible { get; init; } = false;
|
||||
public bool IsNoVisible { get; init; } = false;
|
||||
public bool IsCancelVisible { get; init; } = false;
|
||||
}
|
8
app/Desktop/Dialogs/Progress/IProgressCallback.cs
Normal file
8
app/Desktop/Dialogs/Progress/IProgressCallback.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace DHT.Desktop.Dialogs.Progress;
|
||||
|
||||
interface IProgressCallback {
|
||||
Task Update(string message, int finishedItems, int totalItems);
|
||||
Task Hide();
|
||||
}
|
49
app/Desktop/Dialogs/Progress/ProgressDialog.axaml
Normal file
49
app/Desktop/Dialogs/Progress/ProgressDialog.axaml
Normal file
@@ -0,0 +1,49 @@
|
||||
<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"
|
||||
xmlns:namespace="clr-namespace:DHT.Desktop.Dialogs.Progress"
|
||||
mc:Ignorable="d" d:DesignWidth="500"
|
||||
x:Class="DHT.Desktop.Dialogs.Progress.ProgressDialog"
|
||||
x:DataType="namespace:ProgressDialogModel"
|
||||
Title="{Binding Title}"
|
||||
Icon="avares://DiscordHistoryTracker/Resources/icon.ico"
|
||||
Opened="OnOpened"
|
||||
Closing="OnClosing"
|
||||
Width="500" SizeToContent="Height" CanResize="False"
|
||||
WindowStartupLocation="CenterOwner">
|
||||
|
||||
<Window.DataContext>
|
||||
<namespace:ProgressDialogModel />
|
||||
</Window.DataContext>
|
||||
|
||||
<Window.Styles>
|
||||
<Style Selector="TextBlock">
|
||||
<Setter Property="FontSize" Value="15" />
|
||||
<Setter Property="TextWrapping" Value="Wrap" />
|
||||
</Style>
|
||||
<Style Selector="TextBlock.items">
|
||||
<Setter Property="Margin" Value="10 0 0 0" />
|
||||
<Setter Property="TextAlignment" Value="Right" />
|
||||
<Setter Property="TextWrapping" Value="NoWrap" />
|
||||
</Style>
|
||||
<Style Selector="ProgressBar">
|
||||
<Setter Property="Margin" Value="0 15 0 0" />
|
||||
</Style>
|
||||
</Window.Styles>
|
||||
|
||||
<ItemsRepeater ItemsSource="{Binding Items}" Margin="0 10">
|
||||
<ItemsRepeater.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<StackPanel Margin="20 10" IsHitTestVisible="{Binding IsVisible}" Opacity="{Binding Opacity}">
|
||||
<DockPanel>
|
||||
<TextBlock DockPanel.Dock="Right" Text="{Binding Items}" Classes="items" />
|
||||
<TextBlock DockPanel.Dock="Left" Text="{Binding Message}" />
|
||||
</DockPanel>
|
||||
<ProgressBar Value="{Binding Progress}" />
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
</ItemsRepeater.ItemTemplate>
|
||||
</ItemsRepeater>
|
||||
|
||||
</Window>
|
37
app/Desktop/Dialogs/Progress/ProgressDialog.axaml.cs
Normal file
37
app/Desktop/Dialogs/Progress/ProgressDialog.axaml.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Threading.Tasks;
|
||||
using Avalonia.Controls;
|
||||
|
||||
namespace DHT.Desktop.Dialogs.Progress;
|
||||
|
||||
[SuppressMessage("ReSharper", "MemberCanBeInternal")]
|
||||
public sealed partial class ProgressDialog : Window {
|
||||
private bool isFinished = false;
|
||||
private Task progressTask = Task.CompletedTask;
|
||||
|
||||
public ProgressDialog() {
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
public void OnOpened(object? sender, EventArgs e) {
|
||||
if (DataContext is ProgressDialogModel model) {
|
||||
progressTask = Task.Run(model.StartTask);
|
||||
progressTask.ContinueWith(OnFinished, TaskScheduler.FromCurrentSynchronizationContext());
|
||||
}
|
||||
}
|
||||
|
||||
public void OnClosing(object? sender, WindowClosingEventArgs e) {
|
||||
e.Cancel = !isFinished;
|
||||
}
|
||||
|
||||
private void OnFinished(Task task) {
|
||||
isFinished = true;
|
||||
Close();
|
||||
}
|
||||
|
||||
public async Task ShowProgressDialog(Window owner) {
|
||||
await ShowDialog(owner);
|
||||
await progressTask;
|
||||
}
|
||||
}
|
53
app/Desktop/Dialogs/Progress/ProgressDialogModel.cs
Normal file
53
app/Desktop/Dialogs/Progress/ProgressDialogModel.cs
Normal file
@@ -0,0 +1,53 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Avalonia.Threading;
|
||||
using DHT.Desktop.Common;
|
||||
using DHT.Utils.Models;
|
||||
|
||||
namespace DHT.Desktop.Dialogs.Progress;
|
||||
|
||||
sealed class ProgressDialogModel : BaseModel {
|
||||
public string Title { get; init; } = "";
|
||||
|
||||
public IReadOnlyList<ProgressItem> Items { get; } = Array.Empty<ProgressItem>();
|
||||
|
||||
private readonly TaskRunner? task;
|
||||
|
||||
[Obsolete("Designer")]
|
||||
public ProgressDialogModel() {}
|
||||
|
||||
public ProgressDialogModel(TaskRunner task, int progressItems = 1) {
|
||||
this.Items = Enumerable.Range(0, progressItems).Select(static _ => new ProgressItem()).ToArray();
|
||||
this.task = task;
|
||||
}
|
||||
|
||||
internal async Task StartTask() {
|
||||
if (task != null) {
|
||||
await task(Items.Select(static item => new Callback(item)).ToArray());
|
||||
}
|
||||
}
|
||||
|
||||
public delegate Task TaskRunner(IReadOnlyList<IProgressCallback> callbacks);
|
||||
|
||||
private sealed class Callback : IProgressCallback {
|
||||
private readonly ProgressItem item;
|
||||
|
||||
public Callback(ProgressItem item) {
|
||||
this.item = item;
|
||||
}
|
||||
|
||||
public async Task Update(string message, int finishedItems, int totalItems) {
|
||||
await Dispatcher.UIThread.InvokeAsync(() => {
|
||||
item.Message = message;
|
||||
item.Items = totalItems == 0 ? string.Empty : finishedItems.Format() + " / " + totalItems.Format();
|
||||
item.Progress = totalItems == 0 ? 0 : 100 * finishedItems / totalItems;
|
||||
});
|
||||
}
|
||||
|
||||
public Task Hide() {
|
||||
return Update(string.Empty, 0, 0);
|
||||
}
|
||||
}
|
||||
}
|
41
app/Desktop/Dialogs/Progress/ProgressItem.cs
Normal file
41
app/Desktop/Dialogs/Progress/ProgressItem.cs
Normal file
@@ -0,0 +1,41 @@
|
||||
using DHT.Utils.Models;
|
||||
|
||||
namespace DHT.Desktop.Dialogs.Progress;
|
||||
|
||||
sealed class ProgressItem : BaseModel {
|
||||
private bool isVisible = false;
|
||||
|
||||
public bool IsVisible {
|
||||
get => isVisible;
|
||||
private set {
|
||||
Change(ref isVisible, value);
|
||||
OnPropertyChanged(nameof(Opacity));
|
||||
}
|
||||
}
|
||||
|
||||
public double Opacity => IsVisible ? 1.0 : 0.0;
|
||||
|
||||
private string message = "";
|
||||
|
||||
public string Message {
|
||||
get => message;
|
||||
set {
|
||||
Change(ref message, value);
|
||||
IsVisible = !string.IsNullOrEmpty(value);
|
||||
}
|
||||
}
|
||||
|
||||
private string items = "";
|
||||
|
||||
public string Items {
|
||||
get => items;
|
||||
set => Change(ref items, value);
|
||||
}
|
||||
|
||||
private int progress = 0;
|
||||
|
||||
public int Progress {
|
||||
get => progress;
|
||||
set => Change(ref progress, value);
|
||||
}
|
||||
}
|
57
app/Desktop/Dialogs/TextBox/TextBoxDialog.axaml
Normal file
57
app/Desktop/Dialogs/TextBox/TextBoxDialog.axaml
Normal file
@@ -0,0 +1,57 @@
|
||||
<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"
|
||||
xmlns:namespace="clr-namespace:DHT.Desktop.Dialogs.TextBox"
|
||||
mc:Ignorable="d" d:DesignWidth="500"
|
||||
x:Class="DHT.Desktop.Dialogs.TextBox.TextBoxDialog"
|
||||
x:DataType="namespace:TextBoxDialogModel"
|
||||
Title="{Binding Title}"
|
||||
Icon="avares://DiscordHistoryTracker/Resources/icon.ico"
|
||||
Width="500" SizeToContent="Height" CanResize="False"
|
||||
WindowStartupLocation="CenterOwner">
|
||||
|
||||
<Window.DataContext>
|
||||
<namespace:TextBoxDialogModel />
|
||||
</Window.DataContext>
|
||||
|
||||
<Window.Styles>
|
||||
<Style Selector="Panel.buttons">
|
||||
<Setter Property="Margin" Value="0 20 0 0" />
|
||||
</Style>
|
||||
<Style Selector="Panel.buttons > WrapPanel.right">
|
||||
<Setter Property="HorizontalAlignment" Value="Right" />
|
||||
</Style>
|
||||
<Style Selector="Panel.buttons Button">
|
||||
<Setter Property="MinWidth" Value="80" />
|
||||
<Setter Property="Margin" Value="8 0 0 0" />
|
||||
</Style>
|
||||
</Window.Styles>
|
||||
|
||||
<StackPanel Margin="20">
|
||||
<ScrollViewer MaxHeight="400">
|
||||
<StackPanel Spacing="10">
|
||||
<TextBlock Text="{Binding Description}" TextWrapping="Wrap" />
|
||||
<ItemsRepeater ItemsSource="{Binding Items}">
|
||||
<ItemsRepeater.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<DockPanel Margin="0 5 25 0">
|
||||
<TextBox Name="Input" Text="{Binding Value}" Width="180" VerticalAlignment="Top" DockPanel.Dock="Right" />
|
||||
<Label Target="Input" VerticalAlignment="Center" DockPanel.Dock="Left">
|
||||
<TextBlock Text="{Binding Title}" TextWrapping="Wrap" />
|
||||
</Label>
|
||||
</DockPanel>
|
||||
</DataTemplate>
|
||||
</ItemsRepeater.ItemTemplate>
|
||||
</ItemsRepeater>
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
<Panel Classes="buttons">
|
||||
<WrapPanel Classes="right">
|
||||
<Button Click="ClickOk" IsEnabled="{Binding !HasErrors}">OK</Button>
|
||||
<Button Click="ClickCancel">Cancel</Button>
|
||||
</WrapPanel>
|
||||
</Panel>
|
||||
</StackPanel>
|
||||
|
||||
</Window>
|
21
app/Desktop/Dialogs/TextBox/TextBoxDialog.axaml.cs
Normal file
21
app/Desktop/Dialogs/TextBox/TextBoxDialog.axaml.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Interactivity;
|
||||
using DHT.Desktop.Dialogs.Message;
|
||||
|
||||
namespace DHT.Desktop.Dialogs.TextBox;
|
||||
|
||||
[SuppressMessage("ReSharper", "MemberCanBeInternal")]
|
||||
public sealed partial class TextBoxDialog : Window {
|
||||
public TextBoxDialog() {
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
public void ClickOk(object? sender, RoutedEventArgs e) {
|
||||
Close(DialogResult.OkCancel.Ok);
|
||||
}
|
||||
|
||||
public void ClickCancel(object? sender, RoutedEventArgs e) {
|
||||
Close(DialogResult.OkCancel.Cancel);
|
||||
}
|
||||
}
|
47
app/Desktop/Dialogs/TextBox/TextBoxDialogModel.cs
Normal file
47
app/Desktop/Dialogs/TextBox/TextBoxDialogModel.cs
Normal file
@@ -0,0 +1,47 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using DHT.Utils.Models;
|
||||
|
||||
namespace DHT.Desktop.Dialogs.TextBox;
|
||||
|
||||
class TextBoxDialogModel : BaseModel {
|
||||
public string Title { get; init; } = "";
|
||||
public string Description { get; init; } = "";
|
||||
|
||||
private IReadOnlyList<TextBoxItem> items = Array.Empty<TextBoxItem>();
|
||||
|
||||
public IReadOnlyList<TextBoxItem> Items {
|
||||
get => items;
|
||||
|
||||
protected set {
|
||||
foreach (var item in items) {
|
||||
item.ErrorsChanged -= OnItemErrorsChanged;
|
||||
}
|
||||
|
||||
items = value;
|
||||
|
||||
foreach (var item in items) {
|
||||
item.ErrorsChanged += OnItemErrorsChanged;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool HasErrors => Items.Any(static item => !item.IsValid);
|
||||
|
||||
private void OnItemErrorsChanged(object? sender, DataErrorsChangedEventArgs e) {
|
||||
OnPropertyChanged(nameof(HasErrors));
|
||||
}
|
||||
}
|
||||
|
||||
sealed class TextBoxDialogModel<T> : TextBoxDialogModel {
|
||||
public new IReadOnlyList<TextBoxItem<T>> Items { get; }
|
||||
|
||||
public IEnumerable<TextBoxItem<T>> ValidItems => Items.Where(static item => item.IsValid);
|
||||
|
||||
public TextBoxDialogModel(IEnumerable<TextBoxItem<T>> items) {
|
||||
this.Items = new List<TextBoxItem<T>>(items);
|
||||
base.Items = this.Items;
|
||||
}
|
||||
}
|
42
app/Desktop/Dialogs/TextBox/TextBoxItem.cs
Normal file
42
app/Desktop/Dialogs/TextBox/TextBoxItem.cs
Normal file
@@ -0,0 +1,42 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.ComponentModel;
|
||||
using DHT.Utils.Models;
|
||||
|
||||
namespace DHT.Desktop.Dialogs.TextBox;
|
||||
|
||||
class TextBoxItem : BaseModel, INotifyDataErrorInfo {
|
||||
public string Title { get; init; } = "";
|
||||
public object? Item { get; init; } = null;
|
||||
|
||||
public Func<string, bool> ValidityCheck { get; init; } = static _ => true;
|
||||
public bool IsValid => ValidityCheck(Value);
|
||||
|
||||
private string value = string.Empty;
|
||||
|
||||
public string Value {
|
||||
get => this.value;
|
||||
set {
|
||||
Change(ref this.value, value);
|
||||
ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs(nameof(Value)));
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable GetErrors(string? propertyName) {
|
||||
if (propertyName == nameof(Value) && !IsValid) {
|
||||
yield return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
public bool HasErrors => !IsValid;
|
||||
public event EventHandler<DataErrorsChangedEventArgs>? ErrorsChanged;
|
||||
}
|
||||
|
||||
sealed class TextBoxItem<T> : TextBoxItem {
|
||||
public new T Item { get; }
|
||||
|
||||
public TextBoxItem(T item) {
|
||||
this.Item = item;
|
||||
base.Item = item;
|
||||
}
|
||||
}
|
121
app/Desktop/Discord/DiscordAppSettings.cs
Normal file
121
app/Desktop/Discord/DiscordAppSettings.cs
Normal file
@@ -0,0 +1,121 @@
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Nodes;
|
||||
using System.Threading.Tasks;
|
||||
using DHT.Utils.Logging;
|
||||
using static System.Environment.SpecialFolder;
|
||||
using static System.Environment.SpecialFolderOption;
|
||||
|
||||
namespace DHT.Desktop.Discord;
|
||||
|
||||
static class DiscordAppSettings {
|
||||
private static readonly Log Log = Log.ForType(typeof(DiscordAppSettings));
|
||||
|
||||
private const string JsonKeyDevTools = "DANGEROUS_ENABLE_DEVTOOLS_ONLY_ENABLE_IF_YOU_KNOW_WHAT_YOURE_DOING";
|
||||
|
||||
public static string JsonFilePath { get; }
|
||||
private static string JsonBackupFilePath { get; }
|
||||
|
||||
[SuppressMessage("ReSharper", "ConvertIfStatementToConditionalTernaryExpression")]
|
||||
static DiscordAppSettings() {
|
||||
string rootFolder;
|
||||
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) {
|
||||
rootFolder = Path.Combine(Environment.GetFolderPath(ApplicationData, DoNotVerify), "Discord");
|
||||
}
|
||||
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) {
|
||||
rootFolder = Path.Combine(Environment.GetFolderPath(UserProfile, DoNotVerify), "Library", "Application Support", "Discord");
|
||||
}
|
||||
else {
|
||||
rootFolder = Path.Combine(Environment.GetFolderPath(ApplicationData, DoNotVerify), "discord");
|
||||
}
|
||||
|
||||
JsonFilePath = Path.Combine(rootFolder, "settings.json");
|
||||
JsonBackupFilePath = JsonFilePath + ".bak";
|
||||
}
|
||||
|
||||
public static async Task<bool?> AreDevToolsEnabled() {
|
||||
try {
|
||||
return AreDevToolsEnabled(await ReadSettingsJson());
|
||||
} catch (Exception e) {
|
||||
Log.Error("Cannot read settings file.");
|
||||
Log.Error(e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool AreDevToolsEnabled(JsonObject json) {
|
||||
return json.TryGetPropertyValue(JsonKeyDevTools, out var node) && node?.GetValueKind() == JsonValueKind.True;
|
||||
}
|
||||
|
||||
public static async Task<SettingsJsonResult> ConfigureDevTools(bool enable) {
|
||||
JsonObject json;
|
||||
|
||||
try {
|
||||
json = await ReadSettingsJson();
|
||||
} catch (FileNotFoundException) {
|
||||
return SettingsJsonResult.FileNotFound;
|
||||
} catch (JsonException) {
|
||||
return SettingsJsonResult.InvalidJson;
|
||||
} catch (Exception e) {
|
||||
Log.Error(e);
|
||||
return SettingsJsonResult.ReadError;
|
||||
}
|
||||
|
||||
if (enable == AreDevToolsEnabled(json)) {
|
||||
return SettingsJsonResult.AlreadySet;
|
||||
}
|
||||
|
||||
if (enable) {
|
||||
json[JsonKeyDevTools] = true;
|
||||
}
|
||||
else {
|
||||
json.Remove(JsonKeyDevTools);
|
||||
}
|
||||
|
||||
try {
|
||||
if (!File.Exists(JsonBackupFilePath)) {
|
||||
File.Copy(JsonFilePath, JsonBackupFilePath);
|
||||
}
|
||||
|
||||
await WriteSettingsJson(json);
|
||||
} catch (Exception e) {
|
||||
Log.Error("An error occurred when writing settings file.");
|
||||
Log.Error(e);
|
||||
|
||||
if (File.Exists(JsonBackupFilePath)) {
|
||||
try {
|
||||
File.Move(JsonBackupFilePath, JsonFilePath, true);
|
||||
Log.Info("Restored settings file from backup.");
|
||||
} catch (Exception e2) {
|
||||
Log.Error("Cannot restore settings file from backup.");
|
||||
Log.Error(e2);
|
||||
}
|
||||
}
|
||||
|
||||
return SettingsJsonResult.WriteError;
|
||||
}
|
||||
|
||||
try {
|
||||
File.Delete(JsonBackupFilePath);
|
||||
} catch (Exception e) {
|
||||
Log.Error("Cannot delete backup file.");
|
||||
Log.Error(e);
|
||||
}
|
||||
|
||||
return SettingsJsonResult.Success;
|
||||
}
|
||||
|
||||
private static async Task<JsonObject> ReadSettingsJson() {
|
||||
await using var stream = new FileStream(JsonFilePath, FileMode.Open, FileAccess.Read, FileShare.Read);
|
||||
return await JsonSerializer.DeserializeAsync(stream, DiscordAppSettingsJsonContext.Default.JsonObject) ?? throw new JsonException();
|
||||
}
|
||||
|
||||
private static async Task WriteSettingsJson(JsonObject json) {
|
||||
await using var stream = new FileStream(JsonFilePath, FileMode.Truncate, FileAccess.Write, FileShare.None);
|
||||
await JsonSerializer.SerializeAsync(stream, json, DiscordAppSettingsJsonContext.Default.JsonObject);
|
||||
}
|
||||
}
|
8
app/Desktop/Discord/DiscordAppSettingsJsonContext.cs
Normal file
8
app/Desktop/Discord/DiscordAppSettingsJsonContext.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
using System.Text.Json.Nodes;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace DHT.Desktop.Discord;
|
||||
|
||||
[JsonSourceGenerationOptions(GenerationMode = JsonSourceGenerationMode.Default, WriteIndented = true)]
|
||||
[JsonSerializable(typeof(JsonObject))]
|
||||
sealed partial class DiscordAppSettingsJsonContext : JsonSerializerContext {}
|
10
app/Desktop/Discord/SettingsJsonResult.cs
Normal file
10
app/Desktop/Discord/SettingsJsonResult.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
namespace DHT.Desktop.Discord;
|
||||
|
||||
enum SettingsJsonResult {
|
||||
Success,
|
||||
AlreadySet,
|
||||
FileNotFound,
|
||||
ReadError,
|
||||
InvalidJson,
|
||||
WriteError
|
||||
}
|
68
app/Desktop/Main/AboutWindow.axaml
Normal file
68
app/Desktop/Main/AboutWindow.axaml
Normal file
@@ -0,0 +1,68 @@
|
||||
<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"
|
||||
xmlns:main="clr-namespace:DHT.Desktop.Main"
|
||||
mc:Ignorable="d" d:DesignWidth="480" d:DesignHeight="295"
|
||||
x:Class="DHT.Desktop.Main.AboutWindow"
|
||||
x:DataType="main:AboutWindowModel"
|
||||
Title="About Discord History Tracker"
|
||||
Icon="avares://DiscordHistoryTracker/Resources/icon.ico"
|
||||
Width="480" Height="295" CanResize="False"
|
||||
WindowStartupLocation="CenterOwner">
|
||||
|
||||
<Design.DataContext>
|
||||
<main:AboutWindowModel />
|
||||
</Design.DataContext>
|
||||
|
||||
<Window.Styles>
|
||||
<Style Selector="StackPanel">
|
||||
<Setter Property="Orientation" Value="Horizontal" />
|
||||
<Setter Property="Spacing" Value="5" />
|
||||
</Style>
|
||||
<Style Selector="TextBlock">
|
||||
<Setter Property="TextWrapping" Value="Wrap" />
|
||||
<Setter Property="VerticalAlignment" Value="Center" />
|
||||
</Style>
|
||||
<Style Selector="Grid > Button">
|
||||
<Setter Property="Margin" Value="0 4" />
|
||||
<Setter Property="Padding" Value="8 4" />
|
||||
<Setter Property="HorizontalAlignment" Value="Stretch" />
|
||||
</Style>
|
||||
</Window.Styles>
|
||||
|
||||
<StackPanel Orientation="Vertical" Margin="20" Spacing="12">
|
||||
|
||||
<TextBlock VerticalAlignment="Center">
|
||||
Discord History Tracker was created by chylex and released under the MIT license.
|
||||
</TextBlock>
|
||||
|
||||
<StackPanel>
|
||||
<Button Command="{Binding ShowOfficialWebsite}">Official Website</Button>
|
||||
<Button Command="{Binding ShowSourceCode}">Source Code</Button>
|
||||
</StackPanel>
|
||||
|
||||
<Grid RowDefinitions="Auto,5,Auto,Auto,Auto,Auto,Auto,Auto" ColumnDefinitions="175,125,*" Margin="0 10 0 0">
|
||||
<TextBlock Grid.Row="0" Grid.Column="0" FontWeight="Bold">Third-Party Software</TextBlock>
|
||||
<TextBlock Grid.Row="0" Grid.Column="1" FontWeight="Bold">License</TextBlock>
|
||||
<TextBlock Grid.Row="0" Grid.Column="2" FontWeight="Bold">Link</TextBlock>
|
||||
|
||||
<TextBlock Grid.Row="2" Grid.Column="0">.NET 8</TextBlock>
|
||||
<TextBlock Grid.Row="2" Grid.Column="1">MIT</TextBlock>
|
||||
<Button Grid.Row="2" Grid.Column="2" Command="{Binding ShowLibraryNetCore}">GitHub</Button>
|
||||
|
||||
<TextBlock Grid.Row="3" Grid.Column="0">Avalonia</TextBlock>
|
||||
<TextBlock Grid.Row="3" Grid.Column="1">MIT</TextBlock>
|
||||
<Button Grid.Row="3" Grid.Column="2" Command="{Binding ShowLibraryAvalonia}">NuGet</Button>
|
||||
|
||||
<TextBlock Grid.Row="4" Grid.Column="0">SQLite</TextBlock>
|
||||
<TextBlock Grid.Row="4" Grid.Column="1">Public Domain</TextBlock>
|
||||
<Button Grid.Row="4" Grid.Column="2" Command="{Binding ShowLibrarySqlite}">Official Website</Button>
|
||||
|
||||
<TextBlock Grid.Row="5" Grid.Column="0">Microsoft.Data.Sqlite</TextBlock>
|
||||
<TextBlock Grid.Row="5" Grid.Column="1">Apache-2.0</TextBlock>
|
||||
<Button Grid.Row="5" Grid.Column="2" Command="{Binding ShowLibrarySqliteAdoNet}">NuGet</Button>
|
||||
</Grid>
|
||||
|
||||
</StackPanel>
|
||||
</Window>
|
11
app/Desktop/Main/AboutWindow.axaml.cs
Normal file
11
app/Desktop/Main/AboutWindow.axaml.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Avalonia.Controls;
|
||||
|
||||
namespace DHT.Desktop.Main;
|
||||
|
||||
[SuppressMessage("ReSharper", "MemberCanBeInternal")]
|
||||
public sealed partial class AboutWindow : Window {
|
||||
public AboutWindow() {
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
33
app/Desktop/Main/AboutWindowModel.cs
Normal file
33
app/Desktop/Main/AboutWindowModel.cs
Normal file
@@ -0,0 +1,33 @@
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace DHT.Desktop.Main;
|
||||
|
||||
sealed class AboutWindowModel {
|
||||
public void ShowOfficialWebsite() {
|
||||
OpenUrl("https://dht.chylex.com");
|
||||
}
|
||||
|
||||
public void ShowSourceCode() {
|
||||
OpenUrl("https://github.com/chylex/Discord-History-Tracker");
|
||||
}
|
||||
|
||||
public void ShowLibraryAvalonia() {
|
||||
OpenUrl("https://www.nuget.org/packages/Avalonia");
|
||||
}
|
||||
|
||||
public void ShowLibrarySqlite() {
|
||||
OpenUrl("https://www.sqlite.org");
|
||||
}
|
||||
|
||||
public void ShowLibrarySqliteAdoNet() {
|
||||
OpenUrl("https://www.nuget.org/packages/Microsoft.Data.Sqlite");
|
||||
}
|
||||
|
||||
public void ShowLibraryNetCore() {
|
||||
OpenUrl("https://github.com/dotnet/core");
|
||||
}
|
||||
|
||||
private static void OpenUrl(string url) {
|
||||
Process.Start(new ProcessStartInfo { FileName = url, UseShellExecute = true });
|
||||
}
|
||||
}
|
52
app/Desktop/Main/Controls/AttachmentFilterPanel.axaml
Normal file
52
app/Desktop/Main/Controls/AttachmentFilterPanel.axaml
Normal file
@@ -0,0 +1,52 @@
|
||||
<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"
|
||||
xmlns:controls="clr-namespace:DHT.Desktop.Main.Controls"
|
||||
mc:Ignorable="d"
|
||||
x:Class="DHT.Desktop.Main.Controls.AttachmentFilterPanel"
|
||||
x:DataType="controls:AttachmentFilterPanelModel">
|
||||
|
||||
<Design.DataContext>
|
||||
<controls:AttachmentFilterPanelModel />
|
||||
</Design.DataContext>
|
||||
|
||||
<UserControl.Styles>
|
||||
<Style Selector="WrapPanel > StackPanel">
|
||||
<Setter Property="Margin" Value="0 20 40 0" />
|
||||
<Setter Property="Spacing" Value="4" />
|
||||
</Style>
|
||||
<Style Selector="WrapPanel > StackPanel:nth-last-child(1)">
|
||||
<Setter Property="Margin" Value="0 20 0 0" />
|
||||
</Style>
|
||||
<Style Selector="Grid > Label">
|
||||
<Setter Property="VerticalAlignment" Value="Center" />
|
||||
</Style>
|
||||
<Style Selector="ComboBox">
|
||||
<Setter Property="Margin" Value="8 0 0 0" />
|
||||
</Style>
|
||||
<Style Selector="Button">
|
||||
<Setter Property="Margin" Value="0 0 0 8" />
|
||||
</Style>
|
||||
</UserControl.Styles>
|
||||
|
||||
<StackPanel>
|
||||
<TextBlock Text="{Binding FilterStatisticsText}" />
|
||||
<WrapPanel>
|
||||
<StackPanel>
|
||||
<CheckBox IsChecked="{Binding LimitSize}">Limit Size</CheckBox>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBox Text="{Binding MaximumSize}" IsEnabled="{Binding LimitSize}" HorizontalContentAlignment="Right" />
|
||||
<ComboBox IsEnabled="{Binding LimitSize}" ItemsSource="{Binding Units}" SelectedItem="{Binding MaximumSizeUnit}">
|
||||
<ComboBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding Name}" />
|
||||
</DataTemplate>
|
||||
</ComboBox.ItemTemplate>
|
||||
</ComboBox>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</WrapPanel>
|
||||
</StackPanel>
|
||||
|
||||
</UserControl>
|
11
app/Desktop/Main/Controls/AttachmentFilterPanel.axaml.cs
Normal file
11
app/Desktop/Main/Controls/AttachmentFilterPanel.axaml.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Avalonia.Controls;
|
||||
|
||||
namespace DHT.Desktop.Main.Controls;
|
||||
|
||||
[SuppressMessage("ReSharper", "MemberCanBeInternal")]
|
||||
public sealed partial class AttachmentFilterPanel : UserControl {
|
||||
public AttachmentFilterPanel() {
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
129
app/Desktop/Main/Controls/AttachmentFilterPanelModel.cs
Normal file
129
app/Desktop/Main/Controls/AttachmentFilterPanelModel.cs
Normal file
@@ -0,0 +1,129 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using DHT.Desktop.Common;
|
||||
using DHT.Server.Data.Filters;
|
||||
using DHT.Server.Database;
|
||||
using DHT.Utils.Models;
|
||||
using DHT.Utils.Tasks;
|
||||
|
||||
namespace DHT.Desktop.Main.Controls;
|
||||
|
||||
sealed class AttachmentFilterPanelModel : BaseModel, IDisposable {
|
||||
public sealed record Unit(string Name, uint Scale);
|
||||
|
||||
private static readonly Unit[] AllUnits = {
|
||||
new ("B", 1),
|
||||
new ("kB", 1024),
|
||||
new ("MB", 1024 * 1024)
|
||||
};
|
||||
|
||||
private static readonly HashSet<string> FilterProperties = new () {
|
||||
nameof(LimitSize),
|
||||
nameof(MaximumSize),
|
||||
nameof(MaximumSizeUnit)
|
||||
};
|
||||
|
||||
public string FilterStatisticsText { get; private set; } = "";
|
||||
|
||||
private bool limitSize = false;
|
||||
private ulong maximumSize = 0L;
|
||||
private Unit maximumSizeUnit = AllUnits[0];
|
||||
|
||||
public bool LimitSize {
|
||||
get => limitSize;
|
||||
set => Change(ref limitSize, value);
|
||||
}
|
||||
|
||||
public ulong MaximumSize {
|
||||
get => maximumSize;
|
||||
set => Change(ref maximumSize, value);
|
||||
}
|
||||
|
||||
public Unit MaximumSizeUnit {
|
||||
get => maximumSizeUnit;
|
||||
set => Change(ref maximumSizeUnit, value);
|
||||
}
|
||||
|
||||
public IEnumerable<Unit> Units => AllUnits;
|
||||
|
||||
private readonly IDatabaseFile db;
|
||||
private readonly string verb;
|
||||
|
||||
private readonly AsyncValueComputer<long> matchingAttachmentCountComputer;
|
||||
private long? matchingAttachmentCount;
|
||||
private long? totalAttachmentCount;
|
||||
|
||||
[Obsolete("Designer")]
|
||||
public AttachmentFilterPanelModel() : this(DummyDatabaseFile.Instance) {}
|
||||
|
||||
public AttachmentFilterPanelModel(IDatabaseFile db, string verb = "Matches") {
|
||||
this.db = db;
|
||||
this.verb = verb;
|
||||
|
||||
this.matchingAttachmentCountComputer = AsyncValueComputer<long>.WithResultProcessor(SetAttachmentCounts).Build();
|
||||
|
||||
UpdateFilterStatistics();
|
||||
|
||||
PropertyChanged += OnPropertyChanged;
|
||||
db.Statistics.PropertyChanged += OnDbStatisticsChanged;
|
||||
}
|
||||
|
||||
public void Dispose() {
|
||||
db.Statistics.PropertyChanged -= OnDbStatisticsChanged;
|
||||
}
|
||||
|
||||
private void OnPropertyChanged(object? sender, PropertyChangedEventArgs e) {
|
||||
if (e.PropertyName != null && FilterProperties.Contains(e.PropertyName)) {
|
||||
UpdateFilterStatistics();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnDbStatisticsChanged(object? sender, PropertyChangedEventArgs e) {
|
||||
if (e.PropertyName == nameof(DatabaseStatistics.TotalAttachments)) {
|
||||
totalAttachmentCount = db.Statistics.TotalAttachments;
|
||||
UpdateFilterStatistics();
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateFilterStatistics() {
|
||||
var filter = CreateFilter();
|
||||
if (filter.IsEmpty) {
|
||||
matchingAttachmentCountComputer.Cancel();
|
||||
matchingAttachmentCount = totalAttachmentCount;
|
||||
UpdateFilterStatisticsText();
|
||||
}
|
||||
else {
|
||||
matchingAttachmentCount = null;
|
||||
UpdateFilterStatisticsText();
|
||||
matchingAttachmentCountComputer.Compute(() => db.CountAttachments(filter));
|
||||
}
|
||||
}
|
||||
|
||||
private void SetAttachmentCounts(long matchingAttachmentCount) {
|
||||
this.matchingAttachmentCount = matchingAttachmentCount;
|
||||
UpdateFilterStatisticsText();
|
||||
}
|
||||
|
||||
private void UpdateFilterStatisticsText() {
|
||||
var matchingAttachmentCountStr = matchingAttachmentCount?.Format() ?? "(...)";
|
||||
var totalAttachmentCountStr = totalAttachmentCount?.Format() ?? "(...)";
|
||||
|
||||
FilterStatisticsText = verb + " " + matchingAttachmentCountStr + " out of " + totalAttachmentCountStr + " attachment" + (totalAttachmentCount is null or 1 ? "." : "s.");
|
||||
OnPropertyChanged(nameof(FilterStatisticsText));
|
||||
}
|
||||
|
||||
public AttachmentFilter CreateFilter() {
|
||||
AttachmentFilter filter = new();
|
||||
|
||||
if (LimitSize) {
|
||||
try {
|
||||
filter.MaxBytes = maximumSize * maximumSizeUnit.Scale;
|
||||
} catch (ArithmeticException) {
|
||||
// set no size limit, because the overflown size is larger than any file could possibly be
|
||||
}
|
||||
}
|
||||
|
||||
return filter;
|
||||
}
|
||||
}
|
64
app/Desktop/Main/Controls/MessageFilterPanel.axaml
Normal file
64
app/Desktop/Main/Controls/MessageFilterPanel.axaml
Normal file
@@ -0,0 +1,64 @@
|
||||
<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"
|
||||
xmlns:controls="clr-namespace:DHT.Desktop.Main.Controls"
|
||||
mc:Ignorable="d"
|
||||
x:Class="DHT.Desktop.Main.Controls.MessageFilterPanel"
|
||||
x:DataType="controls:MessageFilterPanelModel">
|
||||
|
||||
<Design.DataContext>
|
||||
<controls:MessageFilterPanelModel />
|
||||
</Design.DataContext>
|
||||
|
||||
<UserControl.Styles>
|
||||
<Style Selector="WrapPanel > StackPanel">
|
||||
<Setter Property="Margin" Value="0 20 40 0" />
|
||||
<Setter Property="Spacing" Value="4" />
|
||||
</Style>
|
||||
<Style Selector="WrapPanel > StackPanel:nth-last-child(1)">
|
||||
<Setter Property="Margin" Value="0 20 0 0" />
|
||||
</Style>
|
||||
<Style Selector="Grid > Label">
|
||||
<Setter Property="VerticalAlignment" Value="Center" />
|
||||
</Style>
|
||||
<Style Selector="Grid > CalendarDatePicker">
|
||||
<Setter Property="CornerRadius" Value="0" />
|
||||
<Setter Property="HorizontalAlignment" Value="Stretch" />
|
||||
<Setter Property="HorizontalContentAlignment" Value="Center" />
|
||||
<Setter Property="VerticalAlignment" Value="Center" />
|
||||
<Setter Property="VerticalContentAlignment" Value="Center" />
|
||||
<Setter Property="IsTodayHighlighted" Value="True" />
|
||||
<Setter Property="SelectedDateFormat" Value="Short" />
|
||||
</Style>
|
||||
<Style Selector="Button">
|
||||
<Setter Property="Margin" Value="0 0 0 8" />
|
||||
</Style>
|
||||
</UserControl.Styles>
|
||||
|
||||
<StackPanel>
|
||||
<TextBlock Text="{Binding FilterStatisticsText}" />
|
||||
<WrapPanel>
|
||||
<StackPanel>
|
||||
<CheckBox IsChecked="{Binding FilterByDate}">Filter by Date</CheckBox>
|
||||
<Grid ColumnDefinitions="Auto, 4, 125" RowDefinitions="Auto, 4, Auto" Margin="4 0">
|
||||
<Label Grid.Row="0" Grid.Column="0">From:</Label>
|
||||
<CalendarDatePicker Grid.Row="0" Grid.Column="2" x:Name="StartDatePicker" IsEnabled="{Binding FilterByDate}" SelectedDateChanged="CalendarDatePicker_OnSelectedDateChanged" />
|
||||
<Label Grid.Row="2" Grid.Column="0">To:</Label>
|
||||
<CalendarDatePicker Grid.Row="2" Grid.Column="2" x:Name="EndDatePicker" IsEnabled="{Binding FilterByDate}" SelectedDateChanged="CalendarDatePicker_OnSelectedDateChanged" />
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
<StackPanel>
|
||||
<CheckBox IsChecked="{Binding FilterByChannel}">Filter by Channel</CheckBox>
|
||||
<Button Command="{Binding OpenChannelFilterDialog}" IsEnabled="{Binding FilterByChannel}">Select Channels...</Button>
|
||||
<TextBlock Text="{Binding ChannelFilterLabel}" />
|
||||
</StackPanel>
|
||||
<StackPanel>
|
||||
<CheckBox IsChecked="{Binding FilterByUser}">Filter by User</CheckBox>
|
||||
<Button Command="{Binding OpenUserFilterDialog}" IsEnabled="{Binding FilterByUser}">Select Users...</Button>
|
||||
<TextBlock Text="{Binding UserFilterLabel}" />
|
||||
</StackPanel>
|
||||
</WrapPanel>
|
||||
</StackPanel>
|
||||
|
||||
</UserControl>
|
26
app/Desktop/Main/Controls/MessageFilterPanel.axaml.cs
Normal file
26
app/Desktop/Main/Controls/MessageFilterPanel.axaml.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Avalonia.Controls;
|
||||
|
||||
namespace DHT.Desktop.Main.Controls;
|
||||
|
||||
[SuppressMessage("ReSharper", "MemberCanBeInternal")]
|
||||
public sealed partial class MessageFilterPanel : UserControl {
|
||||
public MessageFilterPanel() {
|
||||
InitializeComponent();
|
||||
|
||||
var culture = Program.Culture;
|
||||
foreach (var picker in new CalendarDatePicker[] { StartDatePicker, EndDatePicker }) {
|
||||
picker.FirstDayOfWeek = culture.DateTimeFormat.FirstDayOfWeek;
|
||||
picker.SelectedDateFormat = CalendarDatePickerFormat.Custom;
|
||||
picker.CustomDateFormatString = culture.DateTimeFormat.ShortDatePattern;
|
||||
picker.Watermark = culture.DateTimeFormat.ShortDatePattern;
|
||||
}
|
||||
}
|
||||
|
||||
public void CalendarDatePicker_OnSelectedDateChanged(object? sender, SelectionChangedEventArgs e) {
|
||||
if (DataContext is MessageFilterPanelModel model) {
|
||||
model.StartDate = StartDatePicker.SelectedDate;
|
||||
model.EndDate = EndDatePicker.SelectedDate;
|
||||
}
|
||||
}
|
||||
}
|
285
app/Desktop/Main/Controls/MessageFilterPanelModel.cs
Normal file
285
app/Desktop/Main/Controls/MessageFilterPanelModel.cs
Normal file
@@ -0,0 +1,285 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Avalonia.Controls;
|
||||
using DHT.Desktop.Common;
|
||||
using DHT.Desktop.Dialogs.CheckBox;
|
||||
using DHT.Desktop.Dialogs.Message;
|
||||
using DHT.Server.Data;
|
||||
using DHT.Server.Data.Filters;
|
||||
using DHT.Server.Database;
|
||||
using DHT.Utils.Models;
|
||||
using DHT.Utils.Tasks;
|
||||
|
||||
namespace DHT.Desktop.Main.Controls;
|
||||
|
||||
sealed class MessageFilterPanelModel : BaseModel, IDisposable {
|
||||
private static readonly HashSet<string> FilterProperties = new () {
|
||||
nameof(FilterByDate),
|
||||
nameof(StartDate),
|
||||
nameof(EndDate),
|
||||
nameof(FilterByChannel),
|
||||
nameof(IncludedChannels),
|
||||
nameof(FilterByUser),
|
||||
nameof(IncludedUsers)
|
||||
};
|
||||
|
||||
public string FilterStatisticsText { get; private set; } = "";
|
||||
|
||||
public event PropertyChangedEventHandler? FilterPropertyChanged;
|
||||
|
||||
public bool HasAnyFilters => FilterByDate || FilterByChannel || FilterByUser;
|
||||
|
||||
private bool filterByDate = false;
|
||||
private DateTime? startDate = null;
|
||||
private DateTime? endDate = null;
|
||||
private bool filterByChannel = false;
|
||||
private HashSet<ulong>? includedChannels = null;
|
||||
private bool filterByUser = false;
|
||||
private HashSet<ulong>? includedUsers = null;
|
||||
|
||||
public bool FilterByDate {
|
||||
get => filterByDate;
|
||||
set => Change(ref filterByDate, value);
|
||||
}
|
||||
|
||||
public DateTime? StartDate {
|
||||
get => startDate;
|
||||
set => Change(ref startDate, value);
|
||||
}
|
||||
|
||||
public DateTime? EndDate {
|
||||
get => endDate;
|
||||
set => Change(ref endDate, value);
|
||||
}
|
||||
|
||||
public bool FilterByChannel {
|
||||
get => filterByChannel;
|
||||
set => Change(ref filterByChannel, value);
|
||||
}
|
||||
|
||||
public HashSet<ulong> IncludedChannels {
|
||||
get => includedChannels ?? db.GetAllChannels().Select(static channel => channel.Id).ToHashSet();
|
||||
set => Change(ref includedChannels, value);
|
||||
}
|
||||
|
||||
public bool FilterByUser {
|
||||
get => filterByUser;
|
||||
set => Change(ref filterByUser, value);
|
||||
}
|
||||
|
||||
public HashSet<ulong> IncludedUsers {
|
||||
get => includedUsers ?? db.GetAllUsers().Select(static user => user.Id).ToHashSet();
|
||||
set => Change(ref includedUsers, value);
|
||||
}
|
||||
|
||||
private string channelFilterLabel = "";
|
||||
|
||||
public string ChannelFilterLabel {
|
||||
get => channelFilterLabel;
|
||||
set => Change(ref channelFilterLabel, value);
|
||||
}
|
||||
|
||||
private string userFilterLabel = "";
|
||||
|
||||
public string UserFilterLabel {
|
||||
get => userFilterLabel;
|
||||
set => Change(ref userFilterLabel, value);
|
||||
}
|
||||
|
||||
private readonly Window window;
|
||||
private readonly IDatabaseFile db;
|
||||
private readonly string verb;
|
||||
|
||||
private readonly AsyncValueComputer<long> exportedMessageCountComputer;
|
||||
private long? exportedMessageCount;
|
||||
private long? totalMessageCount;
|
||||
|
||||
[Obsolete("Designer")]
|
||||
public MessageFilterPanelModel() : this(null!, DummyDatabaseFile.Instance) {}
|
||||
|
||||
public MessageFilterPanelModel(Window window, IDatabaseFile db, string verb = "Matches") {
|
||||
this.window = window;
|
||||
this.db = db;
|
||||
this.verb = verb;
|
||||
|
||||
this.exportedMessageCountComputer = AsyncValueComputer<long>.WithResultProcessor(SetExportedMessageCount).Build();
|
||||
|
||||
UpdateFilterStatistics();
|
||||
UpdateChannelFilterLabel();
|
||||
UpdateUserFilterLabel();
|
||||
|
||||
PropertyChanged += OnPropertyChanged;
|
||||
db.Statistics.PropertyChanged += OnDbStatisticsChanged;
|
||||
}
|
||||
|
||||
public void Dispose() {
|
||||
db.Statistics.PropertyChanged -= OnDbStatisticsChanged;
|
||||
}
|
||||
|
||||
private void OnPropertyChanged(object? sender, PropertyChangedEventArgs e) {
|
||||
if (e.PropertyName != null && FilterProperties.Contains(e.PropertyName)) {
|
||||
UpdateFilterStatistics();
|
||||
FilterPropertyChanged?.Invoke(sender, e);
|
||||
}
|
||||
|
||||
if (e.PropertyName is nameof(FilterByChannel) or nameof(IncludedChannels)) {
|
||||
UpdateChannelFilterLabel();
|
||||
}
|
||||
else if (e.PropertyName is nameof(FilterByUser) or nameof(IncludedUsers)) {
|
||||
UpdateUserFilterLabel();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnDbStatisticsChanged(object? sender, PropertyChangedEventArgs e) {
|
||||
if (e.PropertyName == nameof(DatabaseStatistics.TotalMessages)) {
|
||||
totalMessageCount = db.Statistics.TotalMessages;
|
||||
UpdateFilterStatistics();
|
||||
}
|
||||
else if (e.PropertyName == nameof(DatabaseStatistics.TotalChannels)) {
|
||||
UpdateChannelFilterLabel();
|
||||
}
|
||||
else if (e.PropertyName == nameof(DatabaseStatistics.TotalUsers)) {
|
||||
UpdateUserFilterLabel();
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateFilterStatistics() {
|
||||
var filter = CreateFilter();
|
||||
if (filter.IsEmpty) {
|
||||
exportedMessageCountComputer.Cancel();
|
||||
exportedMessageCount = totalMessageCount;
|
||||
UpdateFilterStatisticsText();
|
||||
}
|
||||
else {
|
||||
exportedMessageCount = null;
|
||||
UpdateFilterStatisticsText();
|
||||
exportedMessageCountComputer.Compute(() => db.CountMessages(filter));
|
||||
}
|
||||
}
|
||||
|
||||
private void SetExportedMessageCount(long exportedMessageCount) {
|
||||
this.exportedMessageCount = exportedMessageCount;
|
||||
UpdateFilterStatisticsText();
|
||||
}
|
||||
|
||||
private void UpdateFilterStatisticsText() {
|
||||
var exportedMessageCountStr = exportedMessageCount?.Format() ?? "(...)";
|
||||
var totalMessageCountStr = totalMessageCount?.Format() ?? "(...)";
|
||||
|
||||
FilterStatisticsText = verb + " " + exportedMessageCountStr + " out of " + totalMessageCountStr + " message" + (totalMessageCount is null or 1 ? "." : "s.");
|
||||
OnPropertyChanged(nameof(FilterStatisticsText));
|
||||
}
|
||||
|
||||
public async void OpenChannelFilterDialog() {
|
||||
var servers = db.GetAllServers().ToDictionary(static server => server.Id);
|
||||
var items = new List<CheckBoxItem<ulong>>();
|
||||
var included = IncludedChannels;
|
||||
|
||||
foreach (var channel in db.GetAllChannels()) {
|
||||
var channelId = channel.Id;
|
||||
var channelName = channel.Name;
|
||||
|
||||
string title;
|
||||
if (servers.TryGetValue(channel.Server, out var server)) {
|
||||
var titleBuilder = new StringBuilder();
|
||||
var serverType = server.Type;
|
||||
|
||||
titleBuilder.Append('[')
|
||||
.Append(ServerTypes.ToString(serverType))
|
||||
.Append("] ");
|
||||
|
||||
if (serverType == ServerType.DirectMessage) {
|
||||
titleBuilder.Append(channelName);
|
||||
}
|
||||
else {
|
||||
titleBuilder.Append(server.Name)
|
||||
.Append(" - ")
|
||||
.Append(channelName);
|
||||
}
|
||||
|
||||
title = titleBuilder.ToString();
|
||||
}
|
||||
else {
|
||||
title = channelName;
|
||||
}
|
||||
|
||||
items.Add(new CheckBoxItem<ulong>(channelId) {
|
||||
Title = title,
|
||||
Checked = included.Contains(channelId)
|
||||
});
|
||||
}
|
||||
|
||||
var result = await OpenIdFilterDialog(window, "Included Channels", items);
|
||||
if (result != null) {
|
||||
IncludedChannels = result;
|
||||
}
|
||||
}
|
||||
|
||||
public async void OpenUserFilterDialog() {
|
||||
var items = new List<CheckBoxItem<ulong>>();
|
||||
var included = IncludedUsers;
|
||||
|
||||
foreach (var user in db.GetAllUsers()) {
|
||||
var name = user.Name;
|
||||
var discriminator = user.Discriminator;
|
||||
|
||||
items.Add(new CheckBoxItem<ulong>(user.Id) {
|
||||
Title = discriminator == null ? name : name + " #" + discriminator,
|
||||
Checked = included.Contains(user.Id)
|
||||
});
|
||||
}
|
||||
|
||||
var result = await OpenIdFilterDialog(window, "Included Users", items);
|
||||
if (result != null) {
|
||||
IncludedUsers = result;
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateChannelFilterLabel() {
|
||||
long total = db.Statistics.TotalChannels;
|
||||
long included = FilterByChannel ? IncludedChannels.Count : total;
|
||||
ChannelFilterLabel = "Selected " + included.Format() + " / " + total.Pluralize("channel") + ".";
|
||||
}
|
||||
|
||||
private void UpdateUserFilterLabel() {
|
||||
long total = db.Statistics.TotalUsers;
|
||||
long included = FilterByUser ? IncludedUsers.Count : total;
|
||||
UserFilterLabel = "Selected " + included.Format() + " / " + total.Pluralize("user") + ".";
|
||||
}
|
||||
|
||||
public MessageFilter CreateFilter() {
|
||||
MessageFilter filter = new();
|
||||
|
||||
if (FilterByDate) {
|
||||
filter.StartDate = StartDate;
|
||||
filter.EndDate = EndDate?.AddDays(1).AddMilliseconds(-1);
|
||||
}
|
||||
|
||||
if (FilterByChannel) {
|
||||
filter.ChannelIds = new HashSet<ulong>(IncludedChannels);
|
||||
}
|
||||
|
||||
if (FilterByUser) {
|
||||
filter.UserIds = new HashSet<ulong>(IncludedUsers);
|
||||
}
|
||||
|
||||
return filter;
|
||||
}
|
||||
|
||||
private static async Task<HashSet<ulong>?> OpenIdFilterDialog(Window window, string title, List<CheckBoxItem<ulong>> items) {
|
||||
items.Sort(static (item1, item2) => item1.Title.CompareTo(item2.Title));
|
||||
|
||||
var model = new CheckBoxDialogModel<ulong>(items) {
|
||||
Title = title
|
||||
};
|
||||
|
||||
var dialog = new CheckBoxDialog { DataContext = model };
|
||||
var result = await dialog.ShowDialog<DialogResult.OkCancel>(window);
|
||||
|
||||
return result == DialogResult.OkCancel.Ok ? model.SelectedItems.Select(static item => item.Item).ToHashSet() : null;
|
||||
}
|
||||
}
|
49
app/Desktop/Main/Controls/ServerConfigurationPanel.axaml
Normal file
49
app/Desktop/Main/Controls/ServerConfigurationPanel.axaml
Normal file
@@ -0,0 +1,49 @@
|
||||
<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"
|
||||
xmlns:controls="clr-namespace:DHT.Desktop.Main.Controls"
|
||||
mc:Ignorable="d"
|
||||
x:Class="DHT.Desktop.Main.Controls.ServerConfigurationPanel"
|
||||
x:DataType="controls:ServerConfigurationPanelModel">
|
||||
|
||||
<Design.DataContext>
|
||||
<controls:ServerConfigurationPanelModel />
|
||||
</Design.DataContext>
|
||||
|
||||
<UserControl.Styles>
|
||||
<Style Selector="TextBox">
|
||||
<Setter Property="FontFamily" Value="Consolas,Courier" />
|
||||
<Setter Property="FontSize" Value="15" />
|
||||
</Style>
|
||||
<Style Selector="WrapPanel > StackPanel">
|
||||
<Setter Property="Orientation" Value="Vertical" />
|
||||
<Setter Property="Margin" Value="0 0 10 10" />
|
||||
</Style>
|
||||
</UserControl.Styles>
|
||||
|
||||
<StackPanel>
|
||||
<Button Command="{Binding OnClickToggleServerButton}" Content="{Binding ToggleServerButtonText}" IsEnabled="{Binding IsToggleServerButtonEnabled}" />
|
||||
<TextBlock TextWrapping="Wrap" Margin="0 15">
|
||||
The following settings determine how the tracking script communicates with this application. If you change them, you will have to copy/paste the tracking script again.
|
||||
</TextBlock>
|
||||
<WrapPanel>
|
||||
<StackPanel>
|
||||
<Label Target="Port">Port</Label>
|
||||
<TextBox x:Name="Port" Width="70" Text="{Binding InputPort}" />
|
||||
</StackPanel>
|
||||
<StackPanel>
|
||||
<Label Target="Token">Token</Label>
|
||||
<TextBox x:Name="Token" Width="200" Text="{Binding InputToken}" />
|
||||
</StackPanel>
|
||||
<StackPanel VerticalAlignment="Bottom">
|
||||
<Button Command="{Binding OnClickRandomizeToken}">Randomize Token</Button>
|
||||
</StackPanel>
|
||||
</WrapPanel>
|
||||
<StackPanel Orientation="Horizontal" Spacing="10">
|
||||
<Button IsEnabled="{Binding HasMadeChanges}" Command="{Binding OnClickApplyChanges}">Apply & Restart</Button>
|
||||
<Button IsEnabled="{Binding HasMadeChanges}" Command="{Binding OnClickCancelChanges}">Cancel</Button>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
|
||||
</UserControl>
|
11
app/Desktop/Main/Controls/ServerConfigurationPanel.axaml.cs
Normal file
11
app/Desktop/Main/Controls/ServerConfigurationPanel.axaml.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Avalonia.Controls;
|
||||
|
||||
namespace DHT.Desktop.Main.Controls;
|
||||
|
||||
[SuppressMessage("ReSharper", "MemberCanBeInternal")]
|
||||
public sealed partial class ServerConfigurationPanel : UserControl {
|
||||
public ServerConfigurationPanel() {
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
116
app/Desktop/Main/Controls/ServerConfigurationPanelModel.cs
Normal file
116
app/Desktop/Main/Controls/ServerConfigurationPanelModel.cs
Normal file
@@ -0,0 +1,116 @@
|
||||
using System;
|
||||
using Avalonia.Controls;
|
||||
using DHT.Desktop.Dialogs.Message;
|
||||
using DHT.Desktop.Server;
|
||||
using DHT.Server.Database;
|
||||
using DHT.Server.Service;
|
||||
using DHT.Utils.Models;
|
||||
|
||||
namespace DHT.Desktop.Main.Controls;
|
||||
|
||||
sealed class ServerConfigurationPanelModel : BaseModel, IDisposable {
|
||||
private string inputPort;
|
||||
|
||||
public string InputPort {
|
||||
get => inputPort;
|
||||
set {
|
||||
Change(ref inputPort, value);
|
||||
OnPropertyChanged(nameof(HasMadeChanges));
|
||||
}
|
||||
}
|
||||
|
||||
private string inputToken;
|
||||
|
||||
public string InputToken {
|
||||
get => inputToken;
|
||||
set {
|
||||
Change(ref inputToken, value);
|
||||
OnPropertyChanged(nameof(HasMadeChanges));
|
||||
}
|
||||
}
|
||||
|
||||
public bool HasMadeChanges => ServerManager.Port.ToString() != InputPort || ServerManager.Token != InputToken;
|
||||
|
||||
private bool isToggleServerButtonEnabled = true;
|
||||
|
||||
public bool IsToggleServerButtonEnabled {
|
||||
get => isToggleServerButtonEnabled;
|
||||
set => Change(ref isToggleServerButtonEnabled, value);
|
||||
}
|
||||
|
||||
public string ToggleServerButtonText => serverManager.IsRunning ? "Stop Server" : "Start Server";
|
||||
|
||||
public event EventHandler<StatusBarModel.Status>? ServerStatusChanged;
|
||||
|
||||
private readonly Window window;
|
||||
private readonly ServerManager serverManager;
|
||||
|
||||
[Obsolete("Designer")]
|
||||
public ServerConfigurationPanelModel() : this(null!, new ServerManager(DummyDatabaseFile.Instance)) {}
|
||||
|
||||
public ServerConfigurationPanelModel(Window window, ServerManager serverManager) {
|
||||
this.window = window;
|
||||
this.serverManager = serverManager;
|
||||
this.inputPort = ServerManager.Port.ToString();
|
||||
this.inputToken = ServerManager.Token;
|
||||
}
|
||||
|
||||
public void Initialize() {
|
||||
ServerLauncher.ServerStatusChanged += ServerLauncherOnServerStatusChanged;
|
||||
}
|
||||
|
||||
public void Dispose() {
|
||||
ServerLauncher.ServerStatusChanged -= ServerLauncherOnServerStatusChanged;
|
||||
}
|
||||
|
||||
private void ServerLauncherOnServerStatusChanged(object? sender, EventArgs e) {
|
||||
ServerStatusChanged?.Invoke(this, serverManager.IsRunning ? StatusBarModel.Status.Ready : StatusBarModel.Status.Stopped);
|
||||
OnPropertyChanged(nameof(ToggleServerButtonText));
|
||||
IsToggleServerButtonEnabled = true;
|
||||
}
|
||||
|
||||
private void BeforeServerStart() {
|
||||
IsToggleServerButtonEnabled = false;
|
||||
ServerStatusChanged?.Invoke(this, StatusBarModel.Status.Starting);
|
||||
}
|
||||
|
||||
private void StartServer() {
|
||||
BeforeServerStart();
|
||||
serverManager.Launch();
|
||||
}
|
||||
|
||||
private void StopServer() {
|
||||
IsToggleServerButtonEnabled = false;
|
||||
ServerStatusChanged?.Invoke(this, StatusBarModel.Status.Stopping);
|
||||
serverManager.Stop();
|
||||
}
|
||||
|
||||
public void OnClickToggleServerButton() {
|
||||
if (serverManager.IsRunning) {
|
||||
StopServer();
|
||||
}
|
||||
else {
|
||||
StartServer();
|
||||
}
|
||||
}
|
||||
|
||||
public void OnClickRandomizeToken() {
|
||||
InputToken = ServerUtils.GenerateRandomToken(20);
|
||||
}
|
||||
|
||||
public async void OnClickApplyChanges() {
|
||||
if (!ushort.TryParse(InputPort, out ushort port)) {
|
||||
await Dialog.ShowOk(window, "Invalid Port", "Port must be a number between 0 and 65535.");
|
||||
return;
|
||||
}
|
||||
|
||||
BeforeServerStart();
|
||||
serverManager.Relaunch(port, InputToken);
|
||||
OnPropertyChanged(nameof(HasMadeChanges));
|
||||
}
|
||||
|
||||
public void OnClickCancelChanges() {
|
||||
InputPort = ServerManager.Port.ToString();
|
||||
InputToken = ServerManager.Token;
|
||||
}
|
||||
}
|
62
app/Desktop/Main/Controls/StatusBar.axaml
Normal file
62
app/Desktop/Main/Controls/StatusBar.axaml
Normal file
@@ -0,0 +1,62 @@
|
||||
<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"
|
||||
xmlns:controls="clr-namespace:DHT.Desktop.Main.Controls"
|
||||
mc:Ignorable="d"
|
||||
x:Class="DHT.Desktop.Main.Controls.StatusBar"
|
||||
x:DataType="controls:StatusBarModel">
|
||||
|
||||
<Design.DataContext>
|
||||
<controls:StatusBarModel />
|
||||
</Design.DataContext>
|
||||
|
||||
<UserControl.Background>
|
||||
<SolidColorBrush>#3C4F79</SolidColorBrush>
|
||||
</UserControl.Background>
|
||||
|
||||
<UserControl.Styles>
|
||||
<Style Selector="StackPanel > TextBlock">
|
||||
<Setter Property="Foreground" Value="#E7E7E7" />
|
||||
</Style>
|
||||
<Style Selector="StackPanel > TextBlock.label">
|
||||
<Setter Property="FontSize" Value="15" />
|
||||
<Setter Property="FontWeight" Value="SemiLight" />
|
||||
</Style>
|
||||
<Style Selector="StackPanel > TextBlock.value">
|
||||
<Setter Property="FontSize" Value="16" />
|
||||
<Setter Property="FontWeight" Value="SemiLight" />
|
||||
<Setter Property="TextAlignment" Value="Right" />
|
||||
<Setter Property="Margin" Value="0 1" />
|
||||
</Style>
|
||||
<Style Selector="StackPanel > Rectangle">
|
||||
<Setter Property="Margin" Value="14 1" />
|
||||
<Setter Property="Stroke" Value="#697899" />
|
||||
<Setter Property="StrokeThickness" Value="2" />
|
||||
<Setter Property="VerticalAlignment" Value="Stretch" />
|
||||
</Style>
|
||||
</UserControl.Styles>
|
||||
|
||||
<StackPanel Orientation="Horizontal" Margin="6 3">
|
||||
<StackPanel Orientation="Vertical" Width="65">
|
||||
<TextBlock Classes="label">Status</TextBlock>
|
||||
<TextBlock FontSize="12" Margin="0 3 0 0" Text="{Binding StatusText}" />
|
||||
</StackPanel>
|
||||
<Rectangle />
|
||||
<StackPanel Orientation="Vertical">
|
||||
<TextBlock Classes="label">Servers</TextBlock>
|
||||
<TextBlock Classes="value" Text="{Binding DatabaseStatistics.TotalServers, Converter={StaticResource NumberValueConverter}}" />
|
||||
</StackPanel>
|
||||
<Rectangle />
|
||||
<StackPanel Orientation="Vertical">
|
||||
<TextBlock Classes="label">Channels</TextBlock>
|
||||
<TextBlock Classes="value" Text="{Binding DatabaseStatistics.TotalChannels, Converter={StaticResource NumberValueConverter}}" />
|
||||
</StackPanel>
|
||||
<Rectangle />
|
||||
<StackPanel Orientation="Vertical">
|
||||
<TextBlock Classes="label">Messages</TextBlock>
|
||||
<TextBlock Classes="value" Text="{Binding DatabaseStatistics.TotalMessages, Converter={StaticResource NumberValueConverter}}" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
|
||||
</UserControl>
|
11
app/Desktop/Main/Controls/StatusBar.axaml.cs
Normal file
11
app/Desktop/Main/Controls/StatusBar.axaml.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Avalonia.Controls;
|
||||
|
||||
namespace DHT.Desktop.Main.Controls;
|
||||
|
||||
[SuppressMessage("ReSharper", "MemberCanBeInternal")]
|
||||
public sealed partial class StatusBar : UserControl {
|
||||
public StatusBar() {
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
45
app/Desktop/Main/Controls/StatusBarModel.cs
Normal file
45
app/Desktop/Main/Controls/StatusBarModel.cs
Normal file
@@ -0,0 +1,45 @@
|
||||
using System;
|
||||
using DHT.Server.Database;
|
||||
using DHT.Utils.Models;
|
||||
|
||||
namespace DHT.Desktop.Main.Controls;
|
||||
|
||||
sealed class StatusBarModel : BaseModel {
|
||||
public DatabaseStatistics DatabaseStatistics { get; }
|
||||
|
||||
private Status status = Status.Stopped;
|
||||
|
||||
public Status CurrentStatus {
|
||||
get => status;
|
||||
set {
|
||||
status = value;
|
||||
OnPropertyChanged(nameof(StatusText));
|
||||
}
|
||||
}
|
||||
|
||||
public string StatusText {
|
||||
get {
|
||||
return CurrentStatus switch {
|
||||
Status.Starting => "STARTING",
|
||||
Status.Ready => "READY",
|
||||
Status.Stopping => "STOPPING",
|
||||
Status.Stopped => "STOPPED",
|
||||
_ => ""
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
[Obsolete("Designer")]
|
||||
public StatusBarModel() : this(new DatabaseStatistics()) {}
|
||||
|
||||
public StatusBarModel(DatabaseStatistics databaseStatistics) {
|
||||
this.DatabaseStatistics = databaseStatistics;
|
||||
}
|
||||
|
||||
public enum Status {
|
||||
Starting,
|
||||
Ready,
|
||||
Stopping,
|
||||
Stopped
|
||||
}
|
||||
}
|
23
app/Desktop/Main/MainWindow.axaml
Normal file
23
app/Desktop/Main/MainWindow.axaml
Normal file
@@ -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"
|
||||
xmlns:main="clr-namespace:DHT.Desktop.Main"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="DHT.Desktop.Main.MainWindow"
|
||||
x:DataType="main:MainWindowModel"
|
||||
Title="{Binding Title}"
|
||||
Icon="avares://DiscordHistoryTracker/Resources/icon.ico"
|
||||
Width="800" Height="500"
|
||||
MinWidth="520" MinHeight="300"
|
||||
WindowStartupLocation="CenterScreen"
|
||||
Closed="OnClosed">
|
||||
|
||||
<Design.DataContext>
|
||||
<main:MainWindowModel />
|
||||
</Design.DataContext>
|
||||
|
||||
<Panel>
|
||||
<ContentPresenter Content="{Binding CurrentScreen}" />
|
||||
</Panel>
|
||||
</Window>
|
36
app/Desktop/Main/MainWindow.axaml.cs
Normal file
36
app/Desktop/Main/MainWindow.axaml.cs
Normal file
@@ -0,0 +1,36 @@
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO;
|
||||
using Avalonia.Controls;
|
||||
using DHT.Desktop.Main.Pages;
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace DHT.Desktop.Main;
|
||||
|
||||
[SuppressMessage("ReSharper", "MemberCanBeInternal")]
|
||||
public sealed partial class MainWindow : Window {
|
||||
[UsedImplicitly]
|
||||
public MainWindow() {
|
||||
InitializeComponent();
|
||||
DataContext = new MainWindowModel(this, Arguments.Empty);
|
||||
}
|
||||
|
||||
internal MainWindow(Arguments args) {
|
||||
InitializeComponent();
|
||||
DataContext = new MainWindowModel(this, args);
|
||||
}
|
||||
|
||||
public void OnClosed(object? sender, EventArgs e) {
|
||||
if (DataContext is IDisposable disposable) {
|
||||
disposable.Dispose();
|
||||
}
|
||||
|
||||
foreach (var temporaryFile in ViewerPageModel.TemporaryFiles) {
|
||||
try {
|
||||
File.Delete(temporaryFile);
|
||||
} catch (Exception) {
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
116
app/Desktop/Main/MainWindowModel.cs
Normal file
116
app/Desktop/Main/MainWindowModel.cs
Normal file
@@ -0,0 +1,116 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading.Tasks;
|
||||
using Avalonia.Controls;
|
||||
using DHT.Desktop.Dialogs.Message;
|
||||
using DHT.Desktop.Main.Screens;
|
||||
using DHT.Desktop.Server;
|
||||
using DHT.Server.Database;
|
||||
using DHT.Utils.Models;
|
||||
|
||||
namespace DHT.Desktop.Main;
|
||||
|
||||
sealed class MainWindowModel : BaseModel, IDisposable {
|
||||
private const string DefaultTitle = "Discord History Tracker";
|
||||
|
||||
public string Title { get; private set; } = DefaultTitle;
|
||||
|
||||
public UserControl CurrentScreen { get; private set; }
|
||||
|
||||
private readonly WelcomeScreen welcomeScreen;
|
||||
private readonly WelcomeScreenModel welcomeScreenModel;
|
||||
|
||||
private MainContentScreen? mainContentScreen;
|
||||
private MainContentScreenModel? mainContentScreenModel;
|
||||
|
||||
private readonly Window window;
|
||||
|
||||
private IDatabaseFile? db;
|
||||
|
||||
[Obsolete("Designer")]
|
||||
public MainWindowModel() : this(null!, Arguments.Empty) {}
|
||||
|
||||
public MainWindowModel(Window window, Arguments args) {
|
||||
this.window = window;
|
||||
|
||||
welcomeScreenModel = new WelcomeScreenModel(window);
|
||||
welcomeScreen = new WelcomeScreen { DataContext = welcomeScreenModel };
|
||||
CurrentScreen = welcomeScreen;
|
||||
|
||||
welcomeScreenModel.PropertyChanged += WelcomeScreenModelOnPropertyChanged;
|
||||
|
||||
var dbFile = args.DatabaseFile;
|
||||
if (!string.IsNullOrWhiteSpace(dbFile)) {
|
||||
async void OnWindowOpened(object? o, EventArgs eventArgs) {
|
||||
window.Opened -= OnWindowOpened;
|
||||
|
||||
// https://github.com/AvaloniaUI/Avalonia/issues/3071
|
||||
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) {
|
||||
await Task.Delay(500);
|
||||
}
|
||||
|
||||
if (File.Exists(dbFile)) {
|
||||
await welcomeScreenModel.OpenOrCreateDatabaseFromPath(dbFile);
|
||||
}
|
||||
else {
|
||||
await Dialog.ShowOk(window, "Database Error", "Database file not found:\n" + dbFile);
|
||||
}
|
||||
}
|
||||
|
||||
window.Opened += OnWindowOpened;
|
||||
}
|
||||
|
||||
if (args.ServerPort != null) {
|
||||
ServerManager.Port = args.ServerPort.Value;
|
||||
}
|
||||
|
||||
if (args.ServerToken != null) {
|
||||
ServerManager.Token = args.ServerToken;
|
||||
}
|
||||
}
|
||||
|
||||
private async void WelcomeScreenModelOnPropertyChanged(object? sender, PropertyChangedEventArgs e) {
|
||||
if (e.PropertyName == nameof(welcomeScreenModel.Db)) {
|
||||
if (mainContentScreenModel != null) {
|
||||
mainContentScreenModel.DatabaseClosed -= MainContentScreenModelOnDatabaseClosed;
|
||||
mainContentScreenModel.Dispose();
|
||||
}
|
||||
|
||||
db?.Dispose();
|
||||
db = welcomeScreenModel.Db;
|
||||
|
||||
if (db == null) {
|
||||
Title = DefaultTitle;
|
||||
mainContentScreenModel = null;
|
||||
mainContentScreen = null;
|
||||
CurrentScreen = welcomeScreen;
|
||||
}
|
||||
else {
|
||||
Title = Path.GetFileName(db.Path) + " - " + DefaultTitle;
|
||||
mainContentScreenModel = new MainContentScreenModel(window, db);
|
||||
await mainContentScreenModel.Initialize();
|
||||
mainContentScreenModel.DatabaseClosed += MainContentScreenModelOnDatabaseClosed;
|
||||
mainContentScreen = new MainContentScreen { DataContext = mainContentScreenModel };
|
||||
CurrentScreen = mainContentScreen;
|
||||
}
|
||||
|
||||
OnPropertyChanged(nameof(CurrentScreen));
|
||||
OnPropertyChanged(nameof(Title));
|
||||
|
||||
window.Focus();
|
||||
}
|
||||
}
|
||||
|
||||
private void MainContentScreenModelOnDatabaseClosed(object? sender, EventArgs e) {
|
||||
welcomeScreenModel.CloseDatabase();
|
||||
}
|
||||
|
||||
public void Dispose() {
|
||||
welcomeScreenModel.Dispose();
|
||||
mainContentScreenModel?.Dispose();
|
||||
db?.Dispose();
|
||||
db = null;
|
||||
}
|
||||
}
|
26
app/Desktop/Main/Pages/AdvancedPage.axaml
Normal file
26
app/Desktop/Main/Pages/AdvancedPage.axaml
Normal file
@@ -0,0 +1,26 @@
|
||||
<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"
|
||||
xmlns:pages="clr-namespace:DHT.Desktop.Main.Pages"
|
||||
xmlns:controls="clr-namespace:DHT.Desktop.Main.Controls"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="DHT.Desktop.Main.Pages.AdvancedPage"
|
||||
x:DataType="pages:AdvancedPageModel">
|
||||
|
||||
<Design.DataContext>
|
||||
<pages:AdvancedPageModel />
|
||||
</Design.DataContext>
|
||||
|
||||
<StackPanel Orientation="Vertical" Spacing="10">
|
||||
<Expander Header="Internal Server Configuration" IsExpanded="True">
|
||||
<controls:ServerConfigurationPanel DataContext="{Binding ServerConfigurationModel}" />
|
||||
</Expander>
|
||||
<Expander Header="Database Tools" IsExpanded="True">
|
||||
<StackPanel Orientation="Vertical" Spacing="10">
|
||||
<TextBlock TextWrapping="Wrap">Recreates the database to remove any traces of deleted data, which frees up space and prevents forensic recovery.</TextBlock>
|
||||
<Button Command="{Binding VacuumDatabase}">Vacuum Database</Button>
|
||||
</StackPanel>
|
||||
</Expander>
|
||||
</StackPanel>
|
||||
</UserControl>
|
11
app/Desktop/Main/Pages/AdvancedPage.axaml.cs
Normal file
11
app/Desktop/Main/Pages/AdvancedPage.axaml.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Avalonia.Controls;
|
||||
|
||||
namespace DHT.Desktop.Main.Pages;
|
||||
|
||||
[SuppressMessage("ReSharper", "MemberCanBeInternal")]
|
||||
public sealed partial class AdvancedPage : UserControl {
|
||||
public AdvancedPage() {
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
39
app/Desktop/Main/Pages/AdvancedPageModel.cs
Normal file
39
app/Desktop/Main/Pages/AdvancedPageModel.cs
Normal file
@@ -0,0 +1,39 @@
|
||||
using System;
|
||||
using Avalonia.Controls;
|
||||
using DHT.Desktop.Dialogs.Message;
|
||||
using DHT.Desktop.Main.Controls;
|
||||
using DHT.Desktop.Server;
|
||||
using DHT.Server.Database;
|
||||
using DHT.Utils.Models;
|
||||
|
||||
namespace DHT.Desktop.Main.Pages;
|
||||
|
||||
sealed class AdvancedPageModel : BaseModel, IDisposable {
|
||||
public ServerConfigurationPanelModel ServerConfigurationModel { get; }
|
||||
|
||||
private readonly Window window;
|
||||
private readonly IDatabaseFile db;
|
||||
|
||||
[Obsolete("Designer")]
|
||||
public AdvancedPageModel() : this(null!, DummyDatabaseFile.Instance, new ServerManager(DummyDatabaseFile.Instance)) {}
|
||||
|
||||
public AdvancedPageModel(Window window, IDatabaseFile db, ServerManager serverManager) {
|
||||
this.window = window;
|
||||
this.db = db;
|
||||
|
||||
ServerConfigurationModel = new ServerConfigurationPanelModel(window, serverManager);
|
||||
}
|
||||
|
||||
public void Initialize() {
|
||||
ServerConfigurationModel.Initialize();
|
||||
}
|
||||
|
||||
public void Dispose() {
|
||||
ServerConfigurationModel.Dispose();
|
||||
}
|
||||
|
||||
public async void VacuumDatabase() {
|
||||
db.Vacuum();
|
||||
await Dialog.ShowOk(window, "Vacuum Database", "Done.");
|
||||
}
|
||||
}
|
55
app/Desktop/Main/Pages/AttachmentsPage.axaml
Normal file
55
app/Desktop/Main/Pages/AttachmentsPage.axaml
Normal file
@@ -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"
|
||||
xmlns:pages="clr-namespace:DHT.Desktop.Main.Pages"
|
||||
xmlns:controls="clr-namespace:DHT.Desktop.Main.Controls"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="DHT.Desktop.Main.Pages.AttachmentsPage"
|
||||
x:DataType="pages:AttachmentsPageModel">
|
||||
|
||||
<Design.DataContext>
|
||||
<pages:AttachmentsPageModel />
|
||||
</Design.DataContext>
|
||||
|
||||
<UserControl.Styles>
|
||||
<Style Selector="Expander">
|
||||
<Setter Property="Margin" Value="0 5 0 0" />
|
||||
</Style>
|
||||
<Style Selector="DataGridColumnHeader">
|
||||
<Setter Property="FontWeight" Value="Medium" />
|
||||
</Style>
|
||||
<Style Selector="DataGridColumnHeader:nth-child(2)">
|
||||
<Setter Property="HorizontalContentAlignment" Value="Right" />
|
||||
</Style>
|
||||
<Style Selector="DataGridColumnHeader:nth-child(3)">
|
||||
<Setter Property="HorizontalContentAlignment" Value="Right" />
|
||||
</Style>
|
||||
<Style Selector="DataGridCell.right">
|
||||
<Setter Property="HorizontalContentAlignment" Value="Right" />
|
||||
</Style>
|
||||
</UserControl.Styles>
|
||||
|
||||
<StackPanel Orientation="Vertical" Spacing="20">
|
||||
<DockPanel>
|
||||
<Button Command="{Binding OnClickToggleDownload}" Content="{Binding ToggleDownloadButtonText}" IsEnabled="{Binding IsToggleDownloadButtonEnabled}" DockPanel.Dock="Left" />
|
||||
<TextBlock Text="{Binding DownloadMessage}" Margin="10 0 0 0" VerticalAlignment="Center" DockPanel.Dock="Left" />
|
||||
<ProgressBar Value="{Binding DownloadProgress}" IsVisible="{Binding IsDownloading}" Margin="15 0" VerticalAlignment="Center" DockPanel.Dock="Right" />
|
||||
</DockPanel>
|
||||
<controls:AttachmentFilterPanel DataContext="{Binding FilterModel}" IsEnabled="{Binding !IsDownloading, RelativeSource={RelativeSource AncestorType=pages:AttachmentsPageModel}}" />
|
||||
<StackPanel Orientation="Vertical" Spacing="12">
|
||||
<Expander Header="Download Status" IsExpanded="True">
|
||||
<DataGrid ItemsSource="{Binding StatisticsRows}" AutoGenerateColumns="False" CanUserReorderColumns="False" CanUserResizeColumns="False" CanUserSortColumns="False" IsReadOnly="True">
|
||||
<DataGrid.Columns>
|
||||
<DataGridTextColumn Header="State" Binding="{Binding State}" Width="*" />
|
||||
<DataGridTextColumn Header="Attachments" Binding="{Binding Items, Converter={StaticResource NumberValueConverter}}" Width="*" CellStyleClasses="right" />
|
||||
<DataGridTextColumn Header="Size" Binding="{Binding Size, Converter={StaticResource BytesValueConverter}}" Width="*" CellStyleClasses="right" />
|
||||
</DataGrid.Columns>
|
||||
</DataGrid>
|
||||
</Expander>
|
||||
<StackPanel Orientation="Horizontal" Spacing="10">
|
||||
<Button Command="{Binding OnClickRetryFailedDownloads}" IsEnabled="{Binding HasFailedDownloads}">Retry Failed Downloads</Button>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</UserControl>
|
11
app/Desktop/Main/Pages/AttachmentsPage.axaml.cs
Normal file
11
app/Desktop/Main/Pages/AttachmentsPage.axaml.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Avalonia.Controls;
|
||||
|
||||
namespace DHT.Desktop.Main.Pages;
|
||||
|
||||
[SuppressMessage("ReSharper", "MemberCanBeInternal")]
|
||||
public sealed partial class AttachmentsPage : UserControl {
|
||||
public AttachmentsPage() {
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
205
app/Desktop/Main/Pages/AttachmentsPageModel.cs
Normal file
205
app/Desktop/Main/Pages/AttachmentsPageModel.cs
Normal file
@@ -0,0 +1,205 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Threading;
|
||||
using Avalonia.Threading;
|
||||
using DHT.Desktop.Common;
|
||||
using DHT.Desktop.Main.Controls;
|
||||
using DHT.Server.Data;
|
||||
using DHT.Server.Data.Aggregations;
|
||||
using DHT.Server.Data.Filters;
|
||||
using DHT.Server.Database;
|
||||
using DHT.Server.Download;
|
||||
using DHT.Utils.Models;
|
||||
using DHT.Utils.Tasks;
|
||||
|
||||
namespace DHT.Desktop.Main.Pages;
|
||||
|
||||
sealed class AttachmentsPageModel : BaseModel, IDisposable {
|
||||
private static readonly DownloadItemFilter EnqueuedItemFilter = new() {
|
||||
IncludeStatuses = new HashSet<DownloadStatus> {
|
||||
DownloadStatus.Enqueued
|
||||
}
|
||||
};
|
||||
|
||||
private bool isThreadDownloadButtonEnabled = true;
|
||||
|
||||
public string ToggleDownloadButtonText => downloadThread == null ? "Start Downloading" : "Stop Downloading";
|
||||
|
||||
public bool IsToggleDownloadButtonEnabled {
|
||||
get => isThreadDownloadButtonEnabled;
|
||||
set => Change(ref isThreadDownloadButtonEnabled, value);
|
||||
}
|
||||
|
||||
public string DownloadMessage { get; set; } = "";
|
||||
public double DownloadProgress => allItemsCount is null or 0 ? 0.0 : 100.0 * doneItemsCount / allItemsCount.Value;
|
||||
|
||||
public AttachmentFilterPanelModel FilterModel { get; }
|
||||
|
||||
private readonly StatisticsRow statisticsEnqueued = new ("Enqueued");
|
||||
private readonly StatisticsRow statisticsDownloaded = new ("Downloaded");
|
||||
private readonly StatisticsRow statisticsFailed = new ("Failed");
|
||||
private readonly StatisticsRow statisticsSkipped = new ("Skipped");
|
||||
|
||||
public List<StatisticsRow> StatisticsRows {
|
||||
get {
|
||||
return new List<StatisticsRow> {
|
||||
statisticsEnqueued,
|
||||
statisticsDownloaded,
|
||||
statisticsFailed,
|
||||
statisticsSkipped
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsDownloading => downloadThread != null;
|
||||
public bool HasFailedDownloads => statisticsFailed.Items > 0;
|
||||
|
||||
private readonly IDatabaseFile db;
|
||||
private readonly AsyncValueComputer<DownloadStatusStatistics>.Single downloadStatisticsComputer;
|
||||
private BackgroundDownloadThread? downloadThread;
|
||||
|
||||
private int doneItemsCount;
|
||||
private int? allItemsCount;
|
||||
|
||||
public AttachmentsPageModel() : this(DummyDatabaseFile.Instance) {}
|
||||
|
||||
public AttachmentsPageModel(IDatabaseFile db) {
|
||||
this.db = db;
|
||||
this.FilterModel = new AttachmentFilterPanelModel(db);
|
||||
|
||||
this.downloadStatisticsComputer = AsyncValueComputer<DownloadStatusStatistics>.WithResultProcessor(UpdateStatistics).WithOutdatedResults().BuildWithComputer(db.GetDownloadStatusStatistics);
|
||||
this.downloadStatisticsComputer.Recompute();
|
||||
|
||||
db.Statistics.PropertyChanged += OnDbStatisticsChanged;
|
||||
}
|
||||
|
||||
public void Dispose() {
|
||||
db.Statistics.PropertyChanged -= OnDbStatisticsChanged;
|
||||
|
||||
FilterModel.Dispose();
|
||||
DisposeDownloadThread();
|
||||
}
|
||||
|
||||
private void OnDbStatisticsChanged(object? sender, PropertyChangedEventArgs e) {
|
||||
if (e.PropertyName == nameof(DatabaseStatistics.TotalAttachments)) {
|
||||
if (IsDownloading) {
|
||||
EnqueueDownloadItems();
|
||||
}
|
||||
else {
|
||||
downloadStatisticsComputer.Recompute();
|
||||
}
|
||||
}
|
||||
else if (e.PropertyName == nameof(DatabaseStatistics.TotalDownloads)) {
|
||||
downloadStatisticsComputer.Recompute();
|
||||
}
|
||||
}
|
||||
|
||||
private void EnqueueDownloadItems() {
|
||||
var filter = FilterModel.CreateFilter();
|
||||
filter.DownloadItemRule = AttachmentFilter.DownloadItemRules.OnlyNotPresent;
|
||||
db.EnqueueDownloadItems(filter);
|
||||
|
||||
downloadStatisticsComputer.Recompute();
|
||||
}
|
||||
|
||||
private void UpdateStatistics(DownloadStatusStatistics statusStatistics) {
|
||||
var hadFailedDownloads = HasFailedDownloads;
|
||||
|
||||
statisticsEnqueued.Items = statusStatistics.EnqueuedCount;
|
||||
statisticsEnqueued.Size = statusStatistics.EnqueuedSize;
|
||||
|
||||
statisticsDownloaded.Items = statusStatistics.SuccessfulCount;
|
||||
statisticsDownloaded.Size = statusStatistics.SuccessfulSize;
|
||||
|
||||
statisticsFailed.Items = statusStatistics.FailedCount;
|
||||
statisticsFailed.Size = statusStatistics.FailedSize;
|
||||
|
||||
statisticsSkipped.Items = statusStatistics.SkippedCount;
|
||||
statisticsSkipped.Size = statusStatistics.SkippedSize;
|
||||
|
||||
OnPropertyChanged(nameof(StatisticsRows));
|
||||
|
||||
if (hadFailedDownloads != HasFailedDownloads) {
|
||||
OnPropertyChanged(nameof(HasFailedDownloads));
|
||||
}
|
||||
|
||||
allItemsCount = doneItemsCount + statisticsEnqueued.Items;
|
||||
UpdateDownloadMessage();
|
||||
}
|
||||
|
||||
private void UpdateDownloadMessage() {
|
||||
DownloadMessage = IsDownloading ? doneItemsCount.Format() + " / " + (allItemsCount?.Format() ?? "?") : "";
|
||||
|
||||
OnPropertyChanged(nameof(DownloadMessage));
|
||||
OnPropertyChanged(nameof(DownloadProgress));
|
||||
}
|
||||
|
||||
private void DownloadThreadOnOnItemFinished(object? sender, DownloadItem e) {
|
||||
Interlocked.Increment(ref doneItemsCount);
|
||||
|
||||
Dispatcher.UIThread.Invoke(UpdateDownloadMessage);
|
||||
downloadStatisticsComputer.Recompute();
|
||||
}
|
||||
|
||||
private void DownloadThreadOnOnServerStopped(object? sender, EventArgs e) {
|
||||
downloadStatisticsComputer.Recompute();
|
||||
IsToggleDownloadButtonEnabled = true;
|
||||
}
|
||||
|
||||
public void OnClickToggleDownload() {
|
||||
if (downloadThread == null) {
|
||||
EnqueueDownloadItems();
|
||||
downloadThread = new BackgroundDownloadThread(db);
|
||||
downloadThread.OnItemFinished += DownloadThreadOnOnItemFinished;
|
||||
downloadThread.OnServerStopped += DownloadThreadOnOnServerStopped;
|
||||
}
|
||||
else {
|
||||
IsToggleDownloadButtonEnabled = false;
|
||||
DisposeDownloadThread();
|
||||
|
||||
db.RemoveDownloadItems(EnqueuedItemFilter, FilterRemovalMode.RemoveMatching);
|
||||
|
||||
doneItemsCount = 0;
|
||||
allItemsCount = null;
|
||||
UpdateDownloadMessage();
|
||||
}
|
||||
|
||||
OnPropertyChanged(nameof(ToggleDownloadButtonText));
|
||||
OnPropertyChanged(nameof(IsDownloading));
|
||||
}
|
||||
|
||||
public void OnClickRetryFailedDownloads() {
|
||||
var allExceptFailedFilter = new DownloadItemFilter {
|
||||
IncludeStatuses = new HashSet<DownloadStatus> {
|
||||
DownloadStatus.Enqueued,
|
||||
DownloadStatus.Success
|
||||
}
|
||||
};
|
||||
|
||||
db.RemoveDownloadItems(allExceptFailedFilter, FilterRemovalMode.KeepMatching);
|
||||
|
||||
if (IsDownloading) {
|
||||
EnqueueDownloadItems();
|
||||
}
|
||||
}
|
||||
|
||||
private void DisposeDownloadThread() {
|
||||
if (downloadThread != null) {
|
||||
downloadThread.OnItemFinished -= DownloadThreadOnOnItemFinished;
|
||||
downloadThread.StopThread();
|
||||
}
|
||||
|
||||
downloadThread = null;
|
||||
}
|
||||
|
||||
public sealed class StatisticsRow {
|
||||
public string State { get; }
|
||||
public int Items { get; set; }
|
||||
public ulong? Size { get; set; }
|
||||
|
||||
public StatisticsRow(string state) {
|
||||
State = state;
|
||||
}
|
||||
}
|
||||
}
|
32
app/Desktop/Main/Pages/DatabasePage.axaml
Normal file
32
app/Desktop/Main/Pages/DatabasePage.axaml
Normal file
@@ -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"
|
||||
xmlns:pages="clr-namespace:DHT.Desktop.Main.Pages"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="DHT.Desktop.Main.Pages.DatabasePage"
|
||||
x:DataType="pages:DatabasePageModel">
|
||||
|
||||
<Design.DataContext>
|
||||
<pages:DatabasePageModel />
|
||||
</Design.DataContext>
|
||||
|
||||
<UserControl.Styles>
|
||||
<Style Selector="WrapPanel > Button">
|
||||
<Setter Property="Margin" Value="0 0 10 10" />
|
||||
</Style>
|
||||
</UserControl.Styles>
|
||||
|
||||
<StackPanel Spacing="10">
|
||||
<DockPanel>
|
||||
<Button Command="{Binding CloseDatabase}" DockPanel.Dock="Right">Close Database</Button>
|
||||
<TextBox Text="{Binding Db.Path}" Width="NaN" Margin="0 0 10 0" IsReadOnly="True" />
|
||||
</DockPanel>
|
||||
<WrapPanel>
|
||||
<Button Command="{Binding OpenDatabaseFolder}">Open Database Folder</Button>
|
||||
<Button Command="{Binding MergeWithDatabase}">Merge with Database(s)...</Button>
|
||||
<Button Command="{Binding ImportLegacyArchive}">Import Legacy Archive(s)...</Button>
|
||||
</WrapPanel>
|
||||
</StackPanel>
|
||||
|
||||
</UserControl>
|
11
app/Desktop/Main/Pages/DatabasePage.axaml.cs
Normal file
11
app/Desktop/Main/Pages/DatabasePage.axaml.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Avalonia.Controls;
|
||||
|
||||
namespace DHT.Desktop.Main.Pages;
|
||||
|
||||
[SuppressMessage("ReSharper", "MemberCanBeInternal")]
|
||||
public sealed partial class DatabasePage : UserControl {
|
||||
public DatabasePage() {
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
268
app/Desktop/Main/Pages/DatabasePageModel.cs
Normal file
268
app/Desktop/Main/Pages/DatabasePageModel.cs
Normal file
@@ -0,0 +1,268 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Platform.Storage;
|
||||
using Avalonia.Threading;
|
||||
using DHT.Desktop.Common;
|
||||
using DHT.Desktop.Dialogs.File;
|
||||
using DHT.Desktop.Dialogs.Message;
|
||||
using DHT.Desktop.Dialogs.Progress;
|
||||
using DHT.Desktop.Dialogs.TextBox;
|
||||
using DHT.Server.Data;
|
||||
using DHT.Server.Database;
|
||||
using DHT.Server.Database.Import;
|
||||
using DHT.Server.Database.Sqlite;
|
||||
using DHT.Utils.Logging;
|
||||
using DHT.Utils.Models;
|
||||
|
||||
namespace DHT.Desktop.Main.Pages;
|
||||
|
||||
sealed class DatabasePageModel : BaseModel {
|
||||
private static readonly Log Log = Log.ForType<DatabasePageModel>();
|
||||
|
||||
public IDatabaseFile Db { get; }
|
||||
|
||||
public event EventHandler? DatabaseClosed;
|
||||
|
||||
private readonly Window window;
|
||||
|
||||
[Obsolete("Designer")]
|
||||
public DatabasePageModel() : this(null!, DummyDatabaseFile.Instance) {}
|
||||
|
||||
public DatabasePageModel(Window window, IDatabaseFile db) {
|
||||
this.window = window;
|
||||
this.Db = db;
|
||||
}
|
||||
|
||||
public async void OpenDatabaseFolder() {
|
||||
string file = Db.Path;
|
||||
string? folder = Path.GetDirectoryName(file);
|
||||
|
||||
if (folder == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (Environment.OSVersion.Platform) {
|
||||
case PlatformID.Win32NT:
|
||||
Process.Start("explorer.exe", "/select,\"" + file + "\"");
|
||||
break;
|
||||
|
||||
case PlatformID.Unix:
|
||||
Process.Start("xdg-open", new string[] { folder });
|
||||
break;
|
||||
|
||||
case PlatformID.MacOSX:
|
||||
Process.Start("open", new string[] { folder });
|
||||
break;
|
||||
|
||||
default:
|
||||
await Dialog.ShowOk(window, "Feature Not Supported", "This feature is not supported for your operating system.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public void CloseDatabase() {
|
||||
DatabaseClosed?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
public async void MergeWithDatabase() {
|
||||
var paths = await DatabaseGui.NewOpenDatabaseFilesDialog(window, Path.GetDirectoryName(Db.Path));
|
||||
if (paths.Length == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
ProgressDialog progressDialog = new ProgressDialog();
|
||||
progressDialog.DataContext = new ProgressDialogModel(async callbacks => await MergeWithDatabaseFromPaths(Db, paths, progressDialog, callbacks[0])) {
|
||||
Title = "Database Merge"
|
||||
};
|
||||
|
||||
await progressDialog.ShowProgressDialog(window);
|
||||
}
|
||||
|
||||
private static async Task MergeWithDatabaseFromPaths(IDatabaseFile target, string[] paths, ProgressDialog dialog, IProgressCallback callback) {
|
||||
var schemaUpgradeCallbacks = new SchemaUpgradeCallbacks(dialog, paths.Length);
|
||||
|
||||
await PerformImport(target, paths, dialog, callback, "Database Merge", "Database Error", "database file", async path => {
|
||||
IDatabaseFile? db = await DatabaseGui.TryOpenOrCreateDatabaseFromPath(path, dialog, schemaUpgradeCallbacks);
|
||||
|
||||
if (db == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
target.AddFrom(db);
|
||||
return true;
|
||||
} finally {
|
||||
db.Dispose();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private sealed class SchemaUpgradeCallbacks : ISchemaUpgradeCallbacks {
|
||||
private readonly ProgressDialog dialog;
|
||||
private readonly int total;
|
||||
private bool? decision;
|
||||
|
||||
public SchemaUpgradeCallbacks(ProgressDialog dialog, int total) {
|
||||
this.total = total;
|
||||
this.dialog = dialog;
|
||||
}
|
||||
|
||||
public async Task<bool> CanUpgrade() {
|
||||
return decision ??= (total > 1
|
||||
? await DatabaseGui.ShowCanUpgradeMultipleDatabaseDialog(dialog)
|
||||
: await DatabaseGui.ShowCanUpgradeDatabaseDialog(dialog)) == DialogResult.YesNo.Yes;
|
||||
}
|
||||
|
||||
public Task Start(int versionSteps, Func<ISchemaUpgradeCallbacks.IProgressReporter, Task> doUpgrade) {
|
||||
return doUpgrade(new NullReporter());
|
||||
}
|
||||
|
||||
private sealed class NullReporter : ISchemaUpgradeCallbacks.IProgressReporter {
|
||||
public Task NextVersion() {
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task MainWork(string message, int finishedItems, int totalItems) {
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task SubWork(string message, int finishedItems, int totalItems) {
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async void ImportLegacyArchive() {
|
||||
var paths = await window.StorageProvider.OpenFiles(new FilePickerOpenOptions {
|
||||
Title = "Open Legacy DHT Archive",
|
||||
SuggestedStartLocation = await FileDialogs.GetSuggestedStartLocation(window, Path.GetDirectoryName(Db.Path)),
|
||||
AllowMultiple = true
|
||||
});
|
||||
|
||||
if (paths.Length == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
ProgressDialog progressDialog = new ProgressDialog();
|
||||
progressDialog.DataContext = new ProgressDialogModel(async callbacks => await ImportLegacyArchiveFromPaths(Db, paths, progressDialog, callbacks[0])) {
|
||||
Title = "Legacy Archive Import"
|
||||
};
|
||||
|
||||
await progressDialog.ShowProgressDialog(window);
|
||||
}
|
||||
|
||||
private static async Task ImportLegacyArchiveFromPaths(IDatabaseFile target, string[] paths, ProgressDialog dialog, IProgressCallback callback) {
|
||||
var fakeSnowflake = new FakeSnowflake();
|
||||
|
||||
await PerformImport(target, paths, dialog, callback, "Legacy Archive Import", "Legacy Archive Error", "archive file", async path => {
|
||||
await using var jsonStream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read);
|
||||
|
||||
return await LegacyArchiveImport.Read(jsonStream, target, fakeSnowflake, async servers => {
|
||||
SynchronizationContext? prevSyncContext = SynchronizationContext.Current;
|
||||
SynchronizationContext.SetSynchronizationContext(new AvaloniaSynchronizationContext());
|
||||
Dictionary<DHT.Server.Data.Server, ulong>? result = await Dispatcher.UIThread.InvokeAsync(() => AskForServerIds(dialog, servers));
|
||||
SynchronizationContext.SetSynchronizationContext(prevSyncContext);
|
||||
return result;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private static async Task<Dictionary<DHT.Server.Data.Server, ulong>?> AskForServerIds(Window window, DHT.Server.Data.Server[] servers) {
|
||||
static bool IsValidSnowflake(string value) {
|
||||
return string.IsNullOrEmpty(value) || ulong.TryParse(value, out _);
|
||||
}
|
||||
|
||||
var items = new List<TextBoxItem<DHT.Server.Data.Server>>();
|
||||
|
||||
foreach (var server in servers.OrderBy(static server => server.Type).ThenBy(static server => server.Name)) {
|
||||
items.Add(new TextBoxItem<DHT.Server.Data.Server>(server) {
|
||||
Title = server.Name + " (" + ServerTypes.ToNiceString(server.Type) + ")",
|
||||
ValidityCheck = IsValidSnowflake
|
||||
});
|
||||
}
|
||||
|
||||
var model = new TextBoxDialogModel<DHT.Server.Data.Server>(items) {
|
||||
Title = "Imported Server IDs",
|
||||
Description = "Please fill in the IDs of servers and direct messages. First enable Developer Mode in Discord, then right-click each server or direct message, click 'Copy ID', and paste it into the input field. If a server no longer exists, leave its input field empty to use a random ID."
|
||||
};
|
||||
|
||||
var dialog = new TextBoxDialog { DataContext = model };
|
||||
var result = await dialog.ShowDialog<DialogResult.OkCancel>(window);
|
||||
|
||||
if (result != DialogResult.OkCancel.Ok) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return model.ValidItems
|
||||
.Where(static item => !string.IsNullOrEmpty(item.Value))
|
||||
.ToDictionary(static item => item.Item, static item => ulong.Parse(item.Value));
|
||||
}
|
||||
|
||||
private static async Task PerformImport(IDatabaseFile target, string[] paths, ProgressDialog dialog, IProgressCallback callback, string neutralDialogTitle, string errorDialogTitle, string itemName, Func<string, Task<bool>> performImport) {
|
||||
int total = paths.Length;
|
||||
var oldStatistics = target.SnapshotStatistics();
|
||||
|
||||
int successful = 0;
|
||||
int finished = 0;
|
||||
|
||||
foreach (string path in paths) {
|
||||
await callback.Update(Path.GetFileName(path), finished, total);
|
||||
++finished;
|
||||
|
||||
if (!File.Exists(path)) {
|
||||
await Dialog.ShowOk(dialog, errorDialogTitle, "File '" + Path.GetFileName(path) + "' no longer exists.");
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
if (await performImport(path)) {
|
||||
++successful;
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
Log.Error(ex);
|
||||
await Dialog.ShowOk(dialog, errorDialogTitle, "File '" + Path.GetFileName(path) + "' could not be imported: " + ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
await callback.Update("Done", finished, total);
|
||||
|
||||
if (successful == 0) {
|
||||
await Dialog.ShowOk(dialog, neutralDialogTitle, "Nothing was imported.");
|
||||
return;
|
||||
}
|
||||
|
||||
await Dialog.ShowOk(dialog, neutralDialogTitle, GetImportDialogMessage(oldStatistics, target.SnapshotStatistics(), successful, total, itemName));
|
||||
}
|
||||
|
||||
private static string GetImportDialogMessage(DatabaseStatisticsSnapshot oldStatistics, DatabaseStatisticsSnapshot newStatistics, int successfulItems, int totalItems, string itemName) {
|
||||
long newServers = newStatistics.TotalServers - oldStatistics.TotalServers;
|
||||
long newChannels = newStatistics.TotalChannels - oldStatistics.TotalChannels;
|
||||
long newUsers = newStatistics.TotalUsers - oldStatistics.TotalUsers;
|
||||
long newMessages = newStatistics.TotalMessages - oldStatistics.TotalMessages;
|
||||
|
||||
StringBuilder message = new StringBuilder();
|
||||
message.Append("Processed ");
|
||||
|
||||
if (successfulItems == totalItems) {
|
||||
message.Append(successfulItems.Pluralize(itemName));
|
||||
}
|
||||
else {
|
||||
message.Append(successfulItems.Format()).Append(" out of ").Append(totalItems.Pluralize(itemName));
|
||||
}
|
||||
|
||||
message.Append(" and added:\n\n \u2022 ");
|
||||
message.Append(newServers.Pluralize("server")).Append("\n \u2022 ");
|
||||
message.Append(newChannels.Pluralize("channel")).Append("\n \u2022 ");
|
||||
message.Append(newUsers.Pluralize("user")).Append("\n \u2022 ");
|
||||
message.Append(newMessages.Pluralize("message"));
|
||||
|
||||
return message.ToString();
|
||||
}
|
||||
}
|
46
app/Desktop/Main/Pages/DebugPage.axaml
Normal file
46
app/Desktop/Main/Pages/DebugPage.axaml
Normal file
@@ -0,0 +1,46 @@
|
||||
<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"
|
||||
xmlns:pages="clr-namespace:DHT.Desktop.Main.Pages"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="DHT.Desktop.Main.Pages.DebugPage"
|
||||
x:DataType="pages:DebugPageModel">
|
||||
|
||||
<Design.DataContext>
|
||||
<pages:DebugPageModel />
|
||||
</Design.DataContext>
|
||||
|
||||
<UserControl.Styles>
|
||||
<Style Selector="TextBox">
|
||||
<Setter Property="FontFamily" Value="Consolas,Courier" />
|
||||
<Setter Property="FontSize" Value="15" />
|
||||
</Style>
|
||||
<Style Selector="WrapPanel > StackPanel">
|
||||
<Setter Property="Orientation" Value="Vertical" />
|
||||
<Setter Property="Margin" Value="0 0 10 10" />
|
||||
</Style>
|
||||
</UserControl.Styles>
|
||||
|
||||
<StackPanel Orientation="Vertical" Spacing="10">
|
||||
<Expander Header="Generate Random Data" IsExpanded="True">
|
||||
<WrapPanel>
|
||||
<StackPanel>
|
||||
<Label Target="Channels">Channels</Label>
|
||||
<TextBox x:Name="Channels" Width="100" Text="{Binding GenerateChannels}" />
|
||||
</StackPanel>
|
||||
<StackPanel>
|
||||
<Label Target="Users">Users</Label>
|
||||
<TextBox x:Name="Users" Width="100" Text="{Binding GenerateUsers}" />
|
||||
</StackPanel>
|
||||
<StackPanel>
|
||||
<Label Target="Messages">Messages</Label>
|
||||
<TextBox x:Name="Messages" Width="100" Text="{Binding GenerateMessages}" />
|
||||
</StackPanel>
|
||||
<StackPanel VerticalAlignment="Bottom">
|
||||
<Button Command="{Binding OnClickAddRandomDataToDatabase}">Add to Database</Button>
|
||||
</StackPanel>
|
||||
</WrapPanel>
|
||||
</Expander>
|
||||
</StackPanel>
|
||||
</UserControl>
|
11
app/Desktop/Main/Pages/DebugPage.axaml.cs
Normal file
11
app/Desktop/Main/Pages/DebugPage.axaml.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Avalonia.Controls;
|
||||
|
||||
namespace DHT.Desktop.Main.Pages;
|
||||
|
||||
[SuppressMessage("ReSharper", "MemberCanBeInternal")]
|
||||
public sealed partial class DebugPage : UserControl {
|
||||
public DebugPage() {
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
185
app/Desktop/Main/Pages/DebugPageModel.cs
Normal file
185
app/Desktop/Main/Pages/DebugPageModel.cs
Normal file
@@ -0,0 +1,185 @@
|
||||
#if DEBUG
|
||||
using System;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Avalonia.Controls;
|
||||
using DHT.Desktop.Dialogs.Message;
|
||||
using DHT.Desktop.Dialogs.Progress;
|
||||
using DHT.Server.Data;
|
||||
using DHT.Server.Database;
|
||||
using DHT.Server.Service;
|
||||
using DHT.Utils.Models;
|
||||
|
||||
namespace DHT.Desktop.Main.Pages {
|
||||
sealed class DebugPageModel : BaseModel {
|
||||
public string GenerateChannels { get; set; } = "0";
|
||||
public string GenerateUsers { get; set; } = "0";
|
||||
public string GenerateMessages { get; set; } = "0";
|
||||
|
||||
private readonly Window window;
|
||||
private readonly IDatabaseFile db;
|
||||
|
||||
[Obsolete("Designer")]
|
||||
public DebugPageModel() : this(null!, DummyDatabaseFile.Instance) {}
|
||||
|
||||
public DebugPageModel(Window window, IDatabaseFile db) {
|
||||
this.window = window;
|
||||
this.db = db;
|
||||
}
|
||||
|
||||
public async void OnClickAddRandomDataToDatabase() {
|
||||
if (!int.TryParse(GenerateChannels, out int channels) || channels < 1) {
|
||||
await Dialog.ShowOk(window, "Generate Random Data", "Amount of channels must be at least 1!");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!int.TryParse(GenerateUsers, out int users) || users < 1) {
|
||||
await Dialog.ShowOk(window, "Generate Random Data", "Amount of users must be at least 1!");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!int.TryParse(GenerateMessages, out int messages) || messages < 1) {
|
||||
await Dialog.ShowOk(window, "Generate Random Data", "Amount of messages must be at least 1!");
|
||||
return;
|
||||
}
|
||||
|
||||
ProgressDialog progressDialog = new ProgressDialog {
|
||||
DataContext = new ProgressDialogModel(async callbacks => await GenerateRandomData(channels, users, messages, callbacks[0])) {
|
||||
Title = "Generating Random Data"
|
||||
}
|
||||
};
|
||||
|
||||
await progressDialog.ShowProgressDialog(window);
|
||||
}
|
||||
|
||||
private const int BatchSize = 500;
|
||||
|
||||
private async Task GenerateRandomData(int channelCount, int userCount, int messageCount, IProgressCallback callback) {
|
||||
int batchCount = (messageCount + BatchSize - 1) / BatchSize;
|
||||
await callback.Update("Adding messages in batches of " + BatchSize, 0, batchCount);
|
||||
|
||||
var rand = new Random();
|
||||
var server = new DHT.Server.Data.Server {
|
||||
Id = RandomId(rand),
|
||||
Name = RandomName("s"),
|
||||
Type = ServerType.Server,
|
||||
};
|
||||
|
||||
var channels = Enumerable.Range(0, channelCount).Select(i => new Channel {
|
||||
Id = RandomId(rand),
|
||||
Server = server.Id,
|
||||
Name = RandomName("c"),
|
||||
ParentId = null,
|
||||
Position = i,
|
||||
Topic = RandomText(rand, 10),
|
||||
Nsfw = rand.Next(4) == 0,
|
||||
}).ToArray();
|
||||
|
||||
var users = Enumerable.Range(0, userCount).Select(_ => new User {
|
||||
Id = RandomId(rand),
|
||||
Name = RandomName("u"),
|
||||
AvatarUrl = null,
|
||||
Discriminator = rand.Next(0, 9999).ToString(),
|
||||
}).ToArray();
|
||||
|
||||
db.AddServer(server);
|
||||
db.AddUsers(users);
|
||||
|
||||
foreach (var channel in channels) {
|
||||
db.AddChannel(channel);
|
||||
}
|
||||
|
||||
var now = DateTimeOffset.Now;
|
||||
int batchIndex = 0;
|
||||
|
||||
while (messageCount > 0) {
|
||||
int hourOffset = batchIndex;
|
||||
|
||||
var messages = Enumerable.Range(0, Math.Min(messageCount, BatchSize)).Select(i => {
|
||||
DateTimeOffset time = now.AddHours(hourOffset).AddMinutes(i * 60.0 / BatchSize);
|
||||
DateTimeOffset? edit = rand.Next(100) == 0 ? time.AddSeconds(rand.Next(1, 60)) : null;
|
||||
|
||||
var timeMillis = time.ToUnixTimeMilliseconds();
|
||||
var editMillis = edit?.ToUnixTimeMilliseconds();
|
||||
|
||||
return new Message {
|
||||
Id = (ulong) timeMillis,
|
||||
Sender = RandomBiasedIndex(rand, users).Id,
|
||||
Channel = RandomBiasedIndex(rand, channels).Id,
|
||||
Text = RandomText(rand, 100),
|
||||
Timestamp = timeMillis,
|
||||
EditTimestamp = editMillis,
|
||||
RepliedToId = null,
|
||||
Attachments = ImmutableArray<Attachment>.Empty,
|
||||
Embeds = ImmutableArray<Embed>.Empty,
|
||||
Reactions = ImmutableArray<Reaction>.Empty,
|
||||
};
|
||||
}).ToArray();
|
||||
|
||||
db.AddMessages(messages);
|
||||
|
||||
messageCount -= BatchSize;
|
||||
await callback.Update("Adding messages in batches of " + BatchSize, ++batchIndex, batchCount);
|
||||
}
|
||||
}
|
||||
|
||||
private static ulong RandomId(Random rand) {
|
||||
ulong h = unchecked((ulong) rand.Next());
|
||||
ulong l = unchecked((ulong) rand.Next());
|
||||
return (h << 32) | l;
|
||||
}
|
||||
|
||||
private static string RandomName(string prefix) {
|
||||
return prefix + "-" + ServerUtils.GenerateRandomToken(5);
|
||||
}
|
||||
|
||||
private static T RandomBiasedIndex<T>(Random rand, T[] options) {
|
||||
return options[(int) Math.Floor(options.Length * rand.NextDouble() * rand.NextDouble())];
|
||||
}
|
||||
|
||||
private static readonly string[] RandomWords = {
|
||||
"apple", "apricot", "artichoke", "arugula", "asparagus", "avocado",
|
||||
"banana", "bean", "beechnut", "beet", "blackberry", "blackcurrant", "blueberry", "boysenberry", "bramble", "broccoli",
|
||||
"cabbage", "cacao", "cantaloupe", "caper", "carambola", "carrot", "cauliflower", "celery", "chard", "cherry", "chokeberry", "citron", "clementine", "coconut", "corn", "crabapple", "cranberry", "cucumber", "currant",
|
||||
"daikon", "date", "dewberry", "durian",
|
||||
"edamame", "eggplant", "elderberry", "endive",
|
||||
"fig",
|
||||
"garlic", "ginger", "gooseberry", "grape", "grapefruit", "guava",
|
||||
"honeysuckle", "horseradish", "huckleberry",
|
||||
"jackfruit", "jicama",
|
||||
"kale", "kiwi", "kohlrabi", "kumquat",
|
||||
"leek", "lemon", "lentil", "lettuce", "lime",
|
||||
"mandarin", "mango", "mushroom", "myrtle",
|
||||
"nectarine", "nut",
|
||||
"olive", "okra", "onion", "orange",
|
||||
"papaya", "parsnip", "pawpaw", "peach", "pear", "pea", "pepper", "persimmon", "pineapple", "plum", "plantain", "pomegranate", "pomelo", "potato", "prune", "pumpkin",
|
||||
"quandong", "quinoa",
|
||||
"radicchio", "radish", "raisin", "raspberry", "redcurrant", "rhubarb", "rutabaga",
|
||||
"spinach", "strawberry", "squash",
|
||||
"tamarind", "tangerine", "tomatillo", "tomato", "turnip",
|
||||
"vanilla",
|
||||
"watercress", "watermelon",
|
||||
"yam",
|
||||
"zucchini",
|
||||
};
|
||||
|
||||
private static string RandomText(Random rand, int maxWords) {
|
||||
int wordCount = 1 + (int) Math.Floor(maxWords * Math.Pow(rand.NextDouble(), 3));
|
||||
return string.Join(' ', Enumerable.Range(0, wordCount).Select(_ => RandomWords[rand.Next(RandomWords.Length)]));
|
||||
}
|
||||
}
|
||||
}
|
||||
#else
|
||||
using DHT.Utils.Models;
|
||||
|
||||
namespace DHT.Desktop.Main.Pages {
|
||||
sealed class DebugPageModel : BaseModel {
|
||||
public string GenerateChannels { get; set; } = "0";
|
||||
public string GenerateUsers { get; set; } = "0";
|
||||
public string GenerateMessages { get; set; } = "0";
|
||||
|
||||
public void OnClickAddRandomDataToDatabase() {}
|
||||
}
|
||||
}
|
||||
#endif
|
27
app/Desktop/Main/Pages/TrackingPage.axaml
Normal file
27
app/Desktop/Main/Pages/TrackingPage.axaml
Normal file
@@ -0,0 +1,27 @@
|
||||
<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"
|
||||
xmlns:pages="clr-namespace:DHT.Desktop.Main.Pages"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="DHT.Desktop.Main.Pages.TrackingPage"
|
||||
x:DataType="pages:TrackingPageModel">
|
||||
|
||||
<Design.DataContext>
|
||||
<pages:TrackingPageModel />
|
||||
</Design.DataContext>
|
||||
|
||||
<StackPanel Spacing="10">
|
||||
<TextBlock TextWrapping="Wrap">
|
||||
To start tracking messages, copy the tracking script and paste it into the console of either the Discord app, or your browser. The console is usually opened by pressing Ctrl+Shift+I.
|
||||
</TextBlock>
|
||||
<StackPanel DockPanel.Dock="Left" Orientation="Horizontal" Spacing="10">
|
||||
<Button x:Name="CopyTrackingScript" Click="CopyTrackingScriptButton_OnClick">Copy Tracking Script</Button>
|
||||
</StackPanel>
|
||||
<TextBlock TextWrapping="Wrap" Margin="0 5 0 0">
|
||||
By default, the Discord app does not allow opening the console. The button below will change a hidden setting in the Discord app that controls whether the Ctrl+Shift+I shortcut is enabled.
|
||||
</TextBlock>
|
||||
<Button DockPanel.Dock="Right" Command="{Binding OnClickToggleAppDevTools}" Content="{Binding ToggleAppDevToolsButtonText}" IsEnabled="{Binding IsToggleAppDevToolsButtonEnabled}" />
|
||||
</StackPanel>
|
||||
|
||||
</UserControl>
|
32
app/Desktop/Main/Pages/TrackingPage.axaml.cs
Normal file
32
app/Desktop/Main/Pages/TrackingPage.axaml.cs
Normal file
@@ -0,0 +1,32 @@
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Threading.Tasks;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Interactivity;
|
||||
|
||||
namespace DHT.Desktop.Main.Pages;
|
||||
|
||||
[SuppressMessage("ReSharper", "MemberCanBeInternal")]
|
||||
public sealed partial class TrackingPage : UserControl {
|
||||
private bool isCopyingScript;
|
||||
|
||||
public TrackingPage() {
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
public async void CopyTrackingScriptButton_OnClick(object? sender, RoutedEventArgs e) {
|
||||
if (DataContext is TrackingPageModel model) {
|
||||
var originalText = CopyTrackingScript.Content;
|
||||
CopyTrackingScript.MinWidth = CopyTrackingScript.Bounds.Width;
|
||||
|
||||
if (await model.OnClickCopyTrackingScript() && !isCopyingScript) {
|
||||
isCopyingScript = true;
|
||||
CopyTrackingScript.Content = "Script Copied!";
|
||||
|
||||
await Task.Delay(TimeSpan.FromSeconds(2));
|
||||
CopyTrackingScript.Content = originalText;
|
||||
isCopyingScript = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
112
app/Desktop/Main/Pages/TrackingPageModel.cs
Normal file
112
app/Desktop/Main/Pages/TrackingPageModel.cs
Normal file
@@ -0,0 +1,112 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web;
|
||||
using Avalonia.Controls;
|
||||
using DHT.Desktop.Dialogs.Message;
|
||||
using DHT.Desktop.Discord;
|
||||
using DHT.Desktop.Server;
|
||||
using DHT.Utils.Models;
|
||||
using static DHT.Desktop.Program;
|
||||
|
||||
namespace DHT.Desktop.Main.Pages;
|
||||
|
||||
sealed class TrackingPageModel : BaseModel {
|
||||
private bool areDevToolsEnabled;
|
||||
|
||||
private bool AreDevToolsEnabled {
|
||||
get => areDevToolsEnabled;
|
||||
set {
|
||||
Change(ref areDevToolsEnabled, value);
|
||||
OnPropertyChanged(nameof(ToggleAppDevToolsButtonText));
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsToggleAppDevToolsButtonEnabled { get; private set; } = true;
|
||||
|
||||
public string ToggleAppDevToolsButtonText {
|
||||
get {
|
||||
if (!IsToggleAppDevToolsButtonEnabled) {
|
||||
return "Unavailable";
|
||||
}
|
||||
|
||||
return AreDevToolsEnabled ? "Disable Ctrl+Shift+I" : "Enable Ctrl+Shift+I";
|
||||
}
|
||||
}
|
||||
|
||||
private readonly Window window;
|
||||
|
||||
[Obsolete("Designer")]
|
||||
public TrackingPageModel() : this(null!) {}
|
||||
|
||||
public TrackingPageModel(Window window) {
|
||||
this.window = window;
|
||||
}
|
||||
|
||||
public async Task Initialize() {
|
||||
bool? devToolsEnabled = await DiscordAppSettings.AreDevToolsEnabled();
|
||||
if (devToolsEnabled.HasValue) {
|
||||
AreDevToolsEnabled = devToolsEnabled.Value;
|
||||
}
|
||||
else {
|
||||
IsToggleAppDevToolsButtonEnabled = false;
|
||||
OnPropertyChanged(nameof(IsToggleAppDevToolsButtonEnabled));
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> OnClickCopyTrackingScript() {
|
||||
string url = $"http://127.0.0.1:{ServerManager.Port}/get-tracking-script?token={HttpUtility.UrlEncode(ServerManager.Token)}";
|
||||
string script = (await Resources.ReadTextAsync("tracker-loader.js")).Trim().Replace("{url}", url);
|
||||
|
||||
var clipboard = window.Clipboard;
|
||||
if (clipboard == null) {
|
||||
await Dialog.ShowOk(window, "Copy Tracking Script", "Clipboard is not available on this system.");
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
await clipboard.SetTextAsync(script);
|
||||
return true;
|
||||
} catch {
|
||||
await Dialog.ShowOk(window, "Copy Tracking Script", "An error occurred while copying to clipboard.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public async void OnClickToggleAppDevTools() {
|
||||
const string DialogTitle = "Discord App Settings File";
|
||||
|
||||
bool oldState = AreDevToolsEnabled;
|
||||
bool newState = !oldState;
|
||||
|
||||
switch (await DiscordAppSettings.ConfigureDevTools(newState)) {
|
||||
case SettingsJsonResult.Success:
|
||||
AreDevToolsEnabled = newState;
|
||||
await Dialog.ShowOk(window, DialogTitle, "Ctrl+Shift+I was " + (newState ? "enabled." : "disabled.") + " Restart the Discord app for the change to take effect.");
|
||||
break;
|
||||
|
||||
case SettingsJsonResult.AlreadySet:
|
||||
await Dialog.ShowOk(window, DialogTitle, "Ctrl+Shift+I is already " + (newState ? "enabled." : "disabled."));
|
||||
AreDevToolsEnabled = newState;
|
||||
break;
|
||||
|
||||
case SettingsJsonResult.FileNotFound:
|
||||
await Dialog.ShowOk(window, DialogTitle, "Cannot find the settings file:\n" + DiscordAppSettings.JsonFilePath);
|
||||
break;
|
||||
|
||||
case SettingsJsonResult.ReadError:
|
||||
await Dialog.ShowOk(window, DialogTitle, "Cannot read the settings file:\n" + DiscordAppSettings.JsonFilePath);
|
||||
break;
|
||||
|
||||
case SettingsJsonResult.InvalidJson:
|
||||
await Dialog.ShowOk(window, DialogTitle, "Unknown format of the settings file:\n" + DiscordAppSettings.JsonFilePath);
|
||||
break;
|
||||
|
||||
case SettingsJsonResult.WriteError:
|
||||
await Dialog.ShowOk(window, DialogTitle, "Cannot save the settings file:\n" + DiscordAppSettings.JsonFilePath);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
}
|
||||
}
|
38
app/Desktop/Main/Pages/ViewerPage.axaml
Normal file
38
app/Desktop/Main/Pages/ViewerPage.axaml
Normal file
@@ -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"
|
||||
xmlns:pages="clr-namespace:DHT.Desktop.Main.Pages"
|
||||
xmlns:controls="clr-namespace:DHT.Desktop.Main.Controls"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="DHT.Desktop.Main.Pages.ViewerPage"
|
||||
x:DataType="pages:ViewerPageModel">
|
||||
|
||||
<Design.DataContext>
|
||||
<pages:ViewerPageModel />
|
||||
</Design.DataContext>
|
||||
|
||||
<UserControl.Styles>
|
||||
<Style Selector="Expander">
|
||||
<Setter Property="Margin" Value="0 5 0 0" />
|
||||
</Style>
|
||||
</UserControl.Styles>
|
||||
|
||||
<StackPanel Orientation="Vertical" Spacing="20">
|
||||
<StackPanel Orientation="Horizontal" VerticalAlignment="Top">
|
||||
<Button Command="{Binding OnClickOpenViewer}" Margin="0 0 5 0">Open Viewer</Button>
|
||||
<Button Command="{Binding OnClickSaveViewer}" Margin="5 0 0 0">Save Viewer</Button>
|
||||
</StackPanel>
|
||||
<controls:MessageFilterPanel DataContext="{Binding FilterModel}" />
|
||||
<Expander Header="Database Tools">
|
||||
<StackPanel Orientation="Vertical" Spacing="10">
|
||||
<StackPanel Orientation="Vertical" Spacing="4">
|
||||
<RadioButton GroupName="DatabaseToolFilterMode" IsEnabled="{Binding HasFilters}" IsChecked="{Binding DatabaseToolFilterModeKeep}">Keep Only Messages Matching Filters</RadioButton>
|
||||
<RadioButton GroupName="DatabaseToolFilterMode" IsEnabled="{Binding HasFilters}" IsChecked="{Binding DatabaseToolFilterModeRemove}">Remove Messages Matching Filters</RadioButton>
|
||||
</StackPanel>
|
||||
<Button IsEnabled="{Binding HasFilters}" Command="{Binding OnClickApplyFiltersToDatabase}">Apply Filters to Database</Button>
|
||||
</StackPanel>
|
||||
</Expander>
|
||||
</StackPanel>
|
||||
|
||||
</UserControl>
|
11
app/Desktop/Main/Pages/ViewerPage.axaml.cs
Normal file
11
app/Desktop/Main/Pages/ViewerPage.axaml.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Avalonia.Controls;
|
||||
|
||||
namespace DHT.Desktop.Main.Pages;
|
||||
|
||||
[SuppressMessage("ReSharper", "MemberCanBeInternal")]
|
||||
public sealed partial class ViewerPage : UserControl {
|
||||
public ViewerPage() {
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
151
app/Desktop/Main/Pages/ViewerPageModel.cs
Normal file
151
app/Desktop/Main/Pages/ViewerPageModel.cs
Normal file
@@ -0,0 +1,151 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Platform.Storage;
|
||||
using DHT.Desktop.Common;
|
||||
using DHT.Desktop.Dialogs.File;
|
||||
using DHT.Desktop.Dialogs.Message;
|
||||
using DHT.Desktop.Main.Controls;
|
||||
using DHT.Desktop.Server;
|
||||
using DHT.Server.Data.Filters;
|
||||
using DHT.Server.Database;
|
||||
using DHT.Server.Database.Export;
|
||||
using DHT.Server.Database.Export.Strategy;
|
||||
using DHT.Utils.Models;
|
||||
using static DHT.Desktop.Program;
|
||||
|
||||
namespace DHT.Desktop.Main.Pages;
|
||||
|
||||
sealed class ViewerPageModel : BaseModel, IDisposable {
|
||||
public static readonly ConcurrentBag<string> TemporaryFiles = new ();
|
||||
|
||||
public bool DatabaseToolFilterModeKeep { get; set; } = true;
|
||||
public bool DatabaseToolFilterModeRemove { get; set; } = false;
|
||||
|
||||
private bool hasFilters = false;
|
||||
|
||||
public bool HasFilters {
|
||||
get => hasFilters;
|
||||
set => Change(ref hasFilters, value);
|
||||
}
|
||||
|
||||
public MessageFilterPanelModel FilterModel { get; }
|
||||
|
||||
private readonly Window window;
|
||||
private readonly IDatabaseFile db;
|
||||
|
||||
[Obsolete("Designer")]
|
||||
public ViewerPageModel() : this(null!, DummyDatabaseFile.Instance) {}
|
||||
|
||||
public ViewerPageModel(Window window, IDatabaseFile db) {
|
||||
this.window = window;
|
||||
this.db = db;
|
||||
|
||||
FilterModel = new MessageFilterPanelModel(window, db, "Will export");
|
||||
FilterModel.FilterPropertyChanged += OnFilterPropertyChanged;
|
||||
}
|
||||
|
||||
public void Dispose() {
|
||||
FilterModel.Dispose();
|
||||
}
|
||||
|
||||
private void OnFilterPropertyChanged(object? sender, PropertyChangedEventArgs e) {
|
||||
HasFilters = FilterModel.HasAnyFilters;
|
||||
}
|
||||
|
||||
private async Task WriteViewerFile(string path, IViewerExportStrategy strategy) {
|
||||
const string ArchiveTag = "/*[ARCHIVE]*/";
|
||||
|
||||
string indexFile = await Resources.ReadTextAsync("Viewer/index.html");
|
||||
string viewerTemplate = indexFile.Replace("/*[JS]*/", await Resources.ReadJoinedAsync("Viewer/scripts/", '\n'))
|
||||
.Replace("/*[CSS]*/", await Resources.ReadJoinedAsync("Viewer/styles/", '\n'));
|
||||
|
||||
viewerTemplate = strategy.ProcessViewerTemplate(viewerTemplate);
|
||||
|
||||
int viewerArchiveTagStart = viewerTemplate.IndexOf(ArchiveTag);
|
||||
int viewerArchiveTagEnd = viewerArchiveTagStart + ArchiveTag.Length;
|
||||
|
||||
string jsonTempFile = path + ".tmp";
|
||||
|
||||
await using (var jsonStream = new FileStream(jsonTempFile, FileMode.Create, FileAccess.ReadWrite, FileShare.Read)) {
|
||||
await ViewerJsonExport.Generate(jsonStream, strategy, db, FilterModel.CreateFilter());
|
||||
|
||||
char[] jsonBuffer = new char[Math.Min(32768, jsonStream.Position)];
|
||||
jsonStream.Position = 0;
|
||||
|
||||
await using (var outputStream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read))
|
||||
await using (var outputWriter = new StreamWriter(outputStream, Encoding.UTF8)) {
|
||||
await outputWriter.WriteAsync(viewerTemplate[..viewerArchiveTagStart]);
|
||||
|
||||
using (var jsonReader = new StreamReader(jsonStream, Encoding.UTF8)) {
|
||||
int readBytes;
|
||||
while ((readBytes = await jsonReader.ReadAsync(jsonBuffer, 0, jsonBuffer.Length)) > 0) {
|
||||
string jsonChunk = new string(jsonBuffer, 0, readBytes);
|
||||
await outputWriter.WriteAsync(HttpUtility.JavaScriptStringEncode(jsonChunk));
|
||||
}
|
||||
}
|
||||
|
||||
await outputWriter.WriteAsync(viewerTemplate[viewerArchiveTagEnd..]);
|
||||
}
|
||||
}
|
||||
|
||||
File.Delete(jsonTempFile);
|
||||
}
|
||||
|
||||
public async void OnClickOpenViewer() {
|
||||
string rootPath = Path.Combine(Path.GetTempPath(), "DiscordHistoryTracker");
|
||||
string filenameBase = Path.GetFileNameWithoutExtension(db.Path) + "-" + DateTime.Now.ToString("yyyy-MM-dd");
|
||||
string fullPath = Path.Combine(rootPath, filenameBase + ".html");
|
||||
int counter = 0;
|
||||
|
||||
while (File.Exists(fullPath)) {
|
||||
++counter;
|
||||
fullPath = Path.Combine(rootPath, filenameBase + "-" + counter + ".html");
|
||||
}
|
||||
|
||||
TemporaryFiles.Add(fullPath);
|
||||
|
||||
Directory.CreateDirectory(rootPath);
|
||||
await WriteViewerFile(fullPath, new LiveViewerExportStrategy(ServerManager.Port, ServerManager.Token));
|
||||
|
||||
Process.Start(new ProcessStartInfo(fullPath) { UseShellExecute = true });
|
||||
}
|
||||
|
||||
private static readonly FilePickerFileType[] ViewerFileTypes = {
|
||||
FileDialogs.CreateFilter("Discord History Viewer", new string[] { "html" }),
|
||||
};
|
||||
|
||||
public async void OnClickSaveViewer() {
|
||||
string? path = await window.StorageProvider.SaveFile(new FilePickerSaveOptions {
|
||||
Title = "Save Viewer",
|
||||
FileTypeChoices = ViewerFileTypes,
|
||||
SuggestedFileName = Path.GetFileNameWithoutExtension(db.Path) + ".html",
|
||||
SuggestedStartLocation = await FileDialogs.GetSuggestedStartLocation(window, Path.GetDirectoryName(db.Path)),
|
||||
});
|
||||
|
||||
if (path != null) {
|
||||
await WriteViewerFile(path, StandaloneViewerExportStrategy.Instance);
|
||||
}
|
||||
}
|
||||
|
||||
public async void OnClickApplyFiltersToDatabase() {
|
||||
var filter = FilterModel.CreateFilter();
|
||||
|
||||
if (DatabaseToolFilterModeKeep) {
|
||||
if (DialogResult.YesNo.Yes == await Dialog.ShowYesNo(window, "Keep Matching Messages in This Database", db.CountMessages(filter).Pluralize("message") + " will be kept, and the rest will be removed from this database. This action cannot be undone. Proceed?")) {
|
||||
db.RemoveMessages(filter, FilterRemovalMode.KeepMatching);
|
||||
}
|
||||
}
|
||||
else if (DatabaseToolFilterModeRemove) {
|
||||
if (DialogResult.YesNo.Yes == await Dialog.ShowYesNo(window, "Remove Matching Messages in This Database", db.CountMessages(filter).Pluralize("message") + " will be removed from this database. This action cannot be undone. Proceed?")) {
|
||||
db.RemoveMessages(filter, FilterRemovalMode.RemoveMatching);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
120
app/Desktop/Main/Screens/MainContentScreen.axaml
Normal file
120
app/Desktop/Main/Screens/MainContentScreen.axaml
Normal file
@@ -0,0 +1,120 @@
|
||||
<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"
|
||||
xmlns:controls="clr-namespace:DHT.Desktop.Main.Controls"
|
||||
xmlns:screens="clr-namespace:DHT.Desktop.Main.Screens"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="DHT.Desktop.Main.Screens.MainContentScreen"
|
||||
x:DataType="screens:MainContentScreenModel">
|
||||
|
||||
<Design.DataContext>
|
||||
<screens:MainContentScreenModel />
|
||||
</Design.DataContext>
|
||||
|
||||
<UserControl.Styles>
|
||||
<Style Selector="Border.statusBar">
|
||||
<Setter Property="Background" Value="#3C4F79" />
|
||||
</Style>
|
||||
<Style Selector="TextBlock.invisibleTabItem">
|
||||
<Setter Property="FontSize" Value="20" />
|
||||
<Setter Property="FontWeight" Value="SemiLight" />
|
||||
<Setter Property="Margin" Value="17 0" />
|
||||
<Setter Property="Opacity" Value="0" />
|
||||
</Style>
|
||||
<Style Selector="TabControl ItemsPresenter > Grid">
|
||||
<Setter Property="Background" Value="#546A9F" />
|
||||
</Style>
|
||||
<Style Selector="TabItem">
|
||||
<Setter Property="Cursor" Value="Hand" />
|
||||
<Setter Property="Foreground" Value="#E9E9E9" />
|
||||
<Setter Property="FontSize" Value="20" />
|
||||
</Style>
|
||||
<Style Selector="TabItem[TabStripPlacement=Left] /template/ ContentPresenter#PART_ContentPresenter">
|
||||
<Setter Property="Margin" Value="5 0" />
|
||||
</Style>
|
||||
<Style Selector="TabItem:pointerover">
|
||||
<Setter Property="Cursor" Value="Hand" />
|
||||
<Setter Property="Background" Value="#455785" />
|
||||
</Style>
|
||||
<Style Selector="TabItem:pointerover /template/ Border">
|
||||
<Setter Property="Background" Value="#455785" />
|
||||
</Style>
|
||||
<Style Selector="TabItem:pointerover > TextBlock">
|
||||
<Setter Property="Foreground" Value="#E9E9E9" />
|
||||
</Style>
|
||||
<Style Selector="TabItem:selected:pointerover /template/ Border">
|
||||
<Setter Property="Background" Value="#FFFFFF" />
|
||||
</Style>
|
||||
<Style Selector="TabItem:selected:pointerover > TextBlock">
|
||||
<Setter Property="Foreground" Value="#1A2234" />
|
||||
</Style>
|
||||
<Style Selector="TabItem:selected">
|
||||
<Setter Property="Foreground" Value="#1A2234" />
|
||||
<Setter Property="Background" Value="#FFFFFF" />
|
||||
</Style>
|
||||
<Style Selector="TabItem:selected /template/ Border#PART_SelectedPipe">
|
||||
<Setter Property="IsVisible" Value="False" />
|
||||
</Style>
|
||||
<Style Selector="TabItem:disabled > TextBlock">
|
||||
<Setter Property="Foreground" Value="#B2B2B2" />
|
||||
<Setter Property="TextDecorations" Value="Strikethrough" />
|
||||
</Style>
|
||||
<Style Selector="TabItem.first">
|
||||
<Setter Property="Margin" Value="0 13 0 0" />
|
||||
</Style>
|
||||
<Style Selector="TabControl">
|
||||
<Setter Property="Padding" Value="0" />
|
||||
</Style>
|
||||
<Style Selector="ContentPresenter.page">
|
||||
<Setter Property="Margin" Value="15 21 20 21" />
|
||||
</Style>
|
||||
</UserControl.Styles>
|
||||
|
||||
<DockPanel>
|
||||
<Border Classes="statusBar" DockPanel.Dock="Bottom">
|
||||
<DockPanel>
|
||||
<TextBlock Classes="invisibleTabItem" DockPanel.Dock="Left">Attachments</TextBlock>
|
||||
<controls:StatusBar DataContext="{Binding StatusBarModel}" DockPanel.Dock="Right" />
|
||||
</DockPanel>
|
||||
</Border>
|
||||
<TabControl x:Name="TabControl" TabStripPlacement="Left" DockPanel.Dock="Top">
|
||||
<TabControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<Grid ColumnDefinitions="Auto" RowDefinitions="Auto,Auto,Auto,Auto,*,Auto,Auto" />
|
||||
</ItemsPanelTemplate>
|
||||
</TabControl.ItemsPanel>
|
||||
<TabItem x:Name="TabDatabase" Header="Database" Classes="first" Grid.Row="0">
|
||||
<ScrollViewer>
|
||||
<ContentPresenter Content="{Binding DatabasePage}" Classes="page" />
|
||||
</ScrollViewer>
|
||||
</TabItem>
|
||||
<TabItem x:Name="TabTracking" Header="Tracking" Grid.Row="1">
|
||||
<ScrollViewer>
|
||||
<ContentPresenter Content="{Binding TrackingPage}" Classes="page" />
|
||||
</ScrollViewer>
|
||||
</TabItem>
|
||||
<TabItem x:Name="TabAttachments" Header="Attachments" Grid.Row="2">
|
||||
<ScrollViewer>
|
||||
<ContentPresenter Content="{Binding AttachmentsPage}" Classes="page" />
|
||||
</ScrollViewer>
|
||||
</TabItem>
|
||||
<TabItem x:Name="TabViewer" Header="Viewer" Grid.Row="3">
|
||||
<ScrollViewer>
|
||||
<ContentPresenter Content="{Binding ViewerPage}" Classes="page" />
|
||||
</ScrollViewer>
|
||||
</TabItem>
|
||||
<TabItem x:Name="TabAdvanced" Header="Advanced" Grid.Row="5">
|
||||
<ScrollViewer>
|
||||
<ContentPresenter Content="{Binding AdvancedPage}" Classes="page" />
|
||||
</ScrollViewer>
|
||||
</TabItem>
|
||||
<TabItem x:Name="TabDebug" Header="Debug" Grid.Row="6" IsVisible="{Binding HasDebugPage}">
|
||||
<ScrollViewer>
|
||||
<ContentPresenter Content="{Binding DebugPage}" Classes="page" />
|
||||
</ScrollViewer>
|
||||
</TabItem>
|
||||
</TabControl>
|
||||
</DockPanel>
|
||||
|
||||
</UserControl>
|
11
app/Desktop/Main/Screens/MainContentScreen.axaml.cs
Normal file
11
app/Desktop/Main/Screens/MainContentScreen.axaml.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Avalonia.Controls;
|
||||
|
||||
namespace DHT.Desktop.Main.Screens;
|
||||
|
||||
[SuppressMessage("ReSharper", "MemberCanBeInternal")]
|
||||
public sealed partial class MainContentScreen : UserControl {
|
||||
public MainContentScreen() {
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
119
app/Desktop/Main/Screens/MainContentScreenModel.cs
Normal file
119
app/Desktop/Main/Screens/MainContentScreenModel.cs
Normal file
@@ -0,0 +1,119 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Avalonia.Controls;
|
||||
using DHT.Desktop.Dialogs.Message;
|
||||
using DHT.Desktop.Main.Controls;
|
||||
using DHT.Desktop.Main.Pages;
|
||||
using DHT.Desktop.Server;
|
||||
using DHT.Server.Database;
|
||||
using DHT.Server.Service;
|
||||
using DHT.Utils.Logging;
|
||||
|
||||
namespace DHT.Desktop.Main.Screens;
|
||||
|
||||
sealed class MainContentScreenModel : IDisposable {
|
||||
private static readonly Log Log = Log.ForType<MainContentScreenModel>();
|
||||
|
||||
public DatabasePage DatabasePage { get; }
|
||||
private DatabasePageModel DatabasePageModel { get; }
|
||||
|
||||
public TrackingPage TrackingPage { get; }
|
||||
private TrackingPageModel TrackingPageModel { get; }
|
||||
|
||||
public AttachmentsPage AttachmentsPage { get; }
|
||||
private AttachmentsPageModel AttachmentsPageModel { get; }
|
||||
|
||||
public ViewerPage ViewerPage { get; }
|
||||
private ViewerPageModel ViewerPageModel { get; }
|
||||
|
||||
public AdvancedPage AdvancedPage { get; }
|
||||
private AdvancedPageModel AdvancedPageModel { get; }
|
||||
|
||||
public DebugPage? DebugPage { get; }
|
||||
|
||||
#if DEBUG
|
||||
public bool HasDebugPage => true;
|
||||
private DebugPageModel DebugPageModel { get; }
|
||||
#else
|
||||
public bool HasDebugPage => false;
|
||||
#endif
|
||||
|
||||
public StatusBarModel StatusBarModel { get; }
|
||||
|
||||
public event EventHandler? DatabaseClosed {
|
||||
add {
|
||||
DatabasePageModel.DatabaseClosed += value;
|
||||
}
|
||||
remove {
|
||||
DatabasePageModel.DatabaseClosed -= value;
|
||||
}
|
||||
}
|
||||
|
||||
private readonly Window window;
|
||||
private readonly ServerManager serverManager;
|
||||
|
||||
[Obsolete("Designer")]
|
||||
public MainContentScreenModel() : this(null!, DummyDatabaseFile.Instance) {}
|
||||
|
||||
public MainContentScreenModel(Window window, IDatabaseFile db) {
|
||||
this.window = window;
|
||||
this.serverManager = new ServerManager(db);
|
||||
|
||||
ServerLauncher.ServerManagementExceptionCaught += ServerLauncherOnServerManagementExceptionCaught;
|
||||
|
||||
DatabasePageModel = new DatabasePageModel(window, db);
|
||||
DatabasePage = new DatabasePage { DataContext = DatabasePageModel };
|
||||
|
||||
TrackingPageModel = new TrackingPageModel(window);
|
||||
TrackingPage = new TrackingPage { DataContext = TrackingPageModel };
|
||||
|
||||
AttachmentsPageModel = new AttachmentsPageModel(db);
|
||||
AttachmentsPage = new AttachmentsPage { DataContext = AttachmentsPageModel };
|
||||
|
||||
ViewerPageModel = new ViewerPageModel(window, db);
|
||||
ViewerPage = new ViewerPage { DataContext = ViewerPageModel };
|
||||
|
||||
AdvancedPageModel = new AdvancedPageModel(window, db, serverManager);
|
||||
AdvancedPage = new AdvancedPage { DataContext = AdvancedPageModel };
|
||||
|
||||
#if DEBUG
|
||||
DebugPageModel = new DebugPageModel(window, db);
|
||||
DebugPage = new DebugPage { DataContext = DebugPageModel };
|
||||
#else
|
||||
DebugPage = null;
|
||||
#endif
|
||||
|
||||
StatusBarModel = new StatusBarModel(db.Statistics);
|
||||
|
||||
AdvancedPageModel.ServerConfigurationModel.ServerStatusChanged += OnServerStatusChanged;
|
||||
DatabaseClosed += OnDatabaseClosed;
|
||||
|
||||
StatusBarModel.CurrentStatus = serverManager.IsRunning ? StatusBarModel.Status.Ready : StatusBarModel.Status.Stopped;
|
||||
}
|
||||
|
||||
public async Task Initialize() {
|
||||
await TrackingPageModel.Initialize();
|
||||
AdvancedPageModel.Initialize();
|
||||
serverManager.Launch();
|
||||
}
|
||||
|
||||
public void Dispose() {
|
||||
ServerLauncher.ServerManagementExceptionCaught -= ServerLauncherOnServerManagementExceptionCaught;
|
||||
AttachmentsPageModel.Dispose();
|
||||
ViewerPageModel.Dispose();
|
||||
serverManager.Dispose();
|
||||
}
|
||||
|
||||
private void OnServerStatusChanged(object? sender, StatusBarModel.Status e) {
|
||||
StatusBarModel.CurrentStatus = e;
|
||||
}
|
||||
|
||||
private void OnDatabaseClosed(object? sender, EventArgs e) {
|
||||
serverManager.Stop();
|
||||
}
|
||||
|
||||
private async void ServerLauncherOnServerManagementExceptionCaught(object? sender, Exception ex) {
|
||||
Log.Error(ex);
|
||||
await Dialog.ShowOk(window, "Internal Server Error", ex.Message);
|
||||
}
|
||||
}
|
41
app/Desktop/Main/Screens/WelcomeScreen.axaml
Normal file
41
app/Desktop/Main/Screens/WelcomeScreen.axaml
Normal file
@@ -0,0 +1,41 @@
|
||||
<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"
|
||||
xmlns:screens="clr-namespace:DHT.Desktop.Main.Screens"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="DHT.Desktop.Main.Screens.WelcomeScreen"
|
||||
x:DataType="screens:WelcomeScreenModel">
|
||||
|
||||
<Design.DataContext>
|
||||
<screens:WelcomeScreenModel />
|
||||
</Design.DataContext>
|
||||
|
||||
<UserControl.Background>
|
||||
<SolidColorBrush>#546A9F</SolidColorBrush>
|
||||
</UserControl.Background>
|
||||
|
||||
<UserControl.Styles>
|
||||
<Style Selector="Panel#RootPanel">
|
||||
<Setter Property="Background" Value="#FFFFFF" />
|
||||
<Setter Property="Margin" Value="20" />
|
||||
<Setter Property="HorizontalAlignment" Value="Center" />
|
||||
<Setter Property="VerticalAlignment" Value="Center" />
|
||||
</Style>
|
||||
<Style Selector="Button">
|
||||
<Setter Property="Margin" Value="5 0" />
|
||||
</Style>
|
||||
</UserControl.Styles>
|
||||
|
||||
<Panel Name="RootPanel">
|
||||
<StackPanel Margin="42">
|
||||
<TextBlock Text="{Binding Version, StringFormat=Discord History Tracker v{0}}" FontSize="25" Margin="0 0 0 30" HorizontalAlignment="Center" />
|
||||
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
|
||||
<Button Command="{Binding OpenOrCreateDatabase}">Open or Create Database</Button>
|
||||
<Button Command="{Binding ShowAboutDialog}">About</Button>
|
||||
<Button Command="{Binding Exit}">Exit</Button>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Panel>
|
||||
</UserControl>
|
11
app/Desktop/Main/Screens/WelcomeScreen.axaml.cs
Normal file
11
app/Desktop/Main/Screens/WelcomeScreen.axaml.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Avalonia.Controls;
|
||||
|
||||
namespace DHT.Desktop.Main.Screens;
|
||||
|
||||
[SuppressMessage("ReSharper", "MemberCanBeInternal")]
|
||||
public sealed partial class WelcomeScreen : UserControl {
|
||||
public WelcomeScreen() {
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
130
app/Desktop/Main/Screens/WelcomeScreenModel.cs
Normal file
130
app/Desktop/Main/Screens/WelcomeScreenModel.cs
Normal file
@@ -0,0 +1,130 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using Avalonia.Controls;
|
||||
using DHT.Desktop.Common;
|
||||
using DHT.Desktop.Dialogs.Message;
|
||||
using DHT.Desktop.Dialogs.Progress;
|
||||
using DHT.Server.Database;
|
||||
using DHT.Server.Database.Sqlite;
|
||||
using DHT.Utils.Models;
|
||||
|
||||
namespace DHT.Desktop.Main.Screens;
|
||||
|
||||
sealed class WelcomeScreenModel : BaseModel, IDisposable {
|
||||
public string Version => Program.Version;
|
||||
|
||||
public IDatabaseFile? Db { get; private set; }
|
||||
public bool HasDatabase => Db != null;
|
||||
|
||||
private readonly Window window;
|
||||
|
||||
private string? dbFilePath;
|
||||
|
||||
[Obsolete("Designer")]
|
||||
public WelcomeScreenModel() : this(null!) {}
|
||||
|
||||
public WelcomeScreenModel(Window window) {
|
||||
this.window = window;
|
||||
}
|
||||
|
||||
public async void OpenOrCreateDatabase() {
|
||||
var path = await DatabaseGui.NewOpenOrCreateDatabaseFileDialog(window, Path.GetDirectoryName(dbFilePath));
|
||||
if (path != null) {
|
||||
await OpenOrCreateDatabaseFromPath(path);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task OpenOrCreateDatabaseFromPath(string path) {
|
||||
if (Db != null) {
|
||||
Db = null;
|
||||
}
|
||||
|
||||
dbFilePath = path;
|
||||
Db = await DatabaseGui.TryOpenOrCreateDatabaseFromPath(path, window, new SchemaUpgradeCallbacks(window));
|
||||
|
||||
OnPropertyChanged(nameof(Db));
|
||||
OnPropertyChanged(nameof(HasDatabase));
|
||||
}
|
||||
|
||||
private sealed class SchemaUpgradeCallbacks : ISchemaUpgradeCallbacks {
|
||||
private readonly Window window;
|
||||
|
||||
public SchemaUpgradeCallbacks(Window window) {
|
||||
this.window = window;
|
||||
}
|
||||
|
||||
public async Task<bool> CanUpgrade() {
|
||||
return DialogResult.YesNo.Yes == await DatabaseGui.ShowCanUpgradeDatabaseDialog(window);
|
||||
}
|
||||
|
||||
public async Task Start(int versionSteps, Func<ISchemaUpgradeCallbacks.IProgressReporter, Task> doUpgrade) {
|
||||
async Task StartUpgrade(IReadOnlyList<IProgressCallback> callbacks) {
|
||||
var reporter = new ProgressReporter(versionSteps, callbacks);
|
||||
await reporter.NextVersion();
|
||||
await Task.Delay(TimeSpan.FromMilliseconds(800));
|
||||
await doUpgrade(reporter);
|
||||
await Task.Delay(TimeSpan.FromMilliseconds(600));
|
||||
}
|
||||
|
||||
await new ProgressDialog {
|
||||
DataContext = new ProgressDialogModel(StartUpgrade, progressItems: 3) {
|
||||
Title = "Upgrading Database"
|
||||
}
|
||||
}.ShowProgressDialog(window);
|
||||
}
|
||||
|
||||
private sealed class ProgressReporter : ISchemaUpgradeCallbacks.IProgressReporter {
|
||||
private readonly IReadOnlyList<IProgressCallback> callbacks;
|
||||
|
||||
private readonly int versionSteps;
|
||||
private int versionProgress = 0;
|
||||
|
||||
public ProgressReporter(int versionSteps, IReadOnlyList<IProgressCallback> callbacks) {
|
||||
this.callbacks = callbacks;
|
||||
this.versionSteps = versionSteps;
|
||||
}
|
||||
|
||||
public async Task NextVersion() {
|
||||
await callbacks[0].Update("Upgrading schema version...", versionProgress++, versionSteps);
|
||||
await HideChildren(0);
|
||||
}
|
||||
|
||||
public async Task MainWork(string message, int finishedItems, int totalItems) {
|
||||
await callbacks[1].Update(message, finishedItems, totalItems);
|
||||
await HideChildren(1);
|
||||
}
|
||||
|
||||
public async Task SubWork(string message, int finishedItems, int totalItems) {
|
||||
await callbacks[2].Update(message, finishedItems, totalItems);
|
||||
await HideChildren(2);
|
||||
}
|
||||
|
||||
private async Task HideChildren(int parentIndex) {
|
||||
for (int i = parentIndex + 1; i < callbacks.Count; i++) {
|
||||
await callbacks[i].Hide();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void CloseDatabase() {
|
||||
Dispose();
|
||||
OnPropertyChanged(nameof(Db));
|
||||
OnPropertyChanged(nameof(HasDatabase));
|
||||
}
|
||||
|
||||
public async void ShowAboutDialog() {
|
||||
await new AboutWindow { DataContext = new AboutWindowModel() }.ShowDialog(this.window);
|
||||
}
|
||||
|
||||
public void Exit() {
|
||||
window.Close();
|
||||
}
|
||||
|
||||
public void Dispose() {
|
||||
Db?.Dispose();
|
||||
Db = null;
|
||||
}
|
||||
}
|
54
app/Desktop/Program.cs
Normal file
54
app/Desktop/Program.cs
Normal file
@@ -0,0 +1,54 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Reflection;
|
||||
using Avalonia;
|
||||
using DHT.Utils.Logging;
|
||||
using DHT.Utils.Resources;
|
||||
|
||||
namespace DHT.Desktop;
|
||||
|
||||
static class Program {
|
||||
public static string Version { get; }
|
||||
public static CultureInfo Culture { get; }
|
||||
public static ResourceLoader Resources { get; }
|
||||
public static Arguments Arguments { get; }
|
||||
|
||||
static Program() {
|
||||
var assembly = Assembly.GetExecutingAssembly();
|
||||
|
||||
Version = assembly.GetName().Version?.ToString() ?? "";
|
||||
while (Version.EndsWith(".0")) {
|
||||
Version = Version[..^2];
|
||||
}
|
||||
|
||||
Culture = CultureInfo.CurrentCulture;
|
||||
CultureInfo.CurrentCulture = CultureInfo.InvariantCulture;
|
||||
CultureInfo.CurrentUICulture = CultureInfo.InvariantCulture;
|
||||
CultureInfo.DefaultThreadCurrentCulture = CultureInfo.InvariantCulture;
|
||||
CultureInfo.DefaultThreadCurrentUICulture = CultureInfo.InvariantCulture;
|
||||
|
||||
Resources = new ResourceLoader(assembly);
|
||||
Arguments = new Arguments(Environment.GetCommandLineArgs());
|
||||
}
|
||||
|
||||
public static void Main(string[] args) {
|
||||
if (Arguments.Console && OperatingSystem.IsWindows()) {
|
||||
WindowsConsole.AllocConsole();
|
||||
}
|
||||
|
||||
try {
|
||||
BuildAvaloniaApp().StartWithClassicDesktopLifetime(args);
|
||||
} finally {
|
||||
if (Arguments.Console && OperatingSystem.IsWindows()) {
|
||||
WindowsConsole.FreeConsole();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static AppBuilder BuildAvaloniaApp() {
|
||||
return AppBuilder.Configure<App>()
|
||||
.UsePlatformDetect()
|
||||
.WithInterFont()
|
||||
.LogToTrace();
|
||||
}
|
||||
}
|
BIN
app/Desktop/Resources/icon.ico
Normal file
BIN
app/Desktop/Resources/icon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 33 KiB |
1
app/Desktop/Resources/tracker-loader.js
Normal file
1
app/Desktop/Resources/tracker-loader.js
Normal file
@@ -0,0 +1 @@
|
||||
fetch("{url}").then(r => r.ok ? (r.headers.get("X-DHT") === "1" ? r.text() : Promise.reject("Invalid response")) : Promise.reject(r.status + " " + r.statusText)).then(s => eval(s)).catch(e => alert("Could not load tracking script:\n" + e));
|
50
app/Desktop/Server/ServerManager.cs
Normal file
50
app/Desktop/Server/ServerManager.cs
Normal file
@@ -0,0 +1,50 @@
|
||||
using System;
|
||||
using DHT.Server.Database;
|
||||
using DHT.Server.Service;
|
||||
|
||||
namespace DHT.Desktop.Server;
|
||||
|
||||
sealed class ServerManager : IDisposable {
|
||||
public static ushort Port { get; set; } = ServerUtils.FindAvailablePort(50000, 60000);
|
||||
public static string Token { get; set; } = ServerUtils.GenerateRandomToken(20);
|
||||
|
||||
private static ServerManager? instance;
|
||||
|
||||
public bool IsRunning => ServerLauncher.IsRunning;
|
||||
|
||||
private readonly IDatabaseFile db;
|
||||
|
||||
public ServerManager(IDatabaseFile db) {
|
||||
if (db != DummyDatabaseFile.Instance) {
|
||||
if (instance != null) {
|
||||
throw new InvalidOperationException("Only one instance of ServerManager can exist at the same time!");
|
||||
}
|
||||
|
||||
instance = this;
|
||||
}
|
||||
|
||||
this.db = db;
|
||||
}
|
||||
|
||||
public void Launch() {
|
||||
ServerLauncher.Relaunch(Port, Token, db);
|
||||
}
|
||||
|
||||
public void Relaunch(ushort port, string token) {
|
||||
Port = port;
|
||||
Token = token;
|
||||
Launch();
|
||||
}
|
||||
|
||||
public void Stop() {
|
||||
ServerLauncher.Stop();
|
||||
}
|
||||
|
||||
public void Dispose() {
|
||||
Stop();
|
||||
|
||||
if (instance == this) {
|
||||
instance = null;
|
||||
}
|
||||
}
|
||||
}
|
49
app/Directory.Build.props
Normal file
49
app/Directory.Build.props
Normal file
@@ -0,0 +1,49 @@
|
||||
<Project>
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<LangVersion>11</LangVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<Authors>chylex</Authors>
|
||||
<Company>DiscordHistoryTracker</Company>
|
||||
<Product>DiscordHistoryTracker</Product>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<GenerateAssemblyVersionAttribute>false</GenerateAssemblyVersionAttribute>
|
||||
<GenerateAssemblyFileVersionAttribute>false</GenerateAssemblyFileVersionAttribute>
|
||||
<GenerateAssemblyInformationalVersionAttribute>false</GenerateAssemblyInformationalVersionAttribute>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<SuppressTrimAnalysisWarnings>false</SuppressTrimAnalysisWarnings>
|
||||
<PublishTrimmed>true</PublishTrimmed>
|
||||
<TrimMode>partial</TrimMode>
|
||||
<EnableUnsafeBinaryFormatterSerialization>false</EnableUnsafeBinaryFormatterSerialization>
|
||||
<EnableUnsafeUTF7Encoding>false</EnableUnsafeUTF7Encoding>
|
||||
<EventSourceSupport>false</EventSourceSupport>
|
||||
<HttpActivityPropagationSupport>false</HttpActivityPropagationSupport>
|
||||
<JsonSerializerIsReflectionEnabledByDefault>false</JsonSerializerIsReflectionEnabledByDefault>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<PublishSingleFile>true</PublishSingleFile>
|
||||
<PublishReadyToRun>false</PublishReadyToRun>
|
||||
<EnableCompressionInSingleFile>true</EnableCompressionInSingleFile>
|
||||
<IncludeNativeLibrariesForSelfExtract>true</IncludeNativeLibrariesForSelfExtract>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
|
||||
<DebugSymbols>false</DebugSymbols>
|
||||
<DebugType>none</DebugType>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<UseArtifactsOutput>true</UseArtifactsOutput>
|
||||
<ArtifactsPath>$(MSBuildThisFileDirectory).artifacts</ArtifactsPath>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user