mirror of
https://github.com/chylex/Discord-History-Tracker.git
synced 2025-08-17 19:31:42 +02:00
Compare commits
97 Commits
master-bro
...
v35
Author | SHA1 | Date | |
---|---|---|---|
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": []
|
||||
}
|
||||
]
|
||||
}
|
40
README.md
40
README.md
@@ -1,48 +1,32 @@
|
||||
# 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.
|
||||
|
||||
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**
|
||||
Folder organization:
|
||||
* `app/` contains a Visual Studio solution for the desktop app
|
||||
* `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
|
||||
* `web/` contains source code of the [official website](https://dht.chylex.com), which can be used as a template when making your own website
|
||||
|
||||
To start editing source code for the desktop app, 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, you will need [Python 3](https://www.python.org/downloads), which is used by the build process to launch `app/Resources/minify.py` script.
|
||||
|
||||
You can tweak the build process using the following flags:
|
||||
* `python build.py --nominify` to disable [minification](#Minification)
|
||||
When creating `Release` builds on systems other than 64-bit Windows, you will also need [Node + npm](https://nodejs.org/en), and [uglify-js](https://www.npmjs.com/package/uglify-js) installed globally (`npm install uglify-js -g`). On 64-bit Windows, both Node and uglify-js are already included in the `lib/` folder for convenience.
|
||||
|
||||
### Minification
|
||||
|
||||
The build process automatically minifies JS using `UglifyJS@3`, and CSS using a custom minifier.
|
||||
|
||||
* 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
|
||||
To create `Release` builds ready for distribution, run the `app/build.bat` script on Windows, or `app/build.sh` script on other operating systems. This will create self-contained executables for each major operating system, and a portable version that works on all other systems but requires .NET 5 to be installed. All builds are placed in the `app/bin` folder.
|
||||
|
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
|
25
app/.idea/.idea.DiscordHistoryTracker/.idea/avalonia.xml
generated
Normal file
25
app/.idea/.idea.DiscordHistoryTracker/.idea/avalonia.xml
generated
Normal file
@@ -0,0 +1,25 @@
|
||||
<?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/Main/AboutWindow.axaml" value="Desktop/Desktop.csproj" />
|
||||
<entry key="Desktop/Main/Controls/FilterPanel.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/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>
|
448
app/.idea/.idea.DiscordHistoryTracker/.idea/codeStyles/Project.xml
generated
Normal file
448
app/.idea/.idea.DiscordHistoryTracker/.idea/codeStyles/Project.xml
generated
Normal file
@@ -0,0 +1,448 @@
|
||||
<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>
|
||||
<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$/Desktop/bin/Debug/net5.0/DiscordHistoryTracker.exe" />
|
||||
<option name="PROGRAM_PARAMETERS" value="" />
|
||||
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/Desktop/bin/Debug/net5.0" />
|
||||
<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="net5.0" />
|
||||
<method v="2">
|
||||
<option name="Build" />
|
||||
</method>
|
||||
</configuration>
|
||||
</component>
|
23
app/.idea/.idea.DiscordHistoryTracker/.idea/runConfigurations/Minify.xml
generated
Normal file
23
app/.idea/.idea.DiscordHistoryTracker/.idea/runConfigurations/Minify.xml
generated
Normal file
@@ -0,0 +1,23 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="Minify" type="PythonConfigurationType" factoryName="Python">
|
||||
<module name="rider.module" />
|
||||
<option name="INTERPRETER_OPTIONS" value="" />
|
||||
<option name="PARENT_ENVS" value="true" />
|
||||
<envs>
|
||||
<env name="PYTHONUNBUFFERED" value="1" />
|
||||
</envs>
|
||||
<option name="SDK_HOME" value="" />
|
||||
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$" />
|
||||
<option name="IS_MODULE_SDK" value="false" />
|
||||
<option name="ADD_CONTENT_ROOTS" value="false" />
|
||||
<option name="ADD_SOURCE_ROOTS" value="false" />
|
||||
<option name="SCRIPT_NAME" value="$PROJECT_DIR$/minify.py" />
|
||||
<option name="PARAMETERS" value="" />
|
||||
<option name="SHOW_COMMAND_LINE" value="false" />
|
||||
<option name="EMULATE_TERMINAL" value="false" />
|
||||
<option name="MODULE_MODE" value="false" />
|
||||
<option name="REDIRECT_INPUT" value="false" />
|
||||
<option name="INPUT_FILE" value="" />
|
||||
<method v="2" />
|
||||
</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>
|
167
app/Desktop/App.axaml
Normal file
167
app/Desktop/App.axaml
Normal file
@@ -0,0 +1,167 @@
|
||||
<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">
|
||||
|
||||
<Application.Styles>
|
||||
|
||||
<FluentTheme Mode="Light" />
|
||||
|
||||
<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:disabled"><!-- TODO bug in Avalonia (https://github.com/AvaloniaUI/Avalonia/pull/7792) -->
|
||||
<Setter Property="Foreground" Value="{DynamicResource TextControlForegroundDisabled}" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="Expander /template/ ToggleButton#ExpanderHeader">
|
||||
<Setter Property="HorizontalContentAlignment" Value="Left" />
|
||||
<Setter Property="Template">
|
||||
<ControlTemplate>
|
||||
<Border x:Name="ToggleButtonBackground">
|
||||
<Grid ColumnDefinitions="Auto,*" RowDefinitions="35">
|
||||
<Border x:Name="ExpandCollapseChevronBorder"
|
||||
Grid.Column="0"
|
||||
Width="35"
|
||||
Height="35"
|
||||
Margin="2,0"
|
||||
RenderTransformOrigin="50%,50%">
|
||||
<Path x:Name="ExpandCollapseChevron"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
RenderTransformOrigin="50%,50%"
|
||||
Stretch="None"
|
||||
Stroke="{DynamicResource ExpanderChevronForeground}"
|
||||
StrokeThickness="1" />
|
||||
<Border.RenderTransform>
|
||||
<RotateTransform />
|
||||
</Border.RenderTransform>
|
||||
</Border>
|
||||
<ContentPresenter x:Name="PART_ContentPresenter"
|
||||
Grid.Column="1"
|
||||
Margin="0"
|
||||
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
|
||||
VerticalContentAlignment="Center"
|
||||
Background="Transparent"
|
||||
BorderBrush="Transparent"
|
||||
BorderThickness="0"
|
||||
Content="{TemplateBinding Content}"
|
||||
ContentTemplate="{TemplateBinding ContentTemplate}"
|
||||
TextBlock.Foreground="{DynamicResource ExpanderForeground}" />
|
||||
</Grid>
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
</Setter>
|
||||
</Style>
|
||||
<Style Selector="Expander:expanded /template/ ToggleButton#ExpanderHeader /template/ Border#ExpandCollapseChevronBorder">
|
||||
<Style.Animations>
|
||||
<Animation FillMode="Both" Duration="0:0:0.0625">
|
||||
<KeyFrame Cue="0%">
|
||||
<Setter Property="RotateTransform.Angle" Value="180" />
|
||||
</KeyFrame>
|
||||
<KeyFrame Cue="100%">
|
||||
<Setter Property="RotateTransform.Angle" Value="180" />
|
||||
</KeyFrame>
|
||||
</Animation>
|
||||
</Style.Animations>
|
||||
</Style>
|
||||
<Style Selector="Expander:not(:expanded) /template/ ToggleButton#ExpanderHeader /template/ Border#ExpandCollapseChevronBorder">
|
||||
<Style.Animations>
|
||||
<Animation FillMode="Both" Duration="0:0:0.0625">
|
||||
<KeyFrame Cue="0%">
|
||||
<Setter Property="RotateTransform.Angle" Value="0" />
|
||||
</KeyFrame>
|
||||
<KeyFrame Cue="100%">
|
||||
<Setter Property="RotateTransform.Angle" Value="0" />
|
||||
</KeyFrame>
|
||||
</Animation>
|
||||
</Style.Animations>
|
||||
</Style>
|
||||
|
||||
</Application.Styles>
|
||||
|
||||
<Application.Resources>
|
||||
|
||||
<common:NumberValueConverter x:Key="NumberValueConverter" />
|
||||
|
||||
<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">15</Thickness>
|
||||
<SolidColorBrush x:Key="ExpanderBorderBrush" Color="#697DAB" />
|
||||
<SolidColorBrush x:Key="ExpanderBackground" Color="#697DAB" />
|
||||
<SolidColorBrush x:Key="ExpanderForeground" Color="#FFFFFF" />
|
||||
<SolidColorBrush x:Key="ExpanderChevronForeground" Color="#FFFFFF" />
|
||||
<SolidColorBrush x:Key="ExpanderDropDownBorderBrush" Color="#697DAB" />
|
||||
<SolidColorBrush x:Key="ExpanderDropDownBackground" 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(new Arguments(desktop.Args));
|
||||
}
|
||||
|
||||
base.OnFrameworkInitializationCompleted();
|
||||
}
|
||||
}
|
||||
}
|
65
app/Desktop/Arguments.cs
Normal file
65
app/Desktop/Arguments.cs
Normal file
@@ -0,0 +1,65 @@
|
||||
using System;
|
||||
using DHT.Utils.Logging;
|
||||
|
||||
namespace DHT.Desktop {
|
||||
sealed class Arguments {
|
||||
private static readonly Log Log = Log.ForType<Arguments>();
|
||||
|
||||
public static Arguments Empty => new(Array.Empty<string>());
|
||||
|
||||
public string? DatabaseFile { get; }
|
||||
public ushort? ServerPort { get; }
|
||||
public string? ServerToken { get; }
|
||||
|
||||
public Arguments(string[] args) {
|
||||
for (int i = 0; i < args.Length; i++) {
|
||||
string key = args[i];
|
||||
|
||||
switch (key) {
|
||||
case "-debug":
|
||||
Log.IsDebugEnabled = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
string value;
|
||||
|
||||
if (i == 0 && !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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
66
app/Desktop/Common/DatabaseGui.cs
Normal file
66
app/Desktop/Common/DatabaseGui.cs
Normal file
@@ -0,0 +1,66 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using Avalonia.Controls;
|
||||
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 List<FileDialogFilter> DatabaseFileDialogFilter = new() {
|
||||
new FileDialogFilter {
|
||||
Name = "Discord History Tracker Database",
|
||||
Extensions = { "dht" }
|
||||
}
|
||||
};
|
||||
|
||||
public static OpenFileDialog NewOpenDatabaseFileDialog() {
|
||||
return new OpenFileDialog {
|
||||
Title = "Open Database File",
|
||||
InitialFileName = DatabaseFileInitialName,
|
||||
Filters = DatabaseFileDialogFilter
|
||||
};
|
||||
}
|
||||
|
||||
public static SaveFileDialog NewOpenOrCreateDatabaseFileDialog() {
|
||||
return new SaveFileDialog {
|
||||
Title = "Open or Create Database File",
|
||||
InitialFileName = DatabaseFileInitialName,
|
||||
Filters = DatabaseFileDialogFilter
|
||||
};
|
||||
}
|
||||
|
||||
public static async Task<IDatabaseFile?> TryOpenOrCreateDatabaseFromPath(string path, Window window, Func<Task<bool>> checkCanUpgradeDatabase) {
|
||||
IDatabaseFile? file = null;
|
||||
|
||||
try {
|
||||
file = await SqliteDatabaseFile.OpenOrCreate(path, checkCanUpgradeDatabase);
|
||||
} 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");
|
||||
}
|
||||
}
|
||||
}
|
72
app/Desktop/Desktop.csproj
Normal file
72
app/Desktop/Desktop.csproj
Normal file
@@ -0,0 +1,72 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<AssemblyName>DiscordHistoryTracker</AssemblyName>
|
||||
<RootNamespace>DHT.Desktop</RootNamespace>
|
||||
<PackageId>DiscordHistoryTracker</PackageId>
|
||||
<Authors>chylex</Authors>
|
||||
<Company>DiscordHistoryTracker</Company>
|
||||
<Product>DiscordHistoryTracker</Product>
|
||||
<ApplicationIcon>./Resources/icon.ico</ApplicationIcon>
|
||||
<CheckForOverflowUnderflow>true</CheckForOverflowUnderflow>
|
||||
<SatelliteResourceLanguages>en</SatelliteResourceLanguages>
|
||||
<GenerateAssemblyVersionAttribute>false</GenerateAssemblyVersionAttribute>
|
||||
<GenerateAssemblyFileVersionAttribute>false</GenerateAssemblyFileVersionAttribute>
|
||||
<GenerateAssemblyInformationalVersionAttribute>false</GenerateAssemblyInformationalVersionAttribute>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>none</DebugType>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Avalonia" Version="0.10.13" />
|
||||
<PackageReference Include="Avalonia.Desktop" Version="0.10.13" />
|
||||
<PackageReference Include="Avalonia.Diagnostics" Version="0.10.13" Condition=" '$(Configuration)' == 'Debug' " />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Server\Server.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="..\Version.cs" Link="Version.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AvaloniaResource Include="Resources/icon.ico" />
|
||||
<EmbeddedResource Include="../Resources/Tracker/bootstrap.js">
|
||||
<LogicalName>Tracker\%(RecursiveDir)%(Filename)%(Extension)</LogicalName>
|
||||
<Link>Resources/Tracker/%(RecursiveDir)%(Filename)%(Extension)</Link>
|
||||
<Visible>false</Visible>
|
||||
</EmbeddedResource>
|
||||
<EmbeddedResource Include="../Resources/Tracker/scripts/**" Condition=" '$(Configuration)' == 'Debug' ">
|
||||
<LogicalName>Tracker\scripts\%(RecursiveDir)%(Filename)%(Extension)</LogicalName>
|
||||
<Link>Resources/Tracker/scripts/%(RecursiveDir)%(Filename)%(Extension)</Link>
|
||||
<Visible>false</Visible>
|
||||
</EmbeddedResource>
|
||||
<EmbeddedResource Include="../Resources/Tracker/styles/**">
|
||||
<LogicalName>Tracker\styles\%(RecursiveDir)%(Filename)%(Extension)</LogicalName>
|
||||
<Link>Resources/Tracker/styles/%(RecursiveDir)%(Filename)%(Extension)</Link>
|
||||
<Visible>false</Visible>
|
||||
</EmbeddedResource>
|
||||
<EmbeddedResource Include="../Resources/Viewer/**">
|
||||
<LogicalName>Viewer\%(RecursiveDir)%(Filename)%(Extension)</LogicalName>
|
||||
<Link>Resources/Viewer/%(RecursiveDir)%(Filename)%(Extension)</Link>
|
||||
<Visible>false</Visible>
|
||||
</EmbeddedResource>
|
||||
</ItemGroup>
|
||||
<Target Name="MinifyResources" BeforeTargets="PrepareForBuild" Condition=" '$(Configuration)' == 'Release' ">
|
||||
<PropertyGroup>
|
||||
<MinifiedResourceDir>$(ProjectDir)bin/.res/scripts</MinifiedResourceDir>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<UpToDateCheckInput Include="$(ProjectDir)../Resources/Tracker/scripts/**" Visible="false" />
|
||||
<EmbeddedResource Include="$(MinifiedResourceDir)/discord.js" LogicalName="Tracker\scripts\discord.js" Visible="false" />
|
||||
<EmbeddedResource Include="$(MinifiedResourceDir)/dom.js" LogicalName="Tracker\scripts\dom.js" Visible="false" />
|
||||
<EmbeddedResource Include="$(MinifiedResourceDir)/gui.js" LogicalName="Tracker\scripts\gui.js" Visible="false" />
|
||||
<EmbeddedResource Include="$(MinifiedResourceDir)/settings.js" LogicalName="Tracker\scripts\settings.js" Visible="false" />
|
||||
<EmbeddedResource Include="$(MinifiedResourceDir)/state.js" LogicalName="Tracker\scripts\state.js" Visible="false" />
|
||||
</ItemGroup>
|
||||
<RemoveDir Directories="$(ProjectDir)bin/.res/scripts" />
|
||||
<Exec Command="python $(ProjectDir)../Resources/minify.py" WorkingDirectory="$(ProjectDir)../Resources" IgnoreExitCode="false" />
|
||||
</Target>
|
||||
</Project>
|
61
app/Desktop/Dialogs/CheckBox/CheckBoxDialog.axaml
Normal file
61
app/Desktop/Dialogs/CheckBox/CheckBoxDialog.axaml
Normal file
@@ -0,0 +1,61 @@
|
||||
<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"
|
||||
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">
|
||||
<ItemsControl Items="{Binding Items}">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<CheckBox IsChecked="{Binding Checked}">
|
||||
<Label>
|
||||
<TextBlock Text="{Binding Title}" TextWrapping="Wrap" />
|
||||
</Label>
|
||||
</CheckBox>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</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>
|
31
app/Desktop/Dialogs/CheckBox/CheckBoxDialog.axaml.cs
Normal file
31
app/Desktop/Dialogs/CheckBox/CheckBoxDialog.axaml.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Interactivity;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using DHT.Desktop.Dialogs.Message;
|
||||
|
||||
namespace DHT.Desktop.Dialogs.CheckBox {
|
||||
[SuppressMessage("ReSharper", "MemberCanBeInternal")]
|
||||
public sealed class CheckBoxDialog : Window {
|
||||
public CheckBoxDialog() {
|
||||
InitializeComponent();
|
||||
#if DEBUG
|
||||
this.AttachDevTools();
|
||||
#endif
|
||||
}
|
||||
|
||||
private void InitializeComponent() {
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
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.")
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
42
app/Desktop/Dialogs/Message/MessageDialog.axaml
Normal file
42
app/Desktop/Dialogs/Message/MessageDialog.axaml
Normal file
@@ -0,0 +1,42 @@
|
||||
<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"
|
||||
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>
|
37
app/Desktop/Dialogs/Message/MessageDialog.axaml.cs
Normal file
37
app/Desktop/Dialogs/Message/MessageDialog.axaml.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Interactivity;
|
||||
using Avalonia.Markup.Xaml;
|
||||
|
||||
namespace DHT.Desktop.Dialogs.Message {
|
||||
[SuppressMessage("ReSharper", "MemberCanBeInternal")]
|
||||
public sealed class MessageDialog : Window {
|
||||
public MessageDialog() {
|
||||
InitializeComponent();
|
||||
#if DEBUG
|
||||
this.AttachDevTools();
|
||||
#endif
|
||||
}
|
||||
|
||||
private void InitializeComponent() {
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
7
app/Desktop/Dialogs/Progress/IProgressCallback.cs
Normal file
7
app/Desktop/Dialogs/Progress/IProgressCallback.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace DHT.Desktop.Dialogs.Progress {
|
||||
interface IProgressCallback {
|
||||
Task Update(string message, int finishedItems, int totalItems);
|
||||
}
|
||||
}
|
42
app/Desktop/Dialogs/Progress/ProgressDialog.axaml
Normal file
42
app/Desktop/Dialogs/Progress/ProgressDialog.axaml
Normal file
@@ -0,0 +1,42 @@
|
||||
<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"
|
||||
Title="{Binding Title}"
|
||||
Icon="avares://DiscordHistoryTracker/Resources/icon.ico"
|
||||
Opened="Loaded"
|
||||
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>
|
||||
|
||||
<StackPanel Margin="20">
|
||||
<DockPanel>
|
||||
<TextBlock DockPanel.Dock="Right" Text="{Binding Items}" Classes="items" />
|
||||
<TextBlock DockPanel.Dock="Left" Text="{Binding Message}" />
|
||||
</DockPanel>
|
||||
<ProgressBar Value="{Binding Progress}" />
|
||||
</StackPanel>
|
||||
|
||||
</Window>
|
40
app/Desktop/Dialogs/Progress/ProgressDialog.axaml.cs
Normal file
40
app/Desktop/Dialogs/Progress/ProgressDialog.axaml.cs
Normal file
@@ -0,0 +1,40 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Threading.Tasks;
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
|
||||
namespace DHT.Desktop.Dialogs.Progress {
|
||||
[SuppressMessage("ReSharper", "MemberCanBeInternal")]
|
||||
public sealed class ProgressDialog : Window {
|
||||
private bool isFinished = false;
|
||||
|
||||
public ProgressDialog() {
|
||||
InitializeComponent();
|
||||
#if DEBUG
|
||||
this.AttachDevTools();
|
||||
#endif
|
||||
}
|
||||
|
||||
private void InitializeComponent() {
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
|
||||
public void OnClosing(object? sender, CancelEventArgs e) {
|
||||
e.Cancel = !isFinished;
|
||||
}
|
||||
|
||||
public void Loaded(object? sender, EventArgs e) {
|
||||
if (DataContext is ProgressDialogModel model) {
|
||||
Task.Run(model.StartTask).ContinueWith(OnFinished, TaskScheduler.FromCurrentSynchronizationContext());
|
||||
}
|
||||
}
|
||||
|
||||
private void OnFinished(Task task) {
|
||||
isFinished = true;
|
||||
Close();
|
||||
}
|
||||
}
|
||||
}
|
65
app/Desktop/Dialogs/Progress/ProgressDialogModel.cs
Normal file
65
app/Desktop/Dialogs/Progress/ProgressDialogModel.cs
Normal file
@@ -0,0 +1,65 @@
|
||||
using System;
|
||||
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; } = "";
|
||||
|
||||
private string message = "";
|
||||
|
||||
public string Message {
|
||||
get => message;
|
||||
private set => Change(ref message, value);
|
||||
}
|
||||
|
||||
private string items = "";
|
||||
|
||||
public string Items {
|
||||
get => items;
|
||||
private set => Change(ref items, value);
|
||||
}
|
||||
|
||||
private int progress = 0;
|
||||
|
||||
public int Progress {
|
||||
get => progress;
|
||||
private set => Change(ref progress, value);
|
||||
}
|
||||
|
||||
private readonly TaskRunner? task;
|
||||
|
||||
[Obsolete("Designer")]
|
||||
public ProgressDialogModel() {}
|
||||
|
||||
public ProgressDialogModel(TaskRunner task) {
|
||||
this.task = task;
|
||||
}
|
||||
|
||||
internal async Task StartTask() {
|
||||
if (task != null) {
|
||||
await task(new Callback(this));
|
||||
}
|
||||
}
|
||||
|
||||
public delegate Task TaskRunner(IProgressCallback callback);
|
||||
|
||||
private sealed class Callback : IProgressCallback {
|
||||
private readonly ProgressDialogModel model;
|
||||
|
||||
public Callback(ProgressDialogModel model) {
|
||||
this.model = model;
|
||||
}
|
||||
|
||||
async Task IProgressCallback.Update(string message, int finishedItems, int totalItems) {
|
||||
await Dispatcher.UIThread.InvokeAsync(() => {
|
||||
model.Message = message;
|
||||
model.Items = finishedItems.Format() + " / " + totalItems.Format();
|
||||
model.Progress = 100 * finishedItems / totalItems;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
119
app/Desktop/Discord/DiscordAppSettings.cs
Normal file
119
app/Desktop/Discord/DiscordAppSettings.cs
Normal file
@@ -0,0 +1,119 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text.Json;
|
||||
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) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool AreDevToolsEnabled(Dictionary<string, object?> json) {
|
||||
return json.TryGetValue(JsonKeyDevTools, out var value) && value is JsonElement { ValueKind: JsonValueKind.True };
|
||||
}
|
||||
|
||||
public static async Task<SettingsJsonResult> ConfigureDevTools(bool enable) {
|
||||
Dictionary<string, object?> 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<Dictionary<string, object?>> ReadSettingsJson() {
|
||||
await using var stream = new FileStream(JsonFilePath, FileMode.Open, FileAccess.Read, FileShare.Read);
|
||||
return await JsonSerializer.DeserializeAsync<Dictionary<string, object?>?>(stream) ?? throw new JsonException();
|
||||
}
|
||||
|
||||
private static async Task WriteSettingsJson(Dictionary<string, object?> json) {
|
||||
await using var stream = new FileStream(JsonFilePath, FileMode.Truncate, FileAccess.Write, FileShare.None);
|
||||
await JsonSerializer.SerializeAsync(stream, json, new JsonSerializerOptions { WriteIndented = true });
|
||||
}
|
||||
}
|
||||
}
|
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
|
||||
}
|
||||
}
|
67
app/Desktop/Main/AboutWindow.axaml
Normal file
67
app/Desktop/Main/AboutWindow.axaml
Normal file
@@ -0,0 +1,67 @@
|
||||
<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"
|
||||
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 5</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>
|
20
app/Desktop/Main/AboutWindow.axaml.cs
Normal file
20
app/Desktop/Main/AboutWindow.axaml.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
|
||||
namespace DHT.Desktop.Main {
|
||||
[SuppressMessage("ReSharper", "MemberCanBeInternal")]
|
||||
public sealed class AboutWindow : Window {
|
||||
public AboutWindow() {
|
||||
InitializeComponent();
|
||||
#if DEBUG
|
||||
this.AttachDevTools();
|
||||
#endif
|
||||
}
|
||||
|
||||
private void InitializeComponent() {
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
}
|
||||
}
|
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 });
|
||||
}
|
||||
}
|
||||
}
|
60
app/Desktop/Main/Controls/FilterPanel.axaml
Normal file
60
app/Desktop/Main/Controls/FilterPanel.axaml
Normal file
@@ -0,0 +1,60 @@
|
||||
<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.FilterPanel">
|
||||
|
||||
<Design.DataContext>
|
||||
<controls:FilterPanelModel />
|
||||
</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>
|
||||
|
||||
<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>
|
||||
|
||||
</UserControl>
|
34
app/Desktop/Main/Controls/FilterPanel.axaml.cs
Normal file
34
app/Desktop/Main/Controls/FilterPanel.axaml.cs
Normal file
@@ -0,0 +1,34 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
|
||||
namespace DHT.Desktop.Main.Controls {
|
||||
[SuppressMessage("ReSharper", "MemberCanBeInternal")]
|
||||
public sealed class FilterPanel : UserControl {
|
||||
private CalendarDatePicker StartDatePicker => this.FindControl<CalendarDatePicker>("StartDatePicker");
|
||||
private CalendarDatePicker EndDatePicker => this.FindControl<CalendarDatePicker>("EndDatePicker");
|
||||
|
||||
public FilterPanel() {
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void InitializeComponent() {
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
|
||||
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 FilterPanelModel model) {
|
||||
model.StartDate = StartDatePicker.SelectedDate;
|
||||
model.EndDate = EndDatePicker.SelectedDate;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
241
app/Desktop/Main/Controls/FilterPanelModel.cs
Normal file
241
app/Desktop/Main/Controls/FilterPanelModel.cs
Normal file
@@ -0,0 +1,241 @@
|
||||
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;
|
||||
|
||||
namespace DHT.Desktop.Main.Controls {
|
||||
sealed class FilterPanelModel : BaseModel, IDisposable {
|
||||
private static readonly HashSet<string> FilterProperties = new () {
|
||||
nameof(FilterByDate),
|
||||
nameof(StartDate),
|
||||
nameof(EndDate),
|
||||
nameof(FilterByChannel),
|
||||
nameof(IncludedChannels),
|
||||
nameof(FilterByUser),
|
||||
nameof(IncludedUsers)
|
||||
};
|
||||
|
||||
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;
|
||||
|
||||
[Obsolete("Designer")]
|
||||
public FilterPanelModel() : this(null!, DummyDatabaseFile.Instance) {}
|
||||
|
||||
public FilterPanelModel(Window window, IDatabaseFile db) {
|
||||
this.window = window;
|
||||
this.db = db;
|
||||
|
||||
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)) {
|
||||
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.TotalChannels)) {
|
||||
UpdateChannelFilterLabel();
|
||||
}
|
||||
else if (e.PropertyName == nameof(DatabaseStatistics.TotalUsers)) {
|
||||
UpdateUserFilterLabel();
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
48
app/Desktop/Main/Controls/ServerConfigurationPanel.axaml
Normal file
48
app/Desktop/Main/Controls/ServerConfigurationPanel.axaml
Normal file
@@ -0,0 +1,48 @@
|
||||
<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">
|
||||
|
||||
<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 and apply 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>
|
16
app/Desktop/Main/Controls/ServerConfigurationPanel.axaml.cs
Normal file
16
app/Desktop/Main/Controls/ServerConfigurationPanel.axaml.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
|
||||
namespace DHT.Desktop.Main.Controls {
|
||||
[SuppressMessage("ReSharper", "MemberCanBeInternal")]
|
||||
public sealed class ServerConfigurationPanel : UserControl {
|
||||
public ServerConfigurationPanel() {
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void InitializeComponent() {
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
}
|
||||
}
|
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;
|
||||
}
|
||||
}
|
||||
}
|
61
app/Desktop/Main/Controls/StatusBar.axaml
Normal file
61
app/Desktop/Main/Controls/StatusBar.axaml
Normal file
@@ -0,0 +1,61 @@
|
||||
<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">
|
||||
|
||||
<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>
|
16
app/Desktop/Main/Controls/StatusBar.axaml.cs
Normal file
16
app/Desktop/Main/Controls/StatusBar.axaml.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
|
||||
namespace DHT.Desktop.Main.Controls {
|
||||
[SuppressMessage("ReSharper", "MemberCanBeInternal")]
|
||||
public sealed class StatusBar : UserControl {
|
||||
public StatusBar() {
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void InitializeComponent() {
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
}
|
||||
}
|
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
|
||||
}
|
||||
}
|
||||
}
|
22
app/Desktop/Main/MainWindow.axaml
Normal file
22
app/Desktop/Main/MainWindow.axaml
Normal file
@@ -0,0 +1,22 @@
|
||||
<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"
|
||||
Title="{Binding Title}"
|
||||
Icon="avares://DiscordHistoryTracker/Resources/icon.ico"
|
||||
Width="800" Height="500"
|
||||
MinWidth="500" MinHeight="275"
|
||||
WindowStartupLocation="CenterScreen"
|
||||
Closed="OnClosed">
|
||||
|
||||
<Design.DataContext>
|
||||
<main:MainWindowModel />
|
||||
</Design.DataContext>
|
||||
|
||||
<Panel>
|
||||
<ContentPresenter Content="{Binding CurrentScreen}" />
|
||||
</Panel>
|
||||
</Window>
|
35
app/Desktop/Main/MainWindow.axaml.cs
Normal file
35
app/Desktop/Main/MainWindow.axaml.cs
Normal file
@@ -0,0 +1,35 @@
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace DHT.Desktop.Main {
|
||||
[SuppressMessage("ReSharper", "MemberCanBeInternal")]
|
||||
public sealed class MainWindow : Window {
|
||||
[UsedImplicitly]
|
||||
public MainWindow() {
|
||||
InitializeComponent(Arguments.Empty);
|
||||
}
|
||||
|
||||
internal MainWindow(Arguments args) {
|
||||
InitializeComponent(args);
|
||||
}
|
||||
|
||||
private void InitializeComponent(Arguments args) {
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
DataContext = new MainWindowModel(this, args);
|
||||
|
||||
#if DEBUG
|
||||
this.AttachDevTools();
|
||||
#endif
|
||||
}
|
||||
|
||||
public void OnClosed(object? sender, EventArgs e) {
|
||||
if (DataContext is IDisposable disposable) {
|
||||
disposable.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
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;
|
||||
}
|
||||
}
|
||||
}
|
25
app/Desktop/Main/Pages/AdvancedPage.axaml
Normal file
25
app/Desktop/Main/Pages/AdvancedPage.axaml
Normal file
@@ -0,0 +1,25 @@
|
||||
<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">
|
||||
|
||||
<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>
|
16
app/Desktop/Main/Pages/AdvancedPage.axaml.cs
Normal file
16
app/Desktop/Main/Pages/AdvancedPage.axaml.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
|
||||
namespace DHT.Desktop.Main.Pages {
|
||||
[SuppressMessage("ReSharper", "MemberCanBeInternal")]
|
||||
public sealed class AdvancedPage : UserControl {
|
||||
public AdvancedPage() {
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void InitializeComponent() {
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
}
|
||||
}
|
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.");
|
||||
}
|
||||
}
|
||||
}
|
30
app/Desktop/Main/Pages/DatabasePage.axaml
Normal file
30
app/Desktop/Main/Pages/DatabasePage.axaml
Normal file
@@ -0,0 +1,30 @@
|
||||
<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">
|
||||
|
||||
<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>
|
||||
</WrapPanel>
|
||||
</StackPanel>
|
||||
|
||||
</UserControl>
|
16
app/Desktop/Main/Pages/DatabasePage.axaml.cs
Normal file
16
app/Desktop/Main/Pages/DatabasePage.axaml.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
|
||||
namespace DHT.Desktop.Main.Pages {
|
||||
[SuppressMessage("ReSharper", "MemberCanBeInternal")]
|
||||
public sealed class DatabasePage : UserControl {
|
||||
public DatabasePage() {
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void InitializeComponent() {
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
}
|
||||
}
|
157
app/Desktop/Main/Pages/DatabasePageModel.cs
Normal file
157
app/Desktop/Main/Pages/DatabasePageModel.cs
Normal file
@@ -0,0 +1,157 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
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.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 async void MergeWithDatabase() {
|
||||
var fileDialog = DatabaseGui.NewOpenDatabaseFileDialog();
|
||||
fileDialog.Directory = Path.GetDirectoryName(Db.Path);
|
||||
fileDialog.AllowMultiple = true;
|
||||
|
||||
string[]? paths = await fileDialog.ShowAsync(window);
|
||||
if (paths == null || paths.Length == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
ProgressDialog progressDialog = new ProgressDialog();
|
||||
progressDialog.DataContext = new ProgressDialogModel(async callback => await MergeWithDatabaseFromPaths(Db, paths, progressDialog, callback)) {
|
||||
Title = "Database Merge"
|
||||
};
|
||||
|
||||
await progressDialog.ShowDialog(window);
|
||||
}
|
||||
|
||||
public void CloseDatabase() {
|
||||
DatabaseClosed?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
private static async Task MergeWithDatabaseFromPaths(IDatabaseFile target, string[] paths, ProgressDialog dialog, IProgressCallback callback) {
|
||||
int total = paths.Length;
|
||||
|
||||
DialogResult.YesNo? upgradeResult = null;
|
||||
|
||||
async Task<bool> CheckCanUpgradeDatabase() {
|
||||
upgradeResult ??= total > 1
|
||||
? await DatabaseGui.ShowCanUpgradeMultipleDatabaseDialog(dialog)
|
||||
: await DatabaseGui.ShowCanUpgradeDatabaseDialog(dialog);
|
||||
|
||||
return DialogResult.YesNo.Yes == upgradeResult;
|
||||
}
|
||||
|
||||
var oldStatistics = target.Statistics.Clone();
|
||||
var oldMessageCount = target.CountMessages();
|
||||
|
||||
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, "Database Error", "Database '" + Path.GetFileName(path) + "' no longer exists.");
|
||||
continue;
|
||||
}
|
||||
|
||||
IDatabaseFile? db = await DatabaseGui.TryOpenOrCreateDatabaseFromPath(path, dialog, CheckCanUpgradeDatabase);
|
||||
if (db == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
target.AddFrom(db);
|
||||
} catch (Exception ex) {
|
||||
Log.Error(ex);
|
||||
await Dialog.ShowOk(dialog, "Database Error", "Database '" + Path.GetFileName(path) + "' could not be merged: " + ex.Message);
|
||||
continue;
|
||||
} finally {
|
||||
db.Dispose();
|
||||
}
|
||||
|
||||
++successful;
|
||||
}
|
||||
|
||||
await callback.Update("Done", finished, total);
|
||||
|
||||
if (successful == 0) {
|
||||
await Dialog.ShowOk(dialog, "Database Merge", "Nothing was merged.");
|
||||
return;
|
||||
}
|
||||
|
||||
var newStatistics = target.Statistics;
|
||||
long newServers = newStatistics.TotalServers - oldStatistics.TotalServers;
|
||||
long newChannels = newStatistics.TotalChannels - oldStatistics.TotalChannels;
|
||||
long newMessages = target.CountMessages() - oldMessageCount;
|
||||
|
||||
StringBuilder message = new StringBuilder();
|
||||
message.Append("Processed ");
|
||||
|
||||
if (successful == total) {
|
||||
message.Append(successful.Pluralize("database file"));
|
||||
}
|
||||
else {
|
||||
message.Append(successful.Format()).Append(" out of ").Append(total.Pluralize("database file"));
|
||||
}
|
||||
|
||||
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(newMessages.Pluralize("message"));
|
||||
|
||||
await Dialog.ShowOk(dialog, "Database Merge", message.ToString());
|
||||
}
|
||||
}
|
||||
}
|
45
app/Desktop/Main/Pages/DebugPage.axaml
Normal file
45
app/Desktop/Main/Pages/DebugPage.axaml
Normal file
@@ -0,0 +1,45 @@
|
||||
<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">
|
||||
|
||||
<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>
|
16
app/Desktop/Main/Pages/DebugPage.axaml.cs
Normal file
16
app/Desktop/Main/Pages/DebugPage.axaml.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
|
||||
namespace DHT.Desktop.Main.Pages {
|
||||
[SuppressMessage("ReSharper", "MemberCanBeInternal")]
|
||||
public sealed class DebugPage : UserControl {
|
||||
public DebugPage() {
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void InitializeComponent() {
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
}
|
||||
}
|
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 callback => await GenerateRandomData(channels, users, messages, callback)) {
|
||||
Title = "Generating Random Data"
|
||||
}
|
||||
};
|
||||
|
||||
await progressDialog.ShowDialog(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
|
26
app/Desktop/Main/Pages/TrackingPage.axaml
Normal file
26
app/Desktop/Main/Pages/TrackingPage.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"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="DHT.Desktop.Main.Pages.TrackingPage">
|
||||
|
||||
<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>
|
38
app/Desktop/Main/Pages/TrackingPage.axaml.cs
Normal file
38
app/Desktop/Main/Pages/TrackingPage.axaml.cs
Normal file
@@ -0,0 +1,38 @@
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Threading.Tasks;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Interactivity;
|
||||
using Avalonia.Markup.Xaml;
|
||||
|
||||
namespace DHT.Desktop.Main.Pages {
|
||||
[SuppressMessage("ReSharper", "MemberCanBeInternal")]
|
||||
public sealed class TrackingPage : UserControl {
|
||||
private bool isCopyingScript;
|
||||
|
||||
public TrackingPage() {
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void InitializeComponent() {
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
|
||||
public async void CopyTrackingScriptButton_OnClick(object? sender, RoutedEventArgs e) {
|
||||
if (DataContext is TrackingPageModel model) {
|
||||
var button = this.FindControl<Button>("CopyTrackingScript");
|
||||
var originalText = button.Content;
|
||||
button.MinWidth = button.Bounds.Width;
|
||||
|
||||
if (await model.OnClickCopyTrackingScript() && !isCopyingScript) {
|
||||
isCopyingScript = true;
|
||||
button.Content = "Script Copied!";
|
||||
|
||||
await Task.Delay(TimeSpan.FromSeconds(2));
|
||||
button.Content = originalText;
|
||||
isCopyingScript = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
117
app/Desktop/Main/Pages/TrackingPageModel.cs
Normal file
117
app/Desktop/Main/Pages/TrackingPageModel.cs
Normal file
@@ -0,0 +1,117 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web;
|
||||
using Avalonia;
|
||||
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 bootstrap = await Resources.ReadTextAsync("Tracker/bootstrap.js");
|
||||
string script = bootstrap.Replace("= 0; /*[PORT]*/", "= " + ServerManager.Port + ";")
|
||||
.Replace("/*[TOKEN]*/", HttpUtility.JavaScriptStringEncode(ServerManager.Token))
|
||||
.Replace("/*[IMPORTS]*/", await Resources.ReadJoinedAsync("Tracker/scripts/", '\n'))
|
||||
.Replace("/*[CSS-CONTROLLER]*/", await Resources.ReadTextAsync("Tracker/styles/controller.css"))
|
||||
.Replace("/*[CSS-SETTINGS]*/", await Resources.ReadTextAsync("Tracker/styles/settings.css"));
|
||||
|
||||
var clipboard = Application.Current?.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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
40
app/Desktop/Main/Pages/ViewerPage.axaml
Normal file
40
app/Desktop/Main/Pages/ViewerPage.axaml
Normal file
@@ -0,0 +1,40 @@
|
||||
<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"
|
||||
AttachedToVisualTree="OnAttachedToVisualTree"
|
||||
DetachedFromVisualTree="OnDetachedFromVisualTree">
|
||||
|
||||
<Design.DataContext>
|
||||
<pages:ViewerPageModel />
|
||||
</Design.DataContext>
|
||||
|
||||
<UserControl.Styles>
|
||||
<Style Selector="Expander">
|
||||
<Setter Property="Margin" Value="0 25 0 0" />
|
||||
</Style>
|
||||
</UserControl.Styles>
|
||||
|
||||
<StackPanel Orientation="Vertical">
|
||||
<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>
|
||||
<TextBlock Text="{Binding ExportedMessageText}" Margin="0 20 0 0" />
|
||||
<controls:FilterPanel 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>
|
29
app/Desktop/Main/Pages/ViewerPage.axaml.cs
Normal file
29
app/Desktop/Main/Pages/ViewerPage.axaml.cs
Normal file
@@ -0,0 +1,29 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
|
||||
namespace DHT.Desktop.Main.Pages {
|
||||
[SuppressMessage("ReSharper", "MemberCanBeInternal")]
|
||||
public sealed class ViewerPage : UserControl {
|
||||
public ViewerPage() {
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void InitializeComponent() {
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
|
||||
public void OnAttachedToVisualTree(object? sender, VisualTreeAttachmentEventArgs e) {
|
||||
if (DataContext is ViewerPageModel model) {
|
||||
model.SetPageVisible(true);
|
||||
}
|
||||
}
|
||||
|
||||
public void OnDetachedFromVisualTree(object? sender, VisualTreeAttachmentEventArgs e) {
|
||||
if (DataContext is ViewerPageModel model) {
|
||||
model.SetPageVisible(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
144
app/Desktop/Main/Pages/ViewerPageModel.cs
Normal file
144
app/Desktop/Main/Pages/ViewerPageModel.cs
Normal file
@@ -0,0 +1,144 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web;
|
||||
using Avalonia.Controls;
|
||||
using DHT.Desktop.Common;
|
||||
using DHT.Desktop.Dialogs.Message;
|
||||
using DHT.Desktop.Main.Controls;
|
||||
using DHT.Server.Data.Filters;
|
||||
using DHT.Server.Database;
|
||||
using DHT.Server.Database.Export;
|
||||
using DHT.Utils.Models;
|
||||
using static DHT.Desktop.Program;
|
||||
|
||||
namespace DHT.Desktop.Main.Pages {
|
||||
sealed class ViewerPageModel : BaseModel, IDisposable {
|
||||
public string ExportedMessageText { get; private set; } = "";
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
private FilterPanelModel FilterModel { get; }
|
||||
|
||||
private readonly Window window;
|
||||
private readonly IDatabaseFile db;
|
||||
|
||||
private bool isPageVisible = false;
|
||||
|
||||
[Obsolete("Designer")]
|
||||
public ViewerPageModel() : this(null!, DummyDatabaseFile.Instance) {}
|
||||
|
||||
public ViewerPageModel(Window window, IDatabaseFile db) {
|
||||
this.window = window;
|
||||
this.db = db;
|
||||
|
||||
FilterModel = new FilterPanelModel(window, db);
|
||||
FilterModel.FilterPropertyChanged += OnFilterPropertyChanged;
|
||||
db.Statistics.PropertyChanged += OnDbStatisticsChanged;
|
||||
}
|
||||
|
||||
public void Dispose() {
|
||||
db.Statistics.PropertyChanged -= OnDbStatisticsChanged;
|
||||
FilterModel.Dispose();
|
||||
}
|
||||
|
||||
public void SetPageVisible(bool isPageVisible) {
|
||||
this.isPageVisible = isPageVisible;
|
||||
if (isPageVisible) {
|
||||
UpdateStatistics();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnFilterPropertyChanged(object? sender, PropertyChangedEventArgs e) {
|
||||
UpdateStatistics();
|
||||
HasFilters = FilterModel.HasAnyFilters;
|
||||
}
|
||||
|
||||
private void OnDbStatisticsChanged(object? sender, PropertyChangedEventArgs e) {
|
||||
if (isPageVisible && e.PropertyName == nameof(DatabaseStatistics.TotalMessages)) {
|
||||
UpdateStatistics();
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateStatistics() {
|
||||
var filter = FilterModel.CreateFilter();
|
||||
var allMessagesCount = db.Statistics.TotalMessages?.Format() ?? "?";
|
||||
var filteredMessagesCount = filter.IsEmpty ? allMessagesCount : db.CountMessages(filter).Format();
|
||||
|
||||
ExportedMessageText = "Will export " + filteredMessagesCount + " out of " + allMessagesCount + " message(s).";
|
||||
OnPropertyChanged(nameof(ExportedMessageText));
|
||||
}
|
||||
|
||||
private async Task<string> GenerateViewerContents() {
|
||||
string json = ViewerJsonExport.Generate(db, FilterModel.CreateFilter());
|
||||
|
||||
string index = await Resources.ReadTextAsync("Viewer/index.html");
|
||||
string viewer = index.Replace("/*[JS]*/", await Resources.ReadJoinedAsync("Viewer/scripts/", '\n'))
|
||||
.Replace("/*[CSS]*/", await Resources.ReadJoinedAsync("Viewer/styles/", '\n'))
|
||||
.Replace("/*[ARCHIVE]*/", HttpUtility.JavaScriptStringEncode(json));
|
||||
return viewer;
|
||||
}
|
||||
|
||||
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");
|
||||
}
|
||||
|
||||
Directory.CreateDirectory(rootPath);
|
||||
await File.WriteAllTextAsync(fullPath, await GenerateViewerContents());
|
||||
|
||||
Process.Start(new ProcessStartInfo(fullPath) { UseShellExecute = true });
|
||||
}
|
||||
|
||||
public async void OnClickSaveViewer() {
|
||||
var dialog = new SaveFileDialog {
|
||||
Title = "Save Viewer",
|
||||
InitialFileName = "archive.html",
|
||||
Directory = Path.GetDirectoryName(db.Path),
|
||||
Filters = new List<FileDialogFilter> {
|
||||
new() {
|
||||
Name = "Discord History Viewer",
|
||||
Extensions = { "html" }
|
||||
}
|
||||
}
|
||||
}.ShowAsync(window);
|
||||
|
||||
string? path = await dialog;
|
||||
if (!string.IsNullOrEmpty(path)) {
|
||||
await File.WriteAllTextAsync(path, await GenerateViewerContents());
|
||||
}
|
||||
}
|
||||
|
||||
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, MessageFilterRemovalMode.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, MessageFilterRemovalMode.RemoveMatching);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
114
app/Desktop/Main/Screens/MainContentScreen.axaml
Normal file
114
app/Desktop/Main/Screens/MainContentScreen.axaml
Normal file
@@ -0,0 +1,114 @@
|
||||
<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">
|
||||
|
||||
<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">Advanced</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" />
|
||||
</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="TabViewer" Header="Viewer" Grid.Row="2">
|
||||
<ScrollViewer>
|
||||
<ContentPresenter Content="{Binding ViewerPage}" Classes="page" />
|
||||
</ScrollViewer>
|
||||
</TabItem>
|
||||
<TabItem x:Name="TabAdvanced" Header="Advanced" Grid.Row="4">
|
||||
<ScrollViewer>
|
||||
<ContentPresenter Content="{Binding AdvancedPage}" Classes="page" />
|
||||
</ScrollViewer>
|
||||
</TabItem>
|
||||
<TabItem x:Name="TabDebug" Header="Debug" Grid.Row="5" IsVisible="{Binding HasDebugPage}">
|
||||
<ScrollViewer>
|
||||
<ContentPresenter Content="{Binding DebugPage}" Classes="page" />
|
||||
</ScrollViewer>
|
||||
</TabItem>
|
||||
</TabControl>
|
||||
</DockPanel>
|
||||
|
||||
</UserControl>
|
16
app/Desktop/Main/Screens/MainContentScreen.axaml.cs
Normal file
16
app/Desktop/Main/Screens/MainContentScreen.axaml.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
|
||||
namespace DHT.Desktop.Main.Screens {
|
||||
[SuppressMessage("ReSharper", "MemberCanBeInternal")]
|
||||
public sealed class MainContentScreen : UserControl {
|
||||
public MainContentScreen() {
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void InitializeComponent() {
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
}
|
||||
}
|
112
app/Desktop/Main/Screens/MainContentScreenModel.cs
Normal file
112
app/Desktop/Main/Screens/MainContentScreenModel.cs
Normal file
@@ -0,0 +1,112 @@
|
||||
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 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 };
|
||||
|
||||
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;
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
40
app/Desktop/Main/Screens/WelcomeScreen.axaml
Normal file
40
app/Desktop/Main/Screens/WelcomeScreen.axaml
Normal file
@@ -0,0 +1,40 @@
|
||||
<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">
|
||||
|
||||
<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>
|
16
app/Desktop/Main/Screens/WelcomeScreen.axaml.cs
Normal file
16
app/Desktop/Main/Screens/WelcomeScreen.axaml.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
|
||||
namespace DHT.Desktop.Main.Screens {
|
||||
[SuppressMessage("ReSharper", "MemberCanBeInternal")]
|
||||
public sealed class WelcomeScreen : UserControl {
|
||||
public WelcomeScreen() {
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void InitializeComponent() {
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
}
|
||||
}
|
73
app/Desktop/Main/Screens/WelcomeScreenModel.cs
Normal file
73
app/Desktop/Main/Screens/WelcomeScreenModel.cs
Normal file
@@ -0,0 +1,73 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using Avalonia.Controls;
|
||||
using DHT.Desktop.Common;
|
||||
using DHT.Desktop.Dialogs.Message;
|
||||
using DHT.Server.Database;
|
||||
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 dialog = DatabaseGui.NewOpenOrCreateDatabaseFileDialog();
|
||||
dialog.Directory = Path.GetDirectoryName(dbFilePath);
|
||||
|
||||
string? path = await dialog.ShowAsync(window);
|
||||
if (!string.IsNullOrWhiteSpace(path)) {
|
||||
await OpenOrCreateDatabaseFromPath(path);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task OpenOrCreateDatabaseFromPath(string path) {
|
||||
if (Db != null) {
|
||||
Db = null;
|
||||
}
|
||||
|
||||
dbFilePath = path;
|
||||
Db = await DatabaseGui.TryOpenOrCreateDatabaseFromPath(path, window, CheckCanUpgradeDatabase);
|
||||
|
||||
OnPropertyChanged(nameof(Db));
|
||||
OnPropertyChanged(nameof(HasDatabase));
|
||||
}
|
||||
|
||||
private async Task<bool> CheckCanUpgradeDatabase() {
|
||||
return DialogResult.YesNo.Yes == await DatabaseGui.ShowCanUpgradeDatabaseDialog(window);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
39
app/Desktop/Program.cs
Normal file
39
app/Desktop/Program.cs
Normal file
@@ -0,0 +1,39 @@
|
||||
using System.Globalization;
|
||||
using System.Reflection;
|
||||
using Avalonia;
|
||||
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; }
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
public static void Main(string[] args) {
|
||||
BuildAvaloniaApp().StartWithClassicDesktopLifetime(args);
|
||||
}
|
||||
|
||||
private static AppBuilder BuildAvaloniaApp() {
|
||||
return AppBuilder.Configure<App>()
|
||||
.UsePlatformDetect()
|
||||
.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 |
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
28
app/DiscordHistoryTracker.sln
Normal file
28
app/DiscordHistoryTracker.sln
Normal file
@@ -0,0 +1,28 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Desktop", "Desktop\Desktop.csproj", "{6E7E573B-B13E-4F45-B3E3-1BE722DCAACD}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Server", "Server\Server.csproj", "{7F94B470-B06F-43C0-9655-6592A9AE2D92}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Utils", "Utils\Utils.csproj", "{7EEC1C05-1D21-4B39-B1D6-CF51F3795629}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{6E7E573B-B13E-4F45-B3E3-1BE722DCAACD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{6E7E573B-B13E-4F45-B3E3-1BE722DCAACD}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{6E7E573B-B13E-4F45-B3E3-1BE722DCAACD}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{6E7E573B-B13E-4F45-B3E3-1BE722DCAACD}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{7F94B470-B06F-43C0-9655-6592A9AE2D92}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{7F94B470-B06F-43C0-9655-6592A9AE2D92}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{7F94B470-B06F-43C0-9655-6592A9AE2D92}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{7F94B470-B06F-43C0-9655-6592A9AE2D92}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{7EEC1C05-1D21-4B39-B1D6-CF51F3795629}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{7EEC1C05-1D21-4B39-B1D6-CF51F3795629}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{7EEC1C05-1D21-4B39-B1D6-CF51F3795629}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{7EEC1C05-1D21-4B39-B1D6-CF51F3795629}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
EndGlobal
|
BIN
app/Resources/Icons/16.png
Normal file
BIN
app/Resources/Icons/16.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.0 KiB |
BIN
app/Resources/Icons/24.png
Normal file
BIN
app/Resources/Icons/24.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.4 KiB |
BIN
app/Resources/Icons/256.png
Normal file
BIN
app/Resources/Icons/256.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 15 KiB |
BIN
app/Resources/Icons/32.png
Normal file
BIN
app/Resources/Icons/32.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.8 KiB |
BIN
app/Resources/Icons/48.png
Normal file
BIN
app/Resources/Icons/48.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.6 KiB |
BIN
app/Resources/Icons/icon.afdesign
Normal file
BIN
app/Resources/Icons/icon.afdesign
Normal file
Binary file not shown.
42
app/Resources/Schemas/track-channel.yml
Normal file
42
app/Resources/Schemas/track-channel.yml
Normal file
@@ -0,0 +1,42 @@
|
||||
$schema: http://json-schema.org/draft-07/schema
|
||||
type: object
|
||||
properties:
|
||||
server:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
pattern: "^[0-9]+$"
|
||||
name:
|
||||
type: string
|
||||
type:
|
||||
type: string
|
||||
enum:
|
||||
- SERVER
|
||||
- GROUP
|
||||
- DM
|
||||
required:
|
||||
- id
|
||||
- name
|
||||
- type
|
||||
channel:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
pattern: "^[0-9]+$"
|
||||
name:
|
||||
type: string
|
||||
position:
|
||||
type: number
|
||||
minimum: 0
|
||||
topic:
|
||||
type: string
|
||||
nsfw:
|
||||
type: boolean
|
||||
required:
|
||||
- id
|
||||
- name
|
||||
required:
|
||||
- server
|
||||
- channel
|
81
app/Resources/Schemas/track-messages.yml
Normal file
81
app/Resources/Schemas/track-messages.yml
Normal file
@@ -0,0 +1,81 @@
|
||||
$schema: http://json-schema.org/draft-07/schema
|
||||
type: array
|
||||
items:
|
||||
anyOf:
|
||||
- type: object
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
pattern: "^[0-9]+$"
|
||||
sender:
|
||||
type: string
|
||||
pattern: "^[0-9]+$"
|
||||
channel:
|
||||
type: string
|
||||
pattern: "^[0-9]+$"
|
||||
text:
|
||||
type: string
|
||||
timestamp:
|
||||
type: string
|
||||
pattern: "^[0-9]+$"
|
||||
editTimestamp:
|
||||
type: string
|
||||
pattern: "^[0-9]+$"
|
||||
repliedToId:
|
||||
type: string
|
||||
pattern: "^[0-9]+$"
|
||||
attachments:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
pattern: "^[0-9]+$"
|
||||
name:
|
||||
type: string
|
||||
type:
|
||||
type: string
|
||||
size:
|
||||
type: number
|
||||
minimum: 0
|
||||
url:
|
||||
type: string
|
||||
required:
|
||||
- id
|
||||
- name
|
||||
- size
|
||||
- url
|
||||
embeds:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
pattern: "^\{.*\}$"
|
||||
reactions:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
pattern: "^[0-9]+$"
|
||||
name:
|
||||
type: string
|
||||
isAnimated:
|
||||
type: boolean
|
||||
count:
|
||||
type: number
|
||||
minimum: 1
|
||||
anyOf:
|
||||
- required:
|
||||
- id
|
||||
- count
|
||||
- required:
|
||||
- name
|
||||
- count
|
||||
required:
|
||||
- id
|
||||
- sender
|
||||
- channel
|
||||
- text
|
||||
- timestamp
|
19
app/Resources/Schemas/track-users.yml
Normal file
19
app/Resources/Schemas/track-users.yml
Normal file
@@ -0,0 +1,19 @@
|
||||
$schema: http://json-schema.org/draft-07/schema
|
||||
type: array
|
||||
items:
|
||||
anyOf:
|
||||
- type: object
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
pattern: "^[0-9]+$"
|
||||
name:
|
||||
type: string
|
||||
avatar:
|
||||
type: string
|
||||
discriminator:
|
||||
type: string
|
||||
pattern: "^[0-9]{4}$"
|
||||
required:
|
||||
- id
|
||||
- name
|
158
app/Resources/Tracker/bootstrap.js
vendored
Normal file
158
app/Resources/Tracker/bootstrap.js
vendored
Normal file
@@ -0,0 +1,158 @@
|
||||
(function() {
|
||||
const url = window.location.href;
|
||||
|
||||
if (!url.includes("discord.com/") && !url.includes("discordapp.com/") && !confirm("Could not detect Discord in the URL, do you want to run the script anyway?")) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (window.DHT_LOADED) {
|
||||
alert("Discord History Tracker is already loaded.");
|
||||
return;
|
||||
}
|
||||
|
||||
window.DHT_LOADED = true;
|
||||
window.DHT_ON_UNLOAD = [];
|
||||
|
||||
/*[IMPORTS]*/
|
||||
|
||||
const port = 0; /*[PORT]*/
|
||||
const token = "/*[TOKEN]*/";
|
||||
STATE.setup(port, token);
|
||||
|
||||
let delayedStopRequests = 0;
|
||||
const stopTrackingDelayed = function(callback) {
|
||||
delayedStopRequests++;
|
||||
|
||||
window.setTimeout(() => {
|
||||
STATE.setIsTracking(false);
|
||||
delayedStopRequests--;
|
||||
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
}, 200); // give the user visual feedback after clicking the button before switching off
|
||||
};
|
||||
|
||||
let hasJustStarted = false;
|
||||
let isSending = false;
|
||||
|
||||
const onError = function(e) {
|
||||
console.log(e);
|
||||
GUI.setStatus(e.status === "DISCONNECTED" ? "Disconnected" : "Error");
|
||||
stopTrackingDelayed(() => isSending = false);
|
||||
};
|
||||
|
||||
const isNoAction = function(action) {
|
||||
return action === null || action === CONSTANTS.AUTOSCROLL_ACTION_NOTHING;
|
||||
};
|
||||
|
||||
const onTrackingContinued = function(anyNewMessages) {
|
||||
if (!STATE.isTracking()) {
|
||||
return;
|
||||
}
|
||||
|
||||
GUI.setStatus("Tracking");
|
||||
|
||||
if (hasJustStarted) {
|
||||
anyNewMessages = true;
|
||||
hasJustStarted = false;
|
||||
}
|
||||
|
||||
isSending = false;
|
||||
|
||||
if (SETTINGS.autoscroll) {
|
||||
let action = null;
|
||||
|
||||
if (!DISCORD.hasMoreMessages()) {
|
||||
action = SETTINGS.afterFirstMsg;
|
||||
}
|
||||
if (isNoAction(action) && !anyNewMessages) {
|
||||
action = SETTINGS.afterSavedMsg;
|
||||
}
|
||||
|
||||
if (isNoAction(action)) {
|
||||
DISCORD.loadOlderMessages();
|
||||
}
|
||||
else if (action === CONSTANTS.AUTOSCROLL_ACTION_PAUSE || (action === CONSTANTS.AUTOSCROLL_ACTION_SWITCH && !DISCORD.selectNextTextChannel())) {
|
||||
GUI.setStatus("Reached End");
|
||||
STATE.setIsTracking(false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let waitUntilSendingFinishedTimer = null;
|
||||
|
||||
const onMessagesUpdated = async messages => {
|
||||
if (!STATE.isTracking() || delayedStopRequests > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isSending) {
|
||||
window.clearTimeout(waitUntilSendingFinishedTimer);
|
||||
|
||||
waitUntilSendingFinishedTimer = window.setTimeout(() => {
|
||||
waitUntilSendingFinishedTimer = null;
|
||||
onMessagesUpdated(messages);
|
||||
}, 100);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const info = DISCORD.getSelectedChannel();
|
||||
|
||||
if (!info) {
|
||||
GUI.setStatus("Error (Unknown Channel)");
|
||||
stopTrackingDelayed();
|
||||
return;
|
||||
}
|
||||
|
||||
isSending = true;
|
||||
|
||||
try {
|
||||
await STATE.addDiscordChannel(info.server, info.channel);
|
||||
} catch (e) {
|
||||
onError(e);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if (!messages.length) {
|
||||
isSending = false;
|
||||
onTrackingContinued(false);
|
||||
}
|
||||
else {
|
||||
const anyNewMessages = await STATE.addDiscordMessages(info.id, messages);
|
||||
onTrackingContinued(anyNewMessages);
|
||||
}
|
||||
} catch (e) {
|
||||
onError(e);
|
||||
}
|
||||
};
|
||||
|
||||
DISCORD.setupMessageCallback(onMessagesUpdated);
|
||||
|
||||
STATE.onTrackingStateChanged(enabled => {
|
||||
if (enabled) {
|
||||
const messages = DISCORD.getMessages();
|
||||
|
||||
if (messages.length === 0) {
|
||||
stopTrackingDelayed(() => alert("Cannot see any messages."));
|
||||
return;
|
||||
}
|
||||
|
||||
GUI.setStatus("Starting");
|
||||
hasJustStarted = true;
|
||||
// noinspection JSIgnoredPromiseFromCall
|
||||
onMessagesUpdated(messages);
|
||||
}
|
||||
else {
|
||||
isSending = false;
|
||||
}
|
||||
});
|
||||
|
||||
GUI.showController();
|
||||
|
||||
if (IS_FIRST_RUN) {
|
||||
GUI.showSettings();
|
||||
}
|
||||
})();
|
@@ -1,23 +1,5 @@
|
||||
// noinspection JSUnresolvedVariable
|
||||
// noinspection LocalVariableNamingConventionJS
|
||||
class DISCORD {
|
||||
|
||||
// https://discord.com/developers/docs/resources/channel#channel-object-channel-types
|
||||
static CHANNEL_TYPE = {
|
||||
DM: 1,
|
||||
GROUP_DM: 3,
|
||||
ANNOUNCEMENT_THREAD: 10,
|
||||
PUBLIC_THREAD: 11,
|
||||
PRIVATE_THREAD: 12
|
||||
};
|
||||
|
||||
// https://discord.com/developers/docs/resources/channel#message-object-message-types
|
||||
static MESSAGE_TYPE = {
|
||||
DEFAULT: 0,
|
||||
REPLY: 19,
|
||||
THREAD_STARTER: 21
|
||||
};
|
||||
|
||||
static getMessageOuterElement() {
|
||||
return DOM.queryReactClass("messagesWrapper");
|
||||
}
|
||||
@@ -27,7 +9,7 @@ class DISCORD {
|
||||
}
|
||||
|
||||
static getMessageElements() {
|
||||
return this.getMessageOuterElement().querySelectorAll("[class*='message_']");
|
||||
return this.getMessageOuterElement().querySelectorAll("[class*='message-']");
|
||||
}
|
||||
|
||||
static hasMoreMessages() {
|
||||
@@ -46,11 +28,46 @@ class DISCORD {
|
||||
* Calls the provided function with a list of messages whenever the currently loaded messages change.
|
||||
*/
|
||||
static setupMessageCallback(callback) {
|
||||
let skipsLeft = 0;
|
||||
let waitForCleanup = false;
|
||||
const previousMessages = new Set();
|
||||
|
||||
const onMessageElementsChanged = function() {
|
||||
const messages = DISCORD.getMessages();
|
||||
const hasChanged = messages.some(message => !previousMessages.has(message.id)) || !DISCORD.hasMoreMessages();
|
||||
const timer = window.setInterval(() => {
|
||||
if (skipsLeft > 0) {
|
||||
--skipsLeft;
|
||||
return;
|
||||
}
|
||||
|
||||
const view = this.getMessageOuterElement();
|
||||
|
||||
if (!view) {
|
||||
skipsLeft = 2;
|
||||
return;
|
||||
}
|
||||
|
||||
const anyMessage = DOM.queryReactClass("message", this.getMessageOuterElement());
|
||||
const messageCount = anyMessage ? anyMessage.parentElement.children.length : 0;
|
||||
|
||||
if (messageCount > 300) {
|
||||
if (waitForCleanup) {
|
||||
return;
|
||||
}
|
||||
|
||||
skipsLeft = 3;
|
||||
waitForCleanup = true;
|
||||
|
||||
window.setTimeout(() => {
|
||||
const view = this.getMessageScrollerElement();
|
||||
// noinspection JSUnusedGlobalSymbols
|
||||
view.scrollTop = view.scrollHeight / 2;
|
||||
}, 1);
|
||||
}
|
||||
else {
|
||||
waitForCleanup = false;
|
||||
}
|
||||
|
||||
const messages = this.getMessages();
|
||||
const hasChanged = messages.some(message => !previousMessages.has(message.id)) || !this.hasMoreMessages();
|
||||
|
||||
if (!hasChanged) {
|
||||
return;
|
||||
@@ -62,75 +79,23 @@ class DISCORD {
|
||||
}
|
||||
|
||||
callback(messages);
|
||||
};
|
||||
}, 200);
|
||||
|
||||
let debounceTimer;
|
||||
|
||||
/**
|
||||
* Do not trigger the callback too often due to autoscrolling.
|
||||
*/
|
||||
const onMessageElementsChangedLater = function() {
|
||||
window.clearTimeout(debounceTimer);
|
||||
debounceTimer = window.setTimeout(onMessageElementsChanged, 100);
|
||||
};
|
||||
|
||||
const observer = new MutationObserver(function () {
|
||||
onMessageElementsChangedLater();
|
||||
});
|
||||
|
||||
let skipsLeft = 0;
|
||||
let observedElement = null;
|
||||
|
||||
const observerTimer = window.setInterval(() => {
|
||||
if (skipsLeft > 0) {
|
||||
--skipsLeft;
|
||||
return;
|
||||
}
|
||||
|
||||
const view = this.getMessageOuterElement();
|
||||
|
||||
if (!view) {
|
||||
skipsLeft = 1;
|
||||
return;
|
||||
}
|
||||
|
||||
if (observedElement !== null && observedElement.isConnected) {
|
||||
return;
|
||||
}
|
||||
|
||||
observedElement = view.querySelector("[data-list-id='chat-messages']");
|
||||
|
||||
if (observedElement) {
|
||||
console.debug("[DHT] Observed message container.");
|
||||
observer.observe(observedElement, { childList: true });
|
||||
onMessageElementsChangedLater();
|
||||
}
|
||||
}, 400);
|
||||
|
||||
window.DHT_ON_UNLOAD.push(() => {
|
||||
observer.disconnect();
|
||||
observedElement = null;
|
||||
window.clearInterval(observerTimer);
|
||||
});
|
||||
window.DHT_ON_UNLOAD.push(() => window.clearInterval(timer));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the message from a message element.
|
||||
* @returns { null | DiscordMessage } }
|
||||
* Returns the property object of a message element.
|
||||
* @returns { null | { message: DiscordMessage, channel: Object } }
|
||||
*/
|
||||
static getMessageFromElement(ele) {
|
||||
static getMessageElementProps(ele) {
|
||||
const props = DOM.getReactProps(ele);
|
||||
|
||||
if (props && Array.isArray(props.children)) {
|
||||
for (const child of props.children) {
|
||||
if (!(child instanceof Object)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const childProps = child.props;
|
||||
if (childProps instanceof Object && "message" in childProps) {
|
||||
return childProps.message;
|
||||
}
|
||||
if (props.children && props.children.length >= 4) {
|
||||
const childProps = props.children[3].props;
|
||||
|
||||
if ("message" in childProps && "channel" in childProps) {
|
||||
return childProps;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -145,20 +110,16 @@ class DISCORD {
|
||||
const messages = [];
|
||||
|
||||
for (const ele of this.getMessageElements()) {
|
||||
try {
|
||||
const message = this.getMessageFromElement(ele);
|
||||
|
||||
if (message != null) {
|
||||
messages.push(message);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("[DHT] Error extracing message data, skipping it.", e, ele, DOM.tryGetReactProps(ele));
|
||||
const props = this.getMessageElementProps(ele);
|
||||
|
||||
if (props != null) {
|
||||
messages.push(props.message);
|
||||
}
|
||||
}
|
||||
|
||||
return messages;
|
||||
} catch (e) {
|
||||
console.error("[DHT] Error retrieving messages.", e);
|
||||
console.error(e);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
@@ -170,7 +131,7 @@ class DISCORD {
|
||||
*/
|
||||
static getSelectedChannel() {
|
||||
try {
|
||||
let obj = null;
|
||||
let obj;
|
||||
|
||||
try {
|
||||
for (const child of DOM.getReactProps(DOM.queryReactClass("chatContent")).children) {
|
||||
@@ -181,6 +142,15 @@ class DISCORD {
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("[DHT] Error retrieving selected channel from 'chatContent' element.", e);
|
||||
|
||||
for (const ele of this.getMessageElements()) {
|
||||
const props = this.getMessageElementProps(ele);
|
||||
|
||||
if (props != null) {
|
||||
obj = props.channel;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!obj || typeof obj.id !== "string") {
|
||||
@@ -192,7 +162,7 @@ class DISCORD {
|
||||
if (dms) {
|
||||
let name;
|
||||
|
||||
for (const ele of dms.querySelectorAll("[class*='channel_'] [class*='selected_'] [class^='name_'] *")) {
|
||||
for (const ele of dms.querySelectorAll("[class*='channel-'] [class*='selected-'] [class^='name-'] *, [class*='channel-'][class*='selected-'] [class^='name-'] *")) {
|
||||
const node = Array.prototype.find.call(ele.childNodes, node => node.nodeType === Node.TEXT_NODE);
|
||||
|
||||
if (node) {
|
||||
@@ -209,8 +179,8 @@ class DISCORD {
|
||||
|
||||
// https://discord.com/developers/docs/resources/channel#channel-object-channel-types
|
||||
switch (obj.type) {
|
||||
case DISCORD.CHANNEL_TYPE.DM: type = "DM"; break;
|
||||
case DISCORD.CHANNEL_TYPE.GROUP_DM: type = "GROUP"; break;
|
||||
case 1: type = "DM"; break;
|
||||
case 3: type = "GROUP"; break;
|
||||
default: return null;
|
||||
}
|
||||
|
||||
@@ -221,22 +191,9 @@ class DISCORD {
|
||||
return { server, channel };
|
||||
}
|
||||
else if (obj.guild_id) {
|
||||
let guild;
|
||||
|
||||
for (const child of DOM.getReactProps(document.querySelector("nav header [class*='headerContent_']")).children) {
|
||||
if (child && child.props && child.props.guild) {
|
||||
guild = child.props.guild;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!guild || typeof guild.name !== "string" || obj.guild_id !== guild.id) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const server = {
|
||||
"id": guild.id,
|
||||
"name": guild.name,
|
||||
"id": obj.guild_id,
|
||||
"name": document.querySelector("nav header > h1").innerText,
|
||||
"type": "SERVER"
|
||||
};
|
||||
|
||||
@@ -248,7 +205,7 @@ class DISCORD {
|
||||
}
|
||||
};
|
||||
|
||||
if (obj.type === DISCORD.CHANNEL_TYPE.ANNOUNCEMENT_THREAD || obj.type === DISCORD.CHANNEL_TYPE.PUBLIC_THREAD || obj.type === DISCORD.CHANNEL_TYPE.PRIVATE_THREAD) {
|
||||
if (obj.parent_id) {
|
||||
channel["extra"]["parent"] = obj.parent_id;
|
||||
}
|
||||
else {
|
||||
@@ -275,10 +232,10 @@ class DISCORD {
|
||||
|
||||
if (dms) {
|
||||
const currentChannel = DOM.queryReactClass("selected", dms);
|
||||
const currentChannelContainer = currentChannel && currentChannel.closest("[class*='channel_']");
|
||||
const currentChannelContainer = currentChannel && currentChannel.closest("[class*='channel-']");
|
||||
const nextChannel = currentChannelContainer && currentChannelContainer.nextElementSibling;
|
||||
|
||||
if (!nextChannel || !nextChannel.getAttribute("class").includes("channel_")) {
|
||||
if (!nextChannel || !nextChannel.getAttribute("class").includes("channel-")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -305,7 +262,7 @@ class DISCORD {
|
||||
let nextChannel = null;
|
||||
|
||||
for (let index = 0; index < allTextChannels.length - 1; index++) {
|
||||
if (allTextChannels[index].className.includes("selected_")) {
|
||||
if (allTextChannels[index].className.includes("selected-")) {
|
||||
nextChannel = allTextChannels[index + 1];
|
||||
break;
|
||||
}
|
@@ -11,7 +11,7 @@ class DOM {
|
||||
* Returns the first child element containing the specified obfuscated class. Parent defaults to the entire document.
|
||||
*/
|
||||
static queryReactClass(cls, parent) {
|
||||
return (parent || document).querySelector(`[class*="${cls}_"]`);
|
||||
return (parent || document).querySelector(`[class*="${cls}-"]`);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -71,15 +71,4 @@ class DOM {
|
||||
key = keys.find(key => key.startsWith("__reactProps$"));
|
||||
return key ? ele[key] : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns internal React state object of an element, or null if the retrieval throws.
|
||||
*/
|
||||
static tryGetReactProps(ele) {
|
||||
try {
|
||||
return this.getReactProps(ele);
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
157
app/Resources/Tracker/scripts/gui.js
Normal file
157
app/Resources/Tracker/scripts/gui.js
Normal file
@@ -0,0 +1,157 @@
|
||||
// noinspection FunctionWithInconsistentReturnsJS
|
||||
const GUI = (function() {
|
||||
let controller = null;
|
||||
let settings = null;
|
||||
|
||||
const stateChangedEvent = () => {
|
||||
if (settings) {
|
||||
settings.ui.cbAutoscroll.checked = SETTINGS.autoscroll;
|
||||
settings.ui.optsAfterFirstMsg[SETTINGS.afterFirstMsg].checked = true;
|
||||
settings.ui.optsAfterSavedMsg[SETTINGS.afterSavedMsg].checked = true;
|
||||
|
||||
const autoscrollDisabled = !SETTINGS.autoscroll;
|
||||
Object.values(settings.ui.optsAfterFirstMsg).forEach(ele => ele.disabled = autoscrollDisabled);
|
||||
Object.values(settings.ui.optsAfterSavedMsg).forEach(ele => ele.disabled = autoscrollDisabled);
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
showController() {
|
||||
if (controller) {
|
||||
return;
|
||||
}
|
||||
|
||||
const html = `
|
||||
<button id='dht-ctrl-close'>X</button>
|
||||
<button id='dht-ctrl-settings'>Settings</button>
|
||||
<button id='dht-ctrl-track'></button>
|
||||
<p id='dht-ctrl-status'>Waiting</p>`;
|
||||
|
||||
controller = {
|
||||
styles: DOM.createStyle(`/*[CSS-CONTROLLER]*/`),
|
||||
ele: DOM.createElement("div", document.body, "dht-ctrl", html)
|
||||
};
|
||||
|
||||
controller.ui = {
|
||||
btnSettings: DOM.id("dht-ctrl-settings"),
|
||||
btnTrack: DOM.id("dht-ctrl-track"),
|
||||
btnClose: DOM.id("dht-ctrl-close"),
|
||||
textStatus: DOM.id("dht-ctrl-status")
|
||||
};
|
||||
|
||||
controller.ui.btnSettings.addEventListener("click", () => {
|
||||
this.showSettings();
|
||||
});
|
||||
|
||||
controller.ui.btnTrack.addEventListener("click", () => {
|
||||
const isTracking = !STATE.isTracking();
|
||||
STATE.setIsTracking(isTracking);
|
||||
|
||||
if (!isTracking) {
|
||||
controller.ui.textStatus.innerText = "Stopped";
|
||||
}
|
||||
});
|
||||
|
||||
controller.ui.btnClose.addEventListener("click", () => {
|
||||
this.hideController();
|
||||
window.DHT_ON_UNLOAD.forEach(f => f());
|
||||
delete window.DHT_ON_UNLOAD;
|
||||
delete window.DHT_LOADED;
|
||||
});
|
||||
|
||||
STATE.onTrackingStateChanged(isTracking => {
|
||||
controller.ui.btnTrack.innerText = isTracking ? "Pause Tracking" : "Start Tracking";
|
||||
controller.ui.btnSettings.disabled = isTracking;
|
||||
});
|
||||
|
||||
SETTINGS.onSettingsChanged(stateChangedEvent);
|
||||
stateChangedEvent();
|
||||
},
|
||||
|
||||
hideController() {
|
||||
if (controller) {
|
||||
DOM.removeElement(controller.ele);
|
||||
DOM.removeElement(controller.styles);
|
||||
controller = null;
|
||||
}
|
||||
},
|
||||
|
||||
showSettings() {
|
||||
if (settings) {
|
||||
return;
|
||||
}
|
||||
|
||||
const radio = (type, id, label) => "<label><input id='dht-cfg-" + type + "-" + id + "' name='dht-" + type + "' type='radio'> " + label + "</label><br>";
|
||||
const html = `
|
||||
<label><input id='dht-cfg-autoscroll' type='checkbox'> Autoscroll</label><br>
|
||||
<br>
|
||||
<label>After reaching the first message in channel...</label><br>
|
||||
${radio("afm", "nothing", "Do Nothing")}
|
||||
${radio("afm", "pause", "Pause Tracking")}
|
||||
${radio("afm", "switch", "Switch to Next Channel")}
|
||||
<br>
|
||||
<label>After reaching a previously saved message...</label><br>
|
||||
${radio("asm", "nothing", "Do Nothing")}
|
||||
${radio("asm", "pause", "Pause Tracking")}
|
||||
${radio("asm", "switch", "Switch to Next Channel")}
|
||||
<p id='dht-cfg-note'>It is recommended to disable link and image previews to avoid putting unnecessary strain on your browser.</p>`;
|
||||
|
||||
settings = {
|
||||
styles: DOM.createStyle(`/*[CSS-SETTINGS]*/`),
|
||||
overlay: DOM.createElement("div", document.body, "dht-cfg-overlay"),
|
||||
ele: DOM.createElement("div", document.body, "dht-cfg", html)
|
||||
};
|
||||
|
||||
settings.overlay.addEventListener("click", () => {
|
||||
this.hideSettings();
|
||||
});
|
||||
|
||||
settings.ui = {
|
||||
cbAutoscroll: DOM.id("dht-cfg-autoscroll"),
|
||||
optsAfterFirstMsg: {},
|
||||
optsAfterSavedMsg: {}
|
||||
};
|
||||
|
||||
settings.ui.optsAfterFirstMsg[CONSTANTS.AUTOSCROLL_ACTION_NOTHING] = DOM.id("dht-cfg-afm-nothing");
|
||||
settings.ui.optsAfterFirstMsg[CONSTANTS.AUTOSCROLL_ACTION_PAUSE] = DOM.id("dht-cfg-afm-pause");
|
||||
settings.ui.optsAfterFirstMsg[CONSTANTS.AUTOSCROLL_ACTION_SWITCH] = DOM.id("dht-cfg-afm-switch");
|
||||
|
||||
settings.ui.optsAfterSavedMsg[CONSTANTS.AUTOSCROLL_ACTION_NOTHING] = DOM.id("dht-cfg-asm-nothing");
|
||||
settings.ui.optsAfterSavedMsg[CONSTANTS.AUTOSCROLL_ACTION_PAUSE] = DOM.id("dht-cfg-asm-pause");
|
||||
settings.ui.optsAfterSavedMsg[CONSTANTS.AUTOSCROLL_ACTION_SWITCH] = DOM.id("dht-cfg-asm-switch");
|
||||
|
||||
settings.ui.cbAutoscroll.addEventListener("change", () => {
|
||||
SETTINGS.autoscroll = settings.ui.cbAutoscroll.checked;
|
||||
});
|
||||
|
||||
Object.keys(settings.ui.optsAfterFirstMsg).forEach(key => {
|
||||
settings.ui.optsAfterFirstMsg[key].addEventListener("click", () => {
|
||||
SETTINGS.afterFirstMsg = key;
|
||||
});
|
||||
});
|
||||
|
||||
Object.keys(settings.ui.optsAfterSavedMsg).forEach(key => {
|
||||
settings.ui.optsAfterSavedMsg[key].addEventListener("click", () => {
|
||||
SETTINGS.afterSavedMsg = key;
|
||||
});
|
||||
});
|
||||
|
||||
stateChangedEvent();
|
||||
},
|
||||
|
||||
hideSettings() {
|
||||
if (settings) {
|
||||
DOM.removeElement(settings.overlay);
|
||||
DOM.removeElement(settings.ele);
|
||||
DOM.removeElement(settings.styles);
|
||||
settings = null;
|
||||
}
|
||||
},
|
||||
|
||||
setStatus(state) {
|
||||
if (controller) {
|
||||
controller.ui.textStatus.innerText = state;
|
||||
}
|
||||
}
|
||||
};
|
||||
})();
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user