Compare commits

...

1346 commits

Author SHA1 Message Date
c9282706f0 Update README.md 2026-10-13 06:27:43 +00:00
2eff59c883 chore(config): track macos apple-darwin linker configuration 2026-10-13 11:46:49 +05:30
f3355bd180 docs: add download guide image to README 2026-10-11 12:59:33 +05:30
d28fd9ce46 docs: add download guide image for releases 2026-10-11 12:58:15 +05:30
e99da40629 docs: establish cracked release suffix progression and build rules in AGENTS.md 2026-10-11 09:23:56 +05:30
a8826b83d3 fix(skins): fix skin lookup across directories, reliable TCP flush, modern userProperties, and optimize compile time 2026-10-11 09:13:55 +05:30
e06775aae5 feat: update base to official upstream 2.8.0 with all cracked features intact (2.8.0-c1) 2026-10-10 20:21:19 +05:30
e4555c4b8e chore(build): optimize release profile with ThinLTO and remove unused import 2026-10-10 20:20:46 +05:30
0a29f0ab47 feat(ui): port 3D skins tab, offline onboarding and settings, and Forgejo auto-updater to 2.8.0 2026-10-10 18:49:26 +05:30
8d7939c041 feat(skins): integrate proper isolated offline skin system via Authlib-Injector 2026-10-10 18:29:12 +05:30
1e66c9216c feat(auth): enable offline mode accounts and launch without Microsoft account 2026-10-10 18:23:10 +05:30
b979feff0c chore: configure 2.8.0-c1 metadata, packaging, and release workflows 2026-10-10 18:19:17 +05:30
8652be8b87 feat(updater): switch auto-updater and documentation to Forgejo releases API 2026-10-10 18:11:33 +05:30
6793d6f68f chore: ignore dist directory 2026-10-10 15:22:21 +05:30
40838abf2d ci: add native forgejo release workflow with linux packaging and direct asset publishing 2026-10-10 14:06:05 +05:30
LynithDev
1d6ec59825 chore: pin freya to Polyfrost fork on 0.5.0-beta.10 without libxdo 2026-10-10 02:00:17 +02:00
github-actions[bot]
2ae679d220 chore: release 2.8.0 2026-10-09 23:26:24 +00:00
Maciek
56d73e3ab1
feat: multiple minecraft instances (#955)
* feat: mutliple minecraft instances (only dedicated clusters)

* changeset
2026-10-10 01:24:25 +02:00
LynithDev
011c4416b1 fix: open folder freeze issue 2026-10-10 01:16:07 +02:00
Maciek
76e4cac6f4
fix: fixed downloading notification alerting about new notification on every byte read (#952) 2026-10-10 01:10:05 +02:00
Maciek
6ee7bdb443
feat: added status bar for auth states (#954) 2026-10-10 01:02:39 +02:00
Maciek
97d4477c69
fix: added scrollbar gutter to the notifications overlay (#947) 2026-10-10 01:01:51 +02:00
LynithDev
51cb12c964
fix: bundled package regression causing it to disable and not update (#967) 2026-10-10 01:01:30 +02:00
LynithDev
a976a66c8b fix: nightly executable uploads 2026-10-10 01:00:46 +02:00
LynithDev
2f5a64b97d
Fix/linux skia crashes (#966)
* fix: run hooks before early returns in cluster and gallery views

* chore: upgrade freya to 0.5.0-beta.9

* chore: pin freya to 0.5.0-beta.10 branch for skia 0.103.2
2026-10-10 00:58:42 +02:00
Maciek
c6361c7266
fix: fixed shield.io badges not rendering properly (#953)
* fix: fixed shield.io badges not rendering properly

* changeset
2026-10-10 00:55:21 +02:00
Maciek
61d24b688e
fix: added better errors for mclogs (#963)
* fix: added better errors for mclogs

* chore: added a warning when the log is shortened
2026-10-10 00:54:49 +02:00
Maciek
d21c0dc22b
feat: added filter for loaders in browser view (#946)
* feat: added filter for loaders in browser view

* changeset
2026-10-10 00:54:45 +02:00
2b1bfd1485 docs: add offline usage warning embed to README
Some checks are pending
OneClient Nightly (Unsigned) / build (aarch64, app,dmg, darwin, macos-latest, aarch64-apple-darwin) (push) Waiting to run
OneClient Nightly (Unsigned) / build (x86_64, app,dmg, 10.14, darwin, macos-latest, x86_64-apple-darwin) (push) Waiting to run
OneClient Nightly (Unsigned) / build (x86_64, deb,appimage, linux, ubuntu-22.04, ) (push) Waiting to run
OneClient Nightly (Unsigned) / build (x86_64, nsis, windows, windows-latest, ) (push) Waiting to run
2026-10-09 22:23:58 +05:30
Julian Chang
5a8728ef04
Retroactive fix for 1ab294f2 2026-10-09 00:01:40 -04:00
Julian Chang
1b633adcf2
fix bundle newer mods being overwritten by local mods 2026-10-08 03:11:19 -04:00
Julian Chang
66b4ab4e64
update disabled mods still 2026-10-08 00:27:31 -04:00
github-actions[bot]
0f21171fe2 chore: release 2.7.0 2026-10-05 21:22:43 +00:00
Julian Chang
d073e10a3b
Add custom game args to settings (#905) 2026-10-05 23:21:05 +02:00
LynithDev
e7ddd85960
fix: url opening, dgpu dropdown instead of switcher, invisible icons (#941) 2026-10-05 23:20:32 +02:00
Julian Chang
abf708fdb4
mods folder sync (#937) 2026-10-05 23:20:04 +02:00
Julian Chang
1ab294f293
fix opt-in bundles not being asked for new mc versions (#939) 2026-10-05 23:19:49 +02:00
LynithDev
7683651ad4 chore: make nightly release action upload portable executables 2026-10-05 22:10:24 +02:00
LynithDev
a9fab65a0b
chore: add tooltip on settings system info (#926)
* chore: add tooltip on settings system info

* chore: add changeset
2026-10-05 22:06:40 +02:00
LynithDev
c765a3c2d4
feat: github packages can now be opened in browser (#943) 2026-10-05 21:03:29 +02:00
LynithDev
0621da9db0 chore: update description for launcher animation setting 2026-10-05 20:58:05 +02:00
LynithDev
97852aea41
fix: mclogs notification showing repeatedly (#942) 2026-10-05 20:42:47 +02:00
Julian Chang
7a6a7cbc0b
Merge branch 'main' of https://github.com/Polyfrost/OneLauncher 2026-10-03 15:14:39 -04:00
Julian Chang
7c304a2d3a
fix: match os argument rules on apple silicon for old versions 2026-10-03 15:14:35 -04:00
LynithDev
b2384afb4e
feat: log censoring (#925) 2026-10-03 16:00:44 +02:00
LynithDev
c43272683b
feat: add code block and update the open link dialogs with the code block (#924) 2026-10-03 15:17:52 +02:00
LynithDev
cfcf7a7669
chore: new changelog generator (#855)
* chore: new changelog generator

* ci: require a change file on PRs
2026-10-03 00:23:41 +02:00
github-actions[bot]
a2a3e5b7b1 chore: bump to 2.6.1 2026-10-02 14:17:51 +00:00
Maciek
a3d90cc2ce
Merge pull request #908 from Polyfrost/fix/aur-distribution
aur distribution fix
2026-10-02 16:15:17 +02:00
rozwader
ee7c84901f stray commas 2026-10-02 16:14:37 +02:00
rozwader
ad6ca92519 fix 2026-10-02 15:38:05 +02:00
Julian Chang
fb80e6eb60
Merge branch 'main' of https://github.com/Polyfrost/OneLauncher 2026-10-02 04:42:04 -04:00
Julian Chang
a8ba800dba
Reapply "chore: bump to 2.6.0"
This reverts commit 17a99b671e.
2026-10-02 04:41:58 -04:00
Maciek
ed6b014aac
chore: deleted icons from stats views (#906) 2026-10-02 04:23:02 -04:00
Julian Chang
17a99b671e
Revert "chore: bump to 2.6.0"
This reverts commit e74eb60ba2.
2026-10-02 04:21:05 -04:00
Julian Chang
e2db158653
fix: refresh loader version info and metadata manifests (#904) 2026-10-02 03:09:17 -04:00
Julian Chang
80e7cf0e59
Merge branch 'main' of https://github.com/Polyfrost/OneLauncher 2026-10-02 01:43:04 -04:00
Julian Chang
538a0da24d
prompt for different mc versions when installing modpacks 2026-10-02 01:38:42 -04:00
Julian Chang
03c4412d15
bundle opt out per content type (#810)
* exclude opted out bundles from package list

* bundle opt out per content type

* list only the content types a bundle is still opted into

* count inferred bundle installs as opted in

* remove comments from merge

* key bundle opt-out by external id so outdated GitHub installs still count

* fix: keep bundle type opt-out across catalog changes

* fix: keep switched-on bundle files from being re-suppressed

* fix: share bundle liveness and keep package list read-only
2026-10-02 00:48:27 -04:00
Julian Chang
7402fb6e78
exclude opted out bundles from package list (#879)
* exclude opted out bundles from package list

* count inferred bundle installs as opted in

* let opted-in bundles own shared rows
2026-10-02 00:07:44 -04:00
github-actions[bot]
e74eb60ba2 chore: bump to 2.6.0 2026-10-01 22:04:41 +00:00
LynithDev
6f415d65e0
fix: graphics drivers issues on linux in appimage (#903) 2026-10-02 00:03:50 +02:00
LynithDev
7bc9064469
fix: libxdo crash in deb packages (#902) 2026-10-01 22:45:37 +02:00
LynithDev
30f0e1843e
chore: add JVM argument to improve vector operations on Java 21+ (#901) 2026-10-01 22:42:25 +02:00
Maciek
959458abde
chore: added enabling disabled bundled mods from browser (#900) 2026-10-01 22:12:53 +02:00
Maciek
76d929b82a
feat: added warning on clicking cosmetics button (#899) 2026-10-01 11:41:55 -04:00
Julian Chang
efbde53326
ui: improve migration ui 2026-10-01 03:46:10 -04:00
Julian Chang
190723a4f8
feat: optionally copy configs into dedicated-folder targets on migration 2026-10-01 03:46:09 -04:00
Julian Chang
0e98be3d5a
feat: manual migration in both directions, only when there's something to copy 2026-10-01 03:45:55 -04:00
Julian Chang
0b78f16e9a
improve more design elements 2026-10-01 02:01:30 -04:00
Julian Chang
b86080ac62
improve design in some areas 2026-10-01 01:35:20 -04:00
Julian Chang
67cfc4eb83
fix: key cached version info by loader format version
Bump interfrost to 1.5.0 and pass the cluster's loader to download_version_info so a format bump doesn't reuse stale version JSON.
2026-10-01 01:34:40 -04:00
Julian Chang
fc3046d60e
feat: list/grid layout toggle on clusters page 2026-10-01 00:52:30 -04:00
Julian Chang
03c00b4199
feat: group user-created OneClient clusters by release line 2026-10-01 00:52:30 -04:00
Julian Chang
51608f7d94
feat: don't re-provision clusters the user deleted 2026-10-01 00:52:30 -04:00
Julian Chang
13e52ab3ec
feat: use gallery images for package card banners 2026-10-01 00:52:30 -04:00
Julian Chang
cd122e877e
fix: create config dir in file-lock override test 2026-10-01 00:52:30 -04:00
Julian Chang
b2a84490ed
split 1.21.x into minor drops 2026-09-30 21:12:02 -04:00
Julian Chang
4995276c51
pre-warm cluster art 2026-09-30 21:01:26 -04:00
LynithDev
50bf2c07ee fix: use correct OneClient logo 2026-09-30 22:38:13 +02:00
LynithDev
3ef452d451
fix: swapped home screen cluster labels to show the cluster name in big text (#898) 2026-09-30 22:02:33 +02:00
LynithDev
65a9c670cc
feat: package selection (#897) 2026-09-30 21:45:46 +02:00
LynithDev
0512727c22
fix: clear all notifications shouldn't be clearing progress notis (#896) 2026-09-30 21:20:07 +02:00
LynithDev
9b6f872a8d
fix: libappindicator crashing and bundling on linux (#893) 2026-09-30 20:44:10 +02:00
Maciek
06fa7739b3
feat: working modpacks from modrinth and curseforge as well as manually imported; couple ui fixes (#894) 2026-09-30 20:44:04 +02:00
LynithDev
d630c27a88
chore: forcefully migrate old launcher path to new path (#895) 2026-09-30 20:43:59 +02:00
Julian Chang
e1be4c8bcc
wipe off incompatible mc version check entirely 2026-09-30 06:12:11 -04:00
LynithDev
c9c4c9536b
feat: better versions UI (#890) 2026-09-29 20:58:57 +02:00
LynithDev
b2a62f8e17
feat: list required dependencies in the browser versions page (#889) 2026-09-29 20:39:37 +02:00
LynithDev
ef5847f417
feat: improved stats screen (#888) 2026-09-29 20:31:42 +02:00
Maciek
55125b551f
fix: fixed worlds and datapacks tabs jumping in (#887) 2026-09-29 20:27:58 +02:00
Maciek
0ab544f265
feat: custom cluster instances (#859)
* feat: added adding new instances of a cluster with 'start from scratch' option enabling user to create a vanilla/modded cluster; fixed couple pre-existing bugs that wouldn't let other loaders boot up

* feat: fully implemented cluster creation modal design

* chore: refactoring

* feat: expanded the tags section on customization step; found that fabric doesnt work with 1.8.9

* feat: added gallery covers handling for new cluster instances

* feat: added 'redirecting' from fabric to orinthe on versions pre-1.14

* feat: added java-version-override

* feat: added loader types migrations (right now they dont migrate mods because there are no bundles)

* chore: wider loader dropdown for multiloader cluster (if it will be used in the future)

* chore: fixed more issues with isolated and shared dir, with migrations and instances naming

* chore: refactor; taken the loader version dropdown from version picker view back to loader selector

* chore: deleted Quilt from UI

---------

Co-authored-by: LynithDev <61880709+LynithDev@users.noreply.github.com>
2026-09-29 19:08:21 +02:00
LynithDev
bdb9780794
fix: move (almost) all IO off the UI thread (#886) 2026-09-29 19:03:25 +02:00
LynithDev
1574d1afb1 fix: open crate on windows not opening urls properly 2026-09-29 15:34:14 +02:00
LynithDev
95eba5503f chore: update dependencies and optimise archive operations 2026-09-29 15:04:51 +02:00
LynithDev
00237d5281 chore: update open crate to fix issue on windows 2026-09-29 13:59:01 +02:00
Maciek
495dbf05dc
feat: polyplus mark and removal warning (#877)
* feat: polyplus mark and removal warning (some issues with merging)

* chore: deleted the blue background from the polyplus mod

* feat: added warning for removing bundle mods; fixed the new/updated badges on mods cards
2026-09-29 12:38:05 +02:00
Maciek
45da20fac2
feat: release migration (#851)
* feat: added migration mechanism for versions 26.3 and above

* feat: added checking for mods that the migrating target already has

* feat: added scheduled migrations for up to 30 days

* feat: added button in cluster settings for manual migratting; added migratting disabled mods with status saving

* feat: added checking for show_initial_migration flag in metadata.json

* feat: added fabric -> orinthe migration (for 1.8.9)

* chore: force open from debug page

* chore: change some UI elements

* fix: don't migrate packages whose dependency failed to install

* fix: keep checking the release migration waitlist past a broken cluster

* fix: make Migrate on a debug fake plan a no-op

* perf: install the version the migration plan already fetched

* chore: fix the clippy warnings the release migration added

* refactor: open every release migration prompt through one helper

* chore(lint): cargo fmt the release migration code

* fix: check the release migration waitlist queries at compile time

---------

Co-authored-by: LynithDev <61880709+LynithDev@users.noreply.github.com>
2026-09-28 21:06:30 +02:00
Maciek
abb41dcc52
feat: worlds and datapacks views (#870)
* feat: added worlds and datapacks tab

* feat: added datapacks in browser; fixed datapacks view loading too long

* feat: added icons to datapacks

* fix: scope world query invalidation to the world that changed

* fix: keep the selected data pack world in one place and guard deletes

* chore: share one dialog shell between the world prompt and delete confirm

* fix: stop watching every subfolder of saves

---------

Co-authored-by: LynithDev <61880709+LynithDev@users.noreply.github.com>
2026-09-28 21:05:52 +02:00
LynithDev
48a25f0c2c
fix: rotate the advanced section chevron when it opens (#885) 2026-09-28 20:53:41 +02:00
Maciek
7f1a3176cf
feat: added sections for advanced mods (#876)
* feat: added sections for advanced mods

* fix: treat a null bundle file type as normal

* chore: drop ScrollArea::lazy_grid

* chore: pass one header height to lazy_sections

* chore: name BundleFileType::Normal in test fixtures

* feat: don't offer advanced bundle mods as optional mods

---------

Co-authored-by: LynithDev <61880709+LynithDev@users.noreply.github.com>
2026-09-28 20:31:18 +02:00
Maciek
f94ca969b0
feat: bad mods flagging (#882)
* feat: first version of flagging mods thru datastorage

* feat: added fallbacks, version-specific flag and project-specific flag

* chore: downsized the amount of requests sent when looking up for alternatives

* feat: added explanations for flagging mods

* fix: prompt for flagged dependencies instead of silently skipping them

* chore: size the flagged popup with max_height instead of measuring it

* chore: drop is_flagged and the explanation size cap

* feat: refresh the bad mods list with the pre-launch checks

---------

Co-authored-by: LynithDev <61880709+LynithDev@users.noreply.github.com>
2026-09-28 17:40:00 +02:00
Maciek
695406145f
chore: deleted the download process from onboarding (#865)
* chore: deleted the download process from onboarding

* fix: gate onboarding Finish on loaded bundles and correct failure copy

* chore: drop unused install_cluster_bundles re-export from oneclient_core

* chore: drop the unread predownload flag from the versions manifest

---------

Co-authored-by: LynithDev <61880709+LynithDev@users.noreply.github.com>
2026-09-28 17:33:01 +02:00
Maciek
b3b436bc3e
feat: added support for github hosted bundle mods (#883)
* feat: added support for github hosted bundle mods

* fix: keep external bundle mods when their manifest entry gains an id

* fix: match a pending external bundle update to its installed jar

* chore: parse the bundle file host with url instead of by hand

* fix: namespace external bundle file ids under ext:

* chore: rename the Browser tab to Online and list unbundled GitHub mods there

---------

Co-authored-by: LynithDev <61880709+LynithDev@users.noreply.github.com>
2026-09-28 17:30:50 +02:00
Maciek
abc452f294
feat: added deleting browser and local mods (#853)
* feat: added deleting browser and local mods by context menu and on list view by trashcan icon

* fix: only confirm shared deletes that reach other clusters

* fix: confirm shared removals from the browser package page too

* fix: keep "View in folder" alive after the context menu closes

---------

Co-authored-by: LynithDev <61880709+LynithDev@users.noreply.github.com>
2026-09-28 17:30:10 +02:00
Maciek
ac66752b99
feat: added reseting back to the default options in settings tabs (#818)
* feat: added reseting back to the default options in settings tabs

* chore: removed the reset button for whole page and changed it to a reset button for every setting row

* chore: removed old reset options from search index

* chore: remove unused use_game_active and GameState::any_active

* fix: reset global memory to the Default option instead of a fixed size

* feat: add reset buttons to the launcher tab's system settings

* chore: fold with_reset into resettable_all and drop unneeded dispatch clones

* chore: rustfmt the import lists touched by the merge

* fix: treat a missing discrete GPU setting as on

* fix: read the discrete GPU reset default with the same true fallback

---------

Co-authored-by: LynithDev <61880709+LynithDev@users.noreply.github.com>
2026-09-28 17:29:05 +02:00
Maciek
35dcd584d0
chore: changed the UX for optional mods modal (#878)
* chore: changed the UX for optional mods modal

* refactor: fold cancel_optional_mods into close_optional_mods

* fix: forget optional mod picks once the modal closes

* chore: rustfmt the optional mods import

* chore: say what the optional mods buttons actually do

---------

Co-authored-by: LynithDev <61880709+LynithDev@users.noreply.github.com>
2026-09-28 17:24:10 +02:00
LynithDev
8e66212596
fix: treat a missing discrete GPU setting as on (#884) 2026-09-28 17:22:13 +02:00
Luna
7ca7ac04cf
fix: preserve executable bits when extracting Java runtimes (#873)
Co-authored-by: LynithDev <61880709+LynithDev@users.noreply.github.com>
2026-09-28 17:15:41 +02:00
Julian Chang
d632c47320
remove unnecessary tests (#881) 2026-09-28 17:13:41 +02:00
Luna
5f7b624d82
fix: it's -> its for possessive (#872) 2026-09-28 17:13:27 +02:00
Julian Chang
e675b99300
fix corrupted file detection 2026-09-24 23:24:36 -04:00
LynithDev
520e6c1ef1
chore: redesigned browser grid (#869) 2026-09-23 23:49:57 +02:00
LynithDev
3a5f06e8ac
chore: redesigned package grid (#867) 2026-09-23 13:39:11 +02:00
Maciek
36a925da06
fix: homepage cover images are now in much better quality without bigger memory use (#862)
* fix: fixed background covers rendering poorly on bigger resolutions

* fix: fixed onboarding cached images stacking from ~80MB to again ~20MB (like it was before)
2026-09-23 13:31:29 +02:00
LynithDev
2f7476ba6e
chore: redesigned the navbar links to be pills (#866) 2026-09-23 13:27:39 +02:00
a83bda9b53 chore(release): bump version to 2.5.1-c3
Some checks failed
Release Multiplatform Binaries / build (deb,appimage, linux-x86_64, ubuntu-22.04, x86_64-unknown-linux-gnu) (push) Waiting to run
Release Multiplatform Binaries / build (dmg,app, macos-arm64, macos-14, aarch64-apple-darwin) (push) Waiting to run
Release Multiplatform Binaries / build (dmg,app, macos-x86_64, macos-14, x86_64-apple-darwin) (push) Waiting to run
Release Multiplatform Binaries / build (nsis, windows-x86_64, windows-latest, x86_64-pc-windows-msvc) (push) Waiting to run
Release Multiplatform Binaries / create-release (push) Blocked by required conditions
Publish AUR Packages / resolve (release) Waiting to run
Publish AUR Packages / publish-source (release) Blocked by required conditions
Publish AUR Packages / publish-bin (release) Blocked by required conditions
Publish COPR Package / resolve (release) Failing after 0s
Publish COPR Package / publish-bin (release) Has been skipped
2026-09-23 07:12:02 +05:30
70e34d1b66 fix(launch): gate 1.21.11 offlineskins mod to matching minecraft version 2026-09-23 07:08:52 +05:30
7bb797eb16 feat(skins): integrate client-side offline skin mod and isolate player textures 2026-09-23 06:37:28 +05:30
017c9a85f9 fix(skins): fix authlib-injector routing, add RSA profile signing, and populate userProperties 2026-09-22 22:20:13 +05:30
LynithDev
80e7d4d4f8
chore: update freya and enable debug overlay in release (#856)
* chore: bump freya to 0.5.0-rc.6

* feat: add freya debug overlay plugin

* chore: bump to rc.7
2026-09-22 16:13:05 +02:00
700b11a3ce feat(skins): integrate proper isolated offline skin system via Authlib-Injector and eliminate multiplayer skin cloning (2.5.1-c2)
Some checks are pending
Release Multiplatform Binaries / build (deb,appimage, linux-x86_64, ubuntu-22.04, x86_64-unknown-linux-gnu) (push) Waiting to run
Release Multiplatform Binaries / build (dmg,app, macos-arm64, macos-14, aarch64-apple-darwin) (push) Waiting to run
Release Multiplatform Binaries / build (dmg,app, macos-x86_64, macos-14, x86_64-apple-darwin) (push) Waiting to run
Release Multiplatform Binaries / build (nsis, windows-x86_64, windows-latest, x86_64-pc-windows-msvc) (push) Waiting to run
Release Multiplatform Binaries / create-release (push) Blocked by required conditions
2026-09-21 19:49:57 +05:30
Julian Chang
ac52d55cf2
rename Fabric (Ornithe) -> Fabric 2026-09-19 14:09:38 -04:00
LynithDev
850c695f91
feat: system tray and process state controls (#687) 2026-09-19 01:19:34 +02:00
LynithDev
a8cc4e8b7f
feat: better text inputs (#854) 2026-09-19 01:18:49 +02:00
sakuyma
45a970c38a
Small QoL fixes: Java refresh, clipboard toasts, custom image endpoints (#852)
* fix(java): add refresh button for stale runtimes

* fix(notifications): keep clipboard toasts out of the notification center

* fix(images): allow custom endpoints on non-public hosts

* chore: make toast_only notifications not increment notification counter

* chore: reorder navbar links

---------

Co-authored-by: LynithDev <61880709+LynithDev@users.noreply.github.com>
2026-09-18 23:43:49 +02:00
LynithDev
daaa209b30 chore(lint): cargo fmt 2026-09-18 23:05:50 +02:00
991a85c589 feat: update base to official upstream 2.5.1 with all cracked features intact (2.5.1-c1)
Some checks are pending
Release Multiplatform Binaries / build (deb,appimage, linux-x86_64, ubuntu-22.04, x86_64-unknown-linux-gnu) (push) Waiting to run
Release Multiplatform Binaries / build (dmg,app, macos-arm64, macos-14, aarch64-apple-darwin) (push) Waiting to run
Release Multiplatform Binaries / build (dmg,app, macos-x86_64, macos-14, x86_64-apple-darwin) (push) Waiting to run
Release Multiplatform Binaries / build (nsis, windows-x86_64, windows-latest, x86_64-pc-windows-msvc) (push) Waiting to run
Release Multiplatform Binaries / create-release (push) Blocked by required conditions
2026-09-18 07:16:25 +05:30
LynithDev
e0947848f9 fix: oneclient aur source publishes failing 2026-09-18 01:10:22 +02:00
github-actions[bot]
dad96c8d40 chore: bump to 2.5.1 2026-09-17 22:38:18 +00:00
LynithDev
c7f3fdc52b
chore: update bundled packages only after cluster launch (#849) 2026-09-18 00:09:53 +02:00
LynithDev
43108bc8bc fix: update stale package metadata if it's supported versions were updated 2026-09-17 23:54:24 +02:00
Maciek
e25c3e8243
feat: better error handling for process hooks (#847) 2026-09-17 23:23:46 +02:00
Luna
9dccb42fd1
chore: optimize assets with oxipng (#846) 2026-09-17 23:18:00 +02:00
8093cafd53 docs: restore original custom README description and false positive notice 2026-09-17 10:39:13 +05:30
d214afe99b feat: update base to official upstream 2.5.0 with all cracked features intact (2.5.0-c1)
Some checks are pending
Release Multiplatform Binaries / build (deb,appimage, linux-x86_64, ubuntu-22.04, x86_64-unknown-linux-gnu) (push) Waiting to run
Release Multiplatform Binaries / build (dmg,app, macos-arm64, macos-14, aarch64-apple-darwin) (push) Waiting to run
Release Multiplatform Binaries / build (dmg,app, macos-x86_64, macos-14, x86_64-apple-darwin) (push) Waiting to run
Release Multiplatform Binaries / build (nsis, windows-x86_64, windows-latest, x86_64-pc-windows-msvc) (push) Waiting to run
Release Multiplatform Binaries / create-release (push) Blocked by required conditions
- Rebased base onto official Polyfrost OneLauncher 2.5.0 (release v2.5.0 + latest bundle fixes).
- Integrated new Upstream Control Center quick settings & system info overlay and navbar avatar-with-gear trigger.
- Preserved top navbar 'Skins' direct route navigation.
- Maintained guaranteed offline mode launch without requiring any Microsoft accounts.
- Maintained custom 3D offline skin changer, player model switch (Classic/Slim), and targeted Minecraft Java UUID character slot isolation.
- Maintained CustomSkinLoader auto-configuration for local skins, Ely.by, and SkinRestorer.
- Integrated direct GitHub Releases auto-updater pointing to oiupoyt/OneLauncher-Cracked.
- Preserved inline username renaming modal and Change Skin navigation in account settings and onboarding.
- Maintained mods filter popover blur/text ghosting fix in package manager.
- Preserved fast dev profile and multiplatform release workflow for v2.5.0-c1.
- All workspace tests and clippy checks pass with 0 errors.
2026-09-17 07:15:57 +05:30
Julian Chang
41ce177483
Fix version-tag matching and bundle filtering 2026-09-16 18:02:21 -04:00
Julian Chang
09509b1b3d
ignore mc version check for tracked bundle mods 2026-09-16 17:01:50 -04:00
github-actions[bot]
ec055f3043 chore: bump to 2.5.0 2026-09-16 18:32:52 +00:00
LynithDev
a2859e6681 chore: add flag to AUR PKGBUILD to prevent rustc from getting OOM killed 2026-09-16 20:32:19 +02:00
Maciek
a00e11d204
feat: control center and smaller navbar link gaps (#832)
* feat: added a control center

* feat: added no account signed in handling; added a modal on sign out clicked

* feat: use the discord icon for the discord rpc quick setting

* style: fade out disabled quick setting tiles

* style: drop the 'Active' suffix from the control center account subtitle

* feat: added a gear indicator to the navbar avatar button

* add small gear icon on avatar

---------

Co-authored-by: LynithDev <61880709+LynithDev@users.noreply.github.com>
2026-09-16 20:29:14 +02:00
Maciek
b4f067483a
fix: fixed most of the sizing issues in the launcher (#822) 2026-09-16 18:58:10 +02:00
Maciek
d32d3e1782
feat: added tooltips (#837) 2026-09-16 18:34:50 +02:00
Maciek
0a6c9e0f74
fix: fixed scrollbar overlapping with the scrollview content (#817) 2026-09-16 18:30:55 +02:00
Maciek
9879eb2e72
fix: fixed versions metadata not healing when unable to parse or read (#835) 2026-09-16 18:30:20 +02:00
Julian Chang
c3a45371dd
fix: incorrect database and filesystem structure of mods after updates (#834)
Co-authored-by: LynithDev <61880709+LynithDev@users.noreply.github.com>
2026-09-16 18:24:09 +02:00
github-actions[bot]
ebb476029f chore: bump to 2.4.2 2026-09-14 20:15:28 +00:00
LynithDev
84bbf29882 fix: mod updates applying after launch and not before 2026-09-14 21:03:34 +02:00
LynithDev
6ceba7480f fix: mod updates not being symlinked to a clusters mods folder after update 2026-09-14 20:39:07 +02:00
github-actions[bot]
db764e63f9 chore: bump to 2.4.1 2026-09-14 14:47:44 +00:00
LynithDev
80ba104861
chore: improve stats page and cluster overview (#831)
* fix(stats): drop zero units from durations and cap the server pie at ten slices

* fix(stats): say out loud that "when you play" bars are lifetime totals

* feat(stats): paint every bar in the accent colour until one is hovered

* feat(stats): add a calendar range picker to daily playtime

* fix(stats): stop recovery inventing playtime and carry sessions over midnight

* feat(stats): more stats out of the data already recorded

* feat(stats): eight more personas, and let them see the server data

* fix(charts): stop axis labels being cut off to their own bar's width

* feat(stats): give the overview a headline stat instead of seven identical boxes

* feat(stats): turn personas into wrapping badges

* fix(stats): lay the overview out for the window it is actually in

* feat(stats): fold the monthly chart into one "Playtime by hour/day/month" card

* fix(stats): stop the total playtime card making the tile block so tall

* fix(stats): strip the persona badges back to plain tags
2026-09-14 16:45:02 +02:00
LynithDev
2e0ba533fe
fix: issues with mod updating and mods randomly being disabled (#830)
* fix: stop offering bundle-owned packages in the update modal

* fix: match a version's game versions exactly not by substring

* fix: resolve a mod's materialized root from the layout in use

* fix: stop parking unclaimed jars in the cluster mods folder

* fix: stop the duplicate reconciler filing a user-disabled override

* fix: repair the disables duplicate resolution left behind
2026-09-14 16:44:46 +02:00
Julian Chang
a8bdf997d9
Reapply "fix: enable Wayland clipboard for image copy + align MSRV docs (#826)"
This reverts commit b737e39d1b.
2026-09-13 22:42:52 -04:00
Julian Chang
b737e39d1b
Revert "fix: enable Wayland clipboard for image copy + align MSRV docs (#826)"
This reverts commit d2d486db06.
2026-09-13 21:12:38 -04:00
sakuyma
d2d486db06
fix: enable Wayland clipboard for image copy + align MSRV docs (#826)
* docs: align documented Rust MSRV to 1.97 everywhere

* fix: enable wayland clipboard support for image copies
2026-09-13 23:28:24 +02:00
Julian Chang
297abeac00
tweak wording on stuff 2026-09-13 00:34:47 -04:00
github-actions[bot]
3d311c6ee3 chore: bump to 2.4.0 2026-09-13 00:01:39 +00:00
LynithDev
5d8db4abf7 fix: symlinks 2026-09-13 01:59:50 +02:00
LynithDev
54980fa73a
fix: linux use dedicated GPU setting (#816)
* forgot to add this

* fix complex type warning in test

* fix(linux): ask the driver whether a card is discrete
2026-09-13 01:36:47 +02:00
LynithDev
cad4800835 fix: issues caused by merges 2026-09-13 01:24:28 +02:00
Maciek
576c93c96b
feat: added placeholder image for images that couldnt be decoded; fixed overflowing titles in mods view (#802)
fix: drop per-render image fallback logging and narrow its memoization cost

fix: keep the backdrop fallback, the cache is what fails
2026-09-13 00:54:10 +02:00
Maciek
ba74bedf92
feat: added microsoft jdk and new shared mods folder layout (#812)
* feat: added microsoft sdk provider

* feat: changed the default provider to microsoft everywhere

* feat: added checking for microsoft jdks and prompting user to install them (ui for discussion)

* feat: added toast popup when user tries to remove runtime that is pinned to some cluster

* feat: added 'migrating' user to the microsoft jdks

* feat: changed reading mods from cluster folders on launch to having cluster folders symlinks in the shared .minecraft folder where mods stay per given cluster; changed resource packs and shader packs from working per cluster to global, every resource pack and shader pack is now being shared between clusters; added mods disabling whenever a mod is manually removed from the mods folder

* fix/feat: fixed hardlinks counting as a duplicate of files it was pointing to (windows); added a progress bar to the disk storage view in settings

* chore: connected microsoft jdk branch and package folder management one

---------

Co-authored-by: LynithDev <61880709+LynithDev@users.noreply.github.com>
2026-09-13 00:37:40 +02:00
Maciek
a6c03c6f6d
feat: added resource packs and shaders sections inside browse view (#819) 2026-09-13 00:36:46 +02:00
Maciek
53b27a1c7b
chore: added checking for browser autoupdater by published_at (#823) 2026-09-13 00:34:37 +02:00
Julian Chang
44dfa32233
Add Ornithe support (#811)
* Add Ornithe support

* chore: fix the remaining startup issues reltaed to arguments and manifest caching

* fix: stop repairing on every launch for natives-only libraries

---------

Co-authored-by: LynithDev <61880709+LynithDev@users.noreply.github.com>
2026-09-13 00:11:21 +02:00
Julian Chang
fe8bb6bfcc
chore: only download bundle content in onboarding pre-download (#801) 2026-09-12 23:40:49 +02:00
Maciek
4932b61a72
fix: cluster settings not inheriting value from global memory field (#804) 2026-09-12 23:39:06 +02:00
Maciek
0785e58d07
chore: deleted corners on the app (#815) 2026-09-10 14:16:31 +02:00
LynithDev
2378f74e58 chore: add COPR publish workflow 2026-09-09 01:54:35 +02:00
Julian Chang
7b95412511
Update splash_curtain.rs 2026-09-07 23:34:46 -04:00
LynithDev
a70a8a4ffa perf: optimise used memory 2026-09-07 23:29:52 +02:00
github-actions[bot]
0e49c69176 chore: bump to 2.3.1 2026-09-06 23:02:28 +00:00
LynithDev
322c3d5420 chore: log freya info and switched to experimental branch with windows rendering fix 2026-09-07 01:00:58 +02:00
LynithDev
f55f816342 fix: long launches caused by anti-corruption mechanism 2026-09-07 01:00:58 +02:00
github-actions[bot]
068ea1beaa chore: bump to 2.3.0 2026-09-06 17:52:38 +00:00
Julian Chang
275abd13b7
automatically clear out any corrupted files (#708)
Removes non-empty all-NUL files from the game directory before content is materialized, so installations bricked by corrupted files recover on the next launch.
2026-09-06 01:47:32 +02:00
LynithDev
3d0c880096 feat: let new installs choose the data folder 2026-09-06 01:38:32 +02:00
LynithDev
776b5a8562 feat: better UX with folder data changing 2026-09-06 01:08:22 +02:00
LynithDev
7560d7e8b9 chore: log console should enable "debug" log filter by default 2026-09-06 00:34:44 +02:00
LynithDev
3f66e14db3 fix: declining TOS no longer skips onboarding 2026-09-06 00:17:15 +02:00
LynithDev
37001e7a7c chore: make decling and accepting TOS better 2026-09-06 00:14:11 +02:00
Julian Chang
bd8dc0424c fix onboarding being completely reprompted when TOS updates 2026-09-06 00:14:11 +02:00
LynithDev
6e2516e547 chore: ignore weird java awt natives loading 2026-09-06 00:14:11 +02:00
LynithDev
f8f6ef8f07 Revert "fix: stop rejecting java installations over an awt probe"
This reverts commit 22edf1e9dac3051a6d3641b07c5aad1030571e1c.
2026-09-06 00:14:11 +02:00
LynithDev
3dddbb433e fix: debug page resetting onboarding state 2026-09-06 00:14:11 +02:00
Julian Chang
7780a8900c perf: optimize mod toggling (#791) 2026-09-06 00:14:11 +02:00
Maciek
0217a54518 fix: verify and repair missing natives (#797) 2026-09-06 00:14:11 +02:00
Maciek
612cd9e2a8 fix: fixed hidden mods being enabled on every launch (#794) 2026-09-06 00:13:57 +02:00
LynithDev
ca43e56455 fix: allow both path forms in allowed_symlinks.txt 2026-09-06 00:13:57 +02:00
LynithDev
09073319b1 fix: stop rejecting java installations over an awt probe 2026-09-06 00:13:57 +02:00
LynithDev
84d1cf558c perf: extract archives off the caller's executor 2026-09-06 00:13:57 +02:00
Maciek
ea7b1bc922 fix: browser markdown (#768) 2026-09-06 00:13:57 +02:00
Maciek
b697efce1a feat: added an option for starting the launcher maximized (#750) 2026-09-06 00:13:57 +02:00
Maciek
6ef57bffe2 chore: sorted cluster settings (#775) 2026-09-06 00:13:57 +02:00
Maciek
acd2c1d183 feat: shortcuts (#773) 2026-09-06 00:13:57 +02:00
Maciek
86f3049564 feat: local mods support (#748) 2026-09-06 00:12:16 +02:00
Maciek
ba60ce4d32 feat: added checking for bundle updates on game launch (#778) 2026-09-06 00:12:16 +02:00
Maciek
2cc280bbb0 feat: added an opt-in flow for bundle mods shipped disabled (#737) 2026-09-06 00:12:16 +02:00
Maciek
90fb354857 feat: resource packs and shaders quiet reload (#752) 2026-09-06 00:12:16 +02:00
Julian Chang
72a9b63f24 feat: lower the default memory on lower memory machines (#783) 2026-09-06 00:12:16 +02:00
Julian Chang
86145c7180 feat: prefer the dedicated gpu on windows (#792) 2026-09-06 00:12:16 +02:00
Maciek
e40c0185c4 chore: added dedicated gpu flags (#725) 2026-09-06 00:12:16 +02:00
Maciek
2c43aa16ba feat: added a don't show again option to the update prompt (#723) 2026-09-06 00:12:16 +02:00
Maciek
4aaf974f04 feat: added New/Updated badges to cluster package lists (#734) 2026-09-06 00:12:16 +02:00
Maciek
159e1c7e96 feat: added context menu inside the screenshot viewer (#744) 2026-09-06 00:05:16 +02:00
Maciek
2a36bb8a88 feat: added context menu on homepage clusters (#743) 2026-09-06 00:05:16 +02:00
Maciek
f55a86a024 feat: added a picker for the amount of columns in a row (#784) 2026-09-06 00:05:16 +02:00
Maciek
12783fe59a feat: added MB4/MB5 and Esc navigation globally (#742) 2026-09-06 00:04:57 +02:00
Julian Chang
b2c2289853 fix: stop a stray console window opening on windows (#770) 2026-09-06 00:04:49 +02:00
Julian Chang
0251456b11
Merge branch 'main' of https://github.com/Polyfrost/OneLauncher 2026-09-03 02:22:26 -04:00
Julian Chang
a104c4979a
chore: drop duplicate crate-local sqlx cache 2026-09-02 21:27:39 -04:00
Maciek
fb33693f7d
feat: launcher data folder (#767)
* feat: added disk choice on fresh install

* feat: added protection for corrupted/deleted settings.json

* feat: added migration to another drive for already existing users; changed the default directories

* feat: relocation screen

---------

Co-authored-by: LynithDev <61880709+LynithDev@users.noreply.github.com>
2026-09-02 14:07:20 +02:00
Maciek
25651d0cbc
fix: stray java runtime tmps and version files (#724)
* feat: added removal of .tmp files on launcher start after 10 minutes of being untouched; added removal of java runtime files whenever user deletes java runtime inside java settings (before they were only being deleted from the database, not files)

* feat: added modal for removing java runtimes

---------

Co-authored-by: LynithDev <61880709+LynithDev@users.noreply.github.com>
2026-09-02 13:03:16 +02:00
Julian Chang
b25ff12ab5
feat: delete mods from dead bundles (#746) 2026-09-01 14:03:13 +02:00
Maciek
606a15905d
feat: added ability to decline TOS (#751)
* feat: added ability to decline TOS; added blockades for hosts while TOS is declined

* fix: allowed datastorage-v2, meta.polyfrost.org and status.polyfrost.org

* chore: changed typography

* chore: changed TOS decline warning to a modal
2026-09-01 11:31:47 +02:00
Maciek
815b78e227
chore: migrated to freya 0.5.0-rc.4 (#739) 2026-08-26 13:20:44 +02:00
Maciek
419c07b822
feat: added memory allocation presets to minecraft and cluster settings (#747) 2026-08-26 12:53:48 +02:00
Maciek
8ca2c30993
feat: implemented disabling animations (#762)
* feat: implemented disabling animations
* fix: home page and notification toasts animating weirdly

Co-authored-by: LynithDev <61880709+LynithDev@users.noreply.github.com>
2026-08-26 12:44:45 +02:00
Maciek
795d80870b
feat: added rerender when new screenshot is taken (#745) 2026-08-26 12:37:57 +02:00
Julian Chang
f2ecde8927
chore: try to auto update in case of startup crash (#758) 2026-08-25 15:53:13 +02:00
Manor Dev
b00dff4256
fix: make back button on settings page not count sidebar items (#730) 2026-08-25 15:44:08 +02:00
Maciek
f74ed38363
fix: scrollbar obscuring the file path (#741) 2026-08-25 15:20:46 +02:00
Maciek
67e1ca13a0
fix: text that was visible in the center on stats (#753) 2026-08-25 15:20:01 +02:00
Maciek
66359b413e
fix: fixed memory input changing to default on every app launch (#764) 2026-08-25 15:18:38 +02:00
Maciek
5cac70b026
feat: added progress bar on first launch screen; changed progress bar to reusable component (there were 4 duplicates) (#740) 2026-08-25 15:16:01 +02:00
Julian Chang
e659ee122a
completely wipe out hickory 2026-08-24 02:42:18 +07:00
github-actions[bot]
148e665250 chore: bump to 2.2.3 2026-08-22 17:40:31 +00:00
Maciek
caba8ac65f
feat: added cosmetic navbar link (#754) 2026-08-23 00:34:22 +07:00
Maciek
be87077e95
fix: fixed 404 response from curseforge (#749) 2026-08-23 00:31:45 +07:00
Maciek
3d804edf02
feat: added rerendering after every new java runtime addition (#721) 2026-08-23 00:31:18 +07:00
Maciek
f7b22b650a
Fix/refreshing account doesnt change username (#720)
Co-authored-by: LynithDev <61880709+LynithDev@users.noreply.github.com>
2026-08-23 00:30:55 +07:00
Julian Chang
b9f408079e
Decrease minimum RAM allocation in JVM 2026-08-23 00:27:14 +07:00
LynithDev
efed361417
feat: added funfacts changing on background change (#718)
Co-authored-by: rozwader <maciek.rozwadowski2006@gmail.com>
2026-08-19 16:05:42 +02:00
LynithDev
578ec187cd
feat: added search bar into Game Output live logs (#716)
Co-authored-by: rozwader <maciek.rozwadowski2006@gmail.com>
2026-08-19 15:27:13 +02:00
LynithDev
ff22c8ac3b fix(app): normalize trackpad scroll deltas on macOS (#694) 2026-08-19 15:07:15 +02:00
LynithDev
bd7124c873
chore: changed ping url from gstatic to 1.1.1.1 (#719)
Co-authored-by: rozwader <maciek.rozwadowski2006@gmail.com>
2026-08-19 14:14:03 +02:00
LynithDev
020f9a9fa6 fix: panic caused by min > max 2026-08-19 10:36:29 +02:00
LynithDev
0495a5d067
feat: changed windows stack memory from 1MB to 8MB (same as Linux and MacOS) (#717)
Co-authored-by: rozwader <maciek.rozwadowski2006@gmail.com>
2026-08-12 11:15:41 +02:00
Julian Chang
09ff4d1c4d
fix(misc): bring back accidentally removed comment 2026-08-12 13:41:51 +07:00
Julian Chang
33218af293
fix mod card toggle taking 20 years 2026-08-12 13:41:12 +07:00
github-actions[bot]
5f40171142 chore: bump to 2.2.2 2026-08-10 21:27:24 +00:00
LynithDev
0e5ddcc522 fix: increase z-index of label in home recents row 2026-08-10 18:10:37 +02:00
LynithDev
8c5812d652 fix: migration issues, cluster preparation and sentry logging 2026-08-10 18:04:10 +02:00
Julian Chang
d45153a41c
add one more check for awt 2026-08-09 16:23:28 +07:00
Julian Chang
2a711c1ef3
actually properly check for AWT 2026-08-09 16:18:31 +07:00
github-actions[bot]
26c5aa8c49 chore: bump to 2.2.1 2026-08-08 04:44:56 +00:00
Julian Chang
9603e76cea
maybe improve visual code bundling 2026-08-08 11:33:24 +07:00
Julian Chang
e2064a5784
package visual c++ directly in windows installer 2026-08-07 22:07:26 +07:00
Julian Chang
d54283d99c
fix liquid glass icon 2026-08-07 14:55:55 +07:00
github-actions[bot]
277cf4f63f chore: bump to 2.2.0 2026-08-06 19:30:51 +00:00
LynithDev
a6e0bf0ab7 fix(core): bundle overrides being problematic pt. 2 2026-08-06 20:17:28 +02:00
LynithDev
64e1c07a2e chore: update README.md 2026-08-06 19:44:29 +02:00
LynithDev
a8609f59ff chore(app): fixed macos icon properly this time 2026-08-06 19:15:29 +02:00
LynithDev
ef60855f16 fix(core): bundle overrides being problematic 2026-08-06 15:03:32 +02:00
LynithDev
93aa1b5900
feat(logs): live launcher log console in its own window (#697) 2026-08-06 13:25:18 +02:00
LynithDev
2f993a0c3b
fix(auth): actually cancel an in-flight Microsoft sign-in (#696) 2026-08-06 12:47:18 +02:00
LynithDev
e99437f1b4
feat(search): make cluster package search fuzzy (#686) 2026-08-06 12:41:36 +02:00
LynithDev
b60ce5af12
refactor(java): simpler java check (#698) 2026-08-06 12:14:53 +02:00
LynithDev
fdb3b9a0a3 fix(ui): player preview is properly rendered now 2026-08-06 11:44:41 +02:00
Julian Chang
a8531352a1
remove string dedup in jvm args 2026-08-06 16:42:37 +07:00
LynithDev
ef53badeff
chore(ui): accounts page part of the settings layout (#693)
* feat(settings): move accounts into the settings shell

* fix(settings): keep the sidebar still on accounts, enlarge the model
2026-08-06 10:51:07 +02:00
LynithDev
8511048293
feat(ui): hidden bundle packages filter (#692) 2026-08-06 10:41:10 +02:00
LynithDev
a838e1c96b feat(updater): allow disabling the updater through an environment variable 2026-08-05 16:40:22 +02:00
LynithDev
792d716707 fix(ui): make GPU cache limit 512mb instead of 32mb 2026-08-05 13:55:25 +02:00
Julian Chang
413931eb74
fix workflow 2026-08-05 05:09:38 +07:00
Julian Chang
76e7c2ca7e
2.1.5 2026-08-05 05:03:00 +07:00
LynithDev
885bd31f44 fix: downloading errors should be properly handled now 2026-08-04 21:43:37 +02:00
Jacob
e272c66696
fix: fun fact being loaded from the old data storage url (#668) 2026-08-04 19:34:37 +07:00
Julian Chang
413411e1bc
add a huge warning when people try to install skyblock mods 2026-08-04 16:52:02 +07:00
Julian Chang
3d0bd057dd
pin workflows to hash 2026-08-03 20:00:38 +07:00
Julian Chang
791a1a3cbb
revert back to G1GC as a default GC 2026-08-02 23:55:20 +07:00
LynithDev
63ca74f054
chore: update dependencies (#660) 2026-08-02 18:17:32 +02:00
LynithDev
4792c9975f chore(ci): speed up nightly builds 2026-08-01 23:31:03 +02:00
github-actions[bot]
455f69f894 chore: bump to 2.1.4 2026-08-01 21:24:13 +00:00
LynithDev
394146d552 feat: auto-repair as well as verify cluster button in cluster settings 2026-08-01 23:22:20 +02:00
LynithDev
4c6de0d2c4 feat(ui): browser package better multi-version management 2026-08-01 17:10:20 +02:00
LynithDev
fb90eb7bb8 feat(ui): browser grid tags and install button on package cards 2026-08-01 17:10:20 +02:00
LynithDev
6e25ea2bfd feat(ui): check browser package updates on game launch instead 2026-08-01 17:10:20 +02:00
LynithDev
5580093408 feat(ui): per-cluster update prompt for browser content 2026-08-01 17:10:20 +02:00
LynithDev
ddcc6296d9 feat(content): check browser-installed packages for newer versions 2026-08-01 17:10:20 +02:00
LynithDev
1bd0b68e91 feat(db): browser package update cache and per-profile update mode 2026-08-01 17:10:20 +02:00
LynithDev
207abaf64b fix(ui): hide bundle packages marked hidden from the package manager (except in "All" category) 2026-08-01 17:10:20 +02:00
LynithDev
e6515c21ed fix(ui): scope package manager search to the actively viewed category 2026-08-01 17:10:20 +02:00
LynithDev
2e7bc89dbc feat(ui): rename External tab to Browser and add toolbar browse action 2026-08-01 17:10:20 +02:00
LynithDev
d61f6e6655 fix(ui): disable the launch button on click, not until first message response 2026-08-01 17:10:20 +02:00
LynithDev
038dc94725 fix(core): disallow launching multiple instances so fast 2026-08-01 17:10:20 +02:00
LynithDev
44c153dd91 feat(ui): install packages straight from the listing 2026-08-01 17:10:20 +02:00
LynithDev
1947e2d534 feat(java): prefer JDKs, require java.awt, install kits only 2026-08-01 17:10:20 +02:00
LynithDev
038c3542ce chore(onboarding): better pre-download toggle description 2026-08-01 17:10:20 +02:00
github-actions[bot]
39b2f187c5 chore: bump to 2.1.3 2026-07-31 23:00:39 +00:00
LynithDev
5d0101b3aa fix(core): disable hickory dns on windows 2026-08-01 00:42:16 +02:00
LynithDev
b35f258043 chore: github action rust cache 2026-08-01 00:08:26 +02:00
LynithDev
7187e841b6 fix(core): attempt to fix requesting issues 2026-07-31 23:22:14 +02:00
LynithDev
3b436f0d87 feat: storage manager 2026-07-31 17:21:19 +02:00
Julian Chang
65869ca8c0
bump to 2.1.2 2026-07-31 05:35:44 +07:00
Julian Chang
3e864c1d0f
remove the performance flag toggle 2026-07-31 04:50:49 +07:00
Julian Chang
0735b21060
Merge branch 'main' of https://github.com/Polyfrost/OneLauncher 2026-07-31 04:29:08 +07:00
Julian Chang
fb4c16c027
add better default JVM args 2026-07-31 04:29:02 +07:00
LynithDev
8e82c1e26a feat(core): package downloading with dependencies actually downloads required dependencies now 2026-07-30 21:32:26 +02:00
LynithDev
7b300ca5a5 fix(ui): fix startup overlaying on bundle updates 2026-07-30 17:34:30 +02:00
Julian Chang
087365e877
Merge branch 'main' of https://github.com/Polyfrost/OneLauncher 2026-07-29 23:04:28 +09:00
Julian Chang
bf5efab270
sign oneclient binary too 2026-07-29 23:04:24 +09:00
LynithDev
b6044d9e15 chore: bump version to 2.1.1 2026-07-29 00:35:05 +02:00
LynithDev
e31d799e86 fix(ui): version art gets properly prefetched 2026-07-29 00:34:40 +02:00
LynithDev
4aaff39abc feat(ui): browser navbar tab + better installation feedback 2026-07-29 00:06:14 +02:00
LynithDev
34408056c2 fix(core): disabled packages don't enable themselves after updating 2026-07-28 23:04:44 +02:00
LynithDev
ae76595913 fix(ui): forgot to change these compile errors 2026-07-28 22:42:22 +02:00
LynithDev
57d50a95c4 fix: recent cards sizing issue 2026-07-28 22:15:23 +02:00
LynithDev
21dc169578 fix(ui): status bar close button, and the layer saturation behind it 2026-07-28 22:15:17 +02:00
LynithDev
6344d2e985 feat(ui): JVM arguments setting 2026-07-28 22:14:38 +02:00
LynithDev
5f5fa4141a feat(content): make mod toggles work while the game is running 2026-07-28 22:14:29 +02:00
LynithDev
e0c8994dd8 fix(ui): keep home recent cards sized correctly through a resize 2026-07-28 22:14:23 +02:00
LynithDev
18b95ae60a fix(ui): rotate the changelog chevron with the accordion 2026-07-28 22:14:15 +02:00
LynithDev
56b421b187 fix(content): stop removed bundle mods coming back 2026-07-28 22:13:58 +02:00
LynithDev
d9e4f99add refactor(ui): compact the auth error preview in the debug page 2026-07-28 22:13:46 +02:00
LynithDev
875c667313 fix(ui): render the connectivity status bar message properly 2026-07-28 22:13:36 +02:00
LynithDev
1fd587a840 fix(ui): keep progress toasts from covering the navbar 2026-07-28 22:13:32 +02:00
LynithDev
b9f341e579 chore: update to freya 0.4 (fixes browser freeze issue) 2026-07-28 21:26:17 +02:00
LynithDev
e689a43a87 refactor: split core into modular crates 2026-07-28 12:11:02 +02:00
Julian Chang
1962e9c0c4
theoretically support macOS Mojave? 2026-07-26 11:59:04 +09:00
LynithDev
6b584fb957 chore: bump version to 2.1.0 2026-07-25 22:03:27 +02:00
LynithDev
f3d526c0c4 feat(ui): toasts pause on hover + bunch of small general tweaks 2026-07-25 21:06:10 +02:00
LynithDev
430e8ea28f feat(ui): better looking bundle updates 2026-07-25 20:36:52 +02:00
LynithDev
34742efc78 chore(lint): fix clippy warnings 2026-07-25 20:21:27 +02:00
LynithDev
57408aa710 feat(ui): show all individual versions 2026-07-25 20:18:12 +02:00
LynithDev
76c4a03942 fix(core): shader configs are persisted across launches 2026-07-25 18:15:47 +02:00
LynithDev
b2672c8712 fix(core): better parallelisation for downloads and tweaked visual download elements 2026-07-25 17:47:46 +02:00
LynithDev
021976ee10 feat(ui): file/folder drag and drop is now global across the app and prompts the user regarding the import 2026-07-25 13:56:16 +02:00
Julian Chang
0a9b067231
Fix GH action 2026-07-24 10:51:06 +09:00
Julian Chang
14324bb4c8
fix sentry debug gh action 2026-07-24 10:13:54 +09:00
Julian Chang
f9cb754e9e
fix gh action (again) 2026-07-24 09:54:30 +09:00
Julian Chang
ed0dacca43
Fix linux compile errors 2026-07-24 09:31:23 +09:00
Julian Chang
b20b005283
fix rpm packaging 2026-07-24 09:23:09 +09:00
LynithDev
bd685784f8 chore: revert deduplicated builds for legacy auto updater 2026-07-23 19:36:17 +02:00
LynithDev
b3c8d4ab3a feat: add RPM builds, deduplicate .exe and .appimage and disable autoupdating on linux builds that aren't appimages 2026-07-23 19:30:08 +02:00
LynithDev
6fb841bc06 chore: better sentry error reporting regarding stacktraces and errors 2026-07-23 18:16:45 +02:00
LynithDev
07884b9894 fix: macOS window border 2026-07-23 13:30:20 +02:00
Julian Chang
21bc328026
Add README with OneClient and OneLauncher details 2026-07-23 10:37:40 +09:00
LynithDev
87cf48019e feat: Window corner radius is now dynamic on Windows and Linux, whereas on macOS native window attributes are used 2026-07-21 14:06:53 +02:00
LynithDev
fbf2dc29b5 feat: Added debouncing to the browser search input 2026-07-21 13:51:47 +02:00
LynithDev
5929e51920 feat: Make bundle updates prettier 2026-07-21 13:03:33 +02:00
Julian Chang
85d1e09cee
Add readable MS errors 2026-07-21 13:37:03 +09:00
Julian Chang
24d9bc3458
Merge branch 'main' of https://github.com/Polyfrost/OneLauncher 2026-07-21 06:25:00 +09:00
Julian Chang
cf3f7fc989
Fix release workflow 2026-07-21 06:24:54 +09:00
Julian Chang
f435df1046
Add SignPath info 2026-07-21 06:05:51 +09:00
Julian Chang
bf6ee14add
Merge branch 'main' of https://github.com/Polyfrost/OneLauncher 2026-07-21 06:03:30 +09:00
Julian Chang
e5a9e70083
bump version to 2.0.2 2026-07-21 06:03:29 +09:00
LynithDev
179f192ca4 fix: Missing icons on Windows and Linux 2026-07-20 22:35:43 +02:00
Julian Chang
fe1797be68
Update Cargo.toml 2026-07-20 20:35:41 +09:00
Julian Chang
120b9ca460
Maybe fixes #603
Pushing so I can have CI compile for me lol
2026-07-20 19:12:21 +09:00
Julian Chang
bbfc585251
Give actual cause if login fails 2026-07-20 07:32:18 +09:00
Julian Chang
9b74c051e7
Merge branch 'main' of https://github.com/Polyfrost/OneLauncher 2026-07-20 07:30:40 +09:00
Julian Chang
a4e1ad5138
Fix #604 2026-07-20 07:30:38 +09:00
LynithDev
964cfa61b3 feat(ui): Add SQL debug console in debug page 2026-07-19 17:37:04 +02:00
LynithDev
c881bf858d feat(ui): Missing Java Popup 2026-07-19 16:37:08 +02:00
LynithDev
a9481a5789 feat(core): Provision cluster creation in an attempt to not have race conditions 2026-07-19 13:28:27 +02:00
LynithDev
c338d09c60 feat(ui): Made the download notification more descriptive 2026-07-19 11:19:33 +02:00
LynithDev
48a2d67ae6 fix: Use higher stack size in debug builds 2026-07-19 10:37:30 +02:00
LynithDev
2ad94a192b feat: Use different launcher folder in dev builds 2026-07-19 10:34:08 +02:00
Julian Chang
e4c5d3fcd7
Dont use hickory on Windows 2026-07-19 11:46:48 +09:00
Julian Chang
eb7d6ec911
Add nightly builds 2026-07-19 10:30:48 +09:00
Julian Chang
54c9cf1ff6
Fix stale shortcuts when updating from 1.x to 2.x 2026-07-19 10:12:24 +09:00
Julian Chang
778be93a39
Add aur publish 2026-07-19 10:05:19 +09:00
Julian Chang
445ad49ec8
fix auto-updating for new oneclient macos 2026-07-19 08:27:07 +09:00
Julian Chang
668cad9ec4
bump to 2.0.1 2026-07-19 08:03:31 +09:00
Julian Chang
46efd39b6a
fix java locate errors killing whole java installation 2026-07-19 07:57:18 +09:00
Julian Chang
c95e57ef55
remove Legacy Fabric 2026-07-19 07:56:52 +09:00
Julian Chang
0317e3cd8b
Fix updates not being applied 2026-07-19 07:32:48 +09:00
Julian Chang
5b11415aae
Merge branch 'main' of https://github.com/Polyfrost/OneLauncher 2026-07-17 17:42:50 -04:00
Julian Chang
05cdb87a90
Update oneclient_release.yml 2026-07-17 17:42:49 -04:00
LynithDev
ccc34e0950 fix(core): Force plus websocekt to use http 1 2026-07-17 19:03:05 +02:00
LynithDev
5b24a81fe5 feat(core): Launcher migrations now take in effect cluster migrations 2026-07-17 18:23:43 +02:00
Julian Chang
2718105944
vanilla migration
#546
2026-07-16 16:28:21 -04:00
Julian Chang
7696d80fe8
revert lunar 2026-07-16 15:33:28 -04:00
LynithDev
a4753fb06b feat(ui): Hide changelog indicator in the settings sidebar if changelog was read 2026-07-16 20:25:34 +02:00
Julian Chang
7f8b62eb15
Reapply "feat: Added migration from Lunar Client"
This reverts commit 1243dbcd75.
2026-07-16 13:40:37 -04:00
Julian Chang
1243dbcd75
Revert "feat: Added migration from Lunar Client"
This reverts commit 08fe999691.
2026-07-16 13:31:58 -04:00
LynithDev
0cf43816bf feat(ui): Remove / disable some settings 2026-07-16 19:14:39 +02:00
LynithDev
c3c4b37483 feat(ui): Add the All category back to package manager views 2026-07-16 19:03:24 +02:00
LynithDev
98c2da4253 feat(core): Add predownload key to versions metadata and restrict onboarding downloads 2026-07-16 18:45:32 +02:00
LynithDev
6eb16028d6 feat(core): Add Curseforge API token to every curseforge request 2026-07-16 18:40:41 +02:00
LynithDev
8dca2550d1 feat(core): Process recovery (session exit times and live log after restarting launcher) 2026-07-16 17:48:16 +02:00
LynithDev
ddda1d66ba feat(ui): Navbar entry animation + cards only animate on home page entry 2026-07-16 14:38:34 +02:00
LynithDev
08fe999691 feat: Added migration from Lunar Client 2026-07-16 14:24:52 +02:00
LynithDev
0a51018954 feat(ui): Stats page tweaks (pie charts and consistent spacing) 2026-07-16 11:01:51 +02:00
Julian Chang
d593ecb09f
26.1.2 support 2026-07-16 01:49:33 -04:00
Julian Chang
6c7d0a1ca8
compile-time 2026-07-15 16:37:59 -04:00
Julian Chang
668737fc6c
Rewrite bundles page
Fixes #547, #576
2026-07-15 16:24:52 -04:00
Julian Chang
29d1f1c9d2
Bugs
Fixed #492
Fixed #510
Fixed #506
Fixed #568
Fixed #582
Fixed #578
Fixed #567
Fixed #588
Fixed #585
Fixed #571
Fixed #587
Fixed #584
2026-07-15 13:41:43 -04:00
Julian Chang
68337a79dc
Merge branch 'main' of https://github.com/Polyfrost/OneLauncher 2026-07-15 02:12:03 -04:00
Julian Chang
d4fc3a5cb6
support voice chat and stuff 2026-07-15 02:12:02 -04:00
Julian Chang
442feba6f2
Update onboarding message 2 2026-07-14 18:19:57 -04:00
Julian Chang
c98107f722
Update onboarding message 2026-07-14 18:19:39 -04:00
Julian Chang
e5cd435ecf
Merge branch 'main' of https://github.com/Polyfrost/OneLauncher 2026-07-14 16:42:17 -04:00
Julian Chang
669af41e1c
Ig bro 2026-07-14 16:42:15 -04:00
LynithDev
ae5fc08892 fix(core): Use junction crate on windows for symlinks 2026-07-14 19:48:16 +02:00
LynithDev
79497c0cc6 fix(core): Recursion limit reached because of tracing::instrument 2026-07-14 19:15:04 +02:00
LynithDev
983702ed81 fix(auth): Made auth load faster and refresh expired sessions on launch 2026-07-14 19:03:02 +02:00
LynithDev
c43e888ebd fix(ui): Fix cluster long description wrap missing gap + gallery images looking better 2026-07-14 18:23:23 +02:00
LynithDev
04c944ec30 chore: Add a bunch of logging 2026-07-14 18:06:23 +02:00
LynithDev
b7e5b0b75b feat(ui): Let the user pick multiple packages at once 2026-07-13 18:47:21 +02:00
LynithDev
cdd4504b84 chore: Switch to open crate rather than in-house solution 2026-07-13 18:41:55 +02:00
LynithDev
8cd617a78b feat(ui): very basic bundle overrides updating 2026-07-13 18:01:41 +02:00
LynithDev
44897b3e7f feat(ui): better downloading information 2026-07-13 16:41:33 +02:00
LynithDev
557b0e4b7b feat(ui): Make the launcher navigatable with the keyboard 2026-07-13 16:10:21 +02:00
LynithDev
21313b2093 feat(ui): Placeholder UI for bundle update notification + modal 2026-07-12 23:25:49 +02:00
LynithDev
d0c97b4177 feat(ui): Merge Microsoft Auth types into 1 popup
fix(ui): Microsoft Auth notification is no longer persistent on cancel
2026-07-12 19:17:38 +02:00
LynithDev
eeaecd3de4 feat(ui): Account Popup 2026-07-12 18:00:40 +02:00
LynithDev
1c8db8ceff feat: Java 25+ is now downloadable
fix(ui): Scrollviews with no scrollable areas capturing scroll wheel events
2026-07-12 17:38:36 +02:00
LynithDev
68c8762b08 fix(ui): Home page recent cluster cards scale and position properly 2026-07-12 16:20:46 +02:00
Julian Chang
5ef719dec7
Closes #517 2026-07-11 17:52:03 -04:00
Julian Chang
5b9d31195f
improve macOS install window
Closes #514
Closes #515
2026-07-11 17:38:46 -04:00
LynithDev
f32fbf3120 fix(core): Fix symlinks and logging 2026-07-11 21:01:55 +02:00
LynithDev
4ac89a44f2 fix(ui): Onboarding navbar window titlebar buttons finally work 2026-07-10 21:24:30 +02:00
LynithDev
fcc519d25e chore: Fix clippy warnings 2026-07-10 20:55:39 +02:00
LynithDev
f2bc2d888f feat: Onboarding Migration page 2026-07-10 20:53:24 +02:00
LynithDev
7def9a9c8c fix: Blur 2026-07-09 20:53:52 +02:00
LynithDev
784249005e feat: Alex + Slim skins support in avatar and player model renderer 2026-07-08 17:57:05 +02:00
LynithDev
2a2d992649 feat: More compact cluster package manager view + package manager filter popover 2026-07-08 17:19:37 +02:00
LynithDev
0af7c7455b some tweaks 2026-07-06 20:10:14 +02:00
Julian Chang
7416a3407f
enable Win32_UI_WindowsAndMessaging for ShellExecuteW 2026-07-06 10:11:32 -04:00
Julian Chang
199c679238
ig we can try again 2026-07-06 09:46:46 -04:00
Julian Chang
22ec1278d3
fix macOS signing (hopefully) 2026-07-06 09:40:29 -04:00
Julian Chang
21c01e07d3
fix MS auth not working on Windows 2026-07-06 09:23:30 -04:00
Julian Chang
f2f2368eff
Update oneclient_release.yml 2026-07-06 05:55:36 -04:00
Julian Chang
0f04462863
Folder button
Automatically add stuff added to the mods folder to the next-ran cluster if in a shared directory
Add a txt explanation as to why certain folders are empty in shared directory
2026-07-06 05:02:47 -04:00
Julian Chang
a645388a40
Shared .minecraft folder 2026-07-06 04:39:50 -04:00
Julian Chang
700b45dd6b
Fix some mods not downloading 2026-07-06 04:12:02 -04:00
Julian Chang
41801010a4
use authorize oauth2 instead of devicecode oauth2 2026-07-06 02:40:33 -04:00
Julian Chang
c27688c8d4
adds tips and parallax background to onboarding
update client id for MSA + fix account already logged in issues
2026-07-06 02:05:15 -04:00
Julian Chang
2bbecb6e86
Try to fix linux build 2026-07-06 01:58:42 -04:00
Julian Chang
c358dfd95a
Update Cargo.toml 2026-07-06 01:18:46 -04:00
Julian Chang
9cb5db15f0
Attempt installer/updating 2026-07-06 01:00:56 -04:00
Julian Chang
99d095088c
update README/CONTRIBUTING.md 2026-07-05 16:51:07 -04:00
LynithDev
9ab0a0ecdd
Freya rewrite
This is v2 of OneClient. Removed all web-related stuff.

Co-Authored-By: Julian Chang <wyvest@polyfrost.org>
2026-07-05 16:37:05 -04:00
Julian Chang
a10378e9d7
bruh 2026-07-02 11:01:04 -04:00
Julian Chang
a11949ebdf
Update oneclient.artifact-configuration.xml 2026-07-02 10:37:56 -04:00
Julian Chang
c8bd0ffd5e
Change organization ID to use secrets 2026-07-02 10:15:23 -04:00
Julian Chang
a06b21d6eb
1.1.7 2026-07-02 09:31:38 -04:00
Daksh Jain
467164a945
fix macOS (#509) 2026-06-30 18:31:07 -04:00
Julian Chang
e0a645f701
Bump to 1.1.6
Some checks are pending
Build and Lint / ES Checks (push) Waiting to run
Build and Lint / Rust Checks (push) Waiting to run
2026-06-29 01:17:10 -04:00
Julian Chang
d6dc358e35
track playtime 2026-06-29 01:14:10 -04:00
Julian Chang
7d1460f885
bump ver to 1.1.5
Some checks are pending
Build and Lint / ES Checks (push) Waiting to run
Build and Lint / Rust Checks (push) Waiting to run
2026-06-22 07:43:19 +07:00
Julian Chang
723d1e43ae
Fix overrides and updates n stuff 2026-06-22 07:17:23 +07:00
Julian Chang
1635ca38ce
poly+ backend 2026-06-20 05:39:16 +07:00
Wyvest
a822f8d098
Update format.yml to simplify triggers
Removed pull request trigger and workflow_dispatch from format.yml
2026-04-18 01:16:26 -04:00
Wyvest
442245ffca
bump to 1.1.4 2026-04-01 04:16:31 -04:00
Wyvest
c6f347f98c
Fix stale version fetching 2026-04-01 04:16:13 -04:00
Wyvest
ad4c0bd361
Bump to 1.1.3 2026-04-01 03:10:24 -04:00
Wyvest
c5f43a83cf
Remove all automatic dependency downloading 2026-04-01 03:10:05 -04:00
Wyvest
3e9e7b949d
Bump to 1.1.2 2026-04-01 01:10:51 -04:00
Wyvest
ceb6fcd6a9
Fix library downloading 2026-04-01 01:10:32 -04:00
Wyvest
e479813c07
Fix onboarding not being accounted for during migration check 2026-04-01 01:10:12 -04:00
Wyvest
ee177708c1
Update interpulse/interflow 2026-03-31 21:13:18 -04:00
Wyvest
f4cce7c43a
fix onboarding clusters not being accounted in migration stuff 2026-03-31 18:41:06 -04:00
Wyvest
54d259a489
Bump to 1.1.1 2026-03-31 18:31:26 -04:00
Wyvest
2847c44a81
fix rare crash 2026-03-31 18:30:39 -04:00
Wyvest
93fef599ae
add fabric loader updating 2026-03-31 00:41:24 -04:00
Wyvest
0ab1043082
Squashed commit of the following:
commit f1c274101561d8e3ebd24b7779ba989f2029549f
Merge: 49f304e 3b885cd
Author: Wyvest <wyvestbusiness@gmail.com>
Date:   Mon Mar 30 05:51:40 2026 -0400

    Merge branch 'main' into pr/500

commit 49f304e04178ae50c2e8c43b5401ee440b4125d7
Author: belle <sillysys@proton.me>
Date:   Sun Mar 29 20:40:10 2026 +0000

    Reword some descriptions from previous commit

commit e1949dd3b45b5facb6c9b5dad2a463a7e41b4d20
Author: belle <sillysys@proton.me>
Date:   Sun Mar 29 20:35:43 2026 +0000

    Fill placeholders
2026-03-30 05:52:11 -04:00
Wyvest
3b885cdb0f
Improve modpack download resiliency, onboarding
Track and surface partial modpack download failures across core and frontend. Adds a PartialModpackInstall error and counts failed/attempted downloads in the mrpack flow, returning an error if some files failed. Frontend download pipeline now runs parallel downloads that return per-item results, reports/logs failures, and exposes retry/continue UI in onboarding.

Backend robustness improvements: deduplicate concurrent package inserts by reusing existing row on unique-hash constraint, detect hard-link collisions and verify hashes before skipping or erroring, and return meaningful errors when partial installs occur.

Other changes: propagate richer error context when stamping migrated cluster created_at, invalidate relevant queries after migration, make settings writes awaitable and update the query cache optimistically, add queryClient usage in version-migration hook, and various type/bindings updates to reflect the new PartialModpackInstall error. These changes improve reliability and UX during batch package installs and onboarding downloads.
2026-03-30 05:50:29 -04:00
Wyvest
9f1b6d6ae2
Bump to 1.1.0 2026-03-30 03:26:27 -04:00
Wyvest
445eca2775
Spice up onboarding 2026-03-30 03:25:51 -04:00
Wyvest
1c3041679e
unify version downloads 2026-03-30 01:19:15 -04:00
Wyvest
5cac13a54c
Add cluster migration stuff
Implement cluster content migration and local versions override. Adds a new IPC API (copy_cluster_content) and CopyClusterResult, plus copy_dir_all helper to recursively copy files. Introduces migrate_non_bundle_packages and migrate_bundles_to_cluster to map and install provider-managed packages for the target MC version and to translate/copy bundle overrides; files without a compatible provider version are copied as .disabled and returned to the caller. Prefer a local versions_override.json when loading the online manifest (useful for testing) and adapt MC version parsing/formatting for year-based majors. Frontend: update generated bindings/types, add MigrationModal UI and useVersionMigration hook, integrate the migration modal into the root and debug screens, and persist seen_versions in settings. Misc: propagate manifest/versions into several UI routes and adjust bundle/package handling accordingly.
2026-03-30 00:28:14 -04:00
Jacob
247d23083d
fix: Resource Packs Compatibility Check (#498)
Sometimes they are tagged as only supporting Vanilla what is true but
that causes issues when downloading into a fabric cluster. Since fabric
IS NOT vanilla they "don't work" thus won't install. This just skips
that check and downloads it anyways

Co-authored-by: Wyvest <wyvestbusiness@gmail.com>
2026-03-08 16:52:09 +09:00
Jacob
978732a8fa
feat: toasts customizability (#497)
* fix: Toasts always using the info icon

* feat: Toast Position Customizability

* feat: Toast duration customizability

* fix: send toast button

wide lol

* feat: toast auto close customizability

* fix: SettingNumber being bad
2026-03-08 16:50:50 +09:00
belle
73a55ee58d
Fix PKGBUILDs (#495)
* Fix PKGBUILDs

* remove filter from pnpm prep
2026-03-08 16:49:23 +09:00
Jacob
adf9f4bd63
feat: debug page (#493)
* fix: discordRPC route sorting

It wasn't alphabetical and it was annoying me

* feat: debug page

* move stuff into hooks where they should be

* fix: building
2026-03-06 01:25:03 -05:00
Wyvest
963e7044f2
add backups for art images 2026-03-04 08:11:13 -05:00
Wyvest
e40278366c
fix linux window bar 2026-03-04 01:51:03 -05:00
Wyvest
801ed74ace
Fix oneclient description 2026-03-04 01:41:42 -05:00
Wyvest
f7c8256729
try to fix AUR build? 2026-03-04 01:23:23 -05:00
Wyvest
16b1036ccb rename tag 2026-03-03 23:08:37 -05:00
Wyvest
a86e1b174d merge-releases script 2026-03-03 23:06:47 -05:00
Wyvest
1ad200b5dc kath 2026-03-03 20:56:47 -05:00
Jacob
fb5da05ad9
feat: better debug (#486)
* fix: tauri os plugin not being fully loaded

* feat: debug keybind

Bound to `CTRL+SHIFT+D` (and `ALT+F12`) and copies the debug info to the
clipboard

* refactor: __root.tx

Clean up a lot of code and move them into there own files

* feat: os distro in debug

* refactor: get_full_debug_info_parsed

* feat: made by info in settings

Also made it so that you can click on it to copy debug info

* fix: warning in oneclient/desktop/lib.rs

* refactor: copyDebugInfo

Moved it to the backend to later be used

* feat: log out debug info when oneclient launches

* `inDev` -> `In Dev`

* feat: toggle debug logging

* fix rust compiler
2026-03-03 20:46:11 -05:00
Wyvest
45db558013 Fix default categories always being selected
Fixes #489 #481
2026-03-03 20:30:55 -05:00
Wyvest
79e6824891 Bump to 1.0.3 2026-03-01 05:19:17 -05:00
Wyvest
7bae85eb70 fix rust lint 2026-03-01 05:18:37 -05:00
Wyvest
ca80e7d6b2 Fix memory setting not updating 2026-03-01 04:47:55 -05:00
Wyvest
55a7f572a6
Update changelog fetch URL to DataStorage repo 2026-02-26 10:49:49 -05:00
Wyvest
30bb161156
Delete docs/OneClient/CHANGE_LOG.md 2026-02-26 10:49:10 -05:00
Wyvest
1249bbf920 Update CHANGE_LOG.md 2026-02-26 00:25:00 -05:00
Wyvest
d90d9b928c 1.0.2 2026-02-26 00:17:50 -05:00
Wyvest
31f033cc87 fix settings saving 2026-02-26 00:17:07 -05:00
Wyvest
3e6f2361ee sanitize pre-launch/wrapper/post-launch command 2026-02-26 00:16:46 -05:00
Wyvest
44742fa21f port fixes from https://github.com/modrinth/code/pull/2369/ 2026-02-25 23:23:27 -05:00
Wyvest
aecfbad53a pass lint 2026-02-25 21:03:12 -05:00
Wyvest
e7bb7d6b70 Squashed commit of the following:
commit a782b683f30d127936115d0e31a70de1d8268694
Author: Wyvest <wyvestbusiness@gmail.com>
Date:   Wed Feb 25 20:56:05 2026 -0500

    remove debug logging

commit 640950728f480f1b8c7939a720124ea581c227af
Author: Wyvest <wyvestbusiness@gmail.com>
Date:   Wed Feb 25 14:40:03 2026 -0500

    add some ms account error parsing

commit 014e3ecb26d75934798592f5be8cfbb437c92263
Merge: 83a6a1e ecfb98b
Author: Wyvest <wyvestbusiness@gmail.com>
Date:   Wed Feb 25 13:34:16 2026 -0500

    Merge branch 'main' into account-logging

commit 83a6a1e233dd4f3a3de18aabf1c7beac268d1594
Author: Wyvest <wyvestbusiness@gmail.com>
Date:   Wed Feb 25 04:05:05 2026 -0500

    add a lot of debug logging for accounts
2026-02-25 20:56:21 -05:00
Wyvest
5b566a03a3 update copyright year
fixes #485
2026-02-25 15:42:14 -05:00
Wyvest
ecfb98b6db lint 4 2026-02-25 10:36:48 -05:00
Wyvest
ea5054395b lint pt 3 2026-02-25 09:48:08 -05:00
Wyvest
96b380db25 lint pt 2 2026-02-25 09:31:45 -05:00
Wyvest
c515d00da1 lint 2026-02-25 08:14:57 -05:00
Wyvest
6811f3c41a Update discord.rs 2026-02-25 08:08:28 -05:00
Wyvest
db81396aa6 add jvm args setting 2026-02-25 06:43:55 -05:00
Wyvest
6190411635 make discord rpc more stable 2026-02-25 06:36:31 -05:00
Wyvest
d6aba6ab45 search for mods 2026-02-25 06:20:39 -05:00
Wyvest
ca4cea2ffb incredible art update 2026-02-25 05:44:05 -05:00
Wyvest
8d1b69d7a0
Update CHANGE_LOG for version 1.0.1 2026-02-24 07:09:59 -05:00
Wyvest
1e277084c2 Update oneclient_release.yml 2026-02-24 05:45:41 -05:00
Wyvest
229710d30c re-add macos/linux 2026-02-24 04:41:52 -05:00
Wyvest
f8b7a26fe1 remove everything except windows (temp) 2026-02-24 04:40:45 -05:00
Wyvest
6c07a835b0 fix windows process check 2026-02-24 04:40:19 -05:00
Wyvest
c259c39807 Update oneclient_release.yml 2026-02-24 04:22:21 -05:00
Wyvest
8b6ca89a70 Update bindings.gen.ts 2026-02-24 04:16:50 -05:00
Wyvest
a3d193fb4b fix strange freezes 2026-02-24 04:11:50 -05:00
Wyvest
926a4fdf6f bump to 1.0.1 2026-02-24 00:42:48 -05:00
Wyvest
bd7fd8b812 umm 2026-02-24 00:36:25 -05:00
Wyvest
e25e313155
Implement Apple Developer Certificate handling in workflow
Added steps to import and verify Apple Developer Certificate for macOS builds.
2026-02-24 00:20:04 -05:00
Wyvest
a40a4ddba4 Update CHANGE_LOG.md 2026-02-23 22:56:48 -05:00
Wyvest
bd85c8cdef 1.0.0 2026-02-23 22:45:33 -05:00
Wyvest
87cb86943c Add bundle update checking/apply and API
Introduce full bundle update management: adds bundle_updates module with checking, apply flow, per-cluster locking, subscription inference, and apply helpers for additions/updates/removals (plus unit and e2e test scaffold). Exposes new IPC/API methods (download_package_from_bundle, update_bundle_packages, is_bundle_syncing) and integrates bundle tracking in desktop commands. Adds bundle DAO/entity and migrations to support bundle_packages and bundle_overrides, updates related package/DAO/modpack code, and minor frontend bindings/components/hooks to surface the new functionality. Also adds sea-orm as a dev-dependency for the desktop crate.

Co-Authored-By: Jacob <contact@kath.lol>
2026-02-23 22:36:24 -05:00
Wyvest
64b3ec5f90 Squashed commit of the following:
commit 9d138882dec56d9f823a22dd2284b0f264a6b35a
Author: Wyvest <wyvestbusiness@gmail.com>
Date:   Mon Feb 23 17:14:58 2026 -0500

    Watch cluster dirs, toggle & custom-bundle support

    Enable filesystem watches and add support for directory-based packages, toggleable packages, and custom bundles.

    Highlights:
    - Enable tauri-plugin-fs "watch" feature and add fs:allow-watch capability.
    - Frontend: add useAllClusterDirWatch hook to watch cluster folders and call bindings.core.syncCluster on changes; add useCustomBundle to synthesize a "[Custom]" bundle for installed-but-unbundled packages and surface it in mods/datapacks/resource-packs/shaders routes via ModList/ModCard changes.
    - ModCard/ModList: introduce toggle mode (useToggleMode) to treat some bundles as toggleable; implement UI toggle behavior and skip interactivity for directory-based bundles.
    - Bindings: expose syncCluster and togglePackage in bindings.gen for IPC usage.
    - Backend: extend sync_cluster to detect directories, register directory-based packages as local, and reconcile/unlink DB links for files removed from disk; support removing directories when unlinking packages.
    - Backend API: add toggle_package to rename package files (.disabled toggling), update central store path, and return the new enabled state; add Tauri commands for sync_cluster and toggle_package.

    Reason: allow resourcepacks/shaders/datapacks stored as directories to be recognized as local packages, provide automatic sync on filesystem changes, and let users enable/disable packages by toggling their filenames.

commit 1b100f7ed2242d21f3fbb6229745cddbd3199db7
Merge: 4ea4216 6a2352c
Author: Wyvest <wyvestbusiness@gmail.com>
Date:   Mon Feb 23 06:30:25 2026 -0500

    Merge branch 'oneclient/main' into bruhhh

commit 4ea42169ad430bc8b127f829c528c258f49b5a3a
Author: Wyvest <wyvestbusiness@gmail.com>
Date:   Wed Dec 31 03:48:13 2025 +0900

    Fix external mod tracking

commit cb456f4f6d50165d05091ae65c9611ebf956df07
Merge: 8628380 18c37f8
Author: Wyvest <wyvestbusiness@gmail.com>
Date:   Wed Dec 31 01:48:00 2025 +0900

    Merge branch 'oneclient/main' into bruhhh

commit 86283800ab265361540f4cf20e9a843cb2c4f5f0
Author: Wyvest <wyvestbusiness@gmail.com>
Date:   Sun Dec 28 18:19:57 2025 +0900

    h
2026-02-23 17:15:26 -05:00
Wyvest
6a2352c5dd Add support for resource packs + shaders + datapacks 2026-02-23 06:30:01 -05:00
Wyvest
3bcf55907c fix shaderpacks installing to wrong folder 2026-02-23 06:16:19 -05:00
Wyvest
fe0b1eed06 Extract bundle overrides before downloading mods 2026-02-23 06:10:46 -05:00
Wyvest
e745c75cc8 Fix overrides not being downloaded 2026-02-23 05:56:01 -05:00
Wyvest
b5d6beada0 Fix minor verison bug 2026-02-23 04:35:58 -05:00
Jacob
f1317a4ad1
fix: remove account popup spacing (#477)
Closes: https://github.com/Polyfrost/OneLauncher/issues/468
2026-01-11 08:06:31 -05:00
Jacob
9220ed023d
fix(OneClient): building (#476) 2026-01-11 08:05:22 -05:00
Jacob
29adbbc541
feat(OneClient-SettingsRow): optional settings icon (#475)
* feat(OneClient-SettingsRow): optional settings icon

* fix: building
2026-01-11 08:04:50 -05:00
Jacob
682a08765b
feat(OneClient-ChangeLog) (#474)
* feat(OneClient): change log

* feat(OneClient-ChangeLog)

* fix: CHANGE_LOG url

* fix: formatting
2026-01-11 08:03:52 -05:00
Jacob
7050a6b108
feat: nixos dev stuff (#470)
Co-authored-by: not a cow <104355555+not-a-cowfr@users.noreply.github.com>
2026-01-10 09:25:26 -05:00
Jacob
a513b62b0b
update shadow rs (#473) 2026-01-10 09:25:18 -05:00
Jacob
2d806df712
Merge pull request #471 from Kathund/fix/456
feat(OneClient-Onboarding): Version number
2026-01-09 10:50:45 -05:00
Jacob
fce0ea35a4 style: disable prettier 2026-01-09 10:50:22 -05:00
Wyvest
ea7e5e816b bump to 1.0.0-alpha.6 2026-01-02 00:51:38 +09:00
Wyvest
b85d5f6930 refresh accounts function 2026-01-02 00:44:40 +09:00
Wyvest
0418a789ba Update package.tsx 2025-12-31 03:58:15 +09:00
Wyvest
d6359f7f5f Bump to 1.0.0-alpha.5 2025-12-31 03:51:14 +09:00
Wyvest
09749ea78b Track external mods properly 2025-12-31 03:51:14 +09:00
Wyvest
18c37f8661 change default res again 2025-12-31 01:46:22 +09:00
Jacob
ac386301bb chore(OneClient): Change Default Resolution 2025-12-29 17:50:45 +09:00
Jacob
439f426bcd fix(OneClient): Nonexistent Default Resolution
Closes: https://github.com/Polyfrost/OneLauncher/issues/444
2025-12-29 17:50:45 +09:00
Jacob
133097aaaa feat(OneClient-Memory): Add units
Closes: https://github.com/Polyfrost/OneLauncher/issues/450
2025-12-29 17:50:45 +09:00
Jacob
9237291301 fix(OneClient-Memory): Not being able to be set to 0 2025-12-29 17:50:45 +09:00
Jacob
68145b4098
fix(OneClient-Browser): Remove SkyClient Provider Option (#463)
Closes: https://github.com/Polyfrost/OneLauncher/issues/433
2025-12-29 14:58:27 +09:00
Wyvest
c6da92474c
Merge pull request #462 from Kathund/fix/eslint
fix(OneClient): Eslint
2025-12-29 14:55:29 +09:00
Jacob
8e2dce10f7 fix(OneClient): Eslint 2025-12-29 13:44:06 +08:00
Wyvest
64db796830
Merge pull request #461 from Kathund/fixes-colon3
fixes stuff
2025-12-29 14:09:49 +09:00
Jacob
b06ab0ee8b fix(OneClient-SkinManager): Select the player's selected cape
Closes: https://github.com/Polyfrost/OneLauncher/issues/454
2025-12-29 13:06:57 +08:00
Jacob
c998953bd9 fix(OneClient-SkinManager): Default animation Speed
Closes: https://github.com/Polyfrost/OneLauncher/issues/446
2025-12-29 12:52:21 +08:00
Jacob
eccf13f5fd feat(OneClient-SkinManager): Scroll bar on skins and capes
Closes: https://github.com/Polyfrost/OneLauncher/issues/438
2025-12-29 12:52:21 +08:00
Jacob
e304acb945 fix(OneClient): Spamming modrinth requests
Closes: https://github.com/Polyfrost/OneLauncher/issues/437
2025-12-29 12:52:19 +08:00
Wyvest
800f3ecbdb make chmod happen on linux too 2025-12-29 04:06:45 +09:00
Wyvest
e6c758c1e2 Merge branch 'oneclient/main' of https://github.com/Polyfrost/OneLauncher into oneclient/main 2025-12-29 01:32:39 +09:00
Wyvest
79daad5e9c Actually fix decorations showing on Linux 2025-12-29 01:32:37 +09:00
Wyvest
19c0f60a58
Specify shell for linuxdeploy version override step 2025-12-29 00:49:33 +09:00
Wyvest
1e64e5892e
Override linuxdeploy version in setup action
Add step to override linuxdeploy version for Ubuntu 22.04.
2025-12-29 00:47:30 +09:00
Wyvest
d4b6ef149d Merge branch 'oneclient/main' of https://github.com/Polyfrost/OneLauncher into oneclient/main 2025-12-29 00:24:23 +09:00
Wyvest
ac7b3dbcad bump to alpha.4 2025-12-29 00:24:21 +09:00
Wyvest
2e0d04d6d0 actually implement "enabled" for mods / categories 2025-12-29 00:23:26 +09:00
trevi
6e8d6fad65
bump tauri-apps/tauri-action to v1 in onelauncher and oneclient workflow files (#460) 2025-12-28 19:54:01 +09:00
Wyvest
22c59722e9 Only turn on decorations on macOS 2025-12-28 18:43:10 +09:00
Wyvest
f8771fb70c fix stupid import 2025-12-27 23:40:25 +09:00
Wyvest
bde1252ea3 Make back button always go to home page
Closes #441
2025-12-27 21:53:52 +09:00
Wyvest
45859d174b Make MS login window bigger
Closes #455
2025-12-27 21:01:59 +09:00
Wyvest
9edabd9508 Fix lack of window rounding on macOS
Fixes #449
2025-12-27 20:46:00 +09:00
Wyvest
de153a3188 Fix copyright info on macOS
Closes #453
2025-12-27 19:56:03 +09:00
Wyvest
9842196c01 Update __root.tsx 2025-12-27 02:24:58 +09:00
Wyvest
a2329b59a5 Fix cluster being stuck on downloading 2025-12-27 02:21:15 +09:00
Wyvest
60e5356120 bump to alpha.3 2025-12-27 01:56:14 +09:00
Wyvest
418a96f9c4 fix ms auth failing sometimes 2025-12-27 01:55:46 +09:00
Wyvest
37ba07b989 fix copy/paste not working on mac 2025-12-27 01:47:42 +09:00
Wyvest
f750c3a853 Merge branch 'oneclient/main' of https://github.com/Polyfrost/OneLauncher into oneclient/main 2025-12-27 00:34:24 +09:00
Wyvest
960d073ece Kill cluster overview page 2025-12-27 00:34:21 +09:00
Wyvest
b02a6cc3c2
Add Tauri signing keys to release workflow 2025-12-26 23:59:32 +09:00
Wyvest
ed77ee5195 fix gh action 2025-12-26 23:45:16 +09:00
Wyvest
027a003ace bump to alpha.2 2025-12-26 23:44:04 +09:00
Wyvest
c86aed047c auto-updates 2025-12-26 23:42:25 +09:00
Wyvest
ac7a1025ac disable missing changelog/feedback page 2025-12-26 21:54:00 +09:00
Wyvest
5db97bc9ed implement caching for modrinth projects/versions 2025-12-26 21:46:05 +09:00
Wyvest
d69b652726 Remove max concurrent request limit 2025-12-26 20:12:09 +09:00
Wyvest
80c7797ad1 Don't search for external JDKs in OneClient 2025-12-26 20:07:51 +09:00
Wyvest
fbb30b7187 fix ansi reading for mc options.txt 2025-12-26 19:48:43 +09:00
Wyvest
14f9256a61 Revert "Rip out auto-downloading deps"
This reverts commit ba0ccdbb11.
2025-12-26 00:29:50 +09:00
Wyvest
6cea998f08 bump verson to 1.0.0-alpha.1 2025-12-26 00:10:35 +09:00
Wyvest
373147505c ease concurrent request limit 2025-12-25 23:53:38 +09:00
Wyvest
bcb2bd94af add rate limit check specifically for modrinth 2025-12-25 23:52:37 +09:00
Wyvest
f8b307d005 add concurrency request limits 2025-12-25 22:49:25 +09:00
Wyvest
403a9f4be8 fix mod cards, removing mods 2025-12-25 21:18:07 +09:00
Wyvest
6dc99149fb fix parallel download check being broken 2025-12-25 17:42:41 +09:00
Wyvest
ba0ccdbb11 Rip out auto-downloading deps
lol
2025-12-25 17:34:47 +09:00
Jacob
83e2892633
fix imports (#430) 2025-12-23 01:37:04 +09:00
Jacob
2fb9f9ceb9
chore: update react (#429) 2025-12-23 01:19:54 +09:00
LynithDev
05657bf8b3 fix: rewrote ingress emitting system. should fix https://github.com/Polyfrost/OneLauncher/issues/419 and https://github.com/Polyfrost/OneLauncher/issues/420 2025-12-22 12:33:15 +01:00
Jacob
d5a5bd7f05
refactor/cleanup (#426) 2025-12-12 07:44:54 -05:00
Jacob
cbdb43b796
chore: bump react packages (#428) 2025-12-09 08:17:25 -05:00
Wyvest
29c9c19a01 fix bindings 2025-12-04 03:52:28 -05:00
Wyvest
aa5fb07718 remove debug 2025-12-04 03:47:51 -05:00
Wyvest
c1d6a66015 Merge branch 'oneclient/main' of https://github.com/Polyfrost/OneLauncher into oneclient/main 2025-12-04 03:46:07 -05:00
Wyvest
b4555888a1 fix java launching (again) on macos 2025-12-04 03:45:51 -05:00
Jacob
e1f8dac5a1
feat(oneclient): Debug Info (#418) 2025-12-04 02:54:23 -05:00
Wyvest
cb8caf60e8 Fix java downloading on macos 2025-12-04 02:48:46 -05:00
Jacob
d7c644f15e
fix(oneclient-mod-downloading): Not checking for no dependencies (#417) 2025-12-03 12:11:11 -05:00
Jacob
d7c345b24a
feat: mods page (#413) 2025-12-03 11:02:10 -05:00
Jacob
9fc4409a18
fix(oneclient): change build templates to say oneclient and not onelauncher (#416) 2025-12-03 11:01:57 -05:00
Jacob
d15a2ea48a
feat(core): Mod data overrides (#414) 2025-11-19 23:03:15 -05:00
Jacob
ddba559570
feat(gemini): config (#415) 2025-11-19 23:03:01 -05:00
LynithDev
063e9f8837 fix: debounced payloads to prevent excessive payload freezes 2025-11-15 21:02:30 +01:00
LynithDev
bb84df1091 fix: java downloading reading entire archive into memory twice 2025-11-15 20:26:16 +01:00
Jacob
a4d07d3fa3
fix: OneClient Onboarding Stepper (#409) 2025-11-15 02:00:44 -05:00
Jacob
6c97a914c2
fix: OneClient Building (#406) 2025-11-13 02:37:20 -05:00
Jacob
3d73d39549
fix: OneClient Onboarding Help Translate (#405) 2025-11-13 00:10:59 -05:00
Jacob
c07d4637d3
fix: OneClient Skin Manager Crash when Invalid Account (#404)
* fix: oneclient skin manager crash when invalid account

* fix: formatting
2025-11-11 07:44:55 -05:00
LynithDev
878f7ed5ab fix: os rule validation now checks whether the version is new or not 2025-11-08 17:39:48 +01:00
Szedann
7b1161cbf5 feat: oneclient icon 2025-11-05 16:04:08 +01:00
Jacob
23b9e9a9c8
fix: oneclient building (#400)
* fix: oneclient building

* fix: oneclient linux compile

* fix: oneclient macos intel building
2025-10-21 04:43:59 -04:00
Jacob
4f4428108a
feat: oneclient onboarding transition animation (#398)
Closes: https://github.com/Polyfrost/OneLauncher/issues/387
2025-10-17 10:50:00 -04:00
Jacob
1e664fa990
fix: double slash in Modrinth organization urls (#397)
Closes https://github.com/Polyfrost/OneLauncher/issues/394
2025-10-17 10:45:32 -04:00
Jacob
fc36727198
feat: copy link button (#399) 2025-10-17 10:37:36 -04:00
Szedann
7ba64694a7 feat: remove appearance from the settings page 2025-10-17 16:32:52 +02:00
Jacob
387630537d
Fix/mini accounts edit (#384)
* fix: mini accounts skin manager

Fix opening the skin manager by moving it to the new logic
Put all this login into an component so this shouldn't happen again

* fix: formatting
2025-10-09 00:39:20 -04:00
Jacob
dc865c970f
fix: oneclient no account popup (#383) 2025-10-08 10:27:42 -04:00
Szedann
aca92e4910 feat: cleaner stepper 2025-10-04 16:50:35 +02:00
Szedann
4e57d1f66f feat: hash routing 2025-10-04 16:49:17 +02:00
Szedann
990657707b feat: improve the settings page 2025-10-04 15:46:39 +02:00
Jacob
4643994cdf
fix: oneclient account skin manager force reloading (#382)
* fix: oneclient account skin manager reloading

* fix: formatting
2025-10-04 07:47:19 -04:00
Jacob
59e7cde8df
feat: oneclient no account popup (#381)
* feat: oneclient no account popup

Prompts the user for a login when they don't have an account added and
are trying to start the game

* fix: formatting

* fix: account popup text color
2025-10-04 00:44:17 -04:00
Jacob
8a2939597b
fix: oneclient versions page delay (#380) 2025-10-03 00:22:34 -04:00
Jacob
e19f5a4f6c
feat: oneclient open onboarding by default (#379) 2025-10-02 22:30:45 -04:00
LynithDev
c16d032b8d feat: fix bundles 2025-10-02 21:48:45 +02:00
Jacob
b610c1caac
fix: eslint (#378)
* fix: eslint script

closes #345

* format: eslint oneclient

* fix eslint script again

:pleading:
2025-10-02 08:36:42 -04:00
Jacob
6ef5e6354e
fix: oneclient building (#377)
Fixes a bunch of ts errors that stop it from building. Their are still
more but I cannot fix them currently
2025-10-02 00:48:13 -04:00
Jacob
3e06a33c6d
feat: oneclient onbaording language page (#370)
* feat: oneclient onbaording language page

* Update apps/oneclient/frontend/src/routes/onboarding/language.tsx

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update apps/oneclient/frontend/src/routes/onboarding/language.tsx

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-10-01 22:15:22 -04:00
Jacob
0e12968b9c
feat: oneclient account skin manager (#373)
* feat: oneclient account skin manager

* feat: oneclient account skin manager import

* feat: oneclient account skin manager improt from url

* feat: oneclient accoutn skin manager animations

* feat: oneclient account skin manager export skin

* feat: oneclient account skin manager skin history

* feat: oneclient mini accounts edit button

* feat: oneclient account skin manager saving skins

* change order of mini account buttons

* feat: oneclient account skin manager elytra support

* fix: oneclient account skin manager default rotate

See https://github.com/bs-community/skinview3d/issues/198 for where fix
came from

Co-authored-by: undefined303 <undefined303@qq.com>
Co-authored-by: Zickles <zicklesistaken@gmail.com>

* feat: oneclient account skin manager change cape

* feat: oneclient account skin manager import from username

---------

Co-authored-by: undefined303 <undefined303@qq.com>
Co-authored-by: Zickles <zicklesistaken@gmail.com>
2025-10-01 13:31:42 -04:00
Szedann
4b5051b9d9 bind 2025-09-30 20:38:29 +02:00
Szedann
e59fd5369d Merge branch 'oneclient/main' of https://github.com/Polyfrost/OneLauncher into oneclient/main 2025-09-30 20:38:14 +02:00
Szedann
3d0658a950 feat: cluster settings 2025-09-30 20:35:33 +02:00
Jacob
c2ad51017b
feat: oneclient mini accounts delete (#376) 2025-09-29 00:32:53 -04:00
Jacob
c90c41ce45
feat: oneclient onboarding finished page (#375) 2025-09-28 02:07:03 -04:00
Jacob
a26c84e9b4
fix: oneclient onboarding not forcing an account (#374) 2025-09-28 01:09:20 -04:00
LynithDev
3b8afb3289 feat: mojang account configuration (skins / capes) 2025-09-27 11:11:45 +02:00
LynithDev
96c0ca4d5a fix: account popup not refetching the default user 2025-09-27 10:19:35 +02:00
Jacob
403ea1fab1
feat: oneclient onboarding prompt for account (#372) 2025-09-26 08:06:28 -04:00
Jacob
589f7048f4
feat: oneclient settings developer (#371)
Open up the onboarding option coz it was annoying me
2025-09-26 07:51:54 -04:00
Jacob
549bb8e8a3
feat: oneclient remove account (#369) 2025-09-26 01:41:37 -04:00
Jacob
4cb6abddd3
fix: contributing.md urls (#368) 2025-09-25 09:15:54 -04:00
LynithDev
1eb6505c0c feat: oneclient bundles 2025-09-21 17:08:03 +02:00
LynithDev
0a77a31846 feat: oneclient bundles 2025-09-21 17:04:38 +02:00
LynithDev
aafffbacfa fix: curseforge body issues 2025-09-17 17:19:53 +02:00
Szedann
eafc8db786 feat: more user friendly toasts (#365) 2025-09-15 22:37:29 +02:00
Szedann
4de4d2cad6 fix: prose colors 2025-09-15 22:12:21 +02:00
Szedann
aad310f2dd Merge branch 'oneclient/main' of https://github.com/Polyfrost/OneLauncher into oneclient/main 2025-09-15 21:50:33 +02:00
Szedann
7ca986cf9d fix animation duration 2025-09-15 21:50:24 +02:00
emirsassan
3c9ff82314 Merge branch 'oneclient/main' of https://github.com/Polyfrost/OneLauncher into oneclient/main 2025-09-05 14:34:14 +03:00
emirsassan
3ff8bea8ef feat: onboarding and a few refactor 2025-09-05 14:33:22 +03:00
Szedann
035657cb79 feat: account popup from onelauncher (fixes #363) 2025-09-04 16:34:59 +02:00
Szedann
107dd69c49 fix padding issues #358 2025-09-04 16:09:57 +02:00
Szedann
75379a917d fix: cluster browser style consistency 2025-09-04 15:24:14 +02:00
Szedann
4ab3ef7d40 fix: get_body invalid body type 2025-09-04 14:51:20 +02:00
Szedann
c975f3b25f fix License info in mod browser looks weird
Fixes #360
2025-09-04 14:50:06 +02:00
Szedann
633fbcddea refactor: remove unused imports 2025-09-02 19:55:07 +02:00
Szedann
88ba7a9a00 feat: download button visual indication 2025-09-02 19:53:35 +02:00
Szedann
cf6e8494e2 refactor: remove unused variables 2025-08-31 19:49:33 +02:00
Szedann
5b9ff10564 fix: correct file opener 2025-08-31 19:49:00 +02:00
Szedann
48373d193b fix: pagination imports 2025-08-31 17:53:22 +02:00
Szedann
4aaf4aebaa Merge branch 'oneclient/main' of https://github.com/Polyfrost/OneLauncher into oneclient/main 2025-08-29 13:10:44 +02:00
Szedann
67488594bb feat: settings page 2025-08-29 13:07:42 +02:00
LynithDev
bd009c1677 chore: small fixes and tweaks + external link now has confirmation dialog 2025-08-28 19:11:09 +02:00
LynithDev
37296675e4 feat: notifications and running game log 2025-08-25 21:18:10 +02:00
Szedann
e2e1cdd616 feat: package page 2025-08-22 12:08:12 +02:00
LynithDev
57ea231e0c fix: tab bar parent box shouldn't have pointer events 2025-08-21 11:55:00 +02:00
LynithDev
9e662e3f8c fix: floating tab bar from not being visible when scrolling 2025-08-21 11:36:51 +02:00
LynithDev
aa72db1e0b feat: home page animations 2025-08-21 11:19:44 +02:00
Szedann
fcab0ba6ed fix: search defaults 2025-08-20 14:28:47 +02:00
Szedann
f9b833822e fix: pagination jumping 2025-08-20 14:17:58 +02:00
Szedann
a3d61830b2 refactor: category selection nav 2025-08-20 14:13:23 +02:00
Szedann
b2f8a14acb feat: browser 2025-08-20 14:08:06 +02:00
Szedann
e6a865657c Merge branch 'oneclient/main' of https://github.com/Polyfrost/OneLauncher into oneclient/main 2025-08-20 13:05:05 +02:00
Szedann
6f84f5d1cc feat: oneclient browser wip 2025-08-20 13:03:49 +02:00
LynithDev
04cd2183d0 fix: native issue with forge versions 2025-08-18 14:35:35 +02:00
LynithDev
f8151421e8 chore: fix merge conflicts 2025-08-18 14:29:18 +02:00
LynithDev
80ebf6e057 feat: accounts 2025-08-18 14:25:59 +02:00
emirsassan
01afca2b6a Merge branch 'oneclient/main' of https://github.com/Polyfrost/OneLauncher into oneclient/main 2025-08-18 09:49:38 +03:00
emirsassan
6ef1a43abe feat: onboarding 2025-08-18 09:48:55 +03:00
LynithDev
fdf74258a0 fix: get_logs returning folders as well 2025-08-15 19:21:27 +02:00
LynithDev
9db18e4d12 feat: log viewer changes and fixes 2025-08-15 19:16:21 +02:00
LynithDev
6d4e0fa850 feat: log viewer 2025-08-14 22:14:56 +02:00
Szedann
843db8f995 feat: logs page 2025-08-12 22:23:56 +02:00
Szedann
b1c8563853 fix: SheetPage sizing 2025-08-12 21:39:48 +02:00
Szedann
f8cb55ac2b fix: scrollbars on the versions page 2025-08-12 20:13:02 +02:00
Szedann
17b964b08e fix: rename clusters to versions 2025-08-12 20:04:33 +02:00
LynithDev
4ff78da186 fix: cluster tab links not navigating properly 2025-08-10 17:33:28 +02:00
LynithDev
f397ed40bd feat: mapped CF categories and added loader field to search results 2025-08-10 17:03:55 +02:00
Szedann
4fda6e44b2 refactor: adapt frontend to backend changes 2025-08-09 23:12:54 +02:00
LynithDev
37b85adefa fix: Modrinth search categories not being serialized properly 2025-08-07 18:45:53 +02:00
LynithDev
33c865893f feat: mrpack support 2025-08-07 18:39:59 +02:00
Szedann
b4746b8ec6 refactor: categories pt. 1 (pain) 2025-08-06 23:44:18 +02:00
LynithDev
6841051b7a feat: unified package categories + curseforge implementation 2025-08-02 16:51:28 +02:00
Szedann
554e51d543 fix: mod browser UI QC issues pt 3 2025-08-01 22:19:50 +02:00
Szedann
be718c9043 Merge branch 'onelauncher/main' of https://github.com/Polyfrost/OneLauncher into onelauncher/main 2025-08-01 21:46:40 +02:00
Szedann
7768d90b14 fix: mod browser UI QC issues pt 2 2025-08-01 21:46:32 +02:00
Szedann
cfa34f665b fix: reremove fixed heading styles 2025-08-01 21:38:46 +02:00
Wyvest
60212b0b0e
Update README.md 2025-08-02 03:20:43 +09:00
Wyvest
6303c25a84
Update README.md 2025-08-02 03:20:33 +09:00
Szedann
766cb90dd4 fix: mod browser UI QC issues pt 1 2025-08-01 20:18:54 +02:00
Szedann
e5c3ed5988 Merge branch 'onelauncher/main' of https://github.com/Polyfrost/OneLauncher into onelauncher/main 2025-07-25 13:14:45 +02:00
Szedann
ab6b2d0057 tweaks 2025-07-25 13:14:22 +02:00
LynithDev
bc963d5fd7 chore: update bindings 2025-07-25 12:17:44 +02:00
LynithDev
0d77715a78 Merge branch 'oneclient/main' into onelauncher/main 2025-07-25 12:16:37 +02:00
LynithDev
8a322e626c chore: update bindings 2025-07-25 11:56:06 +02:00
LynithDev
2ebb8197ac chore: lint 2025-07-25 11:54:45 +02:00
LynithDev
d7ca81c2ed chore: merge onelauncher/main 2025-07-25 11:54:07 +02:00
Szedann
56777c8230 fix: proper CurseForge capitalization 2025-07-25 11:09:08 +02:00
Szedann
766d3a24c4 refactor: hook based download 2025-07-24 13:52:09 +02:00
Szedann
4eb533d0e3 fix: browser cluster selection 2025-07-24 13:09:42 +02:00
Szedann
916b0b22a2 chore: update bindings 2025-07-24 13:09:30 +02:00
Szedann
8b1c7bc686 fix: eslint 2025-07-24 13:09:20 +02:00
Szedann
3b93fe838a fix: env variables in prepare 2025-07-24 12:49:28 +02:00
LynithDev
4820ed1240 fix: build production version of web_common in prepare script 2025-07-24 12:43:56 +02:00
LynithDev
1c300c01b6 fix: attempt to fix jsx-runtime error in prod 2025-07-24 12:33:24 +02:00
LynithDev
6c7c07bce6 fix: attempt to fix jsx-runtime error in prod 2025-07-24 11:53:09 +02:00
LynithDev
b01671eb14 fix: github release action cannot find tauri script 2025-07-24 11:24:35 +02:00
LynithDev
95898eac96 chore: update github node actions 2025-07-24 11:23:23 +02:00
LynithDev
4dcbbf5158 chore: update Swatinem/rust-cache to master commit 2025-07-24 11:23:09 +02:00
LynithDev
a0db3b4e29 chore: fix github actions 2025-07-24 11:18:51 +02:00
LynithDev
034f0b0d74 fix: temporarily remove updater plugin 2025-07-24 11:04:19 +02:00
LynithDev
8b1f9f253f
fix: allow UNIX systems to look for Java in $PATH (#344)
* fix: allow UNIX systems to search for Java on PATH

* fix unnecessary code in mod.rs
2025-07-24 10:42:37 +02:00
httphypixelnet
2d4e31ccab
feat: add husky and lint staged (#347)
* feat: add husky and lint staged
* Update lint-staged.config.mjs
2025-07-24 10:40:31 +02:00
Wyvest
d17d4521c8
Update tauri.conf.json.bak 2025-07-24 07:04:43 +09:00
Wyvest
d0c38c382e
Rename tauri.conf.json to tauri.conf.json.bak 2025-07-24 06:54:46 +09:00
emirsassan
dfe4c0a562 temp: disable updater 2025-07-24 00:05:16 +03:00
Wyvest
51704349eb
Update tauri.conf.json 2025-07-24 05:44:52 +09:00
Wyvest
e2e71736b7
Update tauri.conf.json 2025-07-24 05:15:03 +09:00
Wyvest
e1df515fb3
Update tauri.conf.json 2025-07-24 05:14:52 +09:00
Wyvest
9fd3acbb71
Update tauri.conf.json 2025-07-24 05:12:18 +09:00
Wyvest
5de1d6e4ea
Update tauri.conf.json 2025-07-24 05:12:06 +09:00
Szedann
059330eb17 fix: un-gitignore bindings for onelauncher 2025-07-23 21:48:50 +02:00
Szedann
32d6d1fae0 fix: un-gitignore bindings for CI 2025-07-23 21:47:02 +02:00
Wyvest
c442bbb283
bump node to 24 2025-07-24 04:27:35 +09:00
Wyvest
745cf27107
Fml 2025-07-24 04:21:47 +09:00
Wyvest
fe603fa42b
specify pnpm version for actions 2025-07-24 04:20:05 +09:00
Szedann
8a1645554e refactor: reactify 2025-07-23 21:12:51 +02:00
Szedann
06aea54d6d fix: don't ddos modrinth please 2025-07-23 21:12:25 +02:00
httphypixelnet
f08aab7d56
Merge branch 'Polyfrost:onelauncher/main' into onelauncher/main 2025-07-22 22:45:38 -07:00
Szedann
e45da0d4db Merge branch 'onelauncher/main' of https://github.com/Polyfrost/OneLauncher into onelauncher/main 2025-07-22 19:28:11 +02:00
Szedann
d3b397662f feat: more screen more packages 2025-07-22 19:22:31 +02:00
LynithDev
cd40d951f0 chore: fix eslint warnings 2025-07-22 12:45:22 +02:00
LynithDev
5ac215a5c2 chore: add web_commons build to prepare script 2025-07-22 11:16:06 +02:00
LynithDev
d4441ae0e4 Merge remote-tracking branch 'origin/onelauncher/main' into oneclient/main 2025-07-22 11:05:50 +02:00
Szedann
efbc1d5c81 fix: versions cache 2025-07-21 19:31:52 +02:00
Szedann
d803e86061 feat: versions page 2025-07-21 19:19:10 +02:00
Szedann
f1ff318a6a default package type 2025-07-21 12:17:43 +02:00
Szedann
b226d19c13 Merge branch 'onelauncher/main' of https://github.com/Polyfrost/OneLauncher into onelauncher/main 2025-07-21 11:38:15 +02:00
Szedann
c0b6443432 feat: links and gfm support 2025-07-21 11:38:12 +02:00
emirsassan
96907b18ad chore: bigger image display in screenshots 2025-07-21 11:47:26 +03:00
emirsassan
6b42f77ce0 feat: open url in the system browser instead of launcher window 2025-07-21 11:33:05 +03:00
emirsassan
29cc34c011 Merge branch 'onelauncher/main' of https://github.com/Polyfrost/OneLauncher into onelauncher/main 2025-07-21 11:10:07 +03:00
httphypixelnet
ef7592ca29
fix unnecessary code in mod.rs 2025-07-20 19:53:57 -07:00
httphypixelnet
6cbc0aaea8
fix: allow UNIX systems to search for Java on PATH 2025-07-20 19:34:51 -07:00
Szedann
3390110397 feat: package body and gallery 2025-07-21 00:47:13 +02:00
emirsassan
4e60aefc9d refactor: modal component 2025-07-21 00:09:59 +03:00
Szedann
8e6ffdccf6 fix: featured h2 2025-07-20 21:05:44 +02:00
Szedann
2d02f64239 feat: featured gallery 2025-07-20 20:56:33 +02:00
Szedann
6634e0648e fix: loader overflow 2025-07-20 19:34:55 +02:00
Szedann
d9c2392992 fix: cache issues 2025-07-20 19:34:36 +02:00
Szedann
7c1aee0190 Merge branch 'onelauncher/main' of https://github.com/Polyfrost/OneLauncher into onelauncher/main 2025-07-20 18:07:26 +02:00
Szedann
eb81d24794 feat: packageitem loader icons (WIP) 2025-07-20 18:07:19 +02:00
emirsassan
142e40856d refactor: improve form validation and component structure 2025-07-20 18:55:13 +03:00
Szedann
52a8382fcd fix: fix package image blur 2025-07-20 17:20:40 +02:00
Szedann
7f67d2d769 chore: switch to hash routing 2025-07-20 17:20:27 +02:00
Szedann
6f3a09b52f chore: change button label 2025-07-20 17:03:09 +02:00
LynithDev
50e3b5af0d chore: move react-just-parallax to pnpm catalog 2025-07-20 16:52:03 +02:00
Szedann
e37d2d11be Merge branch 'onelauncher/main' of https://github.com/Polyfrost/OneLauncher into onelauncher/main 2025-07-20 16:51:38 +02:00
Szedann
1703318703 feat: featured section WIP 2025-07-20 16:51:07 +02:00
LynithDev
69ee55ffe9 chore: move all node dependency versions to pnpm catalogs 2025-07-20 16:47:31 +02:00
LynithDev
d69a328ce9 chore: cleanup + prepare things for open source 2025-07-20 16:16:43 +02:00
LynithDev
46e8d87077 Merge branch 'onelauncher/main' into 'oneclient/main' 2025-07-20 13:55:39 +02:00
LynithDev
181660c638 chore: fix font issues 2025-07-20 13:49:18 +02:00
Szedann
f998722d71 Merge branch 'refactor/frontend' of https://github.com/Polyfrost/OneLauncher into refactor/frontend 2025-07-20 13:26:35 +02:00
Szedann
5e9b93bea2 feat: search pagination 2025-07-20 13:26:31 +02:00
not a cow
4286f054ec
Use cargo binstall in setup scripts (#340)
* use cargo binstall if it exists

* powershell
2025-07-20 12:10:14 +09:00
Szedann
8edce35f76 fix: browser bugs 2025-07-19 22:10:39 +02:00
Szedann
ac7bef4fd0 feat: package page 2025-07-19 22:07:27 +02:00
Szedann
a3ab0d88c9 feat: get users from author 2025-07-19 22:02:46 +02:00
Szedann
4ff7066742 fix: package donation data 2025-07-19 22:01:42 +02:00
Szedann
996df8caa8 chore: change to ES2023 2025-07-19 18:26:28 +02:00
Szedann
608c3b62cc fix: context breaking 2025-07-19 16:17:49 +02:00
Szedann
85443ee5a9 fix: browser main page 2025-07-17 20:28:29 +02:00
LynithDev
c3a851d77e Merge branch 'refactor/frontend' into oneclient/main 2025-07-17 19:53:12 +02:00
Szedann
292e7df7f8 feat: search results and queries 2025-07-16 23:18:15 +02:00
LynithDev
3fa8e7f150 feat: modrinth search implementation 2025-07-16 21:27:01 +02:00
LynithDev
e1defccc3f feat: clusters frontend 2025-07-16 20:36:17 +02:00
Szedann
089fbd3e26 fix: browser category selector 2025-07-15 21:42:09 +02:00
emirsassan
2593b293de chore: add package bindings 2025-07-15 21:34:23 +03:00
emirsassan
d98a3aedcf Merge branch 'refactor/frontend' of https://github.com/Polyfrost/OneLauncher into refactor/frontend 2025-07-15 21:04:08 +03:00
emirsassan
54eb4fd645 feat: styling stuff 2025-07-15 21:03:22 +03:00
Szedann
810b3befea remove unused function 2025-07-15 20:00:09 +02:00
Szedann
6d354d0eca feat: browser context stuff 2025-07-15 19:59:14 +02:00
emirsassan
a02ca5c085 chore: start working on browser page and ui text size adjustment 2025-07-13 11:16:29 +03:00
emirsassan
6fdd5e678f feat: ui additions and testing valtio 2025-07-12 11:19:45 +03:00
emirsassan
ad45798ea2 feat: match notification style and re-enable notifications 2025-07-11 15:43:56 +03:00
emirsassan
65cceba2b3 chore: download java if its missing, remove notifications for a while 2025-07-11 09:56:09 +03:00
emirsassan
18554785ae feat: add screenshots and worlds page 2025-07-10 11:27:30 +03:00
emirsassan
72157f689b i forgot this 💀 2025-07-10 09:32:25 +03:00
emirsassan
cc2a0f284a refactor: move useEffect to /app/cluster and fix icons (again) 2025-07-10 09:32:09 +03:00
emirsassan
541b148984 fix: cluster icons displays the right image 2025-07-09 14:06:37 +03:00
emirsassan
03259473a3 fix: notification provider being undefined after a rerender 2025-07-08 09:54:38 +03:00
emirsassan
1e02f4ee73 feat: add native window frame and unfuck my event listener 2025-07-07 10:14:54 +03:00
emirsassan
cd668fc33a refactor: replace Menu with new Popup component 2025-07-07 09:21:33 +03:00
emirsassan
5f78b6aa15 feat: project can be compiled now
i did it for testing in my other pc
2025-07-06 17:20:13 +03:00
Szedann
176e6cb062 refactor: more actual tailwind 2025-07-04 18:00:50 +02:00
Szedann
76d18e32fd refactor: actual tailwind 2025-07-04 17:56:13 +02:00
Szedann
583e241f67 Merge branch 'refactor/frontend' of https://github.com/Polyfrost/OneLauncher into refactor/frontend 2025-07-04 17:43:10 +02:00
Szedann
4037bb12c3 fix: dropdown scrollbar 2025-07-04 17:43:01 +02:00
emirsassan
52a33922c2 chore: cluster settings 2025-07-04 15:00:03 +03:00
Szedann
af385904fe fix: dropdown width (not unocssy sorry) 2025-07-03 16:41:18 +02:00
Szedann
b7bb137ba0 fix: modal backdrop dismiss 2025-07-02 22:28:10 +02:00
emirsassan
1ab5bd5490 feat: added java downloading 2025-07-01 20:37:56 +03:00
LynithDev
864249dd13 chore(oneclient/frontend): begin work on clusters list 2025-06-28 22:18:50 +02:00
LynithDev
89bbf00136 feat(oneclient/frontend): add parallax background effects 2025-06-28 18:33:53 +02:00
emirsassan
f025673d9d Merge branch 'oneclient/main' of https://github.com/Polyfrost/OneLauncher into refactor/frontend 2025-06-28 09:41:03 +03:00
emirsassan
11c5da9dc5 feat: remove eslint-disable console.log
i forgot this is there 💀
2025-06-28 09:39:52 +03:00
emirsassan
7d1be7f2d3 chore: port useSettings
i have to refactor settings one more time
2025-06-27 10:27:51 +03:00
LynithDev
c0100e3a5d feat: sheet page component + animations 2025-06-26 21:21:11 +02:00
emirsassan
855d421eaa feat: global game settings and notifications
notification system needs some work still
2025-06-26 20:48:48 +03:00
emirsassan
1a099f6c3b feat: cluster logs and ui tweaks 2025-06-19 21:46:19 +03:00
emirsassan
fbf04cba9a fix 2025-06-18 09:39:46 +03:00
emirsassan
816be606ff Merge branch 'oneclient/main' of https://github.com/Polyfrost/OneLauncher into refactor/frontend 2025-06-18 09:39:39 +03:00
emirsassan
b47aa875de chore: cluster logic 2025-06-18 09:38:10 +03:00
LynithDev
4e8b03f86b temp: cluster pages 2025-06-17 21:42:42 +02:00
LynithDev
eb820e45ff feat: finished home page and did lots of stuff 2025-06-15 20:27:44 +02:00
emirsassan
ed0da96040 something i dont know 2025-06-14 12:32:55 +03:00
emirsassan
7a25c017e6 Merge branch 'oneclient/main' of https://github.com/Polyfrost/OneLauncher into refactor/frontend 2025-06-14 12:06:41 +03:00
emirsassan
8385f9415d feat: cluster editing 2025-06-14 12:03:13 +03:00
LynithDev
817b93101a chore: lint 2025-06-13 15:03:23 +02:00
emirsassan
8eab81536e chore: import Version 2025-06-13 14:47:15 +02:00
emirsassan
ccf34585d0 feat: game startup is completed 2025-06-13 14:47:15 +02:00
emirsassan
f2f1e2019c feat: context stuff
i dont know what to name this tbh
2025-06-13 14:46:30 +02:00
emirsassan
48105ee231 chore: merge changes 2025-06-13 14:45:58 +02:00
emirsassan
831514d8c7 chore: merge changes 2025-06-13 14:41:14 +02:00
LynithDev
5a427c0379 feat: oneclient home page almost done 2025-06-13 14:36:55 +02:00
emirsassan
014cd863e6 chore: import Version 2025-06-10 22:00:57 +03:00
emirsassan
a3e80ffc77 Merge branch 'oneclient/main' of https://github.com/Polyfrost/OneLauncher into refactor/frontend 2025-06-10 21:59:42 +03:00
emirsassan
51ba9afd49 feat: game startup is completed 2025-06-10 21:57:53 +03:00
LynithDev
29813acb5a feat: add metadata-related commands 2025-06-10 20:00:41 +02:00
LynithDev
cbfb32e991 chore: add proper error serialization 2025-06-09 18:41:39 +02:00
emirsassan
86c3cc4f40 feat: context stuff
i dont know what to name this tbh
2025-06-07 15:24:42 +03:00
emirsassan
b7cac50020 Merge branch 'oneclient/main' of https://github.com/Polyfrost/OneLauncher into refactor/frontend 2025-06-07 13:32:58 +03:00
LynithDev
17734ca1b9 fix: made functions sendable between threads 2025-06-07 11:51:46 +02:00
LynithDev
9eadef559c chore: created a common package for frontend code 2025-06-06 13:41:04 +02:00
emirsassan
650014532a fix: fixed eslint errors 2025-06-05 14:45:17 +03:00
emirsassan
f26a596a78 temp solution to formatted 2025-06-04 21:35:58 +03:00
LynithDev
7d4acc2468 chore: move shared code from onelauncher to its own package 2025-06-04 20:25:26 +02:00
LynithDev
521b3cfdc9 chore: migrate to taurpc 2025-06-02 19:29:55 +02:00
LynithDev
aad3e058d0 chore: get oneclient desktop working 2025-05-31 13:33:40 +02:00
emirsassan
49843a3a76 i forgot this again 2025-05-29 23:45:52 +03:00
emirsassan
12eea80950 feat: implement new settings UI and components 2025-05-29 23:45:39 +03:00
emirsassan
46cca44837 feat(cluster): add settings, screenshots, and worlds pages 2025-05-28 12:56:44 +03:00
emirsassan
e92508c8de Merge branch 'refactor/frontend' of https://github.com/Polyfrost/OneLauncher into refactor/frontend 2025-05-26 19:14:56 +03:00
emirsassan
be48b6042b feat: add cluster view and improve scroll behavior
mid commit again
2025-05-26 19:14:07 +03:00
LynithDev
9e860c77f8 feat: overlay scrollbars 2025-05-26 16:59:58 +02:00
emirsassan
eebd4e90c2 feat: add settings 2025-05-24 19:28:11 +03:00
emirsassan
28050733cb i forgot to add this 2025-05-22 22:36:22 +03:00
emirsassan
5d88f9a5c1 feat: add cluster management functionality
only creating for now lol
2025-05-22 22:36:11 +03:00
emirsassan
13fb769208 feat: import page ui finished 2025-05-17 10:23:44 +03:00
emirsassan
b85b12d792 feat: add new onboarding routes and components 2025-05-16 17:44:16 +03:00
emirsassan
cd687d7ce9 refactor: adjust layout and remove redundant flex properties 2025-05-16 11:25:43 +03:00
emirsassan
079fb1ca0f feat: add onboarding base
implementation needs a few fixes and not completed
2025-05-16 09:55:43 +03:00
LynithDev
df26bdb95e refactor: begin migration to reactjs 2025-05-11 20:34:30 +02:00
LynithDev
303ac31756 chore: organise project structure and slowly switch out solid for react 2025-05-02 11:42:22 +02:00
LynithDev
37e8e1eaf8 chore: move onelauncher apps into its own folder 2025-05-02 10:41:55 +02:00
LynithDev
81aa87afdd chore: remove dependabot 2025-05-02 10:06:13 +02:00
LynithDev
c81d7a6eeb feat: sort out desktop app base 2025-05-01 15:06:17 +02:00
LynithDev
cb1c8d54de feat: desktop crate is ready to be fixed 2025-05-01 13:29:26 +02:00
LynithDev
f939169aa6 feat: Package downloading and linking 2025-04-26 14:54:21 +02:00
LynithDev
17f8b382a7 feat: Modrinth provider 2025-04-24 12:03:39 +02:00
LynithDev
06215367c2 feat: Launching 2025-04-18 16:28:38 +02:00
LynithDev
71d584e5a3 feat: process management + os specific settings + proxied events + launch half done 2025-04-17 20:14:25 +02:00
LynithDev
a8eb841bd7 feat: auth 2025-04-11 16:06:03 +02:00
LynithDev
6c80a84972 feat: prepare clusters for launch 2025-04-11 14:24:00 +02:00
LynithDev
2a0f53e107 feat: Downloading entire game works now 2025-04-09 18:44:26 +02:00
LynithDev
81ed2de0f5 feat: Downloading game assets + download minecraft example 2025-04-08 16:37:48 +02:00
LynithDev
1f2d560983 feat: Metadata and semaphore store 2025-04-07 19:53:33 +02:00
LynithDev
54d16e5d69 feat: Lots of small tweaks and stuff, begin work on minecraft / interpulse metadata 2025-04-04 19:48:23 +02:00
LynithDev
2f7c26229a feat: Cluster API 2025-04-03 22:12:03 +02:00
LynithDev
11d2ff43ed feat: Customizable core options 2025-04-03 13:02:48 +02:00
LynithDev
bb2d33bef0 feat: Discord RPC + HTTP and Crypto utils 2025-04-02 17:43:59 +02:00
LynithDev
e639344310 feat: common java paths 2025-04-02 13:47:49 +02:00
LynithDev
249898b3d2 feat: Java checker and DB helpers 2025-03-30 15:38:55 +02:00
LynithDev
33ac7b4397 refactor: Switch to sea-orm 2025-03-30 13:34:26 +02:00
LynithDev
7b82ad6887 refactor: fix deadlocks in ingress system 2025-03-29 20:37:12 +01:00
LynithDev
2815c45409 refactor: Base stuff done 2025-03-29 10:26:01 +01:00
LynithDev
5f4d4013b3 chore: Begin work on backend refactor 2025-03-23 19:04:58 +01:00
LynithDev
59183e9124 feat: Launch button debounce + now in cluster sidebar 2025-02-16 11:03:38 +01:00
LynithDev
2231be828f chore: remove package manager lock 2025-02-16 10:23:56 +01:00
LynithDev
6d7cffdec2 fix: Theming adjustments 2025-01-28 21:07:08 +01:00
LynithDev
258beaf6f4 fix: Markdown fixes 2025-01-28 20:56:44 +01:00
LynithDev
bea294c8d6 feat: Don't load packages beginning with '.' + Added open folder to cluster mods 2025-01-28 20:45:23 +01:00
LynithDev
23cdd5856c chore: Change version to 0.1.0-alpha.2 2025-01-28 18:38:53 +01:00
LynithDev
c3c945a06b chore: Bump to 1.0.0-alpha.2 2025-01-28 18:13:16 +01:00
LynithDev
1906e29bee fix: Fix themes not loading properly causing the entire program to break 2025-01-28 17:29:11 +01:00
LynithDev
ea24c96a79 fix: Enable dev tools in prod builds 2025-01-28 17:05:29 +01:00
LynithDev
9dacf35369 feat: Change disabled mod style 2025-01-28 15:22:32 +01:00
LynithDev
6d358f501c fix: Attempt to fix Onboarding and Appearance pages 2025-01-28 15:19:21 +01:00
LynithDev
54e9b505dc LAUNCHER-6 fix: Errors when reading game logs 2025-01-28 11:16:46 +01:00
LynithDev
b435206799 fix: Window frame disappearing due to pagination in browser 2025-01-26 12:09:38 +01:00
LynithDev
8db13f4f8f chore: Change SkyClient package url value placeholder 2025-01-26 11:59:56 +01:00
LynithDev
bfe84dee24 LAUNCHER-11 fix: Package version sorting for SkyClient 2025-01-26 11:58:44 +01:00
LynithDev
1912df7698 LAUNCHER-14 feat: Disabling/enabling packages 2025-01-16 13:51:41 +01:00
LynithDev
04736cc6a4 fix: An error when inferring a package no longer fails the entire inferring process 2025-01-11 15:57:05 +01:00
LynithDev
2a85623da6 LAUNCHER-4 chore: Changed DeleteModal text for various delete actions 2024-12-27 13:02:20 +01:00
LynithDev
de70230645 LAUNCHER-5 feat: Disable mod buttons when the cluster is running
LAUNCHER-16 feat: Running cluster indicator on home page
2024-12-27 12:50:19 +01:00
LynithDev
d4a33ad348 LAUNCHER-9 feat: Send notification when downloading a package 2024-12-27 12:09:19 +01:00
LynithDev
3770d7b896 LAUNCHER-10 feat: Custom path input for launcher imports 2024-12-23 15:49:09 +01:00
LynithDev
c74d091b8e LAUNCHER-12 fix: frontend not reacting to game close 2024-12-22 12:28:37 +01:00
LynithDev
e8ad2e0da7 LAUNCHER-2 fix: new PrismLauncher minecraft folder name 2024-12-22 11:20:55 +01:00
LynithDev
1c05791f70 fix: Theming adjustments and incorrect color fixes 2024-12-21 14:42:00 +01:00
LynithDev
334885af77 fix: ClusterSettings excessive CPU and Memory usage 2024-12-21 12:28:01 +01:00
LynithDev
bab116265c
Merge pull request #235 from ThatGravyBoat/fix/misc-fixes
Fix: Miscellaneous changes/fixes
2024-12-21 11:27:06 +00:00
LynithDev
4b9c6bf127
Merge pull request #234 from ThatGravyBoat/fix/update-loader-icons
Chore: Update loader logos
2024-12-21 10:44:19 +00:00
Gravy Boat
1c8246b177
Some misc fixes 2024-12-20 20:08:22 -03:30
Gravy Boat
43321cc228
Update icons 2024-12-20 17:24:59 -03:30
ev chang
ad7f906d0a
Revert "fix: Downloading fallback + better logging in production"
This reverts commit 2d29f3a5c7.
2024-12-18 17:01:32 -05:00
ev chang
b26f864b1b
Revert "fix: Replace URL in log with correct variable"
This reverts commit 050509bb0d.
2024-12-18 17:01:31 -05:00
ev chang
b8588073e1
Revert "temp: Temporary commit for wyvest issue (todo: revert)"
This reverts commit f5f6e0d54d.
2024-12-18 17:01:29 -05:00
LynithDev
f5f6e0d54d temp: Temporary commit for wyvest issue (todo: revert) 2024-12-18 21:02:56 +01:00
LynithDev
050509bb0d fix: Replace URL in log with correct variable 2024-12-18 16:11:53 +01:00
LynithDev
2d29f3a5c7 fix: Downloading fallback + better logging in production 2024-12-18 16:00:57 +01:00
LynithDev
269f0d9b78 feat: Add forced login page to Onboarding 2024-12-15 19:18:42 +01:00
LynithDev
878fccb1fc fix: Hide create cluster modal after pressing create button 2024-12-15 18:50:30 +01:00
LynithDev
db55aaabfb fix: Settings desync between frontend and backend 2024-12-15 18:47:12 +01:00
LynithDev
4676ac2909 fix: Scrolbar colors 2024-12-15 11:50:56 +01:00
LynithDev
cedc33bef0 chore: Fix weird java prefix + add force reinstall button 2024-12-15 11:41:50 +01:00
LynithDev
74a0e27dbc chore: Update icons... 2024-12-14 17:26:12 +01:00
LynithDev
13809dc9ff chore: Update icons + discord rpc key 2024-12-14 17:18:46 +01:00
LynithDev
769bcec746 fix: Page transition speed on non-webkitgtk 2024-12-14 16:39:13 +01:00
LynithDev
da54c6adc7 fix: Notification tweaks 2024-12-14 16:27:42 +01:00
LynithDev
1777a46daa fix: OnboardingSummary overflow 2024-12-14 16:09:38 +01:00
LynithDev
087f36d6fc fix: java downloading from cluster screen 2024-12-14 16:06:03 +01:00
LynithDev
a5d898b5d5 chore: Update icon + logo 2024-12-14 14:49:42 +01:00
LynithDev
92179d5419 fix: One store for Notifications + dev button to create notis 2024-12-14 14:03:33 +01:00
LynithDev
8f08812216 fix: Fix Cluster java selection modal 2024-12-14 13:31:00 +01:00
LynithDev
f696419884 fix: Notification overlay goes behind modal backdrops 2024-12-12 17:08:01 +01:00
LynithDev
3e9605997b chore: Lint rs partially 2024-12-08 15:27:19 +01:00
LynithDev
2bdfd50e36 chore: Lint 2024-12-08 15:11:05 +01:00
LynithDev
a0e5287720 fix: macOS target compile error 2024-12-08 15:06:03 +01:00
LynithDev
e973812aaf feat: SkyClient Part 2 2024-12-08 11:34:18 +01:00
LynithDev
f49dc71e9d feat: SkyClient Part 1 2024-12-07 15:00:38 +01:00
LynithDev
2554d233f4 feat: Java downloader 2024-12-07 12:03:50 +01:00
LynithDev
aa6c3f333b feat(ui): Featured package 2024-12-01 14:02:30 +01:00
LynithDev
b4100a4a3c Revert "chore: Remove packageManager requirement"
This reverts commit 471f5e8097.
2024-12-01 12:58:16 +01:00
LynithDev
291e93737f Reapply "fix(ui): Fix PlayerHead component on account switch"
This reverts commit 0e8966ede2.
2024-12-01 12:54:45 +01:00
LynithDev
0e8966ede2 Revert "fix(ui): Fix PlayerHead component on account switch"
This reverts commit bda3b8b7f6.
2024-12-01 12:53:53 +01:00
LynithDev
471f5e8097 chore: Remove packageManager requirement 2024-12-01 12:46:15 +01:00
LynithDev
bda3b8b7f6 fix(ui): Fix PlayerHead component on account switch 2024-11-30 12:39:27 +01:00
LynithDev
d7cca68ed2 feat(ui): Add a pixel of more padding to custom titlebar 2024-11-29 21:27:17 +01:00
LynithDev
db6e995836 feat(ui): Create cluster modal now supports importing instances 2024-11-29 21:25:12 +01:00
LynithDev
623632a4fe fix(styles): Fix disabled colours 2024-11-28 17:20:47 +01:00
LynithDev
5a1ed75454 chore(ui): Organise Onboarding imports 2024-11-28 16:57:43 +01:00
LynithDev
ba1f1a2c5d chore(ui): Remove some useless / WIP settings 2024-11-28 16:38:27 +01:00
LynithDev
dcdeb7e3bc chore: Revert deleted config files 💀 2024-11-28 16:30:26 +01:00
LynithDev
fc834bbb74 chore: Remove CurseForge API key from env variables and put it in the constants file 2024-11-28 16:25:46 +01:00
LynithDev
cbc7ae8465 feat: Themes 2024-11-23 19:34:50 +01:00
LynithDev
139f61faa6 fix: Mod icons not showing up 2024-11-23 15:00:19 +01:00
LynithDev
92addf191a fix: Make sure to add Unknown meta to packages that failed to infer 2024-11-23 12:18:35 +01:00
LynithDev
1521bbb96a feat: Package inferring from hash 2024-11-16 14:40:41 +01:00
LynithDev
c07d538575 fix: Launcher imports should work 2024-11-01 11:39:45 +01:00
LynithDev
ef4154c5d3 fix: Launcher import path fixed and some small onboarding frontend tweaks 2024-10-28 13:03:35 +01:00
LynithDev
709d2a0136 feat: Finish off onboarding instance import frontend + multi-select lists 2024-10-28 11:58:54 +01:00
LynithDev
23ff953f64 feat: Add Launcher icons and begin work on importing instances 2024-10-27 17:22:25 +01:00
LynithDev
451987a326 fix: CPU usage after launching a cluster 2024-10-27 15:03:42 +01:00
LynithDev
24f58bec2d fix: Unlisten notifications event listener on unmount 2024-10-27 13:00:37 +01:00
LynithDev
1539062d36 chore: Change Home Banner cluster settings button 2024-10-27 12:45:38 +01:00
LynithDev
facca52c49 chore: Update packages 2024-10-23 18:21:55 +02:00
LynithDev
f00db32cab fix: Ingress, should probably add more event listeners and more ingress types for better user feedback 2024-10-22 22:09:28 +02:00
LynithDev
a158f5a058 fix: Navbar overlays mispositioned on startup 2024-10-20 16:36:47 +02:00
LynithDev
5f0df1af20 chore: Make release action always do draft releases 2024-10-20 15:50:29 +02:00
LynithDev
f315eb95f4 fix(packages): Attempt to fix 'cluster not found in packages map' error 2024-10-20 15:38:21 +02:00
LynithDev
7d9b11ffae chore: Add CURSEFORGE_API_KEY env var + remove .msi build target 2024-10-19 11:44:57 +02:00
LynithDev
a4151252da chore: Remove warning 2024-10-19 11:02:03 +02:00
LynithDev
e20f828bf8 chore: Unignore generated bindings.ts, needed for action builds 2024-10-19 11:01:44 +02:00
LynithDev
72681356fd fix(workflow): i hate devops 2024-10-18 21:36:16 +02:00
LynithDev
45d7c18ab1 fix(workflow): Try release build with pnpm desktop 2024-10-18 21:33:42 +02:00
LynithDev
49bda31369 fix: Relase workflow 2024-10-18 21:29:48 +02:00
LynithDev
692bee88b8 release workflow 2024-10-18 21:16:08 +02:00
LynithDev
fd5e50b628 forgot to rename 2024-10-18 20:28:20 +02:00
LynithDev
528a68ab31 attempt to cache rust 2024-10-18 20:26:43 +02:00
LynithDev
1e50eeec79 update lockfile 2024-10-18 20:02:50 +02:00
LynithDev
1643149aad I hate devops 2024-10-18 19:56:31 +02:00
LynithDev
217d517563 chore(workflow): oops, forgot to add this 2024-10-18 19:53:34 +02:00
LynithDev
15172d5537 chore: Remove prisma and fix up some actions 2024-10-18 19:52:17 +02:00
LynithDev
f6c997e292 chore: GitHub action's rewrite 2024-10-18 19:28:08 +02:00
LynithDev
a39007bfb3 chore: Fix warnings with trivial casts 2024-10-18 19:13:31 +02:00
LynithDev
b4b2d7f22f chore: Update tauri to stable 2024-10-18 19:04:09 +02:00
LynithDev
3745ed03ec
Merge pull request #203 from Polyfrost/feat/curseforge
Feature: Implement Curseforge Package API
2024-10-17 20:48:13 +02:00
LynithDev
26d957b80a chore: Remove experiemntal clippy reason + update packageManager version 2024-10-17 20:16:18 +02:00
Pauline
b5bc602d21
Merge branch 'main' into feat/curseforge 2024-10-15 19:19:19 -04:00
LynithDev
a1f8df7f49 feat: Finished Curseforge implementation? 2024-10-08 18:06:43 +02:00
LynithDev
518c7ec8b9 feat: Implement more functionality for Curseforge 2024-10-03 20:48:01 +02:00
Pauline
4a63c96cd9
🔨 chore: bump deps
Signed-off-by: Pauline <git@ethanlibs.co>
2024-09-28 06:33:53 +02:00
Pauline
a357df0b2d
🐛 fix: clippy lints
Signed-off-by: Pauline <git@ethanlibs.co>
2024-09-28 06:33:12 +02:00
Pauline
8c99ee1b1f
♻️ refactor: clippy lints
Signed-off-by: Pauline <git@ethanlibs.co>
2024-09-27 06:59:27 +02:00
Pauline
a5a95e6346
🔨 chore: bump deps
Signed-off-by: Pauline <git@ethanlibs.co>
2024-09-27 06:38:20 +02:00
LynithDev
e458108923 feat: Add fetching package body 2024-09-22 19:45:36 +02:00
Pauline
6e9d36989d
🐛 fix: mc launch args
Signed-off-by: Pauline <git@ethanlibs.co>
2024-09-22 09:28:09 +02:00
LynithDev
c991319463 feat: Fix curseforge search and tweak browser frontend 2024-09-21 14:53:56 +02:00
LynithDev
0fb2017924 feat: Start working on implementing Curseforge into the frontend + fetch_categories helper script 2024-09-20 22:07:02 +02:00
LynithDev
f9788ae49b chore: Merge main 2024-09-20 20:04:14 +02:00
LynithDev
a5c142d270 feat: Start work on Curseforge API integration 2024-09-20 20:04:14 +02:00
Pauline
81339aa77f
🔨 chore: bump tauri
Signed-off-by: Pauline <git@ethanlibs.co>
2024-09-19 06:32:29 +02:00
Pauline
49408c83d2
🔨 chore: bump tauri to latest rc
Signed-off-by: Pauline <git@ethanlibs.co>
2024-09-19 00:50:25 +02:00
Pauline
791adbf173
🔨 chore: script fixies x2
Signed-off-by: Pauline <git@ethanlibs.co>
2024-09-18 16:25:15 +02:00
Pauline
2ed2891678
Merge branch 'main' of github.com:Polyfrost/OneLauncher 2024-09-18 00:18:28 +02:00
Pauline
a1c7649474
🔨 chore: script fixies
Signed-off-by: Pauline <git@ethanlibs.co>
2024-09-18 00:18:23 +02:00
LynithDev
c0ac142f11 chore: Some small tweaks 2024-09-17 20:35:32 +02:00
Pauline
3e67a85a8d
🐛 fix: h :3
Signed-off-by: Pauline <git@ethanlibs.co>
2024-09-16 20:34:52 +02:00
Pauline
876c4b987b
🔨 chore: merge
Signed-off-by: Pauline <git@ethanlibs.co>
2024-09-16 20:31:45 +02:00
Pauline
79d0cab1f4
🐛 fix: temporary test build fixes
Signed-off-by: Pauline <git@ethanlibs.co>
2024-09-16 20:31:19 +02:00
LynithDev
2fbc3ff8c7 fix(workflow): Try catch updater keys 2024-09-16 20:26:32 +02:00
LynithDev
d350d11cb3 fix: Attempt to fix build CI 2024-09-16 20:01:32 +02:00
LynithDev
0c4673990a fix: Small polishes + remove isolation and CSP 2024-09-16 19:35:04 +02:00
Pauline
885da4f798
🐛 fix: d
Signed-off-by: Pauline <git@ethanlibs.co>
2024-09-16 01:43:41 +02:00
LynithDev
30b2de6129 feat: Add custom window frame option + some tweaks 2024-09-15 20:48:21 +02:00
Pauline
eafd9eed58
🔨 chore: reformat imports
Signed-off-by: Pauline <git@ethanlibs.co>
2024-09-15 06:03:48 +02:00
Pauline
4ad407c90b
🔨 chore: bump des
Signed-off-by: Pauline <git@ethanlibs.co>
2024-09-15 05:45:21 +02:00
LynithDev
305bed88ad feat: Make onboarding experience slightly better + fade into main App after completion 2024-09-14 19:46:13 +02:00
LynithDev
fd9a910b12 feat: Add Onboarding "tasks" 2024-09-13 22:06:15 +02:00
LynithDev
08f432c3e6 feat: Add OnboardingImport and OnboardingComplete pages + polish Onboarding experience 2024-09-12 20:58:45 +02:00
Pauline
c983fc5967
🔨 chore: merge
Signed-off-by: Pauline <git@ethanlibs.co>
2024-09-12 18:33:06 +02:00
Pauline
5f64f4fed1
🚧 wip: is somebody gonna match my tweaks
Signed-off-by: Pauline <git@ethanlibs.co>
2024-09-12 18:32:02 +02:00
LynithDev
7ef4c4bbb6 feat: Start work on Onboarding experience 2024-09-11 21:01:42 +02:00
LynithDev
bfaa6ceb3a feat: Java Version editor (Should probably make it more intuitive though) 2024-09-10 21:05:08 +02:00
Pauline
641a728918
🐛 fix: meow
Signed-off-by: Pauline <git@ethanlibs.co>
2024-09-10 06:34:09 +02:00
Pauline
661062c8ad
🐛 fix: bump cargo deps
Signed-off-by: Pauline <git@ethanlibs.co>
2024-09-08 19:17:30 +02:00
Pauline
5b66bc15d2
🐛 fix: remove lockfile
Signed-off-by: Pauline <git@ethanlibs.co>
2024-09-08 19:02:18 +02:00
Pauline
3c6865a875
🔨 chore: try to unbork deps
Signed-off-by: Pauline <git@ethanlibs.co>
2024-09-08 18:59:36 +02:00
Pauline
992af34764
🔨 chore: bump deps
Signed-off-by: Pauline <git@ethanlibs.co>
2024-09-08 18:31:19 +02:00
LynithDev
f50d99a2bc feat: Category mapping + search results tweaks 2024-09-08 14:07:01 +02:00
LynithDev
02f1aaa429 fix: Package removal and syncing
feat: Added fetching managed packages on ClusterMods Page
2024-09-08 12:53:18 +02:00
LynithDev
3025502d82 feat: Added working search filters 2024-09-06 21:33:21 +02:00
LynithDev
5012f2c3a6 feat: Browser Search sorta works, gotta implement filtering 2024-09-04 22:03:37 +02:00
LynithDev
e1c96dc4d1 feat: Loading Spinners 2024-09-04 20:24:24 +02:00
LynithDev
a6f65bb5fb feat: Pagination + usePagination hook 2024-09-01 19:54:43 +02:00
LynithDev
c31f1086c7 feat: BrowserPackage version list (3/4 done)
chore: Make ErrorBoundary only work during prod
2024-09-01 17:05:03 +02:00
Pauline
82485a509c
🐛 fix: where command on windows
Signed-off-by: Pauline <git@ethanlibs.co>
2024-08-31 22:58:03 +02:00
Pauline
6159b07cbf
🔨 chore: sync
Signed-off-by: Pauline <git@ethanlibs.co>
2024-08-31 22:49:44 +02:00
weltschmerz-dev
d094b49b9b
Fix - FullName cannot be found on this object error
Fix - Node version returning 0.0.0.0
2024-08-31 22:48:26 +02:00
Pauline
28b1f9f0e3
Feature: Package API (#75) (i'm just going to merge this because the other branches are inactive)
* feat: Start work on package api

* feat: Browser Context Controller

* feat: Add packages to cluster metadata
feat: Change structure of Package

* 🐛 fix: format

* 🔨 feat: Add more functions wrapping around Modrinth

* 🔨 feat: Start working on browser frontend

* feat: BrowserPackage work + markdown support

* 🐛 fix: fmt

Signed-off-by: Pauline <git@ethanlibs.co>

* 🚧 wip: h

Signed-off-by: Pauline <git@ethanlibs.co>

* feat: Download button for packages for suitable clusters + tweaks

* 🚧 wip: markdown and stuff

Signed-off-by: Pauline <git@ethanlibs.co>

* 🐛 fix: some patches

Signed-off-by: Pauline <git@ethanlibs.co>

* 🐛 fix: remove react bloat

Signed-off-by: Pauline <git@ethanlibs.co>

* 🔨 chore(pkg/infer): try to reduce reused code

Signed-off-by: Pauline <git@ethanlibs.co>

* 🔨 chore: bump deps

Signed-off-by: Pauline <git@ethanlibs.co>

* 🐛 fix: markdown innerhtml

Signed-off-by: Pauline <git@ethanlibs.co>

* 🐛 fix: h

Signed-off-by: Pauline <git@ethanlibs.co>

* 🐛 fix: tweak

Signed-off-by: Pauline <git@ethanlibs.co>

* 🚧 wip: idk

Signed-off-by: Pauline <git@ethanlibs.co>

* fix: Gamemode compile error
chore: Add missing and remove unused libraries
feat: usePreventLeave hook
feat: ClusterOverview edit mode continues with navigation (assuming there was a navigation attempt) after pressing Yes or No for save

* feat: Add reload clusters button on home page

* feat: More information provided to BrowserPackage

* fix: Minor fixes (Navbar isActive, Markdown click prevention, Markdown list styling)

* 🐛 fix: h

Signed-off-by: Pauline <git@ethanlibs.co>

* 🐛 fix: highlighting

Signed-off-by: Pauline <git@ethanlibs.co>

* 🐛 fix: forge modern

Signed-off-by: Pauline <git@ethanlibs.co>

* 🔨 chore: bump deps

Signed-off-by: Pauline <git@ethanlibs.co>

* fix: Markdown-it issue + Added promptOpener hook for Markdown links

* feat: Made BrowserPackage sidebar sticky

* refactor: Package system (not yet in a working state)

* 🚧 wip: bump deps temp

Signed-off-by: Pauline <git@ethanlibs.co>

* feat: Added a package manager

* ✨ feature: start prisma work, but it doesnt break anything so we can keep it until its done. also fix some stuff

Signed-off-by: Pauline <git@ethanlibs.co>

* 🐛 fix: pushing

Signed-off-by: Pauline <git@ethanlibs.co>

* 🐛 fix: linting

Signed-off-by: Pauline <git@ethanlibs.co>

* ✨ feature: start ingress integration

Signed-off-by: Pauline <git@ethanlibs.co>

* feat: Package Manager should work now 👍

* fix: Package manager tracing spam

* feat: Added tauri commands for cluster packages
format: Auto formatted frontend + backend

* 🐛 fix: tmp

Signed-off-by: Pauline <git@ethanlibs.co>

* 🔨 chore: format

Signed-off-by: Pauline <git@ethanlibs.co>

* 🐛 fix: specta extension

Signed-off-by: Pauline <git@ethanlibs.co>

* 🐛 fix: github actions

Signed-off-by: Pauline <git@ethanlibs.co>

* 🐛 fix: alias

Signed-off-by: Pauline <git@ethanlibs.co>

* chore: Some tweaks and fixes

* chore: Rebase + gitignore bindings.ts

* chore: Remove bindings.ts

* 🐛 fix: migrate

Signed-off-by: Pauline <git@ethanlibs.co>

* 🚧 wip: not sure

Signed-off-by: Pauline <git@ethanlibs.co>

* 🐛 fix: finalize prisma init schema

Signed-off-by: Pauline <git@ethanlibs.co>

* 🔨 chore: format

Signed-off-by: Pauline <git@ethanlibs.co>

* 🐛 fix: bump deps

Signed-off-by: Pauline <git@ethanlibs.co>

* 🐛 fix: system deps

Signed-off-by: Pauline <git@ethanlibs.co>

* fix: solid-router navigation issue

* feat: Added new BrowserMain page

* feat: Tweaks + new package list view

* 🐛 fix: bump + format (todo: tauri2, etc)

Signed-off-by: Pauline <git@ethanlibs.co>

* 🐛 fix: devx (fixes #126)

Signed-off-by: Pauline <git@ethanlibs.co>

* 🔨 chore: migrate to consola

Signed-off-by: Pauline <git@ethanlibs.co>

---------

Signed-off-by: Pauline <git@ethanlibs.co>
Co-authored-by: LynithDev <61880709+LynithDev@users.noreply.github.com>
2024-08-31 16:23:24 -04:00
Pauline
d7faee2066
🔨 chore: bump deps
Signed-off-by: Pauline <git@ethanlibs.co>
2024-08-20 06:15:22 +02:00
Pauline
5194647dd1
🐛 fix: format
Signed-off-by: Pauline <git@ethanlibs.co>
2024-08-19 21:42:36 +02:00
Pauline
f46a63bb07
Merge branch 'main' of github.com:Polyfrost/OneLauncher 2024-08-19 21:40:25 +02:00
Pauline
b0e82cc8ec
🐛 fix: scrap old managed package api (generate_contexts was a bad idea lol), am going to work on feat/package_api
Signed-off-by: Pauline <git@ethanlibs.co>
2024-08-19 21:40:11 +02:00
LynithDev
0e438e5378 🐛 fix: Gamemode bindings 2024-08-19 10:47:39 +02:00
Pauline
85bbf0347c
🐛 fix: popups
Signed-off-by: Pauline <git@ethanlibs.co>
2024-08-19 09:34:56 +02:00
Pauline
fc91b43fc7
🐛 fix: deps
Signed-off-by: Pauline <git@ethanlibs.co>
2024-08-19 08:54:32 +02:00
Pauline
6e0ed8ee7a
✨ feature: fully implement gamemode and bump deps
Signed-off-by: Pauline <git@ethanlibs.co>
2024-08-19 08:52:44 +02:00
Pauline
a87dd17a6e
✨ feature(core): add gamemode support
Signed-off-by: Pauline <git@ethanlibs.co>
2024-08-18 18:54:02 +02:00
Pauline
3e5c29adfa
🔨 chore(todo): update list
Signed-off-by: Pauline <git@ethanlibs.co>
2024-08-18 09:32:27 +02:00
Pauline
755eb0b371
🐛 fix: h
Signed-off-by: Pauline <git@ethanlibs.co>
2024-08-18 08:39:41 +02:00
Pauline
62184abf1b
🐛 fix: format
Signed-off-by: Pauline <git@ethanlibs.co>
2024-08-17 21:44:21 +02:00
Pauline
9b57bc1f24
🐛 fix(actions): release building
Signed-off-by: Pauline <git@ethanlibs.co>
2024-08-17 21:36:02 +02:00
Pauline
986bf51e2e
🐛 fix(actions): use nightly for rustfmt
Signed-off-by: Pauline <git@ethanlibs.co>
2024-08-17 21:22:52 +02:00
Pauline
c820a57fc5
🐛 fix: error handling
Signed-off-by: Pauline <git@ethanlibs.co>
2024-08-17 18:58:05 +02:00
Pauline
27b776a09d
🐛 fix: format
Signed-off-by: Pauline <git@ethanlibs.co>
2024-08-17 18:37:58 +02:00
Pauline
39b37d1b37
Merge branch 'main' of github.com:Polyfrost/OneLauncher 2024-08-17 18:37:34 +02:00
Pauline
112b3303a0
✨ feature: normalize specta feature flag
Signed-off-by: Pauline <git@ethanlibs.co>
2024-08-17 17:36:40 +02:00
LynithDev
d61ef08441 fix: Deep link invalid configuration 2024-08-17 11:41:02 +02:00
Pauline
43f16ceecf
🔨 chore: merge
Signed-off-by: Pauline <git@ethanlibs.co>
2024-08-17 07:36:47 +02:00
Pauline
13c8f9135e
🚧 wip: hii
Signed-off-by: Pauline <git@ethanlibs.co>
2024-08-17 07:36:00 +02:00
LynithDev
baa43f16db chore: Attempt to make UI more consistent with OneConfig V1 Designs 2024-08-16 22:00:14 +02:00
LynithDev
908f2f1c8f chore: Add Discord client_id 2024-08-16 21:23:02 +02:00
LynithDev
6dcf123810 feat: ClusterGame supports stdout + stderr
feat: FormattedLog auto scrolling
refactor: Refactored some @ts-expect-error comments
2024-08-16 13:24:17 +02:00
LynithDev
08528ec7c1 feat: Better ClusterGame page + Added multi-cluster process support 2024-08-16 10:07:57 +02:00
Pauline
ca66cf5684
🐛 fix: h
Signed-off-by: Pauline <git@ethanlibs.co>
2024-08-15 23:18:27 +02:00
Pauline
ec7d19652a
🐛 fix: formatting
Signed-off-by: Pauline <git@ethanlibs.co>
2024-08-15 22:59:33 +02:00
Pauline
8ab0ea17cb
🔨 chore: some version bumps
Signed-off-by: Pauline <git@ethanlibs.co>
2024-08-15 22:39:12 +02:00
Pauline
c5e96afc4c
[no-ci]: add workflow_dispatch to build ci 2024-08-14 05:23:36 -04:00
LynithDev
59734faf9f fix: Attempt to fix Linux aarch64 cargo config 2024-08-14 10:26:06 +02:00
LynithDev
9e3e3817e5 chore: Lint frontend 2024-08-14 10:23:06 +02:00
LynithDev
7185361b65 chore: Merge main branch 2024-08-14 10:15:44 +02:00
LynithDev
6808c97b49 fix: Fix previous / cancel / backdrop ClusterCreationModal issues 2024-08-14 10:05:33 +02:00
Pauline
d8cfb11ed8
🐛 fix: lint unocss
Signed-off-by: Pauline <git@ethanlibs.co>
2024-08-11 19:04:20 +02:00
Pauline
020a74c843
🔨 chore: bump deps
Signed-off-by: Pauline <git@ethanlibs.co>
2024-08-11 04:08:32 +02:00
Pauline
f2710b3620
🔨 chore: bump deps
Signed-off-by: Pauline <git@ethanlibs.co>
2024-08-10 22:49:49 +02:00
LynithDev
fcb7df0430 refactor: ClusterCreationModal, prepare for multi stage onboarding and integrate with ModalController 2024-08-09 22:01:26 +02:00
LynithDev
7eb2f74297 fix: Always return a default user (if there are any added) 2024-08-08 22:05:36 +02:00
LynithDev
444061c0a7 feat: Cluster Worlds Page 2024-08-08 21:23:23 +02:00
LynithDev
dfb8edd069 fix: Force image rendering with GPU on WebKitGTK
fix: Pluralisation of function secondsToWords
feat: Screenshot list works (needs better functionality though)
2024-08-08 19:12:28 +02:00
LynithDev
f289a5c768 fix: MSA Webview works now + Fixed empty "pre" and "wrapper" commands causing errors 2024-08-07 21:30:29 +02:00
LynithDev
4787a571ef chore: Mess around with page animations + fix sidebar color + navbar account hover 2024-08-07 12:28:30 +02:00
LynithDev
a199d4a80b chore: Some small things 2024-08-07 11:52:12 +02:00
LynithDev
87a447ed9e fix: Settings Context usage 2024-08-07 10:57:32 +02:00
LynithDev
3f30a41c5a fix(settings): Reset button not showing properly + unify game settings across Cluster and Global launcher pages 2024-08-07 10:52:07 +02:00
Pauline
7ef1774767
🐛 fix: lint
Signed-off-by: Pauline <git@ethanlibs.co>
2024-08-07 09:14:30 +02:00
LynithDev
1b46c5da2e feat: Cluster + Global settings (with overrides!) 2024-08-06 21:57:05 +02:00
Pauline
574d783821
🐛 fix: prepare scripts for package api
Signed-off-by: Pauline <git@ethanlibs.co>
2024-08-06 06:44:22 +02:00
Pauline
aeefb368b6
🐛 fix: more work on scripts
Signed-off-by: Pauline <git@ethanlibs.co>
2024-08-05 08:25:12 +02:00
Pauline
8bf9a1bc2e
🐛 fix: yay
Signed-off-by: Pauline <git@ethanlibs.co>
2024-08-05 05:51:50 +02:00
Pauline
3e77988443
🐛 fix: praying
Signed-off-by: Pauline <git@ethanlibs.co>
2024-08-05 05:46:11 +02:00
Pauline
13580a0a2c
🐛 fix: ci/cd
Signed-off-by: Pauline <git@ethanlibs.co>
2024-08-05 03:42:06 +02:00
Pauline
880727c501
🐛 fix: ci/cd
Signed-off-by: Pauline <git@ethanlibs.co>
2024-08-05 03:11:12 +02:00
Pauline
1cfe254677
🔨 chore: bump deps
Signed-off-by: Pauline <git@ethanlibs.co>
2024-08-05 02:56:48 +02:00
LynithDev
676c16f14a refactor: ClusterCreationModal (still awaits refactoring, Contexts shouldn't be used like this) 2024-08-04 15:57:58 +02:00
LynithDev
91aa2e03a7 refactor: Modal stacking system + transitions :D 2024-08-04 15:43:09 +02:00
LynithDev
d55e97957b fix: Account popup separator not showing with <=1 accounts 2024-08-04 13:10:56 +02:00
LynithDev
00ca74bf4f fix: joinPath import from pathe 2024-08-04 13:09:43 +02:00
Pauline
fc276f0b22
Delete .github/workflows/stale.yml 2024-08-04 02:06:44 -04:00
Pauline
cd12dfa2f0
🐛 fix: commit lockfile 2024-08-04 05:56:23 +02:00
Pauline
f6b2bb557f
🚧 wip: gh actions users when gh consequence 2024-08-04 05:53:25 +02:00
Pauline
2c0c2282eb
🚧 wip: fshjdjga 2024-08-04 05:52:02 +02:00
Pauline
6bd733bf04
🚧 wip: fshjdjga 2024-08-04 05:50:35 +02:00
Pauline
d8d68828e1
🚧 wip: fix 2024-08-04 05:47:22 +02:00
Pauline
290fa1a531
🚧 wip: fix 2024-08-04 05:45:53 +02:00
Pauline
63c7dfa1fc
🚧 wip: fix 2024-08-04 05:45:08 +02:00
Pauline
58f6e999ba
🚧 wip(actions): some github actions wip 2024-08-04 05:31:41 +02:00
Pauline
6aca816ec6
🐛 fix: some regressions 2024-08-03 17:34:41 +02:00
Pauline
7f9c0b4d05
🔨 chore: merge with main 2024-08-03 17:30:45 +02:00
Pauline
d56720a003
✨ feature: update to tauri@rc.0 2024-08-03 17:24:30 +02:00
Pauline
ee70456c48
🔨 chore: prepare for tauri rc 2024-08-03 08:39:12 +02:00
LynithDev
568e2c438a feat: ClusterGame semi-works 2024-08-02 17:35:27 +02:00
LynithDev
199d7ed22c fix: ClusterSettings + Launcher Disable Animations flicker 2024-08-02 13:24:00 +02:00
LynithDev
a0d64eaffa feat: Banner background inherits cluster icon 2024-08-02 13:04:59 +02:00
LynithDev
32d27bc9fb feat: Revamp Cluster overview page, make it less empty 2024-08-02 12:30:27 +02:00
Pauline
7009f5db08
🐛 fix: bump deps 2024-08-02 10:02:20 +02:00
Pauline
250d059e91
🐛 fix: compilation issues with tauri specta 2024-08-02 06:01:56 +02:00
Pauline
d4781db671
Merge branch 'main' of github.com:Polyfrost/OneLauncher 2024-08-02 05:29:51 +02:00
Pauline
6dd7cf7f7a
🐛 fix: bump deps 2024-08-02 05:29:41 +02:00
LynithDev
710ea9d35f feat: Some banner functionality 2024-08-01 22:09:15 +02:00
LynithDev
99a370cba8 chore: Attempt to get Drag 'n drop working 2024-08-01 18:50:17 +02:00
LynithDev
5dcc24e682 chore: Start work on drag n drop 2024-08-01 17:49:54 +02:00
Pauline
2ec1a04c8b
🔨 chore: bump deps + format 2024-08-01 05:41:58 +02:00
LynithDev
f5f48e349f feat: Screenshots + refactor: Commands 2024-07-31 17:48:35 +02:00
LynithDev
f760a807db chore: Replace my log helpers with pauline's 2024-07-31 17:12:11 +02:00
Pauline
989d08bb2a
fix: root package.json directory, fixes #59 2024-07-31 02:46:30 -04:00
LynithDev
9b8d2b3f6e feat: Log text coloring + formatting 2024-07-30 17:58:56 +02:00
LynithDev
a09f6eb483 feat: Cluster log page 2024-07-30 14:12:58 +02:00
Pauline
e4edbb85fa
📦 deps: bump 2024-07-30 10:09:56 +02:00
Pauline
82c02fbdd7
📝 doc: docum ent atinon 2024-07-30 10:04:59 +02:00
Pauline
5c62379ad3
🔨 chore: format 2024-07-29 22:11:56 +02:00
Pauline
ef729dd8e5
🚧 wip: it builds somehow 2024-07-29 22:03:45 +02:00
LynithDev
db678b9a9d feat: Cluster editing, took too long 2024-07-29 20:27:12 +02:00
Pauline
f6b5c2f570
✨ feature: metadata normalization 2024-07-29 19:47:53 +02:00
LynithDev
4b2f7b2016 chore: Forgot to remove this console.log :D 2024-07-29 15:04:05 +02:00
LynithDev
02fe6fb9e1 fix: Account delete modal 2024-07-29 15:03:46 +02:00
LynithDev
af1ea68a32 feat: Common delete modal + delete modal when removing accounts + remove default user if the user being removed is the default 2024-07-29 14:30:24 +02:00
LynithDev
ca636e65c0 feat: Switch to tauri_specta events and statics 2024-07-29 13:51:53 +02:00
LynithDev
95f6bf9d87 feat: Try to integrate MSA webview with frontend 2024-07-28 22:03:11 +02:00
Pauline
d44b1dc35c
🔨 chore: format 2024-07-28 21:55:49 +02:00
LynithDev
dab365f048 feat: Global Account Controller implented with Context's 2024-07-28 21:52:18 +02:00
Pauline
6d9f145ada
✨ feature: add webview-based auth 2024-07-28 21:05:31 +02:00
Pauline
a73f4b0397
🔨 chore: explain mc auth flow 2024-07-28 20:39:33 +02:00
LynithDev
4539cb5388 feat: Account page fixes + ready to integrate with auth 2024-07-28 20:24:19 +02:00
LynithDev
05e6db5b1d feat: MultiProvider for Contexts 2024-07-28 14:50:45 +02:00
LynithDev
54f6989b6e fix: RwLock deadlock after game exits 2024-07-28 12:30:45 +02:00
Pauline
246b5a3d6a
🐛 fix: standardize rust constants bcs i was bored 2024-07-27 20:19:33 +02:00
Pauline
c853a66969
🐛 fix: revert macro remova 2024-07-27 17:26:11 +02:00
Pauline
bcb2d55b95
🐛 fix: fix client id 2024-07-27 02:52:25 +02:00
LynithDev
36aa0b658f fix: Pauline's refactoring 2024-07-26 12:16:59 +02:00
LynithDev
4481688ef7 fix: Pauline's script refactoring 2024-07-26 11:58:55 +02:00
Pauline
81698aafd7
📝 doc: fix contributing.md to restructuring 2024-07-26 08:08:53 +02:00
Pauline
40e5e1f6ba
♻️ refactor: i promise this is the last big refactor 2024-07-26 04:03:22 +02:00
Pauline
9a4422fe8b
🔨 chore: she lint on my ecmascript til i format 2024-07-26 01:36:59 +02:00
Pauline
646d8dc14c
🔨 chore: format 2024-07-25 13:22:13 +02:00
Pauline
7b486a59a7
🐛 fix: bump deps and finish backport 2024-07-25 11:23:27 +02:00
Pauline
3bff4fbab1
✨ feature: backport non-prisma stuff from prisma branch 2024-07-25 11:20:39 +02:00
LynithDev
2f80658db9 feat: Somewhat working Browser UI 2024-07-24 15:57:33 +02:00
LynithDev
170762fc7b feat: basic modrinth api implementation 2024-07-24 14:13:12 +02:00
LynithDev
56df69f911 feat: Small changes for showcase video 2024-07-24 11:11:56 +02:00
Pauline
a5ff33a912
🐛 fix: preformance improvements 2024-07-23 22:28:23 +02:00
LynithDev
8f928d6305 feat: Added more commands + made sure minecraft launches 2024-07-23 20:56:40 +02:00
LynithDev
316f1fc48a feat: Create clusters! 2024-07-23 20:19:43 +02:00
Pauline
a98eda07a7
🐛 fix: java info metadata 2024-07-23 19:31:11 +02:00
LynithDev
5e6c628f55 feat: Less empty home page + work on integrating backend with frontend 2024-07-23 18:43:24 +02:00
Pauline
0dcc7083a9
🐛 fix: fix mc auth 2024-07-23 18:29:26 +02:00
Pauline
f12eaf5bd8
🐛 fix: idk anymore 2024-07-23 17:09:30 +02:00
LynithDev
5ff48cace8 feat: add more checks and update data in context 2024-07-22 16:58:57 +02:00
LynithDev
8664610560 feat: Cluster creation modal improvements 2024-07-22 16:13:36 +02:00
LynithDev
a95513b6b9 chore: Remove warnings 2024-07-22 10:54:53 +02:00
LynithDev
ff981d1f63 feat: Fix canGoForward state for ClusterCreationModal not being set properly 2024-07-22 10:52:50 +02:00
LynithDev
201ff3fc9d feat: Keep state between ClusterModal step changes 2024-07-21 22:11:09 +02:00
LynithDev
650d2e85eb feat: Disable buttons if the setup isn't ready 2024-07-21 19:17:18 +02:00
LynithDev
0c7115a374 feat: Cluster Modal fully implemented 2024-07-20 21:59:29 +02:00
LynithDev
9c7b895d4c feat: begin work on Cluster creation modal 2024-07-19 22:12:11 +02:00
Pauline
060f1faa10
🐛 fix: fix some code 2024-07-17 02:41:47 +02:00
LynithDev
cf85f478f0 fix: Loader metadata fetching + Fetch metadata when previous attemps in cache failed 2024-07-16 11:47:34 +02:00
Pauline
7d507067df
🔨 chore: h 2024-07-15 04:07:52 +02:00
Pauline
3522142a2c
✨ feature(core): start package api 2024-07-15 03:35:57 +02:00
Pauline
72c2f42c16
🔨 chore: format 2024-07-14 21:42:03 +02:00
Pauline
3d638e28ce
🐛 fix: no more errors yippe 2024-07-14 20:42:29 +02:00
Pauline
b997039619
🔨 chore: ok it finally works 2024-07-14 20:40:06 +02:00
Pauline
00d9d755ad
🐛 fix: this might work 2024-07-14 20:35:10 +02:00
Pauline
d909ead669
🐛 fix: fix the thing 2024-07-14 18:42:59 +02:00
Pauline
046118c6af
🐛 fix: idk 2024-07-14 18:17:26 +02:00
LynithDev
b801963c42 fix: Tauri version conflicts 2024-07-14 13:23:36 +02:00
LynithDev
6999837c9a
Update pnpm version 2024-07-14 12:47:49 +02:00
LynithDev
2d271a3ddc Revert "fix: Dependency version mismatch (tauri-build 18 -> 19)"
This reverts commit 594d5f97f2.
2024-07-14 10:31:01 +02:00
LynithDev
594d5f97f2 fix: Dependency version mismatch (tauri-build 18 -> 19) 2024-07-14 10:30:08 +02:00
Pauline
9c725550a0
🐛 fix: ugh 2024-07-14 08:50:12 +02:00
Pauline
818fee2592
🐛 fix: backport lyniths auth stuff 2024-07-13 15:58:59 +02:00
Pauline
f53d3c3479
🔨 chore(build): reorganize stuff 2024-07-13 02:24:38 +02:00
Pauline
d48bf079b6
🔨 chore(deps): reorganize cargo.toml stuff 2024-07-12 20:33:01 +02:00
Pauline
cb6cf30b74
🔨 chore(deps): she bump on my tauri til i linker error 2024-07-12 20:30:31 +02:00
Pauline
fe660d2e0d
🔨 chore(deps): bump npm tauri 2024-07-12 19:59:26 +02:00
LynithDev
be867a9c8d feat: Cluster Settings page 2024-07-08 13:47:24 +02:00
LynithDev
09b73dc55e feat: Added mod cards component 2024-07-08 12:15:49 +02:00
LynithDev
4754374476 fix: Remove ability to drag links 2024-06-23 22:03:59 +02:00
LynithDev
32741d0c3e feat: Browser mod cards 2024-06-23 22:02:00 +02:00
LynithDev
7432f7cba9 fix: HMR when saving index.ts file 2024-06-20 20:07:45 +02:00
LynithDev
82830d02ee feat: Program info 2024-06-20 15:50:50 +02:00
LynithDev
402d6d6041 feat: More settings 2024-06-20 10:22:09 +02:00
LynithDev
ff989fba41 feat: Pattern Matching TextField + added Minecraft settings page 2024-06-18 22:01:48 +02:00
LynithDev
af177152f0 feat: Implement settings 2024-06-18 18:50:16 +02:00
LynithDev
bf4511e4e3 merge: Merge dependency updates and lint 2024-06-18 16:54:45 +02:00
Pauline
9336505ef6
🔨 chore(deps): bump npm tauri 2024-06-18 00:46:30 +02:00
Pauline
4c04c07c61
🐛 fix(linting): more fotmatting 2024-06-18 00:18:19 +02:00
Pauline
d548024991
🐛 fix(gitignore): ignore .idea everything pls 2024-06-18 00:06:55 +02:00
Pauline
309110d3e5
🔨 chore(deps): bump npm dependencies 2024-06-18 00:05:43 +02:00
LynithDev
19ade5dc81 feat: SettingsContext + Temporary fix for failing to fetch manifest 2024-06-16 21:54:27 +02:00
LynithDev
9cd0cebd06 fix: Tooltip appearing when hovering over the tooltip, not the child 2024-06-16 21:05:45 +02:00
LynithDev
1bdebb0f94 feat: Accounts page 2024-06-16 19:41:17 +02:00
LynithDev
60425c1acd feat: ClusterContext Provider (I forgot Contexts existed 💀) 2024-06-16 14:48:03 +02:00
LynithDev
e8b2328812 chore: Remove unused imports 2024-06-16 13:40:24 +02:00
LynithDev
2c9de654a2 fix: Dropdown overflow 2024-06-16 13:07:45 +02:00
LynithDev
43621b4038 feat: Dropdown don't use children length * 2 2024-06-16 13:04:18 +02:00
LynithDev
a17bcbb471 feat: Dropdown + Text Field modifications. Started work on Browser 2024-06-16 12:47:51 +02:00
LynithDev
88709c6218 feat: Sidebar optional mods section 2024-06-13 20:05:52 +02:00
LynithDev
576c8f0d22 chore: Do some work on reimplementing the Rust side into the frontend side 2024-06-13 19:26:19 +02:00
Pauline
e2495c78d4
🐛 fix(core): bug fixes 2024-06-06 18:31:14 +02:00
Pauline
e32d2e817a
🔨 chore(deps): bump dependencies 2024-06-04 02:20:18 +02:00
LynithDev
963762669c feat: Specta works! 2024-06-02 22:01:15 +02:00
LynithDev
366637c25f fix: snake_case for PackageFile, refactored Modal into 2 components 2024-06-02 21:02:28 +02:00
LynithDev
718f1c00de refactor: Add specta::Type everywhere 2024-06-02 16:33:03 +02:00
LynithDev
ae49d82d9e refactor: Remove all traces of the old bridge + types 2024-06-02 15:21:23 +02:00
LynithDev
8c7a2f4b9b chore: git ignore bindings.ts 2024-06-02 14:54:12 +02:00
LynithDev
f11b620531 chore: Update gitignore and init state before running app 2024-06-02 14:50:26 +02:00
LynithDev
ac6a6de8e3 fix: Rust analyzer not working properly 2024-06-02 13:58:44 +02:00
LynithDev
9fb55128e2 git stashing this, added specta 2024-06-02 11:23:38 +02:00
LynithDev
327d895258 chore(notifications): Notifications need to be rewritten .-. 2024-05-31 21:53:41 +02:00
LynithDev
f2a1ae682b feat(notifications): Overlay component 2024-05-26 22:03:14 +02:00
LynithDev
b2ac76f417 temp(notifications): Temporarily mock a notification system 2024-05-24 21:58:00 +02:00
LynithDev
78cea2ce5b refactor(mods): Search + fixed a bug when trying to resort 2024-05-24 21:22:25 +02:00
LynithDev
429fc1fc53 feat: Experiment with search - should be refactored later on to provide a unified search across the entire app 2024-05-18 22:04:21 +02:00
LynithDev
9f9be695ad fix: Window maximize fix + slight popup tweaks 2024-05-18 15:48:21 +02:00
LynithDev
5b25e50ab8 refactor(popup): Refactored Modals + Popups and made them respect animation + transition durations 2024-05-18 12:25:14 +02:00
LynithDev
ea715f15bb feat: Close confirmation dialog setting 2024-05-17 22:08:44 +02:00
LynithDev
46a5606ade feat: Launcher reduced motion 2024-05-17 21:20:15 +02:00
LynithDev
dfb4c09be7 chore: Did some work on settings page 2024-05-17 18:36:35 +02:00
LynithDev
397b853475 refactor: Cluster + Settings pages to use ScrollableContainer component 2024-05-17 09:25:32 +02:00
LynithDev
99cecbdb43 feat: Add window frame back button 2024-05-16 20:02:23 +02:00
LynithDev
e8fa840aff feat: Added ScrollableContainer for pages with a sidebarg 2024-05-16 19:45:13 +02:00
LynithDev
a970ed27a5 feat: Start work on settings 2024-05-16 12:29:36 +02:00
LynithDev
0843162d69 Merge remote-tracking branch 'origin/paulie/refactor' into lynith
chore: Merge paulines branch
2024-05-16 11:42:25 +02:00
LynithDev
f6166f2ced screenshots 2024-05-15 11:11:30 +02:00
Pauline
269c5e592c
🔨 chore: bump deps 2024-05-14 05:30:59 +02:00
LynithDev
d4f1b8c70e feat: Cluster mods list 2024-05-13 21:25:45 +02:00
Pauline
9e941d8a6b
🚧 wip: wip because i wanna work on kotlin stuf 2024-05-12 21:16:42 +02:00
LynithDev
9105590e27 feat: Sidebar adjustments 2024-05-12 12:31:49 +02:00
LynithDev
4fef2e432b feat: Changed Page Animation transition properties 2024-05-12 11:19:49 +02:00
LynithDev
823c49f6c9 Merge remote-tracking branch 'origin/paulie/refactor' into lynith
Merge paulie/refactor specta + rspc integration
2024-05-12 10:22:49 +02:00
Pauline
3aa9fcd1b5
✨ feature(core): start specta + pcr + rspc integration 2024-05-12 05:53:13 +02:00
LynithDev
7c8e5139aa feat: ClusterLogs + ClusterOverview 2024-05-11 22:06:36 +02:00
LynithDev
f823d610d0 fix: Context Menu positioning 2024-05-11 21:18:25 +02:00
Pauline
4f4af3f3e2
🔨 chore(core): apply cargo clippy fixes 2024-05-11 06:37:44 +02:00
Pauline
1a71848be0
🔨 chore(core): apply cargo clippy fixes 2024-05-11 06:31:44 +02:00
Pauline
e5a0bc6026
🔨 chore: hh 2024-05-11 06:21:13 +02:00
Pauline
101a92bccc
🔨 chore: hh 2024-05-11 06:19:44 +02:00
Pauline
a67796bd6d
✨ feature(core): improve logging system, fix minecraft auth timestamps 2024-05-11 05:39:45 +02:00
Pauline
7b3a89e055
🔨 chore(core): remove unneeded Uuid borrows 2024-05-11 04:43:51 +02:00
Pauline
35ef702f5c
🔨 chore(deps): bump deps & format 2024-05-11 04:40:10 +02:00
LynithDev
8e5d7e77fc merge(notifications): Merged notification work 2024-05-10 19:52:27 +02:00
Pauline
3c0c402410
✅ test(feature): add vitest + jsdom testing 2024-05-09 07:18:50 +02:00
Pauline
0a46344f0c
🔨 chore(deps): bump dependencies and versions 2024-05-09 06:43:19 +02:00
LynithDev
a7aaf37d79 fix: UnoCSS issues 2024-05-08 22:01:23 +02:00
LynithDev
4588be4f24 fix: All Rust-side errors and added TODOs where needed 2024-05-08 20:50:05 +02:00
LynithDev
f60bccf13b fix(package.json): tauri dev and build script corrected 2024-05-08 20:28:43 +02:00
Pauline
a2ce2ea7dd
🔨 chore(deps): bump dependencies and versions 2024-05-07 05:52:47 +02:00
Pauline
2632a8226a
🔨 chore(license): revert licensing change back to GPL 3.0-only 2024-04-29 20:32:31 -04:00
Pauline
8c49553473
🚧 wip: it launches (sort of), temp launching/cluster utils, format, relicense, refactor backend into polyfrost/infrastructure 2024-04-28 23:35:42 -04:00
Pauline
e306d45e0a
🚧 wip(core): preliminary package api 2024-04-27 18:11:52 -04:00
Pauline
4d4fe6b19e
🔨 chore(deps): bump dependencies and versions 2024-04-27 14:00:15 -04:00
Pauline
0729e8fad3
🔨 chore(format): format and fix eslint deps 2024-04-27 02:31:03 -04:00
Pauline
fb41ab1529
🔨 chore(deps): bump dependencies and pnpm version 2024-04-27 02:27:59 -04:00
Pauline
d2c31a1ca0
🚧 wip(everything): this is super unfinished and might have some licensing issues still i dont think so tho 2024-04-25 23:03:41 -04:00
LynithDev
ee991b464f oops, accidently removed these 2 crates 2024-04-01 17:23:42 +02:00
LynithDev
e3de6705d8
Merge pull request #1 from Polyfrost/lynith
Support for Vanilla Minecraft + slight refactoring
2024-04-01 17:19:37 +02:00
LynithDev
abb5857af2
Merge branch 'master' into lynith 2024-04-01 17:19:20 +02:00
LynithDev
ffbab6cb5f fix: Changed way of downloading and using assets in pre 1.6 versions 2024-04-01 17:12:58 +02:00
LynithDev
940e41e291 feat: Downloading assets 2024-04-01 16:21:28 +02:00
LynithDev
025ffa03e5 refactor: Change cluster file structure + use ClusterWithManifest over singular Cluster and Manifest types 2024-04-01 15:30:00 +02:00
LynithDev
e12045697e feat: Settings manager 2024-03-31 22:00:42 +02:00
LynithDev
fbab841bbd feat: Pass stdout + stderr + pid callbacks to frontend 2024-03-31 15:24:41 +02:00
LynithDev
b77f48ed90 feat: it launches! 2024-03-31 13:25:05 +02:00
LynithDev
f000ba9bdf feat: Lots of changes 2024-03-30 19:40:59 +01:00
LynithDev
fb14f96eea refactor: Lots of stuff refactored 2024-03-28 22:08:04 +01:00
LynithDev
0dd1365fa4 feat: Rebrand 'Instance' to 'Cluster' 2024-03-28 21:12:32 +01:00
LynithDev
e607dfe6b6 feat: Fixes 2024-03-28 20:32:16 +01:00
Pauline
ec8c20dc29
start work on interpulse 2024-03-27 11:03:21 +01:00
Pauline
022936d35c
hhhhh 2024-03-27 05:40:20 +01:00
LynithDev
2af86b5fda fix: Minecraft manifest deserializing 2024-03-26 20:01:46 +01:00
Pauline
b0b1aa48e9
bump deps 2024-03-26 03:51:03 +01:00
LynithDev
750ecd414e chore: Start work on creating instances (MC Manifest argument deserializing needs to be fixed) 2024-03-24 22:09:04 +01:00
LynithDev
ec0590bc49 feat: Instance loading and rendering on the Launcher 2024-03-24 15:54:42 +01:00
Pauline
525f38fe5c
bump tauri plugin 2024-03-24 14:03:14 +01:00
LynithDev
1cf4852a78 feat: Get instance by ID 2024-03-24 12:18:26 +01:00
LynithDev
3fecf109c4 refactor: Split logic into its own crate 2024-03-24 11:58:54 +01:00
LynithDev
b9894ec736 refactor: Add macros and add helper functions 2024-03-23 22:04:45 +01:00
LynithDev
a75ce01c72 fix: Caching version list 2024-03-23 17:51:26 +01:00
LynithDev
ffab61ebf0 refactor: Way cleaner version + instance management system 2024-03-23 16:39:44 +01:00
Pauline
629a9c2a44
switch to gh tauri plugins to prepare for custom uri 2024-03-23 11:47:41 +01:00
LynithDev
c28cfc02ea fix: thiserror errors 2024-03-23 10:54:05 +01:00
Pauline
7d6fbf8fd8
format the rust :3 2024-03-23 09:42:40 +01:00
Pauline
b2f459478a
error handling part 2 2024-03-23 09:32:28 +01:00
Pauline
a8e6c2449b
error handling part 1.5 2024-03-23 05:11:54 +01:00
Pauline
e661921539
error handling part 1 2024-03-23 04:15:11 +01:00
Pauline
15f3021701
add prisma as recommended extension 2024-03-22 22:29:56 +01:00
Pauline
ce9c70a273
todo 2024-03-22 22:27:01 +01:00
LynithDev
45ab7048b6 feat: Add downloading libraries 2024-03-22 22:00:27 +01:00
LynithDev
ad61294642 epic context menu 2024-03-21 20:02:32 +01:00
LynithDev
d300366018 i have no idea what ive done 2024-03-21 14:52:08 +01:00
LynithDev
c5baf69850 fix: Vite port + change initial window height to 802 2024-03-21 11:30:09 +01:00
LynithDev
b4bd190b55 Instance pages 2024-03-21 11:23:09 +01:00
Pauline
31c71deb59
taking mc break 2024-03-20 22:12:03 +01:00
Pauline
65e38fb585
probably shouldve testd first 2024-03-20 20:50:54 +01:00
Pauline
7349482c20
format and update schema migrations 2024-03-20 20:48:45 +01:00
Pauline
e00be5d48b
chore(deps): bump vite and solid 2024-03-20 19:54:33 +01:00
LynithDev
ed3896f11d oops, forgot about the database 2024-03-20 19:48:48 +01:00
LynithDev
e6527f1102 refactor: Prisma 2024-03-20 19:33:47 +01:00
Pauline
bfb998f013
DSFHSGDSHRILFDHILAEASHSILDFHLICVDHsrhsdbasadaaaa 2024-03-19 20:00:39 +01:00
LynithDev
2b039cdb04 chore: Add more functions in trait 2024-03-19 18:42:03 +01:00
LynithDev
337c4878fd feat: Finally, a proper client state management 2024-03-19 17:39:22 +01:00
LynithDev
af9c628612 feat: Downloading JREs 2024-03-17 22:03:08 +01:00
LynithDev
087e87963e chore: Start work on JRE downloading 2024-03-17 19:45:34 +01:00
LynithDev
71c98255a1 feat: Instance card dragging 2024-03-17 15:12:50 +01:00
LynithDev
0f5b035e17 feat: Tweaks + PlayerHead component 2024-03-17 13:34:29 +01:00
LynithDev
f699125c7c feat: Added custom scrollbar 2024-03-17 12:51:37 +01:00
LynithDev
00bc9361fb feat: Overflow fixes - I. Hate. Webdev. 2024-03-17 11:48:24 +01:00
LynithDev
31e4efadc3 feat: Scrolling pages 2024-03-17 10:55:11 +01:00
Pauline
86a185a460
i messed up the merge a bit but it should be okay 2024-03-17 07:41:04 +01:00
Pauline
f9ac49223d
chore(devx): migrate eslint config 2024-03-17 07:35:04 +01:00
Pauline
0743d4479a
chore(devx): devx tweaks 2024-03-17 07:31:51 +01:00
LynithDev
8bb0be03fd feat: Instances 2024-03-16 22:10:11 +01:00
LynithDev
af3bc0f1ee feat: Accounts and popup 2024-03-16 20:55:49 +01:00
LynithDev
6de4e42eb3 feat: TextField 2024-03-16 17:36:36 +01:00
LynithDev
0474d49981 feat: Home page hero section 2024-03-16 16:24:16 +01:00
LynithDev
de2b4a2ef5 feat: Page transitions 2024-03-16 15:41:38 +01:00
LynithDev
59f4b2ef16 refactor: Remove Rust window handler and migrate to the JS ones 2024-03-16 13:16:29 +01:00
LynithDev
197b52a168 feat: App quit confirmation dialog 2024-03-16 13:15:01 +01:00
LynithDev
b2226a07e7 feat: Close confirmation dialog 2024-03-15 22:06:36 +01:00
LynithDev
6fdea8c033 feat: Epic Modal + Window titlebar 2024-03-15 21:15:03 +01:00
LynithDev
07a73c6270 feat: Poppin font + Login Microsoft bridge function 2024-03-15 19:16:15 +01:00
LynithDev
6e11ab1052 version manifest structs 2024-03-15 15:16:15 +01:00
LynithDev
9573df0f91 feat: Authentication (Waiting for approval for MSA Application) 2024-03-15 14:21:02 +01:00
LynithDev
c54bb6e6cb first commit 2024-03-14 22:07:54 +01:00
474 changed files with 56446 additions and 18738 deletions

View file

@ -1,3 +1,23 @@
[target.x86_64-unknown-linux-gnu]
linker = "clang"
rustflags = ["-C", "link-arg=-fuse-ld=lld"]
[target.aarch64-apple-darwin]
linker = "aarch64-apple-darwin-clang"
rustflags = [
"-C", "link-arg=-fuse-ld=lld",
"-C", "link-arg=-isysroot",
"-C", "link-arg=/home/oiupoyt/.local/share/MacOSX11.3.sdk",
]
[target.x86_64-apple-darwin]
linker = "x86_64-apple-darwin-clang"
rustflags = [
"-C", "link-arg=-fuse-ld=lld",
"-C", "link-arg=-isysroot",
"-C", "link-arg=/home/oiupoyt/.local/share/MacOSX11.3.sdk",
]
# Linux and Macos by default have 8MB of stack storage. # Linux and Macos by default have 8MB of stack storage.
# Windows has 1MB, it isn't enough, so it's changed to 8MB also. # Windows has 1MB, it isn't enough, so it's changed to 8MB also.
[target.x86_64-pc-windows-msvc] [target.x86_64-pc-windows-msvc]

View file

@ -0,0 +1,122 @@
name: Release Binaries (Forgejo)
on:
push:
tags:
- 'v*'
workflow_dispatch:
inputs:
tag:
description: 'Release Tag (e.g. v2.8.0-c1)'
required: false
default: 'v2.8.0-c1'
type: string
permissions:
contents: write
env:
SQLX_OFFLINE: true
SQLX_OFFLINE_DIR: packages/oneclient_db/.sqlx
jobs:
build-linux:
runs-on: ubuntu-22.04
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
with:
targets: x86_64-unknown-linux-gnu
- name: Rust Cache
uses: Swatinem/rust-cache@v2
with:
key: x86_64-unknown-linux-gnu
cache-all-crates: true
- name: Install Linux build dependencies
run: |
sudo apt-get update
sudo apt-get install -y \
libwebkit2gtk-4.1-dev \
build-essential \
curl \
wget \
file \
libxdo-dev \
libssl-dev \
libayatana-appindicator3-dev \
librsvg2-dev \
libfontconfig1-dev \
libfreetype6-dev \
libgl1-mesa-dev \
libvulkan-dev \
libasound2-dev
- name: Install cargo-packager
run: cargo install cargo-packager --locked || true
- name: Build Release Binary
run: cargo build --release --bin oneclient_app --target x86_64-unknown-linux-gnu
- name: Package Application
run: cargo packager --release --target x86_64-unknown-linux-gnu --formats deb,appimage
continue-on-error: true
- name: Generate RPM Package (Linux)
run: |
cargo install cargo-generate-rpm --locked || true
cargo generate-rpm --target x86_64-unknown-linux-gnu -p oneclient_app || true
- name: Determine Release Tag
id: release_tag
run: |
if [[ "${{ github.ref_name }}" =~ ^v ]]; then
echo "tag=${{ github.ref_name }}" >> $GITHUB_OUTPUT
elif [ -n "${{ inputs.tag }}" ]; then
echo "tag=${{ inputs.tag }}" >> $GITHUB_OUTPUT
else
echo "tag=v2.8.0-c1" >> $GITHUB_OUTPUT
fi
- name: Publish Assets to Forgejo Release
run: |
TAG="${{ steps.release_tag.outputs.tag }}"
API_URL="${{ github.server_url }}/api/v1/repos/${{ github.repository }}"
echo "Connecting to $API_URL for release $TAG"
# 1. Fetch or create release on Forgejo
RELEASE_ID=$(curl -s -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" "$API_URL/releases/tags/$TAG" | jq -r '.id // empty')
if [ -z "$RELEASE_ID" ] || [ "$RELEASE_ID" = "null" ]; then
RESP=$(curl -s -X POST "$API_URL/releases" \
-H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \
-H "Content-Type: application/json" \
-d "{\"tag_name\":\"$TAG\",\"name\":\"OneLauncher-Cracked $TAG\",\"draft\":false,\"prerelease\":false,\"target_commitish\":\"main\"}")
RELEASE_ID=$(echo "$RESP" | jq -r '.id // empty')
fi
echo "Target Release ID: $RELEASE_ID"
# 2. Upload generated packages
shopt -s nullglob
FILES=(
target/x86_64-unknown-linux-gnu/release/*.deb
target/x86_64-unknown-linux-gnu/release/*.AppImage
target/x86_64-unknown-linux-gnu/generate-rpm/*.rpm
target/release/*.deb
target/release/*.AppImage
target/generate-rpm/*.rpm
)
for f in "${FILES[@]}"; do
if [ -f "$f" ]; then
fname=$(basename "$f")
echo "Uploading asset $fname..."
curl -s -X POST "$API_URL/releases/$RELEASE_ID/assets?name=$fname" \
-H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \
-F "attachment=@$f"
fi
done
echo "Release upload completed!"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

After

Width:  |  Height:  |  Size: 21 KiB

35
.github/workflows/changelog_publish.yml vendored Normal file
View file

@ -0,0 +1,35 @@
name: Publish Changelog
on:
release:
types: [published, edited]
workflow_dispatch:
concurrency:
group: changelog-publish
cancel-in-progress: true
jobs:
publish:
runs-on: ubuntu-latest
permissions:
contents: write
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_REPO: ${{ github.repository }}
steps:
# The launcher reads releases/latest/download/changelog.json, so the
# snapshot of the last 50 release bodies always lives on the latest release.
- name: Build changelog.json from release bodies
run: |
gh api "repos/$GH_REPO/releases?per_page=50" --jq '[.[]
| select(.draft | not)
| select(.tag_name | startswith("oneclient-"))
| {
version: (.tag_name | ltrimstr("oneclient-")),
body: ((.body // "") | sub("^Automated release for OneClient [^\n]*\\s*"; ""))
}]' > changelog.json
jq -r '.[].version' changelog.json
- name: Upload to latest release
run: gh release upload "$(gh release view --json tagName -q .tagName)" changelog.json --clobber

23
.github/workflows/changeset_check.yml vendored Normal file
View file

@ -0,0 +1,23 @@
name: Changeset Check
on:
pull_request:
types: [opened, synchronize, reopened, labeled, unlabeled]
jobs:
changeset:
if: ${{ !contains(github.event.pull_request.labels.*.name, 'no changeset') }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
with:
fetch-depth: 0
- name: Require a change file
env:
BASE_REF: ${{ github.base_ref }}
run: |
if ! git diff --name-only --diff-filter=A "origin/$BASE_REF...HEAD" -- '.changeset/*.md' | grep -q .; then
echo "::error::Add a change file with \`knope document-change\`, or label the PR 'no changeset' if it isn't user-facing."
exit 1
fi

View file

@ -85,6 +85,7 @@ jobs:
libfontconfig-dev libfreetype-dev \ libfontconfig-dev libfreetype-dev \
libgl1-mesa-dev libx11-dev libxcursor-dev libxi-dev libxrandr-dev \ libgl1-mesa-dev libx11-dev libxcursor-dev libxi-dev libxrandr-dev \
libxkbcommon-dev libxkbcommon-x11-dev libwayland-dev libdbus-1-dev \ libxkbcommon-dev libxkbcommon-x11-dev libwayland-dev libdbus-1-dev \
libgtk-3-dev libayatana-appindicator3-dev \
libfuse2 libfuse2
- name: Install cargo-packager - name: Install cargo-packager
@ -263,21 +264,61 @@ jobs:
for f in "${{ steps.outdir.outputs.dir }}"/*.dmg \ for f in "${{ steps.outdir.outputs.dir }}"/*.dmg \
"${{ steps.outdir.outputs.dir }}"/*.deb \ "${{ steps.outdir.outputs.dir }}"/*.deb \
"${{ steps.outdir.outputs.dir }}"/*.rpm \ "${{ steps.outdir.outputs.dir }}"/*.rpm \
"${{ steps.outdir.outputs.dir }}"/*.AppImage \ "${{ steps.outdir.outputs.dir }}"/*.AppImage; do
"${{ steps.outdir.outputs.dir }}"/*-setup.exe; do
[ -e "$f" ] || continue [ -e "$f" ] || continue
cp "$f" dist/ cp "$f" dist/
done done
# Raw executable, renamed per platform so the unzipped uploads don't collide.
bin="${{ steps.outdir.outputs.dir }}/oneclient_app"
ext=""
[ -e "$bin.exe" ] && ext=".exe"
cp "$bin$ext" "dist/oneclient_app-${{ matrix.os_key }}-${{ matrix.arch }}$ext"
if [ -z "$(ls -A dist)" ]; then if [ -z "$(ls -A dist)" ]; then
echo "::error::No installers found in ${{ steps.outdir.outputs.dir }}" echo "::error::No installers found in ${{ steps.outdir.outputs.dir }}"
exit 1 exit 1
fi fi
ls -la dist ls -la dist
- name: Upload unsigned installers - name: Upload unsigned executable
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with: with:
name: oneclient-nightly-${{ matrix.os_key }}-${{ matrix.arch }} path: dist/oneclient_app-${{ matrix.os_key }}-${{ matrix.arch }}*
path: dist/* archive: false
if-no-files-found: error
retention-days: 14
- name: (macOS) Upload unsigned dmg
if: matrix.os_key == 'darwin'
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
path: dist/*.dmg
archive: false
if-no-files-found: error
retention-days: 14
- name: (Linux) Upload unsigned deb
if: matrix.os_key == 'linux'
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
path: dist/*.deb
archive: false
if-no-files-found: error
retention-days: 14
- name: (Linux) Upload unsigned rpm
if: matrix.os_key == 'linux'
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
path: dist/*.rpm
archive: false
if-no-files-found: error
retention-days: 14
- name: (Linux) Upload unsigned AppImage
if: matrix.os_key == 'linux'
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
path: dist/*.AppImage
archive: false
if-no-files-found: error if-no-files-found: error
retention-days: 14 retention-days: 14

View file

@ -3,17 +3,6 @@ name: OneClient Release Build
on: on:
workflow_dispatch: workflow_dispatch:
inputs:
bump:
description: 'Bump the workspace version in Cargo.toml before releasing'
type: choice
required: false
default: none
options:
- none
- patch
- minor
- major
# NOTE: CARGO_PACKAGER_SIGN_PRIVATE_KEY* are deliberately NOT declared at # NOTE: CARGO_PACKAGER_SIGN_PRIVATE_KEY* are deliberately NOT declared at
# workflow level. Swatinem/rust-cache folds every `CARGO*`-prefixed environment # workflow level. Swatinem/rust-cache folds every `CARGO*`-prefixed environment
@ -37,18 +26,25 @@ jobs:
sha: ${{ steps.version.outputs.sha }} sha: ${{ steps.version.outputs.sha }}
steps: steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
with:
# Knope finds the commit (and so the PR author) that added each change file
fetch-depth: 0
- name: Setup Rust - name: Setup Rust
if: inputs.bump != 'none'
uses: ./.github/actions/setup-rust uses: ./.github/actions/setup-rust
with: with:
restore-cache: false restore-cache: false
- name: Resolve version, bump if requested, refuse to clobber a release - name: Install Knope
uses: ./.github/actions/install-cargo-tool
with:
tool: knope
version: 0.23.0
- name: Consume changesets
id: version id: version
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
BUMP: ${{ inputs.bump }}
shell: bash shell: bash
run: | run: |
set -euo pipefail set -euo pipefail
@ -57,63 +53,29 @@ jobs:
sed -n -E '/^\[workspace\.package\]/,/^\[/ s/^version[[:space:]]*=[[:space:]]*"([^"]+)".*/\1/p' Cargo.toml | head -n1 sed -n -E '/^\[workspace\.package\]/,/^\[/ s/^version[[:space:]]*=[[:space:]]*"([^"]+)".*/\1/p' Cargo.toml | head -n1
} }
# No change files means a re-run of a release whose version commit
# already landed, so carry on with the current version.
RELEASED=0
if compgen -G ".changeset/*.md" > /dev/null; then
knope prepare-release
cargo update --workspace --offline || cargo update --workspace
RELEASED=1
fi
VERSION="$(read_version)" VERSION="$(read_version)"
if [ -z "$VERSION" ]; then if [ -z "$VERSION" ]; then
echo "::error::Could not read version from [workspace.package] in Cargo.toml" echo "::error::Could not read version from [workspace.package] in Cargo.toml"
exit 1 exit 1
fi fi
echo "Current version: $VERSION"
if [ "$BUMP" != "none" ]; then
if ! [[ "$VERSION" =~ ^([0-9]+)\.([0-9]+)\.([0-9]+)$ ]]; then
echo "::error::Version '$VERSION' is not plain semver major.minor.patch; cannot bump automatically."
exit 1
fi
MAJOR="${BASH_REMATCH[1]}"; MINOR="${BASH_REMATCH[2]}"; PATCH="${BASH_REMATCH[3]}"
case "$BUMP" in
major) MAJOR=$((MAJOR + 1)); MINOR=0; PATCH=0 ;;
minor) MINOR=$((MINOR + 1)); PATCH=0 ;;
patch) PATCH=$((PATCH + 1)) ;;
esac
NEW_VERSION="$MAJOR.$MINOR.$PATCH"
echo "Bumping ($BUMP): $VERSION -> $NEW_VERSION"
# Only rewrite the `version` key inside [workspace.package]; the
# dependency pins further down the file must not be touched.
NEW_VERSION="$NEW_VERSION" python3 - <<'PY'
import os, re
new = os.environ["NEW_VERSION"]
src = open("Cargo.toml").read()
start = src.index("[workspace.package]")
end = src.find("\n[", start + 1)
end = len(src) if end == -1 else end
section, count = re.subn(
r'(?m)^version\s*=\s*"[^"]+"', f'version = "{new}"', src[start:end], count=1
)
assert count == 1, "no version key found in [workspace.package]"
open("Cargo.toml", "w").write(src[:start] + section + src[end:])
PY
# Workspace members inherit `version.workspace = true`, so only the
# lockfile's own entries for those members need refreshing.
cargo update --workspace --offline || cargo update --workspace
ACTUAL="$(read_version)"
if [ "$ACTUAL" != "$NEW_VERSION" ]; then
echo "::error::Version rewrite failed (Cargo.toml still reports '$ACTUAL')"
exit 1
fi
VERSION="$NEW_VERSION"
fi
TAG="oneclient-$VERSION" TAG="oneclient-$VERSION"
echo "Releasing OneClient $VERSION"
# Abort rather than clobber: a published release (or an existing tag, # Abort rather than clobber: a published release (or an existing tag,
# which only appears once a release is published) means this version # which only appears once a release is published) means this version
# already shipped. A *draft* is a previous failed/partial run of this # already shipped. A *draft* is a previous failed/partial run of this
# same workflow, so reuse it. # same workflow, so reuse it.
if git ls-remote --exit-code --tags origin "refs/tags/$TAG" >/dev/null 2>&1; then if git ls-remote --exit-code --tags origin "refs/tags/$TAG" >/dev/null 2>&1; then
echo "::error::Tag $TAG already exists on origin - OneClient $VERSION has already been released. Re-run with a bump, or bump the version in Cargo.toml." echo "::error::Tag $TAG already exists on origin - OneClient $VERSION has already been released. Add a change file with \`knope document-change\` first."
exit 1 exit 1
fi fi
@ -123,17 +85,17 @@ jobs:
RELEASE_EXISTS=1 RELEASE_EXISTS=1
fi fi
if [ "$RELEASE_EXISTS" = "1" ] && [ "$IS_DRAFT" != "true" ]; then if [ "$RELEASE_EXISTS" = "1" ] && [ "$IS_DRAFT" != "true" ]; then
echo "::error::Release $TAG already exists and is published - refusing to overwrite OneClient $VERSION. Re-run with a bump, or bump the version in Cargo.toml." echo "::error::Release $TAG already exists and is published - refusing to overwrite OneClient $VERSION. Add a change file with \`knope document-change\` first."
exit 1 exit 1
fi fi
# Commit the bump only after the guards pass, so a rejected release # Commit only after the guards pass, so a rejected release does not
# does not leave a stray version bump on the branch. # leave a stray version bump on the branch.
if [ "$BUMP" != "none" ]; then if [ "$RELEASED" = "1" ]; then
git config user.name "github-actions[bot]" git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com" git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git add Cargo.toml Cargo.lock git add -A Cargo.toml Cargo.lock .changeset CHANGELOG.md
git commit -m "chore: bump to $VERSION" git commit -m "chore: release $VERSION"
git push origin "HEAD:${GITHUB_REF_NAME}" git push origin "HEAD:${GITHUB_REF_NAME}"
fi fi
@ -143,11 +105,18 @@ jobs:
echo "Reusing existing draft release $TAG" echo "Reusing existing draft release $TAG"
gh release edit "$TAG" --target "$SHA" gh release edit "$TAG" --target "$SHA"
else else
# The body is what the launcher's changelog page shows
awk -v heading="## $VERSION " '/^## /{p = index($0, heading) == 1; next} p' \
CHANGELOG.md > notes.md 2>/dev/null || true
if ! grep -q '[^[:space:]]' notes.md; then
echo "::error::No changelog entry for $VERSION. Add a change file with \`knope document-change\` first."
exit 1
fi
gh release create "$TAG" \ gh release create "$TAG" \
--draft \ --draft \
--target "$SHA" \ --target "$SHA" \
--title "OneClient $VERSION" \ --title "OneClient $VERSION" \
--notes "Automated release for OneClient $VERSION." --notes-file notes.md
fi fi
echo "version=$VERSION" >> "$GITHUB_OUTPUT" echo "version=$VERSION" >> "$GITHUB_OUTPUT"
@ -189,7 +158,7 @@ jobs:
runs-on: ${{ matrix.platform }} runs-on: ${{ matrix.platform }}
steps: steps:
# Pin to the commit `prepare` resolved - with `bump` that is the version # Pin to the commit `prepare` resolved - after a release that is the version
# commit it just pushed, which github.sha would not point at. # commit it just pushed, which github.sha would not point at.
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
with: with:
@ -216,6 +185,7 @@ jobs:
libfontconfig-dev libfreetype-dev \ libfontconfig-dev libfreetype-dev \
libgl1-mesa-dev libx11-dev libxcursor-dev libxi-dev libxrandr-dev \ libgl1-mesa-dev libx11-dev libxcursor-dev libxi-dev libxrandr-dev \
libxkbcommon-dev libxkbcommon-x11-dev libwayland-dev libdbus-1-dev \ libxkbcommon-dev libxkbcommon-x11-dev libwayland-dev libdbus-1-dev \
libgtk-3-dev libayatana-appindicator3-dev \
libfuse2 libfuse2
- name: Install cargo-packager - name: Install cargo-packager

View file

@ -7,9 +7,9 @@ on:
workflow_dispatch: workflow_dispatch:
inputs: inputs:
tag: tag:
description: 'Release Tag (e.g. v2.4.2)' description: 'Release Tag (e.g. v2.5.1)'
required: false required: false
default: 'v2.4.2-c1' default: 'v2.5.1-c3'
type: string type: string
permissions: permissions:
@ -138,7 +138,7 @@ jobs:
elif [ -n "${{ inputs.tag }}" ]; then elif [ -n "${{ inputs.tag }}" ]; then
echo "tag=${{ inputs.tag }}" >> $GITHUB_OUTPUT echo "tag=${{ inputs.tag }}" >> $GITHUB_OUTPUT
else else
echo "tag=v2.4.2-c1" >> $GITHUB_OUTPUT echo "tag=v2.5.1-c3" >> $GITHUB_OUTPUT
fi fi
- name: Clean up previous release if exists - name: Clean up previous release if exists
@ -155,12 +155,15 @@ jobs:
name: "OneLauncher-Cracked ${{ steps.release_tag.outputs.tag }}" name: "OneLauncher-Cracked ${{ steps.release_tag.outputs.tag }}"
body: | body: |
### What's New in ${{ steps.release_tag.outputs.tag }} ### What's New in ${{ steps.release_tag.outputs.tag }}
- **Rebased onto Official Upstream 2.4.2**: Includes all upstream 2.4.0, 2.4.1, and 2.4.2 updates (Linux discrete GPU offloading, shared/cluster mod symlinks, Microsoft Java provider, UI tooltips, analytics graphs, Ornithe loader, bundle fixes). - **Complete Multiplayer Server Skin Support**: Native integration with client-side `OfflineSkins Reloaded` for Fabric 1.21.11, allowing skins to render directly on cracked multiplayer servers (e.g. `alwto.net`) when the server transmits empty player profiles.
- **Guaranteed Offline Game Launch**: Completely eliminated the upstream check in `account_for_launch` that blocked offline accounts from launching without Microsoft accounts. Offline accounts can launch directly with 0 Microsoft accounts required. - **Proper Isolated Offline Skin System (Authlib-Injector)**: Embedded, zero-network-dependency Authlib-Injector session provider with RSA signature verification. Your skin is attached strictly and exclusively to your player profile, completely eliminating the bug where other players or NPCs on offline servers would receive your skin.
- **Custom Offline Skins & Targeted Multiplayer Injection**: In-launcher 3D skin changer, per-account Java UUID hash targeting preventing multiplayer skin cloning, and automatic CustomSkinLoader config generation. - **Dynamic Version Compatibility**: Automatically gates offline skin mod integration strictly to matching versions, while Authlib-Injector handles clean, zero-dependency offline profile signing across all Minecraft versions.
- **Offline / Cracked Accounts**: Full offline account support in onboarding and settings with inline username editing. - **Purged Incompatible Mods**: Completely cleaned and prevented CustomSkinLoader crashes on modern Fabric Loader (0.17+) and Sponge Mixin 0.8.7.
- **Integrated Auto-Updater**: Native background update checks and direct GitHub Releases binary downloads. - **Vanilla & Multi-server Integrity**: Other offline players remain authentic vanilla Steve/Alex, while online players on hybrid servers display their legitimate Mojang skins via transparent caching session proxying.
- **UI Bug Fixes**: Fixed filter popover blur/text ghosting in the package manager. - **Automatic Legacy Cleanup**: Restores pristine `client.jar` and removes stale skin resource packs from `options.txt` on launch.
- **Guaranteed Offline Game Launch**: Direct offline launching with 0 Microsoft accounts required.
- **In-Launcher 3D Skin Changer**: Full 3D skin preview, slim/classic arm model toggle, local file import, and online skin downloads.
- **Rebased on Upstream 2.5.1**: All upstream 2.5.1 features, hook error reporting, and metadata self-healing.
draft: false draft: false
prerelease: false prerelease: false
generate_release_notes: false generate_release_notes: false

1
.gitignore vendored
View file

@ -1,4 +1,5 @@
/target /target
/dist
.DS_Store .DS_Store
/.idea /.idea
OneLauncher.iml OneLauncher.iml

View file

@ -0,0 +1,12 @@
{
"db_name": "SQLite",
"query": "\n INSERT OR IGNORE INTO cluster_bundle_choices (cluster_id, bundle_name, accepted)\n SELECT ?, bundle_name, accepted\n FROM cluster_bundle_choices\n WHERE cluster_id = ?\n ",
"describe": {
"columns": [],
"parameters": {
"Right": 2
},
"nullable": []
},
"hash": "001a994c740f7a015152319eb16fc113954af25ebd2a44d55e95833adda53268"
}

View file

@ -0,0 +1,170 @@
{
"db_name": "SQLite",
"query": "\n\t\tSELECT name, java_path, resolution, force_fullscreen, mem_max, launch_args, game_args, launch_env,\n\t\t hook_pre, hook_wrapper, hook_post, os_extra, browser_update_mode\n\t\tFROM setting_profiles\n\t\tORDER BY name ASC\n\t\t",
"describe": {
"columns": [
{
"name": "name",
"ordinal": 0,
"type_info": "Text",
"origin": {
"Table": {
"table": "setting_profiles",
"name": "name"
}
}
},
{
"name": "java_path",
"ordinal": 1,
"type_info": "Text",
"origin": {
"Table": {
"table": "setting_profiles",
"name": "java_path"
}
}
},
{
"name": "resolution",
"ordinal": 2,
"type_info": "Text",
"origin": {
"Table": {
"table": "setting_profiles",
"name": "resolution"
}
}
},
{
"name": "force_fullscreen",
"ordinal": 3,
"type_info": "Integer",
"origin": {
"Table": {
"table": "setting_profiles",
"name": "force_fullscreen"
}
}
},
{
"name": "mem_max",
"ordinal": 4,
"type_info": "Integer",
"origin": {
"Table": {
"table": "setting_profiles",
"name": "mem_max"
}
}
},
{
"name": "launch_args",
"ordinal": 5,
"type_info": "Text",
"origin": {
"Table": {
"table": "setting_profiles",
"name": "launch_args"
}
}
},
{
"name": "game_args",
"ordinal": 6,
"type_info": "Text",
"origin": {
"Table": {
"table": "setting_profiles",
"name": "game_args"
}
}
},
{
"name": "launch_env",
"ordinal": 7,
"type_info": "Text",
"origin": {
"Table": {
"table": "setting_profiles",
"name": "launch_env"
}
}
},
{
"name": "hook_pre",
"ordinal": 8,
"type_info": "Text",
"origin": {
"Table": {
"table": "setting_profiles",
"name": "hook_pre"
}
}
},
{
"name": "hook_wrapper",
"ordinal": 9,
"type_info": "Text",
"origin": {
"Table": {
"table": "setting_profiles",
"name": "hook_wrapper"
}
}
},
{
"name": "hook_post",
"ordinal": 10,
"type_info": "Text",
"origin": {
"Table": {
"table": "setting_profiles",
"name": "hook_post"
}
}
},
{
"name": "os_extra",
"ordinal": 11,
"type_info": "Text",
"origin": {
"Table": {
"table": "setting_profiles",
"name": "os_extra"
}
}
},
{
"name": "browser_update_mode",
"ordinal": 12,
"type_info": "Text",
"origin": {
"Table": {
"table": "setting_profiles",
"name": "browser_update_mode"
}
}
}
],
"parameters": {
"Right": 0
},
"nullable": [
false,
true,
true,
true,
true,
true,
true,
true,
true,
true,
true,
true,
true
]
},
"hash": "01b2ef24541af92505a3c2bc14037a61470a393dffa06da8bebfba98138709d0"
}

View file

@ -0,0 +1,110 @@
{
"db_name": "SQLite",
"query": "\n\t\tSELECT\n\t\t\ttarget_cluster_id, provider, project_id, content_type,\n\t\t\tsource_hash, display_name, expires_at, enabled\n\t\tFROM release_migration_waitlist\n\t\t",
"describe": {
"columns": [
{
"name": "target_cluster_id",
"ordinal": 0,
"type_info": "Integer",
"origin": {
"Table": {
"table": "release_migration_waitlist",
"name": "target_cluster_id"
}
}
},
{
"name": "provider",
"ordinal": 1,
"type_info": "Integer",
"origin": {
"Table": {
"table": "release_migration_waitlist",
"name": "provider"
}
}
},
{
"name": "project_id",
"ordinal": 2,
"type_info": "Text",
"origin": {
"Table": {
"table": "release_migration_waitlist",
"name": "project_id"
}
}
},
{
"name": "content_type",
"ordinal": 3,
"type_info": "Integer",
"origin": {
"Table": {
"table": "release_migration_waitlist",
"name": "content_type"
}
}
},
{
"name": "source_hash",
"ordinal": 4,
"type_info": "Text",
"origin": {
"Table": {
"table": "release_migration_waitlist",
"name": "source_hash"
}
}
},
{
"name": "display_name",
"ordinal": 5,
"type_info": "Text",
"origin": {
"Table": {
"table": "release_migration_waitlist",
"name": "display_name"
}
}
},
{
"name": "expires_at",
"ordinal": 6,
"type_info": "Text",
"origin": {
"Table": {
"table": "release_migration_waitlist",
"name": "expires_at"
}
}
},
{
"name": "enabled",
"ordinal": 7,
"type_info": "Integer",
"origin": {
"Table": {
"table": "release_migration_waitlist",
"name": "enabled"
}
}
}
],
"parameters": {
"Right": 0
},
"nullable": [
false,
false,
false,
false,
false,
false,
false,
false
]
},
"hash": "15dc690e91fe0bc7e1932a5051f8edd855deaeb31e52209ae436680962dea954"
}

View file

@ -0,0 +1,38 @@
{
"db_name": "SQLite",
"query": "\n SELECT bundle_name, accepted\n FROM cluster_bundle_choices\n WHERE cluster_id = ?\n ",
"describe": {
"columns": [
{
"name": "bundle_name",
"ordinal": 0,
"type_info": "Text",
"origin": {
"Table": {
"table": "cluster_bundle_choices",
"name": "bundle_name"
}
}
},
{
"name": "accepted",
"ordinal": 1,
"type_info": "Integer",
"origin": {
"Table": {
"table": "cluster_bundle_choices",
"name": "accepted"
}
}
}
],
"parameters": {
"Right": 1
},
"nullable": [
false,
false
]
},
"hash": "171c6771d06656c2fe375f6a0da889f828f400e950f103f0ed44f5f98a48dc50"
}

View file

@ -0,0 +1,12 @@
{
"db_name": "SQLite",
"query": "DELETE FROM release_migration_waitlist WHERE target_cluster_id = ? AND provider = ? AND project_id = ?",
"describe": {
"columns": [],
"parameters": {
"Right": 3
},
"nullable": []
},
"hash": "1e43f93b56bb495e13826c734fe5fb64515cf1d7c28bc6a1bf1fb1645fdba2a1"
}

View file

@ -0,0 +1,218 @@
{
"db_name": "SQLite",
"query": "\n\t\tUPDATE clusters\n\t\tSET overall_played = COALESCE(overall_played, 0) + ?,\n\t\t last_played = ?\n\t\tWHERE id = ?\n\t\tRETURNING\n\t\t\tid, name, folder_name, setting_profile_name, mc_version, mc_loader,\n\t\t\tstage, mc_loader_version, created_at, last_played, overall_played, linked_modpack_hash,\n\t\t\tkind, user_created, description, tags, cover_path\n\t\t",
"describe": {
"columns": [
{
"name": "id",
"ordinal": 0,
"type_info": "Integer",
"origin": {
"Table": {
"table": "clusters",
"name": "id"
}
}
},
{
"name": "name",
"ordinal": 1,
"type_info": "Text",
"origin": {
"Table": {
"table": "clusters",
"name": "name"
}
}
},
{
"name": "folder_name",
"ordinal": 2,
"type_info": "Text",
"origin": {
"Table": {
"table": "clusters",
"name": "folder_name"
}
}
},
{
"name": "setting_profile_name",
"ordinal": 3,
"type_info": "Text",
"origin": {
"Table": {
"table": "clusters",
"name": "setting_profile_name"
}
}
},
{
"name": "mc_version",
"ordinal": 4,
"type_info": "Text",
"origin": {
"Table": {
"table": "clusters",
"name": "mc_version"
}
}
},
{
"name": "mc_loader",
"ordinal": 5,
"type_info": "Integer",
"origin": {
"Table": {
"table": "clusters",
"name": "mc_loader"
}
}
},
{
"name": "stage",
"ordinal": 6,
"type_info": "Integer",
"origin": {
"Table": {
"table": "clusters",
"name": "stage"
}
}
},
{
"name": "mc_loader_version",
"ordinal": 7,
"type_info": "Text",
"origin": {
"Table": {
"table": "clusters",
"name": "mc_loader_version"
}
}
},
{
"name": "created_at",
"ordinal": 8,
"type_info": "Text",
"origin": {
"Table": {
"table": "clusters",
"name": "created_at"
}
}
},
{
"name": "last_played",
"ordinal": 9,
"type_info": "Text",
"origin": {
"Table": {
"table": "clusters",
"name": "last_played"
}
}
},
{
"name": "overall_played",
"ordinal": 10,
"type_info": "Integer",
"origin": {
"Table": {
"table": "clusters",
"name": "overall_played"
}
}
},
{
"name": "linked_modpack_hash",
"ordinal": 11,
"type_info": "Text",
"origin": {
"Table": {
"table": "clusters",
"name": "linked_modpack_hash"
}
}
},
{
"name": "kind",
"ordinal": 12,
"type_info": "Integer",
"origin": {
"Table": {
"table": "clusters",
"name": "kind"
}
}
},
{
"name": "user_created",
"ordinal": 13,
"type_info": "Integer",
"origin": {
"Table": {
"table": "clusters",
"name": "user_created"
}
}
},
{
"name": "description",
"ordinal": 14,
"type_info": "Text",
"origin": {
"Table": {
"table": "clusters",
"name": "description"
}
}
},
{
"name": "tags",
"ordinal": 15,
"type_info": "Text",
"origin": {
"Table": {
"table": "clusters",
"name": "tags"
}
}
},
{
"name": "cover_path",
"ordinal": 16,
"type_info": "Text",
"origin": {
"Table": {
"table": "clusters",
"name": "cover_path"
}
}
}
],
"parameters": {
"Right": 3
},
"nullable": [
false,
false,
false,
true,
false,
false,
false,
true,
true,
true,
true,
true,
false,
false,
true,
false,
true
]
},
"hash": "4c6d40e97401e1321abccd7f413b49dda16a5bdf29eb90459ac140d8208db479"
}

View file

@ -0,0 +1,218 @@
{
"db_name": "SQLite",
"query": "\n\t\tINSERT INTO clusters (\n\t\t\tname, folder_name, mc_version, mc_loader, mc_loader_version,\n\t\t\tsetting_profile_name, stage, created_at,\n\t\t\tkind, user_created, description, tags, cover_path\n\t\t)\n\t\tVALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\n\t\tRETURNING\n\t\t\tid, name, folder_name, setting_profile_name, mc_version, mc_loader,\n\t\t\tstage, mc_loader_version, created_at, last_played, overall_played, linked_modpack_hash,\n\t\t\tkind, user_created, description, tags, cover_path\n\t\t",
"describe": {
"columns": [
{
"name": "id",
"ordinal": 0,
"type_info": "Integer",
"origin": {
"Table": {
"table": "clusters",
"name": "id"
}
}
},
{
"name": "name",
"ordinal": 1,
"type_info": "Text",
"origin": {
"Table": {
"table": "clusters",
"name": "name"
}
}
},
{
"name": "folder_name",
"ordinal": 2,
"type_info": "Text",
"origin": {
"Table": {
"table": "clusters",
"name": "folder_name"
}
}
},
{
"name": "setting_profile_name",
"ordinal": 3,
"type_info": "Text",
"origin": {
"Table": {
"table": "clusters",
"name": "setting_profile_name"
}
}
},
{
"name": "mc_version",
"ordinal": 4,
"type_info": "Text",
"origin": {
"Table": {
"table": "clusters",
"name": "mc_version"
}
}
},
{
"name": "mc_loader",
"ordinal": 5,
"type_info": "Integer",
"origin": {
"Table": {
"table": "clusters",
"name": "mc_loader"
}
}
},
{
"name": "stage",
"ordinal": 6,
"type_info": "Integer",
"origin": {
"Table": {
"table": "clusters",
"name": "stage"
}
}
},
{
"name": "mc_loader_version",
"ordinal": 7,
"type_info": "Text",
"origin": {
"Table": {
"table": "clusters",
"name": "mc_loader_version"
}
}
},
{
"name": "created_at",
"ordinal": 8,
"type_info": "Text",
"origin": {
"Table": {
"table": "clusters",
"name": "created_at"
}
}
},
{
"name": "last_played",
"ordinal": 9,
"type_info": "Text",
"origin": {
"Table": {
"table": "clusters",
"name": "last_played"
}
}
},
{
"name": "overall_played",
"ordinal": 10,
"type_info": "Integer",
"origin": {
"Table": {
"table": "clusters",
"name": "overall_played"
}
}
},
{
"name": "linked_modpack_hash",
"ordinal": 11,
"type_info": "Text",
"origin": {
"Table": {
"table": "clusters",
"name": "linked_modpack_hash"
}
}
},
{
"name": "kind",
"ordinal": 12,
"type_info": "Integer",
"origin": {
"Table": {
"table": "clusters",
"name": "kind"
}
}
},
{
"name": "user_created",
"ordinal": 13,
"type_info": "Integer",
"origin": {
"Table": {
"table": "clusters",
"name": "user_created"
}
}
},
{
"name": "description",
"ordinal": 14,
"type_info": "Text",
"origin": {
"Table": {
"table": "clusters",
"name": "description"
}
}
},
{
"name": "tags",
"ordinal": 15,
"type_info": "Text",
"origin": {
"Table": {
"table": "clusters",
"name": "tags"
}
}
},
{
"name": "cover_path",
"ordinal": 16,
"type_info": "Text",
"origin": {
"Table": {
"table": "clusters",
"name": "cover_path"
}
}
}
],
"parameters": {
"Right": 13
},
"nullable": [
false,
false,
false,
false,
false,
false,
false,
true,
true,
true,
true,
true,
false,
false,
true,
false,
true
]
},
"hash": "54dea3f4093ceee9dcd5f0ae9bf072ec3615954b60bffa33bff7fd3f9bf14210"
}

View file

@ -0,0 +1,170 @@
{
"db_name": "SQLite",
"query": "\n\t\tINSERT INTO setting_profiles (\n\t\t\tname, java_path, resolution, force_fullscreen, mem_max, launch_args, game_args, launch_env,\n\t\t\thook_pre, hook_wrapper, hook_post, os_extra, browser_update_mode\n\t\t)\n\t\tVALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\n\t\tON CONFLICT(name) DO UPDATE SET\n\t\t\tjava_path = excluded.java_path,\n\t\t\tresolution = excluded.resolution,\n\t\t\tforce_fullscreen = excluded.force_fullscreen,\n\t\t\tmem_max = excluded.mem_max,\n\t\t\tlaunch_args = excluded.launch_args,\n\t\t\tgame_args = excluded.game_args,\n\t\t\tlaunch_env = excluded.launch_env,\n\t\t\thook_pre = excluded.hook_pre,\n\t\t\thook_wrapper = excluded.hook_wrapper,\n\t\t\thook_post = excluded.hook_post,\n\t\t\tos_extra = excluded.os_extra,\n\t\t\tbrowser_update_mode = excluded.browser_update_mode\n\t\tRETURNING name, java_path, resolution, force_fullscreen, mem_max, launch_args, game_args, launch_env,\n hook_pre, hook_wrapper, hook_post, os_extra, browser_update_mode\n\t\t",
"describe": {
"columns": [
{
"name": "name",
"ordinal": 0,
"type_info": "Text",
"origin": {
"Table": {
"table": "setting_profiles",
"name": "name"
}
}
},
{
"name": "java_path",
"ordinal": 1,
"type_info": "Text",
"origin": {
"Table": {
"table": "setting_profiles",
"name": "java_path"
}
}
},
{
"name": "resolution",
"ordinal": 2,
"type_info": "Text",
"origin": {
"Table": {
"table": "setting_profiles",
"name": "resolution"
}
}
},
{
"name": "force_fullscreen",
"ordinal": 3,
"type_info": "Integer",
"origin": {
"Table": {
"table": "setting_profiles",
"name": "force_fullscreen"
}
}
},
{
"name": "mem_max",
"ordinal": 4,
"type_info": "Integer",
"origin": {
"Table": {
"table": "setting_profiles",
"name": "mem_max"
}
}
},
{
"name": "launch_args",
"ordinal": 5,
"type_info": "Text",
"origin": {
"Table": {
"table": "setting_profiles",
"name": "launch_args"
}
}
},
{
"name": "game_args",
"ordinal": 6,
"type_info": "Text",
"origin": {
"Table": {
"table": "setting_profiles",
"name": "game_args"
}
}
},
{
"name": "launch_env",
"ordinal": 7,
"type_info": "Text",
"origin": {
"Table": {
"table": "setting_profiles",
"name": "launch_env"
}
}
},
{
"name": "hook_pre",
"ordinal": 8,
"type_info": "Text",
"origin": {
"Table": {
"table": "setting_profiles",
"name": "hook_pre"
}
}
},
{
"name": "hook_wrapper",
"ordinal": 9,
"type_info": "Text",
"origin": {
"Table": {
"table": "setting_profiles",
"name": "hook_wrapper"
}
}
},
{
"name": "hook_post",
"ordinal": 10,
"type_info": "Text",
"origin": {
"Table": {
"table": "setting_profiles",
"name": "hook_post"
}
}
},
{
"name": "os_extra",
"ordinal": 11,
"type_info": "Text",
"origin": {
"Table": {
"table": "setting_profiles",
"name": "os_extra"
}
}
},
{
"name": "browser_update_mode",
"ordinal": 12,
"type_info": "Text",
"origin": {
"Table": {
"table": "setting_profiles",
"name": "browser_update_mode"
}
}
}
],
"parameters": {
"Right": 13
},
"nullable": [
false,
false,
true,
true,
true,
true,
true,
true,
true,
true,
true,
true,
true
]
},
"hash": "598a34a0eb0f4d497cfd9bd77696310d1088e8baec677e07843a21ce6978652e"
}

View file

@ -0,0 +1,12 @@
{
"db_name": "SQLite",
"query": "\n INSERT OR IGNORE INTO cluster_bundle_type_opt_outs (cluster_id, bundle_name, content_type)\n VALUES (?, ?, ?)\n ",
"describe": {
"columns": [],
"parameters": {
"Right": 3
},
"nullable": []
},
"hash": "59a29c97c422e8721c29b9517ae834ea97776c57bb129a9d67007cdd979ae841"
}

View file

@ -0,0 +1,218 @@
{
"db_name": "SQLite",
"query": "\n\t\tSELECT\n\t\t\tid, name, folder_name, setting_profile_name, mc_version, mc_loader,\n\t\t\tstage, mc_loader_version, created_at, last_played, overall_played, linked_modpack_hash,\n\t\t\tkind, user_created, description, tags, cover_path\n\t\tFROM clusters\n\t\tWHERE folder_name = ?\n\t\t",
"describe": {
"columns": [
{
"name": "id",
"ordinal": 0,
"type_info": "Integer",
"origin": {
"Table": {
"table": "clusters",
"name": "id"
}
}
},
{
"name": "name",
"ordinal": 1,
"type_info": "Text",
"origin": {
"Table": {
"table": "clusters",
"name": "name"
}
}
},
{
"name": "folder_name",
"ordinal": 2,
"type_info": "Text",
"origin": {
"Table": {
"table": "clusters",
"name": "folder_name"
}
}
},
{
"name": "setting_profile_name",
"ordinal": 3,
"type_info": "Text",
"origin": {
"Table": {
"table": "clusters",
"name": "setting_profile_name"
}
}
},
{
"name": "mc_version",
"ordinal": 4,
"type_info": "Text",
"origin": {
"Table": {
"table": "clusters",
"name": "mc_version"
}
}
},
{
"name": "mc_loader",
"ordinal": 5,
"type_info": "Integer",
"origin": {
"Table": {
"table": "clusters",
"name": "mc_loader"
}
}
},
{
"name": "stage",
"ordinal": 6,
"type_info": "Integer",
"origin": {
"Table": {
"table": "clusters",
"name": "stage"
}
}
},
{
"name": "mc_loader_version",
"ordinal": 7,
"type_info": "Text",
"origin": {
"Table": {
"table": "clusters",
"name": "mc_loader_version"
}
}
},
{
"name": "created_at",
"ordinal": 8,
"type_info": "Text",
"origin": {
"Table": {
"table": "clusters",
"name": "created_at"
}
}
},
{
"name": "last_played",
"ordinal": 9,
"type_info": "Text",
"origin": {
"Table": {
"table": "clusters",
"name": "last_played"
}
}
},
{
"name": "overall_played",
"ordinal": 10,
"type_info": "Integer",
"origin": {
"Table": {
"table": "clusters",
"name": "overall_played"
}
}
},
{
"name": "linked_modpack_hash",
"ordinal": 11,
"type_info": "Text",
"origin": {
"Table": {
"table": "clusters",
"name": "linked_modpack_hash"
}
}
},
{
"name": "kind",
"ordinal": 12,
"type_info": "Integer",
"origin": {
"Table": {
"table": "clusters",
"name": "kind"
}
}
},
{
"name": "user_created",
"ordinal": 13,
"type_info": "Integer",
"origin": {
"Table": {
"table": "clusters",
"name": "user_created"
}
}
},
{
"name": "description",
"ordinal": 14,
"type_info": "Text",
"origin": {
"Table": {
"table": "clusters",
"name": "description"
}
}
},
{
"name": "tags",
"ordinal": 15,
"type_info": "Text",
"origin": {
"Table": {
"table": "clusters",
"name": "tags"
}
}
},
{
"name": "cover_path",
"ordinal": 16,
"type_info": "Text",
"origin": {
"Table": {
"table": "clusters",
"name": "cover_path"
}
}
}
],
"parameters": {
"Right": 1
},
"nullable": [
false,
false,
false,
true,
false,
false,
false,
true,
true,
true,
true,
true,
false,
false,
true,
false,
true
]
},
"hash": "5e18763a28af3660503e223c35875454b1e3fc4bb10c19d1c286d3e45afdd159"
}

View file

@ -0,0 +1,38 @@
{
"db_name": "SQLite",
"query": "\n SELECT bundle_name, content_type\n FROM cluster_bundle_type_opt_outs\n WHERE cluster_id = ?\n ",
"describe": {
"columns": [
{
"name": "bundle_name",
"ordinal": 0,
"type_info": "Text",
"origin": {
"Table": {
"table": "cluster_bundle_type_opt_outs",
"name": "bundle_name"
}
}
},
{
"name": "content_type",
"ordinal": 1,
"type_info": "Integer",
"origin": {
"Table": {
"table": "cluster_bundle_type_opt_outs",
"name": "content_type"
}
}
}
],
"parameters": {
"Right": 1
},
"nullable": [
false,
false
]
},
"hash": "5f45ecf4e5ac27ad3a7ef4e5f9c2e6466ef51af11c332a2e6089c90759b9871f"
}

View file

@ -0,0 +1,170 @@
{
"db_name": "SQLite",
"query": "\n\t\tSELECT name, java_path, resolution, force_fullscreen, mem_max, launch_args, game_args, launch_env,\n\t\t hook_pre, hook_wrapper, hook_post, os_extra, browser_update_mode\n\t\tFROM setting_profiles\n\t\tWHERE name = ?\n\t\t",
"describe": {
"columns": [
{
"name": "name",
"ordinal": 0,
"type_info": "Text",
"origin": {
"Table": {
"table": "setting_profiles",
"name": "name"
}
}
},
{
"name": "java_path",
"ordinal": 1,
"type_info": "Text",
"origin": {
"Table": {
"table": "setting_profiles",
"name": "java_path"
}
}
},
{
"name": "resolution",
"ordinal": 2,
"type_info": "Text",
"origin": {
"Table": {
"table": "setting_profiles",
"name": "resolution"
}
}
},
{
"name": "force_fullscreen",
"ordinal": 3,
"type_info": "Integer",
"origin": {
"Table": {
"table": "setting_profiles",
"name": "force_fullscreen"
}
}
},
{
"name": "mem_max",
"ordinal": 4,
"type_info": "Integer",
"origin": {
"Table": {
"table": "setting_profiles",
"name": "mem_max"
}
}
},
{
"name": "launch_args",
"ordinal": 5,
"type_info": "Text",
"origin": {
"Table": {
"table": "setting_profiles",
"name": "launch_args"
}
}
},
{
"name": "game_args",
"ordinal": 6,
"type_info": "Text",
"origin": {
"Table": {
"table": "setting_profiles",
"name": "game_args"
}
}
},
{
"name": "launch_env",
"ordinal": 7,
"type_info": "Text",
"origin": {
"Table": {
"table": "setting_profiles",
"name": "launch_env"
}
}
},
{
"name": "hook_pre",
"ordinal": 8,
"type_info": "Text",
"origin": {
"Table": {
"table": "setting_profiles",
"name": "hook_pre"
}
}
},
{
"name": "hook_wrapper",
"ordinal": 9,
"type_info": "Text",
"origin": {
"Table": {
"table": "setting_profiles",
"name": "hook_wrapper"
}
}
},
{
"name": "hook_post",
"ordinal": 10,
"type_info": "Text",
"origin": {
"Table": {
"table": "setting_profiles",
"name": "hook_post"
}
}
},
{
"name": "os_extra",
"ordinal": 11,
"type_info": "Text",
"origin": {
"Table": {
"table": "setting_profiles",
"name": "os_extra"
}
}
},
{
"name": "browser_update_mode",
"ordinal": 12,
"type_info": "Text",
"origin": {
"Table": {
"table": "setting_profiles",
"name": "browser_update_mode"
}
}
}
],
"parameters": {
"Right": 1
},
"nullable": [
false,
true,
true,
true,
true,
true,
true,
true,
true,
true,
true,
true,
true
]
},
"hash": "86e3ba70da419464c0be3b3971f613c5a112558472cf2dcdeffa5907e3785318"
}

View file

@ -0,0 +1,218 @@
{
"db_name": "SQLite",
"query": "\n\t\tSELECT\n\t\t\tid, name, folder_name, setting_profile_name, mc_version, mc_loader,\n\t\t\tstage, mc_loader_version, created_at, last_played, overall_played, linked_modpack_hash,\n\t\t\tkind, user_created, description, tags, cover_path\n\t\tFROM clusters\n\t\tWHERE id = ?\n\t\t",
"describe": {
"columns": [
{
"name": "id",
"ordinal": 0,
"type_info": "Integer",
"origin": {
"Table": {
"table": "clusters",
"name": "id"
}
}
},
{
"name": "name",
"ordinal": 1,
"type_info": "Text",
"origin": {
"Table": {
"table": "clusters",
"name": "name"
}
}
},
{
"name": "folder_name",
"ordinal": 2,
"type_info": "Text",
"origin": {
"Table": {
"table": "clusters",
"name": "folder_name"
}
}
},
{
"name": "setting_profile_name",
"ordinal": 3,
"type_info": "Text",
"origin": {
"Table": {
"table": "clusters",
"name": "setting_profile_name"
}
}
},
{
"name": "mc_version",
"ordinal": 4,
"type_info": "Text",
"origin": {
"Table": {
"table": "clusters",
"name": "mc_version"
}
}
},
{
"name": "mc_loader",
"ordinal": 5,
"type_info": "Integer",
"origin": {
"Table": {
"table": "clusters",
"name": "mc_loader"
}
}
},
{
"name": "stage",
"ordinal": 6,
"type_info": "Integer",
"origin": {
"Table": {
"table": "clusters",
"name": "stage"
}
}
},
{
"name": "mc_loader_version",
"ordinal": 7,
"type_info": "Text",
"origin": {
"Table": {
"table": "clusters",
"name": "mc_loader_version"
}
}
},
{
"name": "created_at",
"ordinal": 8,
"type_info": "Text",
"origin": {
"Table": {
"table": "clusters",
"name": "created_at"
}
}
},
{
"name": "last_played",
"ordinal": 9,
"type_info": "Text",
"origin": {
"Table": {
"table": "clusters",
"name": "last_played"
}
}
},
{
"name": "overall_played",
"ordinal": 10,
"type_info": "Integer",
"origin": {
"Table": {
"table": "clusters",
"name": "overall_played"
}
}
},
{
"name": "linked_modpack_hash",
"ordinal": 11,
"type_info": "Text",
"origin": {
"Table": {
"table": "clusters",
"name": "linked_modpack_hash"
}
}
},
{
"name": "kind",
"ordinal": 12,
"type_info": "Integer",
"origin": {
"Table": {
"table": "clusters",
"name": "kind"
}
}
},
{
"name": "user_created",
"ordinal": 13,
"type_info": "Integer",
"origin": {
"Table": {
"table": "clusters",
"name": "user_created"
}
}
},
{
"name": "description",
"ordinal": 14,
"type_info": "Text",
"origin": {
"Table": {
"table": "clusters",
"name": "description"
}
}
},
{
"name": "tags",
"ordinal": 15,
"type_info": "Text",
"origin": {
"Table": {
"table": "clusters",
"name": "tags"
}
}
},
{
"name": "cover_path",
"ordinal": 16,
"type_info": "Text",
"origin": {
"Table": {
"table": "clusters",
"name": "cover_path"
}
}
}
],
"parameters": {
"Right": 1
},
"nullable": [
false,
false,
false,
true,
false,
false,
false,
true,
true,
true,
true,
true,
false,
false,
true,
false,
true
]
},
"hash": "9b769a1467201ee998290c51b2cf51144a34c01975b898ee749b4e2111856119"
}

View file

@ -0,0 +1,218 @@
{
"db_name": "SQLite",
"query": "\n\t\tUPDATE clusters\n\t\tSET name = ?,\n\t\t setting_profile_name = ?,\n\t\t mc_loader_version = ?,\n\t\t linked_modpack_hash = ?,\n\t\t description = ?,\n\t\t tags = ?,\n\t\t cover_path = ?\n\t\tWHERE id = ?\n\t\tRETURNING\n\t\t\tid, name, folder_name, setting_profile_name, mc_version, mc_loader,\n\t\t\tstage, mc_loader_version, created_at, last_played, overall_played, linked_modpack_hash,\n\t\t\tkind, user_created, description, tags, cover_path\n\t\t",
"describe": {
"columns": [
{
"name": "id",
"ordinal": 0,
"type_info": "Integer",
"origin": {
"Table": {
"table": "clusters",
"name": "id"
}
}
},
{
"name": "name",
"ordinal": 1,
"type_info": "Text",
"origin": {
"Table": {
"table": "clusters",
"name": "name"
}
}
},
{
"name": "folder_name",
"ordinal": 2,
"type_info": "Text",
"origin": {
"Table": {
"table": "clusters",
"name": "folder_name"
}
}
},
{
"name": "setting_profile_name",
"ordinal": 3,
"type_info": "Text",
"origin": {
"Table": {
"table": "clusters",
"name": "setting_profile_name"
}
}
},
{
"name": "mc_version",
"ordinal": 4,
"type_info": "Text",
"origin": {
"Table": {
"table": "clusters",
"name": "mc_version"
}
}
},
{
"name": "mc_loader",
"ordinal": 5,
"type_info": "Integer",
"origin": {
"Table": {
"table": "clusters",
"name": "mc_loader"
}
}
},
{
"name": "stage",
"ordinal": 6,
"type_info": "Integer",
"origin": {
"Table": {
"table": "clusters",
"name": "stage"
}
}
},
{
"name": "mc_loader_version",
"ordinal": 7,
"type_info": "Text",
"origin": {
"Table": {
"table": "clusters",
"name": "mc_loader_version"
}
}
},
{
"name": "created_at",
"ordinal": 8,
"type_info": "Text",
"origin": {
"Table": {
"table": "clusters",
"name": "created_at"
}
}
},
{
"name": "last_played",
"ordinal": 9,
"type_info": "Text",
"origin": {
"Table": {
"table": "clusters",
"name": "last_played"
}
}
},
{
"name": "overall_played",
"ordinal": 10,
"type_info": "Integer",
"origin": {
"Table": {
"table": "clusters",
"name": "overall_played"
}
}
},
{
"name": "linked_modpack_hash",
"ordinal": 11,
"type_info": "Text",
"origin": {
"Table": {
"table": "clusters",
"name": "linked_modpack_hash"
}
}
},
{
"name": "kind",
"ordinal": 12,
"type_info": "Integer",
"origin": {
"Table": {
"table": "clusters",
"name": "kind"
}
}
},
{
"name": "user_created",
"ordinal": 13,
"type_info": "Integer",
"origin": {
"Table": {
"table": "clusters",
"name": "user_created"
}
}
},
{
"name": "description",
"ordinal": 14,
"type_info": "Text",
"origin": {
"Table": {
"table": "clusters",
"name": "description"
}
}
},
{
"name": "tags",
"ordinal": 15,
"type_info": "Text",
"origin": {
"Table": {
"table": "clusters",
"name": "tags"
}
}
},
{
"name": "cover_path",
"ordinal": 16,
"type_info": "Text",
"origin": {
"Table": {
"table": "clusters",
"name": "cover_path"
}
}
}
],
"parameters": {
"Right": 8
},
"nullable": [
false,
false,
false,
false,
false,
false,
false,
true,
true,
true,
true,
true,
false,
false,
true,
false,
true
]
},
"hash": "9e4cbbc2c299861132749196c4e248e4309c2449e181b3b9ae16e7f7b4d9e627"
}

View file

@ -0,0 +1,218 @@
{
"db_name": "SQLite",
"query": "\n\t\tUPDATE clusters\n\t\tSET mc_version = ?,\n\t\t mc_loader = ?,\n\t\t mc_loader_version = ?,\n\t\t stage = ?,\n\t\t name = ?,\n\t\t folder_name = ?\n\t\tWHERE id = ?\n\t\tRETURNING\n\t\t\tid, name, folder_name, setting_profile_name, mc_version, mc_loader,\n\t\t\tstage, mc_loader_version, created_at, last_played, overall_played, linked_modpack_hash,\n\t\t\tkind, user_created, description, tags, cover_path\n\t\t",
"describe": {
"columns": [
{
"name": "id",
"ordinal": 0,
"type_info": "Integer",
"origin": {
"Table": {
"table": "clusters",
"name": "id"
}
}
},
{
"name": "name",
"ordinal": 1,
"type_info": "Text",
"origin": {
"Table": {
"table": "clusters",
"name": "name"
}
}
},
{
"name": "folder_name",
"ordinal": 2,
"type_info": "Text",
"origin": {
"Table": {
"table": "clusters",
"name": "folder_name"
}
}
},
{
"name": "setting_profile_name",
"ordinal": 3,
"type_info": "Text",
"origin": {
"Table": {
"table": "clusters",
"name": "setting_profile_name"
}
}
},
{
"name": "mc_version",
"ordinal": 4,
"type_info": "Text",
"origin": {
"Table": {
"table": "clusters",
"name": "mc_version"
}
}
},
{
"name": "mc_loader",
"ordinal": 5,
"type_info": "Integer",
"origin": {
"Table": {
"table": "clusters",
"name": "mc_loader"
}
}
},
{
"name": "stage",
"ordinal": 6,
"type_info": "Integer",
"origin": {
"Table": {
"table": "clusters",
"name": "stage"
}
}
},
{
"name": "mc_loader_version",
"ordinal": 7,
"type_info": "Text",
"origin": {
"Table": {
"table": "clusters",
"name": "mc_loader_version"
}
}
},
{
"name": "created_at",
"ordinal": 8,
"type_info": "Text",
"origin": {
"Table": {
"table": "clusters",
"name": "created_at"
}
}
},
{
"name": "last_played",
"ordinal": 9,
"type_info": "Text",
"origin": {
"Table": {
"table": "clusters",
"name": "last_played"
}
}
},
{
"name": "overall_played",
"ordinal": 10,
"type_info": "Integer",
"origin": {
"Table": {
"table": "clusters",
"name": "overall_played"
}
}
},
{
"name": "linked_modpack_hash",
"ordinal": 11,
"type_info": "Text",
"origin": {
"Table": {
"table": "clusters",
"name": "linked_modpack_hash"
}
}
},
{
"name": "kind",
"ordinal": 12,
"type_info": "Integer",
"origin": {
"Table": {
"table": "clusters",
"name": "kind"
}
}
},
{
"name": "user_created",
"ordinal": 13,
"type_info": "Integer",
"origin": {
"Table": {
"table": "clusters",
"name": "user_created"
}
}
},
{
"name": "description",
"ordinal": 14,
"type_info": "Text",
"origin": {
"Table": {
"table": "clusters",
"name": "description"
}
}
},
{
"name": "tags",
"ordinal": 15,
"type_info": "Text",
"origin": {
"Table": {
"table": "clusters",
"name": "tags"
}
}
},
{
"name": "cover_path",
"ordinal": 16,
"type_info": "Text",
"origin": {
"Table": {
"table": "clusters",
"name": "cover_path"
}
}
}
],
"parameters": {
"Right": 7
},
"nullable": [
false,
false,
false,
true,
false,
false,
false,
true,
true,
true,
true,
true,
false,
false,
true,
false,
true
]
},
"hash": "aeb2fbb78cfa536d6478878ccafb4f4754dfe057468fe06c22d87c3a062132b7"
}

View file

@ -0,0 +1,218 @@
{
"db_name": "SQLite",
"query": "\n\t\tSELECT\n\t\t\tid, name, folder_name, setting_profile_name, mc_version, mc_loader,\n\t\t\tstage, mc_loader_version, created_at, last_played, overall_played, linked_modpack_hash,\n\t\t\tkind, user_created, description, tags, cover_path\n\t\tFROM clusters\n\t\tWHERE mc_version = ? AND mc_loader = ? AND user_created = 0\n\t\tLIMIT 1\n\t\t",
"describe": {
"columns": [
{
"name": "id",
"ordinal": 0,
"type_info": "Integer",
"origin": {
"Table": {
"table": "clusters",
"name": "id"
}
}
},
{
"name": "name",
"ordinal": 1,
"type_info": "Text",
"origin": {
"Table": {
"table": "clusters",
"name": "name"
}
}
},
{
"name": "folder_name",
"ordinal": 2,
"type_info": "Text",
"origin": {
"Table": {
"table": "clusters",
"name": "folder_name"
}
}
},
{
"name": "setting_profile_name",
"ordinal": 3,
"type_info": "Text",
"origin": {
"Table": {
"table": "clusters",
"name": "setting_profile_name"
}
}
},
{
"name": "mc_version",
"ordinal": 4,
"type_info": "Text",
"origin": {
"Table": {
"table": "clusters",
"name": "mc_version"
}
}
},
{
"name": "mc_loader",
"ordinal": 5,
"type_info": "Integer",
"origin": {
"Table": {
"table": "clusters",
"name": "mc_loader"
}
}
},
{
"name": "stage",
"ordinal": 6,
"type_info": "Integer",
"origin": {
"Table": {
"table": "clusters",
"name": "stage"
}
}
},
{
"name": "mc_loader_version",
"ordinal": 7,
"type_info": "Text",
"origin": {
"Table": {
"table": "clusters",
"name": "mc_loader_version"
}
}
},
{
"name": "created_at",
"ordinal": 8,
"type_info": "Text",
"origin": {
"Table": {
"table": "clusters",
"name": "created_at"
}
}
},
{
"name": "last_played",
"ordinal": 9,
"type_info": "Text",
"origin": {
"Table": {
"table": "clusters",
"name": "last_played"
}
}
},
{
"name": "overall_played",
"ordinal": 10,
"type_info": "Integer",
"origin": {
"Table": {
"table": "clusters",
"name": "overall_played"
}
}
},
{
"name": "linked_modpack_hash",
"ordinal": 11,
"type_info": "Text",
"origin": {
"Table": {
"table": "clusters",
"name": "linked_modpack_hash"
}
}
},
{
"name": "kind",
"ordinal": 12,
"type_info": "Integer",
"origin": {
"Table": {
"table": "clusters",
"name": "kind"
}
}
},
{
"name": "user_created",
"ordinal": 13,
"type_info": "Integer",
"origin": {
"Table": {
"table": "clusters",
"name": "user_created"
}
}
},
{
"name": "description",
"ordinal": 14,
"type_info": "Text",
"origin": {
"Table": {
"table": "clusters",
"name": "description"
}
}
},
{
"name": "tags",
"ordinal": 15,
"type_info": "Text",
"origin": {
"Table": {
"table": "clusters",
"name": "tags"
}
}
},
{
"name": "cover_path",
"ordinal": 16,
"type_info": "Text",
"origin": {
"Table": {
"table": "clusters",
"name": "cover_path"
}
}
}
],
"parameters": {
"Right": 2
},
"nullable": [
false,
false,
false,
true,
false,
false,
false,
true,
true,
true,
true,
true,
false,
false,
true,
false,
true
]
},
"hash": "bc1502f185c5e73b59057f9caa0a892fbc2fa5dc45e63b391176e61f83a7676d"
}

View file

@ -0,0 +1,12 @@
{
"db_name": "SQLite",
"query": "\n INSERT OR REPLACE INTO cluster_bundle_choices (cluster_id, bundle_name, accepted)\n VALUES (?, ?, ?)\n ",
"describe": {
"columns": [],
"parameters": {
"Right": 3
},
"nullable": []
},
"hash": "c06af5530a824d31e6e18a3a52cd57639b74e7e4d2ae30e29a6c49fda9521b4d"
}

View file

@ -0,0 +1,218 @@
{
"db_name": "SQLite",
"query": "\n\t\tUPDATE clusters\n\t\tSET stage = ?\n\t\tWHERE id = ?\n\t\tRETURNING\n\t\t\tid, name, folder_name, setting_profile_name, mc_version, mc_loader,\n\t\t\tstage, mc_loader_version, created_at, last_played, overall_played, linked_modpack_hash,\n\t\t\tkind, user_created, description, tags, cover_path\n\t\t",
"describe": {
"columns": [
{
"name": "id",
"ordinal": 0,
"type_info": "Integer",
"origin": {
"Table": {
"table": "clusters",
"name": "id"
}
}
},
{
"name": "name",
"ordinal": 1,
"type_info": "Text",
"origin": {
"Table": {
"table": "clusters",
"name": "name"
}
}
},
{
"name": "folder_name",
"ordinal": 2,
"type_info": "Text",
"origin": {
"Table": {
"table": "clusters",
"name": "folder_name"
}
}
},
{
"name": "setting_profile_name",
"ordinal": 3,
"type_info": "Text",
"origin": {
"Table": {
"table": "clusters",
"name": "setting_profile_name"
}
}
},
{
"name": "mc_version",
"ordinal": 4,
"type_info": "Text",
"origin": {
"Table": {
"table": "clusters",
"name": "mc_version"
}
}
},
{
"name": "mc_loader",
"ordinal": 5,
"type_info": "Integer",
"origin": {
"Table": {
"table": "clusters",
"name": "mc_loader"
}
}
},
{
"name": "stage",
"ordinal": 6,
"type_info": "Integer",
"origin": {
"Table": {
"table": "clusters",
"name": "stage"
}
}
},
{
"name": "mc_loader_version",
"ordinal": 7,
"type_info": "Text",
"origin": {
"Table": {
"table": "clusters",
"name": "mc_loader_version"
}
}
},
{
"name": "created_at",
"ordinal": 8,
"type_info": "Text",
"origin": {
"Table": {
"table": "clusters",
"name": "created_at"
}
}
},
{
"name": "last_played",
"ordinal": 9,
"type_info": "Text",
"origin": {
"Table": {
"table": "clusters",
"name": "last_played"
}
}
},
{
"name": "overall_played",
"ordinal": 10,
"type_info": "Integer",
"origin": {
"Table": {
"table": "clusters",
"name": "overall_played"
}
}
},
{
"name": "linked_modpack_hash",
"ordinal": 11,
"type_info": "Text",
"origin": {
"Table": {
"table": "clusters",
"name": "linked_modpack_hash"
}
}
},
{
"name": "kind",
"ordinal": 12,
"type_info": "Integer",
"origin": {
"Table": {
"table": "clusters",
"name": "kind"
}
}
},
{
"name": "user_created",
"ordinal": 13,
"type_info": "Integer",
"origin": {
"Table": {
"table": "clusters",
"name": "user_created"
}
}
},
{
"name": "description",
"ordinal": 14,
"type_info": "Text",
"origin": {
"Table": {
"table": "clusters",
"name": "description"
}
}
},
{
"name": "tags",
"ordinal": 15,
"type_info": "Text",
"origin": {
"Table": {
"table": "clusters",
"name": "tags"
}
}
},
{
"name": "cover_path",
"ordinal": 16,
"type_info": "Text",
"origin": {
"Table": {
"table": "clusters",
"name": "cover_path"
}
}
}
],
"parameters": {
"Right": 2
},
"nullable": [
false,
false,
false,
true,
false,
false,
false,
true,
true,
true,
true,
true,
false,
false,
true,
false,
true
]
},
"hash": "c3084a5369554a3ada17a50c5e33f8a6c414a9a213fd35c001c957eeebca3ff0"
}

View file

@ -0,0 +1,218 @@
{
"db_name": "SQLite",
"query": "\n\t\tSELECT\n\t\t\tid, name, folder_name, setting_profile_name, mc_version, mc_loader,\n\t\t\tstage, mc_loader_version, created_at, last_played, overall_played, linked_modpack_hash,\n\t\t\tkind, user_created, description, tags, cover_path\n\t\tFROM clusters\n\t\tORDER BY last_played IS NULL, last_played DESC, name ASC\n\t\t",
"describe": {
"columns": [
{
"name": "id",
"ordinal": 0,
"type_info": "Integer",
"origin": {
"Table": {
"table": "clusters",
"name": "id"
}
}
},
{
"name": "name",
"ordinal": 1,
"type_info": "Text",
"origin": {
"Table": {
"table": "clusters",
"name": "name"
}
}
},
{
"name": "folder_name",
"ordinal": 2,
"type_info": "Text",
"origin": {
"Table": {
"table": "clusters",
"name": "folder_name"
}
}
},
{
"name": "setting_profile_name",
"ordinal": 3,
"type_info": "Text",
"origin": {
"Table": {
"table": "clusters",
"name": "setting_profile_name"
}
}
},
{
"name": "mc_version",
"ordinal": 4,
"type_info": "Text",
"origin": {
"Table": {
"table": "clusters",
"name": "mc_version"
}
}
},
{
"name": "mc_loader",
"ordinal": 5,
"type_info": "Integer",
"origin": {
"Table": {
"table": "clusters",
"name": "mc_loader"
}
}
},
{
"name": "stage",
"ordinal": 6,
"type_info": "Integer",
"origin": {
"Table": {
"table": "clusters",
"name": "stage"
}
}
},
{
"name": "mc_loader_version",
"ordinal": 7,
"type_info": "Text",
"origin": {
"Table": {
"table": "clusters",
"name": "mc_loader_version"
}
}
},
{
"name": "created_at",
"ordinal": 8,
"type_info": "Text",
"origin": {
"Table": {
"table": "clusters",
"name": "created_at"
}
}
},
{
"name": "last_played",
"ordinal": 9,
"type_info": "Text",
"origin": {
"Table": {
"table": "clusters",
"name": "last_played"
}
}
},
{
"name": "overall_played",
"ordinal": 10,
"type_info": "Integer",
"origin": {
"Table": {
"table": "clusters",
"name": "overall_played"
}
}
},
{
"name": "linked_modpack_hash",
"ordinal": 11,
"type_info": "Text",
"origin": {
"Table": {
"table": "clusters",
"name": "linked_modpack_hash"
}
}
},
{
"name": "kind",
"ordinal": 12,
"type_info": "Integer",
"origin": {
"Table": {
"table": "clusters",
"name": "kind"
}
}
},
{
"name": "user_created",
"ordinal": 13,
"type_info": "Integer",
"origin": {
"Table": {
"table": "clusters",
"name": "user_created"
}
}
},
{
"name": "description",
"ordinal": 14,
"type_info": "Text",
"origin": {
"Table": {
"table": "clusters",
"name": "description"
}
}
},
{
"name": "tags",
"ordinal": 15,
"type_info": "Text",
"origin": {
"Table": {
"table": "clusters",
"name": "tags"
}
}
},
{
"name": "cover_path",
"ordinal": 16,
"type_info": "Text",
"origin": {
"Table": {
"table": "clusters",
"name": "cover_path"
}
}
}
],
"parameters": {
"Right": 0
},
"nullable": [
false,
false,
false,
true,
false,
false,
false,
true,
true,
true,
true,
true,
false,
false,
true,
false,
true
]
},
"hash": "d636f4db52b3bb77d9d887500afbff5658afa10db3cf56794b8fa5e12535f3f7"
}

View file

@ -0,0 +1,12 @@
{
"db_name": "SQLite",
"query": "\n DELETE FROM cluster_bundle_type_opt_outs\n WHERE cluster_id = ? AND bundle_name = ? AND content_type = ?\n ",
"describe": {
"columns": [],
"parameters": {
"Right": 3
},
"nullable": []
},
"hash": "dff3f2fe8e50eaa7ffebab7490306bc40e846ad708975991346aab83870e0968"
}

View file

@ -0,0 +1,12 @@
{
"db_name": "SQLite",
"query": "\n\t\tINSERT INTO release_migration_waitlist (\n\t\t\ttarget_cluster_id, provider, project_id, content_type,\n\t\t\tsource_hash, display_name, added_at, expires_at, enabled\n\t\t)\n\t\tVALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)\n\t\tON CONFLICT(target_cluster_id, provider, project_id) DO UPDATE SET\n\t\t\tcontent_type = excluded.content_type,\n\t\t\tsource_hash = excluded.source_hash,\n\t\t\tdisplay_name = excluded.display_name,\n\t\t\tadded_at = excluded.added_at,\n\t\t\texpires_at = excluded.expires_at,\n\t\t\tenabled = excluded.enabled\n\t\t",
"describe": {
"columns": [],
"parameters": {
"Right": 9
},
"nullable": []
},
"hash": "e12409b04c8a3eea09a4bfc1014bca6fe5fbc1256b3537ed37195ed20075f944"
}

View file

@ -0,0 +1,12 @@
{
"db_name": "SQLite",
"query": "DELETE FROM release_migration_waitlist WHERE expires_at <= ?",
"describe": {
"columns": [],
"parameters": {
"Right": 1
},
"nullable": []
},
"hash": "ff07b78c0e97f1f38a6900092391d22e475471578b5ef73927e5427c2c0abc77"
}

25
AGENTS.md Normal file
View file

@ -0,0 +1,25 @@
# OneLauncher-Cracked Development & Release Guidelines
## 1. Release Versioning Policy
- **Suffix Progression**: Every fix, rebuild, or update to an existing release MUST increment the cracked revision suffix monotonically:
- Example: `v2.8.0-c1` -> `v2.8.0-c2` -> `v2.8.0-c3`
- **Tag Immutability**: Never force-push or re-upload assets over an existing release tag. The in-launcher `CrackedVersion` SemVer parser relies on strictly increasing revisions to detect and download updates.
- **Cargo.toml Version**: When bumping the suffix, update the workspace `version = "2.8.0-c<N>"` in the root `Cargo.toml`.
## 2. Remote Synchronization (Dual-Push)
- Primary Forgejo remote: `https://git.oiupoyt.space/oiupoyt/OneLauncher-Cracked.git` (remote: `forgejo`)
- GitHub remote: `https://github.com/oiupoyt/OneLauncher-Cracked.git` (remote: `origin`)
- Always push to both remotes:
```bash
git push origin <branch> --tags
git push forgejo <branch> --tags
```
## 3. Build & Compiler Performance
- **ThinLTO**: Use `lto = "thin"` with `codegen-units = 16` and `debug = false` in `[profile.release]`. Do not use `lto = "fat"` (causes 10m+ single-threaded linking stalls).
- **Fast Linker**: On Linux, use `clang -fuse-ld=lld` configured in `.cargo/config.toml`.
- **Offline Mode & Skin Pipeline**:
- Preserve `account_for_launch` bypass in `oneclient_auth`.
- Always verify `paths::all_skins_dirs()` for multi-directory skin search.
- Keep `--userProperties` argument injection in `oneclient_core/src/game/launch.rs`.
- Ensure TCP socket `flush()` and `shutdown()` in `skin_server.rs` before connection termination.

34
CHANGELOG.md Normal file
View file

@ -0,0 +1,34 @@
## 2.8.0 (2026-10-09)
### Features
- Added safe multiple minecraft instances launching, only dedicated clusters tho by [rozwader](https://github.com/rozwader)
### Fixes
- Added better informations about errors from mclogs. by [rozwader](https://github.com/rozwader)
- Added status bar showing up if a player is logged out or he has a stale refresh token by [rozwader](https://github.com/rozwader)
- Added a filter for choosing modpack loaders by [rozwader](https://github.com/rozwader)
- SkyBlock content installed without asking is removed from 26.3 clusters once, and turning an optional bundle down removes its mods
- Fixed shield.io badges not rendering properly in markdown viewers by [rozwader](https://github.com/rozwader)
## 2.7.0 (2026-10-05)
### Features
- Code blocks are selectable, and the open link confirmation has a copy button by [LynithDev](https://github.com/LynithDev)
- Game settings have a Game Arguments field for passing extra arguments to Minecraft, globally or per cluster by [Wyvest](https://github.com/Wyvest)
- Game logs hide account tokens, and the game output starts with the launch command ready to paste into a terminal by [LynithDev](https://github.com/LynithDev)
- GitHub hosted mods have a "View in browser" option that opens their repository by [LynithDev](https://github.com/LynithDev)
- The changelog page now shows each GitHub release's notes by [LynithDev](https://github.com/LynithDev)
- The mods folder now syncs with the launcher: jars added, renamed or replaced by hand show up in the Mods list, and jars deleted by hand are switched off instead of being put back by [Wyvest](https://github.com/Wyvest)
- Choose which GPU the game renders on by [LynithDev](https://github.com/LynithDev)
### Fixes
- Icons show on Linux systems with a comma decimal locale by [LynithDev](https://github.com/LynithDev)
- The "Uploaded to mclo.gs" notification no longer repeats when switching back to the Logs tab by [LynithDev](https://github.com/LynithDev)
- Open folder and open in browser work again on Linux by [LynithDev](https://github.com/LynithDev)
- Optional bundles are now offered before the first launch of every new version instead of being installed or skipped silently by [Wyvest](https://github.com/Wyvest)
- System information in the settings sidebar shows a tooltip explaining it can be clicked to copy by [LynithDev](https://github.com/LynithDev)

View file

@ -73,6 +73,15 @@ and per-OS prerequisites.
Once you have finished making your changes, create a pull request (PR) to submit them. Once you have finished making your changes, create a pull request (PR) to submit them.
- Fill out the template to help reviewers understand your changes and the purpose of your PR. - Fill out the template to help reviewers understand your changes and the purpose of your PR.
- If your change is user-facing, add a change file with `knope document-change`, or create `.changeset/<anything>.md` by hand:
```md
---
default: patch
---
# Fixed the thing players noticed
```
Use `patch` for fixes, `minor` for features and `major` for breaking changes. The title becomes your line in the release notes. PRs without one fail the `Changeset Check` unless a maintainer labels them `no changeset`.
- If you are addressing an existing issue, don't forget to [link your PR to the issue]. - If you are addressing an existing issue, don't forget to [link your PR to the issue].
- Enable the checkbox to [allow maintainer edits] so that the branch can be updated for merging. - Enable the checkbox to [allow maintainer edits] so that the branch can be updated for merging.
- Once you submit your PR, a team member will review your proposal. They may ask questions or request additional information. - Once you submit your PR, a team member will review your proposal. They may ask questions or request additional information.

2360
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -9,14 +9,14 @@ authors = [
"Polyfrost", "Polyfrost",
"Polyfrost Contributors", "Polyfrost Contributors",
] ]
documentation = "https://github.com/oiupoyt/OneLauncher-Cracked" documentation = "https://git.oiupoyt.space/oiupoyt/OneLauncher-Cracked"
edition = "2024" edition = "2024"
homepage = "https://polyfrost.org/projects/oneclient" homepage = "https://polyfrost.org/projects/oneclient"
license = "GPL-3.0-only" license = "GPL-3.0-only"
readme = "README.md" readme = "README.md"
repository = "https://github.com/oiupoyt/OneLauncher-Cracked" repository = "https://git.oiupoyt.space/oiupoyt/OneLauncher-Cracked"
rust-version = "1.97" rust-version = "1.97"
version = "2.4.2-c1" version = "2.8.0-c1"
[workspace.dependencies] [workspace.dependencies]
# core # core
@ -37,10 +37,10 @@ polyio = {path = "./packages/polyio"}
# core (extra) # core (extra)
discord-rich-presence = {version = "=1.1.0"} discord-rich-presence = {version = "=1.1.0"}
interfrost = {version = "=1.4.0"} interfrost = {version = "=1.5.0"}
# logging # logging
sentry = {version = "=0.48.5", default-features = false, features = [ sentry = {version = "=0.49.3", default-features = false, features = [
"backtrace", "backtrace",
"contexts", "contexts",
"debug-images", "debug-images",
@ -49,7 +49,7 @@ sentry = {version = "=0.48.5", default-features = false, features = [
"reqwest", "reqwest",
"rustls", "rustls",
]} ]}
sentry-tracing = {version = "=0.48.5"} sentry-tracing = {version = "=0.49.3"}
tracing = {version = "=0.1.44"} tracing = {version = "=0.1.44"}
tracing-futures = {version = "=0.2.5"} tracing-futures = {version = "=0.2.5"}
tracing-subscriber = {version = "=0.3.23", features = ["env-filter", "fmt", "ansi", "json", "parking_lot", "registry"]} tracing-subscriber = {version = "=0.3.23", features = ["env-filter", "fmt", "ansi", "json", "parking_lot", "registry"]}
@ -68,15 +68,15 @@ sqlx = {version = "=0.9.0", default-features = false, features = [
]} ]}
# async # async
astral-tokio-tar = {version = "=0.6.2"} astral-tokio-tar = {version = "=0.7.0"}
async-compression = {version = "=0.4.42", features = ["tokio", "gzip"]} async-compression = {version = "=0.4.48", features = ["tokio", "gzip"]}
async-stream = {version = "=0.3.6"} async-stream = {version = "=0.3.6"}
async-tempfile = {version = "=0.7.0", features = ["uuid"]} async-tempfile = {version = "=0.8.0", features = ["uuid"]}
async-trait = {version = "=0.1.89"} async-trait = {version = "=0.1.92"}
async_zip = {version = "=0.0.18", features = ["chrono", "tokio", "tokio-fs", "deflate", "deflate64"]} async_zip = {version = "=0.0.19", features = ["tracing", "chrono", "tokio", "tokio-fs", "deflate", "deflate64"]}
futures-lite = {version = "=2.6.1"} futures-lite = {version = "=2.6.1"}
futures-util = {version = "=0.3.32", features = ["tokio-io"]} futures-util = {version = "=0.3.34", features = ["tokio-io"]}
reqwest = {version = "=0.13.3", default-features = false, features = [ reqwest = {version = "=0.13.5", default-features = false, features = [
"json", "json",
"stream", "stream",
"multipart", "multipart",
@ -91,67 +91,69 @@ reqwest = {version = "=0.13.3", default-features = false, features = [
"system-proxy", "system-proxy",
]} ]}
reqwest-websocket = {version = "=0.6.0"} reqwest-websocket = {version = "=0.6.0"}
tokio = {version = "=1.52.3", features = ["full"]} tokio = {version = "=1.53.1", features = ["full"]}
tokio-stream = {version = "=0.1.18", features = ["full"]} tokio-stream = {version = "=0.1.19", features = ["full"]}
tokio-util = {version = "=0.7.18", features = ["full"]} tokio-util = {version = "=0.7.19", features = ["full"]}
# crypto (content hashing) # crypto (content hashing)
byteorder = {version = "=1.5.0"} byteorder = {version = "=1.5.0"}
digest = {version = "=0.10.7", features = ["std"]} digest = {version = "=0.11.3"}
md-5 = {version = "=0.10.6", default-features = false} md-5 = {version = "=0.11.0", default-features = false}
p256 = {version = "=0.13.2", features = ["ecdsa", "pkcs8"]} p256 = {version = "=0.14.0", features = ["ecdsa", "pkcs8"]}
sha1 = {version = "=0.10.6", default-features = false} sha1 = {version = "=0.11.0", default-features = false}
sha2 = {version = "=0.10.9", default-features = false} sha2 = {version = "=0.11.0", default-features = false}
# util # util
arboard = {version = "=3.6.1", features = ["wayland-data-control"]} arboard = {version = "=3.6.1", features = ["wayland-data-control"]}
arc-swap = {version = "=1.7.1"} arc-swap = {version = "=1.9.2"}
base64 = {version = "=0.22.1"} base64 = {version = "=0.23.1"}
bytes = {version = "=1.11.1"} bytes = {version = "=1.12.1"}
chrono = {version = "=0.4.44", features = ["serde"]} chrono = {version = "=0.4.45", features = ["serde"]}
directories = {version = "=6.0.0"} directories = {version = "=6.0.0"}
dunce = {version = "=1.0.5"} dunce = {version = "=1.0.5"}
htmd = {version = "=0.5.4"} htmd = {version = "=0.5.5"}
indicatif = {version = "=0.18.4", features = ["tokio"]} indicatif = {version = "=0.18.6", features = ["tokio"]}
junction = {version = "=2.0.0"} junction = {version = "=2.1.0"}
libc = {version = "=0.2.189"} libc = {version = "=0.2.189"}
notify = {version = "=8.2.0"} notify = {version = "=8.2.0"}
open = {version = "=5.4.4", features = ["shellexecute-on-windows"]}
parking_lot = {version = "=0.12.5"} parking_lot = {version = "=0.12.5"}
pulldown-cmark = {version = "=0.13.4", default-features = false} pulldown-cmark = {version = "=0.13.4", default-features = false}
rand = {version = "=0.10.1"} rand = {version = "=0.10.3"}
regex = {version = "=1.12.3"} regex = {version = "=1.13.1"}
rfd = {version = "=0.17.2"}
rust-embed = {version = "=8.12.0", features = ["include-exclude", "compression", "debug-embed"]} rust-embed = {version = "=8.12.0", features = ["include-exclude", "compression", "debug-embed"]}
sanitize-filename = {version = "=0.6.0"} sanitize-filename = {version = "=0.6.0"}
serde = {version = "=1.0.228"} serde = {version = "=1.0.229"}
serde_json = {version = "=1.0.150"} serde_json = {version = "=1.0.151"}
serde_repr = {version = "=0.1.20"} serde_repr = {version = "=0.1.21"}
sysinfo = {version = "=0.39.5"} sysinfo = {version = "=0.39.6"}
toml = {version = "=1.1.4", default-features = false, features = ["parse", "serde"]} toml = {version = "=1.1.6", default-features = false, features = ["parse", "serde"]}
trash = {version = "=5.2.6"} trash = {version = "=5.2.9"}
percent-encoding = {version = "=2.3.2"} percent-encoding = {version = "=2.3.2"}
url = {version = "=2.5.8", features = ["serde"]} url = {version = "=2.5.8", features = ["serde"]}
uuid = {version = "=1.23.1", features = ["serde", "v4"]} uuid = {version = "=1.26.1", features = ["serde", "v4"]}
windows-sys = {version = "=0.61.2", features = ["Win32_Foundation", "Win32_Storage_FileSystem"]} windows-sys = {version = "=0.61.2", features = ["Win32_Foundation", "Win32_Storage_FileSystem"]}
# util (codegen/macro) # util (codegen/macro)
anyhow = {version = "=1.0.102"} anyhow = {version = "=1.0.104"}
proc-macro2 = {version = "=1.0.106"} proc-macro2 = {version = "=1.0.107"}
quote = {version = "=1.0.45"} quote = {version = "=1.0.47"}
strum = {version = "=0.28.0", features = ["derive"]} strum = {version = "=0.28.0", features = ["derive"]}
syn = {version = "=2.0.117"} syn = {version = "=3.0.6"}
thiserror = {version = "=2.0.18"} thiserror = {version = "=2.0.21"}
# ui # ui
freya = {git = "https://github.com/marc2332/freya", branch = "fix/skip-basic-render-driver", features = ["engine", "winit", "router", "remote-asset", "sdk", "query", "radio", "titlebar"]} freya = {git = "https://github.com/Polyfrost/freya", rev = "12f696f68c4936617b8627c862b39acb3d9cca2b", features = ["engine", "winit", "router", "remote-asset", "sdk-tokio", "query", "radio", "borderless", "titlebar", "tray", "metrics"]}
freya-core = {git = "https://github.com/marc2332/freya", branch = "fix/skip-basic-render-driver"} freya-core = {git = "https://github.com/Polyfrost/freya", rev = "12f696f68c4936617b8627c862b39acb3d9cca2b"}
freya-testing = {git = "https://github.com/marc2332/freya", branch = "fix/skip-basic-render-driver"} freya-testing = {git = "https://github.com/Polyfrost/freya", rev = "12f696f68c4936617b8627c862b39acb3d9cca2b"}
freya-skia-safe = "=0.100.0" # required for the SKSL player renderer freya-skia-safe = "=0.103.2" # required for the SKSL player renderer
image = {version = "=0.25.10", default-features = false, features = ["png", "jpeg"]} image = {version = "=0.25.10", default-features = false, features = ["png", "jpeg"]}
[profile.release] [profile.release]
codegen-units = 1 codegen-units = 16
debug = "line-tables-only" debug = false
lto = "fat" lto = "thin"
opt-level = 3 opt-level = 3
panic = "abort" panic = "abort"
split-debuginfo = "packed" split-debuginfo = "packed"
@ -172,8 +174,9 @@ lto = false
strip = false strip = false
[profile.dev] [profile.dev]
debug = 1 opt-level = 0
debug = 0
strip = "debuginfo"
[profile.dev.package."*"] [profile.dev.package."*"]
opt-level = 3 opt-level = 2
debug = false

View file

@ -1,73 +1,20 @@
<div align="center"> <div align="center">
<img src=".github/media/RepoBanner.png" alt="Repository Banner" /> <img src="https://github.com/user-attachments/assets/e8359baa-23fb-457a-8910-169974487a98" alt="OneLauncher Logo" width="220" style="border-radius: 24px;" />
# OneClient | OneLauncher forked from [OneLauncher](https://github.com/Polyfrost/OneLauncher) allowing for uh offline authentication
The monorepo containing the code for OneLauncher, OneClient, and their core backend.
OneClient is a Minecraft client featuring fully 100% open-source components, offering many packaged and pre-configured mods in one click.
OneLauncher is a WIP Minecraft launcher giving power-users the greatest customization whilst featuring a clean UI.
</div> </div>
## Installing ## download from [**Releases Page**](https://git.oiupoyt.space/oiupoyt/OneLauncher-Cracked/releases).
You can install the latest release of OneClient from our website: [https://polyfrost.org/projects/oneclient](https://polyfrost.org/projects/oneclient) yes the actual client performs normally aswell
as well as our [GitHub releases](https://github.com/Polyfrost/OneLauncher/releases/latest).
| Windows (x86_64) | macOS (Intel & Apple Silicon) | Linux (x86_64) | > [!WARNING]
|------------------|-------------------------------|---------------------------------------------------------| > DO NOT use official Microsoft/Mojang accounts with this launcher. This bypasses authentication and is intended for offline play only. Using with official servers may violate Minecraft's Terms of Service.
| Installer 🔄 | DMG 🔄 | AppImage 🔄 |
| | App Bundle 🔄 | DEB |
| | | RPM |
| | | [AUR](https://aur.archlinux.org/packages/oneclient-bin) |
> 🔄 = Has support for autoupdating built-in
## Contributing
We welcome contributions! Please read our [contributing guidelines](CONTRIBUTING.md) before getting started. #### The Trojan:Win32/Wacatac.B!ml detection is a false positive triggered by Microsoft Defender's machine learning heuristic on newly compiled, unsigned Rust NSIS installers. The software contains no malware, telemetry, or malicious payloads.
intended for offline usage only.
### Requirements
The project targets **Rust 1.97** or later. You can install Rust via [rustup](https://rustup.rs/).
### Building & Running
```sh
# Run the app
cargo run -p oneclient_app
# Build a release binary
cargo build -p oneclient_app --release
```
### Packaging / Releasing
Installers are produced with [**cargo-packager**](https://github.com/crabnebula-dev/cargo-packager)
(the standalone bundler spun out of the Tauri bundler). Config lives in
[`packages/oneclient_app/Cargo.toml`](./packages/oneclient_app/Cargo.toml) under
`[package.metadata.packager]`.
```sh
cargo install cargo-packager --locked
# Build the binary, then bundle it for the current OS:
cargo build --release -p oneclient_app
cargo packager --release -p oneclient_app --formats <targets>
# Windows: nsis macOS: app,dmg Linux: deb,appimage
```
### Versioning
The workspace shares a single version, defined in the root [`Cargo.toml`](./Cargo.toml) under `[workspace.package]`.
## Code signing
This program uses free code signing provided by [SignPath.io](https://signpath.io?utm_source=foundation&utm_medium=github&utm_campaign=0install), and a certificate by the [SignPath Foundation](https://signpath.org?utm_source=foundation&utm_medium=github&utm_campaign=0install). We thank them very much to their contributions to OSS software!

View file

@ -18,6 +18,7 @@ depends=(
'wayland' 'wayland'
'dbus' 'dbus'
'gtk3' 'gtk3'
'libayatana-appindicator'
) )
provides=('oneclient') provides=('oneclient')
conflicts=('oneclient') conflicts=('oneclient')

View file

@ -17,6 +17,7 @@ depends=(
'wayland' 'wayland'
'dbus' 'dbus'
'gtk3' 'gtk3'
'libayatana-appindicator'
) )
makedepends=( makedepends=(
'rust' 'rust'
@ -28,7 +29,10 @@ makedepends=(
) )
provides=('oneclient') provides=('oneclient')
conflicts=('oneclient-bin') conflicts=('oneclient-bin')
options=('!lto') options=(
'!lto'
'!debug'
)
source=("$pkgname-$pkgver.tar.gz::${url}/archive/refs/tags/oneclient-${pkgver}.tar.gz") source=("$pkgname-$pkgver.tar.gz::${url}/archive/refs/tags/oneclient-${pkgver}.tar.gz")
sha256sums=('SKIP') sha256sums=('SKIP')

View file

@ -15,6 +15,8 @@ Source0: %{url}/releases/download/oneclient-%{version}/%{_rpmfile}
ExclusiveArch: x86_64 ExclusiveArch: x86_64
BuildRequires: bsdtar BuildRequires: bsdtar
Requires: gtk3 Requires: gtk3
Requires: libayatana-appindicator-gtk3
Requires: libxdo
Requires: dbus-libs Requires: dbus-libs
Provides: oneclient = %{version}-%{release} Provides: oneclient = %{version}-%{release}

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

View file

@ -0,0 +1,108 @@
# OneLauncher-Cracked 2.8.0 Update Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Rebase and upgrade OneLauncher-Cracked onto upstream OneLauncher 2.8.0 (`1d6ec598`), preserving all offline authentication bypasses, the local Yggdrasil skin server with authlib-injector, Forgejo auto-updater, and custom skin changer UI.
**Architecture:** Port 37 cracked subsystem files onto the clean upstream 2.8.0 codebase. Maintain strict isolation for cracked authentication and skin server logic while adopting upstream's new multi-instance management, package manager filters, and Freya improvements.
**Tech Stack:** Rust 2024, Freya (Dioxus + Skia), Tokio, Reqwest, RSA PKCS#1v15, Authlib-Injector, Forgejo API v1.
**Spec:** Direct human instruction: "update to version 2.8.0".
## Global Constraints
- Target version: `2.8.0-c1`.
- Offline authentication must require 0 Microsoft accounts.
- Local Yggdrasil skin server must eliminate multiplayer skin cloning via RSA-signed profile properties.
- Auto-updater must target Forgejo API at `https://git.oiupoyt.space/api/v1/repos/oiupoyt/OneLauncher-Cracked/releases/latest`.
- All workspace crates must pass tests and compilation without regressions.
---
### Task 1: Project Metadata, Packaging & Workflows
**Files:**
- Modify: `Cargo.toml`
- Modify: `README.md`
- Modify: `.gitignore`
- Create: `.forgejo/workflows/release.yml`
- Create: `packages/oneclient_app/distribution/linux/oneclient_app.desktop`
- [ ] **Step 1: Update Cargo.toml metadata and dev profile**
- [ ] **Step 2: Update README.md with warning embed and Forgejo releases link**
- [ ] **Step 3: Add .forgejo release workflow and desktop file**
- [ ] **Step 4: Commit task 1**
---
### Task 2: Port Offline Authentication Bypass (`oneclient_auth`)
**Files:**
- Modify: `packages/oneclient_auth/src/service.rs`
- Modify: `packages/oneclient_auth/src/store.rs`
- Modify: `packages/oneclient_auth/tests/offline.rs`
- [ ] **Step 1: In `service.rs`, remove `OfflineRequiresMicrosoft` check from `account_for_launch`**
- [ ] **Step 2: In `store.rs`, allow offline accounts without Microsoft accounts and auto-select default user**
- [ ] **Step 3: In `tests/offline.rs`, ensure tests assert offline launch succeeds without Microsoft**
- [ ] **Step 4: Run `cargo test -p oneclient_auth` and verify all tests pass**
- [ ] **Step 5: Commit task 2**
---
### Task 3: Port Path Helpers, Assets & Offline Skin Engine (`oneclient_common` & `oneclient_core`)
**Files:**
- Modify: `packages/oneclient_common/src/paths.rs`
- Modify: `packages/oneclient_core/Cargo.toml`
- Copy assets: `packages/oneclient_core/assets/authlib-injector.jar`
- Copy assets: `packages/oneclient_core/assets/offlineskins-fabric.jar`
- Create: `packages/oneclient_core/src/game/skin_server.rs`
- Modify: `packages/oneclient_core/src/game/mod.rs`
- Modify: `packages/oneclient_core/src/game/launch.rs`
- [ ] **Step 1: Add skin paths and authlib-injector path to `oneclient_common/src/paths.rs`**
- [ ] **Step 2: Copy embedded jar assets and add dependencies to `oneclient_core/Cargo.toml`**
- [ ] **Step 3: Port `skin_server.rs` and register module in `game/mod.rs`**
- [ ] **Step 4: In `game/launch.rs`, inject authlib-injector `-javaagent`, `--userProperties`, and offline skin synchronization**
- [ ] **Step 5: Run `cargo test -p oneclient_core` and verify all tests pass**
- [ ] **Step 6: Commit task 3**
---
### Task 4: Port UI Components, Skins Tab & Auto-Updater (`oneclient_app`)
**Files:**
- Modify: `packages/oneclient_app/src/constants.rs`
- Modify: `packages/oneclient_app/src/updater.rs`
- Modify: `packages/oneclient_app/src/components/avatar.rs`
- Modify: `packages/oneclient_app/src/components/player_model.rs`
- Modify: `packages/oneclient_app/src/components/navbar/app_navbar.rs`
- Create: `packages/oneclient_app/src/view/app/skins.rs`
- Create: `packages/oneclient_app/src/hooks/queries/skin.rs`
- Modify: `packages/oneclient_app/src/hooks/queries/mod.rs`
- Modify: `packages/oneclient_app/src/hooks/mod.rs`
- Modify: `packages/oneclient_app/src/view/onboarding/account.rs`
- Modify: `packages/oneclient_app/src/view/app/settings/accounts.rs`
- Modify: `packages/oneclient_app/src/layout/root_layout.rs`
- Modify: `packages/oneclient_app/src/routes.rs`
- [ ] **Step 1: Update `constants.rs` and `updater.rs` with Forgejo releases endpoint and `CrackedVersion` semver parser**
- [ ] **Step 2: Port skin queries in `hooks/queries/skin.rs` and wire into `hooks/mod.rs`**
- [ ] **Step 3: Port `skins.rs` view and register route in `routes.rs` and `root_layout.rs`**
- [ ] **Step 4: Add Skins pill button to `navbar_center` in `app_navbar.rs`**
- [ ] **Step 5: Port avatar persistent cache in `avatar.rs` and player model custom skin loading in `player_model.rs`**
- [ ] **Step 6: Port offline account inputs in onboarding (`onboarding/account.rs`) and settings (`settings/accounts.rs`)**
- [ ] **Step 7: Run `cargo test -p oneclient_app --lib` and verify all tests pass**
- [ ] **Step 8: Commit task 4**
---
### Task 5: Verification, Packaging & Deployment
- [ ] **Step 1: Run full workspace `cargo check --workspace`**
- [ ] **Step 2: Build release binary locally**
- [ ] **Step 3: Package `.AppImage`, `.deb`, and `.tar.gz` for Linux**
- [ ] **Step 4: Merge `update/2.8.0` into `main`**
- [ ] **Step 5: Tag `v2.8.0-c1` and push branch and tag to Forgejo and GitHub**
- [ ] **Step 6: Publish Forgejo Release `v2.8.0-c1` with all binaries attached**

28
knope.toml Normal file
View file

@ -0,0 +1,28 @@
[package]
versioned_files = ["Cargo.toml"]
changelog = "CHANGELOG.md"
[changes]
ignore_conventional_commits = true
[release_notes]
change_templates = [
"- $summary by [$pr_author_login](https://github.com/$pr_author_login)",
"- $summary",
]
[github]
owner = "Polyfrost"
repo = "OneLauncher"
[[workflows]]
name = "prepare-release"
[[workflows.steps]]
type = "PrepareRelease"
[[workflows]]
name = "document-change"
[[workflows.steps]]
type = "CreateChangeFile"

View file

@ -33,6 +33,7 @@ rust-embed.workspace = true
tracing.workspace = true tracing.workspace = true
tracing-subscriber.workspace = true tracing-subscriber.workspace = true
futures-util.workspace = true
tokio.workspace = true tokio.workspace = true
uuid.workspace = true uuid.workspace = true
bytes.workspace = true bytes.workspace = true
@ -48,12 +49,13 @@ percent-encoding.workspace = true
serde = { workspace = true, features = ["derive"] } serde = { workspace = true, features = ["derive"] }
serde_json.workspace = true serde_json.workspace = true
reqwest.workspace = true reqwest.workspace = true
futures-util = { workspace = true, features = ["std"] }
cargo-packager-updater = "=0.2.3" cargo-packager-updater = "=0.2.3"
rfd = "=0.17.2" # rfd = "=0.17.2"
open = "=5.4.0" # open = "=5.4.0"
rfd.workspace = true
open.workspace = true
# clipboard image copy + decode for it # clipboard image copy + decode for it
arboard.workspace = true arboard.workspace = true
@ -63,6 +65,8 @@ freya-skia-safe.workspace = true
freya.workspace = true freya.workspace = true
freya-core.workspace = true freya-core.workspace = true
pulldown-cmark.workspace = true pulldown-cmark.workspace = true
# inlines svg logos embedded in markdown badges as data urls
base64.workspace = true
[dev-dependencies] [dev-dependencies]
freya-testing.workspace = true freya-testing.workspace = true
@ -70,6 +74,10 @@ freya-testing.workspace = true
[target.'cfg(target_os = "macos")'.dependencies] [target.'cfg(target_os = "macos")'.dependencies]
mimalloc = "=0.1.52" mimalloc = "=0.1.52"
[target.'cfg(target_os = "linux")'.dependencies]
libloading = "=0.8.9"
gtk-sys = "=0.18.2"
[target.'cfg(windows)'.dependencies] [target.'cfg(windows)'.dependencies]
windows = { version = "=0.62.2", features = [ windows = { version = "=0.62.2", features = [
"Win32_Foundation", "Win32_Foundation",
@ -154,6 +162,8 @@ desktop-template = "distribution/linux/org.polyfrost.oneclient.template"
[package.metadata.packager.appimage] [package.metadata.packager.appimage]
# desktop entry generated from the deb template above via the linux config # desktop entry generated from the deb template above via the linux config
libs = ["libayatana-appindicator3.so.1"]
excluded-libraries = ["libwayland-*"]
[package.metadata.generate-rpm] [package.metadata.generate-rpm]
auto-req = "builtin" auto-req = "builtin"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 958 B

After

Width:  |  Height:  |  Size: 920 B

View file

@ -0,0 +1,3 @@
<svg width="100%" height="100%" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8 15V17M12 11V17M16 7V17M6.8 21H17.2C18.88 21 19.72 21 20.36 20.67C20.93 20.39 21.39 19.93 21.67 19.36C22 18.72 22 17.88 22 16.2V7.8C22 6.12 22 5.28 21.67 4.64C21.39 4.07 20.93 3.61 20.36 3.33C19.72 3 18.88 3 17.2 3H6.8C5.12 3 4.28 3 3.64 3.33C3.07 3.61 2.61 4.07 2.33 4.64C2 5.28 2 6.12 2 7.8V16.2C2 17.88 2 18.72 2.33 19.36C2.61 19.93 3.07 20.39 3.64 20.67C4.28 21 5.12 21 6.8 21Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 590 B

View file

@ -0,0 +1 @@
<svg role="img" viewBox="-11.2 -8.5 149 113" xmlns="http://www.w3.org/2000/svg"><path fill="currentColor" d="M81.15,0c-1.2376,2.1973-2.3489,4.4704-3.3591,6.794-9.5975-1.4396-19.3718-1.4396-28.9945,0-.985-2.3236-2.1216-4.5967-3.3591-6.794-9.0166,1.5407-17.8059,4.2431-26.1405,8.0568C2.779,32.5304-1.6914,56.3725.5312,79.8863c9.6732,7.1476,20.5083,12.603,32.0505,16.0884,2.6014-3.4854,4.8998-7.1981,6.8698-11.0623-3.738-1.3891-7.3497-3.1318-10.8098-5.1523.9092-.6567,1.7932-1.3386,2.6519-1.9953,20.281,9.547,43.7696,9.547,64.0758,0,.8587.7072,1.7427,1.3891,2.6519,1.9953-3.4601,2.0457-7.0718,3.7632-10.835,5.1776,1.97,3.8642,4.2683,7.5769,6.8698,11.0623,11.5419-3.4854,22.3769-8.9156,32.0509-16.0631,2.626-27.2771-4.496-50.9172-18.817-71.8548C98.9811,4.2684,90.1918,1.5659,81.1752.0505l-.0252-.0505ZM42.2802,65.4144c-6.2383,0-11.4159-5.6575-11.4159-12.6535s4.9755-12.6788,11.3907-12.6788,11.5169,5.708,11.4159,12.6788c-.101,6.9708-5.026,12.6535-11.3907,12.6535ZM84.3576,65.4144c-6.2637,0-11.3907-5.6575-11.3907-12.6535s4.9755-12.6788,11.3907-12.6788,11.4917,5.708,11.3906,12.6788c-.101,6.9708-5.026,12.6535-11.3906,12.6535Z"/></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

View file

@ -0,0 +1 @@
<svg role="img" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path fill="currentColor" d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z"/></svg>

After

Width:  |  Height:  |  Size: 678 B

View file

@ -1,4 +1,7 @@
<svg width="128" height="128" viewBox="0 0 128 128" fill="none" xmlns="http://www.w3.org/2000/svg"> <svg width="80" height="80" viewBox="0 0 80 80" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M54.4179 7.49976H46.4793C37.369 7.49976 28.9508 12.36 24.3956 20.2498L6.61807 51.0414C6.00926 52.0959 5.48182 53.1831 5.03576 54.2942L30.2924 49.6414C40.4908 47.7626 48.4706 39.7829 50.3494 29.5844L54.4179 7.49976ZM5 73.199C5.45356 74.3413 5.99291 75.4586 6.61807 76.5414L24.3956 107.333C28.9508 115.223 37.369 120.083 46.4793 120.083H54.4331L50.3494 97.9154C48.4706 87.717 40.4908 79.7372 30.2924 77.8585L5 73.199ZM74.4827 120.083H82.0344C91.1447 120.083 99.5629 115.223 104.118 107.333L121.896 76.5414C122.506 75.4839 123.035 74.3935 123.482 73.279L98.6234 77.8585C88.425 79.7372 80.4452 87.717 78.5664 97.9154L74.4827 120.083ZM123.446 54.2142C123.006 53.1309 122.49 52.0706 121.896 51.0414L104.118 20.2498C99.5629 12.36 91.1447 7.49976 82.0344 7.49976H74.498L78.5664 29.5844C80.4452 39.7828 88.425 47.7626 98.6234 49.6414L123.446 54.2142Z" fill="currentColor"/> <path d="M35.0354 10.6199H30.9125C26.1812 10.6199 21.8092 13.1414 19.4435 17.2347L10.2109 33.2097C9.89473 33.7567 9.62082 34.3208 9.38916 34.8972L22.5061 32.4833C27.8025 31.5085 31.9468 27.3686 32.9225 22.0775L35.0354 10.6199Z" fill="currentColor"/>
<path d="M64.458 24.5L67.962 45.0947C69.2479 52.6528 75.0625 58.6345 82.5811 60.1343L100.708 63.75L82.5811 67.3657C75.0625 68.8655 69.2479 74.8472 67.962 82.4053L64.458 103L60.954 82.4053C59.6681 74.8472 53.8535 68.8655 46.335 67.3657L28.208 63.75L46.335 60.1343C53.8536 58.6345 59.6681 52.6528 60.954 45.0947L64.458 24.5Z" fill="currentColor"/> <path d="M9.37051 44.7052C9.60608 45.2979 9.8862 45.8775 10.2109 46.4393L19.4435 62.4143C21.8092 66.5076 26.1812 69.0292 30.9125 69.0292H35.0434L32.9225 57.5283C31.9468 52.2373 27.8025 48.0973 22.5061 47.1225L9.37051 44.7052Z" fill="currentColor"/>
<path d="M45.456 69.0292H49.3779C54.1092 69.0292 58.4812 66.5076 60.8469 62.4143L70.0795 46.4393C70.3966 45.8907 70.6712 45.3249 70.9033 44.7467L57.9933 47.1225C52.6968 48.0973 48.5526 52.2373 47.5769 57.5283L45.456 69.0292Z" fill="currentColor"/>
<path d="M70.8845 34.8557C70.6564 34.2937 70.3881 33.7436 70.0795 33.2097L60.8469 17.2347C58.4812 13.1414 54.1092 10.6199 49.3779 10.6199H45.4639L47.5769 22.0775C48.5526 27.3686 52.6968 31.5085 57.9933 32.4833L70.8845 34.8557Z" fill="currentColor"/>
<path d="M40.1206 11.766C40.1379 11.6124 40.3614 11.6124 40.3787 11.766L41.3999 20.8379C42.4408 30.0839 49.7344 37.3875 58.9875 38.4493L69.66 39.6741C69.8134 39.6917 69.8134 39.9142 69.66 39.9318L58.9875 41.1565C49.7345 42.2183 42.4408 49.5219 41.3999 58.768L40.3787 67.8398C40.3614 67.9935 40.1379 67.9935 40.1206 67.8398L39.1009 58.7822C38.0593 49.5296 30.7564 42.2232 21.4959 41.1688L10.6321 39.9318C10.4786 39.9143 10.4786 39.6915 10.6321 39.674L21.4958 38.437C30.7564 37.3826 38.0593 30.0762 39.1009 20.8236L40.1206 11.766Z" fill="currentColor"/>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

View file

@ -0,0 +1,3 @@
<svg width="100%" height="100%" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M16 17L21 12M21 12L16 7M21 12H9M9 21H7.8C6.12 21 5.28 21 4.64 20.67C4.07 20.39 3.61 19.93 3.33 19.36C3 18.72 3 17.88 3 16.2V7.8C3 6.12 3 5.28 3.33 4.64C3.61 4.07 4.07 3.61 4.64 3.33C5.28 3 6.12 3 7.8 3H9" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 410 B

View file

@ -0,0 +1,3 @@
<svg width="100%" height="100%" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M19.2 12.63A7.2 7.2 0 1 1 11.37 4.8A5.6 5.6 0 0 0 19.2 12.63Z" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 270 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

View file

@ -1,4 +1,4 @@
<svg width="156" height="32" viewBox="0 0 156 32" fill="none" xmlns="http://www.w3.org/2000/svg"> <svg width="156" height="32" viewBox="0 0.41 156 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10.9398 25.6841C9.60192 25.6841 8.3644 25.447 7.22718 24.9728C6.10671 24.4986 5.12003 23.8381 4.26712 22.9913C3.41424 22.1446 2.75365 21.1623 2.28539 20.0446C1.81714 18.9099 1.58301 17.6905 1.58301 16.3865C1.58301 15.0655 1.81714 13.8461 2.28539 12.7284C2.75365 11.6107 3.40587 10.6368 4.24206 9.80701C5.07821 8.96023 6.05654 8.3082 7.17703 7.85093C8.31422 7.37675 9.55174 7.13965 10.8896 7.13965C12.2108 7.13965 13.4316 7.37675 14.5521 7.85093C15.6893 8.3082 16.676 8.96023 17.5122 9.80701C18.365 10.6368 19.0256 11.6191 19.4939 12.7538C19.9621 13.8715 20.1963 15.0909 20.1963 16.4119C20.1963 17.7159 19.9621 18.9353 19.4939 20.07C19.0256 21.1877 18.3734 22.17 17.5372 23.0168C16.7011 23.8466 15.7144 24.4986 14.5772 24.9728C13.4567 25.447 12.2443 25.6841 10.9398 25.6841ZM10.8896 22.0514C11.9432 22.0514 12.863 21.8143 13.649 21.3401C14.4517 20.8659 15.0705 20.2054 15.5053 19.3587C15.9401 18.495 16.1576 17.5042 16.1576 16.3865C16.1576 15.5397 16.0321 14.7776 15.7813 14.1002C15.5304 13.4058 15.1709 12.8131 14.7026 12.3219C14.2344 11.8139 13.6741 11.4328 13.0219 11.1788C12.3864 10.9078 11.6757 10.7723 10.8896 10.7723C9.83606 10.7723 8.90791 11.0094 8.10518 11.4836C7.31917 11.9409 6.70876 12.5929 6.27395 13.4397C5.83913 14.2695 5.62175 15.2518 5.62175 16.3865C5.62175 17.2332 5.74716 18.0038 5.99802 18.6982C6.24886 19.3925 6.60007 19.9937 7.05159 20.5018C7.51986 20.9929 8.08009 21.374 8.73232 21.645C9.38451 21.9159 10.1036 22.0514 10.8896 22.0514ZM24.2015 25.3793V7.4445H26.9609L28.1399 11.0264V25.3793H24.2015ZM36.4181 25.3793L25.9825 11.8393L26.9609 7.4445L37.3963 20.9845L36.4181 25.3793ZM36.4181 25.3793L35.3644 21.7974V7.4445H39.3029V25.3793H36.4181ZM44.2267 25.3793V7.4445H48.1649V25.3793H44.2267ZM47.2368 25.3793V21.9244H56.9448V25.3793H47.2368ZM47.2368 17.8853V14.5574H56.0669V17.8853H47.2368ZM47.2368 10.8739V7.4445H56.8192V10.8739H47.2368Z" fill="currentColor" fill-opacity="0.85"/> <path d="M10.9398 25.6841C9.60192 25.6841 8.3644 25.447 7.22718 24.9728C6.10671 24.4986 5.12003 23.8381 4.26712 22.9913C3.41424 22.1446 2.75365 21.1623 2.28539 20.0446C1.81714 18.9099 1.58301 17.6905 1.58301 16.3865C1.58301 15.0655 1.81714 13.8461 2.28539 12.7284C2.75365 11.6107 3.40587 10.6368 4.24206 9.80701C5.07821 8.96023 6.05654 8.3082 7.17703 7.85093C8.31422 7.37675 9.55174 7.13965 10.8896 7.13965C12.2108 7.13965 13.4316 7.37675 14.5521 7.85093C15.6893 8.3082 16.676 8.96023 17.5122 9.80701C18.365 10.6368 19.0256 11.6191 19.4939 12.7538C19.9621 13.8715 20.1963 15.0909 20.1963 16.4119C20.1963 17.7159 19.9621 18.9353 19.4939 20.07C19.0256 21.1877 18.3734 22.17 17.5372 23.0168C16.7011 23.8466 15.7144 24.4986 14.5772 24.9728C13.4567 25.447 12.2443 25.6841 10.9398 25.6841ZM10.8896 22.0514C11.9432 22.0514 12.863 21.8143 13.649 21.3401C14.4517 20.8659 15.0705 20.2054 15.5053 19.3587C15.9401 18.495 16.1576 17.5042 16.1576 16.3865C16.1576 15.5397 16.0321 14.7776 15.7813 14.1002C15.5304 13.4058 15.1709 12.8131 14.7026 12.3219C14.2344 11.8139 13.6741 11.4328 13.0219 11.1788C12.3864 10.9078 11.6757 10.7723 10.8896 10.7723C9.83606 10.7723 8.90791 11.0094 8.10518 11.4836C7.31917 11.9409 6.70876 12.5929 6.27395 13.4397C5.83913 14.2695 5.62175 15.2518 5.62175 16.3865C5.62175 17.2332 5.74716 18.0038 5.99802 18.6982C6.24886 19.3925 6.60007 19.9937 7.05159 20.5018C7.51986 20.9929 8.08009 21.374 8.73232 21.645C9.38451 21.9159 10.1036 22.0514 10.8896 22.0514ZM24.2015 25.3793V7.4445H26.9609L28.1399 11.0264V25.3793H24.2015ZM36.4181 25.3793L25.9825 11.8393L26.9609 7.4445L37.3963 20.9845L36.4181 25.3793ZM36.4181 25.3793L35.3644 21.7974V7.4445H39.3029V25.3793H36.4181ZM44.2267 25.3793V7.4445H48.1649V25.3793H44.2267ZM47.2368 25.3793V21.9244H56.9448V25.3793H47.2368ZM47.2368 17.8853V14.5574H56.0669V17.8853H47.2368ZM47.2368 10.8739V7.4445H56.8192V10.8739H47.2368Z" fill="currentColor" fill-opacity="0.85"/>
<path d="M69.2499 25.6588C67.9287 25.6588 66.6996 25.4302 65.5623 24.9729C64.4419 24.4987 63.4552 23.8382 62.6023 22.9915C61.7663 22.1447 61.1139 21.1624 60.6456 20.0447C60.1774 18.91 59.9434 17.6906 59.9434 16.3866C59.9434 15.0825 60.1774 13.8716 60.6456 12.7539C61.1139 11.6192 61.7663 10.6369 62.6023 9.80712C63.4386 8.97727 64.4168 8.33371 65.5373 7.87644C66.6746 7.40227 67.9037 7.16516 69.2248 7.16516C70.6296 7.16516 71.8756 7.3938 72.9625 7.85104C74.0663 8.30831 75.0279 8.93493 75.8473 9.7309L73.2134 12.4236C72.7452 11.9156 72.1766 11.5176 71.5076 11.2297C70.8555 10.9418 70.0946 10.7979 69.2248 10.7979C68.4556 10.7979 67.7448 10.9333 67.0927 11.2043C66.4572 11.4583 65.9053 11.8309 65.437 12.322C64.9854 12.8132 64.626 13.4059 64.3583 14.1003C64.1074 14.7946 63.9821 15.5567 63.9821 16.3866C63.9821 17.2334 64.1074 18.0039 64.3583 18.6983C64.626 19.3926 64.9854 19.9854 65.437 20.4765C65.9053 20.9676 66.4572 21.3487 67.0927 21.6197C67.7448 21.8907 68.4556 22.0261 69.2248 22.0261C70.1279 22.0261 70.914 21.8822 71.5828 21.5943C72.2518 21.3064 72.8288 20.9084 73.3137 20.4003L75.9729 23.0931C75.12 23.8891 74.1415 24.5156 73.0377 24.9729C71.9508 25.4302 70.6882 25.6588 69.2499 25.6588ZM79.6466 25.3794V7.44461H83.5851V25.3794H79.6466ZM82.6821 25.3794V21.8737H91.4367V25.3794H82.6821ZM95.2131 25.3794V7.44461H99.1515V25.3794H95.2131ZM104.067 25.3794V7.44461H108.006V25.3794H104.067ZM107.078 25.3794V21.9245H116.786V25.3794H107.078ZM107.078 17.8854V14.5575H115.908V17.8854H107.078ZM107.078 10.874V7.44461H116.66V10.874H107.078ZM121.006 25.3794V7.44461H123.765L124.944 11.0265V25.3794H121.006ZM133.222 25.3794L122.787 11.8394L123.765 7.44461L134.201 20.9846L133.222 25.3794ZM133.222 25.3794L132.169 21.7975V7.44461H136.107V25.3794H133.222ZM145.371 25.3794V7.69862H149.309V25.3794H145.371ZM139.827 10.9249V7.44461H154.853V10.9249H139.827Z" fill="currentColor"/> <path d="M69.2499 25.6588C67.9287 25.6588 66.6996 25.4302 65.5623 24.9729C64.4419 24.4987 63.4552 23.8382 62.6023 22.9915C61.7663 22.1447 61.1139 21.1624 60.6456 20.0447C60.1774 18.91 59.9434 17.6906 59.9434 16.3866C59.9434 15.0825 60.1774 13.8716 60.6456 12.7539C61.1139 11.6192 61.7663 10.6369 62.6023 9.80712C63.4386 8.97727 64.4168 8.33371 65.5373 7.87644C66.6746 7.40227 67.9037 7.16516 69.2248 7.16516C70.6296 7.16516 71.8756 7.3938 72.9625 7.85104C74.0663 8.30831 75.0279 8.93493 75.8473 9.7309L73.2134 12.4236C72.7452 11.9156 72.1766 11.5176 71.5076 11.2297C70.8555 10.9418 70.0946 10.7979 69.2248 10.7979C68.4556 10.7979 67.7448 10.9333 67.0927 11.2043C66.4572 11.4583 65.9053 11.8309 65.437 12.322C64.9854 12.8132 64.626 13.4059 64.3583 14.1003C64.1074 14.7946 63.9821 15.5567 63.9821 16.3866C63.9821 17.2334 64.1074 18.0039 64.3583 18.6983C64.626 19.3926 64.9854 19.9854 65.437 20.4765C65.9053 20.9676 66.4572 21.3487 67.0927 21.6197C67.7448 21.8907 68.4556 22.0261 69.2248 22.0261C70.1279 22.0261 70.914 21.8822 71.5828 21.5943C72.2518 21.3064 72.8288 20.9084 73.3137 20.4003L75.9729 23.0931C75.12 23.8891 74.1415 24.5156 73.0377 24.9729C71.9508 25.4302 70.6882 25.6588 69.2499 25.6588ZM79.6466 25.3794V7.44461H83.5851V25.3794H79.6466ZM82.6821 25.3794V21.8737H91.4367V25.3794H82.6821ZM95.2131 25.3794V7.44461H99.1515V25.3794H95.2131ZM104.067 25.3794V7.44461H108.006V25.3794H104.067ZM107.078 25.3794V21.9245H116.786V25.3794H107.078ZM107.078 17.8854V14.5575H115.908V17.8854H107.078ZM107.078 10.874V7.44461H116.66V10.874H107.078ZM121.006 25.3794V7.44461H123.765L124.944 11.0265V25.3794H121.006ZM133.222 25.3794L122.787 11.8394L123.765 7.44461L134.201 20.9846L133.222 25.3794ZM133.222 25.3794L132.169 21.7975V7.44461H136.107V25.3794H133.222ZM145.371 25.3794V7.69862H149.309V25.3794H145.371ZM139.827 10.9249V7.44461H154.853V10.9249H139.827Z" fill="currentColor"/>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 848 B

After

Width:  |  Height:  |  Size: 847 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.2 KiB

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 840 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

View file

@ -47,8 +47,14 @@ mod tests {
#[test] #[test]
fn both_spellings_carry_the_folder() { fn both_spellings_carry_the_folder() {
assert_eq!(parse(&["--launch", "fabric-1-20"]).launch.as_deref(), Some("fabric-1-20")); assert_eq!(
assert_eq!(parse(&["--launch=fabric-1-20"]).launch.as_deref(), Some("fabric-1-20")); parse(&["--launch", "fabric-1-20"]).launch.as_deref(),
Some("fabric-1-20")
);
assert_eq!(
parse(&["--launch=fabric-1-20"]).launch.as_deref(),
Some("fabric-1-20")
);
} }
#[test] #[test]
@ -68,7 +74,12 @@ mod tests {
#[test] #[test]
fn unknown_flags_are_ignored_not_fatal() { fn unknown_flags_are_ignored_not_fatal() {
assert_eq!(parse(&["--verbose", "--launch", "x", "leftover"]).launch.as_deref(), Some("x")); assert_eq!(
parse(&["--verbose", "--launch", "x", "leftover"])
.launch
.as_deref(),
Some("x")
);
} }
#[test] #[test]

View file

@ -6,7 +6,6 @@ use freya::{
use oneclient_auth::MinecraftAccount; use oneclient_auth::MinecraftAccount;
use crate::{ use crate::{
ui::{divider},
Route, Route,
components::{Avatar, Button, Icon, IconType, OverlayPopup}, components::{Avatar, Button, Icon, IconType, OverlayPopup},
hooks::{ hooks::{
@ -14,6 +13,7 @@ use crate::{
use_accounts, use_current_account, use_dispatch, use_set_default_account, use_accounts, use_current_account, use_dispatch, use_set_default_account,
}, },
theme::colors, theme::colors,
ui::divider,
}; };
#[derive(PartialEq)] #[derive(PartialEq)]
@ -91,7 +91,7 @@ impl Component for AccountPanel {
.opacity(progress) .opacity(progress)
.margin(Gaps::new((1.0 - progress) * -8.0, 0., 0., 0.)) .margin(Gaps::new((1.0 - progress) * -8.0, 0., 0., 0.))
.background(colors::page_elevated().with_a(220)) .background(colors::page_elevated().with_a(220))
.blur(12.) .backdrop_blur(12.)
.corner_radius(CornerRadius::new_all(12.)) .corner_radius(CornerRadius::new_all(12.))
.border( .border(
Border::new() Border::new()
@ -107,11 +107,19 @@ impl Component for AccountPanel {
Color::from_argb(120, 0, 0, 0), Color::from_argb(120, 0, 0, 0),
))) )))
.child( .child(
label() rect()
.text("Accounts") .horizontal()
.font_size(18.) .width(Size::fill())
.font_weight(FontWeight::MEDIUM) .cross_align(Alignment::Center)
.a11y_role(AccessibilityRole::TitleBar), .spacing(6.)
.child(BackToControlCenter)
.child(
label()
.text("Accounts")
.font_size(18.)
.font_weight(FontWeight::MEDIUM)
.a11y_role(AccessibilityRole::TitleBar),
),
) )
.child(divider()) .child(divider())
.child(rows) .child(rows)
@ -120,6 +128,42 @@ impl Component for AccountPanel {
} }
} }
#[derive(PartialEq)]
struct BackToControlCenter;
impl Component for BackToControlCenter {
fn render(&self) -> impl IntoElement {
let dispatch = use_dispatch();
let mut hovered = use_state(|| false);
let back = move |_| {
dispatch.close_account_switcher();
dispatch.open_control_center();
};
rect()
.width(Size::px(28.))
.height(Size::px(28.))
.center()
.corner_radius(CornerRadius::new_all(8.))
.background(Color::RED.with_a(0))
.maybe(*hovered.read(), |el| {
el.background(colors::ghost_overlay_hover())
})
.cursor(CursorIcon::Pointer)
.a11y_role(AccessibilityRole::Button)
.a11y_alt("Back to control center")
.on_pointer_enter(move |_| hovered.set(true))
.on_pointer_leave(move |_| hovered.set(false))
.on_press(back)
.child(
Icon::new(IconType::ArrowLeft)
.size(18.)
.color(colors::fg_secondary()),
)
}
}
struct AccountRow { struct AccountRow {
account: MinecraftAccount, account: MinecraftAccount,
active: bool, active: bool,

View file

@ -4,12 +4,16 @@ use freya::router::RouterContext;
use crate::components::{Button, Icon, IconType}; use crate::components::{Button, Icon, IconType};
use oneclient_common::parse_mc_version; use oneclient_common::parse_mc_version;
use oneclient_core::clusters::ClusterKind;
use crate::hooks::{settled_or_loading, use_active_cluster_id, use_clusters, use_dispatch, use_game_snapshot, use_launcher, use_version_metadata}; use crate::hooks::{
settled_or_loading, use_active_cluster_id, use_clusters, use_dispatch, use_game_snapshot,
use_launcher, use_version_metadata,
};
use crate::routes::Route; use crate::routes::Route;
use crate::theme::colors; use crate::theme::colors;
use crate::utils::sort_clusters_for_home; use crate::utils::sort_clusters_for_home;
use crate::view::app::launch_button_state; use crate::view::app::{launch_button_state, launch_syncing, shared_dir_taken};
#[derive(PartialEq)] #[derive(PartialEq)]
pub struct ActiveClusterPanel; pub struct ActiveClusterPanel;
@ -21,7 +25,6 @@ impl Component for ActiveClusterPanel {
let dispatch = use_dispatch(); let dispatch = use_dispatch();
let game = use_game_snapshot(); let game = use_game_snapshot();
let launcher = use_launcher(); let launcher = use_launcher();
let syncing = launcher.fetching || launcher.syncing_bundles;
let clusters = settled_or_loading(&clusters_query).unwrap_or_default(); let clusters = settled_or_loading(&clusters_query).unwrap_or_default();
@ -72,11 +75,24 @@ impl Component for ActiveClusterPanel {
); );
}; };
let title = format!("{} {}", cluster.mc_version, cluster.mc_loader); let version = format!("{} {}", cluster.mc_version, cluster.mc_loader);
let subtitle = metadata let (title, subtitle) = if cluster.user_created && cluster.kind != ClusterKind::OneClient {
.map(|m| m.name) (cluster.name.clone(), version)
.unwrap_or_else(|| cluster.name.clone()); } else {
let subtitle = match metadata {
Some(m) => format!("OneClient · {}", m.name),
None => "OneClient".to_string(),
};
let title = if cluster.user_created {
cluster.name.clone()
} else {
version
};
(title, subtitle)
};
let cluster_id = cluster.id; let cluster_id = cluster.id;
let syncing = launch_syncing(&launcher, cluster.uses_bundles());
let shared_dir_taken = shared_dir_taken(&game, &sorted, cluster_id);
rect() rect()
.vertical() .vertical()
@ -88,6 +104,9 @@ impl Component for ActiveClusterPanel {
.child( .child(
label() label()
.text(title) .text(title)
.width(Size::fill())
.max_lines(1)
.text_overflow(TextOverflow::Ellipsis)
.font_size(56.) .font_size(56.)
.line_height(1.1) .line_height(1.1)
.font_weight(FontWeight::BOLD) .font_weight(FontWeight::BOLD)
@ -109,7 +128,7 @@ impl Component for ActiveClusterPanel {
.child(launch_button( .child(launch_button(
cluster_id, cluster_id,
dispatch, dispatch,
launch_button_state(&game, cluster_id, syncing), launch_button_state(&game, cluster_id, syncing, shared_dir_taken),
)) ))
.child(cluster_settings_button(cluster_id)), .child(cluster_settings_button(cluster_id)),
) )

View file

@ -0,0 +1,41 @@
use freya::elements::image::{AspectRatio, ImageCover, ImageHandle, image};
use freya::prelude::*;
use super::local_image::decode;
use crate::AppAssets;
#[derive(PartialEq)]
pub struct AssetImage {
path: &'static str,
size: f32,
}
impl AssetImage {
pub fn new(path: &'static str, size: f32) -> Self {
Self { path, size }
}
}
impl Component for AssetImage {
fn render(&self) -> impl IntoElement {
let path = self.path;
let size = self.size;
let handle: Option<ImageHandle> =
use_hook(move || AppAssets::get_bytes(path).as_ref().and_then(decode));
rect()
.key(path)
.width(Size::px(size))
.height(Size::px(size))
.center()
.maybe_child(handle.map(|handle| {
image(handle)
.width(Size::fill())
.height(Size::fill())
.aspect_ratio(AspectRatio::Min)
.image_cover(ImageCover::Center)
.into_element()
}))
}
}

View file

@ -0,0 +1,528 @@
use std::path::{Path, PathBuf};
use freya::prelude::*;
use freya::router::RouterContext;
use notify::{Event, EventKind};
use oneclient_content::modpacks::BlockedFile;
use crate::components::{Button, Icon, IconType, OverlayPopup};
use crate::hooks::{use_dispatch, use_folder_watch, use_notifications_snapshot};
use crate::notifications::BlockedDownloads;
use crate::routes::Route;
use crate::theme::colors;
use crate::ui::border_all_color;
const CARD_BG: Color = Color::from_rgb(26, 34, 41);
const DIALOG_W: f32 = 560.;
const DIALOG_PAD: f32 = 22.;
const LIST_MAX_H: f32 = 300.;
const ROW_H: f32 = 52.;
#[derive(PartialEq)]
pub struct BlockedDownloadsPopup;
impl Component for BlockedDownloadsPopup {
fn render(&self) -> impl IntoElement {
let snapshot = use_notifications_snapshot();
let dispatch = use_dispatch();
let router = RouterContext::get();
use_side_effect_with_deps(&snapshot.open_cluster, move |requested| {
if requested.is_none() {
return;
}
if let Some(cluster_id) = dispatch.take_open_cluster() {
let _ = router.push(Route::ClusterOverview { cluster_id });
}
});
match snapshot.blocked_downloads {
Some(blocked) => BlockedDownloadsDialog { blocked }.into_element(),
None => rect().into_element(),
}
}
}
fn downloads_dir() -> Option<PathBuf> {
directories::UserDirs::new().and_then(|dirs| dirs.download_dir().map(Path::to_path_buf))
}
fn touches_file(_root: &Path, event: &Event) -> bool {
matches!(event.kind, EventKind::Create(_) | EventKind::Modify(_))
}
type ExtraFolder = State<Option<(i64, PathBuf)>>;
fn bump(mut tick: State<u64>) {
let next = *tick.peek() + 1;
tick.set(next);
}
#[derive(PartialEq)]
struct ExtraFolderWatch {
folder: PathBuf,
tick: State<u64>,
key: DiffKey,
}
impl KeyExt for ExtraFolderWatch {
fn write_key(&mut self) -> &mut DiffKey {
&mut self.key
}
}
impl Component for ExtraFolderWatch {
fn render(&self) -> impl IntoElement {
let tick = self.tick;
use_folder_watch(Some(self.folder.clone()), false, touches_file, move || {
bump(tick)
});
rect()
}
fn render_key(&self) -> DiffKey {
self.key.clone().or(self.default_key())
}
}
#[derive(PartialEq)]
struct BlockedDownloadsDialog {
blocked: BlockedDownloads,
}
impl Component for BlockedDownloadsDialog {
fn render(&self) -> impl IntoElement {
let dispatch = use_dispatch();
let downloads = use_hook(downloads_dir);
let tick = use_state(|| 0u64);
let extra: ExtraFolder = use_state(|| None);
use_folder_watch(downloads.clone(), false, touches_file, move || bump(tick));
let cluster_id = self.blocked.cluster_id;
let chosen = extra
.read()
.as_ref()
.filter(|(owner, _)| *owner == cluster_id)
.map(|(_, folder)| folder.clone());
let deps = (
*tick.read(),
cluster_id,
self.blocked.remaining(),
chosen.clone(),
);
{
let dispatch = dispatch.clone();
let downloads = downloads.clone();
use_side_effect_with_deps(&deps, move |(_, cluster_id, files, chosen)| {
if files.is_empty() {
return;
}
let locations: Vec<PathBuf> =
downloads.iter().chain(chosen.iter()).cloned().collect();
if !locations.is_empty() {
dispatch.scan_blocked_downloads(*cluster_id, locations, files.clone());
}
});
}
let close = dispatch.clone();
OverlayPopup::new()
.on_close(move |_| close.close_blocked_downloads())
.child(
rect()
.width(Size::window_percent(100.))
.height(Size::window_percent(100.))
.center()
.child(dialog(
&self.blocked,
downloads.as_deref(),
chosen.as_deref(),
extra,
dispatch,
)),
)
.maybe_child(chosen.map(|folder| {
let key = folder.to_string_lossy().into_owned();
ExtraFolderWatch {
folder,
tick,
key: DiffKey::None,
}
.key(key)
.into_element()
}))
.into_element()
}
}
fn folder_label(path: &Path) -> String {
path.file_name()
.map(|name| name.to_string_lossy().into_owned())
.unwrap_or_else(|| path.display().to_string())
}
fn watched_text(downloads: Option<&Path>, chosen: Option<&Path>) -> String {
match (downloads, chosen) {
(Some(_), Some(chosen)) => format!("Downloads and {}", chosen.display()),
(Some(downloads), None) => downloads.display().to_string(),
(None, Some(chosen)) => chosen.display().to_string(),
(None, None) => "Nothing yet. Add a folder or use Browse.".to_string(),
}
}
fn pick_extra_folder(cluster_id: i64, start: Option<PathBuf>, mut extra: ExtraFolder) {
spawn(async move {
let mut picker = rfd::AsyncFileDialog::new().set_title("Pick the folder you download into");
if let Some(start) = start {
picker = picker.set_directory(start);
}
if let Some(handle) = picker.pick_folder().await {
extra.set(Some((cluster_id, handle.path().to_path_buf())));
}
});
}
fn folder_row(
cluster_id: i64,
downloads: Option<&Path>,
chosen: Option<&Path>,
extra: ExtraFolder,
) -> impl IntoElement {
let start = chosen
.map(Path::to_path_buf)
.or_else(|| downloads.map(Path::to_path_buf));
let has_extra = chosen.is_some();
rect()
.vertical()
.width(Size::fill())
.spacing(6.)
.child(
rect()
.horizontal()
.width(Size::fill())
.content(Content::Flex)
.cross_align(Alignment::Center)
.padding(Gaps::new_symmetric(8., 12.))
.spacing(10.)
.corner_radius(CornerRadius::new_all(10.))
.background(colors::component_bg())
.border(border_all_color(1., colors::component_border()))
.child(
Icon::new(IconType::FolderCheck)
.size(16.)
.color(colors::fg_secondary()),
)
.child(
rect()
.vertical()
.width(Size::flex(1.))
.spacing(2.)
.child(
label()
.text("Watching")
.font_size(11.)
.color(colors::fg_secondary()),
)
.child(
label()
.text(watched_text(downloads, chosen))
.font_size(12.5)
.max_lines(2)
.color(colors::fg_primary()),
),
)
.child(
Button::new()
.secondary()
.small()
.on_press(move |_| pick_extra_folder(cluster_id, start.clone(), extra))
.text(if has_extra {
"Change folder"
} else {
"Add folder"
}),
)
.maybe_child(has_extra.then(|| {
let mut extra = extra;
Button::new()
.ghost()
.small()
.on_press(move |_| extra.set(None))
.text("Remove")
.into_element()
})),
)
.child(
label()
.text(
"Your browser saves to its own download folder. To save somewhere else, \
set it to ask where to save each file.",
)
.font_size(11.)
.max_lines(2)
.color(colors::fg_secondary()),
)
}
fn dialog(
blocked: &BlockedDownloads,
downloads: Option<&Path>,
chosen: Option<&Path>,
extra: ExtraFolder,
dispatch: crate::Actions,
) -> impl IntoElement {
let count = blocked.files.len();
let added = blocked.added.len().min(count);
let done = added == count;
let plural = crate::utils::plural(count as i64);
let where_to = match (downloads, chosen) {
(_, Some(chosen)) => format!(
"Download each one from its page and it will be picked up from Downloads or {} automatically.",
folder_label(chosen)
),
(Some(_), None) => {
"Download each one from its page and it will be picked up from your Downloads folder automatically."
.to_string()
}
(None, None) => {
"Download each one from its page, then add the folder you saved them to or use Browse."
.to_string()
}
};
let subtitle = if done {
format!(
"All {count} file{plural} are in {}. It is ready to play.",
blocked.cluster_name
)
} else {
format!(
"CurseForge does not let launchers download {count} file{plural} that {} needs. {where_to}",
blocked.cluster_name
)
};
let title = if done {
"Manual downloads added".to_string()
} else if added > 0 {
format!("Some files need a manual download ({added} of {count} added)")
} else {
"Some files need a manual download".to_string()
};
let cluster_id = blocked.cluster_id;
let files = blocked.remaining();
let browse = dispatch.clone();
let close = dispatch.clone();
let browse_start = chosen
.map(Path::to_path_buf)
.or_else(|| downloads.map(Path::to_path_buf));
rect()
.vertical()
.width(Size::px(DIALOG_W))
.height(Size::Inner)
.max_width(Size::window_percent(95.))
.max_height(Size::window_percent(85.))
.overflow(Overflow::Clip)
.corner_radius(CornerRadius::new_all(16.))
.background(CARD_BG)
.border(border_all_color(1., colors::component_border()))
.shadow(Shadow::from((
0.,
18.,
52.,
0.,
Color::from_argb(150, 0, 0, 0),
)))
.child(
rect()
.vertical()
.width(Size::fill())
.height(Size::Inner)
.padding(Gaps::new_all(DIALOG_PAD))
.spacing(14.)
.child(
rect()
.vertical()
.width(Size::fill())
.spacing(3.)
.child(
label()
.text(title)
.font_size(17.)
.font_weight(FontWeight::SEMI_BOLD)
.color(colors::fg_primary()),
)
.child(
label()
.text(subtitle)
.font_size(12.5)
.max_lines(4)
.color(colors::fg_secondary()),
),
)
.maybe_child(
(!done)
.then(|| folder_row(cluster_id, downloads, chosen, extra).into_element()),
)
.child(file_list(blocked))
.child(if done {
let close = close.clone();
rect()
.horizontal()
.width(Size::fill())
.main_align(Alignment::End)
.child(
Button::new()
.primary()
.on_press(move |_| close.close_blocked_downloads())
.child(Icon::new(IconType::Check).size(15.))
.text("Done"),
)
.into_element()
} else {
footer(cluster_id, files, browse_start, browse, close).into_element()
}),
)
}
fn footer(
cluster_id: i64,
files: Vec<BlockedFile>,
browse_start: Option<PathBuf>,
browse: crate::Actions,
close: crate::Actions,
) -> impl IntoElement {
rect()
.horizontal()
.width(Size::fill())
.cross_align(Alignment::Center)
.main_align(Alignment::End)
.spacing(8.)
.child(
Button::new()
.ghost()
.on_press(move |_| close.close_blocked_downloads())
.text("Skip for now"),
)
.child(
Button::new()
.primary()
.on_press(move |_| {
let dispatch = browse.clone();
let files = files.clone();
let start = browse_start.clone();
spawn(async move {
let mut picker =
rfd::AsyncFileDialog::new().set_title("Pick the downloaded files");
if let Some(start) = start {
picker = picker.set_directory(start);
}
if let Some(handles) = picker.pick_files().await {
let picked = handles
.iter()
.map(|handle| handle.path().to_path_buf())
.collect();
dispatch.scan_blocked_downloads(cluster_id, picked, files);
}
});
})
.child(Icon::new(IconType::Folder).size(15.))
.text("Browse"),
)
}
fn file_list(blocked: &BlockedDownloads) -> impl IntoElement {
ScrollView::new()
.width(Size::fill())
.height(Size::Inner)
.max_height(Size::px(LIST_MAX_H))
.child(
rect().vertical().width(Size::fill()).spacing(6.).children(
blocked
.files
.iter()
.map(|file| file_row(file, blocked.is_added(file)).into_element()),
),
)
}
fn file_row(file: &BlockedFile, added: bool) -> impl IntoElement {
let page = file.page_url.clone();
let (background, border) = if added {
(colors::success().with_a(28), colors::success().with_a(120))
} else {
(colors::component_bg(), colors::component_border())
};
let trailing = if added {
rect()
.horizontal()
.cross_align(Alignment::Center)
.spacing(6.)
.padding(Gaps::new_symmetric(0., 4.))
.child(
Icon::new(IconType::CheckCircle)
.size(14.)
.color(colors::success()),
)
.child(
label()
.text("Added")
.font_size(12.)
.font_weight(FontWeight::SEMI_BOLD)
.color(colors::success()),
)
.into_element()
} else {
Button::new()
.secondary()
.small()
.enabled(page.is_some())
.on_press(move |_| {
if let Some(url) = &page {
crate::platform::open_url(url);
}
})
.child(Icon::new(IconType::LinkExternal01).size(13.))
.text("Open page")
.into_element()
};
rect()
.key(file.sha1.clone())
.horizontal()
.width(Size::fill())
.height(Size::px(ROW_H))
.content(Content::Flex)
.cross_align(Alignment::Center)
.padding(Gaps::new_symmetric(0., 12.))
.spacing(10.)
.corner_radius(CornerRadius::new_all(10.))
.background(background)
.border(border_all_color(1., border))
.child(
rect()
.vertical()
.width(Size::flex(1.))
.spacing(2.)
.child(
label()
.text(file.project_name.clone())
.font_size(13.)
.font_weight(FontWeight::SEMI_BOLD)
.max_lines(1)
.color(colors::fg_primary()),
)
.child(
label()
.text(file.file_name.clone())
.font_size(11.)
.max_lines(1)
.color(colors::fg_secondary()),
),
)
.child(trailing)
}

View file

@ -0,0 +1,157 @@
use std::collections::HashSet;
use freya::prelude::*;
use crate::components::{Button, Icon, IconType, OverlayPopup, toggle_controlled};
use crate::hooks::{use_dispatch, use_notifications_snapshot};
use crate::theme::colors;
use crate::ui::border_all_color;
const CARD_BG: Color = Color::from_rgb(26, 34, 41);
const DIALOG_W: f32 = 460.;
const LIST_MAX_H: f32 = 306.;
#[derive(PartialEq)]
pub struct BundleChoicesPopup;
impl Component for BundleChoicesPopup {
fn render(&self) -> impl IntoElement {
let snapshot = use_notifications_snapshot();
let dispatch = use_dispatch();
let mut chosen = use_state(HashSet::<String>::new);
let Some(choices) = snapshot.bundle_choices.clone() else {
return rect().into_element();
};
let mut list = rect().vertical().width(Size::fill()).spacing(6.);
for (name, title) in &choices.bundles {
let key = name.clone();
let on_toggle: EventHandler<()> = (move |()| {
let mut next = chosen.read().clone();
if !next.remove(&key) {
next.insert(key.clone());
}
chosen.set(next);
})
.into();
list = list.child(
rect()
.key(name.clone())
.horizontal()
.width(Size::fill())
.content(Content::Flex)
.cross_align(Alignment::Center)
.padding(Gaps::new_symmetric(8., 10.))
.corner_radius(CornerRadius::new_all(8.))
.background(colors::component_bg())
.child(
label()
.text(title.clone())
.font_size(13.)
.max_lines(1)
.width(Size::flex(1.0))
.color(colors::fg_primary()),
)
.child(toggle_controlled(
choices.held.contains(name) != chosen.read().contains(name),
on_toggle,
)),
);
}
let names: Vec<String> = choices
.bundles
.iter()
.map(|(name, _)| name.clone())
.collect();
let held = choices.held.clone();
let later = dispatch.clone();
let close = dispatch.clone();
OverlayPopup::new()
.on_close(move |_| {
chosen.set(HashSet::new());
close.close_bundle_choices(None);
})
.child(
rect()
.width(Size::window_percent(100.))
.height(Size::window_percent(100.))
.center()
.child(
rect()
.vertical()
.width(Size::px(DIALOG_W))
.height(Size::Inner)
.max_width(Size::window_percent(95.))
.max_height(Size::window_percent(85.))
.padding(Gaps::new_all(22.))
.spacing(14.)
.corner_radius(CornerRadius::new_all(16.))
.background(CARD_BG)
.border(border_all_color(1., colors::component_border()))
.child(
label()
.text("Choose your bundles")
.font_size(17.)
.font_weight(FontWeight::SEMI_BOLD)
.color(colors::fg_primary()),
)
.child(
label()
.text(format!(
"{} has optional bundles you have not chosen yet. Turn on the ones you want, turning one off removes its mods.",
choices.cluster_name
))
.font_size(12.5)
.max_lines(4)
.color(colors::fg_secondary()),
)
.child(
ScrollView::new()
.width(Size::fill())
.height(Size::Inner)
.max_height(Size::px(LIST_MAX_H))
.child(list),
)
.child(
rect()
.horizontal()
.width(Size::fill())
.main_align(Alignment::End)
.spacing(8.)
.child(
Button::new()
.ghost()
.on_press(move |_| {
chosen.set(HashSet::new());
later.close_bundle_choices(None);
})
.text("Ask me later"),
)
.child(
Button::new()
.primary()
.on_press(move |_| {
let flipped = chosen.read().clone();
let picked = names
.iter()
.filter(|name| {
held.contains(*name)
!= flipped.contains(*name)
})
.cloned()
.collect();
chosen.set(HashSet::new());
dispatch.close_bundle_choices(Some(picked));
})
.child(Icon::new(IconType::Check).size(15.))
.text("Confirm & launch"),
),
),
),
)
.into_element()
}
}

View file

@ -56,6 +56,7 @@ pub struct Button {
tooltip: Option<Box<str>>, tooltip: Option<Box<str>>,
tooltip_placement: TooltipPlacement, tooltip_placement: TooltipPlacement,
on_press: Option<EventHandler<Event<PressEventData>>>, on_press: Option<EventHandler<Event<PressEventData>>>,
overflow: Overflow,
elements: Vec<Element>, elements: Vec<Element>,
key: DiffKey, key: DiffKey,
@ -117,6 +118,7 @@ impl Button {
tooltip_placement: TooltipPlacement::default(), tooltip_placement: TooltipPlacement::default(),
on_press: None, on_press: None,
elements: Vec::new(), elements: Vec::new(),
overflow: Overflow::Clip,
key: DiffKey::None, key: DiffKey::None,
cursor_icon: CursorIcon::Pointer, cursor_icon: CursorIcon::Pointer,
} }
@ -156,6 +158,11 @@ impl Button {
self self
} }
pub fn overflow(mut self, overflow: Overflow) -> Self {
self.overflow = overflow;
self
}
pub fn font_size(mut self, font_size: impl Into<f32>) -> Self { pub fn font_size(mut self, font_size: impl Into<f32>) -> Self {
self.text_style.font_size = Some(font_size.into().into()); self.text_style.font_size = Some(font_size.into().into());
self self
@ -251,6 +258,8 @@ impl LayoutExt for Button {
impl ContainerExt for Button {} impl ContainerExt for Button {}
impl DecorationExt for Button {}
impl StyleExt for Button { impl StyleExt for Button {
fn get_style(&mut self) -> &mut StyleState { fn get_style(&mut self) -> &mut StyleState {
&mut self.style &mut self.style
@ -314,7 +323,7 @@ impl Component for Button {
.a11y_focusable(enabled() && self.focusable) .a11y_focusable(enabled() && self.focusable)
.a11y_role(AccessibilityRole::Button) .a11y_role(AccessibilityRole::Button)
.map(self.alt.clone(), |rect, alt| rect.a11y_alt(alt)) .map(self.alt.clone(), |rect, alt| rect.a11y_alt(alt))
.overflow(Overflow::Clip) .overflow(self.overflow)
.layout(self.layout.clone()) .layout(self.layout.clone())
.text_style(self.text_style.clone()) .text_style(self.text_style.clone())
.children(self.elements.clone()); .children(self.elements.clone());
@ -339,7 +348,7 @@ impl Component for Button {
if enabled() { if enabled() {
rect = rect rect = rect
.on_pointer_down(move |_| pressing.set(true)) .on_pointer_down(move |_| pressing.set(true))
.on_global_pointer_press(move |_| pressing.set(false)) .on_global_pointer_up(move |_| pressing.set(false))
.on_pointer_over(move |_| hovering.set(true)) .on_pointer_over(move |_| hovering.set(true))
.on_pointer_out(move |_| { .on_pointer_out(move |_| {
hovering.set(false); hovering.set(false);
@ -471,3 +480,29 @@ pub fn open_folder_button(folder: std::path::PathBuf) -> Button {
}) })
.child(Icon::new(IconType::Folder).size(16.)) .child(Icon::new(IconType::Folder).size(16.))
} }
pub fn copy_button(text: String, copied_body: &'static str, dispatch: crate::Actions) -> Button {
Button::new()
.secondary()
.on_press(move |_| {
if let Err(err) = freya::text_edit::Clipboard::set(text.clone()) {
tracing::warn!("clipboard copy failed: {err:?}");
dispatch
.notify("Copy failed")
.body("Could not copy to the clipboard.")
.error()
.toast_only()
.send();
} else {
dispatch
.notify("Copied to clipboard")
.body(copied_body)
.info()
.icon(IconType::ClipboardCheck)
.toast_only()
.send();
}
})
.child(Icon::new(IconType::Copy01).size(14.))
.text("Copy")
}

View file

@ -93,7 +93,7 @@ impl Component for PieChart {
let values = self.values.clone(); let values = self.values.clone();
let hit_values = self.values.clone(); let hit_values = self.values.clone();
let render_cb = RenderCallback::new(move |ctx: &mut CanvasContext| { let render_cb = RenderCallback::new(move |ctx: &mut FillRenderContext| {
let (w, h) = (ctx.size.width, ctx.size.height); let (w, h) = (ctx.size.width, ctx.size.height);
if w <= 0.0 || h <= 0.0 { if w <= 0.0 || h <= 0.0 {
return; return;
@ -182,8 +182,13 @@ impl Component for PieChart {
.height(Size::fill()) .height(Size::fill())
.position(Position::new_absolute()) .position(Position::new_absolute())
.interactive(false) .interactive(false)
// Childless canvas auto-sizes to 0x0 without an explicit size // Childless rect auto-sizes to 0x0 without an explicit size
.child(canvas(render_cb).width(Size::fill()).height(Size::fill())), .child(
rect()
.width(Size::fill())
.height(Size::fill())
.background(render_cb),
),
) )
.child( .child(
rect() rect()
@ -368,7 +373,7 @@ impl Component for BarChart {
rect() rect()
.width(Size::fill()) .width(Size::fill())
.height(Size::px(h)) .height(Size::px(h))
.corner_radius(CornerRadius::new(4., 4., 0., 0.)) .corner_radius(CornerRadius::new(4., 4., 0., 0.))
.background(color), .background(color),
), ),
); );

View file

@ -4,7 +4,9 @@ use freya::{prelude::*, router::RouterContext};
use oneclient_content::packages::{CachedPackageMeta, ProviderId}; use oneclient_content::packages::{CachedPackageMeta, ProviderId};
use crate::components::{Button, Icon, IconType, OverlayPopup, ScrollArea, TabBar, TabItem}; use crate::components::{Button, Icon, IconType, OverlayPopup, ScrollArea, TabBar, TabItem};
use crate::hooks::{package_meta_batch, use_dispatch, use_notifications_snapshot, use_package_meta_batch}; use crate::hooks::{
package_meta_batch, use_dispatch, use_notifications_snapshot, use_package_meta_batch,
};
use crate::notifications::{ClusterUpdateItem, ClusterUpdateSummary}; use crate::notifications::{ClusterUpdateItem, ClusterUpdateSummary};
use crate::routes::Route; use crate::routes::Route;
use crate::theme::colors; use crate::theme::colors;
@ -108,7 +110,11 @@ impl Component for ClusterUpdatePopup {
}) })
.collect(); .collect();
let mut meta = MetaMap::new(); let mut meta = MetaMap::new();
for provider in ProviderId::REMOTE_PROVIDERS.iter().copied() { for provider in ProviderId::REMOTE_PROVIDERS
.iter()
.copied()
.chain([ProviderId::Local])
{
let ids: Vec<String> = all_items let ids: Vec<String> = all_items
.iter() .iter()
.filter(|i| i.provider == provider) .filter(|i| i.provider == provider)
@ -355,11 +361,7 @@ fn change_list(
} }
/// Multi-cluster stand-in for the footer's "Open cluster" shortcut /// Multi-cluster stand-in for the footer's "Open cluster" shortcut
fn cluster_header( fn cluster_header(group: &ClusterGroup, first: bool, dispatch: crate::Actions) -> impl IntoElement {
group: &ClusterGroup,
first: bool,
dispatch: crate::Actions,
) -> impl IntoElement {
let cluster_id = group.cluster_id; let cluster_id = group.cluster_id;
let count = group.names.len(); let count = group.names.len();

View file

@ -0,0 +1,107 @@
use std::borrow::Cow;
use freya::prelude::*;
use crate::components::ScrollArea;
use crate::theme::{self, colors};
use crate::ui::border_all_color;
#[derive(PartialEq)]
pub struct CodeBlock {
code: String,
color: Color,
background: Color,
font_family: Cow<'static, str>,
font_size: f32,
max_lines: Option<usize>,
height: Option<f32>,
key: DiffKey,
}
impl KeyExt for CodeBlock {
fn write_key(&mut self) -> &mut DiffKey {
&mut self.key
}
}
impl CodeBlock {
pub fn new(code: impl Into<String>) -> Self {
Self {
code: code.into(),
color: colors::fg_primary(),
background: colors::component_bg(),
font_family: Cow::Borrowed(theme::MONO_FONT),
font_size: 12.,
max_lines: None,
height: None,
key: DiffKey::None,
}
}
pub fn color(mut self, color: Color) -> Self {
self.color = color;
self
}
pub fn background(mut self, background: Color) -> Self {
self.background = background;
self
}
pub fn font_family(mut self, font_family: Cow<'static, str>) -> Self {
self.font_family = font_family;
self
}
pub fn font_size(mut self, font_size: f32) -> Self {
self.font_size = font_size;
self
}
pub fn max_lines(mut self, max_lines: usize) -> Self {
self.max_lines = Some(max_lines);
self
}
/// Fixes the block's height and scrolls the code inside it
pub fn height(mut self, height: f32) -> Self {
self.height = Some(height);
self
}
}
impl Component for CodeBlock {
fn render(&self) -> impl IntoElement {
let text = SelectableText::new()
.span(self.code.clone())
.font_family(self.font_family.clone())
.font_size(self.font_size)
.max_lines(self.max_lines)
.width(Size::fill())
.color(self.color);
let block = rect()
.width(Size::fill())
.padding(Gaps::new_all(12.))
.corner_radius(CornerRadius::new_all(8.))
.background(self.background)
.border(border_all_color(1., colors::component_border()));
match self.height {
Some(height) => block
.height(Size::px(height))
.overflow(Overflow::Clip)
.child(
ScrollArea::new()
.width(Size::fill())
.height(Size::fill())
.child(text),
),
None => block.child(text),
}
}
fn render_key(&self) -> DiffKey {
self.key.clone().or(self.default_key())
}
}

View file

@ -0,0 +1,626 @@
use freya::{
animation::{AnimNum, Ease, OnCreation, use_animation},
prelude::*,
router::RouterContext,
};
use crate::{
Route,
components::{Avatar, Button, Icon, IconType, OVERLAY_MAX_LEVEL, OverlayPopup},
hooks::{
RemoveAccountKeys, query_error, query_is_loading, try_accounts, try_default_account,
use_accounts, use_control_center_open, use_current_account, use_dispatch,
use_remove_account, use_settings_snapshot,
},
theme::colors,
ui::{border_all_color, divider},
};
const PANEL_WIDTH: f32 = 344.;
#[derive(PartialEq)]
pub struct ControlCenter;
impl Component for ControlCenter {
fn render(&self) -> impl IntoElement {
let open = use_control_center_open();
let dispatch = use_dispatch();
if !open {
return rect().into_element();
}
OverlayPopup::new()
.position(Position::new_global().top(72.).right(40.))
.on_close(move |()| dispatch.close_control_center())
.child(ControlPanel)
.into_element()
}
}
#[derive(PartialEq)]
struct ControlPanel;
impl Component for ControlPanel {
fn render(&self) -> impl IntoElement {
let intro = use_animation(|conf| {
conf.on_creation(OnCreation::Run);
AnimNum::new(0., 1.).time(200).ease(Ease::Out)
});
let progress = intro.read().value();
rect()
.vertical()
.width(Size::px(PANEL_WIDTH))
.padding(Gaps::new_all(12.))
.spacing(10.)
.opacity(progress)
.margin(Gaps::new((1.0 - progress) * -8.0, 0., 0., 0.))
.background(colors::page_elevated().with_a(220))
.backdrop_blur(12.)
.corner_radius(CornerRadius::new_all(14.))
.border(
Border::new()
.fill(colors::component_border())
.width(1.)
.alignment(BorderAlignment::Inner),
)
.shadow(Shadow::from((
0.,
8.,
32.,
0.,
Color::from_argb(120, 0, 0, 0),
)))
.child(AccountHeader)
.child(divider())
.child(
rect()
.vertical()
.width(Size::fill())
.spacing(2.)
.child(NavRow {
icon: IconType::BarChartSquare02,
row_label: "Stats",
target: Route::Stats {},
})
.child(NavRow {
icon: IconType::Settings02,
row_label: "Settings",
target: Route::SettingsLauncher {},
}),
)
.child(section_label("QUICK SETTINGS"))
.child(QuickSettings)
.child(divider())
.child(PanelFooter)
}
}
fn section_label(text: &'static str) -> impl IntoElement {
label()
.text(text)
.font_size(11.)
.letter_spacing(0.8)
.font_weight(FontWeight::SEMI_BOLD)
.color(colors::fg_secondary())
}
#[derive(PartialEq)]
struct AccountHeader;
impl Component for AccountHeader {
fn render(&self) -> impl IntoElement {
let current = use_current_account();
let accounts_query = use_accounts();
let dispatch = use_dispatch();
let mut hovered = use_state(|| false);
let loading = query_is_loading(&current);
let unusable = query_error(&current).is_some();
let account = try_default_account(&current).or_else(|| {
unusable
.then(|| {
try_accounts(&accounts_query)
.unwrap_or_default()
.into_iter()
.next()
})
.flatten()
});
let uuid = account
.as_ref()
.map(|account| account.id.to_string())
.unwrap_or_else(|| uuid::Uuid::nil().to_string());
let username = match account.as_ref() {
Some(account) => account.username.clone(),
None if loading => "Loading…".to_string(),
None => "Not signed in".to_string(),
};
let subtitle = match (account.as_ref(), unusable) {
(_, true) => "Microsoft Account required",
(Some(account), false) if account.is_microsoft() => "Microsoft account",
(Some(_), false) => "Offline account",
(None, false) if loading => "Checking accounts",
(None, false) => "No account selected",
};
let subtitle_color = if unusable {
colors::danger()
} else {
colors::fg_secondary()
};
let signed_out = account.is_none() && !loading;
let action_label = if signed_out { "Sign In" } else { "Switch" };
let open_switcher = move |_| {
dispatch.close_control_center();
if signed_out {
let _ = RouterContext::get().push(Route::SettingsAccounts {});
} else {
dispatch.open_account_switcher();
}
};
rect()
.horizontal()
.width(Size::fill())
.content(Content::Flex)
.cross_align(Alignment::Center)
.spacing(10.)
.padding(Gaps::new_all(4.))
.child(Avatar::new(uuid).width(Size::px(40.)).height(Size::px(40.)))
.child(
rect()
.vertical()
.width(Size::flex(1.0))
.spacing(2.)
.child(
label()
.text(username)
.font_size(16.)
.font_weight(FontWeight::MEDIUM)
.max_lines(1)
.color(colors::fg_primary()),
)
.child(
label()
.text(subtitle)
.font_size(12.)
.max_lines(1)
.color(subtitle_color),
),
)
.child(
rect()
.horizontal()
.cross_align(Alignment::Center)
.spacing(6.)
.padding(Gaps::new_symmetric(6., 8.))
.corner_radius(CornerRadius::new_all(8.))
.background(Color::RED.with_a(0))
.maybe(*hovered.read(), |el| {
el.background(colors::ghost_overlay_hover())
})
.cursor(CursorIcon::Pointer)
.a11y_role(AccessibilityRole::Button)
.on_pointer_enter(move |_| hovered.set(true))
.on_pointer_leave(move |_| hovered.set(false))
.on_press(open_switcher)
.child(
label()
.text(action_label)
.font_size(13.)
.color(colors::fg_secondary()),
)
.child(
Icon::new(IconType::ArrowRight)
.size(14.)
.color(colors::fg_secondary()),
),
)
}
}
#[derive(PartialEq)]
struct NavRow {
icon: IconType,
row_label: &'static str,
target: Route,
}
impl Component for NavRow {
fn render(&self) -> impl IntoElement {
let dispatch = use_dispatch();
let mut hovered = use_state(|| false);
let target = self.target.clone();
let go = move |_| {
dispatch.close_control_center();
let _ = RouterContext::get().push(target.clone());
};
rect()
.horizontal()
.width(Size::fill())
.height(Size::px(42.))
.content(Content::Flex)
.cross_align(Alignment::Center)
.spacing(12.)
.padding(Gaps::new_symmetric(0., 8.))
.corner_radius(CornerRadius::new_all(8.))
.background(Color::RED.with_a(0))
.maybe(*hovered.read(), |el| {
el.background(colors::ghost_overlay_hover())
})
.cursor(CursorIcon::Pointer)
.a11y_role(AccessibilityRole::Button)
.on_pointer_enter(move |_| hovered.set(true))
.on_pointer_leave(move |_| hovered.set(false))
.on_press(go)
.child(Icon::new(self.icon).size(20.).color(colors::fg_secondary()))
.child(
label()
.text(self.row_label)
.font_size(15.)
.width(Size::flex(1.0))
.color(colors::fg_primary()),
)
.child(
Icon::new(IconType::ArrowRight)
.size(16.)
.color(colors::fg_secondary()),
)
}
}
#[derive(PartialEq)]
struct QuickSettings;
impl Component for QuickSettings {
fn render(&self) -> impl IntoElement {
let settings = use_settings_snapshot().settings;
let dispatch = use_dispatch();
let discord_on = settings.discord_enabled;
let toggle_discord = {
let dispatch = dispatch.clone();
move |_| {
dispatch.edit_settings(|s| s.discord_enabled = !s.discord_enabled);
}
};
rect()
.horizontal()
.width(Size::fill())
.content(Content::Flex)
.spacing(8.)
.child(
QuickTile::new(IconType::Discord, "Discord RPC", discord_on)
.on_press(toggle_discord),
)
}
}
struct QuickTile {
icon: IconType,
title: &'static str,
on: bool,
enabled: bool,
on_press: Option<EventHandler<Event<PressEventData>>>,
}
impl PartialEq for QuickTile {
fn eq(&self, other: &Self) -> bool {
self.icon == other.icon
&& self.title == other.title
&& self.on == other.on
&& self.enabled == other.enabled
}
}
impl QuickTile {
fn new(icon: IconType, title: &'static str, on: bool) -> Self {
Self {
icon,
title,
on,
enabled: true,
on_press: None,
}
}
fn on_press(mut self, on_press: impl Into<EventHandler<Event<PressEventData>>>) -> Self {
self.on_press = Some(on_press.into());
self
}
}
impl Component for QuickTile {
fn render(&self) -> impl IntoElement {
let mut hovered = use_state(|| false);
let on = self.on;
let enabled = self.enabled;
let handler = self.on_press.clone();
let background = if !enabled {
colors::component_bg().with_a(120)
} else if on {
if *hovered.read() {
colors::brand_hover()
} else {
colors::brand()
}
} else if *hovered.read() {
colors::component_bg_hover()
} else {
colors::component_bg()
};
let title_color = if enabled {
colors::fg_primary()
} else {
colors::fg_primary_disabled()
};
let sub_color = if !enabled {
colors::fg_primary_disabled()
} else if on {
colors::fg_primary().with_a(200)
} else {
colors::fg_secondary()
};
rect()
.horizontal()
.width(Size::flex(1.0))
.height(Size::px(64.))
.content(Content::Flex)
.cross_align(Alignment::Center)
.spacing(10.)
.padding(Gaps::new_symmetric(0., 12.))
.corner_radius(CornerRadius::new_all(10.))
.background(background)
.maybe(!enabled, |el| el.opacity(0.45))
.maybe(!on || !enabled, |el| {
el.border(
Border::new()
.fill(colors::component_border())
.width(1.)
.alignment(BorderAlignment::Inner),
)
})
.maybe(enabled, |el| {
el.cursor(CursorIcon::Pointer)
.a11y_role(AccessibilityRole::Button)
.on_pointer_enter(move |_| hovered.set(true))
.on_pointer_leave(move |_| hovered.set(false))
})
.map(handler, |el, handler| el.on_press(move |e| handler.call(e)))
.child(Icon::new(self.icon).size(24.).color(title_color))
.child(
rect()
.vertical()
.width(Size::flex(1.0))
.child(
label()
.text(self.title)
.font_size(12.)
.font_weight(FontWeight::MEDIUM)
.max_lines(1)
.color(title_color),
)
.child(
label()
.text(if on { "On" } else { "Off" })
.font_size(11.)
.color(sub_color),
),
)
}
}
#[derive(PartialEq)]
struct PanelFooter;
impl Component for PanelFooter {
fn render(&self) -> impl IntoElement {
let settings = use_settings_snapshot().settings;
let current = use_current_account();
let remove = use_remove_account();
let mut confirming = use_state(|| false);
let folder = settings
.data_dir
.clone()
.or_else(|| oneclient_core::settings::data_dir::default_path().ok());
let account = try_default_account(&current);
let account_id = account.as_ref().map(|account| account.id);
let username = account
.as_ref()
.map(|account| account.username.clone())
.unwrap_or_default();
let confirm_sign_out = move |_| {
if let Some(id) = account_id {
remove.mutate(RemoveAccountKeys { id });
}
confirming.set(false);
};
rect()
.vertical()
.width(Size::fill())
.child(
rect()
.horizontal()
.width(Size::fill())
.content(Content::Flex)
.cross_align(Alignment::Center)
.child(
footer_action(
IconType::Folder,
"Open launcher folder",
colors::fg_primary(),
)
.on_press(move |_| {
if let Some(folder) = folder.clone() {
std::fs::create_dir_all(&folder).ok();
crate::platform::open_path(&folder.to_string_lossy());
}
}),
)
.child(rect().width(Size::flex(1.0)))
.map(account_id, |el, _| {
el.child(
footer_action(
IconType::LogOut01,
"Sign out",
colors::fg_primary(),
)
.on_press(move |_| confirming.set(true)),
)
}),
)
.maybe(*confirming.read(), |el| {
el.child(
OverlayPopup::new()
.overlay_level(OVERLAY_MAX_LEVEL)
.on_close(move |_| confirming.set(false))
.child(
rect()
.width(Size::window_percent(100.))
.height(Size::window_percent(100.))
.center()
.child(
rect()
.vertical()
.width(Size::px(400.))
.max_width(Size::window_percent(90.))
.spacing(14.)
.padding(Gaps::new_all(20.))
.corner_radius(CornerRadius::new_all(14.))
.background(colors::page_elevated())
.border(border_all_color(
1.,
colors::component_border(),
))
.child(
rect()
.horizontal()
.cross_align(Alignment::Center)
.spacing(10.)
.child(
Icon::new(IconType::AlertTriangle)
.size(20.)
.color(colors::code_warn()),
)
.child(
label()
.text("Sign out?")
.font_size(16.)
.font_weight(FontWeight::SEMI_BOLD)
.color(colors::fg_primary()),
),
)
.child(
label()
.text(format!(
"{username} will be removed from OneClient. You will need to sign in again to use this account."
))
.font_size(12.)
.max_lines(4)
.width(Size::fill())
.color(colors::fg_secondary()),
)
.child(
rect()
.horizontal()
.width(Size::fill())
.main_align(Alignment::End)
.spacing(8.)
.child(
Button::new()
.secondary()
.text("Cancel")
.on_press(move |_| {
confirming.set(false)
}),
)
.child(
Button::new()
.danger()
.text("Sign out")
.on_press(confirm_sign_out),
),
),
),
),
)
})
}
}
fn footer_action(icon: IconType, text: &'static str, color: Color) -> FooterAction {
FooterAction {
icon,
text,
color,
on_press: None,
}
}
struct FooterAction {
icon: IconType,
text: &'static str,
color: Color,
on_press: Option<EventHandler<Event<PressEventData>>>,
}
impl PartialEq for FooterAction {
fn eq(&self, other: &Self) -> bool {
self.icon == other.icon && self.text == other.text && self.color == other.color
}
}
impl FooterAction {
fn on_press(mut self, on_press: impl Into<EventHandler<Event<PressEventData>>>) -> Self {
self.on_press = Some(on_press.into());
self
}
}
impl Component for FooterAction {
fn render(&self) -> impl IntoElement {
let mut hovered = use_state(|| false);
let handler = self.on_press.clone();
rect()
.horizontal()
.cross_align(Alignment::Center)
.spacing(8.)
.height(Size::px(34.))
.padding(Gaps::new_symmetric(0., 8.))
.corner_radius(CornerRadius::new_all(8.))
.background(Color::RED.with_a(0))
.maybe(*hovered.read(), |el| {
el.background(colors::ghost_overlay_hover())
})
.cursor(CursorIcon::Pointer)
.a11y_role(AccessibilityRole::Button)
.on_pointer_enter(move |_| hovered.set(true))
.on_pointer_leave(move |_| hovered.set(false))
.map(handler, |el, handler| el.on_press(move |e| handler.call(e)))
.child(Icon::new(self.icon).size(16.).color(self.color))
.child(
label()
.text(self.text)
.font_size(13.)
.max_lines(1)
.color(self.color),
)
}
}

View file

@ -270,8 +270,7 @@ fn month_grid(
outside: day.month0() != shown.month0(), outside: day.month0() != shown.month0(),
enabled: (min..=max).contains(&day), enabled: (min..=max).contains(&day),
in_range: selected.is_some_and(|(a, b)| (a..=b).contains(&day)), in_range: selected.is_some_and(|(a, b)| (a..=b).contains(&day)),
edge: selected.is_some_and(|(a, b)| day == a || day == b) edge: selected.is_some_and(|(a, b)| day == a || day == b) || pending == Some(day),
|| pending == Some(day),
pick: pick.clone(), pick: pick.clone(),
}); });
} }
@ -349,7 +348,11 @@ impl<F: FnMut(NaiveDate) + Clone + 'static> Component for DayCell<F> {
} }
} }
fn footer(has_range: bool, pending: Option<NaiveDate>, mut clear: impl FnMut() + 'static) -> Element { fn footer(
has_range: bool,
pending: Option<NaiveDate>,
mut clear: impl FnMut() + 'static,
) -> Element {
let hint = match pending { let hint = match pending {
Some(day) => format!("From {} — pick an end date", format_day(day)), Some(day) => format!("From {} — pick an end date", format_day(day)),
None => "Click a start date, then an end date".to_string(), None => "Click a start date, then an end date".to_string(),

View file

@ -14,8 +14,11 @@ pub struct Dropdown {
selected: String, selected: String,
options: Vec<String>, options: Vec<String>,
on_select: Option<EventHandler<usize>>, on_select: Option<EventHandler<usize>>,
leading: Option<Element>,
width: Size, width: Size,
height: Size, height: Size,
outlined: bool,
checked: Option<Vec<bool>>,
key: DiffKey, key: DiffKey,
} }
@ -25,8 +28,11 @@ impl Dropdown {
selected: selected.into(), selected: selected.into(),
options, options,
on_select: None, on_select: None,
leading: None,
width: Size::px(72.), width: Size::px(72.),
height: Size::px(24.), height: Size::px(24.),
outlined: false,
checked: None,
key: DiffKey::None, key: DiffKey::None,
} }
} }
@ -43,6 +49,21 @@ impl Dropdown {
self self
} }
pub fn outlined(mut self) -> Self {
self.outlined = true;
self
}
pub fn checked(mut self, checked: Vec<bool>) -> Self {
self.checked = Some(checked);
self
}
pub fn leading(mut self, leading: impl IntoElement) -> Self {
self.leading = Some(leading.into_element());
self
}
pub fn on_select(mut self, handler: impl Into<EventHandler<usize>>) -> Self { pub fn on_select(mut self, handler: impl Into<EventHandler<usize>>) -> Self {
self.on_select = Some(handler.into()); self.on_select = Some(handler.into());
self self
@ -64,17 +85,21 @@ impl Component for Dropdown {
let focus = use_focus(a11y_id); let focus = use_focus(a11y_id);
let mut button_area = use_state(|| None::<Area>); let mut button_area = use_state(|| None::<Area>);
let mut list_size = use_state(|| None::<Size2D>); let mut list_area = use_state(|| None::<Area>);
let selected = self.selected.clone(); let selected = self.selected.clone();
let leading = self.leading.clone();
let options = self.options.clone(); let options = self.options.clone();
let on_select = self.on_select.clone(); let on_select = self.on_select.clone();
let outlined = self.outlined;
let checked = self.checked.clone();
let multi = checked.is_some();
let is_open = open(); let is_open = open();
use_overlay_claim_when(is_open); use_overlay_claim_when(is_open);
if !is_open && list_size().is_some() { if !is_open && list_area().is_some() {
let _ = list_size.take(); let _ = list_area.take();
} }
let trigger_bg = if hovering() { let trigger_bg = if hovering() {
@ -87,13 +112,13 @@ impl Component for Dropdown {
let list_h = let list_h =
visible as f32 * OPTION_HEIGHT + visible.saturating_sub(1) as f32 * OPTION_SPACING; visible as f32 * OPTION_HEIGHT + visible.saturating_sub(1) as f32 * OPTION_SPACING;
let offset_y = match (button_area(), list_size()) { let offset_y = match (button_area(), list_area()) {
(Some(button), Some(list)) => { (Some(button), Some(list)) => {
let root_height = Platform::get().root_size.peek().height; let root_height = Platform::get().root_size.peek().height;
let space_below = root_height - button.max_y(); let space_below = root_height - button.max_y();
let space_above = button.min_y(); let space_above = button.min_y();
if list.height > space_below && list.height <= space_above { if list.height() > space_below && list.height() <= space_above {
-(button.height() + list.height + 8.) -(button.height() + list.height() + 8.)
} else { } else {
0. 0.
} }
@ -105,8 +130,15 @@ impl Component for Dropdown {
.map(|b| Size::px(b.width())) .map(|b| Size::px(b.width()))
.unwrap_or_else(|| self.width.clone()); .unwrap_or_else(|| self.width.clone());
let on_global_pointer_press = move |_: Event<PointerEventData>| { let on_global_pointer_up = move |e: Event<PointerEventData>| {
open.set_if_modified(false); let loc = e.global_location();
let inside = list_area().is_some_and(|area| {
area.translate((0., offset_y).into())
.contains((loc.x as f32, loc.y as f32).into())
});
if !(multi && inside) {
open.set_if_modified(false);
}
}; };
rect() rect()
@ -123,9 +155,13 @@ impl Component for Dropdown {
.horizontal() .horizontal()
.center() .center()
.spacing(4.) .spacing(4.)
.padding(Gaps::new_symmetric(0., 8.)) .padding(Gaps::new_symmetric(0., if outlined { 12. } else { 8. }))
.corner_radius(CornerRadius::new_all(6.)) .corner_radius(CornerRadius::new_all(if outlined { 8. } else { 6. }))
.background(trigger_bg) .background(trigger_bg)
.maybe(outlined, |el| {
el.main_align(Alignment::SpaceBetween)
.border(border_all_color(1., colors::component_border()))
})
.a11y_id(a11y_id) .a11y_id(a11y_id)
.a11y_focusable(true) .a11y_focusable(true)
.a11y_role(AccessibilityRole::Button) .a11y_role(AccessibilityRole::Button)
@ -134,7 +170,7 @@ impl Component for Dropdown {
}) })
.on_pointer_enter(move |_| hovering.set(true)) .on_pointer_enter(move |_| hovering.set(true))
.on_pointer_leave(move |_| hovering.set(false)) .on_pointer_leave(move |_| hovering.set(false))
.on_global_pointer_press(on_global_pointer_press) .on_global_pointer_up(on_global_pointer_up)
.on_sized(move |e: Event<SizedEventData>| { .on_sized(move |e: Event<SizedEventData>| {
button_area.set_if_modified(Some(e.area)); button_area.set_if_modified(Some(e.area));
}) })
@ -143,10 +179,11 @@ impl Component for Dropdown {
e.stop_propagation(); e.stop_propagation();
open.toggle(); open.toggle();
}) })
.maybe_child(leading)
.child( .child(
label() label()
.text(selected) .text(selected)
.font_size(12.) .font_size(if outlined { 13. } else { 12. })
.color(colors::fg_primary()), .color(colors::fg_primary()),
) )
.child( .child(
@ -164,7 +201,7 @@ impl Component for Dropdown {
.offset_y(offset_y) .offset_y(offset_y)
.content(Content::Fit) .content(Content::Fit)
.on_sized(move |e: Event<SizedEventData>| { .on_sized(move |e: Event<SizedEventData>| {
list_size.set_if_modified(Some(e.area.size)); list_area.set_if_modified(Some(e.area));
}) })
.child( .child(
rect() rect()
@ -181,11 +218,16 @@ impl Component for Dropdown {
.children(options.into_iter().enumerate().map( .children(options.into_iter().enumerate().map(
|(idx, option)| { |(idx, option)| {
let on_select = on_select.clone(); let on_select = on_select.clone();
DropdownOption::new(option, move |_| { let checked = checked
.as_ref()
.map(|c| c.get(idx).copied().unwrap_or(false));
DropdownOption::new(option, checked, move |_| {
if let Some(handler) = &on_select { if let Some(handler) = &on_select {
handler.call(idx); handler.call(idx);
} }
open.set(false); if !multi {
open.set(false);
}
}) })
.into_element() .into_element()
}, },
@ -203,16 +245,19 @@ impl Component for Dropdown {
struct DropdownOption { struct DropdownOption {
text: String, text: String,
checked: Option<bool>,
on_press: EventHandler<Event<PressEventData>>, on_press: EventHandler<Event<PressEventData>>,
} }
impl DropdownOption { impl DropdownOption {
pub fn new( pub fn new(
text: impl Into<String>, text: impl Into<String>,
checked: Option<bool>,
on_press: impl Into<EventHandler<Event<PressEventData>>>, on_press: impl Into<EventHandler<Event<PressEventData>>>,
) -> Self { ) -> Self {
Self { Self {
text: text.into(), text: text.into(),
checked,
on_press: on_press.into(), on_press: on_press.into(),
} }
} }
@ -220,7 +265,7 @@ impl DropdownOption {
impl PartialEq for DropdownOption { impl PartialEq for DropdownOption {
fn eq(&self, other: &Self) -> bool { fn eq(&self, other: &Self) -> bool {
self.text == other.text self.text == other.text && self.checked == other.checked
} }
} }
@ -233,7 +278,8 @@ impl Component for DropdownOption {
rect() rect()
.width(Size::fill()) .width(Size::fill())
.padding(Gaps::new_symmetric(6., 8.)) .height(Size::px(OPTION_HEIGHT))
.padding(Gaps::new_symmetric(0., 8.))
.corner_radius(CornerRadius::new_all(6.)) .corner_radius(CornerRadius::new_all(6.))
.a11y_id(a11y_id) .a11y_id(a11y_id)
.a11y_focusable(true) .a11y_focusable(true)
@ -246,11 +292,27 @@ impl Component for DropdownOption {
.on_pointer_enter(move |_| hovering.set(true)) .on_pointer_enter(move |_| hovering.set(true))
.on_pointer_leave(move |_| hovering.set(false)) .on_pointer_leave(move |_| hovering.set(false))
.on_press(self.on_press.clone()) .on_press(self.on_press.clone())
.horizontal()
.content(Content::Flex)
.cross_align(Alignment::Center)
.child( .child(
label() label()
.text(self.text.clone()) .text(self.text.clone())
.width(Size::flex(1.))
.font_size(12.) .font_size(12.)
.color(colors::fg_primary()), .color(colors::fg_primary()),
) )
.maybe_child(self.checked.map(|checked| {
rect()
.width(Size::px(14.))
.height(Size::px(14.))
.maybe_child(checked.then(|| {
Icon::new(IconType::Check)
.size(14.)
.color(colors::brand())
.into_element()
}))
.into_element()
}))
} }
} }

View file

@ -1,25 +1,38 @@
use std::path::PathBuf;
use std::sync::LazyLock;
use bytes::Bytes; use bytes::Bytes;
use freya::animation::{
AnimNum, Ease, Function, OnChange, OnCreation, OnFinish, use_animation_with_dependencies,
};
use freya::prelude::*; use freya::prelude::*;
use oneclient_core::clusters::Cluster;
use oneclient_core::images::DEFAULT_IMAGE_EDGE;
use oneclient_common::domain::GameLoader; use oneclient_common::domain::GameLoader;
use oneclient_common::{VersionKey, parse_mc_version}; use oneclient_common::{VersionKey, parse_mc_version};
use oneclient_core::clusters::Cluster;
use oneclient_core::images::{DEFAULT_IMAGE_EDGE, PREVIEW_IMAGE_EDGE};
use crate::AppAssets; use crate::AppAssets;
use crate::hooks::{loaded_image, use_cached_image, use_version_metadata}; use crate::hooks::{
loaded_image, resolve_art_url, settled_or_loading, use_cached_image, use_local_image,
use_version_art, use_version_metadata,
};
use crate::layout::HOME_BACKGROUND_ASSET; use crate::layout::HOME_BACKGROUND_ASSET;
use crate::theme::colors;
use crate::ui::ImageFallbackExt; use crate::ui::ImageFallbackExt;
/// The size cluster cards request so it is usually already cached when anything else wants it pub const ART_PREVIEW_EDGE: u32 = PREVIEW_IMAGE_EDGE;
pub const ART_PREVIEW_EDGE: u32 = 512;
#[derive(PartialEq, Clone)] #[derive(PartialEq, Clone)]
pub struct DynamicArt { pub struct DynamicArt {
major: Option<u32>, major: Option<u32>,
key: Option<VersionKey>, key: Option<VersionKey>,
loader: Option<GameLoader>, loader: Option<GameLoader>,
cover: Option<PathBuf>,
cover_picked: bool,
max_edge: u32, max_edge: u32,
preview_edge: Option<u32>, preview_edge: Option<u32>,
url: Option<String>,
skeleton: bool,
} }
impl DynamicArt { impl DynamicArt {
@ -28,8 +41,7 @@ impl DynamicArt {
major: Some(major), major: Some(major),
key, key,
loader, loader,
max_edge: DEFAULT_IMAGE_EDGE, ..Self::fallback()
preview_edge: None,
} }
} }
@ -39,8 +51,15 @@ impl DynamicArt {
major: parsed.as_ref().map(|p| p.major), major: parsed.as_ref().map(|p| p.major),
key: parsed.and_then(|p| p.key()), key: parsed.and_then(|p| p.key()),
loader: Some(cluster.mc_loader), loader: Some(cluster.mc_loader),
max_edge: DEFAULT_IMAGE_EDGE, cover: cluster.cover_file(),
preview_edge: None, ..Self::fallback()
}
}
pub fn for_url(url: String) -> Self {
Self {
url: Some(url),
..Self::fallback()
} }
} }
@ -49,11 +68,34 @@ impl DynamicArt {
major: None, major: None,
key: None, key: None,
loader: None, loader: None,
cover: None,
cover_picked: false,
max_edge: DEFAULT_IMAGE_EDGE, max_edge: DEFAULT_IMAGE_EDGE,
preview_edge: None, preview_edge: None,
url: None,
skeleton: false,
} }
} }
#[must_use]
pub fn cover(mut self, cover: Option<PathBuf>) -> Self {
self.cover = cover;
self
}
#[must_use]
pub fn picked_cover(mut self, cover: Option<PathBuf>) -> Self {
self.cover = cover;
self.cover_picked = true;
self
}
#[must_use]
pub fn skeleton(mut self, skeleton: bool) -> Self {
self.skeleton = skeleton;
self
}
#[must_use] #[must_use]
pub fn max_edge(mut self, max_edge: u32) -> Self { pub fn max_edge(mut self, max_edge: u32) -> Self {
self.max_edge = max_edge; self.max_edge = max_edge;
@ -68,26 +110,46 @@ impl DynamicArt {
} }
pub fn use_bytes(&self) -> (String, Bytes) { pub fn use_bytes(&self) -> (String, Bytes) {
use_art_bytes( use_art_bytes(self)
self.major,
self.key,
self.loader,
self.max_edge,
self.preview_edge,
)
} }
} }
pub fn use_art_bytes( pub fn use_art_bytes(art: &DynamicArt) -> (String, Bytes) {
major: Option<u32>, let max_edge = art.max_edge;
key: Option<VersionKey>, use_resolved_art(art)
loader: Option<GameLoader>, .unwrap_or_else(|| (format!("{max_edge}|{HOME_BACKGROUND_ASSET}"), bundled_art()))
max_edge: u32, }
preview_edge: Option<u32>,
) -> (String, Bytes) {
let fallback = use_memo(|| AppAssets::get_bytes(HOME_BACKGROUND_ASSET).unwrap_or_default());
let art_url = use_version_metadata(major, key, loader).and_then(|m| m.art_url); fn bundled_art() -> Bytes {
static ART: LazyLock<Bytes> =
LazyLock::new(|| AppAssets::get_bytes(HOME_BACKGROUND_ASSET).unwrap_or_default());
ART.clone()
}
fn use_resolved_art(art: &DynamicArt) -> Option<(String, Bytes)> {
let DynamicArt {
major,
key,
loader,
cover,
cover_picked,
max_edge,
preview_edge,
url,
skeleton: _,
} = art.clone();
let cover_query = use_local_image(cover.clone().unwrap_or_default(), max_edge, cover_picked);
let cover_bytes = cover.as_ref().and_then(|path| {
settled_or_loading(&cover_query)
.filter(|bytes: &Bytes| !bytes.is_empty())
.map(|bytes| (format!("{max_edge}|{}", path.display()), bytes))
});
let curated = use_version_metadata(major, key, loader);
let candidates = use_version_art(major, key);
let art_url = url.or_else(|| resolve_art_url(curated.as_ref(), &candidates));
let image_query = use_cached_image(art_url.clone(), max_edge); let image_query = use_cached_image(art_url.clone(), max_edge);
@ -105,17 +167,41 @@ pub fn use_art_bytes(
.map(|(url, bytes)| (format!("{edge}|{url}"), bytes)) .map(|(url, bytes)| (format!("{edge}|{url}"), bytes))
}; };
full.or_else(preview).unwrap_or_else(|| { cover_bytes.or(full).or_else(preview)
(
format!("{max_edge}|{HOME_BACKGROUND_ASSET}"),
fallback.read().clone(),
)
})
} }
impl Component for DynamicArt { impl Component for DynamicArt {
fn render(&self) -> impl IntoElement { fn render(&self) -> impl IntoElement {
let (key, bytes) = self.use_bytes(); let resolved = use_resolved_art(self);
let show_skeleton = self.skeleton && resolved.is_none();
let pulse = use_animation_with_dependencies(&show_skeleton, |conf, show| {
conf.on_change(OnChange::Rerun);
if *show {
conf.on_creation(OnCreation::Run);
conf.on_finish(OnFinish::reverse());
}
AnimNum::new(0.4, 0.9)
.time(800)
.ease(Ease::InOut)
.function(Function::Sine)
});
let pulse_v = pulse.read().value();
if show_skeleton {
return rect()
.width(Size::fill())
.height(Size::fill())
.background(colors::component_bg())
.opacity(pulse_v)
.into_element();
}
let max_edge = self.max_edge;
let (key, bytes) = resolved
.unwrap_or_else(|| (format!("{max_edge}|{HOME_BACKGROUND_ASSET}"), bundled_art()));
ImageViewer::new((key, bytes)) ImageViewer::new((key, bytes))
.width(Size::fill()) .width(Size::fill())

View file

@ -0,0 +1,146 @@
use freya::prelude::*;
use crate::components::{
Button, Icon, IconType, Markdown, MarkdownStyle, OverlayPopup, ScrollArea,
};
use crate::hooks::{EssentialGuardKind, use_cluster_mutation, use_essential_guard};
use crate::theme::colors;
use crate::ui::border_all_color;
const CARD_BG: Color = Color::from_rgb(26, 34, 41);
const BODY_MAX_H: f32 = 400.;
#[derive(PartialEq)]
pub struct EssentialConfirmOverlay;
impl Component for EssentialConfirmOverlay {
fn render(&self) -> impl IntoElement {
let mut pending = use_essential_guard();
let cluster = use_cluster_mutation();
let Some(request) = pending.read().clone() else {
return rect().into_element();
};
let (title, confirm_label) = match request.kind {
EssentialGuardKind::Disable => (format!("Turn off {}?", request.name), "Turn it off"),
EssentialGuardKind::Remove => (format!("Remove {}?", request.name), "Remove anyway"),
};
let action = request.action.clone();
OverlayPopup::new()
.on_close(move |_| pending.set(None))
.child(
rect()
.width(Size::window_percent(100.))
.height(Size::window_percent(100.))
.center()
.child(
rect()
.vertical()
.width(Size::px(440.))
.max_width(Size::window_percent(90.))
.spacing(14.)
.padding(Gaps::new_all(20.))
.corner_radius(CornerRadius::new_all(14.))
.background(CARD_BG)
.border(border_all_color(1., colors::component_border()))
.child(
rect()
.horizontal()
.cross_align(Alignment::Center)
.spacing(10.)
.child(
Icon::new(IconType::AlertTriangle)
.size(20.)
.color(colors::code_warn()),
)
.child(
label()
.text(title)
.font_size(16.)
.font_weight(FontWeight::SEMI_BOLD)
.color(colors::fg_primary()),
),
)
.child(WarningBody {
body: request.body.clone(),
})
.child(
rect()
.horizontal()
.width(Size::fill())
.main_align(Alignment::End)
.spacing(8.)
.child(
Button::new()
.secondary()
.on_press(move |_| {
cluster.mutate(action.clone());
pending.set(None);
})
.text(confirm_label),
)
.child(
Button::new()
.primary()
.on_press(move |_| pending.set(None))
.text("Keep it"),
),
),
),
)
.into_element()
}
}
#[derive(PartialEq)]
struct WarningBody {
body: String,
}
impl Component for WarningBody {
fn render(&self) -> impl IntoElement {
let mut content_h = use_state(|| 0f32);
let height = content_h.read().min(BODY_MAX_H);
ScrollArea::new()
.width(Size::fill())
.height(Size::px(height))
.scrollbar_gutter(true)
.child(
rect()
.width(Size::fill())
.on_sized(move |e: Event<SizedEventData>| {
let h = e.area.height();
if (*content_h.read() - h).abs() > 0.5 {
content_h.set(h);
}
})
.child(
rect()
.width(Size::fill())
.padding(Gaps::new_all(12.))
.corner_radius(CornerRadius::new_all(8.))
.background(colors::component_bg())
.border(border_all_color(1., colors::component_border()))
.child(Markdown::new(self.body.clone()).width(Size::fill()).style(
MarkdownStyle {
color: Color::WHITE,
color_link: colors::code_info(),
color_code: Color::WHITE,
background_code: CARD_BG,
background_blockquote: CARD_BG,
border_blockquote: colors::component_border(),
background_divider: colors::component_border(),
headings: [16., 14., 13., 12., 12., 12.],
paragraph_size: 12.,
code_font_size: 11.,
..MarkdownStyle::default()
},
)),
),
)
}
}

View file

@ -3,17 +3,24 @@
use std::collections::HashMap; use std::collections::HashMap;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::time::Duration;
use freya::animation::{AnimNum, Ease, Function, OnCreation, use_animation}; use freya::animation::{AnimNum, Ease, Function, OnCreation, use_animation};
use freya::prelude::*; use freya::prelude::*;
use freya::router::use_route; use freya::router::use_route;
use oneclient_core::clusters::Cluster; use oneclient_content::modpacks::detect_format;
use oneclient_content::packages::ContentType; use oneclient_content::packages::ContentType;
use oneclient_core::clusters::{Cluster, ModpackSource};
use oneclient_db::models::ClusterId; use oneclient_db::models::ClusterId;
use crate::Route; use crate::Route;
use crate::components::{Button, Dropdown, Icon, IconType, OverlayPopup, ScrollArea}; use crate::components::{Button, Dropdown, Icon, IconType, OverlayPopup, ScrollArea};
use crate::hooks::{settled_or_loading, use_active_cluster_id, use_clusters, use_dispatch}; use crate::hooks::{
add_world_datapacks, settled_or_loading, spawn_world_task, try_cluster_worlds,
use_active_cluster_id, use_cluster_worlds, use_clusters, use_datapack_world, use_debounced,
use_dispatch,
};
use crate::launcher::off_ui;
use crate::theme::colors; use crate::theme::colors;
use crate::ui::border_all_color; use crate::ui::border_all_color;
@ -23,19 +30,29 @@ const FILE_ROW_H: f32 = 34.;
const FILE_LIST_MAX_ROWS: usize = 5; const FILE_LIST_MAX_ROWS: usize = 5;
const TYPE_DROPDOWN_W: f32 = 108.; const TYPE_DROPDOWN_W: f32 = 108.;
/// Worlds are directories and modpacks/datapacks have no view so both are excluded /// Worlds are directories and modpacks have no view so both are excluded
const IMPORTABLE: [ContentType; 3] = [ const IMPORTABLE: [ContentType; 4] = [
ContentType::Mod, ContentType::Mod,
ContentType::ResourcePack, ContentType::ResourcePack,
ContentType::Shader, ContentType::Shader,
ContentType::DataPack,
]; ];
const SHADER_HINTS: [&str; 5] = ["shader", "bsl", "seus", "complementary", "sildur"]; const SHADER_HINTS: [&str; 5] = ["shader", "bsl", "seus", "complementary", "sildur"];
const DATAPACK_HINTS: [&str; 4] = ["datapack", "data pack", "data_pack", "data-pack"];
fn importable(mod_loader: bool) -> Vec<ContentType> {
IMPORTABLE
.into_iter()
.filter(|ct| mod_loader || !ct.needs_mod_loader())
.collect()
}
fn content_label(content_type: ContentType) -> &'static str { fn content_label(content_type: ContentType) -> &'static str {
match content_type { match content_type {
ContentType::ResourcePack => "Textures", ContentType::ResourcePack => "Textures",
ContentType::Shader => "Shaders", ContentType::Shader => "Shaders",
ContentType::DataPack => "Data packs",
_ => "Mods", _ => "Mods",
} }
} }
@ -51,26 +68,87 @@ fn file_name(path: &Path) -> String {
} }
/// Precedence `.jar` is always a mod then the current route then a name sniff /// Precedence `.jar` is always a mod then the current route then a name sniff
fn infer_content_type(path: &Path, route_type: Option<ContentType>) -> ContentType { fn infer_content_type(
path: &Path,
route_type: Option<ContentType>,
mod_loader: bool,
) -> Option<ContentType> {
if extension(path).as_deref() == Some("jar") { if extension(path).as_deref() == Some("jar") {
return ContentType::Mod; return mod_loader.then_some(ContentType::Mod);
} }
if let Some(route_type) = route_type { if let Some(route_type) = route_type {
return route_type; if mod_loader || !route_type.needs_mod_loader() {
return Some(route_type);
}
if route_type == ContentType::Shader {
return None;
}
} }
let name = file_name(path).to_lowercase(); let name = file_name(path).to_lowercase();
if SHADER_HINTS.iter().any(|hint| name.contains(hint)) { if DATAPACK_HINTS.iter().any(|hint| name.contains(hint)) {
ContentType::Shader Some(ContentType::DataPack)
} else if mod_loader && SHADER_HINTS.iter().any(|hint| name.contains(hint)) {
Some(ContentType::Shader)
} else { } else {
ContentType::ResourcePack Some(ContentType::ResourcePack)
} }
} }
const DROP_SETTLE: Duration = Duration::from_millis(300);
pub fn accept_drop(
paths: &[PathBuf],
mut pending: State<Vec<PathBuf>>,
mut modpacks: State<Vec<PathBuf>>,
) {
for path in paths {
match extension(path).as_deref() {
Some("mrpack") => modpacks.write().push(path.clone()),
Some("zip") => {
let path = path.clone();
spawn(async move {
let probe = path.clone();
let is_modpack =
off_ui(async move { detect_format(&probe).await.is_ok() }).await;
if is_modpack {
modpacks.write().push(path);
} else {
pending.write().push(path);
}
});
}
_ => pending.write().push(path.clone()),
}
}
}
fn use_dropped_modpack_driver(pending: State<Vec<PathBuf>>, modpacks: State<Vec<PathBuf>>) {
let dispatch = use_dispatch();
let settled = use_debounced(
(pending.read().is_empty(), modpacks.read().len()),
DROP_SETTLE,
);
let settled = *settled.read();
use_side_effect_with_deps(&settled, move |&(prompt_closed, queued)| {
if !prompt_closed || queued == 0 || !pending.peek().is_empty() {
return;
}
let mut modpacks = modpacks;
let dropped = std::mem::take(&mut *modpacks.write());
for path in dropped {
dispatch.install_modpack(ModpackSource::File(path));
}
});
}
fn route_target(route: &Route) -> Option<(ClusterId, ContentType)> { fn route_target(route: &Route) -> Option<(ClusterId, ContentType)> {
match route { match route {
Route::ClusterMods { cluster_id } => Some((*cluster_id, ContentType::Mod)), Route::ClusterMods { cluster_id } => Some((*cluster_id, ContentType::Mod)),
Route::ClusterShaders { cluster_id } => Some((*cluster_id, ContentType::Shader)), Route::ClusterShaders { cluster_id } => Some((*cluster_id, ContentType::Shader)),
Route::ClusterTextures { cluster_id } => Some((*cluster_id, ContentType::ResourcePack)), Route::ClusterTextures { cluster_id } => Some((*cluster_id, ContentType::ResourcePack)),
Route::ClusterDataPacks { cluster_id } => Some((*cluster_id, ContentType::DataPack)),
_ => None, _ => None,
} }
} }
@ -80,10 +158,12 @@ fn route_target(route: &Route) -> Option<(ClusterId, ContentType)> {
pub struct FileDropOverlay { pub struct FileDropOverlay {
pub hovering: State<bool>, pub hovering: State<bool>,
pub pending: State<Vec<PathBuf>>, pub pending: State<Vec<PathBuf>>,
pub modpacks: State<Vec<PathBuf>>,
} }
impl Component for FileDropOverlay { impl Component for FileDropOverlay {
fn render(&self) -> impl IntoElement { fn render(&self) -> impl IntoElement {
use_dropped_modpack_driver(self.pending, self.modpacks);
let files = self.pending.read().clone(); let files = self.pending.read().clone();
if !files.is_empty() { if !files.is_empty() {
@ -191,23 +271,39 @@ impl Component for DropPrompt {
let target = route_target(&route); let target = route_target(&route);
let initial_cluster = target.map(|(id, _)| id).or(*active_cluster.peek()); let initial_cluster = target.map(|(id, _)| id).or(*active_cluster.peek());
let selected_cluster = use_state(move || initial_cluster); let selected_cluster = use_state(move || initial_cluster);
let remembered_world = use_datapack_world();
// Absent entries fall back to the inferred type covering drops landing mid-prompt // Absent entries fall back to the inferred type covering drops landing mid-prompt
let overrides = use_state(HashMap::<PathBuf, ContentType>::new); let overrides = use_state(HashMap::<PathBuf, ContentType>::new);
let clusters: Vec<Cluster> = settled_or_loading(&clusters_query).unwrap_or_default(); let clusters: Vec<Cluster> = settled_or_loading(&clusters_query).unwrap_or_default();
let body = if clusters.is_empty() { // The remembered cluster may have been deleted while the prompt was open
no_clusters_body(pending) let cluster_id = selected_cluster
} else { .read()
prompt_body( .filter(|id| clusters.iter().any(|c| c.id == *id))
.or_else(|| clusters.first().map(|c| c.id));
let worlds_query = use_cluster_worlds(cluster_id.unwrap_or_default());
let worlds: Vec<String> = try_cluster_worlds(&worlds_query)
.unwrap_or_default()
.into_iter()
.map(|w| w.folder_name)
.collect();
let body = match cluster_id {
None => no_clusters_body(pending),
Some(cluster_id) => prompt_body(
&files, &files,
&clusters, &clusters,
cluster_id,
target.map(|(_, ct)| ct), target.map(|(_, ct)| ct),
selected_cluster, selected_cluster,
&worlds,
remembered_world,
overrides, overrides,
dispatch, dispatch,
pending, pending,
) ),
}; };
OverlayPopup::new() OverlayPopup::new()
@ -297,40 +393,92 @@ fn no_clusters_body(mut pending: State<Vec<PathBuf>>) -> Element {
.into_element() .into_element()
} }
#[allow(clippy::too_many_arguments)]
fn prompt_body( fn prompt_body(
files: &[PathBuf], files: &[PathBuf],
clusters: &[Cluster], clusters: &[Cluster],
cluster_id: ClusterId,
route_type: Option<ContentType>, route_type: Option<ContentType>,
selected_cluster: State<Option<ClusterId>>, selected_cluster: State<Option<ClusterId>>,
worlds: &[String],
remembered_world: State<HashMap<ClusterId, String>>,
overrides: State<HashMap<PathBuf, ContentType>>, overrides: State<HashMap<PathBuf, ContentType>>,
dispatch: crate::Actions, dispatch: crate::Actions,
mut pending: State<Vec<PathBuf>>, mut pending: State<Vec<PathBuf>>,
) -> Element { ) -> Element {
// The remembered cluster may have been deleted while the prompt was open
let cluster_id = selected_cluster
.read()
.filter(|id| clusters.iter().any(|c| c.id == *id))
.unwrap_or(clusters[0].id);
let cluster_idx = clusters let cluster_idx = clusters
.iter() .iter()
.position(|c| c.id == cluster_id) .position(|c| c.id == cluster_id)
.unwrap_or_default(); .unwrap_or_default();
let destination = clusters.get(cluster_idx);
let mod_loader = destination.is_none_or(|c| !c.lacks_mod_loader());
let mut rejected: Vec<PathBuf> = Vec::new();
let resolved: Vec<(PathBuf, ContentType)> = files let resolved: Vec<(PathBuf, ContentType)> = files
.iter() .iter()
.map(|path| { .filter_map(|path| {
let content_type = overrides let content_type = overrides
.read() .read()
.get(path) .get(path)
.copied() .copied()
.unwrap_or_else(|| infer_content_type(path, route_type)); .filter(|ct| mod_loader || !ct.needs_mod_loader())
(path.clone(), content_type) .or_else(|| infer_content_type(path, route_type, mod_loader));
match content_type {
Some(content_type) => Some((path.clone(), content_type)),
None => {
rejected.push(path.clone());
None
}
}
}) })
.collect(); .collect();
let has_datapacks = resolved.iter().any(|(_, ct)| *ct == ContentType::DataPack);
let world = remembered_world
.read()
.get(&cluster_id)
.filter(|name| worlds.contains(name))
.cloned()
.or_else(|| worlds.first().cloned());
let can_import = !resolved.is_empty() && (!has_datapacks || world.is_some());
let skipped = rejected.len();
let destination_name = destination.map(|c| c.name.clone()).unwrap_or_default();
let import_list = resolved.clone(); let import_list = resolved.clone();
let import_world = world.clone();
let import = move |_| { let import = move |_| {
dispatch.import_local_files(cluster_id, import_list.clone()); if !can_import {
return;
}
let (datapacks, packages): (Vec<_>, Vec<_>) = import_list
.iter()
.cloned()
.partition(|(_, ct)| *ct == ContentType::DataPack);
dispatch.import_local_files(cluster_id, packages);
if let Some(world) = import_world.clone()
&& !datapacks.is_empty()
{
spawn_world_task(
dispatch.clone(),
"Couldn't add data packs",
add_world_datapacks(
cluster_id,
world,
datapacks.into_iter().map(|(path, _)| path).collect(),
),
);
}
if skipped > 0 {
dispatch
.notify("Some files weren't added")
.body(format!(
"{destination_name} has no mod loader, so {skipped} mod or shader file{} {} skipped.",
if skipped == 1 { "" } else { "s" },
if skipped == 1 { "was" } else { "were" },
))
.send();
}
pending.set(Vec::new()); pending.set(Vec::new());
}; };
@ -347,7 +495,11 @@ fn prompt_body(
"Pick a cluster, then check what each one gets installed as.".to_string(), "Pick a cluster, then check what each one gets installed as.".to_string(),
)) ))
.child(cluster_field(clusters, cluster_idx, selected_cluster)) .child(cluster_field(clusters, cluster_idx, selected_cluster))
.child(file_field(&resolved, overrides)) .maybe_child(
has_datapacks
.then(|| world_field(worlds, world.as_deref(), cluster_id, remembered_world)),
)
.child(file_field(&resolved, &rejected, mod_loader, overrides))
.child( .child(
rect() rect()
.horizontal() .horizontal()
@ -364,6 +516,7 @@ fn prompt_body(
.child( .child(
Button::new() Button::new()
.primary() .primary()
.enabled(can_import)
.on_press(import) .on_press(import)
.child(Icon::new(IconType::Plus).size(15.)) .child(Icon::new(IconType::Plus).size(15.))
.text(match files.len() { .text(match files.len() {
@ -415,14 +568,55 @@ fn cluster_field(
) )
} }
fn world_field(
worlds: &[String],
current: Option<&str>,
cluster_id: ClusterId,
mut remembered: State<HashMap<ClusterId, String>>,
) -> Element {
let Some(current) = current else {
return field(
"Destination world",
label()
.text("This cluster has no worlds yet. Create one in game, then drop the data packs again.")
.font_size(12.)
.color(colors::fg_secondary()),
)
.into_element();
};
let names = worlds.to_vec();
field(
"Destination world",
Dropdown::new(current.to_string(), worlds.to_vec())
.width(Size::fill())
.height(Size::px(32.))
.on_select(move |idx: usize| {
if let Some(name) = names.get(idx) {
remembered.write().insert(cluster_id, name.clone());
}
}),
)
.into_element()
}
fn file_field( fn file_field(
resolved: &[(PathBuf, ContentType)], resolved: &[(PathBuf, ContentType)],
rejected: &[PathBuf],
mod_loader: bool,
overrides: State<HashMap<PathBuf, ContentType>>, overrides: State<HashMap<PathBuf, ContentType>>,
) -> impl IntoElement { ) -> impl IntoElement {
let visible = resolved.len().clamp(1, FILE_LIST_MAX_ROWS); let visible = (resolved.len() + rejected.len()).clamp(1, FILE_LIST_MAX_ROWS);
let rows = resolved let rows = resolved
.iter() .iter()
.map(|(path, content_type)| file_row(path, *content_type, overrides).into_element()) .map(|(path, content_type)| {
file_row(path, Some(*content_type), mod_loader, overrides).into_element()
})
.chain(
rejected
.iter()
.map(|path| file_row(path, None, mod_loader, overrides).into_element()),
)
.collect::<Vec<_>>(); .collect::<Vec<_>>();
field( field(
@ -444,14 +638,33 @@ fn file_field(
fn file_row( fn file_row(
path: &Path, path: &Path,
content_type: ContentType, content_type: Option<ContentType>,
mod_loader: bool,
mut overrides: State<HashMap<PathBuf, ContentType>>, mut overrides: State<HashMap<PathBuf, ContentType>>,
) -> impl IntoElement { ) -> impl IntoElement {
let key = path.to_path_buf(); let key = path.to_path_buf();
let options: Vec<String> = IMPORTABLE let types = importable(mod_loader);
let options: Vec<String> = types
.iter() .iter()
.map(|ct| content_label(*ct).to_string()) .map(|ct| content_label(*ct).to_string())
.collect(); .collect();
let control = match content_type {
Some(content_type) => Dropdown::new(content_label(content_type), options)
.width(Size::px(TYPE_DROPDOWN_W))
.height(Size::px(24.))
.on_select(move |idx: usize| {
if let Some(content_type) = types.get(idx) {
overrides.write().insert(key.clone(), *content_type);
}
})
.into_element(),
None => label()
.text("Needs a mod loader")
.font_size(12.)
.max_lines(1)
.color(colors::fg_secondary())
.into_element(),
};
rect() rect()
.horizontal() .horizontal()
@ -474,14 +687,5 @@ fn file_row(
.width(Size::flex(1.0)) .width(Size::flex(1.0))
.color(colors::fg_primary()), .color(colors::fg_primary()),
) )
.child( .child(control)
Dropdown::new(content_label(content_type), options)
.width(Size::px(TYPE_DROPDOWN_W))
.height(Size::px(24.))
.on_select(move |idx: usize| {
if let Some(content_type) = IMPORTABLE.get(idx) {
overrides.write().insert(key.clone(), *content_type);
}
}),
)
} }

View file

@ -0,0 +1,245 @@
use freya::animation::{AnimNum, Ease, OnCreation, use_animation};
use freya::prelude::*;
use crate::components::{Button, Icon, IconType};
use crate::hooks::use_overlay_claim;
use crate::theme::colors;
const PANEL_W: f32 = 172.;
const BUTTON_W: f32 = 34.;
#[derive(Clone, PartialEq)]
pub struct FilterOption {
text: String,
selected: bool,
on_press: EventHandler<()>,
}
impl FilterOption {
pub fn new(
text: impl Into<String>,
selected: bool,
on_press: impl Into<EventHandler<()>>,
) -> Self {
Self {
text: text.into(),
selected,
on_press: on_press.into(),
}
}
}
#[derive(Clone, PartialEq)]
struct FilterSection {
title: &'static str,
options: Vec<FilterOption>,
}
#[derive(PartialEq)]
pub struct FilterMenu {
active: bool,
sections: Vec<FilterSection>,
}
impl FilterMenu {
pub fn new(active: bool) -> Self {
Self {
active,
sections: Vec::new(),
}
}
pub fn section(
mut self,
title: &'static str,
options: impl IntoIterator<Item = FilterOption>,
) -> Self {
self.sections.push(FilterSection {
title,
options: options.into_iter().collect(),
});
self
}
}
impl Component for FilterMenu {
fn render(&self) -> impl IntoElement {
let mut open = use_state(|| false);
let icon_color = if self.active {
colors::brand()
} else {
colors::fg_secondary()
};
rect()
.width(Size::px(BUTTON_W))
.child(
Button::new()
.secondary()
.icon()
.tooltip("Sort and filter")
.width(Size::px(BUTTON_W))
.height(Size::px(34.))
.on_press(move |e: Event<PressEventData>| {
e.stop_propagation();
open.toggle();
})
.child(Icon::new(IconType::Sliders04).size(16.).color(icon_color)),
)
.maybe_child(open().then(|| {
FilterPanel {
sections: self.sections.clone(),
on_close: (move |()| open.set(false)).into(),
}
.into_element()
}))
}
}
#[derive(PartialEq)]
struct FilterPanel {
sections: Vec<FilterSection>,
on_close: EventHandler<()>,
}
impl Component for FilterPanel {
fn render(&self) -> impl IntoElement {
use_overlay_claim();
let backdrop_close = self.on_close.clone();
let key_close = self.on_close.clone();
let a11y_id = use_a11y();
let fade = use_animation(|conf| {
conf.on_creation(OnCreation::Run);
AnimNum::new(0., 1.).time(160).ease(Ease::Out)
});
let progress = fade.read().value();
let mut panel = rect()
.vertical()
.width(Size::fill())
.spacing(4.)
.padding(Gaps::new_all(8.))
.opacity(progress)
.offset_y((progress - 1.0) * 6.)
.corner_radius(CornerRadius::new_all(10.))
.background(colors::page_elevated().with_a(230))
.backdrop_blur(12.)
.border(crate::ui::border_all_color(1., colors::component_border()))
.shadow(Shadow::from((
0.,
8.,
32.,
0.,
Color::from_argb(120, 0, 0, 0),
)))
.a11y_id(a11y_id)
.a11y_role(AccessibilityRole::Dialog)
.on_global_key_down(move |e: Event<KeyboardEventData>| {
if e.key == Key::Named(NamedKey::Escape) {
key_close.call(());
}
});
for section in &self.sections {
panel = panel.child(section_label(section.title));
for option in &section.options {
panel = panel.child(ChoiceRow {
option: option.clone(),
});
}
}
rect()
.height(Size::px(0.))
.width(Size::px(PANEL_W))
.layer(Layer::Overlay)
.child(
rect()
.layer(Layer::OverlayLevel(10))
.position(Position::new_global().top(0.).left(0.))
.width(Size::window_percent(100.))
.height(Size::window_percent(100.))
.on_press(move |_| backdrop_close.call(())),
)
.child(
rect()
.width(Size::fill())
.layer(Layer::OverlayLevel(12))
.margin(Gaps::new(6., 0., 0., -(PANEL_W - BUTTON_W)))
.child(panel),
)
}
}
fn section_label(text: &'static str) -> impl IntoElement {
label()
.text(text)
.font_size(10.)
.font_weight(FontWeight::SEMI_BOLD)
.color(colors::fg_secondary())
}
#[derive(PartialEq)]
struct ChoiceRow {
option: FilterOption,
}
impl Component for ChoiceRow {
fn render(&self) -> impl IntoElement {
let selected = self.option.selected;
let on_press = self.option.on_press.clone();
let a11y_id = use_a11y();
let focus = use_focus(a11y_id);
let focused = focus().is_focused();
let color = if selected {
colors::fg_primary()
} else {
colors::fg_secondary()
};
let bg = if selected {
colors::component_bg()
} else if focused {
colors::ghost_overlay_hover()
} else {
Color::TRANSPARENT
};
rect()
.horizontal()
.width(Size::fill())
.cross_align(Alignment::Center)
.spacing(8.)
.padding(Gaps::new_symmetric(5., 8.))
.corner_radius(CornerRadius::new_all(6.))
.background(bg)
.content(Content::Flex)
.a11y_id(a11y_id)
.a11y_focusable(true)
.a11y_role(AccessibilityRole::MenuItemRadio)
.maybe(focused, |el| {
el.border(crate::ui::border_all_color(1., colors::brand()))
})
.cursor(CursorIcon::Pointer)
.on_press(move |_| on_press.call(()))
.child(
label()
.text(self.option.text.clone())
.font_size(12.)
.width(Size::flex(1.0))
.color(color),
)
.maybe_child(selected.then(|| {
Icon::new(IconType::Check)
.size(14.)
.color(colors::brand())
.into_element()
}))
}
}

View file

@ -0,0 +1,282 @@
use freya::prelude::*;
use freya::router::RouterContext;
use oneclient_content::packages::ResolvedAlternative;
use crate::components::{
Button, Icon, IconType, Markdown, MarkdownStyle, OverlayPopup, remote_icon,
};
use crate::hooks::{use_cached_image, use_dispatch, use_installs_snapshot};
use crate::routes::Route;
use crate::state::FlaggedInstallPrompt;
use crate::theme::colors;
use crate::ui::border_all_color;
use crate::view::app::browser::encode_package_id;
const CARD_BG: Color = Color::from_rgb(26, 34, 41);
const DIALOG_W: f32 = 460.;
const ROW_H: f32 = 48.;
const ICON_SIZE: f32 = 32.;
const ROW_GAP: f32 = 6.;
const SECTION_GAP: f32 = 14.;
const CONTENT_MAX_H: f32 = 400.;
const DIALOG_PADDING: f32 = 20.;
#[derive(PartialEq)]
pub struct FlaggedInstallPopup;
impl Component for FlaggedInstallPopup {
fn render(&self) -> impl IntoElement {
let installs = use_installs_snapshot();
let dispatch = use_dispatch();
let Some(prompt) = installs.flagged.clone() else {
return rect().into_element();
};
let close = dispatch.clone();
OverlayPopup::new()
.on_close(move |_| close.dismiss_flagged_install())
.child(
rect()
.width(Size::window_percent(100.))
.height(Size::window_percent(100.))
.center()
.child(dialog(prompt, dispatch)),
)
.into_element()
}
}
fn dialog(prompt: FlaggedInstallPrompt, dispatch: crate::Actions) -> impl IntoElement {
let description = if prompt.alternatives.is_empty() {
format!(
"{} is flagged as a problematic mod. Installing it is not recommended.",
prompt.name
)
} else {
format!(
"{} is flagged as a problematic mod. Installing it is not recommended, consider one of these alternatives instead.",
prompt.name
)
};
let mut body = rect()
.vertical()
.width(Size::px(DIALOG_W))
.height(Size::Inner)
.max_width(Size::window_percent(90.))
.max_height(Size::window_percent(85.))
.overflow(Overflow::Clip)
.spacing(SECTION_GAP)
.padding(Gaps::new_all(DIALOG_PADDING))
.corner_radius(CornerRadius::new_all(14.))
.background(CARD_BG)
.border(border_all_color(1., colors::component_border()))
.child(
rect()
.vertical()
.width(Size::fill())
.spacing(SECTION_GAP)
.child(
rect()
.horizontal()
.cross_align(Alignment::Center)
.spacing(10.)
.child(
Icon::new(IconType::AlertTriangle)
.size(20.)
.color(colors::code_warn()),
)
.child(
label()
.text("Flagged mod")
.font_size(16.)
.font_weight(FontWeight::SEMI_BOLD)
.color(colors::fg_primary()),
),
)
.child(
label()
.text(description)
.font_size(12.)
.width(Size::fill())
.color(colors::fg_secondary()),
),
);
if prompt.explanation.is_some() || !prompt.alternatives.is_empty() {
body = body.child(flagged_content(&prompt));
}
let cancel = dispatch.clone();
let anyway = dispatch;
body.child(
rect()
.horizontal()
.width(Size::fill())
.main_align(Alignment::End)
.spacing(8.)
.child(
Button::new()
.secondary()
.on_press(move |_| cancel.dismiss_flagged_install())
.text("Cancel"),
)
.child(
Button::new()
.danger()
.on_press(move |_| anyway.install_flagged_anyway(prompt.clone()))
.text("Install anyway"),
),
)
}
fn flagged_content(prompt: &FlaggedInstallPrompt) -> impl IntoElement {
let mut content = rect().vertical().width(Size::fill()).spacing(SECTION_GAP);
if let Some(markdown) = &prompt.explanation {
content = content.child(explanation_panel(markdown.clone()));
}
if !prompt.alternatives.is_empty() {
content = content.child(
rect()
.vertical()
.width(Size::fill())
.spacing(ROW_GAP)
.children(prompt.alternatives.iter().map(|alternative| {
AlternativeRow {
alternative: alternative.clone(),
cluster_id: prompt.cluster_id,
mc_version: prompt.mc_version.clone(),
}
.into_element()
})),
);
}
ScrollView::new()
.width(Size::fill())
.height(Size::Inner)
.max_height(Size::px(CONTENT_MAX_H))
.child(content)
}
#[derive(PartialEq)]
struct AlternativeRow {
alternative: ResolvedAlternative,
cluster_id: i64,
mc_version: String,
}
impl Component for AlternativeRow {
fn render(&self) -> impl IntoElement {
let dispatch = use_dispatch();
let icon_query = use_cached_image(self.alternative.icon_url.clone(), 256);
let icon = remote_icon(self.alternative.icon_url.as_deref(), &icon_query, ICON_SIZE);
let version = self
.alternative
.version_number
.clone()
.unwrap_or_else(|| format!("No version for {}", self.mc_version));
alternative_row(
self.alternative.clone(),
version,
self.cluster_id,
icon,
dispatch,
)
}
}
fn alternative_row(
alternative: ResolvedAlternative,
version: String,
cluster_id: i64,
icon: Element,
dispatch: crate::Actions,
) -> impl IntoElement {
let ResolvedAlternative {
provider,
project_id,
name,
..
} = alternative;
rect()
.horizontal()
.content(Content::Flex)
.width(Size::fill())
.height(Size::px(ROW_H))
.cross_align(Alignment::Center)
.spacing(10.)
.padding(Gaps::new_symmetric(0., 8.))
.corner_radius(CornerRadius::new_all(8.))
.background(colors::component_bg())
.border(border_all_color(1., colors::component_border()))
.child(icon)
.child(
rect()
.vertical()
.width(Size::flex(1.))
.spacing(2.)
.child(
label()
.text(name)
.font_size(13.)
.font_weight(FontWeight::SEMI_BOLD)
.max_lines(1)
.color(colors::fg_primary()),
)
.child(
label()
.text(version)
.font_size(11.)
.max_lines(1)
.color(colors::fg_secondary()),
),
)
.child(
Button::new()
.primary()
.small()
.on_press(move |_| {
dispatch.dismiss_flagged_install();
let _ = RouterContext::get().push(Route::BrowserPackage {
cluster_id,
package_type: "mod".to_string(),
package_id: encode_package_id(provider, &project_id),
});
})
.child(Icon::new(IconType::ArrowRight).size(14.))
.text("Go to page"),
)
}
fn explanation_panel(markdown: String) -> impl IntoElement {
rect()
.width(Size::fill())
.padding(Gaps::new_all(12.))
.corner_radius(CornerRadius::new_all(8.))
.background(colors::component_bg())
.border(border_all_color(1., colors::component_border()))
.child(
Markdown::new(markdown)
.width(Size::fill())
.style(MarkdownStyle {
color: colors::fg_primary(),
color_link: colors::code_info(),
color_code: colors::fg_primary(),
background_code: CARD_BG,
background_blockquote: CARD_BG,
border_blockquote: colors::component_border(),
background_divider: colors::component_border(),
headings: [16., 14., 13., 12., 12., 12.],
paragraph_size: 12.,
code_font_size: 11.,
..MarkdownStyle::default()
}),
)
}

View file

@ -79,9 +79,7 @@ impl Component for GenericPromptOverlay {
.pick_folder() .pick_folder()
.await .await
{ {
dispatch.answer_prompt( dispatch.answer_prompt(Answer::new(id).with_folder(handle.path()));
Answer::new(id).with_folder(handle.path()),
);
} }
}); });
} }

View file

@ -51,7 +51,11 @@ impl Icon {
impl Component for Icon { impl Component for Icon {
fn render(&self) -> impl IntoElement { fn render(&self) -> impl IntoElement {
let path = self.icon.path(); let path = self.icon.path();
let bytes = use_memo(move || AppAssets::get_bytes(path).unwrap_or_default()); let load = move || (path, AppAssets::get_bytes(path).unwrap_or_default());
let mut bytes = use_state(load);
if bytes.peek().0 != path {
bytes.set(load());
}
let tint = use_hook(try_consume_context::<IconTint>); let tint = use_hook(try_consume_context::<IconTint>);
let color = self let color = self
@ -59,7 +63,7 @@ impl Component for Icon {
.or_else(|| tint.map(|tint| *tint.0.read())) .or_else(|| tint.map(|tint| *tint.0.read()))
.unwrap_or_else(colors::fg_primary); .unwrap_or_else(colors::fg_primary);
SvgViewer::new((path, bytes.read().cloned())) SvgViewer::new((path, bytes.peek().1.clone()))
.show_loader(false) .show_loader(false)
.width(Size::px(self.size_px)) .width(Size::px(self.size_px))
.height(Size::px(self.size_px)) .height(Size::px(self.size_px))
@ -87,6 +91,7 @@ pub enum IconType {
ArrowLeft, ArrowLeft,
ArrowRight, ArrowRight,
Brush01, Brush01,
BarChartSquare02,
Bell01, Bell01,
Calendar, Calendar,
CheckCircle, CheckCircle,
@ -102,6 +107,7 @@ pub enum IconType {
Copy01, Copy01,
Curseforge, Curseforge,
Database01, Database01,
Discord,
DotsGrid, DotsGrid,
DotsVertical, DotsVertical,
Download01, Download01,
@ -113,6 +119,7 @@ pub enum IconType {
Folder, Folder,
FolderCheck, FolderCheck,
FolderDownload, FolderDownload,
Github,
Globe01, Globe01,
HelpCircle, HelpCircle,
IconLogo, IconLogo,
@ -123,10 +130,12 @@ pub enum IconType {
Link03, Link03,
LinkExternal01, LinkExternal01,
Loading02, Loading02,
LogOut01,
Maximize01, Maximize01,
MessageTextSquare01, MessageTextSquare01,
Minus, Minus,
Modrinth, Modrinth,
Moon01,
OnboardingAccount, OnboardingAccount,
OnboardingComplete, OnboardingComplete,
OnboardingLanguage, OnboardingLanguage,

View file

@ -0,0 +1,155 @@
use freya::prelude::*;
use oneclient_core::clusters::Cluster;
use crate::components::{ART_PREVIEW_EDGE, DynamicArt};
use crate::theme::colors;
use crate::ui::{border_all_color, last_played_label};
use crate::utils::{GridSelection, ReleaseLine, line_art_key, line_title};
const ROW_HEIGHT_PX: f32 = 56.;
const THUMB_PX: f32 = 40.;
pub struct InstanceRow {
key: GridSelection,
title: String,
subtitle: String,
played: String,
art: DynamicArt,
selected: bool,
on_press: EventHandler<Event<PressEventData>>,
}
impl InstanceRow {
pub fn new(
cluster: &Cluster,
selected: bool,
on_press: impl Into<EventHandler<Event<PressEventData>>>,
) -> Self {
Self {
key: GridSelection::Instance(cluster.id),
title: cluster.name.clone(),
subtitle: format!("{} \u{b7} {}", cluster.mc_version, cluster.mc_loader),
played: last_played_label(cluster.last_played),
art: DynamicArt::for_cluster(cluster).max_edge(ART_PREVIEW_EDGE),
selected,
on_press: on_press.into(),
}
}
pub fn for_line(
line: ReleaseLine,
clusters: &[Cluster],
caption: String,
selected: bool,
on_press: impl Into<EventHandler<Event<PressEventData>>>,
) -> Self {
Self {
key: GridSelection::Line(line),
title: line_title(line, clusters),
subtitle: caption,
played: last_played_label(clusters.iter().filter_map(|c| c.last_played).max()),
art: DynamicArt::for_version(line.major, line_art_key(line, clusters), None)
.max_edge(ART_PREVIEW_EDGE),
selected,
on_press: on_press.into(),
}
}
}
impl PartialEq for InstanceRow {
fn eq(&self, other: &Self) -> bool {
self.key == other.key
&& self.title == other.title
&& self.subtitle == other.subtitle
&& self.played == other.played
&& self.art == other.art
&& self.selected == other.selected
}
}
impl Component for InstanceRow {
fn render(&self) -> impl IntoElement {
let mut hovering = use_state(|| false);
let a11y_id = use_a11y();
let focus = use_focus(a11y_id);
let selected = self.selected;
let hovered = *hovering.read();
let focused = focus().is_focused();
let on_press = self.on_press.clone();
let background = if selected {
colors::brand().with_a(36)
} else if hovered {
colors::component_bg_hover()
} else {
colors::component_bg()
};
let border_color = if selected || focused {
colors::brand()
} else if hovered {
colors::component_border_hover()
} else {
colors::component_border()
};
rect()
.key(self.key)
.horizontal()
.width(Size::fill())
.height(Size::px(ROW_HEIGHT_PX))
.padding(Gaps::new(0., 14., 0., 8.))
.spacing(14.)
.cross_align(Alignment::Center)
.content(Content::Flex)
.corner_radius(CornerRadius::new_all(12.))
.background(background)
.border(border_all_color(1., border_color).alignment(BorderAlignment::Inner))
.a11y_id(a11y_id)
.a11y_focusable(true)
.a11y_role(AccessibilityRole::Button)
.on_press(move |e| on_press.call(e))
.on_pointer_enter(move |_| hovering.set(true))
.on_pointer_leave(move |_| hovering.set(false))
.child(
rect()
.width(Size::px(THUMB_PX))
.height(Size::px(THUMB_PX))
.corner_radius(CornerRadius::new_all(8.))
.overflow(Overflow::Clip)
.child(self.art.clone()),
)
.child(
rect()
.vertical()
.width(Size::flex(1.0))
.spacing(3.)
.child(
label()
.text(self.title.clone())
.font_size(14.)
.font_weight(FontWeight::MEDIUM)
.max_lines(1)
.text_overflow(TextOverflow::Ellipsis)
.color(colors::fg_primary()),
)
.child(
label()
.text(self.subtitle.clone())
.font_size(12.)
.max_lines(1)
.text_overflow(TextOverflow::Ellipsis)
.color(colors::fg_secondary()),
),
)
.child(
label()
.text(self.played.clone())
.font_size(12.)
.max_lines(1)
.color(colors::fg_secondary()),
)
}
}

View file

@ -1,6 +1,6 @@
use freya::prelude::*; use freya::prelude::*;
use oneclient_java::{JAVA_CHOICE_DOWNLOAD, JAVA_CHOICE_FOLDER, JavaVendor};
use oneclient_events::Answer; use oneclient_events::Answer;
use oneclient_java::{JAVA_CHOICE_DOWNLOAD, JAVA_CHOICE_FOLDER, JavaVendor};
use crate::components::{Button, Icon, IconType, JavaInstallManager, OverlayPopup}; use crate::components::{Button, Icon, IconType, JavaInstallManager, OverlayPopup};
use crate::hooks::{use_dispatch, use_notifications_snapshot}; use crate::hooks::{use_dispatch, use_notifications_snapshot};
@ -65,9 +65,8 @@ impl Component for JavaPromptOverlay {
.pick_folder() .pick_folder()
.await .await
{ {
dispatch.answer_prompt( dispatch
Answer::new(JAVA_CHOICE_FOLDER).with_folder(handle.path()), .answer_prompt(Answer::new(JAVA_CHOICE_FOLDER).with_folder(handle.path()));
);
} }
}); });
}; };
@ -124,9 +123,7 @@ impl Component for JavaPromptOverlay {
.child( .child(
Button::new() Button::new()
.secondary() .secondary()
.on_press(move |_| { .on_press(move |_| cancel.dismiss_prompt())
cancel.dismiss_prompt()
})
.text("Cancel"), .text("Cancel"),
) )
.child( .child(

View file

@ -1,7 +1,9 @@
use freya::prelude::*; use freya::prelude::*;
use crate::components::{Button, Icon, IconType, OverlayPopup}; use crate::components::{
use crate::hooks::use_link_confirm; Button, CodeBlock, Icon, IconType, OVERLAY_BASE_LEVEL, OverlayPopup, copy_button,
};
use crate::hooks::{use_dispatch, use_link_confirm};
use crate::theme::colors; use crate::theme::colors;
use crate::ui::border_all_color; use crate::ui::border_all_color;
@ -13,13 +15,26 @@ pub struct ConfirmLinkOverlay;
impl Component for ConfirmLinkOverlay { impl Component for ConfirmLinkOverlay {
fn render(&self) -> impl IntoElement { fn render(&self) -> impl IntoElement {
let mut pending = use_link_confirm(); let mut pending = use_link_confirm();
let dispatch = use_dispatch();
let Some(url) = pending.read().clone() else { let Some(url) = pending.read().clone() else {
return rect().into_element(); return rect().into_element();
}; };
let open_url = url.clone(); let open_url = url.clone();
let (title, body) = if is_first_party(&url) {
(
"Leave the launcher?",
"This will open the page in your web browser.",
)
} else {
(
"Open external link?",
"This link was provided by a third party. Only open it if you trust the source.",
)
};
OverlayPopup::new() OverlayPopup::new()
.overlay_level(OVERLAY_BASE_LEVEL + 3)
.on_close(move |_| pending.set(None)) .on_close(move |_| pending.set(None))
.child( .child(
rect() rect()
@ -48,7 +63,7 @@ impl Component for ConfirmLinkOverlay {
) )
.child( .child(
label() label()
.text("Open external link?") .text(title)
.font_size(16.) .font_size(16.)
.font_weight(FontWeight::SEMI_BOLD) .font_weight(FontWeight::SEMI_BOLD)
.color(colors::fg_primary()), .color(colors::fg_primary()),
@ -56,25 +71,14 @@ impl Component for ConfirmLinkOverlay {
) )
.child( .child(
label() label()
.text("This link was provided by a third party. Only open it if you trust the source.") .text(body)
.font_size(12.) .font_size(12.)
.color(colors::fg_secondary()), .color(colors::fg_secondary()),
) )
.child( .child(
rect() CodeBlock::new(url.clone())
.width(Size::fill()) .color(colors::code_info())
.padding(Gaps::new_all(10.)) .max_lines(4),
.corner_radius(CornerRadius::new_all(8.))
.background(colors::component_bg())
.border(border_all_color(1., colors::component_border()))
.child(
label()
.text(url.clone())
.font_size(12.)
.max_lines(4)
.width(Size::fill())
.color(colors::code_info()),
),
) )
.child( .child(
rect() rect()
@ -82,6 +86,11 @@ impl Component for ConfirmLinkOverlay {
.width(Size::fill()) .width(Size::fill())
.main_align(Alignment::End) .main_align(Alignment::End)
.spacing(8.) .spacing(8.)
.child(copy_button(
url.clone(),
"Link copied to your clipboard.",
dispatch,
))
.child( .child(
Button::new() Button::new()
.secondary() .secondary()
@ -104,3 +113,13 @@ impl Component for ConfirmLinkOverlay {
.into_element() .into_element()
} }
} }
fn is_first_party(url: &str) -> bool {
let host = url
.split_once("://")
.map_or(url, |(_, rest)| rest)
.split(['/', '?', '#', ':'])
.next()
.unwrap_or_default();
host == "polyfrost.org" || host.ends_with(".polyfrost.org")
}

View file

@ -17,6 +17,7 @@ pub struct LocalImage {
max_edge: u32, max_edge: u32,
cover: bool, cover: bool,
skeleton: bool, skeleton: bool,
picked: bool,
} }
impl LocalImage { impl LocalImage {
@ -26,9 +27,15 @@ impl LocalImage {
max_edge, max_edge,
cover, cover,
skeleton: false, skeleton: false,
picked: false,
} }
} }
pub fn picked(mut self, picked: bool) -> Self {
self.picked = picked;
self
}
pub fn skeleton(mut self, skeleton: bool) -> Self { pub fn skeleton(mut self, skeleton: bool) -> Self {
self.skeleton = skeleton; self.skeleton = skeleton;
self self
@ -37,9 +44,9 @@ impl LocalImage {
impl Component for LocalImage { impl Component for LocalImage {
fn render(&self) -> impl IntoElement { fn render(&self) -> impl IntoElement {
let query = use_local_image(self.path.clone(), self.max_edge); let query = use_local_image(self.path.clone(), self.max_edge, self.picked);
let bytes: Option<Bytes> =
let bytes: Option<Bytes> = settled_or_loading(&query); settled_or_loading(&query).filter(|bytes: &Bytes| !bytes.is_empty());
let mut cache = use_state(|| None::<(usize, ImageHandle)>); let mut cache = use_state(|| None::<(usize, ImageHandle)>);
let holder = bytes.and_then(|bytes| { let holder = bytes.and_then(|bytes| {

View file

@ -455,7 +455,7 @@ fn scroll_body(args: BodyArgs) -> Element {
.height(Size::px(content_h)) .height(Size::px(content_h))
.on_pointer_down(on_body_down) .on_pointer_down(on_body_down)
.on_capture_global_pointer_move(on_global_move) .on_capture_global_pointer_move(on_global_move)
.on_capture_global_pointer_press(on_global_release) .on_capture_global_pointer_up(on_global_release)
.on_global_key_down(on_key_down) .on_global_key_down(on_key_down)
.child(log_body( .child(log_body(
&lines[..], &lines[..],

View file

@ -3,12 +3,13 @@ use std::borrow::Cow;
use std::rc::Rc; use std::rc::Rc;
use freya::elements::image::ImageHandle; use freya::elements::image::ImageHandle;
use freya::engine::prelude::{ClipOp, Paint, SkRect}; use freya::engine::prelude::{ClipOp, FontMgr, Paint, Picture, PictureRecorder, SkRect, svg};
use freya::prelude::*; use freya::prelude::*;
use freya_core::element::{ClipContext, ElementExt, LayoutContext}; use freya_core::element::{ClipContext, ElementExt, LayoutContext};
use freya_core::tree::DiffModifies; use freya_core::tree::DiffModifies;
use super::super::local_image::decode; use super::super::local_image::decode;
use super::svg_compat::prepare_svg;
use crate::hooks::{loaded_image, use_cached_image}; use crate::hooks::{loaded_image, use_cached_image};
use crate::theme::colors; use crate::theme::colors;
@ -34,24 +35,24 @@ impl Component for MarkdownImage {
let query = use_cached_image(Some(self.url.clone()), MAX_EDGE); let query = use_cached_image(Some(self.url.clone()), MAX_EDGE);
let loaded = loaded_image(Some(&self.url), &query); let loaded = loaded_image(Some(&self.url), &query);
let mut cache = use_state(|| None::<(usize, ImageHandle)>); let mut cache = use_state(|| None::<(usize, ImageContent)>);
let holder = loaded.and_then(|(_, bytes)| { let content = loaded.and_then(|(_, bytes)| {
let ptr = bytes.as_ptr() as usize; let ptr = bytes.as_ptr() as usize;
if let Some((cached_ptr, holder)) = cache.read().clone() if let Some((cached_ptr, content)) = cache.read().clone()
&& cached_ptr == ptr && cached_ptr == ptr
{ {
return Some(holder); return Some(content);
} }
let holder = decode(&bytes)?; let content = decode_any(&bytes)?;
cache.set(Some((ptr, holder.clone()))); cache.set(Some((ptr, content.clone())));
Some(holder) Some(content)
}); });
match holder { match content {
Some(holder) => scaled_image(holder) Some(content) => scaled_content(content)
.a11y_role(AccessibilityRole::Image) .a11y_role(AccessibilityRole::Image)
.a11y_alt(self.alt.clone()) .a11y_alt(self.alt.clone())
.into_element(), .into_element(),
@ -64,6 +65,90 @@ impl Component for MarkdownImage {
} }
} }
#[derive(Clone, PartialEq)]
enum ImageContent {
Bitmap(ImageHandle),
Vector(SvgPicture),
}
/// An svg recorded as drawing commands, so it is drawn straight onto the
/// window's pixel grid instead of being resampled from a bitmap
#[derive(Clone)]
struct SvgPicture {
picture: Picture,
/// Logical size, scaled to physical pixels at layout like any `Size::px`
size: Size2D,
}
impl PartialEq for SvgPicture {
fn eq(&self, other: &Self) -> bool {
self.picture.unique_id() == other.picture.unique_id() && self.size == other.size
}
}
fn decode_any(bytes: &Bytes) -> Option<ImageContent> {
if let Some(handle) = decode(bytes) {
return Some(ImageContent::Bitmap(handle));
}
// Skia's codecs can't read svg, which is what shields.io badges are
if !looks_like_svg(bytes) {
return None;
}
record_svg(bytes).map(ImageContent::Vector)
}
fn looks_like_svg(bytes: &[u8]) -> bool {
let head = &bytes[..bytes.len().min(1024)];
let head = head.strip_prefix(&[0xEF, 0xBB, 0xBF]).unwrap_or(head);
let start = head
.iter()
.position(|b| !b.is_ascii_whitespace())
.unwrap_or(head.len());
let head = &head[start..];
head.starts_with(b"<svg")
|| (head.starts_with(b"<?xml")
|| head.starts_with(b"<!--")
|| head.starts_with(b"<!DOCTYPE"))
&& head.windows(4).any(|w| w == b"<svg")
}
fn svg_size(root: &svg::Svg) -> Option<Size2D> {
let intrinsic = root.intrinsic_size();
let size = if intrinsic.width > 0. && intrinsic.height > 0. {
Size2D::new(intrinsic.width, intrinsic.height)
} else {
let view_box = root.view_box()?;
Size2D::new(view_box.width(), view_box.height())
};
let valid = size.width.is_finite() && size.height.is_finite();
(valid && size.width > 0. && size.height > 0.).then_some(size)
}
/// Badges are mostly text, so the dom gets the system fonts rather than an empty manager
fn record_svg(bytes: &[u8]) -> Option<SvgPicture> {
let mut dom = svg::Dom::from_bytes(&prepare_svg(bytes), FontMgr::default()).ok()?;
let declared = svg_size(&dom.root())?;
dom.set_container_size((declared.width, declared.height));
// the dom always draws at its declared size, so an oversized svg is scaled down while recording
let shrink = (MAX_EDGE as f32 / declared.width.max(declared.height)).min(1.);
let size = declared * shrink;
let bounds = SkRect::from_wh(size.width, size.height);
let mut recorder = PictureRecorder::new();
let canvas = recorder.begin_recording(bounds, false);
canvas.clip_rect(bounds, None, None);
canvas.scale((shrink, shrink));
dom.render(canvas);
let picture = recorder.finish_recording_as_picture(Some(&bounds))?;
Some(SvgPicture { picture, size })
}
#[derive(Clone)] #[derive(Clone)]
pub struct ScaledImage { pub struct ScaledImage {
key: DiffKey, key: DiffKey,
@ -74,18 +159,18 @@ pub struct ScaledImage {
pub struct ScaledImageElement { pub struct ScaledImageElement {
accessibility: AccessibilityData, accessibility: AccessibilityData,
layout: LayoutData, layout: LayoutData,
image_handle: ImageHandle, content: ImageContent,
sampling_mode: SamplingMode, sampling_mode: SamplingMode,
corner_radius: CornerRadius, corner_radius: CornerRadius,
} }
pub fn scaled_image(image_handle: ImageHandle) -> ScaledImage { fn scaled_content(content: ImageContent) -> ScaledImage {
ScaledImage { ScaledImage {
key: DiffKey::None, key: DiffKey::None,
element: ScaledImageElement { element: ScaledImageElement {
accessibility: AccessibilityData::default(), accessibility: AccessibilityData::default(),
layout: LayoutData::default(), layout: LayoutData::default(),
image_handle, content,
sampling_mode: SamplingMode::default(), sampling_mode: SamplingMode::default(),
corner_radius: CornerRadius::default(), corner_radius: CornerRadius::default(),
}, },
@ -134,6 +219,18 @@ impl From<ScaledImage> for Element {
} }
} }
impl ImageContent {
/// Size in layout (physical) pixels; bitmaps keep their pixel dimensions
fn size(&self, scale_factor: f32) -> Size2D {
match self {
Self::Bitmap(handle) => {
Size2D::new(handle.image.width() as f32, handle.image.height() as f32)
}
Self::Vector(svg) => svg.size * scale_factor,
}
}
}
impl ElementExt for ScaledImageElement { impl ElementExt for ScaledImageElement {
fn changed(&self, other: &Rc<dyn ElementExt>) -> bool { fn changed(&self, other: &Rc<dyn ElementExt>) -> bool {
let Some(other) = (other.as_ref() as &dyn Any).downcast_ref::<ScaledImageElement>() else { let Some(other) = (other.as_ref() as &dyn Any).downcast_ref::<ScaledImageElement>() else {
@ -157,10 +254,10 @@ impl ElementExt for ScaledImageElement {
diff.insert(DiffModifies::LAYOUT); diff.insert(DiffModifies::LAYOUT);
} }
if self.image_handle != other.image_handle { if self.content != other.content {
diff.insert(DiffModifies::STYLE); diff.insert(DiffModifies::STYLE);
if self.image_handle.image.dimensions() != other.image_handle.image.dimensions() { if self.content.size(1.) != other.content.size(1.) {
diff.insert(DiffModifies::LAYOUT); diff.insert(DiffModifies::LAYOUT);
} }
} }
@ -192,11 +289,9 @@ impl ElementExt for ScaledImageElement {
} }
fn measure(&self, context: LayoutContext) -> Option<(Size2D, Rc<dyn Any>)> { fn measure(&self, context: LayoutContext) -> Option<(Size2D, Rc<dyn Any>)> {
let image = &self.image_handle.image; let natural = self.content.size(context.scale_factor as f32);
let natural = Size2D::new(image.width() as f32, image.height() as f32);
let available = let available = (*context.area_size - context.torin_node.margin.into()).max(Size2D::zero());
(*context.area_size - context.torin_node.margin.into()).max(Size2D::zero());
let ratio = (available.width / natural.width).min(1.); let ratio = (available.width / natural.width).min(1.);
let ratio = if ratio.is_finite() && ratio > 0. { let ratio = if ratio.is_finite() && ratio > 0. {
@ -239,16 +334,28 @@ impl ElementExt for ScaledImageElement {
.canvas .canvas
.clip_rrect(clip_rrect, ClipOp::Intersect, true); .clip_rrect(clip_rrect, ClipOp::Intersect, true);
let mut paint = Paint::default(); match &self.content {
paint.set_anti_alias(true); ImageContent::Bitmap(handle) => {
let mut paint = Paint::default();
paint.set_anti_alias(true);
context.canvas.draw_image_rect_with_sampling_options( context.canvas.draw_image_rect_with_sampling_options(
&self.image_handle.image, &handle.image,
None, None,
rect, rect,
self.sampling_mode.sampling_options(), self.sampling_mode.sampling_options(),
&paint, &paint,
); );
}
ImageContent::Vector(svg) => {
context.canvas.translate((rect.left, rect.top));
context.canvas.scale((
rect.width() / svg.size.width,
rect.height() / svg.size.height,
));
context.canvas.draw_picture(&svg.picture, None, None);
}
}
context.canvas.restore(); context.canvas.restore();
} }
@ -256,8 +363,8 @@ impl ElementExt for ScaledImageElement {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use freya::prelude::Bytes;
use freya::engine::prelude::AlphaType; use freya::engine::prelude::AlphaType;
use freya::prelude::Bytes;
use freya_testing::prelude::*; use freya_testing::prelude::*;
use super::*; use super::*;
@ -272,7 +379,9 @@ mod tests {
rect() rect()
.width(Size::px(panel.0)) .width(Size::px(panel.0))
.height(Size::px(panel.1)) .height(Size::px(panel.1))
.child(scaled_image(handle(image.0, image.1))) .child(scaled_content(ImageContent::Bitmap(handle(
image.0, image.1,
))))
}; };
let test = launch_test(app); let test = launch_test(app);
@ -292,7 +401,7 @@ mod tests {
rect().width(Size::px(300.)).height(Size::px(400.)).child( rect().width(Size::px(300.)).height(Size::px(400.)).child(
paragraph() paragraph()
.span(Span::new("before ")) .span(Span::new("before "))
.child(scaled_image(handle(600, 200))) .child(scaled_content(ImageContent::Bitmap(handle(600, 200))))
.span(Span::new(" after")), .span(Span::new(" after")),
) )
}; };
@ -325,4 +434,175 @@ mod tests {
fn short_panel_does_not_shrink_the_width() { fn short_panel_does_not_shrink_the_width() {
assert_eq!(measured((300., 60.), (600, 200)), Size2D::new(300., 100.)); assert_eq!(measured((300., 60.), (600, 200)), Size2D::new(300., 100.));
} }
const BADGE: &str = r##"<svg xmlns="http://www.w3.org/2000/svg" width="88" height="20"><rect width="88" height="20" fill="#007ec6"/><text x="10" y="14" font-family="Verdana" font-size="11">v3.0.1</text></svg>"##;
#[test]
fn sniffs_svg_documents() {
assert!(looks_like_svg(BADGE.as_bytes()));
assert!(looks_like_svg(
b"\xEF\xBB\xBF <?xml version=\"1.0\"?>\n<svg width=\"1\" height=\"1\"/>"
));
assert!(!looks_like_svg(b"\x89PNG\r\n\x1a\n"));
assert!(!looks_like_svg(b"<html><body>not found</body></html>"));
}
#[test]
fn records_a_badge_at_its_declared_size() {
let svg = record_svg(BADGE.as_bytes()).expect("badge records");
assert_eq!(svg.size, Size2D::new(88., 20.));
}
#[test]
fn falls_back_to_the_view_box() {
let svg = record_svg(
br#"<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 40 10" width="100%"/>"#,
)
.expect("view box svg records");
assert_eq!(svg.size, Size2D::new(40., 10.));
}
fn vector_size(scale_factor: f64) -> Size2D {
let svg = record_svg(BADGE.as_bytes()).expect("badge records");
let app = move || {
rect()
.width(Size::px(300.))
.height(Size::px(400.))
.child(scaled_content(ImageContent::Vector(svg.clone())))
};
let (test, _) = TestingRunner::new(app, Size2D::new(500., 500.), |_| {}, scale_factor);
test.find(|node, element| {
(element as &dyn Any)
.downcast_ref::<ScaledImageElement>()
.map(|_| node.layout())
})
.expect("image node")
.area
.size
}
#[test]
fn a_vector_follows_the_scale_factor_like_text() {
assert_eq!(vector_size(1.), Size2D::new(88., 20.));
assert_eq!(vector_size(1.5), Size2D::new(132., 30.));
}
#[test]
fn a_vector_at_a_fractional_offset_is_not_resampled() {
// grey ends at 39.5, so after the 10.5 offset the colour edge lands on pixel 50
let svg = record_svg(
br##"<svg xmlns="http://www.w3.org/2000/svg" width="88" height="20"><rect width="39.5" height="20" fill="#555555"/><rect x="39.5" width="48.5" height="20" fill="#007ec6"/></svg>"##,
)
.expect("svg records");
let app = move || {
rect()
.horizontal()
.child(rect().width(Size::px(10.5)).height(Size::px(1.)))
.child(scaled_content(ImageContent::Vector(svg.clone())))
};
let mut test = launch_test(app);
let png = test.render();
let snapshot = image::load_from_memory(png.as_bytes())
.expect("snapshot decodes")
.to_rgb8();
let rgb = |x: u32| {
let [r, g, b] = snapshot.get_pixel(x, 10).0;
(r, g, b)
};
assert_eq!(rgb(49), (0x55, 0x55, 0x55));
assert_eq!(rgb(50), (0x00, 0x7e, 0xc6));
}
/// A grey shields-style badge with a 14px logo slot, rendered at the origin
fn logo_pixel(href: &str) -> (u8, u8, u8) {
let badge = format!(
r##"<svg xmlns="http://www.w3.org/2000/svg" width="40" height="20"><rect width="40" height="20" fill="#555555"/><image x="5" y="3" width="14" height="14" href="{href}"/></svg>"##
);
let svg = record_svg(badge.as_bytes()).expect("badge records");
let app = move || rect().child(scaled_content(ImageContent::Vector(svg.clone())));
let mut test = launch_test(app);
let png = test.render();
let snapshot = image::load_from_memory(png.as_bytes())
.expect("snapshot decodes")
.to_rgb8();
let [r, g, b] = snapshot.get_pixel(12, 10).0;
(r, g, b)
}
fn data_url(mime: &str, bytes: &[u8]) -> String {
use base64::Engine;
format!(
"data:{mime};base64,{}",
base64::engine::general_purpose::STANDARD.encode(bytes)
)
}
#[test]
fn draws_a_png_logo_given_by_href() {
let mut png = Vec::new();
image::RgbImage::from_pixel(4, 4, image::Rgb([255, 0, 0]))
.write_to(&mut std::io::Cursor::new(&mut png), image::ImageFormat::Png)
.expect("png encodes");
assert_eq!(logo_pixel(&data_url("image/png", &png)), (255, 0, 0));
}
#[test]
fn draws_an_svg_logo_given_as_a_data_url() {
let logo = br##"<svg fill="#5865F2" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M0 0h24v24H0z"/></svg>"##;
assert_eq!(
logo_pixel(&data_url("image/svg+xml", logo)),
(0x58, 0x65, 0xf2)
);
}
/// Replays a recorded svg at its own size on white and reads one pixel
fn picture_pixel(svg: &SvgPicture, x: u32, y: u32) -> (u8, u8, u8) {
use freya::engine::prelude::{EncodedImageFormat, raster_n32_premul};
let size = (svg.size.width.ceil() as i32, svg.size.height.ceil() as i32);
let mut surface = raster_n32_premul(size).expect("surface");
surface.canvas().clear(Color::WHITE);
surface.canvas().draw_picture(&svg.picture, None, None);
let png = surface
.image_snapshot()
.encode(None, EncodedImageFormat::PNG, None)
.expect("png encodes");
let [r, g, b] = image::load_from_memory(png.as_bytes())
.expect("png decodes")
.to_rgb8()
.get_pixel(x, y)
.0;
(r, g, b)
}
#[test]
fn an_oversized_svg_is_scaled_down_rather_than_cropped() {
let svg = record_svg(
br##"<svg xmlns="http://www.w3.org/2000/svg" width="3200" height="100"><rect width="1600" height="100" fill="#ff0000"/><rect x="1600" width="1600" height="100" fill="#0000ff"/></svg>"##,
)
.expect("svg records");
assert_eq!(svg.size, Size2D::new(1600., 50.));
assert_eq!(picture_pixel(&svg, 10, 40), (255, 0, 0));
assert_eq!(picture_pixel(&svg, 1590, 40), (0, 0, 255));
}
#[test]
fn a_logo_reusing_the_badges_ids_leaves_the_badge_intact() {
let logo = br##"<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><defs><clipPath id="r"><rect width="1" height="1"/></clipPath></defs><rect width="24" height="24" fill="#00ff00" clip-path="url(#r)"/></svg>"##;
let badge = format!(
r##"<svg xmlns="http://www.w3.org/2000/svg" width="80" height="20"><clipPath id="r"><rect width="80" height="20" rx="3"/></clipPath><g clip-path="url(#r)"><rect width="80" height="20" fill="#555555"/></g><image x="5" y="3" width="14" height="14" href="{}"/></svg>"##,
data_url("image/svg+xml", logo)
);
let svg = record_svg(badge.as_bytes()).expect("badge records");
assert_eq!(picture_pixel(&svg, 60, 10), (0x55, 0x55, 0x55));
}
} }

View file

@ -6,6 +6,7 @@ use pulldown_cmark::HeadingLevel;
mod image; mod image;
mod parse; mod parse;
mod render; mod render;
mod svg_compat;
#[derive(Clone, PartialEq)] #[derive(Clone, PartialEq)]
pub struct MarkdownStyle { pub struct MarkdownStyle {
@ -37,7 +38,7 @@ impl Default for MarkdownStyle {
paragraph_size: 16., paragraph_size: 16.,
code_font_size: 14., code_font_size: 14.,
table_font_size: 14., table_font_size: 14.,
code_font_family: Cow::Borrowed("Jetbrains Mono"), code_font_family: Cow::Borrowed(crate::theme::MONO_FONT),
} }
} }
} }
@ -115,4 +116,3 @@ impl Component for Markdown {
self.key.clone().or(self.default_key()) self.key.clone().or(self.default_key())
} }
} }

View file

@ -274,7 +274,10 @@ impl Walker {
} else { } else {
std::mem::take(&mut self.image_alt) std::mem::take(&mut self.image_alt)
}; };
self.place(Inline::Image { url, alt }, |url, alt| Block::Image { url, alt }); self.place(Inline::Image { url, alt }, |url, alt| Block::Image {
url,
alt,
});
} }
TagEnd::Link => { TagEnd::Link => {
self.in_link = false; self.in_link = false;
@ -488,18 +491,27 @@ mod tests {
#[test] #[test]
fn inline_html_keeps_the_text_it_wrapped() { fn inline_html_keeps_the_text_it_wrapped() {
assert_eq!(paragraph_text(&blocks("a <b>bold</b> word")[0]), "a bold word"); assert_eq!(
paragraph_text(&blocks("a <b>bold</b> word")[0]),
"a bold word"
);
} }
#[test] #[test]
fn block_html_keeps_its_text() { fn block_html_keeps_its_text() {
assert_eq!(paragraph_text(&blocks("<div>\nhello\n</div>")[0]).trim(), "hello"); assert_eq!(
paragraph_text(&blocks("<div>\nhello\n</div>")[0]).trim(),
"hello"
);
} }
#[test] #[test]
fn images_arrive_as_paragraph_content() { fn images_arrive_as_paragraph_content() {
assert_eq!(paragraph_text(&blocks("![alt](a.png)")[0]), "[img:alt]"); assert_eq!(paragraph_text(&blocks("![alt](a.png)")[0]), "[img:alt]");
assert_eq!(paragraph_text(&blocks("see ![alt](a.png) here")[0]), "see [img:alt] here"); assert_eq!(
paragraph_text(&blocks("see ![alt](a.png) here")[0]),
"see [img:alt] here"
);
} }
#[test] #[test]
@ -530,7 +542,8 @@ mod tests {
#[test] #[test]
fn tables_split_head_from_body() { fn tables_split_head_from_body() {
let Block::Table { headers, rows } = &blocks("| a | b |\n| - | - |\n| 1 | 2 |\n| 3 | 4 |")[0] let Block::Table { headers, rows } =
&blocks("| a | b |\n| - | - |\n| 1 | 2 |\n| 3 | 4 |")[0]
else { else {
panic!("expected a table"); panic!("expected a table");
}; };

View file

@ -3,32 +3,29 @@ use freya::prelude::*;
use super::MarkdownStyle; use super::MarkdownStyle;
use super::image::MarkdownImage; use super::image::MarkdownImage;
use super::parse::{Block, Inline, List, TextSpan}; use super::parse::{Block, Inline, List, TextSpan};
use crate::components::CodeBlock;
use crate::hooks::LinkConfirmState; use crate::hooks::LinkConfirmState;
pub fn render_block(block: &Block, key: usize, style: &MarkdownStyle) -> Element { pub fn render_block(block: &Block, key: usize, style: &MarkdownStyle) -> Element {
match block { match block {
Block::Heading { level, spans } => { Block::Heading { level, spans } => render_spans(
render_spans(spans, style.heading_size(*level), style.color, style.color_code) spans,
.font_weight(FontWeight::BOLD) style.heading_size(*level),
.key(key) style.color,
.into() style.color_code,
} )
.font_weight(FontWeight::BOLD)
.key(key)
.into(),
Block::Paragraph { content } => render_content(content, style.paragraph_size, style) Block::Paragraph { content } => render_content(content, style.paragraph_size, style)
.key(key) .key(key)
.into(), .into(),
Block::Code { code } => rect() Block::Code { code } => CodeBlock::new(code.clone())
.key(key) .color(style.color_code)
.width(Size::fill())
.background(style.background_code) .background(style.background_code)
.corner_radius(CornerRadius::new_all(6.)) .font_family(style.code_font_family.clone())
.padding(Gaps::new_all(12.)) .font_size(style.code_font_size)
.child( .key(key)
label()
.text(code.clone())
.font_family(style.code_font_family.clone())
.font_size(style.code_font_size)
.color(style.color_code),
)
.into(), .into(),
Block::List(list) => render_list(list, style).key(key).into(), Block::List(list) => render_list(list, style).key(key).into(),
Block::Image { url, alt } => rect() Block::Image { url, alt } => rect()
@ -53,7 +50,9 @@ pub fn render_block(block: &Block, key: usize, style: &MarkdownStyle) -> Element
.alignment(BorderAlignment::Inner), .alignment(BorderAlignment::Inner),
) )
.background(style.background_blockquote) .background(style.background_blockquote)
.child(render_content(content, style.paragraph_size, style).font_slant(FontSlant::Italic)) .child(
render_content(content, style.paragraph_size, style).font_slant(FontSlant::Italic),
)
.into(), .into(),
Block::Rule => rect() Block::Rule => rect()
.key(key) .key(key)
@ -74,7 +73,9 @@ fn styled_span(span: &TextSpan, text_color: Color, code_color: Color) -> Span<'s
styled = styled.font_slant(FontSlant::Italic); styled = styled.font_slant(FontSlant::Italic);
} }
if span.code { if span.code {
styled.font_family("monospace").color(code_color) styled
.font_family(crate::theme::MONO_FONT)
.color(code_color)
} else { } else {
styled.color(text_color) styled.color(text_color)
} }
@ -215,12 +216,7 @@ impl KeyExt for MarkdownLink {
} }
impl MarkdownLink { impl MarkdownLink {
fn new( fn new(url: String, title: Option<String>, content: Vec<Inline>, style: MarkdownStyle) -> Self {
url: String,
title: Option<String>,
content: Vec<Inline>,
style: MarkdownStyle,
) -> Self {
Self { Self {
url, url,
title, title,
@ -242,18 +238,24 @@ impl Component for MarkdownLink {
let mut text = paragraph().font_size(self.style.paragraph_size); let mut text = paragraph().font_size(self.style.paragraph_size);
for item in &self.content { for item in &self.content {
text = match item { text = match item {
Inline::Span(span) => { Inline::Span(span) => text.span(styled_span(
text.span(styled_span(span, self.style.color_link, self.style.color_code)) span,
} self.style.color_link,
self.style.color_code,
)),
Inline::Image { url, alt } => { Inline::Image { url, alt } => {
text.child(MarkdownImage::new(url.clone(), alt.clone())) text.child(MarkdownImage::new(url.clone(), alt.clone()))
} }
Inline::Link { content, .. } => content.iter().fold(text, |text, item| match item { Inline::Link { content, .. } => {
Inline::Span(span) => { content.iter().fold(text, |text, item| match item {
text.span(styled_span(span, self.style.color_link, self.style.color_code)) Inline::Span(span) => text.span(styled_span(
} span,
_ => text, self.style.color_link,
}), self.style.color_code,
)),
_ => text,
})
}
}; };
} }

View file

@ -0,0 +1,434 @@
//! Rewrites the parts of an svg Skia's svg module can't read.
//!
//! shields.io embeds badge logos as `<image href="data:...">`. Skia only reads
//! the SVG 1.1 `xlink:href`, and its `<image>` only decodes bitmaps, so svg logos
//! (`logo=discord`, `logo=modrinth`, ...) are inlined as nested `<svg>` elements instead.
use std::borrow::Cow;
use base64::Engine;
use base64::engine::general_purpose::STANDARD;
const XLINK_NS: &str = "http://www.w3.org/1999/xlink";
/// Logos are a few kilobytes, anything bigger is left for Skia to skip
const MAX_INLINED_SVG: usize = 256 * 1024;
const MAX_INLINED_IMAGES: usize = 16;
/// The `<image>` attributes that position the picture, carried onto the inlined `<svg>`
const PLACEMENT: [&str; 5] = ["x", "y", "width", "height", "preserveAspectRatio"];
pub(super) fn prepare_svg(bytes: &[u8]) -> Cow<'_, [u8]> {
let Ok(text) = std::str::from_utf8(bytes) else {
return Cow::Borrowed(bytes);
};
match rewrite_images(text) {
Some(rewritten) => Cow::Owned(rewritten.into_bytes()),
None => Cow::Borrowed(bytes),
}
}
fn rewrite_images(text: &str) -> Option<String> {
let mut out = String::with_capacity(text.len());
let mut rest = text;
let mut changed = false;
let mut uses_xlink = false;
let mut inlined = 0;
while let Some(at) = find_tag(rest, "image") {
out.push_str(&rest[..at]);
let from_tag = &rest[at..];
let Some(tag) = Tag::parse(from_tag, "image") else {
out.push_str(from_tag);
rest = "";
break;
};
rest = &from_tag[tag.len..];
if inlined < MAX_INLINED_IMAGES
&& let Some(nested) = inline_svg_image(&tag, &mut rest, inlined)
{
out.push_str(&nested);
inlined += 1;
changed = true;
continue;
}
if tag.attr("href").is_some() && tag.attr("xlink:href").is_none() {
out.push_str(&tag.renamed("href", "xlink:href").to_source());
uses_xlink = true;
changed = true;
} else {
out.push_str(&from_tag[..tag.len]);
}
}
out.push_str(rest);
if !changed {
return None;
}
if uses_xlink {
declare_xlink(&mut out);
}
Some(out)
}
/// Replaces an `<image>` holding an svg data url with that svg, placed where the image was
fn inline_svg_image(tag: &Tag<'_>, rest: &mut &str, index: usize) -> Option<String> {
let href = tag.attr("href").or_else(|| tag.attr("xlink:href"))?;
// the logo's ids now share the badge's document, where shields.io already uses `r`, `s` and `blur`
let document = prefix_ids(&decode_svg_data_url(href)?, &format!("logo{index}-"));
let start = find_tag(&document, "svg")?;
let inner = Tag::parse(&document[start..], "svg")?;
let body = &document[start + inner.len..];
// `<image ...></image>` has a closing tag the nested svg must swallow
if !tag.self_closing {
let trimmed = rest.trim_start();
*rest = trimmed.strip_prefix("</image>")?;
}
let mut nested = inner.without(&PLACEMENT);
for name in PLACEMENT {
if let Some(value) = tag.attr(name) {
nested.attrs.push((name, value, '"'));
}
}
let mut out = nested.to_source();
out.push_str(body);
Some(match tag.attr("transform") {
Some(transform) => format!("<g transform=\"{transform}\">{out}</g>"),
None => out,
})
}
fn decode_svg_data_url(href: &str) -> Option<String> {
let payload = href.trim().strip_prefix("data:image/svg+xml")?;
let (params, data) = payload.split_once(',')?;
if !params.split(';').any(|param| param == "base64") {
return None;
}
let data: String = data.chars().filter(|c| !c.is_ascii_whitespace()).collect();
// base64 grows by a third, so the encoded length bounds the decoded one
if data.len() / 4 * 3 > MAX_INLINED_SVG {
return None;
}
let decoded = String::from_utf8(STANDARD.decode(data).ok()?).ok()?;
// one level of nesting, an inlined logo can't pull in further svgs
(!decoded.contains("data:image/svg+xml")).then_some(decoded)
}
/// Prefixes every `id` attribute and the `url(#id)` / `href="#id"` references to it
fn prefix_ids(svg: &str, prefix: &str) -> String {
let mut ids = Vec::new();
let mut out = String::with_capacity(svg.len());
let mut rest = svg;
while let Some(at) = rest.find("id=") {
let attr_start = at > 0 && rest[..at].ends_with(|c: char| c.is_ascii_whitespace());
let (before, after) = rest.split_at(at + "id=".len());
out.push_str(before);
rest = after;
let Some(quote) = rest.chars().next().filter(|c| *c == '"' || *c == '\'') else {
continue;
};
let Some(len) = rest[1..].find(quote) else {
continue;
};
if !attr_start || len == 0 {
continue;
}
let id = &rest[1..1 + len];
out.push(quote);
out.push_str(prefix);
out.push_str(id);
out.push(quote);
ids.push(id);
rest = &rest[len + 2..];
}
out.push_str(rest);
for id in ids {
for (from, to) in [
("url(#", ")"),
("url('#", "')"),
("url(\"#", "\")"),
("href=\"#", "\""),
("href='#", "'"),
] {
out = out.replace(
&format!("{from}{id}{to}"),
&format!("{from}{prefix}{id}{to}"),
);
}
}
out
}
fn declare_xlink(svg: &mut String) {
let Some(root) = find_tag(svg, "svg") else {
return;
};
let Some(tag) = Tag::parse(&svg[root..], "svg") else {
return;
};
if tag.attr("xmlns:xlink").is_some() {
return;
}
svg.insert_str(root + "<svg".len(), &format!(" xmlns:xlink=\"{XLINK_NS}\""));
}
/// Byte offset of the next `<name` start tag, skipping longer names sharing the prefix
fn find_tag(text: &str, name: &str) -> Option<usize> {
let open = format!("<{name}");
let mut from = 0;
while let Some(found) = text[from..].find(&open) {
let at = from + found;
let next = text[at + open.len()..].chars().next();
if matches!(next, Some(c) if c.is_ascii_whitespace() || c == '/' || c == '>') {
return Some(at);
}
from = at + open.len();
}
None
}
/// A start tag, values kept as written so re-emitting them needs no escaping
#[derive(Debug, PartialEq)]
struct Tag<'a> {
name: &'a str,
attrs: Vec<(&'a str, &'a str, char)>,
self_closing: bool,
/// Bytes of the source the tag spans, `>` included
len: usize,
}
impl<'a> Tag<'a> {
fn parse(source: &'a str, name: &'a str) -> Option<Self> {
let mut at = source.strip_prefix('<')?.strip_prefix(name)?.len();
at = source.len() - at;
let mut attrs = Vec::new();
loop {
at += whitespace(&source[at..]);
let rest = &source[at..];
if let Some(after) = rest.strip_prefix("/>") {
let len = source.len() - after.len();
return Some(Self {
name,
attrs,
self_closing: true,
len,
});
}
if rest.starts_with('>') {
return Some(Self {
name,
attrs,
self_closing: false,
len: at + 1,
});
}
let name_len =
rest.find(|c: char| c == '=' || c.is_ascii_whitespace() || c == '>' || c == '/')?;
if name_len == 0 {
return None;
}
let attr = &rest[..name_len];
at += name_len;
at += whitespace(&source[at..]);
source[at..].strip_prefix('=')?;
at += 1;
at += whitespace(&source[at..]);
let quote = source[at..]
.chars()
.next()
.filter(|c| *c == '"' || *c == '\'')?;
at += 1;
let value_len = source[at..].find(quote)?;
attrs.push((attr, &source[at..at + value_len], quote));
at += value_len + 1;
}
}
fn attr(&self, name: &str) -> Option<&'a str> {
self.attrs
.iter()
.find(|(attr, _, _)| *attr == name)
.map(|(_, value, _)| *value)
}
fn renamed(mut self, from: &str, to: &'a str) -> Self {
for attr in &mut self.attrs {
if attr.0 == from {
attr.0 = to;
}
}
self
}
fn without(&self, names: &[&str]) -> Self {
Self {
name: self.name,
attrs: self
.attrs
.iter()
.filter(|(attr, _, _)| !names.contains(attr))
.copied()
.collect(),
self_closing: self.self_closing,
len: self.len,
}
}
fn to_source(&self) -> String {
let mut out = format!("<{}", self.name);
for (attr, value, quote) in &self.attrs {
out.push_str(&format!(" {attr}={quote}{value}{quote}"));
}
out.push_str(if self.self_closing { "/>" } else { ">" });
out
}
}
fn whitespace(text: &str) -> usize {
text.len()
- text
.trim_start_matches(|c: char| c.is_ascii_whitespace())
.len()
}
#[cfg(test)]
mod tests {
use super::*;
fn prepared(svg: &str) -> String {
String::from_utf8(prepare_svg(svg.as_bytes()).into_owned()).expect("utf8")
}
fn svg_data_url(svg: &str) -> String {
format!("data:image/svg+xml;base64,{}", STANDARD.encode(svg))
}
#[test]
fn leaves_an_svg_without_images_untouched() {
let svg = r#"<svg xmlns="http://www.w3.org/2000/svg"><rect width="1" height="1"/></svg>"#;
assert!(matches!(prepare_svg(svg.as_bytes()), Cow::Borrowed(_)));
}
#[test]
fn renames_href_and_declares_xlink_once() {
let svg = r#"<svg xmlns="http://www.w3.org/2000/svg"><image x="5" href="data:image/png;base64,AAAA"/><image href="b.png"/></svg>"#;
assert_eq!(
prepared(svg),
r#"<svg xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg"><image x="5" xlink:href="data:image/png;base64,AAAA"/><image xlink:href="b.png"/></svg>"#
);
}
#[test]
fn keeps_an_existing_xlink_declaration() {
let svg = r#"<svg xmlns:xlink="http://www.w3.org/1999/xlink"><image href="a.png"/></svg>"#;
assert_eq!(
prepared(svg),
r#"<svg xmlns:xlink="http://www.w3.org/1999/xlink"><image xlink:href="a.png"/></svg>"#
);
}
#[test]
fn inlines_an_svg_logo_where_the_image_was() {
let logo = r##"<?xml version="1.0"?><svg fill="#5865F2" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"><path d="M0 0h24v24H0z"/></svg>"##;
let svg = format!(
r#"<svg xmlns="http://www.w3.org/2000/svg"><image x="5" y="3" width="14" height="14" href="{}"/><text>Discord</text></svg>"#,
svg_data_url(logo)
);
assert_eq!(
prepared(&svg),
r##"<svg xmlns="http://www.w3.org/2000/svg"><svg fill="#5865F2" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" x="5" y="3" width="14" height="14"><path d="M0 0h24v24H0z"/></svg><text>Discord</text></svg>"##
);
}
#[test]
fn swallows_the_closing_tag_of_an_open_image() {
let logo = r#"<svg viewBox="0 0 1 1"/>"#;
let svg = format!(
r#"<svg><image width="2" href="{}"> </image></svg>"#,
svg_data_url(logo)
);
assert_eq!(
prepared(&svg),
r#"<svg><svg viewBox="0 0 1 1" width="2"/></svg>"#
);
}
#[test]
fn malformed_base64_falls_back_to_the_xlink_rename() {
let svg = r#"<svg><image href="data:image/svg+xml;base64,@@@"/></svg>"#;
assert_eq!(
prepared(svg),
r#"<svg xmlns:xlink="http://www.w3.org/1999/xlink"><image xlink:href="data:image/svg+xml;base64,@@@"/></svg>"#
);
}
#[test]
fn does_not_inline_a_logo_that_nests_another_svg() {
let inner = svg_data_url(r#"<svg viewBox="0 0 1 1"/>"#);
let logo = format!(r#"<svg viewBox="0 0 1 1"><image href="{inner}"/></svg>"#);
let svg = format!(r#"<svg><image href="{}"/></svg>"#, svg_data_url(&logo));
assert!(prepared(&svg).contains("<image xlink:href=\"data:image/svg+xml;base64,"));
}
#[test]
fn prefixes_the_ids_of_an_inlined_logo() {
let logo = r##"<svg viewBox="0 0 1 1"><clipPath id="r"><rect width="1" height="1"/></clipPath><linearGradient id='s'/><rect grid="r" clip-path="url(#r)" fill="url('#s')"/><use href="#r"/><use xlink:href="#s"/><rect fill="url(#rr)"/></svg>"##;
let svg = format!(r#"<svg><image href="{}"/></svg>"#, svg_data_url(logo));
assert_eq!(
prepared(&svg),
r##"<svg><svg viewBox="0 0 1 1"><clipPath id="logo0-r"><rect width="1" height="1"/></clipPath><linearGradient id='logo0-s'/><rect grid="r" clip-path="url(#logo0-r)" fill="url('#logo0-s')"/><use href="#logo0-r"/><use xlink:href="#logo0-s"/><rect fill="url(#rr)"/></svg></svg>"##
);
}
#[test]
fn each_inlined_logo_gets_its_own_prefix() {
let logo = svg_data_url(r#"<svg><g id="a"/></svg>"#);
let svg = format!(r#"<svg><image href="{logo}"/><image href="{logo}"/></svg>"#);
assert_eq!(
prepared(&svg),
r#"<svg><svg><g id="logo0-a"/></svg><svg><g id="logo1-a"/></svg></svg>"#
);
}
#[test]
fn ignores_tags_that_only_share_the_prefix() {
let svg = r#"<svg><imageset href="a"/></svg>"#;
assert!(matches!(prepare_svg(svg.as_bytes()), Cow::Borrowed(_)));
}
#[test]
fn unterminated_tag_is_left_alone() {
let svg = r#"<svg><image href="a.png"#;
assert!(matches!(prepare_svg(svg.as_bytes()), Cow::Borrowed(_)));
}
}

View file

@ -1,9 +1,9 @@
use crate::state::LoginProgress;
use freya::prelude::*; use freya::prelude::*;
use freya::query::{MutationCapability, MutationStateData, UseMutation}; use freya::query::{MutationCapability, MutationStateData, UseMutation};
use freya::text_edit::Clipboard; use freya::text_edit::Clipboard;
use oneclient_core::LauncherError;
use oneclient_auth::{AuthErrorGuidance, MicrosoftLoginSession}; use oneclient_auth::{AuthErrorGuidance, MicrosoftLoginSession};
use crate::state::LoginProgress; use oneclient_core::LauncherError;
use crate::components::{Button, Icon, IconType, OverlayPopup}; use crate::components::{Button, Icon, IconType, OverlayPopup};
use crate::hooks::{ use crate::hooks::{
@ -225,10 +225,7 @@ pub(crate) fn login_dialog(
.spacing(18.) .spacing(18.)
.child(browser_dialog_body(auth_url)) .child(browser_dialog_body(auth_url))
.child(dialog_divider()) .child(dialog_divider())
.child(device_code_dialog_body( .child(device_code_dialog_body(user_code, verification_uri))
user_code,
verification_uri,
))
.child(status_row(status, error)) .child(status_row(status, error))
.maybe_child(guidance.map(guidance_block)), .maybe_child(guidance.map(guidance_block)),
), ),
@ -314,17 +311,13 @@ fn status_row(status: Option<LoginProgress>, error: Option<String>) -> impl Into
} }
fn guidance_block(guidance: AuthErrorGuidance) -> impl IntoElement { fn guidance_block(guidance: AuthErrorGuidance) -> impl IntoElement {
let mut steps = rect() let mut steps = rect().vertical().width(Size::fill()).spacing(6.).child(
.vertical() label()
.width(Size::fill()) .text("What you can do:")
.spacing(6.) .font_size(12.)
.child( .font_weight(FontWeight::SEMI_BOLD)
label() .color(colors::fg_primary()),
.text("What you can do:") );
.font_size(12.)
.font_weight(FontWeight::SEMI_BOLD)
.color(colors::fg_primary()),
);
for (index, step) in guidance.steps_to_fix.into_iter().enumerate() { for (index, step) in guidance.steps_to_fix.into_iter().enumerate() {
steps = steps.child( steps = steps.child(

View file

@ -1,40 +1,52 @@
mod account_switcher; mod account_switcher;
mod active_cluster_panel; mod active_cluster_panel;
mod asset_image;
mod avatar; mod avatar;
mod blocked_downloads_popup;
mod bundle_choices_popup;
mod button; mod button;
mod charts; mod charts;
mod checkbox; mod checkbox;
mod cluster_landscape_art;
mod cluster_update_popup; mod cluster_update_popup;
mod code_block;
mod context_menu; mod context_menu;
mod control_center;
mod date_range_picker; mod date_range_picker;
mod optional_mods_popup;
mod dropdown; mod dropdown;
mod dynamic_art; mod dynamic_art;
mod essential_confirm;
mod file_drop; mod file_drop;
mod icons; mod filter_menu;
mod java_install_manager; mod flagged_install_popup;
mod generic_prompt; mod generic_prompt;
mod icons;
mod instance_row;
mod java_install_manager;
mod java_prompt; mod java_prompt;
mod microsoft_java_prompt;
mod link_confirm; mod link_confirm;
mod local_image; mod local_image;
mod log_viewer; mod log_viewer;
mod markdown; mod markdown;
mod memory_field; mod memory_field;
mod microsoft_java_prompt;
mod microsoft_login; mod microsoft_login;
mod modpack_confirm_popup;
mod navbar; mod navbar;
mod notifications; mod notifications;
mod optional_mods_popup;
mod overlay_popup; mod overlay_popup;
mod package_delete_dialog;
mod package_row; mod package_row;
mod package_update_popup; mod package_update_popup;
mod pagination; mod pagination;
mod player_model; mod player_model;
mod progress_track; mod progress_track;
mod recents_row; mod recents_row;
mod release_migration_popup;
mod screenshot_viewer; mod screenshot_viewer;
mod scrollview; mod scrollview;
mod segmented_control; mod segmented_control;
mod spinner;
mod splash_curtain; mod splash_curtain;
mod status_bar; mod status_bar;
mod tab_bar; mod tab_bar;
@ -43,56 +55,76 @@ mod toasts;
mod toggle; mod toggle;
mod tooltip; mod tooltip;
mod update_prompt; mod update_prompt;
mod version_card;
pub mod upload_mclogs; pub mod upload_mclogs;
mod version_art_gallery;
mod version_card;
pub use account_switcher::AccountSwitcher; pub use account_switcher::AccountSwitcher;
pub use active_cluster_panel::ActiveClusterPanel; pub use active_cluster_panel::ActiveClusterPanel;
pub use asset_image::AssetImage;
pub use avatar::Avatar; pub use avatar::Avatar;
pub use button::{Button, ButtonSize, ButtonVariant, link_button, open_folder_button}; pub use blocked_downloads_popup::BlockedDownloadsPopup;
pub use bundle_choices_popup::BundleChoicesPopup;
pub use button::{Button, ButtonSize, ButtonVariant, copy_button, link_button, open_folder_button};
pub use charts::{BarChart, PieChart, ValueUnit, slice_color}; pub use charts::{BarChart, PieChart, ValueUnit, slice_color};
pub use checkbox::checkbox_labeled; pub use checkbox::checkbox_labeled;
pub use cluster_landscape_art::ClusterLandscapeArt;
pub use cluster_update_popup::ClusterUpdatePopup; pub use cluster_update_popup::ClusterUpdatePopup;
pub use code_block::CodeBlock;
pub use context_menu::ContextMenu; pub use context_menu::ContextMenu;
pub use control_center::ControlCenter;
pub use date_range_picker::{DateRange, DateRangePicker}; pub use date_range_picker::{DateRange, DateRangePicker};
pub use optional_mods_popup::OptionalModsPopup;
pub use dropdown::Dropdown; pub use dropdown::Dropdown;
pub use dynamic_art::{ART_PREVIEW_EDGE, DynamicArt}; pub use dynamic_art::{ART_PREVIEW_EDGE, DynamicArt};
pub use file_drop::FileDropOverlay; pub use essential_confirm::EssentialConfirmOverlay;
pub use icons::{Icon, IconTint, IconType}; pub use file_drop::{FileDropOverlay, accept_drop};
pub use java_install_manager::JavaInstallManager; pub use filter_menu::{FilterMenu, FilterOption};
pub use flagged_install_popup::FlaggedInstallPopup;
pub use generic_prompt::GenericPromptOverlay; pub use generic_prompt::GenericPromptOverlay;
pub use icons::{Icon, IconTint, IconType};
pub use instance_row::InstanceRow;
pub use java_install_manager::JavaInstallManager;
pub use java_prompt::JavaPromptOverlay; pub use java_prompt::JavaPromptOverlay;
pub use microsoft_java_prompt::MicrosoftJavaPromptOverlay;
pub use link_confirm::ConfirmLinkOverlay; pub use link_confirm::ConfirmLinkOverlay;
pub use local_image::LocalImage; pub use local_image::LocalImage;
pub use markdown::{Markdown, MarkdownStyle};
pub use log_viewer::LogViewer; pub use log_viewer::LogViewer;
pub use markdown::{Markdown, MarkdownStyle};
pub use memory_field::memory_field; pub use memory_field::memory_field;
pub use microsoft_login::use_microsoft_login; pub use microsoft_java_prompt::MicrosoftJavaPromptOverlay;
pub(crate) use microsoft_login::login_dialog; pub(crate) use microsoft_login::login_dialog;
pub use microsoft_login::{MicrosoftLogin, use_microsoft_login};
pub use modpack_confirm_popup::ModpackConfirmPopup;
pub(crate) use navbar::window_controls; pub(crate) use navbar::window_controls;
pub use navbar::{AppNavbar, OnboardingNavbar}; pub use navbar::{AppNavbar, OnboardingNavbar};
pub use notifications::NotificationCenter; pub use notifications::{ChevronToggle, NotificationCenter};
pub use optional_mods_popup::OptionalModsPopup;
pub use overlay_popup::{OVERLAY_BASE_LEVEL, OVERLAY_MAX_LEVEL, OverlayPopup, overlay_is_topmost}; pub use overlay_popup::{OVERLAY_BASE_LEVEL, OVERLAY_MAX_LEVEL, OverlayPopup, overlay_is_topmost};
pub use package_row::{CardLayout, PackageEntry, PackageRow, provider_badge}; pub use package_delete_dialog::use_shared_delete;
pub(crate) use package_row::{CARD_GRID_H, GRID_GAP, GRID_MIN_W, grid_card, package_icon}; pub(crate) use package_row::{
CARD_BG, CARD_GRID_H, CARD_H, CARD_NAME, GRID_GAP, GRID_MIN_W, badge, disable_warning_body,
grid_card, icon_box, kebab_button, meta_size, meta_text, on_secondary, package_icon, pill,
remote_icon, set_enabled_action, toggle_action,
};
pub use package_row::{
CardLayout, PackageEntry, PackageRow, github_badge, package_context_menu, provider_badge,
};
pub use package_update_popup::PackageUpdatePopup; pub use package_update_popup::PackageUpdatePopup;
pub use pagination::Pagination; pub use pagination::Pagination;
pub use player_model::PlayerModel; pub use player_model::PlayerModel;
pub use progress_track::progress_track; pub use progress_track::progress_track;
pub use recents_row::RecentsRow; pub use recents_row::RecentsRow;
pub use release_migration_popup::ReleaseMigrationPopup;
pub use screenshot_viewer::{ScreenshotViewer, screenshot_context_menu}; pub use screenshot_viewer::{ScreenshotViewer, screenshot_context_menu};
pub(crate) use scrollview::corrected_scroll; pub(crate) use scrollview::corrected_scroll;
pub use scrollview::{ScrollArea, ScrollAreaCtx}; pub use scrollview::{LazySection, ScrollArea, ScrollAreaCtx};
pub use segmented_control::{Segment, SegmentedControl}; pub use segmented_control::{Segment, SegmentedControl};
pub use spinner::centered_spinner;
pub use splash_curtain::SplashCurtain; pub use splash_curtain::SplashCurtain;
pub use status_bar::StatusBar; pub use status_bar::StatusBar;
pub use tab_bar::{TabBar, TabItem}; pub use tab_bar::{TabBar, TabItem};
pub use text_input::{TextInput, validate_memory, validate_number}; pub use text_input::{TextInput, validate_memory, validate_number};
pub use toasts::Toasts; pub use toasts::Toasts;
pub use toggle::{toggle, toggle_controlled}; pub use toggle::{toggle, toggle_controlled};
pub use tooltip::{TooltipHost, TooltipPlacement, use_provide_tooltips}; pub use tooltip::{TooltipHost, TooltipPlacement, use_provide_tooltips, use_tooltip_anchor};
pub use update_prompt::UpdatePromptOverlay; pub use update_prompt::UpdatePromptOverlay;
pub use version_art_gallery::{GALLERY_COVER_EDGE, VersionArtGallery};
pub use version_card::VersionCard; pub use version_card::VersionCard;

View file

@ -0,0 +1,259 @@
use freya::prelude::*;
use oneclient_content::modpacks::ModpackSummary;
use crate::components::{Button, Icon, IconType, OverlayPopup};
use crate::hooks::{use_dispatch, use_notifications_snapshot};
use crate::notifications::ModpackConfirm;
use crate::theme::colors;
use crate::ui::border_all_color;
use crate::utils::format_size;
const CARD_BG: Color = Color::from_rgb(26, 34, 41);
const DIALOG_W: f32 = 500.;
const DIALOG_PAD: f32 = 22.;
const FACT_LABEL_W: f32 = 110.;
#[derive(PartialEq)]
pub struct ModpackConfirmPopup;
impl Component for ModpackConfirmPopup {
fn render(&self) -> impl IntoElement {
let snapshot = use_notifications_snapshot();
let dispatch = use_dispatch();
let Some(confirm) = snapshot.modpack_confirm else {
return rect().into_element();
};
let close = dispatch.clone();
OverlayPopup::new()
.on_close(move |_| close.cancel_modpack())
.child(
rect()
.width(Size::window_percent(100.))
.height(Size::window_percent(100.))
.center()
.child(dialog(&confirm, dispatch)),
)
.into_element()
}
}
fn count_label(count: usize, one: &str, many: &str) -> String {
format!("{count} {}", if count == 1 { one } else { many })
}
fn content_line(summary: &ModpackSummary) -> String {
let parts: Vec<String> = [
(summary.mods, "mod", "mods"),
(summary.resource_packs, "resource pack", "resource packs"),
(summary.shaders, "shader", "shaders"),
(summary.other_files, "other file", "other files"),
]
.into_iter()
.filter(|(count, _, _)| *count > 0)
.map(|(count, one, many)| count_label(count, one, many))
.collect();
if parts.is_empty() {
"Configs and settings only".to_string()
} else {
parts.join(", ")
}
}
fn notes(summary: &ModpackSummary) -> Vec<(String, Color)> {
let mut notes = Vec::new();
if summary.optional > 0 {
notes.push((
format!(
"{} optional. They are installed turned off, so you can switch them on in the Mods tab.",
count_label(summary.optional, "file is", "files are")
),
colors::fg_secondary(),
));
}
if summary.manual > 0 {
notes.push((
format!(
"{} must be downloaded by hand from CurseForge. The launcher will show you which ones.",
count_label(summary.manual, "file", "files")
),
colors::code_warn(),
));
}
if summary.unresolved > 0 {
notes.push((
format!(
"{} could not be found on Modrinth or CurseForge and will be skipped.",
count_label(summary.unresolved, "file", "files")
),
colors::danger(),
));
}
notes
}
fn fact(name: &'static str, value: String) -> Element {
rect()
.horizontal()
.width(Size::fill())
.content(Content::Flex)
.spacing(10.)
.child(
label()
.text(name)
.width(Size::px(FACT_LABEL_W))
.font_size(12.5)
.color(colors::fg_secondary()),
)
.child(
label()
.text(value)
.width(Size::flex(1.))
.font_size(12.5)
.max_lines(2)
.color(colors::fg_primary()),
)
.into_element()
}
fn dialog(confirm: &ModpackConfirm, dispatch: crate::Actions) -> impl IntoElement {
let summary = &confirm.summary;
let version = if confirm.version.trim().is_empty() {
"Not stated by the pack".to_string()
} else {
confirm.version.clone()
};
let mut facts = vec![
fact("Instance", confirm.instance_name.clone()),
fact("Version", version),
fact(
"Minecraft",
format!("{} · {}", confirm.mc_version, confirm.loader),
),
fact("Content", content_line(summary)),
];
if summary.download_bytes > 0 {
facts.push(fact(
"Download",
format!(
"Up to {}, less for files the launcher already has",
format_size(summary.download_bytes)
),
));
}
let cancel = dispatch.clone();
let add = dispatch;
rect()
.vertical()
.width(Size::px(DIALOG_W))
.height(Size::Inner)
.max_width(Size::window_percent(95.))
.max_height(Size::window_percent(85.))
.overflow(Overflow::Clip)
.corner_radius(CornerRadius::new_all(16.))
.background(CARD_BG)
.border(border_all_color(1., colors::component_border()))
.shadow(Shadow::from((
0.,
18.,
52.,
0.,
Color::from_argb(150, 0, 0, 0),
)))
.child(
rect()
.vertical()
.width(Size::fill())
.height(Size::Inner)
.padding(Gaps::new_all(DIALOG_PAD))
.spacing(16.)
.child(
rect()
.vertical()
.width(Size::fill())
.spacing(3.)
.child(
label()
.text(format!("Add {}?", confirm.pack_name))
.font_size(17.)
.font_weight(FontWeight::SEMI_BOLD)
.max_lines(2)
.color(colors::fg_primary()),
)
.child(
label()
.text(format!(
"From {}. It is added as a new instance with its own folder.",
confirm.source
))
.font_size(12.5)
.max_lines(2)
.color(colors::fg_secondary()),
),
)
.child(
rect()
.vertical()
.width(Size::fill())
.spacing(8.)
.padding(Gaps::new_all(12.))
.corner_radius(CornerRadius::new_all(10.))
.background(colors::component_bg())
.border(border_all_color(1., colors::component_border()))
.children(facts),
)
.children(notes(summary).into_iter().map(|(text, color)| {
label()
.text(text)
.font_size(12.)
.max_lines(3)
.color(color)
.into_element()
}))
.child(
rect()
.horizontal()
.width(Size::fill())
.cross_align(Alignment::Center)
.main_align(Alignment::End)
.spacing(8.)
.child(
Button::new()
.ghost()
.on_press(move |_| cancel.cancel_modpack())
.text("Cancel"),
)
.child(
Button::new()
.primary()
.on_press(move |_| add.confirm_modpack())
.child(Icon::new(IconType::Plus).size(15.))
.text("Add"),
),
),
)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn empty_categories_are_left_out_of_the_content_line() {
let summary = ModpackSummary {
mods: 336,
shaders: 1,
..ModpackSummary::default()
};
assert_eq!(content_line(&summary), "336 mods, 1 shader");
assert_eq!(
content_line(&ModpackSummary::default()),
"Configs and settings only"
);
}
}

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