317 Commits
v1.1 ... 1.10.6

Author SHA1 Message Date
f089629c74 something 2025-12-19 15:19:09 +01:00
836282c6ac update nuget packages & fixes 2025-12-19 14:54:28 +01:00
ee6b8d443d update update-url 2025-12-19 14:09:01 +01:00
5947f81307 new extras system 2025-12-19 14:07:59 +01:00
32c4065940 Merge branch 'feat/mvvm-for-client' into 'master'
client(ui): migrate to mvvm

See merge request litw-refined/minecraft-modpack-updater!3
2025-12-12 19:32:05 +00:00
8c29cf9e8a client(ui): migrate to mvvm 2025-12-12 19:32:05 +00:00
16b1a655aa ui(manager): use Menus with HeaderMenuItems instead of ImageDropDownButtons 2025-12-02 15:54:45 +01:00
12b5a63003 update nuget packages 2025-12-02 15:54:03 +01:00
035d2eeb9b remove getter for LastMinecraftProfilePath config option 2025-11-28 09:00:33 +01:00
eaaca4ddb8 fix duplicated recent files 2025-11-26 16:30:19 +01:00
c1960abe3a update to .net 10 2025-11-23 11:41:34 +01:00
cbd4b1c346 cli: info 2025-11-23 11:39:56 +01:00
db108fe36e manager: scroll into view 2025-11-23 10:11:37 +01:00
bd8a08f03c check null and whitespace for github release name 2025-11-22 14:42:12 +01:00
7e2d7f56df fix update 2025-11-22 14:17:21 +01:00
c7b2fff7e7 fix GitHub tag detection 2025-11-22 14:05:27 +01:00
a6a3d9d5a9 add missing source tag 2025-11-22 13:59:43 +01:00
5a90cc7e82 increase timeout of curseforge requsts 2025-11-22 13:44:14 +01:00
1e3d2701fc use dedicated publish script 2025-11-22 09:30:08 +01:00
f5e84b6da7 fix delete Artifacts directory 2025-11-21 19:57:52 +01:00
c864d9125a merge update urls and include app short name in distro 2025-11-21 19:52:58 +01:00
Pascal
e7aa79db64 ui(client): recent profile selector 2025-11-20 06:49:21 +01:00
Pascal
c0f013a6c5 code clenaup 2025-11-19 07:56:55 +01:00
Pascal
f21e31ebcc ui(manager): use popup for app update message 2025-11-19 07:55:46 +01:00
Pascal
0ae2a780b0 ui(manager): arrange DataGrid columns width & order 2025-11-19 07:47:25 +01:00
Pascal
474f76df4a disable update on debug builds 2025-11-19 07:40:05 +01:00
Pascal
6454d97173 ui(manager): add name column 2025-11-19 07:30:56 +01:00
Pascal
e57d2316de ui(manager): search for DataGrid 2025-11-19 07:21:19 +01:00
2cbe25e0f8 ui(manager): improve search bindings 2025-11-18 16:21:34 +01:00
86f93cf3d7 publish: remove artifacts 2025-11-18 07:30:05 +01:00
bbec2cbe07 fix build of cli 2025-11-18 07:22:17 +01:00
07bb19771a ui(manager)+manager: improve display version 2025-11-18 07:20:05 +01:00
49845e841d ui(manager): fix zip archive path binding 2025-11-18 06:54:25 +01:00
d44ccbaaab enable trimming 2025-11-18 06:53:23 +01:00
b87d896b8f update 2025-11-17 19:15:53 +01:00
26a807c8be appupdater 2025-11-17 19:02:40 +01:00
4e657469b3 move to right directory 2025-11-17 18:40:37 +01:00
535679d4ee fix publish script 2025-11-17 18:38:06 +01:00
c220d7f2b8 publish script for manager 2025-11-17 18:34:55 +01:00
ada6f59434 hotkey fix & contextmenu 2025-11-17 18:25:21 +01:00
ce09b47456 optimizations 2025-11-17 16:54:15 +01:00
cd1006b422 fixes 2025-11-17 16:44:49 +01:00
b2331926d7 translation update 2025-11-17 16:37:11 +01:00
e04e5f0b13 local folder workspace 2025-11-17 16:35:02 +01:00
5df8019cfb update nuget packages 2025-11-17 16:02:27 +01:00
fb36f897d6 fixes 2025-11-17 15:59:49 +01:00
d68df750a6 progressbar tweaks 2025-11-17 15:51:50 +01:00
ea8d1522f1 fixes 2025-11-17 15:08:31 +01:00
1a8b03696d add missing icon 2025-11-17 15:02:58 +01:00
7e2a103dbe complete 2025-11-17 15:02:47 +01:00
d80f5c47b1 localization of enums 2025-11-17 13:57:19 +01:00
c2ac96938e work 2025-11-17 09:45:25 +01:00
0bc93bb24b fix 2025-11-17 07:36:20 +01:00
c9eb3e32b4 fix search 2025-11-17 07:35:19 +01:00
afc9ceaa1f fix 2025-11-17 07:31:46 +01:00
7c745cc2f7 visible fix 2025-11-17 07:21:00 +01:00
e584996043 work 2025-11-17 07:19:43 +01:00
72a81e05ad fixes 2025-11-16 22:34:14 +01:00
da79ada91e fix 2025-11-16 20:09:30 +01:00
e47d6599b5 work 2025-11-16 19:48:28 +01:00
7469d037d5 ui(client): improve margin of window & minheight of status label 2025-11-16 13:55:15 +01:00
8f132afe92 complete visual ui 2025-11-16 13:36:47 +01:00
a5db244bc1 update 2025-11-16 10:06:37 +01:00
0b50f1ade2 and even more work 2025-11-16 09:48:03 +01:00
f6b39cb678 progress 2025-11-15 22:55:15 +01:00
d2a454aca0 update 2025-11-15 18:38:17 +01:00
795e3dd1a7 fix 2025-11-15 17:20:29 +01:00
b9ddc20b7d a lot of work 2025-11-15 17:17:52 +01:00
Pascal
336b6ad1fd more work 2025-11-14 11:40:30 +01:00
Pascal
a22f6238d4 more work 2025-11-14 11:38:51 +01:00
Pascal
bb96755232 update 2025-11-14 11:38:25 +01:00
Pascal
b4e4154445 update 2025-11-14 11:35:40 +01:00
Pascal
cd766f73fc part of manager main window 2025-11-14 08:10:42 +01:00
d798eea1d8 update 2025-11-12 14:29:31 +01:00
8ab1689309 width & height 2025-11-10 19:07:30 +01:00
6cf27b4867 manager: migrate base project structure & deps 2025-11-10 18:28:20 +01:00
da25510543 ui(client): more theme tests 2025-11-09 17:55:08 +01:00
a2d15425d1 ui(client): use fluent normal theme (again) 2025-11-09 17:50:18 +01:00
3618d83ef8 ui(client): use fluent compact theme 2025-11-09 17:48:49 +01:00
2edbcf72c7 fixes for client cli publish script 2025-11-09 14:18:50 +01:00
19b9a64721 fix missing version 2025-11-09 14:17:31 +01:00
ec6830e636 also add the new file !!!! 2025-11-09 14:16:06 +01:00
9a5c70581b build also cli 2025-11-09 14:15:53 +01:00
a6175b3874 ui(client) also publish win-arm64, osx-x64, osx-arm64 2025-11-09 14:04:24 +01:00
27ce41e0d1 ui(client): disable flatpak build 2025-11-09 13:59:30 +01:00
574a8d5ec2 final fix 2025-11-09 13:58:43 +01:00
d570d29c92 update nuget packages 2025-11-09 13:43:20 +01:00
562ce27341 ui(client): optimize installation key caching & show invalid key hint 2025-11-09 08:40:18 +01:00
463c2bbf2b ui(client): use Margin instead of RowSpacing and ColumnSpacing
-> fixes visible spacing of invisible rows
2025-11-09 08:11:27 +01:00
145a5771a1 ignore .idea 2025-11-08 22:51:12 +01:00
3adfcda472 update 2025-11-08 22:50:45 +01:00
078d5c0661 version improvement 2025-11-08 22:49:46 +01:00
06db3289c0 fix home share permission 2025-11-08 18:40:51 +01:00
a573f4fb89 fix release build 2025-11-08 18:36:45 +01:00
091d820332 fix build 2025-11-08 18:29:04 +01:00
1d44f93a92 enable network & home directory access for flatpak 2025-11-08 18:17:13 +01:00
3de764b833 fixes 2025-11-08 17:31:17 +01:00
3e6b70bc4c fixes 2025-11-08 16:36:40 +01:00
065d915d46 improvements & deployment 2025-11-08 15:46:56 +01:00
11b76ee73b add license 2025-11-08 11:33:29 +00:00
1c070891f1 ui(client): formatting & code improvements 2025-11-07 15:22:28 +01:00
4baf571713 Merge branch 'feat/migrate-client-to-avalonia' into 'master'
Feat/migrate client to avalonia

See merge request litw-refined/minecraft-modpack-updater!2
2025-11-07 13:58:36 +00:00
Pascal
b899d2ee7d ui(client): improvements 2025-11-07 10:45:55 +01:00
Pascal
05b94a3189 migrate to Pilz.Features 2025-11-06 12:20:03 +01:00
Pascal
6a6fd7efe6 ui(client): translation 2025-11-06 11:42:08 +01:00
Pascal
a89145071d ui(client): migrate to AvaloniaUI 2025-11-06 07:42:44 +01:00
554304c801 version bump, again 2025-08-23 09:28:24 +02:00
785c4ce41d version bump 2025-08-23 08:41:02 +02:00
bd7ce5bbe7 use full version string for {version} 2025-08-23 08:40:09 +02:00
8ef522bc6c 1.9.3 2025-08-20 08:32:27 +02:00
582752c987 seperated cli & some work for options 2025-08-20 08:17:33 +02:00
b434ddf480 preparation for dynamic options 2025-07-31 14:51:30 +02:00
8300f571a3 update nuget packages 2025-07-31 14:43:27 +02:00
e86fe2ee17 another fix 2025-07-04 22:27:37 +02:00
531c2a60bf fix option shit 2025-07-04 22:13:47 +02:00
b1ac3cd73c uff, enough for now 2025-06-30 18:55:30 +02:00
6674872848 more release shit 2025-06-30 17:31:42 +02:00
03f27214d6 update client app symbol 2025-06-30 14:44:07 +02:00
d8c798fe09 remove linux update url 2025-06-30 11:49:24 +02:00
70786fc169 version bump 2025-06-30 11:45:38 +02:00
aed70d4f3f update publish profile 2025-06-30 05:53:38 +02:00
45ef76e647 ui: hide installation key if no extras key can be used 2025-06-30 05:50:41 +02:00
433613e43d ui: migrate client back to WinForms
-> let's use wine for comatibility again
-> easier to maintain
-> easier to build dynamic options with
2025-06-29 20:03:02 +02:00
6e8b4f0a9d manager: tree view & action set infos editor panel 2025-06-29 14:19:58 +02:00
87cb9b8b37 update nuget packages 2025-06-29 13:40:36 +02:00
Pascal
77a93b585a manager: add proper support for options 2025-06-27 11:24:53 +02:00
Pascal
d0e3d2fa61 finalize model for option sets 2025-06-27 09:29:57 +02:00
Pascal
f5596ab0ba manager: allow parent path annotations
- Allows unlimited amount of `..\` or `../` at the start of DestPath string.
- Each of then will go back one folder.
- Similar to how Linux works or Visual Studio project styles.
- This allows us to install/update lwjgl3ify in PrismLauncher.
2025-06-27 09:10:18 +02:00
Pascal
09c1ac8b08 ui: simplify workspaces menu 2025-06-27 08:34:03 +02:00
Pascal
4e69acaf04 ui: row context menu fine tuning 2025-06-27 08:24:20 +02:00
Pascal
55db801c4c update nuget packages 2025-06-27 08:22:08 +02:00
Pascal
cbbb546f30 ui: fixed adding new row beeing empty 2025-06-27 08:09:26 +02:00
04848d4622 repair manager project 2025-06-26 18:53:00 +02:00
d85a41c158 update url 2025-06-19 12:54:11 +02:00
798961ea22 update publish profiles 2025-06-19 12:25:33 +02:00
bf0037cf79 hide console on windows only 2025-06-19 12:15:31 +02:00
c52c4059a0 version bump 2025-06-19 11:41:52 +02:00
03fefa33fe update AppUpdate to support linux updates 2025-06-19 11:41:04 +02:00
30b1832cd0 ui: migrate client to gtk 2025-06-17 09:40:10 +02:00
0aa6ed98c6 add install options (WIP) 2025-06-16 15:34:15 +02:00
c1616fecf0 fix modlist plugin not loading 2025-06-16 15:33:37 +02:00
a5eb9fad43 fix some null reference exceptions 2025-04-23 15:40:01 +02:00
df21d8180d version bump 2025-04-23 14:46:04 +02:00
f19974599f add repair mode 2025-04-23 14:45:22 +02:00
e14dedc924 support first placeholder & overwriting version 2025-04-23 13:38:51 +02:00
0285892f20 fixes 2025-04-21 20:55:55 +02:00
01fc5606cb add reftag commandline argument 2025-04-21 20:50:16 +02:00
70805083ba version bump 2025-04-21 20:48:19 +02:00
96cd9bcaac add support for {ref} placeholder 2025-04-21 20:43:09 +02:00
986257c0a4 v1.6.5 2025-04-21 18:40:19 +02:00
b11ad06287 add support for version placeholder in download links 2025-04-21 18:39:47 +02:00
d68dd09ad2 add option to generate markdown table 2025-04-03 06:19:47 +02:00
1b3070493c order by name 2025-01-28 20:46:47 +01:00
1c9c50778a add the missing files from the last commit (wtf?) 2025-01-28 20:43:27 +01:00
33a209a01a add generate modlist feature 2025-01-28 20:38:27 +01:00
2ce73ad032 list only relevant modrinth versions 2025-01-26 09:19:07 +01:00
ecb7ae0d1a fail silently if url can not be resolved 2024-12-22 18:48:30 +01:00
40380d088f version bump 2024-12-07 18:11:48 +01:00
50cbf081e1 fix endless update 2024-12-07 18:11:04 +01:00
3b79335e85 more logging 2024-12-05 07:22:01 +01:00
3e1ab72162 update deps & enable console logging 2024-12-05 07:06:06 +01:00
a83d109f24 fix first install includes ALL updates 2024-12-05 06:56:45 +01:00
e50d5f4874 v1.6.2 2024-10-31 21:16:00 +01:00
72b765e6fd final fix 2024-10-31 21:15:15 +01:00
26347ca9a4 wtf? 2024-10-19 22:05:06 +02:00
9c8eb4a9a8 v1.6.1 2024-10-19 22:01:45 +02:00
f8801cc861 probably fix install key not saving on first installation 2024-10-19 22:01:20 +02:00
97bf2b1323 showdialog 2024-10-14 16:55:14 +02:00
8fae2a2c9d improvements for first installation 2024-10-13 11:54:36 +02:00
334b9583c6 allow remove updates from update collector 2024-10-13 09:31:43 +02:00
8561fdb09b add simple generate changelog feature 2024-10-13 09:22:19 +02:00
ee69d8af56 some fixes 2024-10-03 15:46:06 +02:00
cd929485d0 bump settings version 2024-10-03 12:46:39 +02:00
2fddf23e6a show correct app version 2024-10-03 12:46:20 +02:00
d8847a8a0e client: bump version 2024-10-03 12:43:20 +02:00
e321c66f72 minor fixes 2024-10-03 11:51:05 +02:00
ac146900d0 use CurrentUpdates property 2024-10-03 11:43:40 +02:00
5fc97ed522 only load modpack config if url is provided 2024-10-03 11:42:42 +02:00
9694ba56fd use modpack config for finding updates 2024-10-03 11:36:29 +02:00
2a389ae5d4 make ModpackConfig.ConfigUrl private 2024-10-03 11:31:27 +02:00
6a0bf70e97 add modloader to modpackconfig 2024-10-03 11:30:31 +02:00
a329d21feb add modpack config to workspace (read-only via url) 2024-10-03 11:28:34 +02:00
c07ba7fc28 optimize updatescollectorui 2024-10-03 11:17:27 +02:00
07a94cfa5f add icons to new features 2024-10-03 10:51:30 +02:00
690355266c add options to update and clear direct links 2024-10-03 10:46:16 +02:00
3ec94fa22d add option to prefer directlinks 2024-10-03 10:03:40 +02:00
95a81a9306 move actions to separated code & add sinlge update search action 2024-10-03 09:54:22 +02:00
934b8db71b some fixes 2024-09-29 09:58:32 +02:00
31f557cc8d finish healthy check & other sources implemtation 2024-09-28 14:08:30 +02:00
6f8ac996fd minor fixes 2024-09-26 10:18:29 +02:00
a420e7bf74 fix wrong menu item for tools features 2024-09-26 09:33:59 +02:00
12c00841ff add pluginfeatureprovider for updatescollectorfeature 2024-09-26 09:32:55 +02:00
f64c8cdba0 proper finish updates collector 2024-09-26 09:30:12 +02:00
2f9d60f1a8 some more work 2024-09-25 12:08:38 +02:00
bc6c483ba6 allow curseforge and modrinth as source 2024-09-22 09:41:09 +02:00
7e5acd413e add/remove action rows 2024-09-09 09:20:24 +02:00
333f5011de check against Id instead of Name 2024-09-09 08:58:16 +02:00
c6153488cd fix namepsace of Modpack Features 2024-09-09 08:57:29 +02:00
32d9212edc add/edit/delete updates 2024-09-09 08:54:53 +02:00
1747083c99 change treeview to listcontrol 2024-09-08 17:16:02 +02:00
f2a78a462c reorder columns 2024-09-08 17:11:28 +02:00
4c54f592d3 remove unused using 2024-09-08 17:04:48 +02:00
27879f15c3 allow edit 2024-09-08 17:04:13 +02:00
a02f275577 disable icon for now due performance 2024-09-08 10:01:47 +02:00
4b964e548f add icon for source type & minor fixes 2024-09-08 09:57:03 +02:00
8087c0539a make list 2024-09-08 09:12:39 +02:00
19fccd8af9 add icon to manager form 2024-09-07 09:49:41 +02:00
043b14e9a9 some work 2024-09-07 09:45:38 +02:00
f6219c0aa8 use AssemblyAppVersion for Client 2024-09-07 09:30:13 +02:00
f453e85956 update nuget packages & minor fixes 2024-09-06 20:08:40 +02:00
5e678db077 some work for loading a workspace 2024-09-06 16:55:04 +02:00
f0f63a1895 add icons to manager window 2024-09-06 16:19:49 +02:00
0276f11a8a use Win11 as default theme for Updater 2024-09-06 16:13:14 +02:00
5083af5fd1 fix 2024-09-06 16:01:10 +02:00
86b7ec67fc finish editor for gitlab repo workspace config 2024-09-06 15:59:13 +02:00
3239fb5431 add cancel icon 2024-09-06 15:58:48 +02:00
9bfd83ee4a some background code for manager workspaces 2024-09-06 15:16:19 +02:00
ebc57e05d5 some work on Manager 2024-09-06 09:44:24 +02:00
5bccd070f4 more refactoring 2024-09-06 09:14:25 +02:00
3625962a27 re-organze namespace 2024-09-06 08:45:36 +02:00
939c87820f begin finding assets from bottom to top 2024-09-05 15:17:01 +02:00
b4f0d43b46 add GitHub as source & allow inheritance 2024-09-05 15:03:53 +02:00
46179463e1 migration due nuget updates 2024-09-05 14:53:39 +02:00
7c4dae8c8d update nuget packages 2024-09-05 14:27:18 +02:00
6427ce4726 stop shipping whole framework on publication 2024-08-24 09:08:28 +02:00
b35e3d5187 update help texts 2024-08-10 09:35:40 +02:00
deeee34f87 v1.5.2.2 2024-08-10 09:27:13 +02:00
f254ac03e1 draw help always even if not noui is set 2024-08-10 09:27:09 +02:00
c9650c6118 v1.5.2.1 2024-08-10 09:24:07 +02:00
2e467c0a96 draw help 2024-08-10 09:23:57 +02:00
69cd869cd1 v1.5.2 2024-08-09 15:37:45 +02:00
ef314ac227 show console on cmd 2024-08-09 15:37:36 +02:00
a30893e04c fix: also check side for initial install 2024-08-09 15:37:21 +02:00
368948d277 fail safe when no unleash api endpoint configured 2024-07-10 18:49:25 +02:00
60b11d949d remove modpack key 2024-07-10 18:45:22 +02:00
1e41fa8b10 v1.5.1.0 2024-07-10 18:25:17 +02:00
d1f35676ee unleash api via modpack config 2024-07-10 18:24:55 +02:00
1bd0e87211 Lösche ModpackUpdater.sln.bak 2024-06-22 17:05:25 +00:00
64e89eb6ba Merge branch 'feat/rework-install' into 'master'
Feat/rework install

See merge request gaming/minecraft/minecraft-modpack-updater!1
2024-06-22 16:59:07 +00:00
b6e0f6f77a v1.5.0.6 2024-06-22 18:58:17 +02:00
ade4f8f245 fix loading url & key again 2024-06-22 18:58:07 +02:00
80d41c869c v1.5.0.5 2024-06-22 18:49:23 +02:00
725711877c fix version 2024-06-22 18:49:06 +02:00
9fb56678f3 v1.5.0.4 2024-06-22 18:46:07 +02:00
a4a0549920 fix pasting url & ley 2024-06-22 18:45:56 +02:00
b7a201e621 v1.5.0.3 2024-06-22 18:27:08 +02:00
1fba037e3f fix loading everything on startup 2024-06-22 18:26:58 +02:00
bf38ed6df6 v1.5.0.2 2024-06-22 18:08:39 +02:00
1e00870f29 fix install button never accessable 2024-06-22 18:08:07 +02:00
ebb15bf25c v1.5.0.1 2024-06-22 17:52:14 +02:00
b5878572aa fix loading configu url from modpackinfo 2024-06-22 17:52:09 +02:00
7804a60377 v1.5.0 2024-06-22 13:40:12 +02:00
44450546d0 show version without git tag 2024-06-22 13:39:52 +02:00
11e9290fc8 some more bugfixes & cleanup 2024-06-22 13:39:17 +02:00
4b78ce1b6f some fixes 2024-06-22 11:22:14 +02:00
Schedel Pascal
20c1e5dc8e install keys 2024-06-21 08:55:46 +02:00
Schedel Pascal
f1185c242c Revert "completely revert extra property"
This reverts commit 1746bb6442.
2024-06-21 06:45:33 +02:00
Schedel Pascal
ab1d3f38b0 include release configuration 2024-06-21 06:16:35 +02:00
Schedel Pascal
1746bb6442 completely revert extra property 2024-06-21 06:07:30 +02:00
68b940fddd AllowExtras 2024-06-20 22:42:46 +02:00
87cfacfdbb remove allow extras from option -> controlled by update config 2024-06-20 22:40:19 +02:00
ee46f7272e remove extras toogle from ui 2024-06-20 22:37:54 +02:00
3be25d7070 include extras 2024-06-20 22:34:38 +02:00
4ea4a70e50 add option to include/exclude extra files 2024-06-20 22:19:18 +02:00
c97a04c4ce load modpack info before modpack installer 2024-06-20 15:05:01 +02:00
604d35856f adjust maintenance text to be not too long 2024-06-20 07:47:26 +02:00
3dc69a4f26 migrate to svg symbols & add maintenance info 2024-06-20 07:24:23 +02:00
fc27fc1c34 add maintenance mode & improve & bugfixing 2024-06-20 06:55:25 +02:00
c31cc3aa20 add side 2024-06-20 06:30:24 +02:00
00de4d8708 add installation without gui & pass arguments to gui 2024-06-20 06:12:28 +02:00
f3b2d07117 allow update after install 2024-06-20 06:11:41 +02:00
6808f772c1 some bugfixing & more 2024-06-19 20:08:00 +02:00
0f4ba96963 cmd tools 2024-06-18 11:58:49 +02:00
c95e73be48 rework ui 2024-06-18 11:41:40 +02:00
40279ba6b9 migrate settings 2024-06-18 11:17:57 +02:00
0f3e93bfff code cosmetic 2024-06-18 10:59:08 +02:00
deb14caf24 convert to c# 2024-06-18 10:56:32 +02:00
f07fad0a80 use assembly file version instead of product version for update check 2024-06-17 20:20:13 +02:00
b21d33237f fix converter for UpdateInfo.Version 2024-06-17 20:13:45 +02:00
b32d2c5512 remove unused imports 2024-06-17 20:11:58 +02:00
e303b6c381 fix AppUpdater using 2024-06-17 20:11:29 +02:00
a7a3abf6e5 update update url 2024-06-17 20:02:08 +02:00
4dd112cd8c rework updater 2024-06-17 19:02:04 +02:00
af58f0d8ea install: more directory power 2024-06-17 18:46:49 +02:00
f1b11ea3c6 install: use local zip cache 2024-06-17 18:40:30 +02:00
54ab66fba0 add property for modpack name 2024-06-17 18:32:12 +02:00
9c43055580 install: rework ModpackInstaller (incomplete) 2024-06-17 18:30:57 +02:00
a4d5fff876 install: remove legacy code 2024-06-17 18:01:32 +02:00
99b1db952b install: begin rework 2024-06-17 17:20:02 +02:00
96c178b310 v1.4.1 2024-06-17 16:56:08 +02:00
bbac69b79e support urls 2024-06-17 16:55:31 +02:00
97b38ed90c update nuget packages 2024-06-17 16:24:06 +02:00
c0fb1e3904 minor code improvements 2024-04-21 11:40:49 +02:00
fd701f3615 update structure 2024-04-21 09:36:33 +02:00
34fa5fbffe v1.4 2023-08-26 21:40:19 +02:00
0776611de4 add automatic in-app-updater 2023-08-26 21:40:15 +02:00
0e9667ab59 add product version to app title 2023-08-26 21:39:54 +02:00
8692c40323 update Pilz deps 2023-08-26 20:38:35 +02:00
69c5de7e42 update nuget packages 2023-08-26 19:57:28 +02:00
f1dc55c63f v1.3.3 2023-08-26 16:32:03 +02:00
7e814b37c3 catch errors on update/install 2023-08-26 16:31:36 +02:00
9407f3fed6 v1.3.2 2023-05-05 14:26:59 +02:00
5925836736 add IsDirectory and support Delete actions 2023-05-05 14:26:55 +02:00
461efe7d14 v1.3.1 2023-05-05 08:14:43 +02:00
b74cc0d633 create dest directory if not exists 2023-05-05 08:14:29 +02:00
52943e03be add update url to settings form 2023-05-04 11:22:02 +02:00
2c69c3eb1b delete SupportedOSPlatform again 2023-05-04 11:18:00 +02:00
60c625ae08 v1.3 2023-05-04 11:17:00 +02:00
0c1ffd82c5 v1.3 2023-05-04 11:13:39 +02:00
0d7e570676 use new system via an update info 2023-05-04 11:12:21 +02:00
6dd6721667 d 2023-02-26 16:41:56 +01:00
a7c31d6086 revertsadfasdf 2023-02-26 16:30:08 +01:00
cc558ab274 allow remove 2023-02-26 15:50:03 +01:00
ad18e33a6b v1.2.1 2023-02-20 17:25:26 +01:00
fa9bb19e79 v1.2 2023-02-17 19:35:11 +01:00
c0c8878bc3 do not ignore removed files by default via AppConfig 2023-02-17 19:34:36 +01:00
2be12d0630 update nugets 2023-01-13 12:33:58 +01:00
216 changed files with 8586 additions and 7777 deletions

5
.gitignore vendored
View File

@@ -174,7 +174,7 @@ publish/
*.azurePubxml
# Note: Comment the next line if you want to checkin your web deploy settings,
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
# *.pubxml
*.publishproj
# Microsoft Azure Web App publish settings. Comment the next line if you want to
@@ -348,3 +348,6 @@ MigrationBackup/
# Ionide (cross platform F# VS Code tools) working folder
.ionide/
*.json
.idea/

3
.gitmodules vendored Normal file
View File

@@ -0,0 +1,3 @@
[submodule "publish-scripts"]
path = publish-scripts
url = https://git.pilzinsel64.de/Pilz.NET/publish-scripts.git

37
Directory.Build.props Normal file
View File

@@ -0,0 +1,37 @@
<Project>
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<LangVersion>latest</LangVersion>
<ImplicitUsings>true</ImplicitUsings>
<Nullable>enable</Nullable>
<JsonSerializerIsReflectionEnabledByDefault>true</JsonSerializerIsReflectionEnabledByDefault>
</PropertyGroup>
<PropertyGroup>
<PackageProjectUrl>https://git.pilzinsel64.de/litw-refined/minecraft-modpack-updater</PackageProjectUrl>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<GenerateSerializationAssemblies>False</GenerateSerializationAssemblies>
<GenerateDocumentationFile>True</GenerateDocumentationFile>
<NoWarn>1591</NoWarn>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)'=='Debug'">
<DefineConstants>$(DefineConstants);DISABLE_UPDATE</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)'=='Release'">
<DebugSymbols>False</DebugSymbols>
<DebugType>None</DebugType>
</PropertyGroup>
<ItemGroup>
<TrimmerRootAssembly Include="Newtonsoft.Json"/>
<TrimmerRootAssembly Include="Pilz"/>
<TrimmerRootAssembly Include="Yggdrasil.Engine"/>
<TrimmerRootAssembly Include="Octokit"/>
<TrimmerRootAssembly Include="Modrinth.Net"/>
<TrimmerRootAssembly Include="CurseForge.APIClient"/>
</ItemGroup>
</Project>

21
LICENSE Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2025 Pilzinsel64
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,11 @@
<Application xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="ModpackUpdater.Apps.Client.Gui.App"
RequestedThemeVariant="Default">
<!-- "Default" ThemeVariant follows system theme variant. "Dark" or "Light" are other available options. -->
<Application.Styles>
<FluentTheme/>
<StyleInclude Source="avares://Pilz.UI.AvaloniaUI/Assets/Styles/EnhancedDefaults.axaml"/>
</Application.Styles>
</Application>

View File

@@ -0,0 +1,41 @@
using Avalonia;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Data.Core.Plugins;
using Avalonia.Markup.Xaml;
namespace ModpackUpdater.Apps.Client.Gui;
public partial class App : Application
{
public override void Initialize()
{
AvaloniaXamlLoader.Load(this);
AppGlobals.Initialize();
}
public override void OnFrameworkInitializationCompleted()
{
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
{
DisableAvaloniaDataAnnotationValidation();
desktop.MainWindow = new MainView
{
DataContext = new MainViewModel(),
};
}
base.OnFrameworkInitializationCompleted();
}
private void DisableAvaloniaDataAnnotationValidation()
{
// Get an array of plugins to remove
var dataValidationPluginsToRemove =
BindingPlugins.DataValidators.OfType<DataAnnotationsValidationPlugin>().ToArray();
// remove each entry found
foreach (var plugin in dataValidationPluginsToRemove)
{
BindingPlugins.DataValidators.Remove(plugin);
}
}
}

View File

@@ -0,0 +1,28 @@
using Newtonsoft.Json;
using Pilz.Configuration;
namespace ModpackUpdater.Apps.Client.Gui;
public class AppConfig : ISettingsNode, ISettingsIdentifier
{
public static string Identifier => "pilz.appconfig";
public List<string> RecentMinecraftProfilePaths { get; } = [];
[JsonProperty, Obsolete]
private string? LastMinecraftProfilePath
{
set
{
if (!string.IsNullOrWhiteSpace(value))
RecentMinecraftProfilePaths.Insert(0, value);
}
}
public void Reset()
{
RecentMinecraftProfilePaths.Clear();
}
public static AppConfig Instance => Program.Settings.Get<AppConfig>();
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 KiB

View File

@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<svg width="480" height="480" viewBox="0 0 480 480" fill="none" xmlns="http://www.w3.org/2000/svg">
<defs>
<clipPath id="clip_path_1">
<rect width="480" height="480" />
</clipPath>
</defs>
<g clip-path="url(#clip_path_1)">
<path d="M360 40L180 40L140 0C140 0 40 0 40 0C17.91 0 0 17.91 0 40C0 40 0 120 0 120L400 120C400 120 400 80 400 80C400 57.91 382.09 40 360 40C360 40 360 40 360 40Z" fill="#FFA000" transform="translate(20 100)" />
<g transform="translate(53 -7)">
<path d="M160.042 63.3334L0 7.91667L13.9167 213.75L160.042 300.833L278.333 205.833L292.25 0L160.042 63.3334Z" fill="#5D4037" fill-rule="evenodd" transform="translate(20.875 63.333)" />
<path d="M0 0L13.9167 205.834L160.042 292.917L160.042 55.4167L0 0Z" fill="#8D6E63" fill-rule="evenodd" transform="translate(20.875 71.25)" />
<path d="M13.9167 112.084L13.9167 79.1667L27.8333 72.2554L27.8333 95L41.75 87.0833L41.75 118.75L62.625 109.028L62.625 55.4167L76.5417 48.6954L76.5417 87.0833L90.4583 80.5679L90.4583 63.3333L104.375 55.4167L104.375 35.3637L118.292 28.6979L118.292 47.5L129.425 41.1667L132.208 0L0 63.3333L0 118.75L13.9167 112.084Z" fill="#43A047" fill-rule="evenodd" transform="translate(180.917 63.333)" />
<path d="M0 47.5L139.167 0L292.25 39.5833L160.042 102.917L0 47.5Z" fill="#B2FF59" fill-rule="evenodd" transform="translate(20.875 23.75)" />
<path d="M0 0L2.78333 40.6442L13.9167 44.3888L13.9167 34.9363L27.8333 39.5833L27.8333 65.0513L48.7083 72.2792L48.7083 34.6196L62.625 39.5833L62.625 77.1004L76.5417 81.9217L76.5417 58.9713L90.4583 63.7925L90.4583 86.743L104.375 91.5563L104.375 82.1671L118.292 87.0834L118.292 74.6067L132.208 79.1667L132.208 101.199L160.042 110.833L160.042 55.4167L0 0Z" fill="#66BB6A" fill-rule="evenodd" transform="translate(20.875 71.25)" />
</g>
<path d="M360 0C360 0 40 0 40 0C17.91 0 0 15.1595 0 33.8571C0 33.8571 0 203.143 0 203.143C0 221.84 17.91 237 40 237C40 237 360 237 360 237C382.09 237 400 221.84 400 203.143C400 203.143 400 33.8571 400 33.8571C400 15.1595 382.09 0 360 0C360 0 360 0 360 0Z" fill="#FFCA28" transform="translate(20 183)" />
<path d="M70 80L0 0L140 0L70 80Z" fill="#1565C0" transform="translate(150 380)" />
<path d="M0 0L60 0L60 111.25L0 111.25L0 0Z" fill="#1565C0" transform="translate(190 280)" />
<path d="M30 15C30 23.28 23.29 30 15 30C6.71002 30 0 23.28 0 15C0 6.72 6.71002 0 15 0C23.29 0 30 6.72 30 15C30 15 30 15 30 15Z" fill="#4A148C" transform="translate(430 60)" />
<path d="M125 100C72.62 100 30 57.38 30 5C30 5 30 0 30 0L0 0L0 10C0 10 0.25 10 0.25 10C2.84 74.93 55.08 127.16 120 129.75C120 129.75 120 130 120 130L130 130L130 100L125 100C125 100 125 100 125 100Z" fill="#9C27B0" transform="translate(330 60)" />
<path d="M75 50C50.19 50 30 29.81 30 5C30 5 30 0 30 0L0 0L0 10C0 10 0.25 10 0.25 10C2.73004 47.36 32.63 77.27 70 79.75C70 79.75 70 80 70 80L80 80L80 50L75 50C75 50 75 50 75 50Z" fill="#7B1FA2" transform="translate(380 60)" />
<path d="M175 150C95.05 150 30 84.95 30 5C30 5 30 0 30 0L0 0L0 10C0 10 0.25 10 0.25 10C2.88998 102.5 77.51 177.11 170 179.75C170 179.75 170 180 170 180L180 180L180 150L175 150C175 150 175 150 175 150Z" fill="#BA68C8" transform="translate(280 60)" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.2 KiB

View File

@@ -0,0 +1,81 @@
//------------------------------------------------------------------------------
// <auto-generated>
// Dieser Code wurde von einem Tool generiert.
// Laufzeitversion:4.0.30319.42000
//
// Änderungen an dieser Datei können falsches Verhalten verursachen und gehen verloren, wenn
// der Code erneut generiert wird.
// </auto-generated>
//------------------------------------------------------------------------------
namespace ModpackUpdater.Apps.Client.Gui {
using System;
/// <summary>
/// Eine stark typisierte Ressourcenklasse zum Suchen von lokalisierten Zeichenfolgen usw.
/// </summary>
// Diese Klasse wurde von der StronglyTypedResourceBuilder automatisch generiert
// -Klasse über ein Tool wie ResGen oder Visual Studio automatisch generiert.
// Um einen Member hinzuzufügen oder zu entfernen, bearbeiten Sie die .ResX-Datei und führen dann ResGen
// mit der /str-Option erneut aus, oder Sie erstellen Ihr VS-Projekt neu.
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class FiledialogFilters {
private static global::System.Resources.ResourceManager resourceMan;
private static global::System.Globalization.CultureInfo resourceCulture;
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal FiledialogFilters() {
}
/// <summary>
/// Gibt die zwischengespeicherte ResourceManager-Instanz zurück, die von dieser Klasse verwendet wird.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Resources.ResourceManager ResourceManager {
get {
if (object.ReferenceEquals(resourceMan, null)) {
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("ModpackUpdater.Apps.Client.Gui.FiledialogFilters", typeof(FiledialogFilters).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
/// <summary>
/// Überschreibt die CurrentUICulture-Eigenschaft des aktuellen Threads für alle
/// Ressourcenzuordnungen, die diese stark typisierte Ressourcenklasse verwenden.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
set {
resourceCulture = value;
}
}
/// <summary>
/// Sucht eine lokalisierte Zeichenfolge, die Json files (*.json) ähnelt.
/// </summary>
internal static string JSON_Display {
get {
return ResourceManager.GetString("JSON_Display", resourceCulture);
}
}
/// <summary>
/// Sucht eine lokalisierte Zeichenfolge, die *.json ähnelt.
/// </summary>
internal static string JSON_Filter {
get {
return ResourceManager.GetString("JSON_Filter", resourceCulture);
}
}
}
}

View File

@@ -0,0 +1,242 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace ModpackUpdater.Apps.Client.Gui.LangRes {
using System;
/// <summary>
/// A strongly-typed resource class, for looking up localized strings, etc.
/// </summary>
// This class was auto-generated by the StronglyTypedResourceBuilder
// class via a tool like ResGen or Visual Studio.
// To add or remove a member, edit your .ResX file then rerun ResGen
// with the /str option, or rebuild your VS project.
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
public class GeneralLangRes {
private static global::System.Resources.ResourceManager resourceMan;
private static global::System.Globalization.CultureInfo resourceCulture;
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal GeneralLangRes() {
}
/// <summary>
/// Returns the cached ResourceManager instance used by this class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
public static global::System.Resources.ResourceManager ResourceManager {
get {
if (object.ReferenceEquals(resourceMan, null)) {
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("ModpackUpdater.Apps.Client.Gui.LangRes.GeneralLangRes", typeof(GeneralLangRes).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
/// <summary>
/// Overrides the current thread's CurrentUICulture property for all
/// resource lookups using this strongly typed resource class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
public static global::System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
set {
resourceCulture = value;
}
}
/// <summary>
/// Looks up a localized string similar to An update is available!.
/// </summary>
public static string AnUpdateIsAvailable {
get {
return ResourceManager.GetString("AnUpdateIsAvailable", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Check for updates.
/// </summary>
public static string CheckForUpdates {
get {
return ResourceManager.GetString("CheckForUpdates", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Checking for Updates....
/// </summary>
public static string CheckingForUpdates {
get {
return ResourceManager.GetString("CheckingForUpdates", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Config incomplete or not loaded!.
/// </summary>
public static string ConfigIncompleteOrNotLoaded {
get {
return ResourceManager.GetString("ConfigIncompleteOrNotLoaded", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Downloading program update....
/// </summary>
public static string DownloadProgramUpdate {
get {
return ResourceManager.GetString("DownloadProgramUpdate", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Error on update check or while updating!.
/// </summary>
public static string ErrorOnUpdateCheckOrUpdating {
get {
return ResourceManager.GetString("ErrorOnUpdateCheckOrUpdating", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Everything is right and up-to-date..
/// </summary>
public static string EverythingIsRightAndUpToDate {
get {
return ResourceManager.GetString("EverythingIsRightAndUpToDate", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Install.
/// </summary>
public static string Install {
get {
return ResourceManager.GetString("Install", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Installation key.
/// </summary>
public static string InstallationKey {
get {
return ResourceManager.GetString("InstallationKey", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Installation key seems to be invalid.
/// </summary>
public static string InstallationKeyNotValid {
get {
return ResourceManager.GetString("InstallationKeyNotValid", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Installing....
/// </summary>
public static string Installing {
get {
return ResourceManager.GetString("Installing", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Minecraft profile.
/// </summary>
public static string MinecraftProfile {
get {
return ResourceManager.GetString("MinecraftProfile", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Minecraft profile folder seems to be not valid..
/// </summary>
public static string MinecraftProfileFolderSeemsInvalid {
get {
return ResourceManager.GetString("MinecraftProfileFolderSeemsInvalid", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Modpack config url.
/// </summary>
public static string ModpackConfigUrl {
get {
return ResourceManager.GetString("ModpackConfigUrl", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to No recent profiles available..
/// </summary>
public static string NoRecentProfilesAvailable {
get {
return ResourceManager.GetString("NoRecentProfilesAvailable", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Repair.
/// </summary>
public static string Repair {
get {
return ResourceManager.GetString("Repair", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Select.
/// </summary>
public static string Select {
get {
return ResourceManager.GetString("Select", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Select the minecraft profile folder (usually named .minecraft).
/// </summary>
public static string SelectMinecraftProfileFolder {
get {
return ResourceManager.GetString("SelectMinecraftProfileFolder", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Status.
/// </summary>
public static string Status {
get {
return ResourceManager.GetString("Status", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to The update servers are in maintenance..
/// </summary>
public static string UpdateServerInMaintenance {
get {
return ResourceManager.GetString("UpdateServerInMaintenance", resourceCulture);
}
}
}
}

View File

@@ -117,32 +117,64 @@
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<assembly alias="System.Windows.Forms" name="System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
<data name="icons8_checkmark_16px" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>Resources\icons8_checkmark_16px.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
<data name="AnUpdateIsAvailable" xml:space="preserve">
<value>An update is available!</value>
</data>
<data name="icons8_delete_16px" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>Resources\icons8_delete_16px.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
<data name="CheckForUpdates" xml:space="preserve">
<value>Check for updates</value>
</data>
<data name="icons8_download_from_ftp_16px" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>Resources\icons8_download_from_ftp_16px.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
<data name="CheckingForUpdates" xml:space="preserve">
<value>Checking for Updates...</value>
</data>
<data name="icons8_general_warning_sign_16px" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>Resources\icons8_general_warning_sign_16px.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
<data name="ConfigIncompleteOrNotLoaded" xml:space="preserve">
<value>Config incomplete or not loaded!</value>
</data>
<data name="icons8_opened_folder_16px" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>Resources\icons8_opened_folder_16px.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
<data name="DownloadProgramUpdate" xml:space="preserve">
<value>Downloading program update...</value>
</data>
<data name="icons8_save_16px" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>Resources\icons8_save_16px.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
<data name="ErrorOnUpdateCheckOrUpdating" xml:space="preserve">
<value>Error on update check or while updating!</value>
</data>
<data name="icons8_software_installer_16px" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>Resources\icons8_software_installer_16px.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
<data name="EverythingIsRightAndUpToDate" xml:space="preserve">
<value>Everything is right and up-to-date.</value>
</data>
<data name="icons8_update_16px" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>Resources\icons8_update_16px.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
<data name="Install" xml:space="preserve">
<value>Install</value>
</data>
<data name="icons8_wrench_16px" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>Resources\icons8_wrench_16px.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
<data name="InstallationKey" xml:space="preserve">
<value>Installation key</value>
</data>
<data name="Installing" xml:space="preserve">
<value>Installing...</value>
</data>
<data name="MinecraftProfile" xml:space="preserve">
<value>Minecraft profile</value>
</data>
<data name="MinecraftProfileFolderSeemsInvalid" xml:space="preserve">
<value>Minecraft profile folder seems to be not valid.</value>
</data>
<data name="ModpackConfigUrl" xml:space="preserve">
<value>Modpack config url</value>
</data>
<data name="Repair" xml:space="preserve">
<value>Repair</value>
</data>
<data name="Select" xml:space="preserve">
<value>Select</value>
</data>
<data name="SelectMinecraftProfileFolder" xml:space="preserve">
<value>Select the minecraft profile folder (usually named .minecraft)</value>
</data>
<data name="Status" xml:space="preserve">
<value>Status</value>
</data>
<data name="UpdateServerInMaintenance" xml:space="preserve">
<value>The update servers are in maintenance.</value>
</data>
<data name="InstallationKeyNotValid" xml:space="preserve">
<value>Installation key seems to be invalid</value>
</data>
<data name="NoRecentProfilesAvailable" xml:space="preserve">
<value>No recent profiles available.</value>
</data>
</root>

View File

@@ -0,0 +1,72 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace ModpackUpdater.Apps.Client.Gui.LangRes {
using System;
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
[System.Diagnostics.DebuggerNonUserCodeAttribute()]
[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
public class MsgBoxLangRes {
private static System.Resources.ResourceManager resourceMan;
private static System.Globalization.CultureInfo resourceCulture;
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal MsgBoxLangRes() {
}
[System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)]
public static System.Resources.ResourceManager ResourceManager {
get {
if (object.Equals(null, resourceMan)) {
System.Resources.ResourceManager temp = new System.Resources.ResourceManager("ModpackUpdater.Apps.Client.Gui.LangRes.MsgBoxLangRes", typeof(MsgBoxLangRes).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
[System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)]
public static System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
set {
resourceCulture = value;
}
}
public static string UpdateAvailable {
get {
return ResourceManager.GetString("UpdateAvailable", resourceCulture);
}
}
public static string UpdateAvailable_Title {
get {
return ResourceManager.GetString("UpdateAvailable_Title", resourceCulture);
}
}
public static string ErrorWhileUpdate_Title {
get {
return ResourceManager.GetString("ErrorWhileUpdate_Title", resourceCulture);
}
}
public static string ErrorWhileUpdate {
get {
return ResourceManager.GetString("ErrorWhileUpdate", resourceCulture);
}
}
}
}

View File

@@ -117,25 +117,17 @@
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="StatusTest_EverythingOk" xml:space="preserve">
<value>Everything is right and up-to-date.</value>
<data name="UpdateAvailable" xml:space="preserve">
<value>A new version of this program is available! Install now?
If you confirm, the update will be installed automatically within a few seconds.</value>
</data>
<data name="StatusText_CheckingForUpdates" xml:space="preserve">
<value>Checking for Updates...</value>
<data name="UpdateAvailable_Title" xml:space="preserve">
<value>New program version available</value>
</data>
<data name="StatusText_ConfigIncompleteOrNotLoaded" xml:space="preserve">
<value>Config incomplete or not loaded!</value>
<data name="ErrorWhileUpdate_Title" xml:space="preserve">
<value>Error while updating</value>
</data>
<data name="StatusText_ErrorWhileUpdateCheckOrUpdate" xml:space="preserve">
<value>Error on update check or while updating!</value>
</data>
<data name="StatusText_Installing" xml:space="preserve">
<value>Installing...</value>
</data>
<data name="StatusText_MinecraftProfileWarning" xml:space="preserve">
<value>Minecraft profile folder seems to be not valid.</value>
</data>
<data name="StatusText_UpdateAvailable" xml:space="preserve">
<value>An update is available!</value>
<data name="ErrorWhileUpdate" xml:space="preserve">
<value>An error happened while updating the program. Error message:\n{0}</value>
</data>
</root>

View File

@@ -0,0 +1,152 @@
<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:lang="clr-namespace:ModpackUpdater.Apps.Client.Gui.LangRes"
xmlns:pilz="https://git.pilzinsel64.de/pilz-framework/pilz"
xmlns:gui="clr-namespace:ModpackUpdater.Apps.Client.Gui"
xmlns:symbols="clr-namespace:Pilz.UI.Symbols;assembly=Pilz.UI"
mc:Ignorable="d"
x:Class="ModpackUpdater.Apps.Client.Gui.MainView"
x:DataType="gui:MainViewModel"
x:Name="window"
Width="520"
SizeToContent="Height"
WindowStartupLocation="CenterScreen"
CanMaximize="false"
Title="Minecraft Modpack Updater"
Icon="/Assets/app.ico"
Loaded="Control_OnLoaded">
<Design.DataContext>
<gui:MainViewModel/>
</Design.DataContext>
<Grid
RowDefinitions="Auto,Auto,Auto,Auto,Auto"
HorizontalAlignment="Stretch"
VerticalAlignment="Top"
Margin="3"
x:Name="MainGrid">
<Grid.IsEnabled>
<MultiBinding Converter="{x:Static BoolConverters.And}">
<Binding Path="HasInitialized"/>
<Binding Path="!IsUpdating"/>
</MultiBinding>
</Grid.IsEnabled>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*" MinWidth="250"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<!-- Labels -->
<Label Grid.Row="0" Grid.Column="0" Margin="3" Content="{x:Static lang:GeneralLangRes.MinecraftProfile}" Target="TextBoxMinecraftProfileFolder"/>
<Label Grid.Row="1" Grid.Column="0" Margin="3" Content="{x:Static lang:GeneralLangRes.ModpackConfigUrl}" Target="TextBoxModpackConfig"/>
<Label Grid.Row="2" Grid.Column="0" Margin="3" IsVisible="{Binding CanUseExtrasKey}" Content="{x:Static lang:GeneralLangRes.InstallationKey}" Target="TextBoxInstallKey"/>
<TextBlock Grid.Row="3" Grid.Column="0" Margin="3" Text="{x:Static lang:GeneralLangRes.Status}"/>
<StackPanel Grid.Row="3" Grid.Column="1" Grid.ColumnSpan="2" Margin="3" Orientation="Horizontal" Spacing="6" MinHeight="{Binding MinHeight, ElementName=TextBoxMinecraftProfileFolder}">
<Image Width="{x:Static symbols:SymbolGlobals.DefaultImageSmallSize}" Source="{Binding StatusImage}"/>
<TextBlock Text="{Binding StatusText}"/>
</StackPanel>
<!-- TextBoxes: Profile -->
<TextBox
x:Name="TextBoxMinecraftProfileFolder"
Grid.Row="0"
Grid.Column="1"
Margin="3"
VerticalAlignment="Center"
Watermark="C:\..."
Text="{Binding MinecraftProfileFolder}"/>
<!-- TextBoxes: ModpackConfig -->
<TextBox
x:Name="TextBoxModpackConfig"
Grid.Row="1"
Grid.Column="1"
Grid.ColumnSpan="2"
Margin="3"
VerticalAlignment="Center"
Watermark="https://..."
Text="{Binding ModpackConfigUrl}"/>
<!-- TextBoxes: InstallKey -->
<TextBox
x:Name="TextBoxInstallKey"
Grid.Row="2"
Grid.Column="1"
Grid.ColumnSpan="2"
Margin="3"
VerticalAlignment="Center"
Watermark="XXXXX-YYYYY-ZZZZZ-AAAAA-BBBBB"
Text="{Binding InstallKey}"
IsVisible="{Binding CanUseExtrasKey}"/>
<!-- Button: SearchProfileFolder -->
<pilz:ImageSplitButton
x:Name="ButtonSearchProfileFolder"
Grid.Row="0"
Grid.Column="2"
Margin="3"
VerticalAlignment="Stretch"
HorizontalAlignment="Stretch"
Text="{x:Static lang:GeneralLangRes.Select}"
Click="ButtonSearchProfileFolder_Click">
<pilz:ImageSplitButton.DataTemplates>
<DataTemplate DataType="gui:MainViewModel+EmptyRecentFilesItem">
<TextBlock Text="{x:Static lang:GeneralLangRes.NoRecentProfilesAvailable}" FontStyle="Italic"/>
</DataTemplate>
<DataTemplate DataType="gui:MainViewModel+RecentFilesItem">
<MenuItem Header="{Binding Display}" Command="{Binding DataContext.OpenRecentPathCommand, ElementName=window}" CommandParameter="{Binding Path}" />
</DataTemplate>
</pilz:ImageSplitButton.DataTemplates>
<pilz:ImageSplitButton.Flyout>
<MenuFlyout ItemsSource="{Binding RecentMinecraftProfilePathItems}">
<MenuFlyout.ItemTemplate>
<DataTemplate>
<ContentControl Content="{Binding}"/>
</DataTemplate>
</MenuFlyout.ItemTemplate>
</MenuFlyout>
</pilz:ImageSplitButton.Flyout>
</pilz:ImageSplitButton>
<!-- Button: CheckForUpdates -->
<pilz:ImageButton
x:Name="ButtonCheckForUpdates"
Grid.Row="4"
Grid.Column="1"
Margin="3"
VerticalAlignment="Stretch"
HorizontalAlignment="Stretch"
Text="{x:Static lang:GeneralLangRes.CheckForUpdates}"
Command="{Binding CheckForUpdatesCommand}"
IsEnabled="{Binding CanUpdate}"/>
<!-- Button: Install -->
<pilz:ImageSplitButton
x:Name="ButtonInstall"
Grid.Row="4"
Grid.Column="2"
Margin="3"
VerticalAlignment="Stretch"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Center"
Text="{x:Static lang:GeneralLangRes.Install}"
Command="{Binding InstallCommand}"
IsEnabled="{Binding CanUpdate}">
<SplitButton.Flyout>
<MenuFlyout>
<MenuItem x:Name="MenuItemRepair" Header="{x:Static lang:GeneralLangRes.Repair}" Command="{Binding RepairCommand}"/>
</MenuFlyout>
</SplitButton.Flyout>
</pilz:ImageSplitButton>
</Grid>
</Window>

View File

@@ -0,0 +1,51 @@
using System.Reflection;
using Avalonia.Controls;
using Avalonia.Interactivity;
using Avalonia.Platform.Storage;
using Avalonia.Threading;
using ModpackUpdater.Apps.Client.Gui.LangRes;
using Pilz.Extensions;
using Pilz.UI.Symbols;
namespace ModpackUpdater.Apps.Client.Gui;
public partial class MainView : Window
{
public MainViewModel Model => DataContext as MainViewModel ?? throw new NullReferenceException();
public MainView()
{
InitializeComponent();
Title = $"{Title} (v{Assembly.GetExecutingAssembly().GetAppVersion().ToShortHumanString()})";
ButtonSearchProfileFolder.ImageSource = AppGlobals.Symbols.GetImageSource(AppSymbols.opened_folder);
ButtonCheckForUpdates.ImageSource = AppGlobals.Symbols.GetImageSource(AppSymbols.update_done);
ButtonInstall.ImageSource = AppGlobals.Symbols.GetImageSource(AppSymbols.software_installer);
MenuItemRepair.Icon = AppGlobals.Symbols.GetImage(AppSymbols.wrench, SymbolSize.Small);
}
private async void InitializeViewModel()
{
await Model.CheckForUpdates(this);
await Model.Initialize();
}
private void Control_OnLoaded(object? sender, RoutedEventArgs e)
{
Dispatcher.UIThread.Post(InitializeViewModel, DispatcherPriority.Background);
}
private async void ButtonSearchProfileFolder_Click(object? sender, RoutedEventArgs e)
{
var filePaths = await StorageProvider.OpenFolderPickerAsync(new FolderPickerOpenOptions
{
Title = GeneralLangRes.SelectMinecraftProfileFolder,
SuggestedStartLocation = await StorageProvider.TryGetFolderFromPathAsync(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData)),
AllowMultiple = false,
});
if (filePaths.Count >= 1)
Model.MinecraftProfileFolder = filePaths[0].Path.AbsolutePath;
}
}

View File

@@ -0,0 +1,340 @@
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Windows.Input;
using Avalonia.Controls;
using Avalonia.Media;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using ModpackUpdater.Apps.Client.Gui.LangRes;
using ModpackUpdater.Manager;
using Pilz.Extensions.Collections;
namespace ModpackUpdater.Apps.Client.Gui;
public partial class MainViewModel : ObservableObject
{
public class EmptyRecentFilesItem;
public class RecentFilesItem(string path)
{
public string Path => path;
public string Display => path.Length > 50 ? $"...{path[^50..]}" : path;
}
private readonly UpdateCheckOptions updateOptions = new();
private ModpackInfo modpackInfo = new();
private ModpackConfig updateConfig = new();
private UpdateCheckResult? lastUpdateCheckResult;
[ObservableProperty] private string? minecraftProfileFolder;
[ObservableProperty] private string? modpackConfigUrl;
[ObservableProperty] private string? installKey;
[ObservableProperty] private string statusText = "-";
[ObservableProperty] private IImage? statusImage;
[ObservableProperty] private bool hasInitialized;
[ObservableProperty] private bool isUpdating;
[ObservableProperty] private bool loadingData;
[ObservableProperty] private bool canUpdate;
[ObservableProperty] private bool canUseExtrasKey;
public ObservableCollection<object> RecentMinecraftProfilePathItems { get; } = [];
public ICommand CheckForUpdatesCommand { get; }
public ICommand InstallCommand { get; }
public ICommand RepairCommand { get; }
public MainViewModel()
{
CheckForUpdatesCommand = new AsyncRelayCommand(async () => await CheckStatusAndUpdate(false));
InstallCommand = new AsyncRelayCommand(async () => await ExecuteUpdate(true, false));
RepairCommand = new AsyncRelayCommand(async () => await ExecuteUpdate(true, true));
}
public async Task CheckForUpdates(Window parent)
{
var updates = new AppUpdates("client", parent);
updates.OnDownloadProgramUpdate += (_, _) => SetStatus(GeneralLangRes.DownloadProgramUpdate, AppGlobals.Symbols.GetImageSource(AppSymbols.software_installer));
await updates.UpdateApp();
}
public async Task Initialize()
{
ClearStatus();
LoadProfileToUi();
LoadRecentFilesToUi();
HasInitialized = true;
await CheckStatusAndUpdate(true);
}
partial void OnMinecraftProfileFolderChanged(string? value)
{
if (!LoadingData)
_ = CheckStatusAndUpdate(true);
}
partial void OnModpackConfigUrlChanged(string? value)
{
if (!LoadingData)
_ = CheckStatusAndUpdate(false);
}
partial void OnInstallKeyChanged(string? value)
{
if (!LoadingData)
_ = CheckStatusAndUpdate(false);
}
public void SetStatus(string statusText, IImage? image)
{
StatusText = statusText;
StatusImage = image;
}
public void ClearStatus()
{
StatusText = "-";
StatusImage = null;
}
private bool AllowExtras()
{
return !string.IsNullOrWhiteSpace(modpackInfo.ExtrasKey) && updateConfig.ExtrasKeys.Contains(modpackInfo.ExtrasKey);
}
public void LoadProfileToUi()
{
LoadingData = true;
MinecraftProfileFolder = modpackInfo.LocalPath ?? AppConfig.Instance.RecentMinecraftProfilePaths.FirstOrDefault() ?? MinecraftProfileFolder;
ModpackConfigUrl = modpackInfo.ConfigUrl ?? ModpackConfigUrl;
InstallKey = modpackInfo.ExtrasKey ?? InstallKey;
LoadingData = false;
}
private void LoadOptionsToUi()
{
//foreach (var set in )
//{
// // ...
//}
}
public void LoadRecentFilesToUi()
{
RecentMinecraftProfilePathItems.Clear();
if (AppConfig.Instance.RecentMinecraftProfilePaths.Count == 0)
{
RecentMinecraftProfilePathItems.Add(new EmptyRecentFilesItem());
return;
}
AppConfig.Instance.RecentMinecraftProfilePaths.ForEach(path =>
{
if (Directory.Exists(path))
RecentMinecraftProfilePathItems.Add(new RecentFilesItem(path));
});
}
private void StoreRecentMinecraftProfilePath(string? path)
{
if (string.IsNullOrWhiteSpace(path))
return;
AppConfig.Instance.RecentMinecraftProfilePaths.RemoveAll(n => n == path);
AppConfig.Instance.RecentMinecraftProfilePaths.Insert(0, path);
AppConfig.Instance.RecentMinecraftProfilePaths.Skip(10).ForEach(n => AppConfig.Instance.RecentMinecraftProfilePaths.Remove(n));
}
[RelayCommand]
private void OpenRecentPath(string path)
{
if (!string.IsNullOrWhiteSpace(path))
MinecraftProfileFolder = path;
}
public async Task CheckStatusAndUpdate(bool loadProfileToUi)
{
if (!CheckStatus(loadProfileToUi))
return;
await ExecuteUpdate(false, false);
StoreRecentMinecraftProfilePath(modpackInfo.LocalPath);
LoadRecentFilesToUi();
}
private bool CheckStatus(bool loadProfileToUi)
{
ClearStatus();
try
{
modpackInfo = ModpackInfo.TryLoad(MinecraftProfileFolder?.Trim());
}
catch
{
// Ignore
}
if (loadProfileToUi)
LoadProfileToUi();
try
{
updateConfig = ModpackConfig.LoadFromUrl(ModpackConfigUrl);
}
catch
{
// Ignore
}
modpackInfo.ExtrasKey = InstallKey?.Trim();
if (!string.IsNullOrWhiteSpace(modpackInfo.ExtrasKey) && !AllowExtras())
{
SetStatus(GeneralLangRes.InstallationKeyNotValid, AppGlobals.Symbols.GetImageSource(AppSymbols.general_warning_sign));
return false;
}
CanUseExtrasKey = updateConfig.ExtrasKeys.Count > 0;
if (string.IsNullOrWhiteSpace(MinecraftProfileFolder) /*|| modpackInfo.Valid*/)
{
SetStatus(GeneralLangRes.MinecraftProfileFolderSeemsInvalid, AppGlobals.Symbols.GetImageSource(AppSymbols.general_warning_sign));
CanUpdate = false;
return false;
}
else if (string.IsNullOrWhiteSpace(ModpackConfigUrl))
{
SetStatus(GeneralLangRes.ConfigIncompleteOrNotLoaded, AppGlobals.Symbols.GetImageSource(AppSymbols.general_warning_sign));
CanUpdate = false;
return false;
}
else if (updateConfig.Maintenance && !updateOptions.IgnoreMaintenance)
{
SetStatus(GeneralLangRes.UpdateServerInMaintenance, AppGlobals.Symbols.GetImageSource(AppSymbols.services));
CanUpdate = false;
return false;
}
LoadOptionsToUi();
CanUpdate = true;
return true;
}
private async Task ExecuteUpdate(bool doInstall, bool repair)
{
ClearStatus();
// Ensure set extras key
modpackInfo.ExtrasKey = InstallKey?.Trim();
var updater = new ModpackInstaller(updateConfig, modpackInfo);
updater.InstallProgessUpdated += Updater_InstallProgressUpdated;
updater.CheckingProgressUpdated += Updater_CheckingProgressUpdated;
void error()
{
SetStatus(GeneralLangRes.ErrorOnUpdateCheckOrUpdating, AppGlobals.Symbols.GetImageSource(AppSymbols.close));
IsUpdating = false;
}
void installing()
{
SetStatus(GeneralLangRes.Installing, AppGlobals.Symbols.GetImageSource(AppSymbols.software_installer));
IsUpdating = true;
}
void updatesAvailable()
{
SetStatus(GeneralLangRes.AnUpdateIsAvailable, AppGlobals.Symbols.GetImageSource(AppSymbols.software_installer));
IsUpdating = false;
}
void everythingOk()
{
SetStatus(GeneralLangRes.EverythingIsRightAndUpToDate, AppGlobals.Symbols.GetImageSource(AppSymbols.done));
IsUpdating = false;
}
// Check only if not pressed "install", not really needed otherwise.
if (lastUpdateCheckResult is null || !doInstall || repair)
{
SetStatus(GeneralLangRes.CheckingForUpdates, AppGlobals.Symbols.GetImageSource(AppSymbols.update_done));
// Check for extras once again
updateOptions.IncludeExtras = AllowExtras();
// Force re-install on repair
updateOptions.IgnoreInstalledVersion = repair;
try
{
lastUpdateCheckResult = await updater.Check(updateOptions);
}
catch
{
error();
if (Debugger.IsAttached)
throw;
}
}
// Error while update check
if (lastUpdateCheckResult is null || lastUpdateCheckResult.HasError)
{
error();
return;
}
// Load options
// lastUpdateCheckResult.OptionsAvailable...
// lastUpdateCheckResult.OptionsEnabled...
// No updates available
if (!lastUpdateCheckResult.HasUpdates)
{
everythingOk();
return;
}
// Updates available (but don't install)
if (!doInstall)
{
updatesAvailable();
return;
}
// Install updates
installing();
IsUpdating = true;
try
{
// Install
if (await updater.Install(lastUpdateCheckResult) == false)
{
error();
return;
}
// Success
lastUpdateCheckResult = null; // Reset last update check, a new one would be needed now.
everythingOk();
}
catch
{
// Error
error();
if (Debugger.IsAttached)
throw;
}
}
private void Updater_CheckingProgressUpdated(int toCheck, int processed)
{
SetStatus(Math.Round(processed / (double)toCheck * 100d, 1) + "%", AppGlobals.Symbols.GetImageSource(AppSymbols.update_done));
}
private void Updater_InstallProgressUpdated(UpdateCheckResult result, int processedSyncs)
{
var actionCount = result.Actions.Count;
SetStatus(Math.Round(processedSyncs / (double)actionCount * 100d, 1) + "%", AppGlobals.Symbols.GetImageSource(AppSymbols.software_installer));
}
}

View File

@@ -0,0 +1,99 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<ApplicationIcon>Assets\app.ico</ApplicationIcon>
<AssemblyName>MinecraftModpackUpdater</AssemblyName>
<IncludeAllContentForSelfExtract>true</IncludeAllContentForSelfExtract>
<BuiltInComInteropSupport>true</BuiltInComInteropSupport>
<AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>
</PropertyGroup>
<ItemGroup>
<TrimmerRootAssembly Include="MinecraftModpackUpdater"/>
<TrimmerRootAssembly Include="ModpackUpdater.Manager"/>
<TrimmerRootAssembly Include="ModpackUpdater.Apps"/>
<TrimmerRootAssembly Include="ModpackUpdater"/>
<TrimmerRootAssembly Include="Pilz.Updating"/>
<TrimmerRootAssembly Include="Pilz.Updating.Client"/>
<TrimmerRootAssembly Include="Pilz.Configuration"/>
<TrimmerRootAssembly Include="ExCSS"/>
</ItemGroup>
<ItemGroup>
<Compile Include="..\Version.cs" />
</ItemGroup>
<ItemGroup>
<AvaloniaResource Include="Assets\**" />
</ItemGroup>
<ItemGroup>
<Compile Update="FiledialogFilters.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>FiledialogFilters.resx</DependentUpon>
</Compile>
<Compile Update="LangRes\GeneralLangRes.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>GeneralLangRes.resx</DependentUpon>
</Compile>
<Compile Update="LangRes\MsgBoxLangRes.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>MsgBoxLangRes.resx</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Update="FiledialogFilters.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>FiledialogFilters.Designer.cs</LastGenOutput>
</EmbeddedResource>
<EmbeddedResource Update="LangRes\GeneralLangRes.resx">
<Generator>PublicResXFileCodeGenerator</Generator>
<LastGenOutput>GeneralLangRes.Designer.cs</LastGenOutput>
</EmbeddedResource>
<EmbeddedResource Update="LangRes\MsgBoxLangRes.resx">
<Generator>PublicResXFileCodeGenerator</Generator>
<LastGenOutput>MsgBoxLangRes.Designer.cs</LastGenOutput>
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Avalonia.BuildServices" Version="11.3.2">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
<PackageReference Include="MessageBox.Avalonia" Version="3.3.1" />
<PackageReference Include="Mono.Options" Version="6.12.0.148" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.4" />
<PackageReference Include="Pilz" Version="2.7.8" />
<PackageReference Include="Pilz.Configuration" Version="3.2.8" />
<PackageReference Include="Pilz.Cryptography" Version="2.1.3" />
<PackageReference Include="Pilz.IO" Version="2.1.1" />
<PackageReference Include="Pilz.UI" Version="3.1.5" />
<PackageReference Include="Pilz.UI.AvaloniaUI" Version="1.2.21" />
<PackageReference Include="Avalonia" Version="11.3.10" />
<PackageReference Include="Avalonia.Desktop" Version="11.3.10" />
<PackageReference Include="Avalonia.Svg" Version="11.3.0" />
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.3.10" />
<PackageReference Include="Avalonia.Fonts.Inter" Version="11.3.10" />
<!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.-->
<PackageReference Include="Avalonia.Diagnostics" Version="11.3.10">
<IncludeAssets Condition="'$(Configuration)' != 'Debug'">None</IncludeAssets>
<PrivateAssets Condition="'$(Configuration)' != 'Debug'">All</PrivateAssets>
</PackageReference>
<PackageReference Include="Pilz.Updating" Version="4.3.6" />
<PackageReference Include="Pilz.Updating.Client" Version="4.4.8" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\ModpackUpdater.Apps\ModpackUpdater.Apps.csproj" />
<ProjectReference Include="..\ModpackUpdater.Manager\ModpackUpdater.Manager.csproj" />
<ProjectReference Include="..\ModpackUpdater\ModpackUpdater.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,47 @@
using Avalonia;
using Castle.Core.Logging;
using Newtonsoft.Json;
using Pilz;
using Pilz.Configuration;
namespace ModpackUpdater.Apps.Client.Gui;
public static class Program
{
private static readonly SettingsManager settingsManager;
public static ISettings Settings => settingsManager.Instance;
public static ILogger Log { get; } = new ConsoleLogger();
static Program()
{
settingsManager = new(GetSettingsPath(2), true);
}
[STAThread]
internal static void Main(string[] args)
{
BuildAvaloniaApp().StartWithClassicDesktopLifetime(args);
}
public static AppBuilder BuildAvaloniaApp()
{
return AppBuilder.Configure<App>()
.UsePlatformDetect()
.WithInterFont()
.LogToTrace();
}
private static string GetSettingsPath(int? settingsVersion = 3)
{
const string appDataDirectoryName = "MinecraftModpackUpdater";
var fileNamePostfix = settingsVersion == null ? string.Empty : $"V{settingsVersion}";
var settingsFileName = $"Settings{fileNamePostfix}.json";
var settingsPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), appDataDirectoryName);
Directory.CreateDirectory(settingsPath);
settingsPath = Path.Combine(settingsPath, settingsFileName);
return settingsPath;
}
}

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
https://go.microsoft.com/fwlink/?LinkID=208121.
-->
<Project>
<PropertyGroup>
<Configuration>Release</Configuration>
<Platform>Any CPU</Platform>
<PublishDir>..\publish\ui\</PublishDir>
<PublishProtocol>FileSystem</PublishProtocol>
<_TargetId>Folder</_TargetId>
<TargetFramework>net8.0-windows</TargetFramework>
<RuntimeIdentifier>win-x86</RuntimeIdentifier>
<SelfContained>true</SelfContained>
<PublishSingleFile>true</PublishSingleFile>
<PublishReadyToRun>false</PublishReadyToRun>
</PropertyGroup>
</Project>

View File

@@ -0,0 +1,92 @@
# PUPNET DEPLOY: 1.9.1
# Use: 'pupnet --help conf' for information.
# APP PREAMBLE
AppBaseName = MinecraftModpackUpdater
AppFriendlyName = Minecraft Modpack Updater
AppId = de.pilzinsel64.minecraft-modpack-updater
AppVersionRelease = 1.0.0[1]
AppShortSummary = Install and Update Minecraft Modpacks easliy.
AppDescription = """
Minecraft Modpack Updater is a simple tool to install and update a modpack to a selected minecraft profile by a selected modpack configuration file. It downloads a config file via https and checks the version there and what files has been changed and download the updateded files via a given link from the config.
"""
AppLicenseId = MIT
AppLicenseFile = ../LICENSE
AppChangeFile =
# PUBLISHER
PublisherName = Pilzinsel64
PublisherId = de.pilzinsel64
PublisherCopyright = Copyright (C) Pilzinsel64 2025
PublisherLinkName = Pilzinsel64 Homepage
PublisherLinkUrl = https://pilzinsel64.de
PublisherEmail =
# DESKTOP INTEGRATION
DesktopNoDisplay = false
DesktopTerminal = false
DesktopFile =
StartCommand =
PrimeCategory =
MetaFile =
IconFiles = """
Assets/app.ico
Assets/app.svg
"""
# DOTNET PUBLISH
DotnetProjectPath = ModpackUpdater.Apps.Client.Gui.csproj
DotnetPublishArgs = -p:Version=${APP_VERSION} --self-contained true -p:DebugType=None -p:DebugSymbols=false -p:PublishSingleFile=true -p:PublishTrimmed=true
DotnetPostPublish =
DotnetPostPublishOnWindows =
# PACKAGE OUTPUT
PackageName = minecraft-modpack-updater
OutputDirectory = ../publish/client-ui
# APPIMAGE OPTIONS
AppImageArgs =
AppImageRuntimePath =
AppImageVersionOutput = false
# FLATPAK OPTIONS
FlatpakPlatformRuntime = org.freedesktop.Platform
FlatpakPlatformSdk = org.freedesktop.Sdk
FlatpakPlatformVersion = 25.08
FlatpakFinishArgs = """
--socket=wayland
--socket=fallback-x11
--filesystem=home
--share=network
"""
FlatpakBuilderArgs =
# RPM OPTIONS
RpmAutoReq = false
RpmAutoProv = true
RpmRequires = """
krb5-libs
libicu
openssl-libs
"""
# DEBIAN OPTIONS
DebianRecommends = """
libc6
libgcc1
libgssapi-krb5-2
libicu70
libssl3
libstdc++6
zlib1g
"""
# WINDOWS SETUP OPTIONS
SetupGroupName =
SetupAdminInstall = false
SetupCommandPrompt =
SetupMinWindowsVersion = 10
SetupSignTool =
SetupSuffixOutput =
SetupVersionOutput = false
SetupUninstallScript =

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 KiB

View File

@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<svg width="480" height="480" viewBox="0 0 480 480" fill="none" xmlns="http://www.w3.org/2000/svg">
<defs>
<clipPath id="clip_path_1">
<rect width="480" height="480" />
</clipPath>
</defs>
<g clip-path="url(#clip_path_1)">
<path d="M360 40L180 40L140 0C140 0 40 0 40 0C17.91 0 0 17.91 0 40C0 40 0 120 0 120L400 120C400 120 400 80 400 80C400 57.91 382.09 40 360 40C360 40 360 40 360 40Z" fill="#FFA000" transform="translate(20 100)" />
<g transform="translate(53 -7)">
<path d="M160.042 63.3334L0 7.91667L13.9167 213.75L160.042 300.833L278.333 205.833L292.25 0L160.042 63.3334Z" fill="#5D4037" fill-rule="evenodd" transform="translate(20.875 63.333)" />
<path d="M0 0L13.9167 205.834L160.042 292.917L160.042 55.4167L0 0Z" fill="#8D6E63" fill-rule="evenodd" transform="translate(20.875 71.25)" />
<path d="M13.9167 112.084L13.9167 79.1667L27.8333 72.2554L27.8333 95L41.75 87.0833L41.75 118.75L62.625 109.028L62.625 55.4167L76.5417 48.6954L76.5417 87.0833L90.4583 80.5679L90.4583 63.3333L104.375 55.4167L104.375 35.3637L118.292 28.6979L118.292 47.5L129.425 41.1667L132.208 0L0 63.3333L0 118.75L13.9167 112.084Z" fill="#43A047" fill-rule="evenodd" transform="translate(180.917 63.333)" />
<path d="M0 47.5L139.167 0L292.25 39.5833L160.042 102.917L0 47.5Z" fill="#B2FF59" fill-rule="evenodd" transform="translate(20.875 23.75)" />
<path d="M0 0L2.78333 40.6442L13.9167 44.3888L13.9167 34.9363L27.8333 39.5833L27.8333 65.0513L48.7083 72.2792L48.7083 34.6196L62.625 39.5833L62.625 77.1004L76.5417 81.9217L76.5417 58.9713L90.4583 63.7925L90.4583 86.743L104.375 91.5563L104.375 82.1671L118.292 87.0834L118.292 74.6067L132.208 79.1667L132.208 101.199L160.042 110.833L160.042 55.4167L0 0Z" fill="#66BB6A" fill-rule="evenodd" transform="translate(20.875 71.25)" />
</g>
<path d="M360 0C360 0 40 0 40 0C17.91 0 0 15.1595 0 33.8571C0 33.8571 0 203.143 0 203.143C0 221.84 17.91 237 40 237C40 237 360 237 360 237C382.09 237 400 221.84 400 203.143C400 203.143 400 33.8571 400 33.8571C400 15.1595 382.09 0 360 0C360 0 360 0 360 0Z" fill="#FFCA28" transform="translate(20 183)" />
<path d="M70 80L0 0L140 0L70 80Z" fill="#1565C0" transform="translate(150 380)" />
<path d="M0 0L60 0L60 111.25L0 111.25L0 0Z" fill="#1565C0" transform="translate(190 280)" />
<path d="M30 15C30 23.28 23.29 30 15 30C6.71002 30 0 23.28 0 15C0 6.72 6.71002 0 15 0C23.29 0 30 6.72 30 15C30 15 30 15 30 15Z" fill="#4A148C" transform="translate(430 60)" />
<path d="M125 100C72.62 100 30 57.38 30 5C30 5 30 0 30 0L0 0L0 10C0 10 0.25 10 0.25 10C2.84 74.93 55.08 127.16 120 129.75C120 129.75 120 130 120 130L130 130L130 100L125 100C125 100 125 100 125 100Z" fill="#9C27B0" transform="translate(330 60)" />
<path d="M75 50C50.19 50 30 29.81 30 5C30 5 30 0 30 0L0 0L0 10C0 10 0.25 10 0.25 10C2.73004 47.36 32.63 77.27 70 79.75C70 79.75 70 80 70 80L80 80L80 50L75 50C75 50 75 50 75 50Z" fill="#7B1FA2" transform="translate(380 60)" />
<path d="M175 150C95.05 150 30 84.95 30 5C30 5 30 0 30 0L0 0L0 10C0 10 0.25 10 0.25 10C2.88998 102.5 77.51 177.11 170 179.75C170 179.75 170 180 170 180L180 180L180 150L175 150C175 150 175 150 175 150Z" fill="#BA68C8" transform="translate(280 60)" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.2 KiB

View File

@@ -0,0 +1,36 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<ApplicationIcon>Assets\app.ico</ApplicationIcon>
<AssemblyName>MinecraftModpackUpdaterCli</AssemblyName>
</PropertyGroup>
<ItemGroup>
<TrimmerRootAssembly Include="MinecraftModpackUpdaterCli"/>
<TrimmerRootAssembly Include="ModpackUpdater.Manager"/>
<TrimmerRootAssembly Include="ModpackUpdater"/>
</ItemGroup>
<ItemGroup>
<Compile Include="..\Version.cs" />
</ItemGroup>
<ItemGroup>
<AvaloniaResource Include="Assets\**" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Mono.Options" Version="6.12.0.148" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.4" />
<PackageReference Include="Pilz" Version="2.7.8" />
<PackageReference Include="Pilz.Cryptography" Version="2.1.3" />
<PackageReference Include="Pilz.IO" Version="2.1.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\ModpackUpdater.Manager\ModpackUpdater.Manager.csproj" />
<ProjectReference Include="..\ModpackUpdater\ModpackUpdater.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,41 @@
using Mono.Options;
namespace ModpackUpdater.Apps.Client;
internal class Options
{
private readonly List<string> additionals = [];
private readonly OptionSet options;
public IReadOnlyList<string> Additionals => additionals;
public bool Help { get; private set; }
public bool Silent { get; private set; }
public string? RefTag { get; private set; }
public string? Version { get; private set; }
public UpdateCheckOptionsAdv UpdateOptions { get; } = new();
public Options(string[] args)
{
options = new OptionSet
{
{ "silent", "Do not output anything.", s => Silent = true },
{ "h|help", "Writes the help text as output.", h => Help = true },
{ "p|profile=", "Sets the minecraft profile folder.", p => UpdateOptions.ProfileFolder = p },
{ "c|config=", "Sets the modpack update info url.", c => UpdateOptions.ModpackConfig = c },
{ "s|side=", "Sets the installation side.\nDefault side is Client.\nAvailable: Client, Server", s => UpdateOptions.Side = Enum.Parse<Side>(s)},
{ "u|uai", "Disallow an update directly after install. This only has effect if there is no existing installation.", uai => UpdateOptions.AllowUpdaterAfterInstall = false},
{ "m|maintenance", "Ignores the maintenance mode.", m => UpdateOptions.IgnoreMaintenance = true},
{ "i|nonpublic", "Include non public (currently hidden) updates.", i => UpdateOptions.IncludeNonPublic = true},
{ "k|key=", "An key for retriving extra files on updates.", k => UpdateOptions.ExtrasKey = k},
{ "r|reftag=", "Force uses a specific reference tag, if supported.", r => RefTag = r},
{ "v|version=", "Force uses a specific version, if supported.", v => Version = v},
};
additionals.AddRange(options.Parse(args));
}
public void DrawHelp()
{
options.WriteOptionDescriptions(Console.Out);
}
}

View File

@@ -0,0 +1,74 @@
using System.Reflection;
using Castle.Core.Logging;
using ModpackUpdater.Manager;
using Pilz.Extensions;
namespace ModpackUpdater.Apps.Client;
public static class Program
{
private static readonly ILogger log = new ConsoleLogger();
public static ILogger Log => log;
internal static Options Options { get; private set; } = null!;
[STAThread]
internal static void Main(string[] args)
{
Options = new Options(args);
if (Options.Help)
{
DrawInfo();
Options.DrawHelp();
return;
}
if (!Options.Silent)
DrawInfo();
InstallWithoutGui(Options.UpdateOptions, Options.Silent);
}
private static void DrawInfo()
{
Console.WriteLine("Minecraft Modpack Updater CLI");
Console.WriteLine("Version " + Assembly.GetExecutingAssembly().GetAppVersion().ToShortHumanString());
Console.WriteLine("------------------------------");
}
private static void InstallWithoutGui(UpdateCheckOptionsAdv updateOptions, bool silent)
{
var info = ModpackInfo.TryLoad(updateOptions.ProfileFolder);
var config = ModpackConfig.LoadFromUrl(CheckModpackConfigUrl(updateOptions.ModpackConfig!, info));
// Check features
if (!string.IsNullOrWhiteSpace(updateOptions.ExtrasKey))
info.ExtrasKey = updateOptions.ExtrasKey;
if (!string.IsNullOrWhiteSpace(info.ExtrasKey))
updateOptions.IncludeExtras = !string.IsNullOrWhiteSpace(info.ExtrasKey) && config.ExtrasKeys.Contains(info.ExtrasKey);
// Check for update
var installer = new ModpackInstaller(config, info)
{
OverwriteRefTag = Options.RefTag,
OverwriteVersion = Options.Version,
Log = Log,
};
var result = installer.Check(updateOptions).Result;
if (result.HasUpdates)
{
var success = installer.Install(result).Result;
if (!silent)
Log.Info($"Installation {(success ?? false ? "completed successfully" : "failed")}!");
}
else if (!silent)
Log.Info("No updates available");
}
private static string CheckModpackConfigUrl(string configUrl, ModpackInfo info)
{
if (string.IsNullOrWhiteSpace(configUrl) && !string.IsNullOrWhiteSpace(info.ConfigUrl))
return info.ConfigUrl;
return configUrl;
}
}

View File

@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- https://go.microsoft.com/fwlink/?LinkID=208121. -->
<Project>
<PropertyGroup>
<Configuration>Release</Configuration>
<Platform>Any CPU</Platform>
<PublishDir>..\publish\cli\linux-arm64</PublishDir>
<PublishProtocol>FileSystem</PublishProtocol>
<_TargetId>Folder</_TargetId>
<TargetFramework>net8.0</TargetFramework>
<RuntimeIdentifier>linux-arm64</RuntimeIdentifier>
<SelfContained>true</SelfContained>
<PublishSingleFile>true</PublishSingleFile>
<PublishTrimmed>true</PublishTrimmed>
</PropertyGroup>
</Project>

View File

@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- https://go.microsoft.com/fwlink/?LinkID=208121. -->
<Project>
<PropertyGroup>
<Configuration>Release</Configuration>
<Platform>Any CPU</Platform>
<PublishDir>..\publish\cli\linux-x64</PublishDir>
<PublishProtocol>FileSystem</PublishProtocol>
<_TargetId>Folder</_TargetId>
<TargetFramework>net8.0</TargetFramework>
<RuntimeIdentifier>linux-x64</RuntimeIdentifier>
<SelfContained>true</SelfContained>
<PublishSingleFile>true</PublishSingleFile>
<PublishTrimmed>true</PublishTrimmed>
</PropertyGroup>
</Project>

View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- https://go.microsoft.com/fwlink/?LinkID=208121. -->
<Project>
<PropertyGroup>
<Configuration>Release</Configuration>
<Platform>Any CPU</Platform>
<PublishDir>..\publish\cli\windows\</PublishDir>
<PublishProtocol>FileSystem</PublishProtocol>
<_TargetId>Folder</_TargetId>
<TargetFramework>net8.0</TargetFramework>
<RuntimeIdentifier>win-x86</RuntimeIdentifier>
<SelfContained>true</SelfContained>
<PublishSingleFile>true</PublishSingleFile>
<PublishReadyToRun>false</PublishReadyToRun>
<PublishTrimmed>true</PublishTrimmed>
</PropertyGroup>
</Project>

View File

@@ -0,0 +1,10 @@
using ModpackUpdater.Manager;
namespace ModpackUpdater.Apps.Client;
public class UpdateCheckOptionsAdv : UpdateCheckOptions
{
public string? ProfileFolder { get; set; }
public string? ModpackConfig { get; set; }
public string? ExtrasKey { get; set; }
}

View File

@@ -0,0 +1,90 @@
# PUPNET DEPLOY: 1.9.1
# Use: 'pupnet --help conf' for information.
# APP PREAMBLE
AppBaseName = MinecraftModpackUpdaterCli
AppFriendlyName = Minecraft Modpack Updater CLI
AppId = de.pilzinsel64.minecraft-modpack-updater-cli
AppVersionRelease = 1.0.0[1]
AppShortSummary = Install and Update Minecraft Modpacks easliy.
AppDescription = """
Minecraft Modpack Updater is a simple tool to install and update a modpack to a selected minecraft profile by a selected modpack configuration file. It downloads a config file via https and checks the version there and what files has been changed and download the updateded files via a given link from the config.
"""
AppLicenseId = MIT
AppLicenseFile = ../LICENSE
AppChangeFile =
# PUBLISHER
PublisherName = Pilzinsel64
PublisherId = de.pilzinsel64
PublisherCopyright = Copyright (C) Pilzinsel64 2025
PublisherLinkName = Pilzinsel64 Homepage
PublisherLinkUrl = https://pilzinsel64.de
PublisherEmail =
# DESKTOP INTEGRATION
DesktopNoDisplay = true
DesktopTerminal = false
DesktopFile =
StartCommand =
PrimeCategory =
MetaFile =
IconFiles = """
"""
# DOTNET PUBLISH
DotnetProjectPath = ModpackUpdater.Apps.Client.csproj
DotnetPublishArgs = -p:Version=${APP_VERSION} --self-contained true -p:DebugType=None -p:DebugSymbols=false -p:PublishSingleFile=true -p:PublishTrimmed=true
DotnetPostPublish =
DotnetPostPublishOnWindows =
# PACKAGE OUTPUT
PackageName = minecraft-modpack-updater-cli
OutputDirectory = ../publish/client-cli
# APPIMAGE OPTIONS
AppImageArgs =
AppImageRuntimePath =
AppImageVersionOutput = false
# FLATPAK OPTIONS
FlatpakPlatformRuntime = org.freedesktop.Platform
FlatpakPlatformSdk = org.freedesktop.Sdk
FlatpakPlatformVersion = 25.08
FlatpakFinishArgs = """
--socket=wayland
--socket=fallback-x11
--filesystem=home
--share=network
"""
FlatpakBuilderArgs =
# RPM OPTIONS
RpmAutoReq = false
RpmAutoProv = true
RpmRequires = """
krb5-libs
libicu
openssl-libs
"""
# DEBIAN OPTIONS
DebianRecommends = """
libc6
libgcc1
libgssapi-krb5-2
libicu70
libssl3
libstdc++6
zlib1g
"""
# WINDOWS SETUP OPTIONS
SetupGroupName =
SetupAdminInstall = false
SetupCommandPrompt =
SetupMinWindowsVersion = 10
SetupSignTool =
SetupSuffixOutput =
SetupVersionOutput = false
SetupUninstallScript =

View File

@@ -0,0 +1,8 @@
namespace ModpackUpdater.Apps.Manager.Api;
public static class FeatureTypes
{
public static string Workspace => "workspace";
public static string Tools => "tools";
public static string ActionsContextMenu => "cm.actions";
}

View File

@@ -0,0 +1,12 @@
using Avalonia.Controls;
using ModpackUpdater.Apps.Manager.Ui.Models;
using ModpackUpdater.Apps.Manager.Ui.Models.MainWindow;
namespace ModpackUpdater.Apps.Manager.Api.Model;
public interface IMainApi
{
Window MainWindow { get; }
MainWindowViewModel Model { get; }
bool HasClosed { get; }
}

View File

@@ -0,0 +1,19 @@
using System.Diagnostics.CodeAnalysis;
namespace ModpackUpdater.Apps.Manager.Api.Model;
public interface IWorkspace
{
WorkspaceConfig Config { get; }
ModpackConfig? ModpackConfig { get; }
InstallInfos? InstallInfos { get; }
UpdateInfos? UpdateInfos { get; }
[MemberNotNullWhen(true, nameof(InstallInfos), nameof(UpdateInfos))]
Task<bool> Load();
Task<bool> Save();
}

View File

@@ -0,0 +1,14 @@
using Newtonsoft.Json;
namespace ModpackUpdater.Apps.Manager.Api.Model;
public abstract class WorkspaceConfig
{
[JsonProperty]
public string ProviderId { get; internal set; } = "origin.unknown";
[JsonIgnore]
public abstract string DisplayText { get; }
public string? ModpackConfigUrl { get; set; }
}

View File

@@ -0,0 +1,8 @@
namespace ModpackUpdater.Apps.Manager.Api.Model;
public class WorkspaceContext(IMainApi mainApi, IWorkspace? workspace)
{
public IMainApi MainApi => mainApi;
public IWorkspace? Workspace { get; set; } = workspace;
public bool Canceled { get; set; }
}

View File

@@ -0,0 +1,30 @@
using ModpackUpdater.Apps.Manager.Api.Model;
using Pilz.Features;
namespace ModpackUpdater.Apps.Manager.Api.Plugins.Features;
public abstract class WorkspaceFeature(string identifier, string name) : PluginFeature(FeatureTypes.Workspace, identifier, name)
{
public virtual bool CanConfigure(IWorkspace workspace)
{
return workspace?.Config == null || workspace.Config.ProviderId == Identifier;
}
public virtual async Task Configure(WorkspaceContext context)
{
await OnConfigure(context);
if (!context.Canceled && context.Workspace?.Config is not null)
context.Workspace.Config.ProviderId = Identifier;
}
public virtual IWorkspace CreateFromConfig(WorkspaceConfig config)
{
OnCreate(out var workspace, config);
return workspace;
}
protected abstract void OnCreate(out IWorkspace workspace, WorkspaceConfig config);
protected abstract Task OnConfigure(WorkspaceContext context);
}

View File

@@ -0,0 +1,9 @@
using ModpackUpdater.Apps.Manager.Api.Model;
using Pilz.Features;
namespace ModpackUpdater.Apps.Manager.Api.Plugins.Params;
public class MainApiParameters(IMainApi api) : PluginFunctionParameter
{
public IMainApi Api { get; } = api;
}

View File

@@ -0,0 +1,15 @@
<Application xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="ModpackUpdater.Apps.Manager.App"
RequestedThemeVariant="Default">
<!-- "Default" ThemeVariant follows system theme variant. "Dark" or "Light" are other available options. -->
<Application.Styles>
<FluentTheme DensityStyle="Normal" />
<!-- <FluentTheme DensityStyle="Compact" /> -->
<!-- <SimpleTheme /> -->
<StyleInclude Source="avares://Avalonia.Controls.DataGrid/Themes/Fluent.xaml"/>
<!-- <StyleInclude Source="avares://Avalonia.Controls.DataGrid/Themes/Simple.xaml"/> -->
<StyleInclude Source="avares://Pilz.UI.AvaloniaUI/Assets/Styles/EnhancedDefaults.axaml"/>
</Application.Styles>
</Application>

View File

@@ -0,0 +1,24 @@
using Avalonia;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Markup.Xaml;
using ModpackUpdater.Apps.Manager.Ui;
using Pilz.Features;
namespace ModpackUpdater.Apps.Manager;
public partial class App : Application
{
public override void Initialize()
{
AvaloniaXamlLoader.Load(this);
AppGlobals.Initialize();
PluginFeatureController.Instance.RegisterAllOwn();
}
public override void OnFrameworkInitializationCompleted()
{
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
desktop.MainWindow = new MainWindow();
base.OnFrameworkInitializationCompleted();
}
}

View File

@@ -0,0 +1 @@
[assembly: PropertyChanged.FilterType("ModpackUpdater.Apps.Manager.Ui.Models")]

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 KiB

View File

@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<svg width="480" height="480" viewBox="0 0 480 480" fill="none" xmlns="http://www.w3.org/2000/svg">
<defs>
<clipPath id="clip_path_1">
<rect width="480" height="480" />
</clipPath>
</defs>
<g clip-path="url(#clip_path_1)">
<path d="M360 40L180 40L140 0C140 0 40 0 40 0C17.91 0 0 17.91 0 40C0 40 0 120 0 120L400 120C400 120 400 80 400 80C400 57.91 382.09 40 360 40C360 40 360 40 360 40Z" fill="#FFA000" transform="translate(20 100)" />
<g transform="translate(53 -7)">
<path d="M160.042 63.3334L0 7.91667L13.9167 213.75L160.042 300.833L278.333 205.833L292.25 0L160.042 63.3334Z" fill="#5D4037" fill-rule="evenodd" transform="translate(20.875 63.333)" />
<path d="M0 0L13.9167 205.834L160.042 292.917L160.042 55.4167L0 0Z" fill="#8D6E63" fill-rule="evenodd" transform="translate(20.875 71.25)" />
<path d="M13.9167 112.084L13.9167 79.1667L27.8333 72.2554L27.8333 95L41.75 87.0833L41.75 118.75L62.625 109.028L62.625 55.4167L76.5417 48.6954L76.5417 87.0833L90.4583 80.5679L90.4583 63.3333L104.375 55.4167L104.375 35.3637L118.292 28.6979L118.292 47.5L129.425 41.1667L132.208 0L0 63.3333L0 118.75L13.9167 112.084Z" fill="#43A047" fill-rule="evenodd" transform="translate(180.917 63.333)" />
<path d="M0 47.5L139.167 0L292.25 39.5833L160.042 102.917L0 47.5Z" fill="#B2FF59" fill-rule="evenodd" transform="translate(20.875 23.75)" />
<path d="M0 0L2.78333 40.6442L13.9167 44.3888L13.9167 34.9363L27.8333 39.5833L27.8333 65.0513L48.7083 72.2792L48.7083 34.6196L62.625 39.5833L62.625 77.1004L76.5417 81.9217L76.5417 58.9713L90.4583 63.7925L90.4583 86.743L104.375 91.5563L104.375 82.1671L118.292 87.0834L118.292 74.6067L132.208 79.1667L132.208 101.199L160.042 110.833L160.042 55.4167L0 0Z" fill="#66BB6A" fill-rule="evenodd" transform="translate(20.875 71.25)" />
</g>
<path d="M360 0C360 0 40 0 40 0C17.91 0 0 15.1595 0 33.8571C0 33.8571 0 203.143 0 203.143C0 221.84 17.91 237 40 237C40 237 360 237 360 237C382.09 237 400 221.84 400 203.143C400 203.143 400 33.8571 400 33.8571C400 15.1595 382.09 0 360 0C360 0 360 0 360 0Z" fill="#FFCA28" transform="translate(20 183)" />
<path d="M70 80L0 0L140 0L70 80Z" fill="#1565C0" transform="translate(150 380)" />
<path d="M0 0L60 0L60 111.25L0 111.25L0 0Z" fill="#1565C0" transform="translate(190 280)" />
<path d="M30 15C30 23.28 23.29 30 15 30C6.71002 30 0 23.28 0 15C0 6.72 6.71002 0 15 0C23.29 0 30 6.72 30 15C30 15 30 15 30 15Z" fill="#4A148C" transform="translate(430 60)" />
<path d="M125 100C72.62 100 30 57.38 30 5C30 5 30 0 30 0L0 0L0 10C0 10 0.25 10 0.25 10C2.84 74.93 55.08 127.16 120 129.75C120 129.75 120 130 120 130L130 130L130 100L125 100C125 100 125 100 125 100Z" fill="#9C27B0" transform="translate(330 60)" />
<path d="M75 50C50.19 50 30 29.81 30 5C30 5 30 0 30 0L0 0L0 10C0 10 0.25 10 0.25 10C2.73004 47.36 32.63 77.27 70 79.75C70 79.75 70 80 70 80L80 80L80 50L75 50C75 50 75 50 75 50Z" fill="#7B1FA2" transform="translate(380 60)" />
<path d="M175 150C95.05 150 30 84.95 30 5C30 5 30 0 30 0L0 0L0 10C0 10 0.25 10 0.25 10C2.88998 102.5 77.51 177.11 170 179.75C170 179.75 170 180 170 180L180 180L180 150L175 150C175 150 175 150 175 150Z" fill="#BA68C8" transform="translate(280 60)" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.2 KiB

View File

@@ -0,0 +1,27 @@
using ModpackUpdater.Apps.Manager.Api;
using ModpackUpdater.Apps.Manager.Api.Plugins.Params;
using ModpackUpdater.Apps.Manager.LangRes;
using Pilz.Features;
using Pilz.UI.Symbols;
namespace ModpackUpdater.Apps.Manager.Features.CM;
internal class CheckSingleActionHealthyFeature : PluginFunction, IPluginFeatureProvider<CheckSingleActionHealthyFeature>
{
public static CheckSingleActionHealthyFeature Instance { get; } = new();
public CheckSingleActionHealthyFeature() : base(FeatureTypes.ActionsContextMenu, "origin.checksingleactionhearlthy", FeatureNamesLangRes.CheckSingleActionHealthy)
{
Icon = AppGlobals.Symbols.GetImage(AppSymbols.heart_with_pulse, SymbolSize.Small);
}
protected override async Task<object?> ExecuteFunctionAsync(PluginFunctionParameter? @params)
{
if (@params is not MainApiParameters p || p.Api.Model.SelectedGridRow is not { } row)
return null;
await SharedFunctions.CheckActionHealthy(p.Api, row);
return null;
}
}

View File

@@ -0,0 +1,24 @@
using ModpackUpdater.Apps.Manager.Api;
using ModpackUpdater.Apps.Manager.Api.Plugins.Params;
using ModpackUpdater.Apps.Manager.LangRes;
using Pilz.Features;
using Pilz.UI.Symbols;
namespace ModpackUpdater.Apps.Manager.Features.CM;
internal class ClearDirectLinkFeature : PluginFunction, IPluginFeatureProvider<ClearDirectLinkFeature>
{
public static ClearDirectLinkFeature Instance { get; } = new();
public ClearDirectLinkFeature() : base(FeatureTypes.ActionsContextMenu, "origin.cleardirectlink", FeatureNamesLangRes.ClearDirectLinkFeature)
{
Icon = AppGlobals.Symbols.GetImage(AppSymbols.broom, SymbolSize.Small);
}
protected override Task<object?> ExecuteFunctionAsync(PluginFunctionParameter? @params)
{
if (@params is MainApiParameters p && p.Api.Model.SelectedGridRow is { } row)
SharedFunctions.ClearDirectLinks(p.Api, row);
return Task.FromResult<object?>(null);
}
}

View File

@@ -0,0 +1,26 @@
using ModpackUpdater.Apps.Manager.Api;
using ModpackUpdater.Apps.Manager.Api.Plugins.Params;
using ModpackUpdater.Apps.Manager.LangRes;
using Pilz.Features;
using Pilz.UI.Symbols;
namespace ModpackUpdater.Apps.Manager.Features.CM;
internal class UpdateCollectorFeature : PluginFunction, IPluginFeatureProvider<UpdateCollectorFeature>
{
public static UpdateCollectorFeature Instance { get; } = new();
public UpdateCollectorFeature() : base(FeatureTypes.ActionsContextMenu, "origin.updatecollector", FeatureNamesLangRes.UpdateCollectorFeature)
{
Icon = AppGlobals.Symbols.GetImage(AppSymbols.search, SymbolSize.Small);
}
protected override async Task<object?> ExecuteFunctionAsync(PluginFunctionParameter? @params)
{
if (@params is not MainApiParameters p || p.Api.Model.SelectedGridRow is not { } row)
return null;
await SharedFunctions.CollectUpdates(p.Api, row.Action);
return null;
}
}

View File

@@ -0,0 +1,28 @@
using ModpackUpdater.Apps.Manager.Api;
using ModpackUpdater.Apps.Manager.Api.Plugins.Params;
using ModpackUpdater.Apps.Manager.LangRes;
using ModpackUpdater.Apps.Manager.Ui.Models.MainWindow;
using Pilz.Features;
using Pilz.UI.Symbols;
namespace ModpackUpdater.Apps.Manager.Features.CM;
internal class UpdateDirectLinkFeature : PluginFunction, IPluginFeatureProvider<UpdateDirectLinkFeature>
{
public static UpdateDirectLinkFeature Instance { get; } = new();
public UpdateDirectLinkFeature() : base(FeatureTypes.ActionsContextMenu, "origin.updatedirectlink", FeatureNamesLangRes.UpdateDirectLinkFeature)
{
Icon = AppGlobals.Symbols.GetImage(AppSymbols.renew, SymbolSize.Small);
}
protected override async Task<object?> ExecuteFunctionAsync(PluginFunctionParameter? @params)
{
if (@params is not MainApiParameters p || p.Api.Model.SelectedGridRow is not MainWindowGridRow row)
return null;
await SharedFunctions.FindNewDirectLinks(p.Api, row);
return null;
}
}

View File

@@ -0,0 +1,284 @@
using System.Text;
using Avalonia.Media;
using ModpackUpdater.Apps.Manager.Api.Model;
using ModpackUpdater.Apps.Manager.LangRes;
using ModpackUpdater.Apps.Manager.Ui;
using ModpackUpdater.Apps.Manager.Ui.Models.MainWindow;
using ModpackUpdater.Apps.Manager.Ui.Models.UpdatesCollectorViewMode;
using ModpackUpdater.Manager;
using OfficeOpenXml;
using OfficeOpenXml.Table;
using Pilz.UI.AvaloniaUI.Dialogs;
namespace ModpackUpdater.Apps.Manager.Features;
internal static class SharedFunctions
{
private static readonly IImage? imageSourceSuccess = AppGlobals.Symbols.GetImageSource(AppSymbols.done);
private static readonly IImage? imageSourceWorking = AppGlobals.Symbols.GetImageSource(AppSymbols.hourglass);
private static readonly IImage? imageSourceFailed = AppGlobals.Symbols.GetImageSource(AppSymbols.close);
public static async Task CheckActionHealthy(IMainApi api, params MainWindowGridRow[] rows)
{
var rowsCount = rows.Length;
var failed = false;
var factory = new ModpackFactory();
api.Model.Progress.Start(rowsCount);
for (var i = 0; i < rowsCount; i++)
{
var row = rows[i];
if (row.SourceType == SourceType.DirectLink)
continue;
row.StateImage = imageSourceWorking;
try
{
var result = await factory.ResolveSourceUrl(row.Action);
failed = string.IsNullOrWhiteSpace(result);
}
catch
{
// Ignore
}
if (api.HasClosed)
return;
row.StateImage = failed ? imageSourceFailed : imageSourceSuccess;
api.Model.Progress.Set(i);
}
api.Model.Progress.Stop();
}
public static async Task<bool> CollectUpdates(IMainApi api, params InstallAction[] actions)
{
if (api.Model.CurrentWorkspace?.UpdateInfos is null || api.Model.CurrentTreeNodes is null)
return false;
// Collect updates
var result = await AvaloniaFlyoutBase.Show(new UpdatesCollectorView(api.Model.CurrentWorkspace, actions), api.MainWindow);
if (api.HasClosed || result.Result is not ModUpdates resultUpdates)
return false;
// Collect versions with changes
var updates = resultUpdates.List.Where(update => update.Origin.SourceTag != update.AvailableVersions[update.NewVersion].Tag).ToList();
// Path install actions
foreach (var update in updates)
{
var sourceTag = update.AvailableVersions[update.NewVersion].Tag;
if (api.Model.CurrentGridRows.List.Items.FirstOrDefault(n => n.Action == update.Origin) is { } row)
row.SourceTag = sourceTag;
else
update.Origin.SourceTag = sourceTag;
}
// Create update actions
var updateSet = new UpdateInfo();
foreach (var update in updates)
{
updateSet.Actions.Add(new()
{
InheritFrom = update.Origin.Id,
});
}
api.Model.CurrentWorkspace.UpdateInfos.Updates.Insert(0, updateSet);
// Add update to ui
api.Model.CurrentTreeNodes[1].Nodes.Insert(0, new ActionSetTreeNode(updateSet));
return true;
}
public static async Task FindNewDirectLinks(IMainApi api, params MainWindowGridRow[] rows)
{
var factory = new ModpackFactory();
api.Model.Progress.Start(rows.Length);
for (var i = 0; i < rows.Length; i++)
{
var row = rows[i];
if (row.SourceType == SourceType.DirectLink)
continue;
row.StateImage = imageSourceWorking;
try
{
row.SourceUrl = await factory.ResolveSourceUrl(row.Action);
row.StateImage = imageSourceSuccess;
}
catch (Exception)
{
row.StateImage = imageSourceFailed;
}
if (api.HasClosed)
return;
api.Model.Progress.Set(i);
}
api.Model.Progress.Stop();
}
public static void ClearDirectLinks(IMainApi api, params MainWindowGridRow[] rows)
{
api.Model.Progress.Start(rows.Length);
for (var i = 0; i < rows.Length; i++)
{
var row = rows[i];
if (row.SourceType != SourceType.DirectLink)
row.SourceUrl = null;
row.StateImage = null;
api.Model.Progress.Set(i);
}
api.Model.Progress.Stop();
}
public static string GenerateChangelog(InstallInfos installInfos, UpdateInfo updateInfos)
{
var log = new StringBuilder();
foreach (var action in updateInfos.Actions.OrderBy(n => n.Type))
{
// Create copy
var copy = new UpdateAction();
ModpackInstaller.DuplicateTo(action, copy);
// Resolve inherit
if (!string.IsNullOrWhiteSpace(copy.InheritFrom))
ModpackInstaller.ResolveInherit(copy, installInfos);
if (string.IsNullOrWhiteSpace(copy.Name) || copy.Type != UpdateActionType.Update)
continue;
// Append bullet
log.Append('-');
// Append action indicator
log.Append(' ');
log.Append("⚒️");
// Append name
log.Append(' ');
log.Append("**");
log.Append(copy.Name);
log.Append("**");
// Append new version
if (!string.IsNullOrWhiteSpace(copy.SourceTag))
{
log.Append(' ');
log.Append('`');
log.Append(copy.SourceTag);
log.Append('`');
}
// Append new line
log.AppendLine();
}
return log.ToString().TrimEnd();
}
public static string GenerateModlistAsMarkdown(InstallInfos installInfos)
{
var sb = new StringBuilder();
sb.Append("|" + ActionsListLangRes.Col_Name);
sb.Append("|" + ActionsListLangRes.Col_SrcTag);
sb.Append("|" + ActionsListLangRes.Col_Side);
sb.Append("|" + ActionsListLangRes.Col_SrcType);
sb.Append("|" + ActionsListLangRes.Col_SrcOwner);
sb.Append("|" + ActionsListLangRes.Col_SrcName);
sb.AppendLine("|");
sb.AppendLine("|---|---|---|---|---|---|");
// Rows
foreach (var action in installInfos.Actions.OrderBy(n => n.Name))
{
if (action.IsExtra || action.IsZip || string.IsNullOrWhiteSpace(action.Id) || !action.Id.StartsWith("mod:"))
continue;
if (string.IsNullOrWhiteSpace(action.Website))
sb.Append($"|{action.Name}");
else
sb.Append($"|[{action.Name}]({action.Website})");
if (string.IsNullOrWhiteSpace(action.SourceUrl))
sb.Append($"|{action.SourceTag}");
else
sb.Append($"|[{action.SourceTag}]({action.GetSourceUrl(installInfos.Version)})");
sb.Append($"|{action.Side.ToString()}");
sb.Append($"|{action.SourceType}");
sb.Append($"|{action.SourceOwner}");
sb.Append($"|{action.SourceName}");
sb.AppendLine("|");
}
return sb.ToString().TrimEnd();
}
public static ExcelPackage GenerateModlistAsExcel(InstallInfos installInfos)
{
var pkg = new ExcelPackage();
var ws = pkg.Workbook.Worksheets.Add(string.Format(GeneralLangRes.ModlistForVersionX, installInfos.Version));
var cr = 1;
var cc = 1;
// Header
ws.Cells[cr, cc++].Value = ActionsListLangRes.Col_Name;
ws.Cells[cr, cc++].Value = ActionsListLangRes.Col_SrcTag;
ws.Cells[cr, cc++].Value = ActionsListLangRes.Col_Side;
ws.Cells[cr, cc++].Value = ActionsListLangRes.Col_SrcType;
ws.Cells[cr, cc++].Value = ActionsListLangRes.Col_SrcOwner;
ws.Cells[cr, cc++].Value = ActionsListLangRes.Col_SrcName;
cr += 1;
cc = 1;
// Rows
foreach (var action in installInfos.Actions.OrderBy(n => n.Name))
{
if (action.IsExtra || action.IsZip || string.IsNullOrWhiteSpace(action.Id) || !action.Id.StartsWith("mod:"))
continue;
var cellName = ws.Cells[cr, cc++];
cellName.Value = action.Name;
if (!string.IsNullOrWhiteSpace(action.Website))
cellName.SetHyperlink(new Uri(action.Website));
var cellTag = ws.Cells[cr, cc++];
cellTag.Value = string.IsNullOrWhiteSpace(action.SourceTag) ? "direct link" : action.SourceTag;
if (!string.IsNullOrWhiteSpace(action.SourceUrl))
cellTag.SetHyperlink(new Uri(action.GetSourceUrl(installInfos.Version)));
ws.Cells[cr, cc++].Value = action.Side.ToString();
ws.Cells[cr, cc++].Value = action.SourceType;
ws.Cells[cr, cc++].Value = action.SourceOwner;
ws.Cells[cr, cc++].Value = action.SourceName;
cr += 1;
cc = 1;
}
// Styling
cc = 1;
ws.Column(cc++).Width = 30;
ws.Column(cc++).Width = 20;
ws.Column(cc++).Width = 10;
ws.Column(cc++).Width = 20;
ws.Column(cc++).Width = 20;
ws.Column(cc++).Width = 30;
var tableDef = ws.Tables.Add(ws.Cells[1, 1, cr - 1, cc - 1], "Table");
tableDef.TableStyle = TableStyles.Medium16;
tableDef.ShowHeader = true;
return pkg;
}
}

View File

@@ -0,0 +1,27 @@
using ModpackUpdater.Apps.Manager.Api;
using ModpackUpdater.Apps.Manager.Api.Plugins.Params;
using ModpackUpdater.Apps.Manager.LangRes;
using Pilz.Features;
using Pilz.UI.Symbols;
namespace ModpackUpdater.Apps.Manager.Features.Tools;
internal class CheckAllActionsHealthyFeature : PluginFunction, IPluginFeatureProvider<CheckAllActionsHealthyFeature>
{
public static CheckAllActionsHealthyFeature Instance { get; } = new();
public CheckAllActionsHealthyFeature() : base(FeatureTypes.Tools, "origin.checkallactionshearlthy", FeatureNamesLangRes.CheckAllActionsHealthy)
{
Icon = AppGlobals.Symbols.GetImage(AppSymbols.heart_with_pulse, SymbolSize.Small);
}
protected override async Task<object?> ExecuteFunctionAsync(PluginFunctionParameter? @params)
{
if (@params is not MainApiParameters p)
return null;
await SharedFunctions.CheckActionHealthy(p.Api, [.. p.Api.Model.CurrentGridRows.View]);
return null;
}
}

View File

@@ -0,0 +1,24 @@
using ModpackUpdater.Apps.Manager.Api;
using ModpackUpdater.Apps.Manager.Api.Plugins.Params;
using ModpackUpdater.Apps.Manager.LangRes;
using Pilz.Features;
using Pilz.UI.Symbols;
namespace ModpackUpdater.Apps.Manager.Features.Tools;
internal class ClearDirectLinksFeature : PluginFunction, IPluginFeatureProvider<ClearDirectLinksFeature>
{
public static ClearDirectLinksFeature Instance { get; } = new();
public ClearDirectLinksFeature() : base(FeatureTypes.Tools, "origin.cleardirectlinks", FeatureNamesLangRes.ClearDirectLinksFeature)
{
Icon = AppGlobals.Symbols.GetImage(AppSymbols.broom, SymbolSize.Small);
}
protected override Task<object?> ExecuteFunctionAsync(PluginFunctionParameter? @params)
{
if (@params is MainApiParameters p)
SharedFunctions.ClearDirectLinks(p.Api, [.. p.Api.Model.CurrentGridRows.View]);
return Task.FromResult<object?>(null);
}
}

View File

@@ -0,0 +1,38 @@
using ModpackUpdater.Apps.Manager.Api;
using ModpackUpdater.Apps.Manager.Api.Plugins.Params;
using ModpackUpdater.Apps.Manager.LangRes;
using ModpackUpdater.Apps.Manager.Ui.Models.MainWindow;
using MsBox.Avalonia;
using MsBox.Avalonia.Enums;
using Pilz.Features;
using Pilz.UI.Symbols;
namespace ModpackUpdater.Apps.Manager.Features.Tools;
internal class GenerateChangelogFeature : PluginFunction, IPluginFeatureProvider<GenerateChangelogFeature>
{
public static GenerateChangelogFeature Instance { get; } = new();
public GenerateChangelogFeature() : base(FeatureTypes.Tools, "origin.genchangelog", FeatureNamesLangRes.GenerateChangelogFeature)
{
Icon = AppGlobals.Symbols.GetImage(AppSymbols.time_machine, SymbolSize.Small);
}
protected override async Task<object?> ExecuteFunctionAsync(PluginFunctionParameter? @params)
{
if (@params is not MainApiParameters p
|| p.Api.Model.CurrentWorkspace?.InstallInfos is null
|| p.Api.Model.SelectedTreeNode is not ActionSetTreeNode node
|| node.Infos is not UpdateInfo updateInfo)
return null;
var changelog = SharedFunctions.GenerateChangelog(p.Api.Model.CurrentWorkspace.InstallInfos, updateInfo);
if (string.IsNullOrWhiteSpace(changelog))
return null;
await p.Api.MainWindow.Clipboard?.SetTextAsync(changelog);
await MessageBoxManager.GetMessageBoxStandard(MsgBoxLangRes.ChangelogCopiedToClipboard_Title, MsgBoxLangRes.ChangelogCopiedToClipboard, ButtonEnum.Ok, MsBox.Avalonia.Enums.Icon.Info).ShowAsPopupAsync(p.Api.MainWindow);
return null;
}
}

View File

@@ -0,0 +1,43 @@
using Avalonia.Controls;
using ModpackUpdater.Apps.Manager.Api;
using ModpackUpdater.Apps.Manager.Api.Plugins.Params;
using ModpackUpdater.Apps.Manager.LangRes;
using ModpackUpdater.Apps.Manager.Utils;
using MsBox.Avalonia;
using MsBox.Avalonia.Enums;
using Pilz.Features;
using Pilz.UI.Symbols;
namespace ModpackUpdater.Apps.Manager.Features.Tools;
internal class GenerateModlistAsExcelFeature : PluginFunction, IPluginFeatureProvider<GenerateModlistAsExcelFeature>
{
public static GenerateModlistAsExcelFeature Instance { get; } = new();
public GenerateModlistAsExcelFeature() : base(FeatureTypes.Tools, "origin.genmodlist.xlsx", FeatureNamesLangRes.GenerateModlistAsExcelFeature)
{
Icon = AppGlobals.Symbols.GetImage(AppSymbols.list_view, SymbolSize.Small);
}
protected override async Task<object?> ExecuteFunctionAsync(PluginFunctionParameter? @params)
{
if (@params is not MainApiParameters p || p.Api.Model.CurrentWorkspace?.InstallInfos is null)
return null;
// Generate excel
using var pkg = SharedFunctions.GenerateModlistAsExcel(p.Api.Model.CurrentWorkspace.InstallInfos);
// Ask for save
var file = await TopLevel.GetTopLevel(p.Api.MainWindow)!.StorageProvider.SaveFilePickerAsync(new()
{
FileTypeChoices = [MyFilePickerFileTypes.Excel]
});
if (file is null)
return null;
// Save file
await pkg.SaveAsAsync(file.Path.AbsolutePath);
await MessageBoxManager.GetMessageBoxStandard(MsgBoxLangRes.ModlistGenerated_Title, MsgBoxLangRes.ModlistGenerated, ButtonEnum.Ok, MsBox.Avalonia.Enums.Icon.Info).ShowAsPopupAsync(p.Api.MainWindow);
return null;
}
}

View File

@@ -0,0 +1,31 @@
using ModpackUpdater.Apps.Manager.Api;
using ModpackUpdater.Apps.Manager.Api.Plugins.Params;
using ModpackUpdater.Apps.Manager.LangRes;
using MsBox.Avalonia;
using MsBox.Avalonia.Enums;
using Pilz.Features;
using Pilz.UI.Symbols;
namespace ModpackUpdater.Apps.Manager.Features.Tools;
internal class GenerateModlistAsMarkdownFeature : PluginFunction, IPluginFeatureProvider<GenerateModlistAsMarkdownFeature>
{
public static GenerateModlistAsMarkdownFeature Instance { get; } = new();
public GenerateModlistAsMarkdownFeature() : base(FeatureTypes.Tools, "origin.genmodlist.md", FeatureNamesLangRes.GenerateModlistAsMarkdownFeature)
{
Icon = AppGlobals.Symbols.GetImage(AppSymbols.list_view, SymbolSize.Small);
}
protected override async Task<object?> ExecuteFunctionAsync(PluginFunctionParameter? @params)
{
if (@params is not MainApiParameters p || p.Api.Model.CurrentWorkspace?.InstallInfos is null)
return null;
p.Api.MainWindow.Clipboard?.SetTextAsync(SharedFunctions.GenerateModlistAsMarkdown(p.Api.Model.CurrentWorkspace.InstallInfos));
await MessageBoxManager.GetMessageBoxStandard(MsgBoxLangRes.ModlistCopiedToClipboard_Title, MsgBoxLangRes.ModlistCopiedToClipboard, ButtonEnum.Ok, MsBox.Avalonia.Enums.Icon.Info).ShowAsPopupAsync(p.Api.MainWindow);
return null;
}
}

View File

@@ -0,0 +1,27 @@
using ModpackUpdater.Apps.Manager.Api;
using ModpackUpdater.Apps.Manager.Api.Plugins.Params;
using ModpackUpdater.Apps.Manager.LangRes;
using Pilz.Features;
using Pilz.UI.Symbols;
namespace ModpackUpdater.Apps.Manager.Features.Tools;
internal class UpdateDirectLinksFeature : PluginFunction, IPluginFeatureProvider<UpdateDirectLinksFeature>
{
public static UpdateDirectLinksFeature Instance { get; } = new();
public UpdateDirectLinksFeature() : base(FeatureTypes.Tools, "origin.updatedirectlinks", FeatureNamesLangRes.UpdateDirectLinksFeature)
{
Icon = AppGlobals.Symbols.GetImage(AppSymbols.renew, SymbolSize.Small);
}
protected override async Task<object?> ExecuteFunctionAsync(PluginFunctionParameter? @params)
{
if (@params is not MainApiParameters p)
return null;
await SharedFunctions.FindNewDirectLinks(p.Api, [.. p.Api.Model.CurrentGridRows.View]);
return null;
}
}

View File

@@ -0,0 +1,27 @@
using ModpackUpdater.Apps.Manager.Api;
using ModpackUpdater.Apps.Manager.Api.Plugins.Params;
using ModpackUpdater.Apps.Manager.LangRes;
using Pilz.Features;
using Pilz.UI.Symbols;
namespace ModpackUpdater.Apps.Manager.Features.Tools;
internal class UpdatesCollectorFeature : PluginFunction, IPluginFeatureProvider<UpdatesCollectorFeature>
{
public static UpdatesCollectorFeature Instance { get; } = new();
public UpdatesCollectorFeature() : base(FeatureTypes.Tools, "origin.updatescollector", FeatureNamesLangRes.UpdatesCollectorFeature)
{
Icon = AppGlobals.Symbols.GetImage(AppSymbols.search, SymbolSize.Small);
}
protected override async Task<object?> ExecuteFunctionAsync(PluginFunctionParameter? @params)
{
if (@params is not MainApiParameters p || p.Api.Model.CurrentWorkspace?.InstallInfos is null)
return null;
await SharedFunctions.CollectUpdates(p.Api, [.. p.Api.Model.CurrentWorkspace.InstallInfos.Actions]);
return null;
}
}

View File

@@ -0,0 +1,93 @@
using ModpackUpdater.Apps.Manager.Api.Model;
using NGitLab;
using NGitLab.Models;
namespace ModpackUpdater.Apps.Manager.Features.Workspaces.GitLabRepo;
internal class GitLabRepoWorkspace(GitLabRepoWorkspaceConfig config) : IWorkspace
{
private string? rawInstallInfos;
private string? rawUpdateInfos;
public WorkspaceConfig Config => ConfigX;
public GitLabRepoWorkspaceConfig ConfigX { get; } = config;
public IGitLabClient Gitlab { get; } = new GitLabClient(config.InstanceUrl, config.ApiToken);
public ModpackConfig? ModpackConfig { get; private set; }
public InstallInfos? InstallInfos { get; private set; }
public UpdateInfos? UpdateInfos { get; private set; }
public async Task<bool> Load()
{
if (!string.IsNullOrWhiteSpace(Config.ModpackConfigUrl))
ModpackConfig = ModpackConfig.LoadFromUrl(Config.ModpackConfigUrl);
rawInstallInfos = await GetContent(ConfigX.FileLocationInstallJson);
InstallInfos = InstallInfos.Parse(rawInstallInfos);
rawUpdateInfos = await GetContent(ConfigX.FileLocationUpdateJson);
UpdateInfos = UpdateInfos.Parse(rawUpdateInfos);
ConfigX.RepoName = (await Gitlab.Projects.GetByIdAsync((int)ConfigX.RepoId, new())).Name;
return InstallInfos != null && UpdateInfos != null;
}
public async Task<bool> Save()
{
var failed = true;
if (InstallInfos != null)
{
var newInstallInfos = InstallInfos.ToString();
if (newInstallInfos != rawInstallInfos)
{
if (await SaveContent(ConfigX.FileLocationInstallJson, newInstallInfos))
rawInstallInfos = newInstallInfos;
else
failed = true;
}
}
if (UpdateInfos != null)
{
var newUpdateInfos = UpdateInfos.ToString();
if (newUpdateInfos != rawUpdateInfos)
{
if (await SaveContent(ConfigX.FileLocationUpdateJson, newUpdateInfos))
rawUpdateInfos = newUpdateInfos;
else
failed = true;
}
}
return failed;
}
private async Task<string> GetContent(string path)
{
var repoId = new ProjectId(ConfigX.RepoId);
var repo = Gitlab.GetRepository(repoId);
var data = await repo.Files.GetAsync(path, ConfigX.RepoBranche);
return data.DecodedContent;
}
private Task<bool> SaveContent(string path, string content)
{
var repoId = new ProjectId(ConfigX.RepoId);
var repo = Gitlab.GetRepository(repoId);
var update = new FileUpsert
{
Branch = ConfigX.RepoBranche,
CommitMessage = "update " + Path.GetFileName(path),
RawContent = content,
Path = path,
};
if (repo.Files.FileExists(path, ConfigX.RepoBranche))
repo.Files.Update(update);
else
repo.Files.Create(update);
return Task.FromResult(true);
}
}

View File

@@ -0,0 +1,20 @@
using ModpackUpdater.Apps.Manager.Api.Model;
namespace ModpackUpdater.Apps.Manager.Features.Workspaces.GitLabRepo;
public class GitLabRepoWorkspaceConfig : WorkspaceConfig, ICloneable
{
public override string DisplayText => $"{RepoName ?? "?"} | {RepoBranche} | {InstanceUrl}";
public string? RepoName { get; set; }
public string InstanceUrl { get; set; } = "https://gitlab.com";
public string? ApiToken { get; set; }
public long RepoId { get; set; } = 0L;
public string RepoBranche { get; set; } = "master";
public string FileLocationInstallJson { get; set; } = "install.json";
public string FileLocationUpdateJson { get; set; } = "updates.json";
public object Clone()
{
return MemberwiseClone();
}
}

View File

@@ -0,0 +1,37 @@
<dialogs:AvaloniaFlyoutBase
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:dialogs="https://git.pilzinsel64.de/pilz-framework/pilz"
xmlns:gitLabRepo="clr-namespace:ModpackUpdater.Apps.Manager.Features.Workspaces.GitLabRepo"
xmlns:langRes="clr-namespace:ModpackUpdater.Apps.Manager.LangRes"
mc:Ignorable="d"
d:DesignWidth="800"
d:DesignHeight="450"
Width="300"
x:Class="ModpackUpdater.Apps.Manager.Features.Workspaces.GitLabRepo.GitLabRepoWorkspaceConfigEditorView"
x:DataType="gitLabRepo:GitLabRepoWorkspaceConfig">
<dialogs:AvaloniaFlyoutBase.MainContent>
<StackPanel Spacing="6">
<Label Target="TextBoxInstanceUrl" Content="{x:Static langRes:GeneralLangRes.GitLabInstanceUrl}"/>
<TextBox Name="TextBoxInstanceUrl" Text="{Binding InstanceUrl}"/>
<Label Target="TextBoxApiToken" Content="{x:Static langRes:GeneralLangRes.GitLabApiToken}"/>
<TextBox Name="TextBoxApiToken" Text="{Binding ApiToken}"/>
<Label Target="NumericUpDownRepoId" Content="{x:Static langRes:GeneralLangRes.RepositoryId}"/>
<NumericUpDown Name="NumericUpDownRepoId" Value="{Binding RepoId}"/>
<Label Target="TextBoxInstallJson" Content="{x:Static langRes:GeneralLangRes.FileLocationOfInstallJson}"/>
<TextBox Name="TextBoxInstallJson" Text="{Binding FileLocationInstallJson}"/>
<Label Target="TextBoxUpdateJson" Content="{x:Static langRes:GeneralLangRes.FileLocationOfUpdateJson}"/>
<TextBox Name="TextBoxUpdateJson" Text="{Binding FileLocationUpdateJson}"/>
<Label Target="TextBoxModpackConfigUrl" Content="{x:Static langRes:GeneralLangRes.ModpackConfigUrl}"/>
<TextBox Name="TextBoxModpackConfigUrl" Text="{Binding ModpackConfigUrl}"/>
</StackPanel>
</dialogs:AvaloniaFlyoutBase.MainContent>
</dialogs:AvaloniaFlyoutBase>

View File

@@ -0,0 +1,23 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using Pilz.UI.AvaloniaUI.Dialogs;
namespace ModpackUpdater.Apps.Manager.Features.Workspaces.GitLabRepo;
public partial class GitLabRepoWorkspaceConfigEditorView : AvaloniaFlyoutBase
{
public GitLabRepoWorkspaceConfig Settings { get; }
public GitLabRepoWorkspaceConfigEditorView(GitLabRepoWorkspaceConfig settings)
{
Settings = settings;
DataContext = settings;
InitializeComponent();
}
protected override object? GetResult()
{
return Settings;
}
}

View File

@@ -0,0 +1,37 @@
using ModpackUpdater.Apps.Manager.Api.Model;
using ModpackUpdater.Apps.Manager.Api.Plugins.Features;
using ModpackUpdater.Apps.Manager.LangRes;
using Pilz.Features;
using Pilz.UI.AvaloniaUI.Dialogs;
using Pilz.UI.Symbols;
namespace ModpackUpdater.Apps.Manager.Features.Workspaces.GitLabRepo;
internal class GitLabRepoWorkspaceFeature() : WorkspaceFeature("origin.gitlab", FeatureNamesLangRes.GitLabWorkspace), IPluginFeatureProvider<GitLabRepoWorkspaceFeature>
{
public static GitLabRepoWorkspaceFeature Instance { get; } = new();
public override object? Icon => AppGlobals.Symbols.GetImage(AppSymbols.gitlab, SymbolSize.Small);
protected override async Task OnConfigure(WorkspaceContext context)
{
var settings = context.Workspace?.Config as GitLabRepoWorkspaceConfig ?? new();
if ((await AvaloniaFlyoutBase.Show(
new GitLabRepoWorkspaceConfigEditorView((GitLabRepoWorkspaceConfig)settings.Clone()),
context.MainApi.MainWindow,
TitlesLangRes.GitLabRepoWorkspaceEditor,
AppGlobals.Symbols.GetImageSource(AppSymbols.gitlab)))
.Result is GitLabRepoWorkspaceConfig settingsNew)
context.Workspace = new GitLabRepoWorkspace(settingsNew);
else
context.Canceled = true;
}
protected override void OnCreate(out IWorkspace workspace, WorkspaceConfig config)
{
if (config is not GitLabRepoWorkspaceConfig gitlabConfig)
throw new NotSupportedException($"Only configs of type {nameof(GitLabRepoWorkspaceConfig)} are allowed.");
workspace = new GitLabRepoWorkspace(gitlabConfig);
}
}

View File

@@ -0,0 +1,85 @@
using ModpackUpdater.Apps.Manager.Api.Model;
namespace ModpackUpdater.Apps.Manager.Features.Workspaces.LocalFolder;
internal class LocalFolderWorkspace(LocalFolderWorkspaceConfig config) : IWorkspace
{
private string? rawInstallInfos;
private string? rawUpdateInfos;
public WorkspaceConfig Config => ConfigX;
public LocalFolderWorkspaceConfig ConfigX { get; } = config;
public ModpackConfig? ModpackConfig { get; private set; }
public InstallInfos? InstallInfos { get; private set; }
public UpdateInfos? UpdateInfos { get; private set; }
public async Task<bool> Load()
{
if (!string.IsNullOrWhiteSpace(Config.ModpackConfigUrl))
ModpackConfig = ModpackConfig.LoadFromUrl(Config.ModpackConfigUrl);
rawInstallInfos = await GetContent(ConfigX.FileLocationInstallJson);
InstallInfos = InstallInfos.Parse(rawInstallInfos);
rawUpdateInfos = await GetContent(ConfigX.FileLocationUpdateJson);
UpdateInfos = UpdateInfos.Parse(rawUpdateInfos);
if (string.IsNullOrWhiteSpace(ConfigX.FolderName))
ConfigX.FolderName = Path.GetFileName(ConfigX.RootFolderPath);
return InstallInfos != null && UpdateInfos != null;
}
public async Task<bool> Save()
{
var failed = false;
if (InstallInfos != null)
{
var newInstallInfos = InstallInfos.ToString();
if (newInstallInfos != rawInstallInfos)
{
if (await SaveContent(ConfigX.FileLocationInstallJson, newInstallInfos))
rawInstallInfos = newInstallInfos;
else
failed = true;
}
}
if (UpdateInfos != null)
{
var newUpdateInfos = UpdateInfos.ToString();
if (newUpdateInfos != rawUpdateInfos)
{
if (await SaveContent(ConfigX.FileLocationUpdateJson, newUpdateInfos))
rawUpdateInfos = newUpdateInfos;
else
failed = true;
}
}
return !failed;
}
private async Task<string?> GetContent(string path)
{
if (ConfigX.RootFolderPath == null)
return null;
var filePath = Path.Combine(ConfigX.RootFolderPath!, path);
return File.Exists(filePath) ? await File.ReadAllTextAsync(filePath) : null;
}
private async Task<bool> SaveContent(string path, string content)
{
if (ConfigX.RootFolderPath == null)
return false;
var filePath = Path.Combine(ConfigX.RootFolderPath!, path);
if (!File.Exists(filePath))
return false;
await File.WriteAllTextAsync(filePath, content);
return true;
}
}

View File

@@ -0,0 +1,17 @@
using ModpackUpdater.Apps.Manager.Api.Model;
namespace ModpackUpdater.Apps.Manager.Features.Workspaces.LocalFolder;
public class LocalFolderWorkspaceConfig : WorkspaceConfig, ICloneable
{
public override string DisplayText => FolderName ?? string.Empty;
public string? FolderName { get; set; }
public string? RootFolderPath { get; set; }
public string FileLocationInstallJson { get; set; } = "install.json";
public string FileLocationUpdateJson { get; set; } = "updates.json";
public object Clone()
{
return MemberwiseClone();
}
}

View File

@@ -0,0 +1,50 @@
<dialogs:AvaloniaFlyoutBase
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:dialogs="https://git.pilzinsel64.de/pilz-framework/pilz"
xmlns:langRes="clr-namespace:ModpackUpdater.Apps.Manager.LangRes"
xmlns:localFolder="clr-namespace:ModpackUpdater.Apps.Manager.Features.Workspaces.LocalFolder"
mc:Ignorable="d"
d:DesignWidth="800"
d:DesignHeight="450"
Width="300"
x:Class="ModpackUpdater.Apps.Manager.Features.Workspaces.LocalFolder.LocalFolderWorkspaceConfigEditorView"
x:DataType="localFolder:LocalFolderWorkspaceConfig">
<dialogs:AvaloniaFlyoutBase.MainContent>
<StackPanel Spacing="6">
<Label Target="TextBoxName" Content="{x:Static langRes:GeneralLangRes.Name}"/>
<TextBox Name="TextBoxName" Text="{Binding FolderName}"/>
<Label Target="TextBoxRootFolderPath" Content="{x:Static langRes:GeneralLangRes.RootFolderPath}"/>
<Grid
ColumnDefinitions="*,Auto"
ColumnSpacing="6">
<TextBox
Grid.Column="0"
x:Name="TextBoxRootFolderPath"
Text="{Binding RootFolderPath}"/>
<dialogs:ImageButton
Grid.Column="1"
x:Name="ButtonSearch"
VerticalAlignment="Stretch"
HorizontalAlignment="Right"
Text="{x:Static langRes:GeneralLangRes.Select}"
Click="ButtonSearch_OnClick"/>
</Grid>
<Label Target="TextBoxInstallJson" Content="{x:Static langRes:GeneralLangRes.FileLocationOfInstallJson}"/>
<TextBox Name="TextBoxInstallJson" Text="{Binding FileLocationInstallJson}"/>
<Label Target="TextBoxUpdateJson" Content="{x:Static langRes:GeneralLangRes.FileLocationOfUpdateJson}"/>
<TextBox Name="TextBoxUpdateJson" Text="{Binding FileLocationUpdateJson}"/>
<Label Target="TextBoxModpackConfigUrl" Content="{x:Static langRes:GeneralLangRes.ModpackConfigUrl}"/>
<TextBox Name="TextBoxModpackConfigUrl" Text="{Binding ModpackConfigUrl}"/>
</StackPanel>
</dialogs:AvaloniaFlyoutBase.MainContent>
</dialogs:AvaloniaFlyoutBase>

View File

@@ -0,0 +1,49 @@
using Avalonia.Controls;
using Avalonia.Interactivity;
using Avalonia.Platform.Storage;
using ModpackUpdater.Apps.Manager.LangRes;
using Pilz.UI.AvaloniaUI.Dialogs;
namespace ModpackUpdater.Apps.Manager.Features.Workspaces.LocalFolder;
public partial class LocalFolderWorkspaceConfigEditorView : AvaloniaFlyoutBase
{
public LocalFolderWorkspaceConfig Settings { get; }
public LocalFolderWorkspaceConfigEditorView() : this(new())
{
}
public LocalFolderWorkspaceConfigEditorView(LocalFolderWorkspaceConfig settings)
{
Settings = settings;
DataContext = settings;
InitializeComponent();
ButtonSearch.ImageSource = AppGlobals.Symbols.GetImageSource(AppSymbols.opened_folder);
}
protected override object? GetResult()
{
return Settings;
}
private async void ButtonSearch_OnClick(object? sender, RoutedEventArgs e)
{
if (TopLevel.GetTopLevel(this) is not { } topLevel)
return;
var filePaths = await topLevel.StorageProvider.OpenFolderPickerAsync(new FolderPickerOpenOptions
{
Title = GeneralLangRes.SelectRootFolder,
AllowMultiple = false,
});
if (filePaths.Count < 1)
return;
TextBoxRootFolderPath.Text = filePaths[0].Path.AbsolutePath;
if (string.IsNullOrWhiteSpace(TextBoxName.Text))
TextBoxName.Text = filePaths[0].Name;
}
}

View File

@@ -0,0 +1,37 @@
using ModpackUpdater.Apps.Manager.Api.Model;
using ModpackUpdater.Apps.Manager.Api.Plugins.Features;
using ModpackUpdater.Apps.Manager.LangRes;
using Pilz.Features;
using Pilz.UI.AvaloniaUI.Dialogs;
using Pilz.UI.Symbols;
namespace ModpackUpdater.Apps.Manager.Features.Workspaces.LocalFolder;
internal class LocalFolderWorkspaceFeature() : WorkspaceFeature("origin.localfolder", FeatureNamesLangRes.LocalFolderWorkspace), IPluginFeatureProvider<LocalFolderWorkspaceFeature>
{
public static LocalFolderWorkspaceFeature Instance { get; } = new();
public override object? Icon => AppGlobals.Symbols.GetImage(AppSymbols.opened_folder, SymbolSize.Small);
protected override async Task OnConfigure(WorkspaceContext context)
{
var settings = context.Workspace?.Config as LocalFolderWorkspaceConfig ?? new();
if ((await AvaloniaFlyoutBase.Show(
new LocalFolderWorkspaceConfigEditorView((LocalFolderWorkspaceConfig)settings.Clone()),
context.MainApi.MainWindow,
TitlesLangRes.LocalFolderWorkspaceEditor,
AppGlobals.Symbols.GetImageSource(AppSymbols.gitlab)))
.Result is LocalFolderWorkspaceConfig settingsNew)
context.Workspace = new LocalFolderWorkspace(settingsNew);
else
context.Canceled = true;
}
protected override void OnCreate(out IWorkspace workspace, WorkspaceConfig config)
{
if (config is not LocalFolderWorkspaceConfig gitlabConfig)
throw new NotSupportedException($"Only configs of type {nameof(LocalFolderWorkspaceConfig)} are allowed.");
workspace = new LocalFolderWorkspace(gitlabConfig);
}
}

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
<PropertyChanged />
</Weavers>

View File

@@ -0,0 +1,74 @@
<?xml version="1.0" encoding="utf-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. -->
<xs:element name="Weavers">
<xs:complexType>
<xs:all>
<xs:element name="PropertyChanged" minOccurs="0" maxOccurs="1">
<xs:complexType>
<xs:attribute name="InjectOnPropertyNameChanged" type="xs:boolean">
<xs:annotation>
<xs:documentation>Used to control if the On_PropertyName_Changed feature is enabled.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="TriggerDependentProperties" type="xs:boolean">
<xs:annotation>
<xs:documentation>Used to control if the Dependent properties feature is enabled.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="EnableIsChangedProperty" type="xs:boolean">
<xs:annotation>
<xs:documentation>Used to control if the IsChanged property feature is enabled.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="EventInvokerNames" type="xs:string">
<xs:annotation>
<xs:documentation>Used to change the name of the method that fires the notify event. This is a string that accepts multiple values in a comma separated form.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="CheckForEquality" type="xs:boolean">
<xs:annotation>
<xs:documentation>Used to control if equality checks should be inserted. If false, equality checking will be disabled for the project.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="CheckForEqualityUsingBaseEquals" type="xs:boolean">
<xs:annotation>
<xs:documentation>Used to control if equality checks should use the Equals method resolved from the base class.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="UseStaticEqualsFromBase" type="xs:boolean">
<xs:annotation>
<xs:documentation>Used to control if equality checks should use the static Equals method resolved from the base class.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="SuppressWarnings" type="xs:boolean">
<xs:annotation>
<xs:documentation>Used to turn off build warnings from this weaver.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="SuppressOnPropertyNameChangedWarning" type="xs:boolean">
<xs:annotation>
<xs:documentation>Used to turn off build warnings about mismatched On_PropertyName_Changed methods.</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
</xs:element>
</xs:all>
<xs:attribute name="VerifyAssembly" type="xs:boolean">
<xs:annotation>
<xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="VerifyIgnoreCodes" type="xs:string">
<xs:annotation>
<xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="GenerateXsd" type="xs:boolean">
<xs:annotation>
<xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
</xs:element>
</xs:schema>

View File

@@ -0,0 +1,156 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace ModpackUpdater.Apps.Manager.LangRes {
using System;
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
[System.Diagnostics.DebuggerNonUserCodeAttribute()]
[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
public class ActionsListLangRes {
private static System.Resources.ResourceManager resourceMan;
private static System.Globalization.CultureInfo resourceCulture;
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal ActionsListLangRes() {
}
[System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)]
public static System.Resources.ResourceManager ResourceManager {
get {
if (object.Equals(null, resourceMan)) {
System.Resources.ResourceManager temp = new System.Resources.ResourceManager("ModpackUpdater.Apps.Manager.LangRes.ActionsListLangRes", typeof(ActionsListLangRes).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
[System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)]
public static System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
set {
resourceCulture = value;
}
}
public static string Col_DestPath {
get {
return ResourceManager.GetString("Col_DestPath", resourceCulture);
}
}
public static string Col_Id {
get {
return ResourceManager.GetString("Col_Id", resourceCulture);
}
}
public static string Col_InheritFrom {
get {
return ResourceManager.GetString("Col_InheritFrom", resourceCulture);
}
}
public static string Col_IsDir {
get {
return ResourceManager.GetString("Col_IsDir", resourceCulture);
}
}
public static string Col_IsExtra {
get {
return ResourceManager.GetString("Col_IsExtra", resourceCulture);
}
}
public static string Col_IsZip {
get {
return ResourceManager.GetString("Col_IsZip", resourceCulture);
}
}
public static string Col_Name {
get {
return ResourceManager.GetString("Col_Name", resourceCulture);
}
}
public static string Col_Side {
get {
return ResourceManager.GetString("Col_Side", resourceCulture);
}
}
public static string Col_SrcName {
get {
return ResourceManager.GetString("Col_SrcName", resourceCulture);
}
}
public static string Col_SrcOwner {
get {
return ResourceManager.GetString("Col_SrcOwner", resourceCulture);
}
}
public static string Col_SrcPath {
get {
return ResourceManager.GetString("Col_SrcPath", resourceCulture);
}
}
public static string Col_SrcRegEx {
get {
return ResourceManager.GetString("Col_SrcRegEx", resourceCulture);
}
}
public static string Col_SrcTag {
get {
return ResourceManager.GetString("Col_SrcTag", resourceCulture);
}
}
public static string Col_SrcType {
get {
return ResourceManager.GetString("Col_SrcType", resourceCulture);
}
}
public static string Col_SrcUrl {
get {
return ResourceManager.GetString("Col_SrcUrl", resourceCulture);
}
}
public static string Col_UpdateType {
get {
return ResourceManager.GetString("Col_UpdateType", resourceCulture);
}
}
public static string Col_Website {
get {
return ResourceManager.GetString("Col_Website", resourceCulture);
}
}
public static string Col_ZipPath {
get {
return ResourceManager.GetString("Col_ZipPath", resourceCulture);
}
}
}
}

View File

@@ -0,0 +1,174 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="Col_DestPath" xml:space="preserve">
<value>Destination path</value>
</data>
<data name="Col_Id" xml:space="preserve">
<value>Id</value>
</data>
<data name="Col_InheritFrom" xml:space="preserve">
<value>Inherit from</value>
</data>
<data name="Col_IsDir" xml:space="preserve">
<value>Is directory</value>
</data>
<data name="Col_IsExtra" xml:space="preserve">
<value>Is Extra</value>
</data>
<data name="Col_IsZip" xml:space="preserve">
<value>Is ZIP</value>
</data>
<data name="Col_Name" xml:space="preserve">
<value>Name</value>
</data>
<data name="Col_Side" xml:space="preserve">
<value>Side</value>
</data>
<data name="Col_SrcName" xml:space="preserve">
<value>Source name</value>
</data>
<data name="Col_SrcOwner" xml:space="preserve">
<value>Source owner</value>
</data>
<data name="Col_SrcPath" xml:space="preserve">
<value>Source path</value>
</data>
<data name="Col_SrcRegEx" xml:space="preserve">
<value>Source RegEx</value>
</data>
<data name="Col_SrcTag" xml:space="preserve">
<value>Source tag</value>
</data>
<data name="Col_SrcType" xml:space="preserve">
<value>Source type</value>
</data>
<data name="Col_SrcUrl" xml:space="preserve">
<value>Source URL</value>
</data>
<data name="Col_UpdateType" xml:space="preserve">
<value>Update type</value>
</data>
<data name="Col_Website" xml:space="preserve">
<value>Website</value>
</data>
<data name="Col_ZipPath" xml:space="preserve">
<value>ZIP archive path</value>
</data>
</root>

View File

@@ -0,0 +1,126 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace ModpackUpdater.Apps.Manager.LangRes {
using System;
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
[System.Diagnostics.DebuggerNonUserCodeAttribute()]
[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
public class FeatureNamesLangRes {
private static System.Resources.ResourceManager resourceMan;
private static System.Globalization.CultureInfo resourceCulture;
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal FeatureNamesLangRes() {
}
[System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)]
public static System.Resources.ResourceManager ResourceManager {
get {
if (object.Equals(null, resourceMan)) {
System.Resources.ResourceManager temp = new System.Resources.ResourceManager("ModpackUpdater.Apps.Manager.LangRes.FeatureNamesLangRes", typeof(FeatureNamesLangRes).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
[System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)]
public static System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
set {
resourceCulture = value;
}
}
public static string CheckAllActionsHealthy {
get {
return ResourceManager.GetString("CheckAllActionsHealthy", resourceCulture);
}
}
public static string CheckSingleActionHealthy {
get {
return ResourceManager.GetString("CheckSingleActionHealthy", resourceCulture);
}
}
public static string ClearDirectLinkFeature {
get {
return ResourceManager.GetString("ClearDirectLinkFeature", resourceCulture);
}
}
public static string ClearDirectLinksFeature {
get {
return ResourceManager.GetString("ClearDirectLinksFeature", resourceCulture);
}
}
public static string GenerateChangelogFeature {
get {
return ResourceManager.GetString("GenerateChangelogFeature", resourceCulture);
}
}
public static string GenerateModlistAsExcelFeature {
get {
return ResourceManager.GetString("GenerateModlistAsExcelFeature", resourceCulture);
}
}
public static string GenerateModlistAsMarkdownFeature {
get {
return ResourceManager.GetString("GenerateModlistAsMarkdownFeature", resourceCulture);
}
}
public static string GitLabWorkspace {
get {
return ResourceManager.GetString("GitLabWorkspace", resourceCulture);
}
}
public static string LocalFolderWorkspace {
get {
return ResourceManager.GetString("LocalFolderWorkspace", resourceCulture);
}
}
public static string UpdateCollectorFeature {
get {
return ResourceManager.GetString("UpdateCollectorFeature", resourceCulture);
}
}
public static string UpdateDirectLinkFeature {
get {
return ResourceManager.GetString("UpdateDirectLinkFeature", resourceCulture);
}
}
public static string UpdateDirectLinksFeature {
get {
return ResourceManager.GetString("UpdateDirectLinksFeature", resourceCulture);
}
}
public static string UpdatesCollectorFeature {
get {
return ResourceManager.GetString("UpdatesCollectorFeature", resourceCulture);
}
}
}
}

View File

@@ -0,0 +1,159 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="CheckAllActionsHealthy" xml:space="preserve">
<value>Check healthy</value>
</data>
<data name="CheckSingleActionHealthy" xml:space="preserve">
<value>Check healthy</value>
</data>
<data name="ClearDirectLinkFeature" xml:space="preserve">
<value>Clear direct link</value>
</data>
<data name="ClearDirectLinksFeature" xml:space="preserve">
<value>Clear direct links</value>
</data>
<data name="GenerateChangelogFeature" xml:space="preserve">
<value>Generate changelog</value>
</data>
<data name="GenerateModlistAsExcelFeature" xml:space="preserve">
<value>Generate modlist excel file</value>
</data>
<data name="GenerateModlistAsMarkdownFeature" xml:space="preserve">
<value>Generate modlist markdown table</value>
</data>
<data name="GitLabWorkspace" xml:space="preserve">
<value>GitLab</value>
</data>
<data name="LocalFolderWorkspace" xml:space="preserve">
<value>Local folder</value>
</data>
<data name="UpdateCollectorFeature" xml:space="preserve">
<value>Find update</value>
</data>
<data name="UpdateDirectLinkFeature" xml:space="preserve">
<value>Update direct link</value>
</data>
<data name="UpdateDirectLinksFeature" xml:space="preserve">
<value>Update direct links</value>
</data>
<data name="UpdatesCollectorFeature" xml:space="preserve">
<value>Find updates</value>
</data>
</root>

View File

@@ -0,0 +1,330 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace ModpackUpdater.Apps.Manager.LangRes {
using System;
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
[System.Diagnostics.DebuggerNonUserCodeAttribute()]
[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
public class GeneralLangRes {
private static System.Resources.ResourceManager resourceMan;
private static System.Globalization.CultureInfo resourceCulture;
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal GeneralLangRes() {
}
[System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)]
public static System.Resources.ResourceManager ResourceManager {
get {
if (object.Equals(null, resourceMan)) {
System.Resources.ResourceManager temp = new System.Resources.ResourceManager("ModpackUpdater.Apps.Manager.LangRes.GeneralLangRes", typeof(GeneralLangRes).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
[System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)]
public static System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
set {
resourceCulture = value;
}
}
public static string ModlistForVersionX {
get {
return ResourceManager.GetString("ModlistForVersionX", resourceCulture);
}
}
public static string GitLabInstanceUrl {
get {
return ResourceManager.GetString("GitLabInstanceUrl", resourceCulture);
}
}
public static string GitLabApiToken {
get {
return ResourceManager.GetString("GitLabApiToken", resourceCulture);
}
}
public static string RepositoryId {
get {
return ResourceManager.GetString("RepositoryId", resourceCulture);
}
}
public static string RootFolderPath {
get {
return ResourceManager.GetString("RootFolderPath", resourceCulture);
}
}
public static string FileLocationOfInstallJson {
get {
return ResourceManager.GetString("FileLocationOfInstallJson", resourceCulture);
}
}
public static string FileLocationOfUpdateJson {
get {
return ResourceManager.GetString("FileLocationOfUpdateJson", resourceCulture);
}
}
public static string ModpackConfigUrl {
get {
return ResourceManager.GetString("ModpackConfigUrl", resourceCulture);
}
}
public static string Remove {
get {
return ResourceManager.GetString("Remove", resourceCulture);
}
}
public static string WorkspacePreferences {
get {
return ResourceManager.GetString("WorkspacePreferences", resourceCulture);
}
}
public static string SaveWorkspace {
get {
return ResourceManager.GetString("SaveWorkspace", resourceCulture);
}
}
public static string NewWorkspace {
get {
return ResourceManager.GetString("NewWorkspace", resourceCulture);
}
}
public static string RecentWorkspaces {
get {
return ResourceManager.GetString("RecentWorkspaces", resourceCulture);
}
}
public static string Workspace {
get {
return ResourceManager.GetString("Workspace", resourceCulture);
}
}
public static string CreateUpdate {
get {
return ResourceManager.GetString("CreateUpdate", resourceCulture);
}
}
public static string RemoveUpdate {
get {
return ResourceManager.GetString("RemoveUpdate", resourceCulture);
}
}
public static string Update {
get {
return ResourceManager.GetString("Update", resourceCulture);
}
}
public static string Tools {
get {
return ResourceManager.GetString("Tools", resourceCulture);
}
}
public static string Updates {
get {
return ResourceManager.GetString("Updates", resourceCulture);
}
}
public static string Add {
get {
return ResourceManager.GetString("Add", resourceCulture);
}
}
public static string Public {
get {
return ResourceManager.GetString("Public", resourceCulture);
}
}
public static string Id {
get {
return ResourceManager.GetString("Id", resourceCulture);
}
}
public static string Side {
get {
return ResourceManager.GetString("Side", resourceCulture);
}
}
public static string UpdateType {
get {
return ResourceManager.GetString("UpdateType", resourceCulture);
}
}
public static string SourceType {
get {
return ResourceManager.GetString("SourceType", resourceCulture);
}
}
public static string DestinationPath {
get {
return ResourceManager.GetString("DestinationPath", resourceCulture);
}
}
public static string State {
get {
return ResourceManager.GetString("State", resourceCulture);
}
}
public static string InheritFrom {
get {
return ResourceManager.GetString("InheritFrom", resourceCulture);
}
}
public static string SourcePath {
get {
return ResourceManager.GetString("SourcePath", resourceCulture);
}
}
public static string IsDirectory {
get {
return ResourceManager.GetString("IsDirectory", resourceCulture);
}
}
public static string General {
get {
return ResourceManager.GetString("General", resourceCulture);
}
}
public static string Name {
get {
return ResourceManager.GetString("Name", resourceCulture);
}
}
public static string IsExtra {
get {
return ResourceManager.GetString("IsExtra", resourceCulture);
}
}
public static string Destination {
get {
return ResourceManager.GetString("Destination", resourceCulture);
}
}
public static string Source {
get {
return ResourceManager.GetString("Source", resourceCulture);
}
}
public static string IsZipArchive {
get {
return ResourceManager.GetString("IsZipArchive", resourceCulture);
}
}
public static string SourceOwner {
get {
return ResourceManager.GetString("SourceOwner", resourceCulture);
}
}
public static string SourceName {
get {
return ResourceManager.GetString("SourceName", resourceCulture);
}
}
public static string SourceTag {
get {
return ResourceManager.GetString("SourceTag", resourceCulture);
}
}
public static string SourceRegex {
get {
return ResourceManager.GetString("SourceRegex", resourceCulture);
}
}
public static string SourceUrl {
get {
return ResourceManager.GetString("SourceUrl", resourceCulture);
}
}
public static string ZipArchivePath {
get {
return ResourceManager.GetString("ZipArchivePath", resourceCulture);
}
}
public static string Metadata {
get {
return ResourceManager.GetString("Metadata", resourceCulture);
}
}
public static string Website {
get {
return ResourceManager.GetString("Website", resourceCulture);
}
}
public static string Select {
get {
return ResourceManager.GetString("Select", resourceCulture);
}
}
public static string SelectRootFolder {
get {
return ResourceManager.GetString("SelectRootFolder", resourceCulture);
}
}
public static string Search {
get {
return ResourceManager.GetString("Search", resourceCulture);
}
}
}
}

View File

@@ -0,0 +1,261 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="ModlistForVersionX" xml:space="preserve">
<value>Modlist for v{0}</value>
</data>
<data name="GitLabInstanceUrl" xml:space="preserve">
<value>GitLab instance url</value>
</data>
<data name="GitLabApiToken" xml:space="preserve">
<value>GitLab Api token</value>
</data>
<data name="RepositoryId" xml:space="preserve">
<value>Repository Id</value>
</data>
<data name="RootFolderPath" xml:space="preserve">
<value>Root folder path</value>
</data>
<data name="FileLocationOfInstallJson" xml:space="preserve">
<value>File location of "install.json"</value>
</data>
<data name="FileLocationOfUpdateJson" xml:space="preserve">
<value>File location of "update.json"</value>
</data>
<data name="ModpackConfigUrl" xml:space="preserve">
<value>Modpack config url</value>
</data>
<data name="Remove" xml:space="preserve">
<value>Remove</value>
</data>
<data name="WorkspacePreferences" xml:space="preserve">
<value>Workspace preferences</value>
</data>
<data name="SaveWorkspace" xml:space="preserve">
<value>Save workspace</value>
</data>
<data name="NewWorkspace" xml:space="preserve">
<value>New workspace</value>
</data>
<data name="RecentWorkspaces" xml:space="preserve">
<value>Recent workspace</value>
</data>
<data name="Workspace" xml:space="preserve">
<value>Workspace</value>
</data>
<data name="CreateUpdate" xml:space="preserve">
<value>Create update</value>
</data>
<data name="RemoveUpdate" xml:space="preserve">
<value>Remove update</value>
</data>
<data name="Update" xml:space="preserve">
<value>Update</value>
</data>
<data name="Tools" xml:space="preserve">
<value>Tools</value>
</data>
<data name="Updates" xml:space="preserve">
<value>Updates</value>
</data>
<data name="Add" xml:space="preserve">
<value>Add</value>
</data>
<data name="Public" xml:space="preserve">
<value>Public</value>
</data>
<data name="Id" xml:space="preserve">
<value>Id</value>
</data>
<data name="Side" xml:space="preserve">
<value>Side</value>
</data>
<data name="UpdateType" xml:space="preserve">
<value>Update type</value>
</data>
<data name="SourceType" xml:space="preserve">
<value>Source type</value>
</data>
<data name="DestinationPath" xml:space="preserve">
<value>Destination path</value>
</data>
<data name="State" xml:space="preserve">
<value>State</value>
</data>
<data name="InheritFrom" xml:space="preserve">
<value>Inherit from</value>
</data>
<data name="SourcePath" xml:space="preserve">
<value>Source path</value>
</data>
<data name="IsDirectory" xml:space="preserve">
<value>Is directory</value>
</data>
<data name="General" xml:space="preserve">
<value>General</value>
</data>
<data name="Name" xml:space="preserve">
<value>Name</value>
</data>
<data name="IsExtra" xml:space="preserve">
<value>Is extra</value>
</data>
<data name="Destination" xml:space="preserve">
<value>Destination</value>
</data>
<data name="Source" xml:space="preserve">
<value>Source</value>
</data>
<data name="IsZipArchive" xml:space="preserve">
<value>Is Zip archive</value>
</data>
<data name="SourceOwner" xml:space="preserve">
<value>Source owner</value>
</data>
<data name="SourceName" xml:space="preserve">
<value>Source name</value>
</data>
<data name="SourceTag" xml:space="preserve">
<value>Source tag</value>
</data>
<data name="SourceRegex" xml:space="preserve">
<value>Source RegEx</value>
</data>
<data name="SourceUrl" xml:space="preserve">
<value>Source URL</value>
</data>
<data name="ZipArchivePath" xml:space="preserve">
<value>ZIP archive path</value>
</data>
<data name="Metadata" xml:space="preserve">
<value>Metadata</value>
</data>
<data name="Website" xml:space="preserve">
<value>Website</value>
</data>
<data name="Select" xml:space="preserve">
<value>Select</value>
</data>
<data name="SelectRootFolder" xml:space="preserve">
<value>Select root folder</value>
</data>
<data name="Search" xml:space="preserve">
<value>Search</value>
</data>
</root>

View File

@@ -0,0 +1,108 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace ModpackUpdater.Apps.Manager.LangRes {
using System;
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
[System.Diagnostics.DebuggerNonUserCodeAttribute()]
[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
public class MsgBoxLangRes {
private static System.Resources.ResourceManager resourceMan;
private static System.Globalization.CultureInfo resourceCulture;
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal MsgBoxLangRes() {
}
[System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)]
public static System.Resources.ResourceManager ResourceManager {
get {
if (object.Equals(null, resourceMan)) {
System.Resources.ResourceManager temp = new System.Resources.ResourceManager("ModpackUpdater.Apps.Manager.LangRes.MsgBoxLangRes", typeof(MsgBoxLangRes).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
[System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)]
public static System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
set {
resourceCulture = value;
}
}
public static string ChangelogCopiedToClipboard {
get {
return ResourceManager.GetString("ChangelogCopiedToClipboard", resourceCulture);
}
}
public static string ChangelogCopiedToClipboard_Title {
get {
return ResourceManager.GetString("ChangelogCopiedToClipboard_Title", resourceCulture);
}
}
public static string ModlistCopiedToClipboard {
get {
return ResourceManager.GetString("ModlistCopiedToClipboard", resourceCulture);
}
}
public static string ModlistCopiedToClipboard_Title {
get {
return ResourceManager.GetString("ModlistCopiedToClipboard_Title", resourceCulture);
}
}
public static string ModlistGenerated {
get {
return ResourceManager.GetString("ModlistGenerated", resourceCulture);
}
}
public static string ModlistGenerated_Title {
get {
return ResourceManager.GetString("ModlistGenerated_Title", resourceCulture);
}
}
public static string RemoveUpdate {
get {
return ResourceManager.GetString("RemoveUpdate", resourceCulture);
}
}
public static string RemoveUpdate_Title {
get {
return ResourceManager.GetString("RemoveUpdate_Title", resourceCulture);
}
}
public static string UpdateAvailable {
get {
return ResourceManager.GetString("UpdateAvailable", resourceCulture);
}
}
public static string UpdateAvailable_Title {
get {
return ResourceManager.GetString("UpdateAvailable_Title", resourceCulture);
}
}
}
}

View File

@@ -0,0 +1,151 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="ChangelogCopiedToClipboard" xml:space="preserve">
<value>The changelog for the selected version has been generated and copied to the clipboard.</value>
</data>
<data name="ChangelogCopiedToClipboard_Title" xml:space="preserve">
<value>Changelog generated successfully</value>
</data>
<data name="ModlistCopiedToClipboard" xml:space="preserve">
<value>The modlist has been generated and copied to the clipboard.</value>
</data>
<data name="ModlistCopiedToClipboard_Title" xml:space="preserve">
<value>Modlist generated successfully</value>
</data>
<data name="ModlistGenerated" xml:space="preserve">
<value>The modlist has been generated successfully and saved to the selected location.</value>
</data>
<data name="ModlistGenerated_Title" xml:space="preserve">
<value>Modlist generated successfully</value>
</data>
<data name="RemoveUpdate" xml:space="preserve">
<value>Are you sure that you want to delete this update?</value>
</data>
<data name="RemoveUpdate_Title" xml:space="preserve">
<value>Remove update</value>
</data>
<data name="UpdateAvailable" xml:space="preserve">
<value>A new version of this program is available! Install now?
If you confirm, the update will be installed automatically within a few seconds.</value>
</data>
<data name="UpdateAvailable_Title" xml:space="preserve">
<value>New program version available</value>
</data>
</root>

View File

@@ -0,0 +1,66 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace ModpackUpdater.Apps.Manager.LangRes {
using System;
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
[System.Diagnostics.DebuggerNonUserCodeAttribute()]
[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class SideLangRes {
private static System.Resources.ResourceManager resourceMan;
private static System.Globalization.CultureInfo resourceCulture;
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal SideLangRes() {
}
[System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)]
internal static System.Resources.ResourceManager ResourceManager {
get {
if (object.Equals(null, resourceMan)) {
System.Resources.ResourceManager temp = new System.Resources.ResourceManager("ModpackUpdater.Apps.Manager.LangRes.SideLangRes", typeof(SideLangRes).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
[System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)]
internal static System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
set {
resourceCulture = value;
}
}
internal static string Client {
get {
return ResourceManager.GetString("Client", resourceCulture);
}
}
internal static string Server {
get {
return ResourceManager.GetString("Server", resourceCulture);
}
}
internal static string Both {
get {
return ResourceManager.GetString("Both", resourceCulture);
}
}
}
}

View File

@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:element name="root" msdata:IsDataSet="true">
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>1.3</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="Client" xml:space="preserve">
<value>Client</value>
</data>
<data name="Server" xml:space="preserve">
<value>Server</value>
</data>
<data name="Both" xml:space="preserve">
<value>Both</value>
</data>
</root>

View File

@@ -0,0 +1,78 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace ModpackUpdater.Apps.Manager.LangRes {
using System;
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
[System.Diagnostics.DebuggerNonUserCodeAttribute()]
[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class SourceTypeLangRes {
private static System.Resources.ResourceManager resourceMan;
private static System.Globalization.CultureInfo resourceCulture;
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal SourceTypeLangRes() {
}
[System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)]
internal static System.Resources.ResourceManager ResourceManager {
get {
if (object.Equals(null, resourceMan)) {
System.Resources.ResourceManager temp = new System.Resources.ResourceManager("ModpackUpdater.Apps.Manager.LangRes.SourceTypeLangRes", typeof(SourceTypeLangRes).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
[System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)]
internal static System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
set {
resourceCulture = value;
}
}
internal static string DirectLink {
get {
return ResourceManager.GetString("DirectLink", resourceCulture);
}
}
internal static string GitHub {
get {
return ResourceManager.GetString("GitHub", resourceCulture);
}
}
internal static string GitLab {
get {
return ResourceManager.GetString("GitLab", resourceCulture);
}
}
internal static string CurseForge {
get {
return ResourceManager.GetString("CurseForge", resourceCulture);
}
}
internal static string Modrinth {
get {
return ResourceManager.GetString("Modrinth", resourceCulture);
}
}
}
}

View File

@@ -0,0 +1,36 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:element name="root" msdata:IsDataSet="true">
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>1.3</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="DirectLink" xml:space="preserve">
<value>Direct link</value>
</data>
<data name="GitHub" xml:space="preserve">
<value>GitHub</value>
</data>
<data name="GitLab" xml:space="preserve">
<value>GitLab</value>
</data>
<data name="CurseForge" xml:space="preserve">
<value>CurseForge</value>
</data>
<data name="Modrinth" xml:space="preserve">
<value>Modrinth</value>
</data>
</root>

View File

@@ -0,0 +1,72 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace ModpackUpdater.Apps.Manager.LangRes {
using System;
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
[System.Diagnostics.DebuggerNonUserCodeAttribute()]
[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
public class TitlesLangRes {
private static System.Resources.ResourceManager resourceMan;
private static System.Globalization.CultureInfo resourceCulture;
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal TitlesLangRes() {
}
[System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)]
public static System.Resources.ResourceManager ResourceManager {
get {
if (object.Equals(null, resourceMan)) {
System.Resources.ResourceManager temp = new System.Resources.ResourceManager("ModpackUpdater.Apps.Manager.LangRes.TitlesLangRes", typeof(TitlesLangRes).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
[System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)]
public static System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
set {
resourceCulture = value;
}
}
public static string CreateUpdate {
get {
return ResourceManager.GetString("CreateUpdate", resourceCulture);
}
}
public static string EditUpdate {
get {
return ResourceManager.GetString("EditUpdate", resourceCulture);
}
}
public static string GitLabRepoWorkspaceEditor {
get {
return ResourceManager.GetString("GitLabRepoWorkspaceEditor", resourceCulture);
}
}
public static string LocalFolderWorkspaceEditor {
get {
return ResourceManager.GetString("LocalFolderWorkspaceEditor", resourceCulture);
}
}
}
}

View File

@@ -0,0 +1,132 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="CreateUpdate" xml:space="preserve">
<value>Create update</value>
</data>
<data name="EditUpdate" xml:space="preserve">
<value>Edit update</value>
</data>
<data name="GitLabRepoWorkspaceEditor" xml:space="preserve">
<value>Setup GitLab</value>
</data>
<data name="LocalFolderWorkspaceEditor" xml:space="preserve">
<value>Setup local folder</value>
</data>
</root>

View File

@@ -0,0 +1,78 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace ModpackUpdater.Apps.Manager.LangRes {
using System;
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
[System.Diagnostics.DebuggerNonUserCodeAttribute()]
[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class UpdateActionTypeLangRes {
private static System.Resources.ResourceManager resourceMan;
private static System.Globalization.CultureInfo resourceCulture;
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal UpdateActionTypeLangRes() {
}
[System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)]
internal static System.Resources.ResourceManager ResourceManager {
get {
if (object.Equals(null, resourceMan)) {
System.Resources.ResourceManager temp = new System.Resources.ResourceManager("ModpackUpdater.Apps.Manager.LangRes.UpdateActionTypeLangRes", typeof(UpdateActionTypeLangRes).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
[System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)]
internal static System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
set {
resourceCulture = value;
}
}
internal static string None {
get {
return ResourceManager.GetString("None", resourceCulture);
}
}
internal static string Update {
get {
return ResourceManager.GetString("Update", resourceCulture);
}
}
internal static string Delete {
get {
return ResourceManager.GetString("Delete", resourceCulture);
}
}
internal static string Move {
get {
return ResourceManager.GetString("Move", resourceCulture);
}
}
internal static string Copy {
get {
return ResourceManager.GetString("Copy", resourceCulture);
}
}
}
}

View File

@@ -0,0 +1,36 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:element name="root" msdata:IsDataSet="true">
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>1.3</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="None" xml:space="preserve">
<value>None</value>
</data>
<data name="Update" xml:space="preserve">
<value>Update</value>
</data>
<data name="Delete" xml:space="preserve">
<value>Delete</value>
</data>
<data name="Move" xml:space="preserve">
<value>Move</value>
</data>
<data name="Copy" xml:space="preserve">
<value>Copy</value>
</data>
</root>

View File

@@ -0,0 +1,146 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<ApplicationIcon>Assets\app.ico</ApplicationIcon>
<AssemblyName>MinecraftModpackUpdateManager</AssemblyName>
<IncludeAllContentForSelfExtract>true</IncludeAllContentForSelfExtract>
<BuiltInComInteropSupport>true</BuiltInComInteropSupport>
<AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>
</PropertyGroup>
<ItemGroup>
<TrimmerRootAssembly Include="MinecraftModpackUpdateManager"/>
<TrimmerRootAssembly Include="ModpackUpdater.Manager"/>
<TrimmerRootAssembly Include="ModpackUpdater.Apps"/>
<TrimmerRootAssembly Include="ModpackUpdater"/>
<TrimmerRootAssembly Include="NGitLab"/>
<TrimmerRootAssembly Include="Pilz.Updating"/>
<TrimmerRootAssembly Include="Pilz.Updating.Client"/>
<TrimmerRootAssembly Include="Pilz.Configuration"/>
<TrimmerRootAssembly Include="ExCSS"/>
</ItemGroup>
<ItemGroup>
<Compile Include="..\Version.cs" />
<Compile Update="LangRes\UpdateActionTypeLangRes.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>UpdateActionTypeLangRes.resx</DependentUpon>
</Compile>
<Compile Update="LangRes\SideLangRes.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>SideLangRes.resx</DependentUpon>
</Compile>
<Compile Update="LangRes\SourceTypeLangRes.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>SourceTypeLangRes.resx</DependentUpon>
</Compile>
<Compile Update="Features\Workspaces\LocalFolder\LocalFolderWorkspaceConfigEditorView.axaml.cs">
<DependentUpon>GitLabRepoWorkspaceConfigEditorView.axaml</DependentUpon>
<SubType>Code</SubType>
</Compile>
</ItemGroup>
<ItemGroup>
<AvaloniaResource Include="Assets\**" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Avalonia.Controls.DataGrid" Version="11.3.10" />
<PackageReference Include="DynamicData" Version="9.4.1" />
<PackageReference Include="MessageBox.Avalonia" Version="3.3.1" />
<PackageReference Include="EPPlus" Version="8.4.0" />
<PackageReference Include="NGitLab" Version="11.1.0" />
<PackageReference Include="Pilz" Version="2.7.8" />
<PackageReference Include="Pilz.Configuration" Version="3.2.8" />
<PackageReference Include="Pilz.Cryptography" Version="2.1.3" />
<PackageReference Include="Pilz.Features" Version="2.13.1" />
<PackageReference Include="Pilz.UI" Version="3.1.5" />
<PackageReference Include="Pilz.UI.AvaloniaUI" Version="1.2.21" />
<PackageReference Include="Pilz.UI.AvaloniaUI.Features" Version="1.0.2" />
<PackageReference Include="Avalonia" Version="11.3.10" />
<PackageReference Include="Avalonia.Desktop" Version="11.3.10" />
<PackageReference Include="Avalonia.Svg" Version="11.3.0" />
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.3.10" />
<PackageReference Include="Avalonia.Fonts.Inter" Version="11.3.10" />
<!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.-->
<PackageReference Include="Avalonia.Diagnostics" Version="11.3.10">
<IncludeAssets Condition="'$(Configuration)' != 'Debug'">None</IncludeAssets>
<PrivateAssets Condition="'$(Configuration)' != 'Debug'">All</PrivateAssets>
</PackageReference>
<PackageReference Include="PropertyChanged.Fody" Version="4.1.0" PrivateAssets='All' />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\ModpackUpdater.Apps\ModpackUpdater.Apps.csproj" />
<ProjectReference Include="..\ModpackUpdater.Manager\ModpackUpdater.Manager.csproj" />
<ProjectReference Include="..\ModpackUpdater\ModpackUpdater.csproj" />
</ItemGroup>
<ItemGroup>
<Compile Update="LangRes\ActionsListLangRes.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>ActionsListLangRes.resx</DependentUpon>
</Compile>
<Compile Update="LangRes\FeatureNamesLangRes.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>FeatureNamesLangRes.resx</DependentUpon>
</Compile>
<Compile Update="LangRes\GeneralLangRes.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>GeneralLangRes.resx</DependentUpon>
</Compile>
<Compile Update="LangRes\MsgBoxLangRes.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>MsgBoxLangRes.resx</DependentUpon>
</Compile>
<Compile Update="LangRes\TitlesLangRes.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>TitlesLangRes.resx</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Update="LangRes\ActionsListLangRes.resx">
<Generator>PublicResXFileCodeGenerator</Generator>
<LastGenOutput>ActionsListLangRes.Designer.cs</LastGenOutput>
</EmbeddedResource>
<EmbeddedResource Update="LangRes\FeatureNamesLangRes.resx">
<Generator>PublicResXFileCodeGenerator</Generator>
<LastGenOutput>FeatureNamesLangRes.Designer.cs</LastGenOutput>
</EmbeddedResource>
<EmbeddedResource Update="LangRes\GeneralLangRes.resx">
<Generator>PublicResXFileCodeGenerator</Generator>
<LastGenOutput>GeneralLangRes.Designer.cs</LastGenOutput>
</EmbeddedResource>
<EmbeddedResource Update="LangRes\MsgBoxLangRes.resx">
<Generator>PublicResXFileCodeGenerator</Generator>
<LastGenOutput>MsgBoxLangRes.Designer.cs</LastGenOutput>
</EmbeddedResource>
<EmbeddedResource Update="LangRes\TitlesLangRes.resx">
<Generator>PublicResXFileCodeGenerator</Generator>
<LastGenOutput>TitlesLangRes.Designer.cs</LastGenOutput>
</EmbeddedResource>
<EmbeddedResource Update="LangRes\UpdateActionTypeLangRes.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>UpdateActionTypeLangRes.Designer.cs</LastGenOutput>
</EmbeddedResource>
<EmbeddedResource Update="LangRes\SideLangRes.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>SideLangRes.Designer.cs</LastGenOutput>
</EmbeddedResource>
<EmbeddedResource Update="LangRes\SourceTypeLangRes.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>SourceTypeLangRes.Designer.cs</LastGenOutput>
</EmbeddedResource>
</ItemGroup>
</Project>

View File

@@ -0,0 +1,51 @@
using Avalonia;
using Castle.Core.Logging;
using OfficeOpenXml;
using Pilz.Configuration;
namespace ModpackUpdater.Apps.Manager;
public static class Program
{
internal static readonly SettingsManager settingsManager;
public static ISettings Settings => settingsManager.Instance;
static Program()
{
ExcelPackage.License.SetNonCommercialPersonal("Pilzinsel64");
settingsManager = new(GetSettingsPath(), true);
settingsManager.Instance.Logger = new ConsoleLogger("Settings");
}
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
internal static void Main(string[] args)
{
// To customize application configuration such as set high DPI settings or default font,
// see https://aka.ms/applicationconfiguration.
BuildAvaloniaApp().StartWithClassicDesktopLifetime(args);
}
public static AppBuilder BuildAvaloniaApp()
{
return AppBuilder.Configure<App>()
.UsePlatformDetect()
.WithInterFont()
.LogToTrace();
}
private static string GetSettingsPath()
{
const string AppDataDirectoryName = "MinecraftModpackUpdateManager";
const string settingsFileName = "Settings.json";
var settingsPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), AppDataDirectoryName);
Directory.CreateDirectory(settingsPath);
settingsPath = Path.Combine(settingsPath, settingsFileName);
return settingsPath;
}
}

View File

@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- https://go.microsoft.com/fwlink/?LinkID=208121. -->
<Project>
<PropertyGroup>
<Configuration>Release</Configuration>
<Platform>Any CPU</Platform>
<PublishDir>H:\Applications\Minecraft Modpack Manager</PublishDir>
<PublishProtocol>FileSystem</PublishProtocol>
<_TargetId>Folder</_TargetId>
<TargetFramework>net8.0-windows</TargetFramework>
<SelfContained>false</SelfContained>
</PropertyGroup>
</Project>

View File

@@ -0,0 +1,16 @@
using ModpackUpdater.Apps.Manager.Api.Model;
using Pilz.Configuration;
namespace ModpackUpdater.Apps.Manager.Settings;
internal class WorkspaceSettings : IChildSettings, ISettingsIdentifier
{
public static string Identifier => "origin.workspaces";
public List<WorkspaceConfig> Workspaces { get; } = [];
public void Reset()
{
Workspaces.Clear();
}
}

View File

@@ -0,0 +1,457 @@
<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:pilz="https://git.pilzinsel64.de/pilz-framework/pilz"
xmlns:symbols="clr-namespace:Pilz.UI.Symbols;assembly=Pilz.UI"
xmlns:mainWindow="clr-namespace:ModpackUpdater.Apps.Manager.Ui.Models.MainWindow"
xmlns:langRes="clr-namespace:ModpackUpdater.Apps.Manager.LangRes"
mc:Ignorable="d" d:DesignWidth="1000" d:DesignHeight="450"
x:Class="ModpackUpdater.Apps.Manager.Ui.MainWindow"
Title="Minecraft Modpack Manager"
WindowState="Maximized"
Loaded="Window_OnLoaded"
Closed="Window_OnClosed"
KeyDown="Window_OnKeyDown">
<Grid
x:Name="GridMain"
x:DataType="mainWindow:MainWindowViewModel"
RowDefinitions="Auto,*"
ColumnDefinitions="Auto,*,500"
RowSpacing="6"
ColumnSpacing="6"
Margin="3">
<!-- Menu: Workspace -->
<Menu
Grid.Column="0"
Grid.Row="0">
<Menu.Items>
<!-- MenuItem: Workspace -->
<pilz:HeaderMenuItem
x:Name="MenuItemWorkspace"
HeaderText="{x:Static langRes:GeneralLangRes.Workspace}">
<pilz:HeaderMenuItem.Items>
<MenuItem x:Name="MenuItemWorkspacePreferences" Header="{x:Static langRes:GeneralLangRes.WorkspacePreferences}" Click="MenuItemWorkspacePreferences_OnClick"/>
<MenuItem x:Name="MenuItemSaveWorkspace" Header="{x:Static langRes:GeneralLangRes.SaveWorkspace}" HotKey="Ctrl+S" Click="MenuItemSaveWorkspace_OnClick"/>
<Separator/>
<MenuItem x:Name="MenuItemNewWorkspace" Header="{x:Static langRes:GeneralLangRes.NewWorkspace}"/>
<Separator/>
<MenuItem x:Name="MenuItemRecentWorkspaces" Header="{x:Static langRes:GeneralLangRes.RecentWorkspaces}"/>
</pilz:HeaderMenuItem.Items>
</pilz:HeaderMenuItem>
<!-- MenuItem: Update -->
<pilz:HeaderMenuItem
x:Name="MenuItemUpdate"
HeaderText="{x:Static langRes:GeneralLangRes.Update}">
<pilz:HeaderMenuItem.Items>
<MenuItem x:Name="MenuItemCreateUpdate" Header="{x:Static langRes:GeneralLangRes.CreateUpdate}" Click="MenuItemCreateUpdate_OnClick"/>
<MenuItem x:Name="MenuItemRemoveUpdate" Header="{x:Static langRes:GeneralLangRes.RemoveUpdate}" Click="MenuItemRemoveUpdate_OnClick"/>
</pilz:HeaderMenuItem.Items>
</pilz:HeaderMenuItem>
<!-- MenuItem: Workspace -->
<pilz:HeaderMenuItem
x:Name="MenuItemTools"
HeaderText="{x:Static langRes:GeneralLangRes.Tools}"/>
</Menu.Items>
</Menu>
<!-- TreeView: Workspace -->
<ScrollViewer
Grid.Column="0"
Grid.Row="1"
VerticalScrollBarVisibility="Auto">
<StackPanel>
<TreeView
x:Name="TreeViewWorkspace"
ItemsSource="{Binding CurrentTreeNodes}"
SelectedItem="{Binding SelectedTreeNode}">
<TreeView.ItemTemplate>
<TreeDataTemplate
ItemsSource="{Binding Nodes}">
<StackPanel Orientation="Horizontal" Spacing="6">
<Image
Source="{Binding Image}"
Width="{x:Static symbols:SymbolGlobals.DefaultImageSmallSize}"/>
<TextBlock Text="{Binding DisplayText}"/>
</StackPanel>
</TreeDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
</StackPanel>
</ScrollViewer>
<!-- Panel: List header -->
<StackPanel
Grid.Column="1"
Grid.Row="0"
Spacing="6"
Orientation="Horizontal"
VerticalAlignment="Center">
<!-- Menu: Actions -->
<Menu>
<Menu.Items>
<pilz:HeaderMenuItem x:Name="MenuItemAddAction" HeaderText="{x:Static langRes:GeneralLangRes.Add}" Click="ButtonAddAction_OnClick"/>
<pilz:HeaderMenuItem x:Name="MenuItemRemoveAction" HeaderText="{x:Static langRes:GeneralLangRes.Remove}" Click="ButtonRemoveAction_OnClick"/>
</Menu.Items>
</Menu>
<!-- TextBox: Search -->
<TextBox
Width="200"
Watermark="{x:Static langRes:GeneralLangRes.Search}"
Text="{Binding CurrentGridRows.SearchText}"/>
<!-- Panel: Menu -->
<ContentControl
Content="{Binding SelectedTreeNode}">
<ContentControl.DataTemplates>
<DataTemplate
DataType="mainWindow:ActionSetTreeNode">
<StackPanel
Orientation="Horizontal"
Spacing="6">
<!-- TextBox: Version -->
<TextBox
Width="100"
Text="{Binding Version}"/>
<!-- CheckBox: Is public -->
<CheckBox
Content="{x:Static langRes:GeneralLangRes.Public}"
IsChecked="{Binding IsPublic}"/>
</StackPanel>
</DataTemplate>
<DataTemplate
DataType="mainWindow:MainWindowTreeNode"/>
</ContentControl.DataTemplates>
</ContentControl>
<!-- ProgressBar: Status -->
<ProgressBar
HorizontalAlignment="Left"
VerticalAlignment="Stretch"
Maximum="{Binding Progress.MaxValue}"
Value="{Binding Progress.Value}"
ShowProgressText="{Binding Progress.ShowText}"
IsIndeterminate="{Binding Progress.IsGeneric}"
IsVisible="{Binding Progress.IsVisible}"/>
</StackPanel>
<!-- DataGrid -->
<DataGrid
Grid.Column="1"
Grid.Row="1"
x:Name="DataGridActions"
VerticalAlignment="Stretch"
ItemsSource="{Binding CurrentGridRows.View}"
SelectedItem="{Binding SelectedGridRow}">
<DataGrid.ContextMenu>
<ContextMenu x:Name="ContextMenuActions"/>
</DataGrid.ContextMenu>
<DataGrid.Columns>
<DataGridTextColumn
Header="{x:Static langRes:GeneralLangRes.Id}"
Binding="{Binding InheritedId}"
Width="*"/>
<DataGridTextColumn
Header="{x:Static langRes:GeneralLangRes.Name}"
Binding="{Binding InheritedName}"
Width="*"/>
<DataGridTextColumn
Header="{x:Static langRes:GeneralLangRes.UpdateType}"
Binding="{Binding InheritedUpdateType}"
IsVisible="{Binding IsUpdate}"/>
<DataGridTextColumn
Header="{x:Static langRes:GeneralLangRes.Side}"
Binding="{Binding InheritedSide}"/>
<DataGridTextColumn
Header="{x:Static langRes:GeneralLangRes.SourceType}"
Binding="{Binding InheritedSourceType}"/>
<DataGridTextColumn
Header="{x:Static langRes:GeneralLangRes.DestinationPath}"
Binding="{Binding InheritedDestPath}"
Width="*"/>
<DataGridTemplateColumn
Header="{x:Static langRes:GeneralLangRes.State}">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Image
Width="{x:Static symbols:SymbolGlobals.DefaultImageSmallSize}"
Source="{Binding StateImage}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
<!-- Action editor -->
<ScrollViewer
x:Name="ScrollViewerInstallAction"
Grid.Column="2"
Grid.Row="1"
DataContext="{Binding SelectedGridRow}"
VerticalScrollBarVisibility="Auto">
<StackPanel
Spacing="6">
<!-- Update -->
<Grid
ColumnDefinitions="150,*"
RowDefinitions="Auto,Auto,Auto,Auto,Auto"
RowSpacing="6"
ColumnSpacing="6"
IsVisible="{Binding IsUpdate}">
<!-- Header -->
<StackPanel
Grid.ColumnSpan="2"
Orientation="Horizontal"
Spacing="6">
<Image x:Name="ImageUpdate" Width="{x:Static symbols:SymbolGlobals.DefaultImageMediumSize}"/>
<TextBlock Text="{x:Static langRes:GeneralLangRes.Update}" FontSize="{x:Static symbols:SymbolGlobals.DefaultImageMediumSize}"/>
</StackPanel>
<!-- Inherit from -->
<Label Grid.Row="1" Grid.Column="0" Content="{x:Static langRes:GeneralLangRes.InheritFrom}" Target="TextBoxUpdateActionInheritFrom"/>
<TextBox
Grid.Row="1" Grid.Column="1"
x:Name="TextBoxUpdateActionInheritFrom"
Text="{Binding InheritFrom}"/>
<!-- Update type -->
<Label Grid.Row="2" Grid.Column="0" Content="{x:Static langRes:GeneralLangRes.UpdateType}" Target="ComboBoxUpdateActionSourceType"/>
<ComboBox
Grid.Row="2" Grid.Column="1"
x:Name="ComboBoxUpdateActionSourceType"
ItemsSource="{x:Static mainWindow:MainWindowGridRow.UpdateActionTypes}"
DisplayMemberBinding="{Binding Value}"
SelectedValueBinding="{Binding Key}"
SelectedValue="{Binding UpdateType}"/>
<!-- Source path -->
<Label Grid.Row="3" Grid.Column="0" Content="{x:Static langRes:GeneralLangRes.SourcePath}" Target="TextBoxInstallActionSourcePath"/>
<TextBox
Grid.Row="3" Grid.Column="1"
x:Name="TextBoxInstallActionSourcePath"
Text="{Binding SrcPath}"/>
<!-- Is Directory -->
<CheckBox Grid.Row="4" Grid.Column="1"
x:Name="CheckBoxUpdateActionIsDir"
Content="{x:Static langRes:GeneralLangRes.IsDirectory}"
IsChecked="{Binding IsDirectory}"/>
</Grid>
<!-- General -->
<Grid
ColumnDefinitions="150,*"
RowDefinitions="Auto,Auto,Auto,Auto,Auto"
RowSpacing="6"
ColumnSpacing="6">
<!-- Header -->
<StackPanel
Grid.ColumnSpan="2"
Orientation="Horizontal"
Spacing="6">
<Image
x:Name="ImageGeneral"
Width="{x:Static symbols:SymbolGlobals.DefaultImageMediumSize}"/>
<TextBlock Text="{x:Static langRes:GeneralLangRes.General}" FontSize="20"/>
</StackPanel>
<!-- Id -->
<Label Grid.Row="1" Grid.Column="0" Content="{x:Static langRes:GeneralLangRes.Id}" Target="TextBoxInstallActionId"/>
<TextBox
Grid.Row="1" Grid.Column="1"
x:Name="TextBoxInstallActionId"
Text="{Binding Id}"/>
<!-- Name -->
<Label Grid.Row="2" Grid.Column="0" Content="{x:Static langRes:GeneralLangRes.Name}" Target="TextBoxInstallActionName"/>
<TextBox
Grid.Row="2" Grid.Column="1"
x:Name="TextBoxInstallActionName"
Text="{Binding Name}"/>
<!-- Side -->
<Label Grid.Row="3" Grid.Column="0" Content="{x:Static langRes:GeneralLangRes.Side}" Target="ComboBoxInstallActionSide"/>
<ComboBox
Grid.Row="3" Grid.Column="1"
x:Name="ComboBoxInstallActionSide"
ItemsSource="{x:Static mainWindow:MainWindowGridRow.Sides}"
DisplayMemberBinding="{Binding Value}"
SelectedValueBinding="{Binding Key}"
SelectedValue="{Binding Side}"/>
<!-- Is Extra -->
<CheckBox
Grid.Row="4" Grid.Column="1"
x:Name="CheckBoxInstallActionIsExtra"
Content="{x:Static langRes:GeneralLangRes.IsExtra}"
IsChecked="{Binding IsExtra}"/>
</Grid>
<!-- Destination -->
<Grid
ColumnDefinitions="150,*"
RowDefinitions="Auto,Auto"
RowSpacing="6"
ColumnSpacing="6">
<!-- Header -->
<StackPanel
Grid.ColumnSpan="2"
Orientation="Horizontal"
Spacing="6">
<Image
x:Name="ImageDestination"
Width="{x:Static symbols:SymbolGlobals.DefaultImageMediumSize}"/>
<TextBlock Text="{x:Static langRes:GeneralLangRes.Destination}" FontSize="20"/>
</StackPanel>
<!-- Destination path -->
<Label Grid.Row="1" Grid.Column="0" Content="{x:Static langRes:GeneralLangRes.DestinationPath}" Target="TextBoxInstallDestPath"/>
<TextBox
Grid.Row="1" Grid.Column="1"
x:Name="TextBoxInstallDestPath"
Text="{Binding DestPath}"/>
</Grid>
<!-- Source -->
<Grid
ColumnDefinitions="150,*"
RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto"
RowSpacing="6"
ColumnSpacing="6">
<!-- Header -->
<StackPanel
Grid.ColumnSpan="2"
Orientation="Horizontal"
Spacing="6">
<Image
x:Name="ImageSource"
Width="{x:Static symbols:SymbolGlobals.DefaultImageMediumSize}"/>
<TextBlock Text="{x:Static langRes:GeneralLangRes.Source}" FontSize="20"/>
</StackPanel>
<!-- Source types -->
<Label Grid.Row="1" Grid.Column="0" Content="{x:Static langRes:GeneralLangRes.SourceType}" Target="CheckBoxInstallActionSourceType"/>
<ComboBox
Grid.Row="1" Grid.Column="1"
x:Name="CheckBoxInstallActionSourceType"
ItemsSource="{x:Static mainWindow:MainWindowGridRow.SourceTypes}"
DisplayMemberBinding="{Binding Value}"
SelectedValueBinding="{Binding Key}"
SelectedValue="{Binding SourceType}"/>
<!-- Is Zip -->
<CheckBox Grid.Row="2" Grid.Column="1"
x:Name="CheckBoxInstallActionIsZip"
Content="{x:Static langRes:GeneralLangRes.IsZipArchive}"
IsChecked="{Binding IsZip}"/>
<!-- Source owner -->
<Label Grid.Row="3" Grid.Column="0" Content="{x:Static langRes:GeneralLangRes.SourceOwner}" Target="TextBoxInstallActionSourceOwner"/>
<TextBox
Grid.Row="3" Grid.Column="1"
x:Name="TextBoxInstallActionSourceOwner"
Text="{Binding SourceOwner}"/>
<!-- Source name -->
<Label Grid.Row="4" Grid.Column="0" Content="{x:Static langRes:GeneralLangRes.SourceName}" Target="TextBoxInstallActionSourceName"/>
<TextBox
Grid.Row="4" Grid.Column="1"
x:Name="TextBoxInstallActionSourceName"
Text="{Binding SourceName}"/>
<!-- Source tag -->
<Label Grid.Row="5" Grid.Column="0" Content="{x:Static langRes:GeneralLangRes.SourceTag}" Target="TextBoxInstallActionSourceTag"/>
<TextBox
Grid.Row="5" Grid.Column="1"
x:Name="TextBoxInstallActionSourceTag"
Text="{Binding SourceTag}"/>
<!-- Source RegEx -->
<Label Grid.Row="6" Grid.Column="0" Content="{x:Static langRes:GeneralLangRes.SourceRegex}" Target="TextBoxInstallActionSourceRegEx"/>
<TextBox
Grid.Row="6" Grid.Column="1"
x:Name="TextBoxInstallActionSourceRegEx"
Text="{Binding SourceRegex}"/>
<!-- Source url -->
<Label Grid.Row="7" Grid.Column="0" Content="{x:Static langRes:GeneralLangRes.SourceUrl}" Target="TextBoxInstallActionSourceUrl"/>
<TextBox
Grid.Row="7" Grid.Column="1"
x:Name="TextBoxInstallActionSourceUrl" Text="{Binding SourceUrl}"/>
<!-- Zip archive path -->
<Label Grid.Row="8" Grid.Column="0" Content="{x:Static langRes:GeneralLangRes.ZipArchivePath}" Target="TextBoxInstallActionZipArchivePath"/>
<TextBox
Grid.Row="8" Grid.Column="1"
x:Name="TextBoxInstallActionZipArchivePath"
Text="{Binding ZipPath}"/>
</Grid>
<!-- Metadata -->
<Grid
ColumnDefinitions="150,*"
RowDefinitions="Auto,Auto"
RowSpacing="6"
ColumnSpacing="6"
VerticalAlignment="Center">
<!-- Header -->
<StackPanel
Grid.ColumnSpan="2"
Orientation="Horizontal"
Spacing="6">
<Image
x:Name="ImageMetadata"
Width="{x:Static symbols:SymbolGlobals.DefaultImageMediumSize}"/>
<TextBlock Text="{x:Static langRes:GeneralLangRes.Metadata}" FontSize="20"/>
</StackPanel>
<!-- Website -->
<Label Grid.Row="1" Grid.Column="0" Content="{x:Static langRes:GeneralLangRes.Website}" Target="TextBoxInstallActionWebsite"/>
<TextBox
Grid.Row="1" Grid.Column="1"
x:Name="TextBoxInstallActionWebsite"
Text="{Binding Website}"/>
</Grid>
</StackPanel>
</ScrollViewer>
</Grid>
</Window>

View File

@@ -0,0 +1,271 @@
using System.Reflection;
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Interactivity;
using DynamicData;
using ModpackUpdater.Apps.Manager.Api;
using ModpackUpdater.Apps.Manager.Api.Model;
using ModpackUpdater.Apps.Manager.Api.Plugins.Features;
using ModpackUpdater.Apps.Manager.Api.Plugins.Params;
using ModpackUpdater.Apps.Manager.Settings;
using ModpackUpdater.Apps.Manager.Ui.Models.MainWindow;
using Pilz.Extensions;
using Pilz.Extensions.Collections;
using Pilz.Features;
using Pilz.UI.AvaloniaUI.Features;
using Pilz.UI.Symbols;
namespace ModpackUpdater.Apps.Manager.Ui;
public partial class MainWindow : Window, IMainApi
{
private WorkspaceFeature? curWs;
public MainWindowViewModel Model { get; } = new();
Window IMainApi.MainWindow => this;
public IMainApi MainApi => this;
public bool HasClosed { get; private set; }
public MainWindow()
{
InitializeComponent();
Title = $"{Title} (v{Assembly.GetExecutingAssembly().GetAppVersion().ToShortHumanString()})";
GridMain.DataContext = Model;
MenuItemWorkspace.Icon = AppGlobals.Symbols.GetImage(AppSymbols.workspace, SymbolSize.Small);
MenuItemWorkspacePreferences.Icon = AppGlobals.Symbols.GetImage(AppSymbols.settings, SymbolSize.Small);
MenuItemSaveWorkspace.Icon = AppGlobals.Symbols.GetImage(AppSymbols.save, SymbolSize.Small);
MenuItemNewWorkspace.Icon = AppGlobals.Symbols.GetImage(AppSymbols.new_window, SymbolSize.Small);
MenuItemRecentWorkspaces.Icon = AppGlobals.Symbols.GetImage(AppSymbols.time_machine, SymbolSize.Small);
MenuItemUpdate.Icon = AppGlobals.Symbols.GetImage(AppSymbols.update_done, SymbolSize.Small);
MenuItemTools.Icon = AppGlobals.Symbols.GetImage(AppSymbols.tools, SymbolSize.Small);
MenuItemCreateUpdate.Icon = AppGlobals.Symbols.GetImage(AppSymbols.add, SymbolSize.Small);
MenuItemRemoveUpdate.Icon = AppGlobals.Symbols.GetImage(AppSymbols.remove, SymbolSize.Small);
MenuItemAddAction.HeaderIcon = AppGlobals.Symbols.GetImage(AppSymbols.add, SymbolSize.Small);
MenuItemRemoveAction.HeaderIcon = AppGlobals.Symbols.GetImage(AppSymbols.remove, SymbolSize.Small);
ImageUpdate.Source = AppGlobals.Symbols.GetImageSource(AppSymbols.update_done);
ImageMetadata.Source = AppGlobals.Symbols.GetImageSource(AppSymbols.show_property);
ImageGeneral.Source = AppGlobals.Symbols.GetImageSource(AppSymbols.normal_screen);
ImageSource.Source = AppGlobals.Symbols.GetImageSource(AppSymbols.input);
ImageDestination.Source = AppGlobals.Symbols.GetImageSource(AppSymbols.output);
PluginFeatureController.Instance.Features.Get(FeatureTypes.Workspace).Ordered().InsertItemsTo(MenuItemNewWorkspace.Items,
customClickHandler: MenuItemNewWorkspaceItem_Click,
insertPrioSplitters: true);
PluginFeatureController.Instance.Features.Get(FeatureTypes.ActionsContextMenu).Ordered().InsertItemsTo(ContextMenuActions.Items,
customClickHandler: MenuItemActionItem_Click,
insertPrioSplitters: true);
PluginFeatureController.Instance.Functions.Get(FeatureTypes.Tools).InsertItemsTo(MenuItemTools.Items,
customClickHandler: MenuItemToolsItem_Click,
insertPrioSplitters: true);
}
private async Task LoadNewWorkspace(IWorkspace? workspace, WorkspaceFeature feature)
{
if (workspace is null)
return;
if (!await workspace.Load())
return;
if (workspace != Model.CurrentWorkspace)
{
Model.CurrentWorkspace = workspace;
curWs = feature;
}
AddToRecentFiles(workspace);
LoadRecentWorkspaces();
}
private async Task SaveWorkspace()
{
if (Model.CurrentWorkspace is not { } ws)
return;
Model.Progress.Start();
await ws.Save();
Model.Progress.Stop();
}
private void LoadRecentWorkspaces()
{
var settings = Program.Settings.Get<WorkspaceSettings>();
MenuItemRecentWorkspaces.Items.Clear();
settings.Workspaces.ForEach(config =>
{
if (PluginFeatureController.Instance.Features.Get(FeatureTypes.Workspace).OfType<WorkspaceFeature>().FirstOrDefault(n => n.Identifier == config.ProviderId) is not WorkspaceFeature feature)
return;
var item = new MenuItem
{
Header = config.DisplayText,
Icon = feature.Icon,
DataContext = new MainWindowRecentFilesItem(config, feature),
};
item.Click += MenuItemRecentWorkspaceItem_Click;
MenuItemRecentWorkspaces.Items.Add(item);
});
}
private static void AddToRecentFiles(IWorkspace workspace)
{
var settings = Program.Settings.Get<WorkspaceSettings>();
settings.Workspaces.RemoveAll(n => n == workspace.Config);
settings.Workspaces.Insert(0, workspace.Config);
settings.Workspaces.Skip(20).ForEach(n => settings.Workspaces.Remove(n));
}
private async void Window_OnLoaded(object? sender, RoutedEventArgs e)
{
var updater = new AppUpdates("manager", this)
{
UsePopups = true,
};
updater.OnDownloadProgramUpdate += (_, _) => Model.Progress.Start();
await updater.UpdateApp();
Model.Progress.Stop();
LoadRecentWorkspaces();
}
private void Window_OnClosed(object? sender, EventArgs e)
{
HasClosed = true;
}
private async void Window_OnKeyDown(object? sender, KeyEventArgs e)
{
if (e.KeyModifiers == KeyModifiers.Control && e.Key == Key.Space)
await SaveWorkspace();
}
private async void MenuItemNewWorkspaceItem_Click(object? sender, RoutedEventArgs e)
{
if (sender is not MenuItem item || item.Tag is not WorkspaceFeature feature)
return;
var context = new WorkspaceContext(MainApi, null);
await feature.Configure(context);
if (!context.Canceled)
await LoadNewWorkspace(context.Workspace, feature);
}
private async void MenuItemWorkspacePreferences_OnClick(object? sender, RoutedEventArgs e)
{
if (curWs is null || Model.CurrentWorkspace is null)
return;
var context = new WorkspaceContext(MainApi, Model.CurrentWorkspace);
await curWs.Configure(context);
if (!context.Canceled)
await LoadNewWorkspace(context.Workspace, curWs);
}
private async void MenuItemSaveWorkspace_OnClick(object? sender, RoutedEventArgs e)
{
await SaveWorkspace();
}
private async void MenuItemRecentWorkspaceItem_Click(object? sender, RoutedEventArgs e)
{
if (sender is MenuItem item && item.DataContext is MainWindowRecentFilesItem tag && tag.Feature.CreateFromConfig(tag.Config) is IWorkspace workspace)
await LoadNewWorkspace(workspace, tag.Feature);
}
private async void MenuItemToolsItem_Click(object? sender, RoutedEventArgs e)
{
if (sender is MenuItem item && item.Tag is PluginFunction func)
await func.ExecuteAsync(new MainApiParameters(this));
}
private async void MenuItemActionItem_Click(object? sender, RoutedEventArgs e)
{
if (sender is MenuItem item && item.Tag is PluginFunction func)
await func.ExecuteAsync(new MainApiParameters(this));
}
private void MenuItemCreateUpdate_OnClick(object? sender, RoutedEventArgs e)
{
if (Model.CurrentWorkspace?.UpdateInfos is null
|| Model.CurrentTreeNodes?.ElementAtOrDefault(1) is not { } nodeUpdates)
return;
var update = new UpdateInfo
{
Version = new(),
};
Model.CurrentWorkspace.UpdateInfos.Updates.Insert(0, update);
var item = new ActionSetTreeNode(update);
nodeUpdates.Nodes.Insert(0, item);
TreeViewWorkspace.SelectedItem = item;
TreeViewWorkspace.ScrollIntoView(item);
}
private void MenuItemRemoveUpdate_OnClick(object? sender, RoutedEventArgs e)
{
if (Model.CurrentWorkspace?.UpdateInfos is null
|| Model.CurrentTreeNodes?.ElementAtOrDefault(1) is not { } nodeUpdates
|| Model.SelectedTreeNode is not ActionSetTreeNode nodeUpdate
|| nodeUpdate.Infos is not UpdateInfo update)
return;
nodeUpdates.Nodes.Remove(nodeUpdate);
Model.CurrentWorkspace.UpdateInfos.Updates.Remove(update);
}
private void ButtonAddAction_OnClick(object? sender, RoutedEventArgs e)
{
if (Model.CurrentWorkspace?.InstallInfos is not { } rootInfos
|| Model.SelectedTreeNode is not ActionSetTreeNode nodeUpdate
|| Model.CurrentGridRows is not { } rows)
return;
InstallAction action;
switch (nodeUpdate.Infos)
{
case UpdateInfo updateInfos:
updateInfos.Actions.Add((UpdateAction)(action = new UpdateAction()));
break;
case InstallInfos installInfos:
installInfos.Actions.Add(action = new InstallAction());
break;
default:
return;
}
var row = new MainWindowGridRow(action, rootInfos);
rows.List.Add(row);
DataGridActions.SelectedItem = row;
DataGridActions.ScrollIntoView(row, null);
}
private void ButtonRemoveAction_OnClick(object? sender, RoutedEventArgs e)
{
if (Model.SelectedGridRow is not { } row
|| Model.SelectedTreeNode is not ActionSetTreeNode nodeUpdate
|| Model.CurrentGridRows is not { } rows)
return;
switch (nodeUpdate.Infos)
{
case UpdateInfo updateInfos:
if (row.Action is UpdateAction action)
updateInfos.Actions.Remove(action);
break;
case InstallInfos installInfos:
installInfos.Actions.Remove(row.Action);
break;
default:
return;
}
rows.List.Remove(row);
}
}

View File

@@ -0,0 +1,34 @@
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using DynamicData;
namespace ModpackUpdater.Apps.Manager.Ui.Models;
public class DynamicDataView<T> : INotifyPropertyChanged where T : notnull
{
public event PropertyChangedEventHandler? PropertyChanged;
private string? searchText;
private readonly Subject<string?> searchTextSubject = new();
public SourceList<T> List { get; } = new();
public ReadOnlyObservableCollection<T> View { get; }
public DynamicDataView(Func<string?, Func<T, bool>> predicate)
{
List.Connect()
.Filter(searchTextSubject/*.Throttle(TimeSpan.FromMilliseconds(250))*/.Select(predicate))
.Bind(out var view)
.Subscribe();
searchTextSubject?.OnNext(searchText);
View = view;
}
public string? SearchText
{
get => searchText;
set => searchTextSubject.OnNext(searchText = value);
}
}

View File

@@ -0,0 +1,165 @@
using System.ComponentModel;
using Avalonia.Media;
using ModpackUpdater.Apps.Manager.LangRes;
using ModpackUpdater.Apps.Manager.Utils;
using PropertyChanged;
namespace ModpackUpdater.Apps.Manager.Ui.Models.MainWindow;
public class MainWindowGridRow(InstallAction action, IActionSet baseActions) : INotifyPropertyChanged
{
public event PropertyChangedEventHandler? PropertyChanged;
public static Dictionary<SourceType, string> SourceTypes { get; } = Enum.GetValues<SourceType>().ToDictionary(n => n, n => SourceTypeLangRes.ResourceManager.GetString( Enum.GetName(n)!)!);
public static Dictionary<Side, string> Sides { get; } = Enum.GetValues<Side>().ToDictionary(n => n, n => SideLangRes.ResourceManager.GetString(Enum.GetName(n)!)!);
public static Dictionary<UpdateActionType, string> UpdateActionTypes { get; } = Enum.GetValues<UpdateActionType>().ToDictionary(n => n, n => UpdateActionTypeLangRes.ResourceManager.GetString(Enum.GetName(n)!)!);
public InstallAction Action => action;
private InstallAction? Base => action is UpdateAction ua ? baseActions.Actions.FirstOrDefault(n => n.Id == ua.InheritFrom) : null;
private InstallAction Inherited => Base ?? action;
public bool IsUpdate => action is UpdateAction;
public IImage? StateImage { get; set; }
[DependsOn(nameof(Id), nameof(InheritFrom))]
public string? InheritedId => action is UpdateAction ua && !string.IsNullOrWhiteSpace(ua.InheritFrom) ? ua.InheritFrom : action.Id;
[DependsOn(nameof(Side), nameof(InheritedId), nameof(Id))]
public string InheritedSide => Sides[Inherited.Side];
[DependsOn(nameof(UpdateType), nameof(InheritedId), nameof(Id))]
public string InheritedUpdateType => action is UpdateAction ua ? UpdateActionTypes[ua.Type] : string.Empty;
[DependsOn(nameof(SourceType), nameof(InheritedId), nameof(Id))]
public string InheritedSourceType => SourceTypes[Inherited.SourceType];
[DependsOn(nameof(DestPath), nameof(InheritedId), nameof(Id))]
public string? InheritedDestPath => Inherited.DestPath;
[DependsOn(nameof(Name), nameof(InheritedId), nameof(Id))]
public string? InheritedName => Inherited.Name;
public string? Id
{
get => action.Id;
set => action.Id = value?.Trim().Nullify();
}
public string? Name
{
get => action.Name;
set => action.Name = value?.Trim().Nullify();
}
public string? Website
{
get => action.Website;
set => action.Website = value?.Trim().Nullify();
}
public bool IsZip
{
get => action.IsZip;
set => action.IsZip = value;
}
public string? ZipPath
{
get => action.ZipPath;
set => action.ZipPath = value?.Trim().Nullify();
}
public string? DestPath
{
get => action.DestPath;
set => action.DestPath = value?.Trim().Nullify();
}
public string? SourceUrl
{
get => action.SourceUrl;
set => action.SourceUrl = value?.Trim().Nullify();
}
public SourceType SourceType
{
get => action.SourceType;
set => action.SourceType = value;
}
public string? SourceOwner
{
get => action.SourceOwner;
set => action.SourceOwner = value?.Trim().Nullify();
}
public string? SourceName
{
get => action.SourceName;
set => action.SourceName = value?.Trim().Nullify();
}
public string? SourceRegex
{
get => action.SourceRegex;
set => action.SourceRegex = value?.Trim().Nullify();
}
public string? SourceTag
{
get => action.SourceTag;
set => action.SourceTag = value?.Trim().Nullify();
}
public Side Side
{
get => action.Side;
set => action.Side = value;
}
public bool IsExtra
{
get => action.IsExtra;
set => action.IsExtra = value;
}
public UpdateActionType UpdateType
{
get => action is UpdateAction ua ? ua.Type : default;
set
{
if (action is UpdateAction ua)
ua.Type = value;
}
}
public string? SrcPath
{
get => (action as UpdateAction)?.SrcPath;
set
{
if (action is UpdateAction ua)
ua.SrcPath = value?.Trim().Nullify();
}
}
public bool IsDirectory
{
get => action is UpdateAction ua && ua.IsDirectory;
set
{
if (action is UpdateAction ua)
ua.IsDirectory = value;
}
}
public string? InheritFrom
{
get => (action as UpdateAction)?.InheritFrom;
set
{
if (action is UpdateAction ua)
ua.InheritFrom = value?.Trim().Nullify();
}
}
}

View File

@@ -0,0 +1,6 @@
using ModpackUpdater.Apps.Manager.Api.Model;
using ModpackUpdater.Apps.Manager.Api.Plugins.Features;
namespace ModpackUpdater.Apps.Manager.Ui.Models.MainWindow;
public record class MainWindowRecentFilesItem(WorkspaceConfig Config, WorkspaceFeature Feature);

View File

@@ -0,0 +1,61 @@
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using Avalonia.Media;
using PropertyChanged;
namespace ModpackUpdater.Apps.Manager.Ui.Models.MainWindow;
public abstract class MainWindowTreeNode : INotifyPropertyChanged
{
public event PropertyChangedEventHandler? PropertyChanged;
public abstract string DisplayText { get; }
public virtual IImage? Image { get; set; }
public ObservableCollection<MainWindowTreeNode> Nodes { get; init; } = [];
protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
protected bool SetField<T>(ref T field, T value, [CallerMemberName] string? propertyName = null)
{
if (EqualityComparer<T>.Default.Equals(field, value)) return false;
field = value;
OnPropertyChanged(propertyName);
return true;
}
}
public class SimpleMainWindowTreeNode(string text) : MainWindowTreeNode
{
public override string DisplayText => text;
}
public class ActionSetTreeNode(IActionSetInfos infos) : MainWindowTreeNode
{
private string editVersion = infos.Version?.ToString() ?? string.Empty;
public override string DisplayText => infos.Version?.ToString() ?? string.Empty;
public IActionSetInfos Infos => infos;
public override IImage? Image => AppGlobals.Symbols.GetImageSource(infos.IsPublic ? AppSymbols.eye : AppSymbols.invisible);
[AlsoNotifyFor(nameof(DisplayText))]
public string Version
{
get => editVersion;
set
{
infos.Version = System.Version.TryParse(value, out var v) ? v : new();
editVersion = value;
}
}
[AlsoNotifyFor(nameof(Image))]
public bool IsPublic
{
get => infos.IsPublic;
set => infos.IsPublic = value;
}
}

View File

@@ -0,0 +1,85 @@
using System.Collections.ObjectModel;
using System.ComponentModel;
using ModpackUpdater.Apps.Manager.Api.Model;
using ModpackUpdater.Apps.Manager.LangRes;
using PropertyChanged;
namespace ModpackUpdater.Apps.Manager.Ui.Models.MainWindow;
public class MainWindowViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler? PropertyChanged;
private ObservableCollection<MainWindowTreeNode>? currentTreeNodes;
private MainWindowTreeNode? selectedTreeNode;
private IWorkspace? currentWorkspace;
public bool IsUpdate => selectedTreeNode is ActionSetTreeNode node && node.Infos is UpdateInfo;
public ProgressInfos Progress { get; } = new();
public MainWindowGridRow? SelectedGridRow { get; set; }
public DynamicDataView<MainWindowGridRow> CurrentGridRows { get; } = new(FilterGridRows);
[AlsoNotifyFor(nameof(CurrentTreeNodes))]
public IWorkspace? CurrentWorkspace
{
get => currentWorkspace;
set
{
currentTreeNodes = null;
currentWorkspace = value;
}
}
public ObservableCollection<MainWindowTreeNode>? CurrentTreeNodes
{
get
{
if (currentTreeNodes == null && currentWorkspace?.InstallInfos != null && currentWorkspace?.UpdateInfos != null)
currentTreeNodes =
[
new ActionSetTreeNode(currentWorkspace.InstallInfos),
new SimpleMainWindowTreeNode(GeneralLangRes.Updates)
{
Image = AppGlobals.Symbols.GetImageSource(AppSymbols.update_done),
Nodes = [.. currentWorkspace.UpdateInfos.Updates.Select(n => new ActionSetTreeNode(n))],
},
];
return currentTreeNodes;
}
}
public MainWindowTreeNode? SelectedTreeNode
{
get => selectedTreeNode;
set
{
selectedTreeNode = value;
CurrentGridRows.List.Edit(list =>
{
list.Clear();
if (CurrentWorkspace?.InstallInfos != null && selectedTreeNode is ActionSetTreeNode node)
list.AddRange(node.Infos.Actions.Select(n => new MainWindowGridRow(n, CurrentWorkspace.InstallInfos)));
});
}
}
private static Func<MainWindowGridRow, bool> FilterGridRows(string? searchText)
{
return n => string.IsNullOrWhiteSpace(searchText)
|| (!string.IsNullOrWhiteSpace(n.Name) && n.Name.Contains(searchText))
|| (!string.IsNullOrWhiteSpace(n.InheritFrom) && n.InheritFrom.Contains(searchText))
|| (!string.IsNullOrWhiteSpace(n.InheritedDestPath) && n.InheritedDestPath.Contains(searchText))
|| (!string.IsNullOrWhiteSpace(n.InheritedId) && n.InheritedId.Contains(searchText))
|| (!string.IsNullOrWhiteSpace(n.InheritedSide) && n.InheritedSide.Contains(searchText))
|| (!string.IsNullOrWhiteSpace(n.InheritedSourceType) && n.InheritedSourceType.Contains(searchText))
|| (!string.IsNullOrWhiteSpace(n.InheritedUpdateType) && n.InheritedUpdateType.Contains(searchText))
|| (!string.IsNullOrWhiteSpace(n.SourceName) && n.SourceName.Contains(searchText))
|| (!string.IsNullOrWhiteSpace(n.SourceOwner) && n.SourceOwner.Contains(searchText))
|| (!string.IsNullOrWhiteSpace(n.SourceRegex) && n.SourceRegex.Contains(searchText))
|| (!string.IsNullOrWhiteSpace(n.SourceTag) && n.SourceTag.Contains(searchText))
|| (!string.IsNullOrWhiteSpace(n.SourceUrl) && n.SourceUrl.Contains(searchText))
|| (!string.IsNullOrWhiteSpace(n.SrcPath) && n.SrcPath.Contains(searchText))
|| (!string.IsNullOrWhiteSpace(n.Website) && n.Website.Contains(searchText))
;
}
}

View File

@@ -0,0 +1,48 @@
using System.ComponentModel;
namespace ModpackUpdater.Apps.Manager.Ui.Models;
public class ProgressInfos : INotifyPropertyChanged
{
public event PropertyChangedEventHandler? PropertyChanged;
public bool IsVisible { get; set; }
public bool IsGeneric { get; set; }
public bool ShowText { get; set; }
public double MaxValue { get; set; }
public double Value { get; set; }
public void Start(int maxValue)
{
IsGeneric = false;
Value = 0;
MaxValue = maxValue;
ShowText = true;
IsVisible = true;
}
public void Start()
{
ShowText = false;
IsGeneric = true;
IsVisible = true;
}
public void Set(int value)
{
if (!IsGeneric)
Value = value;
}
public void Increment()
{
if (!IsGeneric)
Value += 1;
}
public void Stop()
{
IsVisible = false;
IsGeneric = false;
}
}

View File

@@ -0,0 +1,30 @@
using System.ComponentModel;
using ModpackUpdater.Manager;
namespace ModpackUpdater.Apps.Manager.Ui.Models.UpdatesCollectorViewMode;
public record ModUpdateInfo(ModVersionInfo[] AvailableVersions, InstallAction Origin) : INotifyPropertyChanged
{
public event PropertyChangedEventHandler? PropertyChanged;
public int NewVersion { get; set; }
public bool Visible { get; set; } = true;
public string? OldVersion
{
get
{
if (AvailableVersions.FirstOrDefault(n => n.Tag.Equals(Origin.SourceTag)) is { } old
&& !old.Name.Equals(old.Tag, StringComparison.InvariantCulture))
return $"{old.Name} ({old.Tag})";
return Origin.SourceTag;
}
}
public IEnumerable<string> DisplayVersions { get; } = AvailableVersions.Select(n =>
{
if (string.IsNullOrWhiteSpace(n.Tag) || n.Tag.Equals(n.Name, StringComparison.InvariantCulture))
return n.Name;
return $"{n.Name} ({n.Tag})";
});
}

View File

@@ -0,0 +1,3 @@
namespace ModpackUpdater.Apps.Manager.Ui.Models.UpdatesCollectorViewMode;
public record ModUpdates(IList<ModUpdateInfo> List);

View File

@@ -0,0 +1,22 @@
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Reactive;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using DynamicData;
using PropertyChanged;
namespace ModpackUpdater.Apps.Manager.Ui.Models.UpdatesCollectorViewMode;
public class UpdatesCollectorViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler? PropertyChanged;
public ProgressInfos Progress { get; } = new();
public DynamicDataView<ModUpdateInfo> Updates { get; } = new(FilterUpdates);
private static Func<ModUpdateInfo, bool> FilterUpdates(string? searchText)
{
return n => string.IsNullOrWhiteSpace(searchText) || (n.Origin.Name != null && n.Origin.Name.Contains(searchText, StringComparison.InvariantCultureIgnoreCase));
}
}

View File

@@ -0,0 +1,94 @@
<dialogs:AvaloniaFlyoutBase
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:dialogs="https://git.pilzinsel64.de/pilz-framework/pilz"
xmlns:updatesCollectorViewMode="clr-namespace:ModpackUpdater.Apps.Manager.Ui.Models.UpdatesCollectorViewMode"
xmlns:langRes="clr-namespace:ModpackUpdater.Apps.Manager.LangRes"
mc:Ignorable="d"
d:DesignWidth="600"
d:DesignHeight="450"
Width="800"
Height="600"
x:Class="ModpackUpdater.Apps.Manager.Ui.UpdatesCollectorView"
x:DataType="updatesCollectorViewMode:UpdatesCollectorViewModel"
Loaded="Me_OnLoaded">
<!-- Main -->
<dialogs:AvaloniaFlyoutBase.MainContent>
<Grid
RowDefinitions="Auto,*"
RowSpacing="6"
VerticalAlignment="Stretch">
<!-- TextBox: Search -->
<TextBox
Grid.Row="0"
Watermark="{x:Static langRes:GeneralLangRes.Search}"
Text="{Binding Updates.SearchText}"/>
<!-- ScrollViewer: Updates -->
<ScrollViewer
Grid.Row="1">
<ItemsControl
ItemsSource="{Binding Updates.View}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid
ColumnDefinitions="20*,20*,20*,Auto"
ColumnSpacing="6"
RowSpacing="6"
Margin="3">
<!-- Label: Name -->
<TextBlock
Grid.Column="0"
VerticalAlignment="Center"
Text="{Binding Origin.Name}"/>
<!-- Label: Version (old) -->
<TextBlock
Grid.Column="1"
VerticalAlignment="Center"
Text="{Binding OldVersion}"/>
<!-- ComboBox: Version (new) -->
<ComboBox
HorizontalAlignment="Stretch"
Grid.Column="2"
ItemsSource="{Binding DisplayVersions}"
SelectedIndex="{Binding NewVersion}"/>
<!-- Button: RemoveUpdate -->
<dialogs:ImageButton
Grid.Column="3"
VerticalAlignment="Center"
HorizontalAlignment="Center"
Content="{x:Static langRes:GeneralLangRes.Remove}"
Click="ButtonRemoveUpdate_Click"/>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
</Grid>
</dialogs:AvaloniaFlyoutBase.MainContent>
<!-- Footer -->
<dialogs:AvaloniaFlyoutBase.FooterContent>
<!-- ProgressBar: Status -->
<ProgressBar
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Maximum="{Binding Progress.MaxValue}"
Value="{Binding Progress.Value}"
ShowProgressText="{Binding Progress.ShowText}"
IsIndeterminate="{Binding Progress.IsGeneric}"
IsVisible="{Binding Progress.IsVisible}"/>
</dialogs:AvaloniaFlyoutBase.FooterContent>
</dialogs:AvaloniaFlyoutBase>

Some files were not shown because too many files have changed in this diff Show More