Compare commits

..
Author SHA1 Message Date
Jai A 8a9fc2be30 fix jsign not running 2025-02-01 19:56:12 -08:00
Jai A 3442baa356 don't use array 2025-01-31 21:36:23 -08:00
Jai A d6e92e0434 use bash instead 2025-01-31 20:25:36 -08:00
Jai A c908065585 Remove product name 2025-01-31 20:23:35 -08:00
Jai A f9125d4060 Fix jsign 2025-01-31 19:43:14 -08:00
Jai A 153a46e9c1 Add jsign support 2025-01-31 18:25:17 -08:00
Jai A eb9ec074de use old act file 2025-01-31 17:17:09 -08:00
Jai A ab215c0bda attempt signed build 2025-01-31 17:15:40 -08:00
Jai A 01664f8cba add back rust setup 2025-01-31 15:41:47 -08:00
Jai A 6a99ea5808 don't install rust 2025-01-31 15:27:21 -08:00
Jai A fa6d55cd35 hardcode so runner picks up job 2025-01-31 15:15:43 -08:00
Jai A a7b40a0aa7 try new runner label 2025-01-31 15:12:47 -08:00
Jai A 60b5605438 switch to self hosted runner for windows code signing 2025-01-31 15:10:43 -08:00
ProspectorandGitHub 9574e8e639 Add version deleting from the version list (#3204) 2025-01-30 23:51:52 +00:00
ProspectorandGitHub 14dacb2352 Moderation checklist message generation fixes with newlines (#3205)
* Add endline to end of file list in moderation checklist

* More line breaks and formatting in checklist
2025-01-30 23:51:39 +00:00
ProspectorandGitHub 8baa2a72fb Allow admins to edit collections on frontend (#3207) 2025-01-30 23:51:27 +00:00
Erb3andGitHub a5427f7287 fix(labrinth): cors headers on ratelimited (#3189)
Kinda hotfix.

Fixes #2591
Fixes #2529
2025-01-30 15:52:13 -08:00
ProspectorandGitHub 9180f5c8d0 Add seattle location to servers marketing, update CPU count (#3203) 2025-01-30 11:36:59 -08:00
felixandGitHub b5abce161f Update ChartDisplay.vue (#3200)
Signed-off-by: felix <60808107+ItsFelix5@users.noreply.github.com>
2025-01-29 20:08:29 +00:00
Prospector 8b3ede4218 Update number of projects stat in home page to 50,000 2025-01-28 19:11:03 -08:00
a4e024c690 Analyse logs without storing them on mclo.gs (#3196)
Co-authored-by: Prospector <6166773+Prospector@users.noreply.github.com>
2025-01-29 02:53:39 +00:00
Erb3andGitHub e368e35e74 fix(frontend): finish auth when redirect URI is supplied (#3191)
- Fixes #3188
- Refactors if-else statement into ternary
2025-01-29 02:51:41 +00:00
OwehttamyandGitHub 1e09305fb3 Fix grammatical error (#3180)
Signed-off-by: Owehttamy <47429979+Owehttamy@users.noreply.github.com>
2025-01-29 02:50:36 +00:00
Erb3andGitHub 4d32bb2330 feat: git attributes to enforce lf (#3193)
I noticed that the `.vscode` folder says LF endings should be used. However, I still regularily see CRLF. To fix this I've added a `.gitattributes` file which makes git convert endings to LF.
2025-01-29 02:50:05 +00:00
Magnus JensenandGitHub 46710c9501 fix: add correct styling class to button (#3170) 2025-01-29 02:40:19 +00:00
Erb3andGitHub 79d131c7eb feat(frontend): update search to reset to page 1 (#3192)
Fixes #3176

**Changes**:

- Sets the pagination to page one if the search is updated. This is the norm on most websites, and how users expect it to work.
- Join `setPage` into `updateSearchResults`
  - Take a page number in `updateSearchResults`
- Remove unused param to `updateSearchResults`
- Update `watch` to not double requests
- use `scrollToTop` utility function
2025-01-29 02:37:57 +00:00
b1955363a6 refactor(knossos): Rewrite date range system on analytics dashboard. (#1301)
* Start work on refactoring date range system.

* Use timeResolution terminology.

* "Last month" initial default.

* Migrate fully to dayjs - ease of use.

* Discard changes to pnpm-lock.yaml

* utilize getter

* Fix date label in ChartDisplay.vue

* Finish cleanup

* Update STAGING_API_URL in nuxt.config.ts

* Lint fixes

* Refactor ChartDisplay.vue to handle loading state in selectedRange and formattedCategorySubtitle

* Remove modal impl

---------

Signed-off-by: Calum H. <contact@mineblock11.dev>
Co-authored-by: Prospector <6166773+Prospector@users.noreply.github.com>
2025-01-29 02:34:28 +00:00
Jai A 495dbbb7f8 Add server ID to staff refund page 2025-01-17 17:34:37 -08:00
Jai A e0d0736f7e Fix wrong func call for refresh 2025-01-17 17:27:46 -08:00
Jai A 12bfebd8b5 run intl:extract 2025-01-17 17:25:08 -08:00
Jai A af791f78b7 Fix refund frontend 2025-01-17 17:20:12 -08:00
Jai A 0f4af98a21 Fix integer overflow on charges 2025-01-17 17:01:35 -08:00
Jai AgrawalandGitHub 75b357a069 Staff support dashboard routes (#3160)
* Staff support dashboard routes

* Fix clippy
2025-01-17 16:41:49 -08:00
Jai A d7814e115d fix migration typo 2025-01-17 09:05:42 -08:00
Jai A 24295ea482 fix version uploading 2025-01-17 08:53:54 -08:00
Jai A 701bf853d5 Fix broken migration on labrinth (againx2) 2025-01-16 23:59:07 -08:00
Jai A 7fd3d737b8 Fix broken migration on labrinth (again) 2025-01-16 23:32:26 -08:00
Jai A 497b0bca0b Fix broken migration on labrinth 2025-01-16 23:00:11 -08:00
Jai A 208015a911 Bump rust version 2025-01-16 18:21:11 -08:00
Jai A 8abe2283d7 Fix clippy 2025-01-16 17:49:26 -08:00
Jai A 9e97c068d8 Fix version_fields, loader_fields_loaders missing primary keys 2025-01-16 17:41:41 -08:00
Jai A abbfb3ca2f Merge remote-tracking branch 'origin/main' 2025-01-16 16:43:38 -08:00
Jai A 5c8e7a8b38 Support new delphi response type 2025-01-16 16:40:13 -08:00
0d7934e3b8 Fix importing newer Prism instances (#3129)
* Fix importing newer Prism instances and clean up import code a bit

* cargo fmt

---------

Co-authored-by: Jai Agrawal <18202329+Geometrically@users.noreply.github.com>
2025-01-15 15:34:21 -08:00
ProspectorandGitHub e4cc8ef509 Fix typescript for OverflowMenu (#3139)
* Fix typescript for OverflowMenu

* Revert Discover content dropdown change to non-hoverable OverflowMenu

* Lint
2025-01-15 12:59:24 -08:00
Evan SongandGitHub d670a5cbb6 PY-53 Subscription Force Renewal Button (#3153)
* chore: show resubscribe on `failed` status

Signed-off-by: Evan Song <theevansong@gmail.com>

* chore: hide cancel on failed

Signed-off-by: Evan Song <theevansong@gmail.com>

* update copy

Signed-off-by: Evan Song <theevansong@gmail.com>

---------

Signed-off-by: Evan Song <theevansong@gmail.com>
2025-01-15 20:44:51 +00:00
227386bb0d Fix forever installing (#3135)
* Rough draft for fix for Mojang servers being down causing infinite installation

* Add "pack installed" install step

* Allow repairing an instance from Library to recover pack contents

* Allow repair from instance page

* Deduplicate repair code

* Fix lint

* Fix lint (for real this time)

---------

Co-authored-by: Jai Agrawal <18202329+Geometrically@users.noreply.github.com>
2025-01-11 15:27:47 -08:00
abd679d716 feat(frontend): project tags link to a search (#3126)
* feat(frontend): tags link to a search

* fix category type

* process feedback

---------

Co-authored-by: Prospector <6166773+Prospector@users.noreply.github.com>
2025-01-10 08:52:05 -08:00
ec5e3b0050 feat(frontend): Add filter query and download hash to the url during download modal use (#3138)
* feat: Automatically open download modal when filter queries are present

* chore: Use a hash to open the modal, and make the filter queries independent of the modal

* chore: Correct to use emit for taking functions

* chore: Add filter query and download hash to the url during modal use, and fix linting issues

* chore(frontend): Undo changes to NewModal

My computer does not like running the app, making testing a lot harder, so I'll undo this change, at least for now

* Remove extra line

---------

Signed-off-by: Prospector <6166773+Prospector@users.noreply.github.com>
Co-authored-by: Prospector <6166773+Prospector@users.noreply.github.com>
2025-01-10 08:51:36 -08:00
Erb3andGitHub 494616e9f2 chore: run lint (#3140)
* chore: run lint

* fix: whoops messed up lint
2025-01-10 08:42:31 -08:00
Erb3andGitHub 82f81dc154 fix(ui): slider number input field with limits (#2899)
- Added min, max, and step fields to the input field of a slider
- Made the input field a number field to apply min, max, step, and accessibility
2025-01-09 16:47:29 -08:00
Awakened RedstoneandGitHub 316fe72ea5 feat(frontend): Automatically open download modal when filter queries are present (on the main mod page) (#3133)
* feat: Automatically open download modal when filter queries are present

* chore: Use a hash to open the modal, and make the filter queries independent of the modal
2025-01-09 16:45:23 -08:00
Erb3andGitHub 6266f29b99 fix(frontend): lowercase giftcard sorting (#2986)
Resolves #1409
2025-01-09 15:16:17 -08:00
fd9653e283 fix: Properly handle empty version list on version/latest (#3132)
Co-authored-by: Jai Agrawal <18202329+Geometrically@users.noreply.github.com>
2025-01-09 15:14:12 -08:00
b2f4366415 fix: an extra "2" after type "void" (#3127)
Co-authored-by: Jai Agrawal <18202329+Geometrically@users.noreply.github.com>
Co-authored-by: Prospector <6166773+Prospector@users.noreply.github.com>
2025-01-09 15:13:30 -08:00
f859c34442 Fix some text color issues. Remove experimental colors reset for now. (#3136)
Co-authored-by: Jai Agrawal <18202329+Geometrically@users.noreply.github.com>
2025-01-09 15:13:05 -08:00
c52d5e9a74 feat(app): update profile every time token is refreshed (#2328)
Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
Co-authored-by: Prospector <6166773+Prospector@users.noreply.github.com>
2025-01-09 13:37:42 -08:00
021fee616d Allow skipping updater at runtime with an environmental variable (#2388)
Co-authored-by: Modrinth Bot <106493074+modrinth-bot@users.noreply.github.com>
2025-01-09 13:37:17 -08:00
Jai A 0409fcec2f skip projects with no issues 2025-01-08 22:19:04 -08:00
Jai A 8e754cfeb5 Add delphi integration, fix search showing plugins 2025-01-08 22:06:05 -08:00
Jai A adf3d9540d Remove clean.io for web traffic 2025-01-08 13:42:15 -08:00
Evan SongandGitHub d5f2ada8f7 Fix server suspension statuses (#3100)
* chore: correctly type suspension reason

Signed-off-by: Evan Song <theevansong@gmail.com>

* fix: faulty suspension condition allowing for fallthrough

Signed-off-by: Evan Song <theevansong@gmail.com>

* fix: here as well

Signed-off-by: Evan Song <theevansong@gmail.com>

* chore: add support suspension reason

Signed-off-by: Evan Song <theevansong@gmail.com>

* feat: handle support suspensions

Signed-off-by: Evan Song <theevansong@gmail.com>

* chore: patch pyroservers to handle 503

Signed-off-by: Evan Song <theevansong@gmail.com>

* feat: handle 503 in server root

Signed-off-by: Evan Song <theevansong@gmail.com>

* chore: dont make pyroservers errors scream at me anymore

Signed-off-by: Evan Song <theevansong@gmail.com>

---------

Signed-off-by: Evan Song <theevansong@gmail.com>
2025-01-06 22:06:34 -08:00
Jai A a01da5452c Update reports cap 2025-01-04 20:51:01 -07:00
Jai A 17d61277fa Bump meilisearch version 2024-12-30 20:55:37 -07:00
Jai AgrawalandGitHub bb3de4b74b Add balance route (#3107) 2024-12-30 20:17:51 -07:00
01fe08f079 Make the update checker work for non-mods (#3088)
* Fix https://github.com/modrinth/code/issues/1057

* Make sure mods use the installed loader

* Switch &PathBuf to &Path

* Clippy fix

* Deduplicate some code

---------

Co-authored-by: Jai Agrawal <18202329+Geometrically@users.noreply.github.com>
2024-12-28 20:23:27 -07:00
he3alsandGitHub 8b7547ae38 fix(server backup settings): number input -> dropdown (#3099)
* feat(backup settings): number input -> dropdown

* fix(servers teleport dropdown): round last element

* fix index
2024-12-29 02:19:24 +00:00
0437503b75 feat(servers content): file upload + extra mod info + misc (#3055)
* feat: only scroll up if scrolled down

* feat: no query results message

* feat: content files support, mobile fixes

* fix(drag & drop): type of file prop

* chore: show number of mods in searchbar

Signed-off-by: Evan Song <theevansong@gmail.com>

* chore: adjust btn styles

Signed-off-by: Evan Song <theevansong@gmail.com>

* feat: prepare for mod author in backend response

Signed-off-by: Evan Song <theevansong@gmail.com>

* fix: external mods & mobile

* chore: adjust edit mod version modal copy

Signed-off-by: Evan Song <theevansong@gmail.com>

* chore: add tooltips for version/filename

Signed-off-by: Evan Song <theevansong@gmail.com>

* chore: swap delete/change version btn

Signed-off-by: Evan Song <theevansong@gmail.com>

* fix: dont allow mod link to be dragged

Signed-off-by: Evan Song <theevansong@gmail.com>

* fix: oops

Signed-off-by: Evan Song <theevansong@gmail.com>

* chore: remove author field

Signed-off-by: Evan Song <theevansong@gmail.com>

* chore: drill down tooltip

Signed-off-by: Evan Song <theevansong@gmail.com>

* fix: fighting types

Signed-off-by: Evan Song <theevansong@gmail.com>

* prepare for owner field

Signed-off-by: Evan Song <theevansong@gmail.com>

---------

Signed-off-by: Evan Song <theevansong@gmail.com>
Co-authored-by: Evan Song <theevansong@gmail.com>
Co-authored-by: Evan Song <52982404+ferothefox@users.noreply.github.com>
2024-12-29 00:31:52 +00:00
Jai AgrawalandGitHub 2fea772ffb Fix sockets causing actix hangs (#3089)
* Fix sockets causing actix hangs

* Fix fmt issues

* Retry failed S3 uploads

* Ignore launcher socket from sentry
2024-12-27 22:44:09 -07:00
Jai A 24765db045 Fix locking timeout on invalid IDs 2024-12-27 00:58:28 -07:00
Jai A 82393f2ae7 Fix locking timeout issues 2024-12-27 00:20:37 -07:00
Jai A c86c98d000 fix search flashing in prod 2024-12-26 23:07:19 -07:00
Jai A 4d9741c424 fix search flashing, reorder filters on mods 2024-12-26 22:59:15 -07:00
Jai A 81ec068747 Mute audio via wry + v0.9.2 2024-12-25 14:41:28 -07:00
Jai A 4a031f7bbd v0.9.1 fixes 2024-12-24 22:30:10 -07:00
Jai A 5b00ac17e5 fix lint, labrinth crash 2024-12-23 17:13:13 -07:00
GeometricallyandGitHub 9952c3a0c1 0.9.0 release (app) (#3056)
* Debug app

* Bump version, final fixes
2024-12-23 13:38:52 -07:00
GeometricallyandGitHub cae6f12ea0 More app fixes 0.9.0 (#3054)
* initial set of fixes (toggle sidebar, profile pagination)

* more fixes, bump version

* fix lint:

* fix quick switcher ordering
2024-12-22 20:03:58 -07:00
Evan SongandGitHub ef08d8e538 Handle server upgrades (suspensions) in frontend (#3049)
* chore: make serverlisting handle upgrading server state

Signed-off-by: Evan Song <theevansong@gmail.com>

* feat: more prominent suspension status bar in server listing

Signed-off-by: Evan Song <theevansong@gmail.com>

* feat: handle upgrading in root server page

Signed-off-by: Evan Song <theevansong@gmail.com>

---------

Signed-off-by: Evan Song <theevansong@gmail.com>
2024-12-22 19:38:50 -07:00
Evan SongandGitHub 8457d1484b feat: hawk tuah (#3050) 2024-12-22 19:38:13 -07:00
Prospector 591fcfaf40 Remove empty header on search page 2024-12-22 02:20:28 -08:00
Prospector dfd4cbd7ca Fix projects page 2024-12-22 02:19:02 -08:00
Prospector b48601ed06 make quick instances square to match elsewhere and add tint 2024-12-21 20:00:03 -08:00
Jai A 39ab03786c fix moderation 2024-12-21 16:53:04 -07:00
246df0d107 chore(knossos): auto resource pack versions (1.20-1.21.4) (#3042)
* Add resource pack versions (1.20-1.21.4)

* prettier

---------

Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
2024-12-21 16:49:37 -07:00
a873e6623b fix #2949 (#3043)
Signed-off-by: bendy1234 <83143663+bendy1234@users.noreply.github.com>
Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
2024-12-21 16:49:15 -07:00
JoshuaandGitHub fa4bca5cb8 fix: Change platform button leads to wrong page (#3046) 2024-12-21 17:44:00 +00:00
Joshua SamenfinkandGitHub 7e81083c04 Update link to the new labrinth codebase (#3047)
* The repository is archived now
2024-12-21 10:43:40 -07:00
GeometricallyandGitHub c082148ecd Fix crash on windows (#3041) 2024-12-18 23:53:00 -07:00
76b1d1df8c Misc fixes, new instance & project cards (#3040)
* Fix some TS errors, and misc settings fixes

* New instance + project cards

* bug fixes + lint

* Quick instance switcher

---------

Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
Co-authored-by: Jai A <jaiagr+gpg@pm.me>
2024-12-18 16:09:16 -07:00
Jai A 02dd2a3980 fix build while using modrinth tauri fork 2024-12-18 11:45:43 -07:00
GeometricallyandGitHub c1b1e67a2c Fix app builds not launching (#3035)
* Fix app builds not launching

* Revert tauri version change
2024-12-18 11:30:49 -07:00
Jai A b7f514c6cc fix lint 2024-12-18 00:42:27 -07:00
547ec730c7 New instance settings in app (#3033)
* Tabbed interface component

* Start instance settings

* New instance settings, mostly done minus modpacks

* Extract i18n

* Some more fixes with settings, still no modpacks yet

* Lint

* Modpack installation settings

* Change no friends language

* Remove options legacy button

* fix lint, small bug

* fix invalid cond on friends ui

* update resource management page

---------

Signed-off-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
Co-authored-by: Jai A <jaiagr+gpg@pm.me>
Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
2024-12-18 00:06:01 -07:00
GeometricallyandGitHub 6ceed4b226 App fixes 0.9.0 (#3034)
* push fixes to test on windows

* Fix searching mods

* Fix search not saving, fix scrolling issues, etc
2024-12-17 23:23:30 -07:00
GeometricallyandGitHub 7e8ceadfd4 Update servers route (#3032) 2024-12-16 17:34:20 -07:00
Evan SongandGitHub fee8d6c34e Files UX Sprint (#3019)
* chore: dedupe lockfile

Signed-off-by: Evan Song <theevansong@gmail.com>

* fix: incorrect spacing between editing and browsing state

Signed-off-by: Evan Song <theevansong@gmail.com>

* chore: improve files image viewer toolbar

Signed-off-by: Evan Song <theevansong@gmail.com>

* chore: image viewer cursor affordance

Signed-off-by: Evan Song <theevansong@gmail.com>

* chore: clean imports

Signed-off-by: Evan Song <theevansong@gmail.com>

* chore: add tooltips

Signed-off-by: Evan Song <theevansong@gmail.com>

* chore: use black background

Signed-off-by: Evan Song <theevansong@gmail.com>

* feat: show scale factor, handle large images, consolidate state

Signed-off-by: Evan Song <theevansong@gmail.com>

* chore: add types to fs operations

Signed-off-by: Evan Song <theevansong@gmail.com>

* feat: add date create sorting option

Signed-off-by: Evan Song <theevansong@gmail.com>

* fix: match name of folder creation modal

Signed-off-by: Evan Song <theevansong@gmail.com>

* fix: add it here too

Signed-off-by: Evan Song <theevansong@gmail.com>

* feat: add creation date to file item, file manager header

Signed-off-by: Evan Song <theevansong@gmail.com>

* chore: a11y

Signed-off-by: Evan Song <theevansong@gmail.com>

* fix: ensure move item modal always has leading slash

Signed-off-by: Evan Song <theevansong@gmail.com>

* fix: correct move input placeholder

Signed-off-by: Evan Song <theevansong@gmail.com>

* chore: correct design disparity

Signed-off-by: Evan Song <theevansong@gmail.com>

* chore: add better affordance on active file item state

Signed-off-by: Evan Song <theevansong@gmail.com>

* fix: correct instances where we dont sentence case

Signed-off-by: Evan Song <theevansong@gmail.com>

* chore: clean

Signed-off-by: Evan Song <theevansong@gmail.com>

* chore: notify that server restarted on saveandrestart

Signed-off-by: Evan Song <theevansong@gmail.com>

* fix: consolidate error state in file manager

Signed-off-by: Evan Song <theevansong@gmail.com>

* chore: adjust sizing

Signed-off-by: Evan Song <theevansong@gmail.com>

* feat: drag and drop file items to move them

Signed-off-by: Evan Song <theevansong@gmail.com>

* feat: enable ability to drag folders too

Signed-off-by: Evan Song <theevansong@gmail.com>

* feat: better file movement toasts

Signed-off-by: Evan Song <theevansong@gmail.com>

* just say u hate me

Signed-off-by: Evan Song <theevansong@gmail.com>

* feat: uploading indicator for file uploads

Signed-off-by: Evan Song <theevansong@gmail.com>

* chore: cleaner file item ghost when dragging

Signed-off-by: Evan Song <theevansong@gmail.com>

* fix: enforce max length and truncate on ghost

Signed-off-by: Evan Song <theevansong@gmail.com>

* chore: improve item rename toast

Signed-off-by: Evan Song <theevansong@gmail.com>

* chore: improve item create toast

Signed-off-by: Evan Song <theevansong@gmail.com>

* feat: undo and redo stack

Signed-off-by: Evan Song <theevansong@gmail.com>

* fix: confusing behavior where folders were not sorted alphabetically

Signed-off-by: Evan Song <theevansong@gmail.com>

* feat: find and replace in file editor

Signed-off-by: Evan Song <theevansong@gmail.com>

* feat: correctly set language mode of file editor

Signed-off-by: Evan Song <theevansong@gmail.com>

* chore: slop

Signed-off-by: Evan Song <theevansong@gmail.com>

* chore: actually handle case with multiple dots in file name before setting language

Signed-off-by: Evan Song <theevansong@gmail.com>

* fix: match move icons in file context/threedot

Signed-off-by: Evan Song <theevansong@gmail.com>

* feat: upload indicator

Signed-off-by: Evan Song <theevansong@gmail.com>

* chore: dedupe lockfile again

Signed-off-by: Evan Song <theevansong@gmail.com>

* lockfile

Signed-off-by: Evan Song <theevansong@gmail.com>

* fix: file undefinedness

Signed-off-by: Evan Song <theevansong@gmail.com>

* checkpoint

Signed-off-by: Evan Song <theevansong@gmail.com>

* checkpoint

Signed-off-by: Evan Song <theevansong@gmail.com>

* checkpoint

Signed-off-by: Evan Song <theevansong@gmail.com>

* remove shitty animation logic

Signed-off-by: Evan Song <theevansong@gmail.com>

* feat: file upload queuer

Signed-off-by: Evan Song <theevansong@gmail.com>

* chore: only allow editable files to have active affordance

Signed-off-by: Evan Song <theevansong@gmail.com>

* fix: properly throw pyrofetcherror when rename fails

Signed-off-by: Evan Song <theevansong@gmail.com>

* feat: cancel file uploads

Signed-off-by: Evan Song <theevansong@gmail.com>

* chore: clean

Signed-off-by: Evan Song <theevansong@gmail.com>

---------

Signed-off-by: Evan Song <theevansong@gmail.com>
2024-12-16 23:13:31 +00:00
Erb3andGitHub 9aa70359a8 fix(frontend): ignore case in collection search for followed (#3005)
* fix(frontend): ignore case in collection search for followed

When searching for collections, the casing will normally be ignored. The item for "Followed Projects" will be removed if using capitalized characters though. This is especially confusing since it's titled with partially capital letters.

Resolves modrinth/code#1370

* chore: revert import sort
2024-12-15 06:15:59 +00:00
ZefirandGitHub 58cbc1ada5 Use en dash for range of versions (#2997) 2024-12-15 05:24:57 +00:00
Jai A 5f7d550a5a fix failed fetches caching in app state 2024-12-14 22:16:15 -07:00
Jai A b5d788ca6c move profile listener 2024-12-14 22:02:38 -07:00
GeometricallyandGitHub 217b5700a2 Fix advanced rendering, ads showing over modals (#3029) 2024-12-14 21:36:00 -07:00
ca7cfb30c7 Project installation indicator (#3023)
* Add indicator for project installation

* fix lint

---------

Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
Co-authored-by: Jai A <jaiagr+gpg@pm.me>
2024-12-14 21:35:50 -07:00
283a915a12 Fix popouts not working (#3028)
* Attempt at fixing popouts? no idea if will work

* Try with no theme?

* Revert "Try with no theme?"

This reverts commit 7fb3a6c6a6.

* Try to remove triggers from theme

* Remove duplicate plugin

* Fix version issue

* Try to remove theme and use default theme

* Add back theme for later fixing

* Lint

---------

Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
2024-12-15 04:15:50 +00:00
24271a2388 Fix modal animation when modal is in another modal, and add close label (#3027)
Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
2024-12-15 02:52:49 +00:00
3e5b842b7a Fix content list error when projects is undefined (#3026)
Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
2024-12-15 02:48:21 +00:00
465f7ac0c5 Fix multiselect styling (#3025)
Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
2024-12-15 02:47:42 +00:00
284f156e9f Fix vite table warning (#3024)
Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
2024-12-15 02:46:02 +00:00
ac25a36bce OLED fixes (#3022)
* Make sidebar darker in OLED theme

* Fix experimental styles in OLED mode

---------

Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
2024-12-15 02:45:31 +00:00
GeometricallyandGitHub f2e653b732 Bump tauri to v2 (#3018)
* Bump tauri to v2

* Switch to virtual typed list for content page

* Fix unexpected hang on windows

* Fix ads window scaling issues on some devices

* Use DPR from browser

* Fix link opens, bump version, fix lint
2024-12-14 19:23:47 -07:00
7f445e9b8c Fix lack of collections tab (#2936)
Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
2024-12-14 21:54:30 +00:00
Evan SongandGitHub 57e27fb0fa chore: regenerate lockfile (#3016) 2024-12-12 23:59:48 +00:00
Evan SongandGitHub e86c9df39d Platform page UX improvements (#3009)
* chore: initial fixes from app redesign merge

Signed-off-by: Evan Song <theevansong@gmail.com>

* fix: ccpa hydration error

Signed-off-by: Evan Song <theevansong@gmail.com>

* fix: migrate tailwind to esm

Signed-off-by: Evan Song <theevansong@gmail.com>

* feat: default platform selection to current mc version

Signed-off-by: Evan Song <theevansong@gmail.com>

* fix: navigating and installing content

Signed-off-by: Evan Song <theevansong@gmail.com>

* chore: respect sentence case

Signed-off-by: Evan Song <theevansong@gmail.com>

* fix: match new page padding

Signed-off-by: Evan Song <theevansong@gmail.com>

* feat: allow user to erase all data when installing from modpack

Signed-off-by: Evan Song <theevansong@gmail.com>

* chore: hide hide installed content check if modpack search

Signed-off-by: Evan Song <theevansong@gmail.com>

* chore: wording

Signed-off-by: Evan Song <theevansong@gmail.com>

* chore: make erase data toggle more prominent

Signed-off-by: Evan Song <theevansong@gmail.com>

---------

Signed-off-by: Evan Song <theevansong@gmail.com>
2024-12-12 23:12:54 +00:00
Jai A 1f060b8513 Fix lint, socket echo 2024-12-12 14:36:02 -08:00
GeometricallyandGitHub c970e9c015 Fix sockets issues (#3015)
* Fix sockets issues

* Fix app comp
2024-12-12 13:25:25 -08:00
Jai A 10ef25eabb fix intellij proj 2024-12-11 20:45:38 -08:00
Prospector 381cbe837b swap out fake blog post for a real one 2024-12-11 20:22:16 -08:00
c39bb78e38 App redesign (#2946)
* Start of app redesign

* format

* continue progress

* Content page nearly done

* Fix recursion issues with content page

* Fix update all alignment

* Discover page progress

* Settings progress

* Removed unlocked-size hack that breaks web

* Revamp project page, refactor web project page to share code with app, fixed loading bar, misc UI/UX enhancements, update ko-fi logo, update arrow icons, fix web issues caused by floating-vue migration, fix tooltip issues, update web tooltips, clean up web hydration issues

* Ads + run prettier

* Begin auth refactor, move common messages to ui lib, add i18n extraction to all apps, begin Library refactor

* fix ads not hiding when plus log in

* rev lockfile changes/conflicts

* Fix sign in page

* Add generated

* (mostly) Data driven search

* Fix search mobile issue

* profile fixes

* Project versions page, fix typescript on UI lib and misc fixes

* Remove unused gallery component

* Fix linkfunction err

* Search filter controls at top, localization for locked filters

* Fix provided filter names

* Fix navigating from instance browse to main browse

* Friends frontend (#2995)

* Friends system frontend

* (almost) finish frontend

* finish friends, fix lint

* Fix lint

---------

Signed-off-by: Geometrically <18202329+Geometrically@users.noreply.github.com>

* Refresh macOS app icon

* Update web search UI more

* Fix link opens

* Fix frontend build

---------

Signed-off-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
Co-authored-by: Jai A <jaiagr+gpg@pm.me>
Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
2024-12-11 19:54:18 -08:00
6ec1dcf088 Servers console performance improvements (#3007)
* feat: init selecting paper+purpur on purchase flow

Signed-off-by: Evan Song <theevansong@gmail.com>

* feat: properly implement Paper/Purpur in Platform

Signed-off-by: Evan Song <theevansong@gmail.com>

* chore: correct wording

Signed-off-by: Evan Song <theevansong@gmail.com>

* feat: redo platform modal

Signed-off-by: Evan Song <theevansong@gmail.com>

* Switch to HCaptcha for Auth-related captchas (#2945)

* Switch to HCaptcha for Auth-related captchas

* run fmt

* fix hcaptcha not loading

* fix: more robust loader dropdown logic

Signed-off-by: Evan Song <theevansong@gmail.com>

* fix: handle "not yet supported" install err

Signed-off-by: Evan Song <theevansong@gmail.com>

* chore: fix icon kerfuffles

Signed-off-by: Evan Song <theevansong@gmail.com>

* chore: improve vanilla install modal title

Signed-off-by: Evan Song <theevansong@gmail.com>

* fix: spacing

Signed-off-by: Evan Song <theevansong@gmail.com>

* feat: usePyroConsole store instead of passing a prop to prevent bulk panel refreshing

* chore: improve no loader state

Signed-off-by: Evan Song <theevansong@gmail.com>

* fix: type error

Signed-off-by: Evan Song <theevansong@gmail.com>

* chore: adjust mod version modal title

Signed-off-by: Evan Song <theevansong@gmail.com>

* chore: adjust modpack warning copy

Signed-off-by: Evan Song <theevansong@gmail.com>

* feat: vanilla empty state in content page

Signed-off-by: Evan Song <theevansong@gmail.com>

* chore: adjust copy

Signed-off-by: Evan Song <theevansong@gmail.com>

* chore: update icon

Signed-off-by: Evan Song <theevansong@gmail.com>

* fix: loader type

Signed-off-by: Evan Song <theevansong@gmail.com>

* fix: loader type

Signed-off-by: Evan Song <theevansong@gmail.com>

* feat: always show dropdown if possible

Signed-off-by: Evan Song <theevansong@gmail.com>

* chore: improve spacing

Signed-off-by: Evan Song <theevansong@gmail.com>

* chore: appear disabled

Signed-off-by: Evan Song <theevansong@gmail.com>

* h

Signed-off-by: Evan Song <theevansong@gmail.com>

* chore: if reinstalling, show it on the modal title

Signed-off-by: Evan Song <theevansong@gmail.com>

* feat: put it in the dropdown, they said

Signed-off-by: Evan Song <theevansong@gmail.com>

* chore: adjust style

Signed-off-by: Evan Song <theevansong@gmail.com>

* chore: sort paper-purpur versions desc

Signed-off-by: Evan Song <theevansong@gmail.com>

* fix: do not consider backup limit in reinstall prompt

Signed-off-by: Evan Song <theevansong@gmail.com>

* feat: backup locking, plugin support

* fix: content type error

Signed-off-by: Evan Song <theevansong@gmail.com>

* fix: casing

Signed-off-by: Evan Song <theevansong@gmail.com>

* fix: plugins pt 2

* feat: backups, mrpack

* fix: type errors come on

Signed-off-by: Evan Song <theevansong@gmail.com>

* fix: spacing

Signed-off-by: Evan Song <theevansong@gmail.com>

* fix: type maxing

* chore: show copy button on allocation rows

Signed-off-by: Evan Song <theevansong@gmail.com>

* feat: suspend improvement

---------

Signed-off-by: Evan Song <theevansong@gmail.com>
Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
Co-authored-by: Jai A <jaiagr+gpg@pm.me>
Co-authored-by: TheWander02 <48934424+thewander02@users.noreply.github.com>
2024-12-11 23:49:22 +00:00
742c0edd9e Paper and Purpur + Backups (#3004)
* feat: init selecting paper+purpur on purchase flow

Signed-off-by: Evan Song <theevansong@gmail.com>

* feat: properly implement Paper/Purpur in Platform

Signed-off-by: Evan Song <theevansong@gmail.com>

* chore: correct wording

Signed-off-by: Evan Song <theevansong@gmail.com>

* feat: redo platform modal

Signed-off-by: Evan Song <theevansong@gmail.com>

* Switch to HCaptcha for Auth-related captchas (#2945)

* Switch to HCaptcha for Auth-related captchas

* run fmt

* fix hcaptcha not loading

* fix: more robust loader dropdown logic

Signed-off-by: Evan Song <theevansong@gmail.com>

* fix: handle "not yet supported" install err

Signed-off-by: Evan Song <theevansong@gmail.com>

* chore: fix icon kerfuffles

Signed-off-by: Evan Song <theevansong@gmail.com>

* chore: improve vanilla install modal title

Signed-off-by: Evan Song <theevansong@gmail.com>

* fix: spacing

Signed-off-by: Evan Song <theevansong@gmail.com>

* chore: improve no loader state

Signed-off-by: Evan Song <theevansong@gmail.com>

* fix: type error

Signed-off-by: Evan Song <theevansong@gmail.com>

* chore: adjust mod version modal title

Signed-off-by: Evan Song <theevansong@gmail.com>

* chore: adjust modpack warning copy

Signed-off-by: Evan Song <theevansong@gmail.com>

* feat: vanilla empty state in content page

Signed-off-by: Evan Song <theevansong@gmail.com>

* chore: adjust copy

Signed-off-by: Evan Song <theevansong@gmail.com>

* chore: update icon

Signed-off-by: Evan Song <theevansong@gmail.com>

* fix: loader type

Signed-off-by: Evan Song <theevansong@gmail.com>

* fix: loader type

Signed-off-by: Evan Song <theevansong@gmail.com>

* feat: always show dropdown if possible

Signed-off-by: Evan Song <theevansong@gmail.com>

* chore: improve spacing

Signed-off-by: Evan Song <theevansong@gmail.com>

* chore: appear disabled

Signed-off-by: Evan Song <theevansong@gmail.com>

* h

Signed-off-by: Evan Song <theevansong@gmail.com>

* chore: if reinstalling, show it on the modal title

Signed-off-by: Evan Song <theevansong@gmail.com>

* feat: put it in the dropdown, they said

Signed-off-by: Evan Song <theevansong@gmail.com>

* chore: adjust style

Signed-off-by: Evan Song <theevansong@gmail.com>

* chore: sort paper-purpur versions desc

Signed-off-by: Evan Song <theevansong@gmail.com>

* fix: do not consider backup limit in reinstall prompt

Signed-off-by: Evan Song <theevansong@gmail.com>

* feat: backup locking, plugin support

* fix: content type error

Signed-off-by: Evan Song <theevansong@gmail.com>

* fix: casing

Signed-off-by: Evan Song <theevansong@gmail.com>

* fix: plugins pt 2

* feat: backups, mrpack

* fix: type errors come on

Signed-off-by: Evan Song <theevansong@gmail.com>

* fix: spacing

Signed-off-by: Evan Song <theevansong@gmail.com>

* fix: type maxing

* chore: show copy button on allocation rows

Signed-off-by: Evan Song <theevansong@gmail.com>

* feat: suspend improvement

---------

Signed-off-by: Evan Song <theevansong@gmail.com>
Co-authored-by: Evan Song <theevansong@gmail.com>
Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
Co-authored-by: Jai A <jaiagr+gpg@pm.me>
Co-authored-by: Evan Song <52982404+ferothefox@users.noreply.github.com>
2024-12-11 06:49:50 +00:00
eff3189ded feat(docs): General documentation improvements (OAuth documentation, docs meta) (#2855)
* feat: All good Docs things under the sun (favicon, oauth2, docs meta)

* Remove favicon changes (already fixed in other PR)

Signed-off-by: worldwidepixel <58098422+worldwidepixel@users.noreply.github.com>

---------

Signed-off-by: worldwidepixel <58098422+worldwidepixel@users.noreply.github.com>
Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
2024-12-10 20:26:46 +00:00
Erb3andGitHub 7d480ffbd6 fix(frontend): remove duplicate id for searchbars (#3000)
Only one element should have the same id. The id is used to link with the label's `for` attribute. There may be similar issues other places, but this is the one that was brought to my attention.

Resolves #2999
2024-12-10 20:16:13 +00:00
Evan SongandGitHub d851f32ea0 fix: do not consider backup limit in reinstall prompt (#2994)
Signed-off-by: Evan Song <theevansong@gmail.com>
2024-12-09 23:10:36 +00:00
Erb3andGitHub 88b1a370d4 fix(frontend): redirect report button to signin if needed (#2953)
Fixes #2951
2024-12-07 19:45:32 +00:00
Erb3andGitHub b8957d386a fix(frontend): internal server error on 404 (#2990) 2024-12-07 19:45:02 +00:00
Erb3andGitHub 414e2ed2cd fix: issue templates used outdated label names (#2991) 2024-12-07 19:44:45 +00:00
GeometricallyandGitHub 2987f507fe Refunds + Upgrading/Downgrading plans (#2983)
* Refunds + Upgrading/Downgrading plans

* Servers list route

* Finish, lint

* add GAM fee to payouts

* Sync payment intent id with stripe

* fix lint, update migrations

* Remove tauri generated files

* Register refund route

* fix refund bugs
2024-12-06 19:37:17 -08:00
Jai A 2cfb637451 Remove sync agree state 2024-11-27 13:17:01 -07:00
GeometricallyandGitHub 47b0ccdf78 Friends system for app (#2958)
* Friends system for app

* Fix impl issues

* move friends to in-memory store
2024-11-26 18:23:29 -07:00
Erb3andGitHub 7184c5f5c7 fix(app): update linux-schema.json (#2900)
Keeps bugging me in git. See https://discord.com/channels/734077874708938864/848329986577399818/1302710819792093266
2024-11-24 05:03:27 +00:00
Erb3andGitHub 53d45dafc2 fix(backend): validate PAT name (#2906)
Uses the macros provided by validator to validate the name. The name already had a macro, but .validate was not called.

Resolves #1549
2024-11-24 05:03:12 +00:00
Evan SongandGitHub d90cc09620 Implements MOD-171: Intercom messenger integration on servers panel (#2959)
* feat: intercom

Signed-off-by: Evan Song <theevansong@gmail.com>

* fix: double check

Signed-off-by: Evan Song <theevansong@gmail.com>

* fix: address double booting

Signed-off-by: Evan Song <theevansong@gmail.com>

---------

Signed-off-by: Evan Song <theevansong@gmail.com>
2024-11-22 23:25:33 +00:00
Jai A c741ba26cc add sync agree state 2024-11-21 13:06:18 -08:00
Jai A 510a6977be update ads.txt 2024-11-19 19:42:41 -08:00
Jai A d83e1a5c6b fix lint 2024-11-19 17:59:42 -08:00
Jai A 1557a6e1c0 RevIQ test 2024-11-19 17:58:35 -08:00
Jai A 396cf14d61 Add new PGAM lines 2024-11-19 14:13:31 -08:00
Erb3andGitHub 4685330eaf feat(labrinth): totp skew (#2887) 2024-11-19 00:31:48 +00:00
Jai A 8cd77ad1d8 Fix captcha not showing on create account page 2024-11-18 16:29:55 -08:00
GeometricallyandGitHub 3b0e59c8ab Switch to Sendy for newsletter (#2954) 2024-11-18 16:19:12 -08:00
Evan SongandGitHub 1f230383ce Fixes MOD-160 (#2948)
* fix: refresh backups automatically when ongoing backups are running

Signed-off-by: Evan Song <theevansong@gmail.com>

* chore: improve backup creation rate limit msg

Signed-off-by: Evan Song <theevansong@gmail.com>

* chore: increase polling timeout

Signed-off-by: Evan Song <theevansong@gmail.com>

* chore: clean

Signed-off-by: Evan Song <theevansong@gmail.com>

* chore: add notice of automatic refresh

Signed-off-by: Evan Song <theevansong@gmail.com>

---------

Signed-off-by: Evan Song <theevansong@gmail.com>
2024-11-18 02:59:01 +00:00
Evan SongandGitHub a1a920ee67 Fixes MOD-163 (#2952)
* init: correctly type powerstate in crash state

Signed-off-by: Evan Song <theevansong@gmail.com>

* feat: handle oom_killed and exit_code when a server crashes

Signed-off-by: Evan Song <theevansong@gmail.com>

---------

Signed-off-by: Evan Song <theevansong@gmail.com>
2024-11-18 02:58:43 +00:00
Jai A 1aa2299b55 fix hcaptcha not loading 2024-11-16 17:44:24 -08:00
GeometricallyandGitHub b188b3feb3 Switch to HCaptcha for Auth-related captchas (#2945)
* Switch to HCaptcha for Auth-related captchas

* run fmt
2024-11-16 16:57:32 -08:00
Jai A 5ab1263495 Disable app ads clicks 2024-11-12 15:17:02 -08:00
GeometricallyandGitHub 7bb3059cd9 Revert TLS changes causing docker build fail (#2937)
* Revert TLS changes causing docker build fail

* Update build to push
2024-11-11 18:31:06 -08:00
Jai A 42db6001ed Fix labrinth docker image breakage 2024-11-11 17:16:23 -08:00
GeometricallyandGitHub 3fa07f64d3 Ratelimit acct creation (#2933) 2024-11-10 22:47:58 -08:00
Erb3andGitHub 648b40a8f5 fix(frontend): hide report button on own projects (#2905)
* fix(frontend): hide report button on own projects

Resolves #1381

* fix(frontend): hide report button on self
2024-11-11 05:15:37 +00:00
Erb3andGitHub ac46026f99 fix(frontend): mod search page with trailing slash (#2914)
Replaces the substring operation to find project type with a regex. The regex is a bit complex, so I added a comment explaining it.

Resolves #1382
2024-11-11 05:14:55 +00:00
Evan SongandGitHub 3ef56ce66e chore: show all java versions toggle (#2932)
Signed-off-by: Evan Song <theevansong@gmail.com>
2024-11-11 05:14:14 +00:00
b4998d3603 Update [id].vue (#2352)
Signed-off-by: aw <aw@a0.ax>
Co-authored-by: Prospector <6166773+Prospector@users.noreply.github.com>
2024-11-08 19:34:36 -08:00
ae2cd947b2 Add beta and alpha badges (#2929)
Co-authored-by: Prospector <prospectordev@gmail.com>
2024-11-08 19:26:28 -08:00
Jai A aae00e1312 Fix forge versions being reversed 2024-11-08 16:26:02 -08:00
he3alsandGitHub 24e90f0a54 feat(marketing): miami, overallocation card, misc fixes (#2926)
* fix(marketing): make faq headings medium

* feat(marketing): add card for overallocation

* feat(marketing): add miami location

* fix(marketing): 'login' -> 'sign in' consistency

* feat: plan query string support + simplify buttons
2024-11-08 23:52:32 +00:00
Jai A f5208a85b0 Add rev.iq back to ads.txt 2024-11-07 19:08:12 -08:00
Jai A 8db4b3f83b Fix modrinth plus subscribing 2024-11-07 19:01:53 -08:00
Erb3andGitHub 33ad04d036 feat(frontend): show date of user join (#2901)
* feat(frontend): show date of user join

Shows and formats the date when the user joined, on hover. Can add `cursor-help` if wanted.

Resolves #2243

* chore(frontend): lint
2024-11-08 02:25:02 +00:00
Jai A 4bcdb3f495 Fix lint + docker build 2024-11-07 18:21:01 -08:00
Jai A 70979172b0 fix daedalus neoforge manifest misformatting 2024-11-07 18:13:57 -08:00
Jai A 493b9a3975 Re-add inmobi 2024-11-06 13:25:39 -07:00
Jai A 5a21a67d46 Remove inmobi (temp) 2024-11-06 11:27:35 -07:00
Evan SongandGitHub ff72c906ba fix: correct uri encode paths in fs module (#2922)
Signed-off-by: Evan Song <theevansong@gmail.com>
2024-11-05 23:35:43 +00:00
Evan SongandGitHub 907b1f67ed fix: multipart mrpack by gently holding ofetch's hand and saying "we got this, actually" and we ignore all the bullshit that comes with it (#2921)
* fix: multipart mrpack

Signed-off-by: Evan Song <theevansong@gmail.com>

* fix: use native fetch

Signed-off-by: Evan Song <theevansong@gmail.com>

---------

Signed-off-by: Evan Song <theevansong@gmail.com>
2024-11-05 23:21:41 +00:00
Evan SongandGitHub 72cbe7f905 fix: unnecessary refetch of server modules on first mount (#2918)
Signed-off-by: Evan Song <theevansong@gmail.com>
2024-11-05 22:53:14 +00:00
Evan SongandGitHub deb16aa7ab pyro: multipart mrpack uploads (#2917)
* chore: impl in pyroservers

Signed-off-by: Evan Song <theevansong@gmail.com>

* chore: disable install btn if loading

Signed-off-by: Evan Song <theevansong@gmail.com>

---------

Signed-off-by: Evan Song <theevansong@gmail.com>
2024-11-05 22:24:13 +00:00
Evan SongandGitHub d0efa44c9e refactor(terminal): rewrite terminal virtualization (#2916)
Signed-off-by: Evan Song <theevansong@gmail.com>
2024-11-05 20:32:11 +00:00
he3alsandGitHub d321843c02 feat(servers): full custom plan capacity checks & more (#2911)
* feat(servers): improve plan button logic

* feat(servers): custom plan capacity checks

* feat(servers): custom plan dynamic ram values

* feat(servers): add custom plan selector back

* fix(servers): final fixes
2024-11-05 02:07:40 +00:00
Jai A 2b44b145cb Remove direct clean.io script 2024-11-04 17:58:27 -07:00
nullptrandGitHub 9f5606889e fix: remove plan selector (#2909) 2024-11-04 16:27:49 -08:00
Evan SongandGitHub 417f2a8b91 fix: do not prepend modrinth.com to backup download url (#2908)
Signed-off-by: Evan Song <theevansong@gmail.com>
2024-11-04 23:42:32 +00:00
Jai A d3a7bf967e kill ads loop after 10s 2024-11-03 23:50:55 -07:00
Jai A db145cd8ad More ads test 2024-11-03 20:35:04 -07:00
Jai A 7d614f7ac5 Remove inmobi for US traffic (test) 2024-11-03 15:27:41 -08:00
Prospector c7a2a3e29b Fix navigation tab highlights for certain project types 2024-11-03 10:45:43 -08:00
nullptrandGitHub 3b966c03ee fix(servers): faqs not opening on nav (#2897)
* feat(servers): allow scrolling faq into view via hashes

* fix(servers): hide webkit details marker for faq

* fix(servers): faqs not opening on nav

---------

Signed-off-by: nullptr <62841684+not-nullptr@users.noreply.github.com>
2024-11-03 17:43:23 +00:00
Erb3andGitHub 66d943d391 fix(frontend): alt-text for flags (#2894)
Fixes #2397
2024-11-03 17:10:09 +00:00
nullptrandGitHub f5f876e458 fix(servers): hide webkit details marker for faq (#2895)
* feat(servers): allow scrolling faq into view via hashes

* fix(servers): hide webkit details marker for faq

---------

Signed-off-by: nullptr <62841684+not-nullptr@users.noreply.github.com>
2024-11-03 17:09:29 +00:00
Erb3andGitHub 27c3439120 fix(frontend): remove double dollar sign in chart (#2896)
Resolves #2400 by removing the SVG suffix.
2024-11-03 17:09:17 +00:00
f212d04261 feat(servers): allow scrolling faq into view via hashes (#2893)
Co-authored-by: Prospector <6166773+Prospector@users.noreply.github.com>
2024-11-03 16:35:24 +00:00
Erb3andGitHub 06f01aa85c fix(frontend): generate auth URL with provider (#2891)
Resolves an issue where the frontend generated URLs without provider property when there is a frontend redirect uri. Without the provider, labrinth defaults to GitHub.

Fixes #2884
2024-11-03 16:10:24 +00:00
nullptrandGitHub 5f48dc08a9 fix(servers): standalone versions not showing up in loader page (#2890) 2024-11-03 16:00:06 +00:00
Prospector e81e056758 Fix mistake in Servers marketing 2024-11-03 02:21:17 -08:00
2d95ff0830 chore: massage dependencies and features to remove openssl/native-tls (#2859)
Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
Co-authored-by: Prospector <6166773+Prospector@users.noreply.github.com>
2024-11-03 09:48:35 +00:00
Evan SongandGitHub 81d921d625 Pyro: Initial Marketing Fixes (#2885)
* fix: typo

Signed-off-by: Evan Song <theevansong@gmail.com>

* chore: specify specs on reliable hosting card

Signed-off-by: Evan Song <theevansong@gmail.com>

* chore: clarify locations

Signed-off-by: Evan Song <theevansong@gmail.com>

* chore: clarify SFTP and networking features

Signed-off-by: Evan Song <theevansong@gmail.com>

* chore: adjust copy

Signed-off-by: Evan Song <theevansong@gmail.com>

* feat: pushj faq section

Signed-off-by: Evan Song <theevansong@gmail.com>

* chore: address review

Signed-off-by: Evan Song <theevansong@gmail.com>

---------

Signed-off-by: Evan Song <theevansong@gmail.com>
2024-11-03 01:05:47 -08:00
e988513ed7 Typo: Change millibit to megabyte (#2406)
Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
Co-authored-by: Prospector <6166773+Prospector@users.noreply.github.com>
2024-11-03 09:42:57 +00:00
783d4f82d9 removed 1rem top margin for project description (#2523)
Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
Co-authored-by: Prospector <6166773+Prospector@users.noreply.github.com>
2024-11-03 09:32:29 +00:00
b5011f458f Add minecraft as loader to create version api docs (#2877)
Co-authored-by: Prospector <6166773+Prospector@users.noreply.github.com>
2024-11-03 09:32:18 +00:00
5d0e4366d2 set right margin to 0 on svgs inside a square notification button (#2871)
Co-authored-by: Prospector <6166773+Prospector@users.noreply.github.com>
2024-11-03 09:30:59 +00:00
8643dc02dd Consistent alignment for 'project settings page confirmation' buttons (#2524)
* buttons in .button-group now aligned to flex start

* revert global style change for .button-group

* manual fix for merge

* scoped .button-group styles

* /tags whitespace revert

---------

Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
Co-authored-by: Prospector <6166773+Prospector@users.noreply.github.com>
2024-11-03 09:29:44 +00:00
Jai A 7c4dcb2817 Fix game version filters 2024-11-02 23:18:31 -07:00
Jai A 6b64fdafcb Fix loaders/gvs not showing 2024-11-02 22:45:50 -07:00
+2 185dd47668 Pyro Integration (#2503)
* fix

Signed-off-by: Evan Song <theevansong@gmail.com>

* fix

Signed-off-by: Evan Song <theevansong@gmail.com>

* refactor(fileitem): optimize

Signed-off-by: Evan Song <theevansong@gmail.com>

* chore(fileitem): fixed width timestamp

Signed-off-by: Evan Song <theevansong@gmail.com>

* fix(fileitem): allow editing json5/jsonc

Signed-off-by: Evan Song <theevansong@gmail.com>

* feat: motd pt 1, auto backups scaffolding, editing navbar changes

* feat: fancy sidebar animations

* fix: files

* fix: files pt2

* fix: faulty name validation disallowing spaces in file names

Signed-off-by: Evan Song <theevansong@gmail.com>

* refactor: fileitem props

Signed-off-by: Evan Song <theevansong@gmail.com>

* fix: upload files not refreshing files list

Signed-off-by: Evan Song <theevansong@gmail.com>

* fix(imgviewer): handle invalid/empty images

Signed-off-by: Evan Song <theevansong@gmail.com>

* fix: return of the sticky files header

Signed-off-by: Evan Song <theevansong@gmail.com>

* chore: prevent servericon from shrinking

Signed-off-by: Evan Song <theevansong@gmail.com>

* fix: wtf were we thinking with this anyway

Signed-off-by: Evan Song <theevansong@gmail.com>

* fix: further mobile optimization

Signed-off-by: Evan Song <theevansong@gmail.com>

* chore: propagate margin

Signed-off-by: Evan Song <theevansong@gmail.com>

* chore: truncation fixes

Signed-off-by: Evan Song <theevansong@gmail.com>

* fix: track navbar with sentinel

Signed-off-by: Evan Song <theevansong@gmail.com>

* chore: clean

Signed-off-by: Evan Song <theevansong@gmail.com>

* fix(files): a11y

Signed-off-by: Evan Song <theevansong@gmail.com>

* chore: improve inspector styles

Signed-off-by: Evan Song <theevansong@gmail.com>

* chore: clean

Signed-off-by: Evan Song <theevansong@gmail.com>

* feat: console preformance improvements, decrease blur

* feat(mobile): new server header

* fix: linting

* fix: useless z indeces

Signed-off-by: Evan Song <theevansong@gmail.com>

* chore: adjust file filter names

Signed-off-by: Evan Song <theevansong@gmail.com>

* feat(files): true breadcrumbs

Signed-off-by: Evan Song <theevansong@gmail.com>

* fix(marketing): make custom responsive

* fix(marketing): mobile file manager card

* feat: trackable navtabs

Signed-off-by: Evan Song <theevansong@gmail.com>

* fix: oh no

Signed-off-by: Evan Song <theevansong@gmail.com>

* fix: smartly truncate

Signed-off-by: Evan Song <theevansong@gmail.com>

* fix(terminal): z-indexes

* fix: autofocus more inputs

Signed-off-by: Evan Song <theevansong@gmail.com>

* fix: color

Signed-off-by: Evan Song <theevansong@gmail.com>

* chore: adjust copy

Signed-off-by: Evan Song <theevansong@gmail.com>

* chore: backup modal usability improvements

Signed-off-by: Evan Song <theevansong@gmail.com>

* fix: padding

Signed-off-by: Evan Song <theevansong@gmail.com>

* chore: title

Signed-off-by: Evan Song <theevansong@gmail.com>

* fix(content): update banner mobile support

* fix: server listing icons

Signed-off-by: Evan Song <theevansong@gmail.com>

* fix: ignore clicks in server listing for labels

Signed-off-by: Evan Song <theevansong@gmail.com>

* feat(mobile): backup card

* fix(backups): make plural conditional

* fix: debounce file item selectitem

Signed-off-by: Evan Song <theevansong@gmail.com>

* fix: lint

Signed-off-by: Evan Song <theevansong@gmail.com>

* stuff

Signed-off-by: Evan Song <theevansong@gmail.com>

* fix: temp sidebar fix until i can be smart

* chore: clean

Signed-off-by: Evan Song <theevansong@gmail.com>

* chore: explictly set button type in file modals

Signed-off-by: Evan Song <theevansong@gmail.com>

* fix: properly sort backups

Signed-off-by: Evan Song <theevansong@gmail.com>

* feat: add getautobackup method to pyroservers

Signed-off-by: Evan Song <theevansong@gmail.com>

* choer: update autobackup params

Signed-off-by: Evan Song <theevansong@gmail.com>

* chore: update autobackup methods (REALLY GUYS)

Signed-off-by: Evan Song <theevansong@gmail.com>

* feat: implement autobackups

Signed-off-by: Evan Song <theevansong@gmail.com>

* feat: implement backup-while-running preference

Signed-off-by: Evan Song <theevansong@gmail.com>

* feat: make server labels a component

* feat: implement 'All details' modal

* fix(mobile): server manage page

* feat(files): mobile compatible

* fix(info labels): wrap

* chore(inspector): clean

Signed-off-by: Evan Song <theevansong@gmail.com>

* fix(backup settings): swap + and -

* fix(manage): new -> plans instead of modal

* feat: more small mobile fixes

* fix(auto backup modal): manual input validation

* fix(file browse navbar): home margin

* feat(purchase modal): mobile support

* fix(marketing): faded line alignments

* feat: add servers to mobile nav

* feat(network): dns record fixes

* feat: make all settings work on mobile

* fix(loader settings): modpack mobile

* chore: clean

Signed-off-by: Evan Song <theevansong@gmail.com>

* feat(marketing): add 'Manage your servers' button

* fix(marketing): only check servers if logged in

* fix(network): allocation edit & delete button

* fix(backups): use UiServersTeleportOverflowMenu

* chore: linting

* chore: but here comes the sentence case

Signed-off-by: Evan Song <theevansong@gmail.com>

* feat(marketing): make buttons consistent

* lint

Signed-off-by: Evan Song <theevansong@gmail.com>

* fix(loader): prevent multiline version names in dropdown

Signed-off-by: Evan Song <theevansong@gmail.com>

* lint

Signed-off-by: Evan Song <theevansong@gmail.com>

* fix: copy

Signed-off-by: Evan Song <theevansong@gmail.com>

* fix: sentence case

Signed-off-by: Evan Song <theevansong@gmail.com>

* fix: linting

* chore: rename dumbass preference key

Signed-off-by: Evan Song <theevansong@gmail.com>

* refactor: rewrite power action buttons

Signed-off-by: Evan Song <theevansong@gmail.com>

* fix: robust download logic

Signed-off-by: Evan Song <theevansong@gmail.com>

* fix(loader mobile): modpack dropdown width

* fix: sentence case

* fix(save & 'working on it'): look good on mobile

* fix(TeleportDropdown): width

* fix(inspecting error): mobile

* fix: show action button dropdown when installing

* fix(navtabs): temp fix for mobile scrolling issue

* fix(install error): mobile compatible

* chore: just remove tracking

Signed-off-by: Evan Song <theevansong@gmail.com>

* chore: clean

Signed-off-by: Evan Song <theevansong@gmail.com>

* chore: clean

Signed-off-by: Evan Song <theevansong@gmail.com>

* fix: cleanup

* fix: broken svg clr in checkbox when using experimental styles

Signed-off-by: Evan Song <theevansong@gmail.com>

* chore: adjust vanilla icon

Signed-off-by: Evan Song <theevansong@gmail.com>

* chore: adjust loader props

Signed-off-by: Evan Song <theevansong@gmail.com>

* revert changes to serversidebar

Signed-off-by: Evan Song <theevansong@gmail.com>

* fix: server properties flicker

Signed-off-by: Evan Song <theevansong@gmail.com>

* fix(backups): plural

* fix: cases where the telepoverflow would clash with viewport edge

Signed-off-by: Evan Song <theevansong@gmail.com>

* feat(backups): auto-backups label

* fix(network): titlecase

* feat(fileitem): new rename icon

* fix(properties): wiki proper noun

* fix: disable motd for the time being

* chore: adjust wording for power conifmration

Signed-off-by: Evan Song <theevansong@gmail.com>

* feat: "external" to billing

Signed-off-by: Evan Song <theevansong@gmail.com>

* fix: icon

Signed-off-by: Evan Song <theevansong@gmail.com>

* fix: add EULA checkbox

* chore: clean

Signed-off-by: Evan Song <theevansong@gmail.com>

* me and bro deciding which case rules to enforce

Signed-off-by: Evan Song <theevansong@gmail.com>

* feat(sftp): copy address & username, launch tooltip

* feat(files): better move

* chore: attempt to mitigate excessive stack depth type

Signed-off-by: Evan Song <theevansong@gmail.com>

* fix(loader): prevent versions 1.2.4 and below

* feat(dns table): placeholder improvements

* feat(pyroServer): error handling

* fix: intrinsic size on loader icon

Signed-off-by: Evan Song <theevansong@gmail.com>

* chore: adjust wording

Signed-off-by: Evan Song <theevansong@gmail.com>

* fix: sentence case

Signed-off-by: Evan Song <theevansong@gmail.com>

* chore: adjust wording

Signed-off-by: Evan Song <theevansong@gmail.com>

* fix: types

Signed-off-by: Evan Song <theevansong@gmail.com>

* fix: "implemented" key in preference

Signed-off-by: Evan Song <theevansong@gmail.com>

* feat(connection lost): redesign

* feat(connection error): make icon orange

* fix: cleanup

* chore(connection lost): redesign pt 2

Signed-off-by: Evan Song <theevansong@gmail.com>

* fix: OOOOHHH MY GOD

Signed-off-by: Evan Song <theevansong@gmail.com>

* feat: implement capacity api on marketing

Signed-off-by: Evan Song <theevansong@gmail.com>

* chore: update createdat backup type

Signed-off-by: Evan Song <theevansong@gmail.com>

* refactor: all of backups

Signed-off-by: Evan Song <theevansong@gmail.com>

* chore: update backup types

Signed-off-by: Evan Song <theevansong@gmail.com>

* refactor: backups pt 2

Signed-off-by: Evan Song <theevansong@gmail.com>

* fix: comically small icons

Signed-off-by: Evan Song <theevansong@gmail.com>

* chore: align designs

Signed-off-by: Evan Song <theevansong@gmail.com>

* chore: hide ram graph if ram as bytes enabled

Signed-off-by: Evan Song <theevansong@gmail.com>

* base add content page

* Fix conflict

* feat(content): mobile-compatible header, sticky

* fix(marketing): md instead of sm for custom

* fix: compiler macro warning

Signed-off-by: Evan Song <theevansong@gmail.com>

* again

Signed-off-by: Evan Song <theevansong@gmail.com>

* fix: loader type error

Signed-off-by: Evan Song <theevansong@gmail.com>

* fix: default uptime seconds prop

Signed-off-by: Evan Song <theevansong@gmail.com>

* fix: hydration errors on server listing

Signed-off-by: Evan Song <theevansong@gmail.com>

* feat: move custom URL to general

Signed-off-by: Evan Song <theevansong@gmail.com>

* feat: indiviudally checkj capacities

Signed-off-by: Evan Song <theevansong@gmail.com>

* fix: falsey

Signed-off-by: Evan Song <theevansong@gmail.com>

* fix: missing prop

Signed-off-by: Evan Song <theevansong@gmail.com>

* chore: Derive On That Thang

Signed-off-by: Evan Song <theevansong@gmail.com>

* chore: adjust gap

Signed-off-by: Evan Song <theevansong@gmail.com>

* fix: add default name for backups

* fix: the backup number should PROBABLY be computed lol

* fix(backups): truncate text, mobile fixes

* fix(loader): modpack mobile fix

* feat(plans): add vcpus

* fix(backup modal): blank by default, maxlength

* fix(subdomain): separate length & valid chars

* feat: mrpack installs functionality (untested), forbidden handling, backups grammar

* feat(content): make responsive on mobile

* fix: disable plan buttons separately

* fix(backup modal): update name max length

* fix(purchase): wrapping on eula, eula link

* fix: move skeleton

* fix(server mobile header): truncation

* fix(server header): proper alignment

* Finish content page fixes

* fix: who up rinthing

Signed-off-by: Evan Song <theevansong@gmail.com>

* wip

Signed-off-by: Evan Song <theevansong@gmail.com>

* fix(staging & email banner): z-index

* feat: make eula tickbox more visible

* fix: move "powered by pyro" below buttons on hero

* fix: oops sorry ellie, also updated the main screenshot

* feat: update content screenshot

* fix: content page card should hide image on lg

* feat: hide total storage for now

* fix: terminal card now uses terminal icon

* fix(marketing): make medium plan card border solid

* feat: modloader card, move pyro BACK below buttons, beta release pill

* fix: spinning logo should be behind hero

* feat: surgically remove the hero's massive forehead

* feat(marketing): mobile UI screenshot

* fix(hero): z-index goes over mobile nav

* fix: consistent borders, files breakpoints

* chore: update turbo

* chore: adjust hero sizing

Signed-off-by: Evan Song <theevansong@gmail.com>

* chore: mention region restrictions

* chore: double check if we are at capcity

Signed-off-by: Evan Song <theevansong@gmail.com>

* fix: measure twice cut once

Signed-off-by: Evan Song <theevansong@gmail.com>

* chore: bro cut twice and measured once 💀

Signed-off-by: Evan Song <theevansong@gmail.com>

* fix(marketing): login first

* fix: out of capacity text when logged out

* fix(slider): reset some values for frontend

* feat: wip hero section

Signed-off-by: Evan Song <theevansong@gmail.com>

* New navigation to support the new products (#2879)

* Nav

* oops extra file

* feat: mrpack uploading with existing modpack, fix: choose modpack duplicate

* chore: clean

Signed-off-by: Evan Song <theevansong@gmail.com>

* feat: update features section

Signed-off-by: Evan Song <theevansong@gmail.com>

* Nav adjustments

* fix: server manager empty state clashing with loading state

Signed-off-by: Evan Song <theevansong@gmail.com>

* chore: query param hard

Signed-off-by: Evan Song <theevansong@gmail.com>

* fix: do not count uptime if crashed

Signed-off-by: Evan Song <theevansong@gmail.com>

* fix: grammar

Signed-off-by: Evan Song <theevansong@gmail.com>

* hide hero img on lg breakpoints

* Make plugins a plug

* chore: prep for buffered text selection terminal

Signed-off-by: Evan Song <theevansong@gmail.com>

* fix: marketing responsive stuff, n fixes

* fix hoverable prop

* fix: edit mod spacing

* fix: type error for display name in dropdown

Signed-off-by: Evan Song <theevansong@gmail.com>

* feat: custom plans

* fix: no more console.log

* fix: properly linked prop label

Signed-off-by: Evan Song <theevansong@gmail.com>

* fix(install hero mobile): padding

* fix: prevent x overflow on servers page

Signed-off-by: Evan Song <theevansong@gmail.com>

* fix lint oh ym fucking god yal

Signed-off-by: Evan Song <theevansong@gmail.com>

* Migrate modpack install to search

* fix(custom plan): warning icon variable

* fix: loading probally and modal loader things

* fix(marketing): login icon colours

* fix(marketing): responsiveness

* fix(marketing): responsiveness v2

* fix: sync button for icon tm

* fix(marketing): responsiveness v3

* fix: hero image

Signed-off-by: Evan Song <theevansong@gmail.com>

* chore: clean

Signed-off-by: Evan Song <theevansong@gmail.com>

* chore: switch to cdn links

Signed-off-by: Evan Song <theevansong@gmail.com>

* chore: switch to cdn links

Signed-off-by: Evan Song <theevansong@gmail.com>

* chore: switch to cdn links

Signed-off-by: Evan Song <theevansong@gmail.com>

* chore: switch to cdn links

Signed-off-by: Evan Song <theevansong@gmail.com>

* Remove prod override

---------

Signed-off-by: Evan Song <theevansong@gmail.com>
Co-authored-by: Evan Song <theevansong@gmail.com>
Co-authored-by: TheWander02 <48934424+thewander02@users.noreply.github.com>
Co-authored-by: he3als <65787561+he3als@users.noreply.github.com>
Co-authored-by: Evan Song <52982404+ferothefox@users.noreply.github.com>
Co-authored-by: Lio <git@lio.cat>
Co-authored-by: Jai A <jaiagr+gpg@pm.me>
Co-authored-by: not-nullptr <needhelpwithrift@gmail.com>
Co-authored-by: Prospector <6166773+Prospector@users.noreply.github.com>
Co-authored-by: Prospector <prospectordev@gmail.com>
Co-authored-by: sticks <tanner@teamhydra.dev>
2024-11-02 21:14:00 -07:00
Jai A f165665a35 Fix primary filter 2024-11-02 01:44:05 -07:00
Jai A ad38749f98 fix lint 2024-10-31 15:24:13 -07:00
Jai A 7825dd64ca Sync app consent with inmobi consent 2024-10-31 15:14:41 -07:00
Jai A f6af620643 Add inmobi to app iframe 2024-10-30 15:30:24 -07:00
b5aeef7ebf Allow Bearer prefix on authorization tokens (#2854)
Signed-off-by: Skye <me@skye.vg>
Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
2024-10-27 19:04:17 +00:00
JuneandGitHub f5a201dd94 add top margin to author actions section (#2861) 2024-10-27 11:27:06 -07:00
Erb3andGitHub 72dab12033 fix(docs): correct favicon url (#2843)
Starlight defaults to favicon.svg, however a favicon.ico was added to the repo.

Changes:

- Format astro config according to Prettier
- Properly set favicon
2024-10-24 13:24:13 -07:00
Jai A ce4b4ba41d Fix daedalus run 2024-10-23 13:41:22 -07:00
Jai A 945206153d ads.txt update 2024-10-22 12:10:54 -07:00
GeometricallyandGitHub 9f977d082b Merge pull request #2840 from modrinth/daedalus-add
Add daedalus to monorepo
2024-10-21 15:33:58 -07:00
Jai A 4a2ec0c40c Merge remote-tracking branch 'origin/daedalus-add' into daedalus-add 2024-10-21 13:22:30 -07:00
Jai A 70bf61a645 Fix fmt 2024-10-21 13:21:56 -07:00
GeometricallyandGitHub 674f4b1095 Merge branch 'main' into daedalus-add 2024-10-21 12:40:17 -07:00
Jai A 3304070034 Add custom parameter for app/web traffic 2024-10-21 12:34:46 -07:00
GeometricallyandGitHub 19ca5f08c6 Merge branch 'main' into daedalus-add 2024-10-21 12:16:41 -07:00
Jai A 5605910ac8 Build spec package 2024-10-21 12:11:14 -07:00
Erb3andGitHub b1eda435a5 fix: update old docs links (#2845)
- Markdown guide link to support article
- API version deprecated error to new docs
- Modpack permissions error to support article
- Labrinth README links to new docs
- App WebView2 link to support article
2024-10-21 12:08:06 -07:00
Jai A e88ca8430e rust update 2024-10-19 20:11:18 -07:00
Jai A d39db02a73 Fix docker build 2024-10-19 20:04:21 -07:00
Jai A 86a64ca929 Update readmes 2024-10-19 19:46:40 -07:00
Jai A 5ea0f7d4c7 Fix docker build again 2024-10-19 19:42:52 -07:00
Jai A b3fa2fa6d2 Fix docker build 2024-10-19 19:38:36 -07:00
Jai A 999dc640bc fix package 2024-10-19 15:44:37 -07:00
Jai A 9be9658ffb Add daedalus 2024-10-19 15:14:30 -07:00
Jai A 595d5362f6 Merge remote-tracking branch 'daedalus/monorepo-migration' into daedalus-add 2024-10-19 14:59:12 -07:00
Jai A f212fcf892 Start monorepo migration 2024-10-19 14:40:58 -07:00
GeometricallyandGitHub 8c1c5572c0 Merge pull request #2517 from modrinth/labrinth-add
Add labrinth to monorepo
2024-10-19 12:42:38 -07:00
Jai A 31d151638a Temp disable labrinth test in CI 2024-10-19 12:39:46 -07:00
Jai A fb6b41630c Fix compose script 2024-10-19 10:07:22 -07:00
Jai A 6ab806cbde Merge remote-tracking branch 'origin/labrinth-add' into labrinth-add 2024-10-18 21:28:01 -07:00
Jai A c0267f7746 Remove all features 2024-10-18 21:14:25 -07:00
GeometricallyandGitHub a54b6dc7b9 Merge branch 'main' into labrinth-add 2024-10-18 19:45:43 -07:00
Jai A 9ec43ebe70 Fix lint 2024-10-18 19:45:00 -07:00
GeometricallyandGitHub 486cd68bf7 New docs site (#2521)
* initial docs

* more docs work

* Update readme + add license

* update frontend GH action
2024-10-18 17:36:20 -07:00
Jai A 98c050e7e9 Fix lint again 2024-10-18 17:33:15 -07:00
Jai A 25fcee984b Add container labels 2024-10-18 17:00:36 -07:00
Jai A 86922c4547 Fix lint not working 2024-10-18 16:54:21 -07:00
Jai A 7bbdfd25cd [skip ci] remove ds store 2024-10-18 16:28:40 -07:00
Jai A b8ad22a6fb Fix checks 2024-10-18 16:23:32 -07:00
Jai A 8dd955563e Fix clippy errors + lint, use turbo CI 2024-10-18 16:07:35 -07:00
Jai A 663ab83b08 Fix build deps 2024-10-18 15:47:23 -07:00
Jai A c143929b69 Move rust checks to own file 2024-10-18 15:43:30 -07:00
Jai A 1b73d248b3 bump rustc 2024-10-16 16:12:23 -07:00
Jai A cc22a92daf Update dockerfile 2024-10-16 16:07:24 -07:00
Jai A 39f0408929 Fix contact path 2024-10-16 16:04:45 -07:00
Jai A e66f46a464 Fix check config 2024-10-16 16:02:55 -07:00
Jai A 9243296197 Fix again 2024-10-16 16:01:50 -07:00
Jai A 26ce83f8f1 Fix docker path 2024-10-16 15:59:29 -07:00
Jai A 907ef38189 make checks work 2024-10-16 15:57:44 -07:00
Jai A a7d4001b00 Merge remote-tracking branch 'labrinth/mono-repo-migrate' 2024-10-16 14:15:45 -07:00
Jai A e3a3379615 move to monorepo dir 2024-10-16 14:11:42 -07:00
Jai A 356a06e694 add signal for app vs web 2024-10-15 23:47:51 -07:00
GeometricallyandGitHub fce516a76f Remove ads muting (#2511) 2024-10-15 23:43:49 -07:00
Jai A 42ade0fbd1 make script non-async 2024-10-15 10:56:35 -07:00
Jai A ba07f5dad4 Add clean.io direct 2024-10-15 10:52:00 -07:00
Jai A cc89e0f3f1 remove ad cookie (main) 2024-10-14 23:49:27 -07:00
Jai A 0e14d3f9c1 update ads.txt 2024-10-14 23:47:42 -07:00
GeometricallyandGitHub ff7975773e Prorations (#975)
* Prorations

* Fix pyro integration

* set server uuid on creation

* fix comp

* Fix new charge date, pyro suspend reason

* Update server creation endpoint
2024-10-14 13:30:04 -07:00
6716e2277d fix(theseus): Files drag & drop (#2499)
Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
2024-10-12 13:27:07 -07:00
MysticdrewandGitHub f986dc5d11 Remove extra "not" (#2506)
Signed-off-by: Mysticdrew <drewhaas@gmail.com>
2024-10-10 15:27:43 -07:00
GeometricallyandGitHub 570a4096f9 Update billing with backend changes (#2505) 2024-10-09 21:11:49 -07:00
GeometricallyandGitHub c88bfbb5f0 Move charges to DB + fix subscription recurring payments (#971)
* Move charges to DB + fix subscription recurring payments

* Finish most + pyro integration

* Finish billing

* Run prepare

* Fix intervals

* Fix clippy

* Remove unused test
2024-10-09 21:11:30 -07:00
Jai A d302795512 Fix ad init not working on no tauri invoke 2024-10-09 14:04:33 -07:00
Jai A 115acce80c Add hashing to ads 2024-10-09 13:54:35 -07:00
GeometricallyandGitHub a8731b0ca2 Fix unfollowing projects (#2496) 2024-10-08 15:19:46 -07:00
nekkandGitHub fd596bf418 type in ErrorModal.vue (#2492)
Signed-off-by: nekk <108535017+iam-nekk@users.noreply.github.com>
2024-10-06 21:11:39 +00:00
GeometricallyandGitHub ef7cfffeb6 Add support for Optima (#2489) 2024-10-04 13:35:39 -07:00
Jai A ac9bcabd9c Add RPM to website 2024-10-02 13:33:47 -07:00
GeometricallyandGitHub 5bb961f16b Fix linux appimage (#2482) 2024-10-02 12:02:14 -07:00
Jai A a46677832b Fix ads init 2024-09-29 17:57:16 -07:00
GeometricallyandGitHub 624abf0df4 Mute audio from playing in ads (#2471)
* Mute audio from playing in ads

* Update tauri version, get rid of custom

* bump wry

* fix more

* Fix lint
2024-09-29 17:51:51 -07:00
Erb3andGitHub e81a4ade97 chore: run lint (#2411)
* chore: run lint

* chore: fix lint
2024-09-28 01:28:13 +00:00
GeometricallyandGitHub 9708685506 Add GDPR export (#2461) 2024-09-27 13:33:28 -07:00
GeometricallyandGitHub 28b6bf8603 GDPR export route (#969)
* GDPR export route

* make users able to access
2024-09-27 12:43:17 -07:00
GeometricallyandGitHub 2713f0e610 Fix markdown images (#2452) 2024-09-25 12:29:51 -07:00
GeometricallyandGitHub f7d1cd2a4f Pause subscription renewals (#968) 2024-09-24 13:02:19 -07:00
Prospector 3b8963fad0 Update ads.txt 2024-09-16 19:19:34 -07:00
Prospector 060682a1ac Add moderation button to user menu 2024-09-14 15:02:23 -07:00
GeometricallyandGitHub 95cd48571e Improve ad security, add CMP changes (#2399) 2024-09-13 20:33:51 -07:00
Jai A 3d619e6a98 Fix creator balance showing as zero 2024-09-12 15:52:37 -07:00
GeometricallyandGitHub edb7e5f323 Integrate with Aditude API for payouts (#965)
* Integrate with Aditude API for payouts

* Update expiry

* Fix tests
2024-09-12 14:52:17 -07:00
GeometricallyandGitHub 0221034b60 Fix file paths opening (#2382) 2024-09-10 01:36:56 -07:00
Jai A 9500384100 Update ads.txt 2024-09-10 00:32:14 -07:00
GeometricallyandGitHub 0b31f2eb41 0.8.5 fixes (#2369)
* 0.8.5 fixes

* Attempt to fix binary name

* bump version + add nsis installmode

* (temp) Use cargo version of tauri CLI

* fix cli build

* Fix build env var

* Bump tauri version

* remove old invalid installs

* Fix old shortcuts + NSIS build
2024-09-10 00:24:17 -07:00
b3a6393c91 Add TypeScript to app-frontend (#2364)
* Add TypeScript to app-frontend

Co-authored-by: Evan Song <52982404+ferothefox@users.noreply.github.com>

* Switch app-frontend to ESLint 9 & Nuxt config

* Fix ESLint issues after config change in app-frontend

---------

Co-authored-by: Evan Song <52982404+ferothefox@users.noreply.github.com>
2024-09-07 23:40:40 -07:00
GeometricallyandGitHub 5b5599128a Optimize user-generated images for reduced bandwidth (#961)
* Optimize user-generated images for reduced bandwidth

* run prepare

* Finish compression
2024-09-07 17:44:49 -07:00
GeometricallyandGitHub cb0f03ca9c Slack webhooks (#959)
* Slack webhooks

* Fix automod rejecting audio and locale packs

* Run prepare
2024-09-06 23:42:54 -07:00
GeometricallyandGitHub 2e35f3608b Remove name field (and remove existing data) (#935)
* Remove name field (and remove existing data)

* run prep, fmt

* fix dummy data
2024-09-06 20:16:28 -07:00
Jai A 16c5a5a3a6 revert ads changes 2024-09-05 14:56:17 -07:00
Jai A 2e7db502a9 fix empty tag 2024-09-04 09:37:58 -07:00
Jai A d29b71ec45 Fix app ads styling 2024-09-04 09:37:07 -07:00
Jai A 9cd0af914a fix slot not rendering 2024-09-04 09:31:39 -07:00
Jai A 4a575393f0 remove inmobi 2024-09-04 09:29:06 -07:00
Jai A 76c93c767d rev.iq support 2024-09-04 09:23:18 -07:00
Jai A e69337a1fc update ads.txt with new dsps 2024-09-03 21:22:24 -07:00
Sasha SorokinandGitHub 50734af6cd Fix Game versions input not showing (#2358)
There is a bug in vue-multiselect library where it will default
`custom-label` property to raw properies object. Presumably this is a
bug that appeared because they ported library to Vue 3 and have not
considered that the signature for `default` prop's function has changed.

To work around this, we can provide custom `custom-label` property that
is just a function that returns the input itself. In other places, where
objects are passed, `label` property can be used. In this case the first
suggestion applies. DO NOT USE VUE-MULTISELECT WITOUT `LABEL` OR
`CUSTOM-LABEL` PROPS!
2024-09-03 16:44:21 +00:00
NorbirosandGitHub 81b0922c93 chore(theseus): Updated Tauri links to v2 (#2345) 2024-09-01 21:57:13 +00:00
NorbirosandGitHub d4f8fff7af fix(theseus): Posthog scripts, focus app when opening deeplink, update linux-schema (#2351)
* fix(theseus): Focus app when opening deeplink (#2347)

* fix(theseus): Allow for posthog scripts

* chores(theseus): Update `linux-schema.json`
2024-09-01 21:56:53 +00:00
GeometricallyandGitHub bd61f5d591 Fix auto updater, add failure message, fix modals (#2335)
* Fix auto updater, add failure message, fix modals

* Fix ads hiding, updater UI

* dummy version, fix gh actions cache

* fix release conf

* actual version bump

* Fix ads hiding sometimes

* Fix event state init

* fix remaining bugs

* Fix lint on linux

* Fix deep linking on Windows

* Fix ad links opening multiple times
2024-08-30 10:42:58 -07:00
GeometricallyandGitHub 016c3d779b Fix localhost windows opening (#2331) 2024-08-28 23:21:35 -07:00
GeometricallyandGitHub acf26940d6 Implement ads in desktop app (#2318)
* Implement ads in desktop app

* Finish ads

* use git dep instead

* attempt to fix linux build (temp)

* bump version + lint

* comment more

* fix build

* try to fix linux build

* Fix crashing on windows

* Fix icons not showing

* Remove useless env vars

* Actual linux build fix

* Run fmt

* Fix scrolling

* fix clippy

* bump version + fix localhost

* rev linux build patch

* update version num

* update csp

* update csp

* update csp

* Switch to mousewheel event
2024-08-28 21:44:08 -07:00
4bafae881f fix: Correctly fix App Build workflow (#2322)
Signed-off-by: Norbiros <norbiros@protonmail.com>
Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
2024-08-28 10:18:54 -07:00
8311451420 Update public-facing orgs page, componetize page headers (#2307)
* Update public-facing orgs page, componetize page headers

* Improve supported environments

* Move user page stats to top and remove details card

* Fix padding on orgs page when no navlinks

* fix lint

---------

Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
2024-08-28 10:12:25 -07:00
NorbirosandGitHub 4b75cb8357 fix: App Build workflow (#2320)
Signed-off-by: Norbiros <norbiros@protonmail.com>
2024-08-28 14:25:53 +00:00
Jai A 679ffbcce7 Fix Fabric Loader 0.16.0, old forge versions 2024-08-21 18:42:02 -07:00
GeometricallyandGitHub 637a923e84 Fix empty subscriptions (#954) 2024-08-18 11:35:39 -07:00
Jai A 1f8d569b79 Merge remote-tracking branch 'origin/master' 2024-08-17 13:28:20 -07:00
Jai A 93ae24e707 Fix forge format version 1 2024-08-17 13:28:00 -07:00
GeometricallyandGitHub 7dd340f0b6 Fix subscriptions edge case (#952)
* Fix subscriptions edge case

* prep
2024-08-15 02:03:46 -07:00
GeometricallyandGitHub 1d0d8d7fbe Payments/subscriptions support (#943)
* [wip] Payments/subscriptions support

* finish

* working payment flow

* finish subscriptions, lint, clippy, etc

* docker compose
2024-08-14 17:14:52 -07:00
NorbirosandGitHub 6de8d2684a fix: Disable Run Meta on forks (#19)
* fix: Disable `Run Meta` on forks

* fix: Also don't run `docker` action
2024-07-31 13:14:39 -07:00
NorbirosandGitHub 9763a43943 fix: Correctly replace linux natives for LWJGL 3.3.1 (#20) 2024-07-31 13:14:30 -07:00
60edbcd5f0 Allow user to select GitHub account (#922)
Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
2024-07-09 15:12:15 -07:00
Jai A 0ae1e40d79 fix typo 2024-06-28 17:03:54 -07:00
Jai A 34d931573c try with bookworm 2024-06-28 17:02:30 -07:00
Jai A 721365578a update action to use master branch 2024-06-28 16:59:06 -07:00
Jai A 7be02318e0 add openssl 2024-06-28 16:55:54 -07:00
Jai A 88db79188c use rustls 2024-06-28 16:50:47 -07:00
Jai A 8a0329b23d don't run in detached mode 2024-06-28 16:48:03 -07:00
Jai A 02ebe59d2f add dispatch support for meta 2024-06-28 16:45:06 -07:00
Jai A 6e8e053b88 fix newline 2024-06-28 16:40:12 -07:00
Jai A fc3056b0e0 add rust log env 2024-06-28 15:53:59 -07:00
GeometricallyandGitHub 4274a8ed68 Fix forge install issues (#18)
* Fix forge install issues

* remove mac garb
2024-06-28 15:44:17 -07:00
GeometricallyandGitHub 8b16cd1b36 Daedalus Rewrite + Code Cleanup (#16)
* [wip] rewrite daedalus, vanilla, fabric, and quilt

* finish forge + neo

* fix docker

* fix neoforge 1.21+

* update concurrency limit

* finish

* remove mac garb
2024-06-25 15:47:27 -07:00
GeometricallyandGitHub 5148e27448 Fix neoforge check, make forge validator more lenient (#928) 2024-06-14 13:16:05 -07:00
GeometricallyandGitHub 608e55c01f Fix duplicate file names (#927)
* Fix duplicate file names

* Fix checks
2024-06-12 21:40:24 -07:00
GeometricallyandGitHub b8963d272a Update validators again (#925)
* Update validators again

* fix tests + clippy
2024-06-12 14:38:35 -07:00
GeometricallyandGitHub beaaed6613 Use row level locking for payouts (#926) 2024-06-12 14:19:15 -07:00
6bbd8c9b16 Update link to LICENSE in COPYING.md (#865)
The link to the license was outdated and resulted in a broken path. The correct filename is "LICENSE.txt".

Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
2024-06-12 13:41:40 -07:00
GeometricallyandGitHub 872ffa02ce fix version update route perf (#923)
* fix version update route perf

* fix tests
2024-06-12 09:58:01 -07:00
GeometricallyandGitHub b933202694 Fix quilt validator (#916) 2024-05-08 20:28:40 -07:00
GeometricallyandGitHub 49cf0c8a9a Fix user deletion (#907)
* Fix user deletion

* run prep+fmt

* Update validators
2024-04-22 17:46:56 -07:00
GeometricallyandGitHub 83ccf4928f Fix mod msg status (#896)
* Fix mod msg status

* Fix validators
2024-04-22 13:09:35 -07:00
28b0d34bff Fix search query parameter validation (#863)
* Fix search query parameter validation

* fix compile

* make it actually work

---------

Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
2024-04-22 11:40:31 -07:00
GeometricallyandGitHub 0a0837ea02 update ratelimiter (#897)
* update ratelimiter

* Switch to old scheduler
2024-03-27 15:56:29 -07:00
GeometricallyandGitHub a0aa350a08 Fix cache stampede issues + generalize cache (#884)
* caching changes

* fix cache stampede issues

* Use pub/sub for better DB fetches

* remove pubsub

* remove debugs

* Fix caches not working

* fix search indexing removal
2024-03-26 21:15:50 -07:00
GeometricallyandGitHub decfcb6c27 Fix issue with moderator identities being revealed (#892)
* Fix issue with moderator identities being revealed

* Fix on multiple threads route

* Fix thread notifs

* Fix failing test

* fix thread messages returning nothing
2024-03-19 17:25:49 -07:00
GeometricallyandGitHub 730913bec4 Fix issue in specifying dependencies (#891) 2024-03-18 13:56:06 -07:00
GeometricallyandGitHub f8f037196e Fix payouts desync (#890)
* Fix payouts desync

* fix tests + user payouts req
2024-03-12 10:52:40 -07:00
GeometricallyandGitHub e2ffeab8fa fix views analytics (#885)
* fix views analytics

* update ip stripping

* update clickhouse tables

* fix broken queries

* Fix panics

* fix download undercounting

* fix packerator failing sometimes

* run prep
2024-03-02 14:04:46 -07:00
GeometricallyandGitHub 04d834187b Automatic moderation (#875)
* Automatic moderation

* finish

* modpack fixes

* fix unknown license msg

* fix moderation issues
2024-02-21 16:24:21 -07:00
GeometricallyandGitHub 33b2a94d90 Fix version creation taking forever (#878)
* Fix version creation taking forever

* run fmt + prep

* fix tests?
2024-02-05 12:24:12 -07:00
GeometricallyandGitHub ce3b024fea Fix gift cards (#877)
* Fix gift card cashout

* Fix mutex locks
2024-02-05 12:02:07 -07:00
GeometricallyandGitHub a02aa7586b Fix version files updates route (#876)
* Fix version updates files route

* run fmt + prep

* actually work

* update query perf

* fix
2024-02-04 20:19:46 -07:00
GeometricallyandGitHub d5107f2ef6 Fix unlisted showing (#873)
* Fix projects showing draft

* fix build

* run fmt
2024-01-27 19:11:00 -05:00
5b63b0b398 jemalloc (#861)
* jemalloc

* featurizeP

---------

Co-authored-by: Wyatt Verchere <wverchere@gmail.com>
2024-01-27 18:13:52 -05:00
GeometricallyandGitHub fc577241bd Update deps (#859) 2024-01-14 12:36:11 -05:00
GeometricallyandGitHub bb8a0e596c Fix settings fail (#856)
* Fix settings fail

* Revert settings equality checks
2024-01-13 21:09:28 -05:00
GeometricallyandGitHub 2a63b703f9 Fix additional categories not showing up correctly (#855) 2024-01-13 20:19:45 -05:00
bfeff78164 Update search queries (#854)
* Update search queries

* Bump accepted payload limit

* fixes

* push test changes

* fmt clippy prepare

---------

Co-authored-by: Wyatt Verchere <wverchere@gmail.com>
2024-01-13 18:20:28 -05:00
4826289020 hardcodes search facets (#852)
* hardcodes search facets

* disable search

* Uncomment

---------

Co-authored-by: Jai A <jaiagr+gpg@pm.me>
Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
2024-01-13 17:42:45 -05:00
0aebf37ef8 Index swapping when meilisearch reindex (#853)
* cycling indices

* removed printlns

* uses swap indices instead

* Bring back deletion

* Fix tests

* Fix version deletion

---------

Co-authored-by: Jai A <jaiagr+gpg@pm.me>
2024-01-13 17:40:30 -05:00
GeometricallyandGitHub d1a09d0b95 Add assets and migrations to dockerfile (#851) 2024-01-12 15:00:37 -05:00
GeometricallyandGitHub 7b00003958 Org fixes (#850)
* Org fixes

* payouts bug

* Update dockerfile fix test

* Update to bookworm

* clippy
2024-01-12 14:19:39 -05:00
4483bb147c re-modularizes-search-facets (#842)
Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
2024-01-12 11:05:46 -05:00
Wyatt VerchereandGitHub ef31c0c0da Cleans up and removes TODOs, adds tests (#844)
* removes version ordering from v2; simplifies now-unecessary three-level faceting

* resolved some todos

* test for game version updating

* merge fixes; display_categories fix
2024-01-11 21:11:27 -05:00
Wyatt VerchereandGitHub 76c885f080 Fixes incorrect loader fields (#849)
* loader_fields fix

* tested, fixed

* added direct file check for invalid file_parts

* search fixes

* removed printlns

* Adds check for loaders

* removes println
2024-01-11 18:36:01 -05:00
f16e93bd3a Raise project limit on collections from 64 to 1024 (#847)
Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
2024-01-11 10:27:38 -05:00
GeometricallyandGitHub 05d2a96900 Organization payouts (#848) 2024-01-11 10:27:15 -05:00
Julian VennenandGitHub 9c70c35669 Ignore spaces when parsing facets (#846) 2024-01-10 08:40:19 -08:00
9d54c41a2b Fix melisearch name replacements with operators other than : (#845)
* Fix melisearch name replacements with operators other than :

* Pass facet by refrence

---------

Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
2024-01-09 13:16:36 -08:00
Wyatt VerchereandGitHub 3464fbb2e8 fixes capitalization issue; modifies existing test to check for it (#841) 2024-01-09 15:42:17 -05:00
Wyatt VerchereandGitHub d51d6517be fixes modpacks losing version data on modification (#840) 2024-01-08 10:28:33 -05:00
GeometricallyandGitHub 5f6cc1281e Fix user revenue display (#839)
* Fix user revenue display

* fix rev display
2024-01-07 20:57:37 -05:00
Jai A 035fc69060 temp pause 2024-01-07 18:38:40 -05:00
GeometricallyandGitHub 34baf44534 Fix packs logic (#837)
* Fix packs logic

* Clippy
2024-01-07 18:36:42 -05:00
c3448033de Fixes missing plugin/datapack in search (#829)
* fixes datapack/plugin issue

* fixes level

* server side searching; org projects

* total hits

* total hits fixes

---------

Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
2024-01-07 16:36:26 -05:00
GeometricallyandGitHub aee9b6a951 Fix Plugin/Datapack creation and featuring (#836) 2024-01-07 15:08:28 -05:00
Wyatt VerchereandGitHub 75e5bec962 test (#828) 2024-01-07 01:47:42 -05:00
Wyatt VerchereandGitHub 59c269c8d0 Fixes game versions v2 display (#827)
* fixes game versions display

* reverses from version route
2024-01-07 01:04:13 -05:00
Wyatt VerchereandGitHub 541022cdc3 Featured filtering patch (#825) 2024-01-06 22:31:34 -05:00
Wyatt VerchereandGitHub 527521328f Fixes search not reading sidetypes (#823) 2024-01-06 16:36:26 -05:00
GeometricallyandGitHub 917b89e44f Fix org adding (#821) 2024-01-06 16:34:20 -05:00
Wyatt VerchereandGitHub 87862f3e23 changes tests to a macro (#822) 2024-01-06 14:08:03 -05:00
Wyatt VerchereandGitHub 10eed05d87 Fixes failing tests (#813)
* fixes failing  tests

* fmt clippy

* updated dockerfile

* fixes failing tests; adds important fix from extracts_versions PR

* assert_eq -> assert_status, giving better error messages

* fixed random failure bug

* fmt, clippy, etc
2024-01-05 11:20:56 -05:00
GeometricallyandGitHub f5802fee31 Staging bug fixes (#819)
* Staging bug fixes

* Finish fixes

* fix tests

* Update migration

* Update migrations

* fix side types being added for ineligible loaders

* fix tests

* Fix tests

* Finish fixes

* Add slug display names
2024-01-04 16:24:33 -05:00
GeometricallyandGitHub cf9c8cbb4f Fix deletion of private notes (#814) 2023-12-28 20:25:55 -05:00
GeometricallyandGitHub f199ecf8e9 Final release fixes (#811) 2023-12-27 13:54:17 -05:00
Wyatt VerchereandGitHub 3bdd551d40 fixes bug (#809) 2023-12-24 18:53:58 -05:00
Wyatt VerchereandGitHub 4a7936a51d adds text, fixed problem (#808) 2023-12-21 18:50:25 -08:00
Wyatt VerchereandGitHub 76e00c2432 Filtering refactoring (#806)
* switching computers

* fmt clippy sqlx prepare

* merge fixes
2023-12-21 16:36:30 -08:00
Wyatt VerchereandGitHub b46f3bf2c4 Live test fixes (#807)
* fixes

* changes
2023-12-21 15:34:42 -08:00
Wyatt VerchereandGitHub f7b4b782bf Organization ownership (#796)
* organization changes

* changes

* fixes failing test

* version changes

* removed printlns

* add_team_member comes pre-accepted

* no notification on force accept

* fixes tests

* merge fixes
2023-12-20 17:27:57 -05:00
Wyatt VerchereandGitHub 60c535e861 Misc testing improvements (#805)
* made dummy data more consistent; not an option

* fixed variable dropping issue crashing actix (?)

* removed scopes specific tests, removed schedule tests

* team routes use api

* removed printlns, fmt clippy prepare
2023-12-20 14:46:53 -05:00
d59c522f7f Sanity checked all of V2 route conversions (#803)
* follows

* all v2 routes now either convert or have a comment

* added common structs, clippy

* merge fix

---------

Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
2023-12-19 10:20:32 -08:00
9f798559cf country testing (#801)
Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
2023-12-15 12:49:55 -07:00
f939e59463 Testing bug fixes (#788)
* fixes

* adds tests- fixes failures

* changes

* moved transaction commits/caches around

* collections nullable

* merge fixes

* sqlx prepare

* revs

* lf fixes

* made changes back

* added collections update

---------

Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
2023-12-14 15:19:50 -07:00
Wyatt VerchereandGitHub 50e89ad98b fix (#800) 2023-12-13 17:08:58 -07:00
GeometricallyandGitHub c0c5978028 Bump meilisearch version (#799) 2023-12-12 21:04:40 -07:00
abbeed394e Fix project visibility in hash routes (#792)
* Fix project visibility in hash routes

* improve

* clippy

* CLIPPYYYYYYYYYYYYYY

* clippy, I hope you know that I hate you

---------

Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
2023-12-12 20:45:32 -07:00
f5b8c15388 small add (#795)
Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
2023-12-12 20:20:09 -07:00
GeometricallyandGitHub f53b6b550f Fix high memory usage during search (#798)
* Fix high memory usage during search

* Fix settings lag

* Fix clippy + fmt
2023-12-12 20:11:56 -07:00
Wyatt VerchereandGitHub 00e55b1874 get dependencies fix (#794) 2023-12-12 19:57:51 -07:00
90954dac49 Testing search prod (#791)
* testing push

* lowers it

* removed unwrap

* reduced to 500

* Really took down time

* reorders chunking

* rebuild docker

* reverted most changes

* cargo fmt

* reduced meilisearch limit

* added logs, removed deletion of index

* one client creation

* changes

* reverted gallery cahnge

* testing re-splitting again

* Remove chunking + index deletion

* Bring back chunking

* Update chunk size

---------

Co-authored-by: Jai A <jaiagr+gpg@pm.me>
Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
2023-12-11 20:01:15 -08:00
6217523cc8 Test permissions use api (#784)
* initial push

* fmt; clippy

---------

Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
2023-12-11 18:24:49 -08:00
Wyatt VerchereandGitHub 27ccd3dfa8 summary change to description (#793) 2023-12-11 18:53:30 -07:00
Wyatt VerchereandGitHub 235f4f10ef Chunking searches (#787)
* new attempt

* revised searching CTEs

* prepare fix

* fix tests

* fixes

* restructured project_item to use queries

* search changes! fmt clippy prepare

* small changes
2023-12-08 23:14:17 -07:00
GeometricallyandGitHub 945e5a2dc3 Fix semi-colon causing query to fail (#785) 2023-12-04 21:18:50 -07:00
Wyatt VerchereandGitHub 4b6a2685d0 fixed gallery mislabeled field (#783) 2023-12-04 20:44:48 -07:00
GeometricallyandGitHub e76b6c3bde Optimize country analytics (#782) 2023-12-04 19:45:17 -07:00
GeometricallyandGitHub 4630d175d7 Optimize analytics queries (#781)
* Optimize analytics queries

* fix clippy
2023-12-04 18:49:51 -07:00
Wyatt VerchereandGitHub 27055b96e3 fixes urls (#780) 2023-12-04 18:03:13 -07:00
Wyatt VerchereandGitHub 0ef96c0bca fixed issue (#778) 2023-12-04 13:51:28 -08:00
Wyatt VerchereandGitHub b2be4a7d67 Search overhaul (#771)
* started work; switching context

* working!

* fmt clippy prepare

* fixes

* fixes

* revs

* merge fixes

* changed comments

* merge issues
2023-12-03 07:27:12 -07:00
Wyatt VerchereandGitHub a70df067bc Misc v3 linear tasks (#767)
* v3_reroute 404 error

* hash change

* fixed issue with error conversion

* added new model confirmation tests
+ title name change

* renaming, fields

* owner; test changes

* clippy prepare

* fmt

* merge fixes

* clippy

* working merge

* revs

* merge fixes
2023-12-01 20:15:00 -07:00
Wyatt VerchereandGitHub 2d92b08404 V2 removal and _internal rerouting (#770)
* deleteed v3 exclusive routes

* moved routes around

* fixed linkage that movement broke

* initial merge errors

* fixes
2023-12-01 10:02:11 -08:00
Wyatt VerchereandGitHub 4bbc57b0dc Links (#763) 2023-11-30 23:14:52 -08:00
Wyatt VerchereandGitHub 756c14d988 fixed redis deserialization issue (#775) 2023-11-30 19:57:15 -08:00
Wyatt VerchereandGitHub b3b55210f7 Renamed default project type to unknown. (#774)
* small change

* plugins and datapacks now correctly return to project

* Adds to search
2023-11-30 17:17:21 -08:00
Wyatt VerchereandGitHub 58093a9438 Modifies sql queries to use CTEs (#773)
* fixes huge slowodwn on version item

* changes!

* fixes, touch ups, indices

* clippy prepare
2023-11-30 11:10:56 -08:00
Wyatt VerchereandGitHub ed33dd2127 fixed issue (#772) 2023-11-30 10:30:30 -08:00
GeometricallyandGitHub d4f9c97cca Payouts code (#765)
* push to rebase

* finish most

* finish most

* Finish impl

* Finish paypal

* run prep

* Fix comp err
2023-11-29 11:00:08 -07:00
f731c1080d Side types overhaul (#762)
* side types overhaul

* fixes, fmt clippy

* migration fix for v3 bug

* fixed migration issues

* more tested migration changes

* fmt, clippy

* bump cicd

---------

Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
2023-11-28 11:36:59 -07:00
fd18185ef0 More staging fixes (#768)
* Fixes issues

* staging fixes

* passes tests

* fixes. fmt/clippy

* drops datapack/plugin extras

* fixed failing test

---------

Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
2023-11-26 20:29:59 -07:00
CarterandGitHub 0efbbed5e2 Add fields to OAuth (#769)
* Add url and description fields to OAuthClient
model

* Add OAuth client icon editing and deleting
endpoints

* updated query data

* fix missed queries

* sqlx prep

* update with tests builds
2023-11-25 21:48:51 -07:00
bad350e49b Plugins (#758)
* plugins; datapacks

* merge fixes/changes

---------

Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
2023-11-25 18:11:38 -07:00
Wyatt VerchereandGitHub 172b93d07f Tests v2 recreate (#760)
* added common project information; setup for v2 test change

* all tests now use with_test_environment

* progress, failing

* finished re-adding tests

* prepare

* cargo sqlx prepare -- --tests

* fmt; clippy; prepare

* sqlx prepare

* adds version_create fix and corresponding test

* merge fixes; rev

* fmt, clippy, prepare

* test cargo sqlx prepare
2023-11-25 14:42:39 -07:00
Wyatt VerchereandGitHub ade8c162cd Staging fixes (#766)
* fixes bugs

* fixed more things

* fixes version creation

* small change

* removed modpack
2023-11-24 11:42:06 -08:00
79e634316d Analytics permissions (#761)
* adds test; permissions fix

* clippy

---------

Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
2023-11-21 10:02:07 -07:00
dfba6c7c91 Compiler improvements (#753)
* basic redis add

* toml; reverted unnecessary changes

* merge issues

* increased test connections

---------

Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
2023-11-19 20:10:13 -07:00
e06a77af28 Adds code coverage (#757)
* coverage initial push

* compiles on PR

* adds db env variable

* fixed env variables being on the wrong action

* added more tests yml code

* refresh

* tried copying over tests.yml

* removed accidental tests

* shotgun attempts

* generated yml

* more tries

* shotgun again

* small mistakes

* repush

* repush

* Adds env variables to tarp

* removes unused actions and tests cfg attribute on main.rs

* only will work on push to master

* changed to 60%

---------

Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
2023-11-17 17:58:15 -08:00
Wyatt VerchereandGitHub 74973e73e6 Tests 3 restructure (#754)
* moved files

* moved files

* initial v3 additions

* moves req data

* tests passing, restructuring, remove v2

* fmt; clippy; prepare

* merge conflicts + issues

* merge conflict, fmt, clippy, prepare

* revs

* fixed failing test

* fixed tests
2023-11-16 11:36:03 -07:00
GeometricallyandGitHub ac07ac5234 Fix date parsing for partial versions (#15) 2023-11-15 16:35:33 -07:00
Wyatt VerchereandGitHub f4880d0519 more games information, games route (#756)
* more games information, games route

* adds banner url
2023-11-14 11:01:31 -07:00
Wyatt VerchereandGitHub 375f992a0c Adds ordering on loader fields enum (#755)
* now sorts on ordering, fmt clippy prepare

* fixed tests

* removed accidenetal printlns
2023-11-13 19:19:06 -07:00
ae1c5342f2 Search test + v3 (#731)
* search patch for accurate loader/gv filtering

* backup

* basic search test

* finished test

* incomplete commit; backing up

* Working multipat reroute backup

* working rough draft v3

* most tests passing

* works

* search v2 conversion

* added some tags.rs v2 conversions

* Worked through warnings, unwraps, prints

* refactors

* new search test

* version files changes fixes

* redesign to revs

* removed old caches

* removed games

* fmt clippy

* merge conflicts

* fmt, prepare

* moved v2 routes over to v3

* fixes; tests passing

* project type changes

* moved files over

* fmt, clippy, prepare, etc

* loaders to loader_fields, added tests

* fmt, clippy, prepare

* fixed sorting bug

* reversed back- wrong order for consistency

* fmt; clippy; prepare

---------

Co-authored-by: Jai A <jaiagr+gpg@pm.me>
2023-11-11 16:40:10 -08:00
CarterandGitHub 97ccb7df94 Knossos Oauth 2 Flow Changes (#752)
* adjust type and response format

* Replace Found with Ok for handled redirects

* scope parse fix

* change apps query from body to query

* adjust tests for new response type

* remove unused imports

* Clippy fixes
2023-11-11 10:42:01 -07:00
GeometricallyandGitHub a818199b5a fix download counts for ipv4 ips (#750) 2023-11-08 20:55:08 -07:00
GeometricallyandGitHub 6fe1fa3455 Fix crash when MC updates (#14)
* Fix crash when MC updates

* Fix fabric ordering + add neoforge support

* run clippy

* run clippy
2023-11-08 17:57:37 -07:00
GeometricallyandGitHub aab95444a8 Fix download counts (#746) (#747)
* Fix download counts (#746)

* Fix download counts

* remove unsafe send

* update indexing time

* run prep

* run prep again
2023-11-06 15:04:32 -07:00
Wyatt VerchereandGitHub 40f28be3b4 rounds dates for revenue (#745)
* rounds dates for revenue

* analytics tests pass
2023-11-03 21:03:30 -07:00
Jackson KrugerandGitHub 911d442340 Version ordering [MOD-551] (#740)
* Version ordering

* cargo sqlx prepare

* Use version ordering for maven

* Use version ordering when sorting versions in Rust (not just SQL)

* Thanks clippy
2023-11-01 09:36:39 -07:00
Jackson KrugerandGitHub d5594b03e3 Fix organizations route typo (#743) 2023-10-31 10:58:05 -05:00
GeometricallyandGitHub 89f1ddf4d7 Route to view user's orgs (#742) 2023-10-30 16:59:53 -07:00
Jackson KrugerandGitHub 6cfd4637db OAuth 2.0 Authorization Server [MOD-559] (#733)
* WIP end-of-day push

* Authorize endpoint, accept endpoints, DB stuff for oauth clients, their redirects, and client authorizations

* OAuth Client create route

* Get user clients

* Client delete

* Edit oauth client

* Include redirects in edit client route

* Database stuff for tokens

* Reorg oauth stuff out of auth/flows and into its own module

* Impl OAuth get access token endpoint

* Accept oauth access tokens as auth and update through AuthQueue

* User OAuth authorization management routes

* Forgot to actually add the routes lol

* Bit o cleanup

* Happy path test for OAuth and minor fixes for things it found

* Add dummy data oauth client (and detect/handle dummy data version changes)

* More tests

* Another test

* More tests and reject endpoint

* Test oauth client and authorization management routes

* cargo sqlx prepare

* dead code warning

* Auto clippy fixes

* Uri refactoring

* minor name improvement

* Don't compile-time check the test sqlx queries

* Trying to fix db concurrency problem to get tests to pass

* Try fix from test PR

* Fixes for updated sqlx

* Prevent restricted scopes from being requested or issued

* Get OAuth client(s)

* Remove joined oauth client info from authorization returns

* Add default conversion to OAuthError::error so we can use ?

* Rework routes

* Consolidate scopes into SESSION_ACCESS

* Cargo sqlx prepare

* Parse to OAuthClientId automatically through serde and actix

* Cargo clippy

* Remove validation requiring 1 redirect URI on oauth client creation

* Use serde(flatten) on OAuthClientCreationResult
2023-10-30 09:14:38 -07:00
Jackson KrugerandGitHub 8803e11945 Upgrade to sqlx 0.7.2 (#736)
* Update to sqlx 0.7.2

* Somehow missed one (and remove queries from other branch)
2023-10-23 14:30:39 -05:00
GeometricallyandGitHub b1ca2cc2b6 Fix quilt + fabric intermediary syncing issues (#13)
* Fix quilt + fabric intermediary syncing issues

* fix comp

* Fix random versions not working/updating meta
2023-10-21 13:09:21 -07:00
GeometricallyandGitHub 9a8f3d7bad Fix analytics routes + add revenue route (#734) 2023-10-19 09:42:49 -07:00
Wyatt VerchereandGitHub 9d0e762f36 More tests (#729)
* permissions tests

* finished permissions; organization tests

* clippy, fmt

* post-merge fixes

* teams changes

* refactored to use new api

* fmt, clippy

* sqlx prepare

* revs

* revs

* re-tested

* re-added name

* reverted to matrix
2023-10-17 00:53:10 -07:00
Jackson KrugerandGitHub abf4cd71ba Add redis caching to getting user notifications and projects [MOD-540] (#723)
* Add redis caching to getting a user's project ids

* Run `cargo sqlx prepare` to update the sqlx-data.json

* Add redis caching for getting user notifications

* Fix new clippy warnings

* Remove log that shouldn't have been committed

* Batch insert of notifications (untested)

* sqlx prepare...

* Fix merge conflict things and use new redis struct

* Fix bug with calling delete_many without any elements (caught by tests)

* cargo sqlx prepare

* Add tests around cache invalidation (and fix bug they caught!)

* Some test reorg based on code review suggestions
2023-10-12 15:52:24 -07:00
GeometricallyandGitHub d66270eef0 Remove scheduling webhook check (#728) 2023-10-12 11:21:26 -07:00
07ecd13554 Switch to Trolley for Modrinth Payments (#727)
* most of trolley

* Switch to trolley for payments

* run prepare

* fix clippy

* fix more

* Fix most tests + bitflags

* Update src/auth/flows.rs

Co-authored-by: Jackson Kruger <jak.kruger@gmail.com>

* Finish trolley

* run prep for merge

* Update src/queue/payouts.rs

Co-authored-by: Jackson Kruger <jak.kruger@gmail.com>

---------

Co-authored-by: Jackson Kruger <jak.kruger@gmail.com>
2023-10-11 15:55:01 -07:00
f1ff88f452 fix maven filters for versions with dashes (#725)
Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
2023-10-11 14:01:32 -07:00
Jackson KrugerandGitHub d92272ffa0 Batch inserts [MOD-555] (#726)
* Batch a bunch of inserts, but still more to do

* Insert many for clickhouse (+ tests)

* Batch the remaining ones except those requiring deduplication

* Risky dedups

* Bit o cleanup and formatting

* cargo sqlx prepare

* Add test around batch editing project categories

* Add struct to satisfy clippy

* Fix silly mistake that was caught by the tests!

* Leave room for growth in dummy_data
2023-10-11 11:32:58 -07:00
Jackson KrugerandGitHub dfa43f3c5a Add /metrics endpoint for Prometheus (#724) 2023-10-06 15:58:02 -07:00
Wyatt VerchereandGitHub 259c5ef3d0 Tests (#719)
* computer switch

* some fixes; github action

* added pr to master

* sqlx database setup

* switched intial GHA test db

* removed sqlx database setup

* unfinished patch route

* bug fixes + tests

* more tests, more fixes, cargo fmt

* merge fixes

* more tests, full reorganization

* fmt, clippy

* sqlx-data

* revs

* removed comments

* delete revs
2023-10-06 09:57:33 -07:00
a1b59d4545 Organizations (#712)
* untested, unformatted, un-refactored

* minor simplification

* simplification fix

* refactoring, changes

* some fixes

* fixes, refactoring

* missed cache

* revs

* revs - more!

* removed donation links; added all org members to route

* renamed slug to title

---------

Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
2023-10-02 10:56:57 -07:00
Emma Alexia TriphoraandGitHub 58a61051b9 Don't show processing projects in thread message inbox (#722)
They already show up in the Review tab, so, no reason to also have them in the Messages tab
2023-10-01 13:22:29 -07:00
Emma Alexia TriphoraandGitHub 51777c3f33 Replace moderation project queue with moderation action log (#718) 2023-09-24 12:04:58 -04:00
Emma Alexia TriphoraandGitHub 3767e9fae9 Only show mod files in pack external dependencies (#710) 2023-09-24 11:49:41 -04:00
Emma Alexia TriphoraandGitHub 4bbfc8ccc5 Add NeoForge modpack and autoupdater support (#707) 2023-09-24 11:40:03 -04:00
Wyatt VerchereandGitHub 531b8214c0 Analytics query (#716) 2023-09-24 11:22:35 -04:00
Emma Alexia TriphoraandGitHub 5cab618bf7 Fix reports sometimes being broken on the mod dashboard (#717) 2023-09-24 08:04:18 -05:00
Wyatt VerchereandGitHub 3380f4d11c downloads route (#713) 2023-09-20 08:11:09 -07:00
Wyatt VerchereandGitHub 4bf030993a Collections users route (#711)
* users route

* Added user route

* collections
2023-09-18 11:43:58 -07:00
Emma Alexia TriphoraandGitHub f65f949a36 Fix issue with thread messages not being sent to moderators on non-processing projects (#705) 2023-09-15 15:39:03 -04:00
Julian VennenandGitHub 2864abd8c2 Add created and modified timestamp to search facets (#708) 2023-09-15 15:29:16 -04:00
Wyatt VerchereandGitHub 9bd2cb3c7e Collections (#688)
* initial draft; unfinished

* images, fixes

* fixes

* println

* revisions

* fixes

* alternate context setup version

* rev

* partial revs

* rev

* clippy ,fmt

* fmt/clippy/prepare

* fixes

* revs
2023-09-13 22:22:32 -07:00
35cd277fcf analytics (#695)
* playtime

* other routes, improvements

* fmt clippy

* revs

---------

Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
2023-09-13 16:35:47 -07:00
GeometricallyandGitHub 57b1932b5e Fix minecraft meta links (#12)
* Fix minecraft meta links

* remove debug

* bump v

* no mut
2023-09-12 14:19:24 -04:00
Emma Alexia TriphoraandGitHub e6818023a3 Allow moderators to edit version info (#703)
* Allow moderators to edit version info

* Clippy fix
2023-09-11 14:21:50 -04:00
BasiqueEvangelistandGitHub 35a541f99b Maven version filters for duplicate version numbers (#625) 2023-09-10 12:59:10 -04:00
5fb00a947c Allow BungeeCord plugins that use bungee.yml (#698)
Co-authored-by: Emma Alexia Triphora <emma@modrinth.com>
2023-09-10 12:49:35 -04:00
Modrinth BotandGitHub 6288f679b9 [no ci] synced file(s) with modrinth/.github (#706) 2023-09-10 12:45:44 -04:00
GeometricallyandGitHub e766759b8c Fix scheduling bug (#693)
* Fix scheduling bug

* Fix comp err
2023-08-23 12:08:18 -04:00
Jai A c1d28381e8 actually bump version 2023-08-21 14:36:43 -04:00
GeometricallyandGitHub 2fa8371bae Neoforge support (#11) 2023-08-21 14:34:22 -04:00
a1cfdf1a5b Socket cleanup (#682)
* testing changes

* added success

* removed success

* Fix compile error

---------

Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
Co-authored-by: Jai A <jaiagr+gpg@pm.me>
2023-08-21 14:21:05 -04:00
e9c7f5d664 Fix #683 (#691)
* Fix #683

* whitespace

---------

Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
2023-08-21 10:44:09 -04:00
c85f12fe2c Fix closing reports not marking the report as closed (#690)
Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
2023-08-21 10:43:06 -04:00
GeometricallyandGitHub 13e5644c89 Fix creator payouts + scheduler (#686) 2023-08-21 10:42:33 -04:00
Sasha SorokinandGitHub eac029aef4 Webhook emojis update (#685) 2023-08-20 16:33:36 -04:00
GeometricallyandGitHub a5195920fa update validators (#678) 2023-08-08 14:27:38 -07:00
GeometricallyandGitHub 5676a13290 Fix sponge validator (#679) 2023-08-08 13:20:15 -07:00
GeometricallyandGitHub d11f0e864e gv loader cache bring back (#677)
* gv loader cache bring back

* run prep fmt
2023-08-08 10:14:49 -07:00
GeometricallyandGitHub df83fcc5b9 Optimizations (#676) 2023-08-07 23:05:08 -07:00
GeometricallyandGitHub f21c756793 Attempt to fix db timeouts (#674) 2023-08-07 12:37:29 -07:00
4b07ee2fa8 Added Paper Plugins support (#673)
Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
2023-08-07 11:53:21 -07:00
JamalamandGitHub ae3a39ee65 remove 'source file' validation (#671) 2023-08-07 11:41:49 -07:00
GeometricallyandGitHub e9f5bd4ac1 Fix redis pool timeout (#669)
* Fix redis pool timeout

* remove search dep project issues

* run fmt + prep
2023-08-06 15:34:03 -07:00
GeometricallyandGitHub 1f4ad732fd fix analytics route not working (#668) 2023-08-05 12:07:38 -07:00
GeometricallyandGitHub 5637d37ee1 fix updates route (#667) 2023-08-04 22:52:17 -07:00
GeometricallyandGitHub c370da2fef Fix version deps (#666) 2023-08-04 22:15:19 -07:00
GeometricallyandGitHub d168209721 put assets in docker (#665) 2023-08-04 20:20:05 -07:00
GeometricallyandGitHub ca0468b8d5 Auth fixes (#664)
* Auth fixes

* destroy flows after use

* fix comp err

* add bearer err msg
2023-08-04 16:22:15 -07:00
GeometricallyandGitHub 039d26feeb Add launcher analytics (#661)
* Add more analytics

* finish hydra move

* Finish websocket flow

* add minecraft account flow

* Finish playtime vals + payout automation
2023-08-02 14:43:04 -07:00
GeometricallyandGitHub 10e7b66f38 Fix loader ordering (#10) 2023-07-28 16:22:38 -07:00
GeometricallyandGitHub 4bb47d7e01 Finish authentication (#659) 2023-07-18 15:02:54 -07:00
GeometricallyandGitHub ec80c2b9db Next auth fixes (#658) 2023-07-14 22:55:00 -07:00
GeometricallyandGitHub a89418e33b First auth fixes (#656) 2023-07-13 19:50:42 -07:00
GeometricallyandGitHub 0d88ff8dae Verify Email + Reset Password flows (#654)
* verifiers

* add missing emails

* fix gh perms
2023-07-12 20:40:24 -07:00
GeometricallyandGitHub 4bdf9bff3a 2FA + Add/Remove Auth Providers (#652)
* 2FA + Add/Remove Auth Providers

* fix fmt issue
2023-07-11 19:13:07 -07:00
GeometricallyandGitHub 7fbb8838e7 Scoped PATs (#651)
* Scoped PATs

* fix threads issues

* fix migration
2023-07-10 16:44:40 -07:00
GeometricallyandGitHub 366ea63209 Fix session del (#650) 2023-07-08 22:07:11 -07:00
GeometricallyandGitHub 6c0ad7fe1a Sessions Route + Password Auth (#649)
* Sessions Route + Password Auth

* run prep + fix clippy

* changing passwords + logging in

* register login
2023-07-08 14:29:17 -07:00
ef9c90a43a [no ci] synced local '.github/ISSUE_TEMPLATE/' with remote 'issue_templates/' (#645)
Co-authored-by: modrinth-bot <null>
Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
2023-07-08 14:11:30 -07:00
GeometricallyandGitHub 239214ef92 Initial Auth Impl + More Caching (#647)
* Port redis to staging

* redis cache on staging

* add back legacy auth callback

* Begin work on new auth flows

* Finish all auth flows

* Finish base session authentication

* run prep + fix clippy

* make compilation work
2023-07-07 12:20:16 -07:00
EmmaandGitHub b0057b130e [no ci] Sync issue templates (#641) 2023-06-21 19:32:36 -04:00
Wyatt VerchereandGitHub d64c043838 sends own addr to auth callback (#639)
* sends own addr to auth callback

* shouldn't have http on local

* actually, both should have

* changed for consistency
2023-06-15 16:12:02 -07:00
Wyatt VerchereandGitHub dd3599f5b3 removed route (#637)
* removed route

* email change support
2023-06-13 14:43:20 -07:00
GeometricallyandGitHub 0d56127758 Add new mojang args (#9) 2023-06-11 13:40:31 -07:00
GeometricallyandGitHub ea043517c5 Fix version file visibility (#630)
* Fix version file visibility

* add missing

* update prepare
2023-06-08 20:25:03 -07:00
Wyatt VerchereandGitHub b84d9c5d55 github token support (#629)
* github token support

* sqlx-data

* renamed github, modrinth tokens

* removed prints
2023-06-08 16:35:12 -07:00
GeometricallyandGitHub 6c628afe5d Auto doing fixes (#8) 2023-06-02 16:13:00 -07:00
GeometricallyandGitHub abc99c7e69 Fix report + mod deletion (#626) 2023-06-01 08:53:16 -07:00
fe25cd3bec Minos push (#589) (#590)
* Minos push (#589)

* moving to other computer

* working redirection

* incomplete pat setup

* no more errors

* new migrations

* fixed bugs; added user check

* pats

* resized pats

* removed testing callback

* lowered kratos_id size

* metadata support

* google not working

* refactoring

* restructured github_id

* kratos-id optional, legacy accounts connect

* default picture

* merge mistake

* clippy

* sqlx-data.json

* env vars, clippy

* merge error

* scopes into an i64, name

* requested changes

* removed banning

* partial completion of github flow

* revision

---------

Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
2023-05-31 16:03:08 -07:00
ProspectorandGitHub 2eb51edfb6 Merge pull request #623 from modrinth/reports-fixes
Reports fixes
2023-05-25 12:53:40 -07:00
GeometricallyandGitHub 715d564028 Merge branch 'master' into reports-fixes 2023-05-25 12:36:13 -07:00
Jai A 989b704efc Reports fixes 2023-05-25 15:27:15 -04:00
triphoraandGitHub 0bfaeb8521 Update Discord webhook emojis (#592)
Fixes #569
2023-05-19 11:04:40 -07:00
GeometricallyandGitHub 3db00534c2 Fix launcher overrides (#7)
* Fix launcher overrides

* Remove none set

* Patch to work with legacy versions

* remove mc
2023-05-12 20:42:18 -07:00
triphoraandGitHub b713b324f9 Fix incorrect response to invalid input on bulk edit route (#579) 2023-05-06 10:45:29 -04:00
GeometricallyandGitHub 6512dbae1c Merge pull request #6 from modrinth/quilt-m1-support
Support for ARM + Quilt
2023-04-26 12:05:46 -07:00
Jai A 3afda71349 fix some data 2023-04-26 10:41:43 -07:00
Jai A 568e5a9bb8 run fmt 2023-04-25 19:37:56 -07:00
Jai A 89e56ae279 Support for ARM + Quilt 2023-04-25 19:36:21 -07:00
GeometricallyandGitHub 339ac05443 Fix TM pending in project route (#584) 2023-04-22 17:44:51 -07:00
GeometricallyandGitHub 72cfa683cf Fix edit bug staging (#582)
* Fix edit bug staging

* Fix comp err

* Fix mod message bug

* Fix compile bug

* Run fmt

* Fix other bug
2023-04-21 13:55:50 -07:00
GeometricallyandGitHub 3a6b9f04f9 Fix reports creation (#580) 2023-04-21 11:10:57 -07:00
GeometricallyandGitHub 59f24df294 Add dependencies to search (#578)
* Add dependencies to search

* add attrs for faceting

* run prepare

* Add user data route from token

* update to 24hrs

* Fix report bugs
2023-04-20 16:38:30 -07:00
GeometricallyandGitHub 5c559af936 Fix project scheduling (#576)
* Fix project scheduling

* fix comp

* Fix again

* Fix compile err

* Fix compile err

* Fix compile err

* fix random err
2023-04-18 17:12:44 -07:00
GeometricallyandGitHub bb80505b76 Return pending TMs, fix notifs serde (#575)
* Return pending TMs, fix notifs serde

* fix compile
2023-04-18 14:16:41 -07:00
GeometricallyandGitHub a560f6e9f6 Monetization status, additional files fix, deps fix (#574) 2023-04-16 20:03:53 -07:00
GeometricallyandGitHub 95ae981698 Overhaul notifs + threads fixes (#573)
* Overhaul notifs + threads fixes

* fix lang
2023-04-15 19:48:21 -07:00
GeometricallyandGitHub 969eb67217 Add replies, private notes, get many threads (#572)
* Add replies, private notes, get many threads

* register multiple route

* filter out moderators in threads
2023-04-13 15:04:08 -07:00
ProspectorandGitHub 0dfebbad9d Merge pull request #571 from modrinth/threads-fixes
more threads fixes
2023-04-13 10:18:28 -07:00
Jai A f87f4bd8cc more threads fixes 2023-04-13 09:54:03 -07:00
GeometricallyandGitHub 352caa85da Fix messages not showing (#570) 2023-04-12 21:18:03 -07:00
8f61e9876f Add report + moderation messaging (#567)
* Add report + moderation messaging

* Add system messages

* address review comments

* Remove ds store

* Update messaging

* run prep

---------

Co-authored-by: Geometrically <geometrically@Jais-MacBook-Pro.local>
2023-04-12 17:59:43 -07:00
GeometricallyandGitHub fd19bb7cd5 Merge pull request #4 from modrinth/many-fixes-again
Fix forge syncing not working
2023-04-05 12:40:17 -07:00
Jai A 0c2e9137a2 Update id merging 2023-04-05 12:39:16 -07:00
Jai A bf5a25a96f fmt + clippy 2023-04-05 12:02:47 -07:00
Jai A aa84f21fde Fix forge syncing not working 2023-04-05 12:02:00 -07:00
GeometricallyandGitHub b9de2b4b58 Merge pull request #3 from modrinth/many-fixes
Many fixes
2023-04-04 21:17:41 -07:00
Jai A 9754f2d1c5 Add limiter for forge downloading 2023-04-04 21:17:19 -07:00
Jai A f66fc06b4f fix debug stuff 2023-04-04 20:26:10 -07:00
Jai A 79ceb56c60 Fix issues 2023-04-04 20:25:17 -07:00
triphoraandGitHub 7605df1bd9 Fix some routes not working (#566) 2023-03-23 09:39:46 -07:00
GeometricallyandGitHub b91ec48178 Fix notification spam (#565) 2023-03-17 09:36:12 -07:00
triphoraandGitHub 3c2f144795 Perses finale (#558)
* Move v2 routes to v2 module

* Remove v1 routes and make it run

* Make config declaration consistent, add v3 module

* Readd API v1 msgs

* Fix imports
2023-03-16 11:56:04 -07:00
0271337f8e Fixed an issue with colliding slugs when modifying a project (#562)
* Fixed an issue with colliding slugs when modifying a project

* Update projects.rs

---------

Co-authored-by: triphora <emma@modrinth.com>
2023-03-14 15:06:33 -07:00
GeometricallyandGitHub 630a71c46c Queue Dates + Warnings, some cleanup (#549)
* Queue Dates + Warnings, some cleanup

* Fix ping

* Fix repeated discord messaging

* Fix compile error + run fmt
2023-03-14 14:48:46 -07:00
GeometricallyandGitHub 150329dd4a Fix GV + Loader syncing on version create (#564)
* Fix GV + Loader syncing on version create

* Update rustc v
2023-03-12 16:42:25 -07:00
triphoraandGitHub 59d7bce518 Actually fix pagination (#557) 2023-03-04 08:53:26 -07:00
Magnus JensenandGitHub 3c1e3cd38e Fix version name can be empty string (#537) 2023-03-03 18:20:04 -05:00
triphoraandGitHub a2eb0bf9fe Add license name to license_text response (#555) 2023-03-03 18:09:27 -05:00
Orchid system (Emma)andGitHub 5d48ecf86a Project Perses (API v1 yeetenings): parts 4 and 5 (#554)
* Project Perses (API v1 yeetenings): parts 4 and 5

Resolves MOD-219
Resolves MOD-220

* Note to Emma: Please test your stuff
2023-03-02 10:50:46 -07:00
GeometricallyandGitHub 00d09aa01e Housekeeping + Fix DB perf issues (#542)
* Housekeeping + fix db perf issues

* run prep
2023-02-22 16:11:14 -07:00
9afdc55416 Add project color to embed accent color (#522)
Closes #521

Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
2023-02-21 17:44:18 -07:00
GeometricallyandGitHub 2c942c8809 Fix limit ordering (#543) 2023-02-16 14:24:57 -07:00
c15acc4ce3 Add API V1 flickers (#541)
Co-authored-by: triphora <emmaffle@modrinth.com>
2023-02-15 17:05:28 -05:00
GeometricallyandGitHub b056610eaa Version slugs (#533)
* Version slugs

* Get rid of new field, finish it up
2023-02-15 13:38:37 -07:00
8eb9fb1834 fix slug colliding error not showing when editing project (#534)
* fix slug colliding error not showing when editing project

* format

* re-introduce old code

* run sqlx prepare

---------

Co-authored-by: MagnusHJensen <magnus.holm.jensen@lego.dk>
Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
2023-02-15 10:41:00 -07:00
7ec518b41c update index to use approval date if set (#540)
Co-authored-by: MagnusHJensen <magnus.holm.jensen@lego.dk>
Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
2023-02-15 10:20:34 -07:00
dc15914a85 chore(validate): update error description to space actual error (#538)
Co-authored-by: MagnusHJensen <magnus.holm.jensen@lego.dk>
2023-02-15 09:40:32 -07:00
GeometricallyandGitHub 3b22f59988 Fix ariadne req URL (#531) 2023-02-01 11:17:46 -07:00
GeometricallyandGitHub afdab0300e Update Analytics req + Fix versions list (#529)
* Update Analytics req + Fix versions list

* Fix sentry support

* Fix lint
2023-02-01 10:08:02 -07:00
GeometricallyandGitHub 26533c47e7 Add loaders + game versions param to mods (#528) 2023-01-27 19:24:40 -07:00
triphoraandGitHub df3aeed291 Add scary warning for people still using API v1 (#525)
* Add scary warning for people still using API v1

* change [] brackets to headers
2023-01-17 15:01:26 -07:00
GeometricallyandGitHub 867ba7b68f Fix various issues (#524)
* Fix various issues

* Fix multipart body hang

* drop req if error

* Make multipart errors more helpful
2023-01-16 16:45:19 -07:00
triphoraandGitHub 1679a3f844 Fix file uploading for admins (#519) 2023-01-07 19:35:40 -07:00
triphoraandGitHub 1611049623 Fix query params giving plain text error (#509) 2023-01-05 17:14:39 -05:00
GeometricallyandGitHub 7d195367a8 Bulk Editing + Random Projects Route (#517)
* Bulk Editing + Random Projects Route

* Run fmt + clippy + prepare

* Remove license_url
2023-01-04 19:23:47 -07:00
GeometricallyandGitHub 88a4f25689 Required slugs (#516) 2022-12-30 20:03:13 -07:00
161dee89ec Add notification for project status updates (#511)
* Add notification for project status updates

* aaaaaa

* cargo sqlx prepare

* use friendly name of statuses

* Update src/models/projects.rs

Co-authored-by: triphora <emma@modrinth.com>

* only send notifications to accepted users

* only send notifications for people not on the team

* cargo sqlx prepare

* !=

* fully address pr comments

Co-authored-by: triphora <emma@modrinth.com>
Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
2022-12-30 14:40:00 -07:00
GeometricallyandGitHub 34af33607b Update file restrictions, download counting, project colors, etc (#515)
* Update file restrictions, download counting, project colors, etc

* Run fmt + clippy + prepare
2022-12-30 13:56:41 -07:00
GeometricallyandGitHub 5bb188a822 Project Colors (#512)
* Inital tests

* Finish project colors

* Run fmt + clippy + prepare

* Fix dp+rp fmting
2022-12-29 17:20:50 -07:00
GeometricallyandGitHub 60bb6f105d Nullable file types (#514) 2022-12-29 09:32:46 -07:00
GeometricallyandGitHub df4680ee09 Fix data pack webhook (#513) 2022-12-29 09:13:39 -07:00
6aaab09601 Fix file type setting (#510)
Co-authored-by: triphora <emmaffle@modrinth.com>
2022-12-26 13:21:37 -07:00
GeometricallyandGitHub 5da42575fd Add validator for file types + datapacks (#507)
* Add validator for file types + datapacks

* Make it compile
2022-12-23 19:49:47 -07:00
GeometricallyandGitHub fe256d6a62 File types (#506)
* File types

* Run prepare + fmt

* Switch to struct

* Update docker version
2022-12-23 16:36:53 -07:00
triphoraandGitHub 5f175141e1 Improve error messages for report with invalid input (#505) 2022-12-23 15:24:45 -05:00
triphoraandGitHub 983e2df065 Miscellaneous improvements and removals (#502) 2022-12-23 15:19:15 -05:00
triphoraandGitHub 16d5a70c08 Add ordering to categories, gallery images, and team members (#501) 2022-12-23 14:34:04 -05:00
Jai A b7e2d7fb8e Update copyright 2022-12-22 20:14:26 -07:00
Jai A fb5f7a336d bump rust version in container (2) 2022-12-22 20:12:05 -07:00
Jai A 78dc5f4bf4 bump rust version in container 2022-12-22 20:11:22 -07:00
Jai A 45dbf5393f Bump versions + switch AWS/S3 library 2022-12-22 19:01:41 -07:00
GeometricallyandGitHub 9fed1cde25 Fix webhook again (#499) 2022-12-09 12:29:16 -07:00
GeometricallyandGitHub 5c7b175e90 Webhook update (#498)
* Update webhook

* Run clippy
2022-12-08 19:44:46 -07:00
GeometricallyandGitHub 30b29de8ce Fix statuses again (#497)
* Fix statuses again

* Make it compile
2022-12-08 17:25:24 -07:00
GeometricallyandGitHub a5f9331023 Fix hashes not showing (#496)
* Fix hashes not showing

* Run prepare + fmt
2022-12-08 15:42:59 -07:00
GeometricallyandGitHub d8b9d8431e Shader fixes (#495)
* Shader fixes

* Add core shaders validator

* Update validator again

* Rename shaders

* Fix build
2022-12-08 15:13:01 -07:00
GeometricallyandGitHub 91a2ce2b3f Switch out references of 'TO_JSONB' (#494) 2022-12-07 23:30:41 -07:00
GeometricallyandGitHub 4da1871567 Public Webhook Fixes (#493)
* Public discord webhook

* Switch to jsonb for most queries + make gallery featured first

* Run fmt + clippy + prepare
2022-12-07 09:56:53 -07:00
GeometricallyandGitHub e809f77461 Public discord webhook (#492) 2022-12-06 19:51:03 -07:00
GeometricallyandGitHub e96d23cc3f Initial work on new status sys + scheduling releases (#489)
* Initial work on new status sys + scheduling releases

* Finish project statuses + begin work on version statuses

* Finish version statuses

* Regenerate prepare

* Run fmt + clippy
2022-12-06 09:14:52 -07:00
GeometricallyandGitHub c34e2ab3e1 Fix team member splits being set to 0 by default (#490) 2022-12-02 10:32:17 -07:00
triphoraandGitHub 820519b4f7 Move to SPDX licenses (#449)
* Move to SPDX licenses

Found a way to do this without breaking API compat, so here it is, instead of waiting for v3

Resolves MOD-129
Resolves #396

* License URL updates

* what was I thinking

* Do a thing

* Add open source filter

* Remove dead imports

* Borrow

* Update 20220910132835_spdx-licenses.sql

* Add license text route

* Update migration

* Address comments
2022-11-29 21:53:24 -07:00
triphoraandGitHub 34688852a4 Remove redundant files (#488) 2022-11-26 19:11:56 -07:00
GeometricallyandGitHub 151f28081a [skip ci] Add security notice (#486)
* [skip ci] Add security notice

* Fix grammar
2022-11-24 22:13:31 -07:00
GeometricallyandGitHub 213a64b1ff FlameAnvil fixes (#484) 2022-11-21 18:59:21 -07:00
GeometricallyandGitHub f259d81249 FlameAnvil Project Sync (#481)
* FlameAnvil Project Sync

* Perm fixes

* Fix compile

* Fix clippy + run prepare
2022-11-20 19:50:14 -07:00
triphoraandGitHub 589761bfd9 Statistics route (#453)
* Statistics route

Staging: https://i.imgur.com/YWx9uPA.png

* Remove users

* Address comment
2022-11-20 15:35:22 -07:00
18fde86a20 Fix #464 (#467)
Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
2022-11-19 17:54:56 -07:00
ba28bc94d3 Fix all default clippy warnings (#480)
All trivial, fixes were for:
 - #[deny(clippy::if_same_then_else)]
 - #[warn(clippy::explicit_auto_deref)]
 - #[warn(clippy::bool_assert_comparison)]

Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
2022-11-17 10:09:53 -07:00
da19a07943 Payouts fees changes (#478)
* Payouts fees changes

* Update src/queue/payouts.rs

Co-authored-by: triphora <emmaffle@modrinth.com>

Co-authored-by: triphora <emmaffle@modrinth.com>
2022-11-16 13:46:36 -07:00
GeometricallyandGitHub ecc500fc91 Fix payouts bug with decimals (#477) 2022-11-11 10:04:46 -07:00
GeometricallyandGitHub c22ac1e60a Support unenrolling from payouts (#476) 2022-11-09 16:01:10 -07:00
GeometricallyandGitHub 55d9aa2a4c Allow owner payout split to be edited (#475) 2022-11-08 19:15:30 -07:00
GeometricallyandGitHub 1d391e68e5 Better ser/deser for payouts vals (#474) 2022-11-08 14:14:07 -07:00
GeometricallyandGitHub 0429c44d18 Fix payouts conditions (#473)
* Fix payouts conditions

* Make it build
2022-11-07 20:17:44 -07:00
GeometricallyandGitHub 2c1bcaafc1 Use auto payments with paypal (#472)
* Use auto payments with paypal

* Remove sandbox key
2022-11-07 15:38:25 -07:00
GeometricallyandGitHub 35891c74cd Final fixes payouts (#471)
* Final fixes payouts

* add minimum payout
2022-11-01 09:53:43 -07:00
GeometricallyandGitHub 2ca6e67b37 Payouts finish (#470)
* Almost done

* More work on midas

* Finish payouts backend

* Update Cargo.lock

* Run fmt + prepare
2022-10-30 23:34:56 -07:00
GeometricallyandGitHub 6e72be54cb R2 impl (#466)
* Add Cloudflare R2 impl

* Bump actix version

* Fix sec issues
2022-10-22 21:23:31 -07:00
GeometricallyandGitHub 07edb998e4 Fix integration with backblaze API (#461)
* Fix integration with backblaze API

* Remove keys (already reset, dw)
2022-10-09 19:10:06 -07:00
GeometricallyandGitHub 3e52f804a7 More reasonable length restrictions (#458) 2022-09-26 18:09:50 -07:00
GeometricallyandGitHub 75b7583832 Increase dependency limit (#454) 2022-09-21 21:27:02 -07:00
wafflecoffeeandGitHub d754eb74f7 Ignore any dependencies set manually for modpacks (#433)
* Ignore any dependencies set manually for modpacks

* actually build
2022-09-17 13:17:32 -07:00
wafflecoffeeandGitHub 60252267d5 Add slug to searchable attributes and add project_id to facets (#447)
Fixes #358

Adding project_id to the facets would allow the inclusion or exclusion of individual projects from search. For example, this would allow people to be able to exclude projects which they've already followed or are not interested in. My personal vision for this is to merge the [followed projects page](https://modrinth.com/settings/follows) into search itself.
2022-09-05 21:39:50 -07:00
GeometricallyandGitHub b25af641e2 Fix gallery date display (#445)
* Fix gallery date display

* Fix approved date not setting
2022-09-04 13:37:11 -07:00
GeometricallyandGitHub e7c3f8bf47 Initial work on payouts (badges, perms, splits) (#440)
* Initial work on payouts (badges, perms, splits)

* Fix clippy error, bitflag consistency
2022-09-02 12:38:58 -07:00
4c1dca73c4 Replace remaining icon.ext image paths with hash (#435)
Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
2022-09-02 12:19:39 -07:00
moozandGitHub 0bbb6b91fe Add plugin.yml support for bungeecord & waterfall (#438) 2022-09-02 08:39:58 -07:00
ee93d9b495 Sort version files and mods on user profile (#432)
* ???

* Moardering fixes

Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
2022-08-28 21:54:42 -07:00
GeometricallyandGitHub bf8ac214a1 Attempt to fix multipart errors (#436) 2022-08-27 15:18:25 -07:00
GeometricallyandGitHub 9c7b34d5e6 Apply additional categories fix to search indexing (#428)
* Apply additional categories fix to search indexing

* fix edit version validator
2022-08-20 22:34:00 -07:00
GeometricallyandGitHub 76c0fa2fe2 Remove database requirement (#427) 2022-08-17 22:06:28 -07:00
GeometricallyandGitHub ac3a17b178 Fix plugin validator, fix version urls, clippy lints, additional categories (#421) 2022-08-16 17:42:04 -07:00
GeometricallyandGitHub c76b527b93 Make maven support duplicate versions (#418) 2022-08-13 18:53:12 -07:00
GeometricallyandGitHub ded4f95537 Fix additional category editing (#417) 2022-08-12 20:48:01 -07:00
GeometricallyandGitHub 8272386733 Fix approved value setting (#415) 2022-08-07 09:21:49 -07:00
GeometricallyandGitHub 33988ed3fb Allow duplicate version numbers, fix version sorting, edit validators (#414) 2022-08-06 17:44:16 -07:00
wafflecoffeeandGitHub 411b8e3cb6 Initial work on site moderation improvements (#410) 2022-08-02 23:31:56 -07:00
GeometricallyandGitHub 916da16523 Fix filters (#389) 2022-08-01 21:23:13 -07:00
GeometricallyandGitHub d165c081f7 Fix API breakage project creation (#409) 2022-07-31 21:54:17 -07:00
GeometricallyandGitHub 992de7d66e Fix search not working (#408) 2022-07-31 15:26:25 -07:00
GeometricallyandGitHub 46ab7bbcbe Fix category query (#407) 2022-07-31 14:42:22 -07:00
b04bced37f More project data (#406)
* More project data

* Array_agg fixes + cleanup

* fix prepare

* Add approval dates to search

* Update migrations/20220725204351_more-project-data.sql

Co-authored-by: wafflecoffee <emmaffle@modrinth.com>

* Add category labels + display categories

Co-authored-by: wafflecoffee <emmaffle@modrinth.com>
2022-07-31 13:29:20 -07:00
13335cadc6 Adds /teams route (#373)
* basic list (no grouping yet)

* now groups and checks auth, moved Team::get_many to TeamMember::get_from_team_full_many

* Ran 'cargo sqlx prepare'

* batch TeamMember::get_from_user_id

* Batches before for loop

* Ran 'cargo sqlx prepare'

Co-authored-by: Emma Cypress <emmaffle@modrinth.com>
Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
2022-07-26 22:40:20 -07:00
b864791fa6 Limit 'superuser' status of current moderators (#386)
Resolves MOD-88

Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
2022-07-23 18:47:32 -07:00
Emma Cypress ⚘andGitHub 6614b56298 Revert "Add auto-reporting inappropriate text content" (#397)
* Revert "Add auto-reporting inappropriate text content (#387)"

This reverts commit 68f7dc9512.

* Maybe don't revert the whole thing
2022-07-10 10:02:41 -07:00
02c3894fc9 Add even more validators (#385)
* Add even more validators

I was gonna add shaderpacks too, but those have no standard metadata file at all.

* Make it compile

* Fix logic

* Update validators

* fix mistake

Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
2022-07-09 20:25:44 -07:00
68f7dc9512 Add auto-reporting inappropriate text content (#387)
* Add initial support for blocking inappropriate text content

To make something clear, **nothing** is automatically censored or
deleted as a result of this pull request. This pull request is
meant to add two things:
- Regenerate new IDs (project, version, user, etc.) with profanity
- Send reports to the moderators for new inappropriate content

* Make it build

* Fix logic issue

Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
2022-07-09 18:51:55 -07:00
venashialandGitHub 18d1bc56fd Fix HTTP status code 200 -> 204 (#394) 2022-07-08 13:18:41 -07:00
GeometricallyandGitHub 1e4d07a52c Download counting (#388) 2022-07-01 19:31:37 -07:00
GeometricallyandGitHub 1fc579e907 Add project check route (#384)
* Add project check route

* Fix responsee
2022-06-28 14:36:47 -07:00
Danielle Hutzley 827c4e31ee fixup! Migrate to piston-meta 2022-06-27 13:59:06 -07:00
Danielle Hutzley c2a1ed926e Migrate to piston-meta 2022-06-27 13:54:09 -07:00
GeometricallyandGitHub 4f86c117c3 Merge pull request #2 from modrinth/feature/bincode
Add Bincode support
2022-06-26 18:41:00 -07:00
Danielle Hutzley 93817ba92f Actually compile without Bincode 2022-06-26 16:41:22 -07:00
Danielle Hutzley 18153e0fcc Bump version 2022-06-26 16:23:57 -07:00
Danielle Hutzley 3123f6444f Add Bincode feature for efficient binary storage 2022-06-26 16:23:41 -07:00
GeometricallyandGitHub 4e97a3b3d5 More project type validators (#383) 2022-06-26 10:39:38 -07:00
134c43ad9e Always return the username as fetched from the database (#382)
Co-authored-by: Patrick <cryne@gmx.de>
Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
2022-06-25 22:04:34 -07:00
e74b4b35b9 Don't consider a user's name taken by self (#376)
* Don't consider a user's name taken if self

* Fix incorrect types

* try-use more idiomatic Option tech

* true if `None`

Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
2022-06-25 21:50:41 -07:00
16e7194dfe Add Embedded dependency type (#380)
* Add Embedded dependency type

I couldn't find any SQL tables or anything for dependency types, so I'm going to assume there aren't any

* Make modpacks use Embedded instead of Required

Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
2022-06-25 19:16:46 -07:00
Emma CypressandGitHub 932b0ccf24 Allow archived projects to show in search (#381)
Resolves MOD-96
2022-06-24 14:26:18 -07:00
Emma CypressandGitHub 3e5c7f62d0 Update Mojang meta URL (#379)
Urgent, needs to be on prod before the next snapshot comes out
2022-06-22 12:22:19 -07:00
GeometricallyandGitHub bf19f5b9c0 Fix auth URL condition again (#378) 2022-06-19 17:20:21 -07:00
GeometricallyandGitHub 08a879bbb1 Fix auth URL condition (#377) 2022-06-19 14:41:41 -07:00
GeometricallyandGitHub cd514285d9 Fix rejected files showing in hash routes (#375)
* Fix rejected files showing in hash routes

* Run prepare and formatter

* Add modrinth.com exception for callback URLs

* run fmt
2022-06-18 14:09:37 -07:00
GeometricallyandGitHub 782bb11894 Secure auth route, fix quilt deps bug, optimize queries more (#374)
* Secure auth route, fix quilt deps bug, optimize queries more

* Add to_lowercase for multiple hashes functions
2022-06-17 16:56:28 -07:00
GeometricallyandGitHub 355689ed19 Replace ignore IP system with keys (#368) 2022-06-09 15:28:40 -07:00
GeometricallyandGitHub 75614fb13c Move downloads to queue for better performance (#367) 2022-06-09 12:21:51 -07:00
GeometricallyandGitHub 5c4a864680 Fix dep out of bounds error (#366) 2022-06-08 23:03:22 -07:00
GeometricallyandGitHub eaeff891d6 Reimplement old database code for better performance (#365) 2022-06-08 22:24:20 -07:00
GeometricallyandGitHub f0ab40d748 Fix update route (#364)
* Fix version updates route

* Run formatter, fix clippy, run prepare
2022-06-08 21:17:17 -07:00
GeometricallyandGitHub e497af4c26 Add deps list for override mods, fix version editing for packs (#363) 2022-06-05 10:42:33 -07:00
GeometricallyandGitHub f860f57363 Fix version editing (#362) 2022-06-02 20:56:12 -07:00
GeometricallyandGitHub 02bf5ada89 Increase file size limit (#361) 2022-05-30 16:29:23 -07:00
d3b578fe8f Fix wrongfully parsed hashes for the version_files route(s) (#353)
* Fix wrongfully parsed hashes for the version_files route(s)

* Remove the hex dependency

* Remove unwraps

.

.

Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
2022-05-26 18:32:32 -07:00
GeometricallyandGitHub d29d910ac6 Add mod lists for modpacks, liteloader support, update actix, fix moderation webhook (#357) 2022-05-26 10:08:19 -07:00
Jai A e7b41f9a4c Urgent fixes 2022-05-15 12:05:44 -07:00
GeometricallyandGitHub dd0aed4614 Bump version (#348) 2022-05-15 09:27:02 -07:00
GeometricallyandGitHub b9b4f2bb7f Fix download count logic (#347) 2022-05-14 15:55:02 -07:00
GeometricallyandGitHub 3533d2a2cc Fix search rules, register download route (#346) 2022-05-11 18:09:29 -04:00
GeometricallyandGitHub 26d9ef5398 Rework download route (#345) 2022-05-11 11:32:01 -04:00
GeometricallyandGitHub a0f840bcf8 Add quilt validators, Deps fix, Fix slug collisions (#338) 2022-04-24 13:02:41 -07:00
stairman06andGitHub 33d2a77e37 Maven fix (#337) 2022-04-24 10:55:56 -07:00
GeometricallyandGitHub 80e00a80d5 Switch to time crate, add file sizes (#329)
* Switch to time crate, add file sizes

* Update deps, adjust pack format

* Run formatter, fix clippy
2022-03-29 19:35:09 -07:00
GeometricallyandGitHub a3d5479878 Optimize DB pooling (#328) 2022-03-28 19:39:02 -07:00
Jai A a49dc04f5d Add download set check 2022-03-27 19:14:39 -07:00
GeometricallyandGitHub d1c0c9739d Shulkers of fixes (#327)
* Shulkers of fixes

* Fix validation message

* Update deps

* Bump docker image version
2022-03-27 19:12:42 -07:00
Emma C. Pointer-NullandGitHub 7415b07586 Add more version creation data aliases (#325)
For consistency and also for making the OpenAPI spec a bit less janky
2022-03-24 19:58:07 -07:00
GeometricallyandGitHub 023663b268 Fix permissions checks for projects, fix gallery URLs (#321) 2022-03-16 07:49:09 -07:00
GeometricallyandGitHub 3883c509b9 Bypass compile-time query check (#317) 2022-03-06 21:06:29 -07:00
GeometricallyandGitHub 18f34b4f83 Fix dep route again (#316) 2022-03-05 13:09:19 -07:00
Jai A caed86d846 Fix filenames, dep route again 2022-02-28 16:00:12 -07:00
GeometricallyandGitHub 459e36c027 Run fmt, fix dep route (#312) 2022-02-27 21:44:00 -07:00
GeometricallyandGitHub 725f8571bb Fix deps, download URLs, remove duplicate deps (#310) 2022-02-26 21:16:11 -07:00
DanielleandGitHub b7c7c0e862 Fix download counting (#309) 2022-02-26 08:37:24 -07:00
DanielleandGitHub 3f671b918a Move download counting to worker (#306)
* Move download counting to worker

* Run `cargo sqlx prepare`

* Format & some Clippy fixes
2022-02-21 19:57:40 -07:00
GeometricallyandGitHub 9492363b22 Fix uploading (#305)
* Upgrade rust-s3 to fix tokio panics

* Run fmt

* Update deps
2022-02-20 20:16:32 -07:00
3ee144459f Allow setting primary file when creating version (#304)
Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
2022-02-20 19:44:59 -07:00
GeometricallyandGitHub c0c80c0fdf See if RateLimit is cors issue (#303) 2022-02-19 17:51:24 -07:00
7c80b61666 Automatically generate updates.json for Forge mods (#298)
* Automatically generate updates.json for Forge mods

https://api.modrinth.com/updates/{id}/forge_updates.json serves a minimal update JSON for the Forge update checker

Closes #281

* Authenticate update JSON requests

Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
2022-02-19 13:09:09 -07:00
GeometricallyandGitHub d128f3e14e Send CORS wildcard, allow editing of non-accepted team members (#299) 2022-02-15 13:09:10 -07:00
GeometricallyandGitHub 4498b89ac4 Fix lax cors configuration (#295) 2022-02-12 19:57:00 -07:00
GeometricallyandGitHub e576a58ead Fix error messages, auth routes, and remove category unique constraint (#293)
* Fix error messages, auth routes, and remove category unique constraint

* Run prepare

* Remove debug secrets

* Fix team member editing
2022-02-10 10:56:45 -07:00
GeometricallyandGitHub eb4375258e Fix routing errors 2 + Version File Response (#290) 2022-02-06 17:54:01 -07:00
GeometricallyandGitHub 0cbc2001e2 Fix routing errors] (#289) 2022-02-06 16:35:52 -07:00
GeometricallyandGitHub 6bf5dbabee Upgrade to Actix V2, bump SQLX version, code cleanup, intergrate ratelimiter (#288)
* Upgrade to Actix V2, bump SQLX version, code cleanup, intergrate ratelimiter

* Add pack file path validation

* Fix compilation error caused by incorrect merge
2022-02-05 23:08:30 -07:00
BasiqueEvangelistandGitHub 6a89646e66 Support other project types in maven (#284) 2022-02-05 22:19:02 -07:00
EmmaandGitHub f3234a6b5e fix v1 parity issue: local- removed from mod_id field (#283) 2022-01-28 16:04:34 -07:00
GeometricallyandGitHub 73a8c302e9 Fix duplicate dates (#282) 2022-01-25 13:00:32 -07:00
989f2d3001 Add support for hashes in Maven (#264)
Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
2022-01-25 10:57:45 -07:00
GeometricallyandGitHub 2badcfa546 Fix dependency updates and creating versions with no game versions/loaders (#280) 2022-01-24 11:27:39 -07:00
GeometricallyandGitHub 384e14b32d Fix pack URL validation, Version file update route, and spaces in file download URLs (#275) 2022-01-09 15:35:01 -07:00
GeometricallyandGitHub 016e743653 Fix version deletion (#273) 2021-12-29 22:46:37 -05:00
Jai A 00adf3631a Remove replacing assets index URL in manifest to preserve file integrity 2021-12-20 09:11:40 -07:00
Jai A 09aef18999 Fix incorrect condition for forge and incorrect fabric loader version ordering 2021-12-19 20:51:34 -07:00
Jai A 1d86aac338 Fix java version not being optional 2021-12-19 19:56:56 -07:00
Jai A 80173634a0 Fix java version specifier being in snake case 2021-12-19 19:04:42 -07:00
Jai A c9598b674c Fix forge erroring out due to wonky version 2021-12-19 18:52:00 -07:00
Jai A 2a588d1e9a Make java stuff public, fix forge erroring out due to ratelimiting 2021-12-19 15:09:36 -07:00
Jai A 5a6c06c8a3 Host all loaders for forge, fix stable markers, add java version to daedalus 2021-12-18 22:55:03 -07:00
GeometricallyandGitHub b2ef4e9619 Fix modpack env field being required (#270) 2021-12-14 22:25:46 -07:00
GeometricallyandGitHub 9e9d6e45b4 Set pack format content type (#269) 2021-12-14 21:07:43 -07:00
GeometricallyandGitHub 6752457ad8 Pack format changes (#268) 2021-12-14 18:08:53 -07:00
GeometricallyandGitHub ddcb5cd4d3 Fix game version ordering, fix deleting versions with deps not working (#265) 2021-12-09 18:12:21 -07:00
GeometricallyandGitHub a54b2db81b Fix gallery creation validation and validators returning incorrect er… (#263)
* Fix gallery creation validation and validators returning incorrect errors

* Remove docker image

* Add URL validation for pack files

* Remove unneeded dependencies
2021-11-30 20:07:23 -07:00
GeometricallyandGitHub 6740124364 Fix search returning incorrect ownership information (#261) 2021-11-24 13:20:25 -07:00
GeometricallyandGitHub 157731e4f8 Remove package lists to preserve space (#260) 2021-11-14 17:49:55 -07:00
GeometricallyandGitHub 2dd1496ef4 Fix HTTPS requests not working in the image (#259) 2021-11-14 16:27:33 -07:00
GeometricallyandGitHub d1e4e72693 Switch docker image OS (#258) 2021-11-14 14:36:01 -07:00
GeometricallyandGitHub 77e8143290 Fix transferring ownership (#256) 2021-11-13 16:35:21 -07:00
GeometricallyandGitHub 7f791d4919 Move validators to seperate thread, other fixes (#253)
* Move validators to seperate thread, other fixes

* Update rust version in Dockerfile

* Fix notifs not working

* Fix pack validator not enforcing files
2021-11-13 15:46:08 -07:00
Jai A d7e0468776 Fix library URL being set incorrectly 2021-11-10 18:40:12 -07:00
Jai A f6c611bbba Add classpath variable for libraries 2021-11-10 17:34:53 -07:00
Jai A 0990ac4fc1 Add forge data to main version info 2021-11-08 20:17:20 -07:00
Jai A e91f8f693b Add local libs to modrinth maven and other fixes 2021-11-07 18:42:33 -07:00
Jai A 2a7dbda133 Bump version + Fix partial version and full version argument joining 2021-11-03 17:49:58 -07:00
Jai A 793e542312 Bump version 2021-11-02 21:44:59 -07:00
Jai A 240269eb25 Fix lib not parsing maven file extensions 2021-11-02 21:44:31 -07:00
Jai A c744dc8cc3 Bump library version 2021-11-02 20:25:55 -07:00
Jai A d596bdb454 Fix import errors 2021-11-02 20:24:51 -07:00
Jai A bec54b4283 Fix action title 2021-11-02 20:20:55 -07:00
Jai A 061b88f5b5 Fix docker action again 2021-11-02 20:19:43 -07:00
Jai A 8704eff632 Fix docker action 2021-11-02 20:17:12 -07:00
Jai A fb16f25b07 Fixes in forge universal lib + other things 2021-11-02 19:59:10 -07:00
Jai A e8057a5c8a Fix incorrect docker registry 2021-10-24 16:22:02 -07:00
GeometricallyandGitHub 3c5edb6171 Delete clippy.yml~ 2021-10-24 16:18:17 -07:00
Jai A e36a191240 Fix incorrect file names (again) 2021-10-24 16:16:49 -07:00
Jai A ecdfd65f50 Fix incorrect file names 2021-10-24 16:16:23 -07:00
Jai A 4294081abb Add GitHub actions again 2021-10-24 16:13:04 -07:00
Jai A 5218543c58 Add GitHub Actions 2021-10-24 16:10:38 -07:00
Jai A d8332a27e5 Finish newer forge versions 2021-10-24 14:25:24 -07:00
Jai A 673658dfd2 Simplify mod loader manifests, start work on new forge profiles 2021-10-19 23:08:44 -07:00
Jai A 6528d3d7da Legacy Forge Support (forgot to git add) 2021-10-17 23:23:27 -07:00
Jai A 16af479b83 Legacy Forge Support 2021-10-17 23:22:23 -07:00
13187de97d Rustic cleanups, dedups and making the code less hard to read in general (#251)
* typos :help_me:

* (part 1/?) massive cleanup to make the code more Rust-ic and cut down heap allocations.

* (part 2/?) massive cleanup to make the code more Rust-ic and cut down heap allocations.

* (part 3/?) cut down some pretty major heap allocations here - more Bytes and BytesMuts, less Vec<u8>s

also I don't really understand why you need to `to_vec` when you don't really use it again afterwards

* (part 4/?) deduplicate error handling in backblaze logic

* (part 5/?) fixes, cleanups, refactors, and reformatting

* (part 6/?) cleanups and refactors

* remove loads of `as_str` in types that already are `Display`

* Revert "remove loads of `as_str` in types that already are `Display`"

This reverts commit 4f974310cfb167ceba03001d81388db4f0fbb509.

* reformat and move routes util to the util module

* use streams

* Run prepare + formatting issues

Co-authored-by: Jai A <jaiagr+gpg@pm.me>
Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
2021-10-11 20:26:59 -07:00
Jai A 32850f6770 Fabric support 2021-10-09 14:04:45 -07:00
Jai A 4a7f4bde4a Working mirroring of minecraft metadata 2021-10-05 22:52:17 -07:00
Kir_AntipovandGitHub 0010119440 Maven repo should return primary file by default (#252)
* Maven repo should return primary file by default

* Added fallback for versions that don't have a primary file
2021-10-02 16:10:16 -07:00
91065a6168 [no ci] Remove contributing file and instead direct people to docs (#248)
This fixes a long standing issue where people would point to the frontend homepage where it says 'fully documented' then point to the README here and say 'well that's sure hypocritical'

Thus the branch name :tiny_pumpkin:

Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
2021-09-26 15:41:10 -07:00
efa8d5c575 Added monitoring, limited concurent connections (#245)
* reduced the default, and added environment override.

* Using parse is more stable and doesn't fail CI this time :P

* Added support for monitoring
This support is currently basic, but it can be improved later down the road.

* Forgot scheduler file

* Added health check

* Cargo fix

* Update cargo.lock to avoid action fails.

Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
2021-09-20 21:26:16 -07:00
GeometricallyandGitHub 04998d0215 Fix gallery validation (again) (#247)
* Remove accidental URL validation for gallery

* Remove accidental URL validation for gallery
2021-09-02 22:39:29 -07:00
GeometricallyandGitHub d0efa5d3fe Remove accidental URL validation for gallery (#246) 2021-09-01 21:19:49 -07:00
GeometricallyandGitHub c87e72e08e Switch to alternate query strategy which simplifies code (#244) 2021-09-01 06:04:38 -07:00
GeometricallyandGitHub efb82847cb Switch to ARRAY_AGG for database aggregations to improve peformance + fix gallery images not showing up (#242) 2021-08-31 15:29:51 -07:00
GeometricallyandGitHub f37e267a5e Make gallery item featuring exclusive (#241)
* Make gallery featured value exclusive

* Run prepare
2021-08-29 15:18:45 -07:00
GeometricallyandGitHub 69928219a3 Fix project creation hash lookups failing (#239) 2021-08-25 23:18:44 -07:00
GeometricallyandGitHub fdf8845a2f Fix validators (#237)
* Fix file extension checks not working

* Fix validators not validating files of a non-matching extension
2021-08-22 09:11:38 -07:00
GeometricallyandGitHub 4073a7abc3 Force files to be unique, require all new versions to have at least one file (#236) 2021-08-21 19:38:32 -07:00
GeometricallyandGitHub ffd9a34cf5 Query optimization (#235)
* Optimize version queries and decrease some query complexity

* Run formatter
2021-08-20 16:33:09 -07:00
07226c6d21 Fix Docs Link (#233)
Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
2021-08-14 12:39:06 -07:00
GeometricallyandGitHub b1bc7c1fc2 Add fields to gallery items (#234) 2021-08-05 22:01:26 -07:00
GeometricallyandGitHub 1b33f0cea9 Fix multiple projects query taking seconds to complete (#232) 2021-08-01 19:55:36 -07:00
GeometricallyandGitHub 8ece3b00f5 Fix project dependencies returning invalid values (#231)
* Fix project dependencies returning invalid values

* Run prepare
2021-08-01 16:11:07 -07:00
GeometricallyandGitHub c9c58b65a6 Optimize dependencies route and change return value (#230) 2021-07-31 19:23:06 -07:00
GeometricallyandGitHub 66becbc4cc Fix dependencies route only showing one dependency per version (#229) 2021-07-28 11:34:47 -07:00
GeometricallyandGitHub 5b8612c919 Fix dependency route having incorrect return value (#228) 2021-07-27 18:48:56 -07:00
GeometricallyandGitHub 430c22e06e Add gallery parameter to meilisearch attributes (#227) 2021-07-27 18:10:40 -07:00
GeometricallyandGitHub 76b62eda3a Allow gallery featuring, add gallery images to search, rename rejection reasons, transfer ownership route (#226) 2021-07-27 16:50:07 -07:00
venashialandGitHub bc983162f3 Detect if redirect url contains a query string (#225) 2021-07-27 16:31:41 -07:00
GeometricallyandGitHub 4922598aee Add gallery item deletion + making them optional (#224) 2021-07-21 09:54:29 -07:00
GeometricallyandGitHub b2f8bb9990 Fix panic on requesting projects (#223) 2021-07-19 14:47:14 -07:00
GeometricallyandGitHub 9ee92fb9e9 Project gallery, webhook fixes, remove cache, re-enable donation URLs (#222) 2021-07-19 11:30:39 -07:00
GeometricallyandGitHub 981bf1d56f Fix caching bug, and moderation webhook being sent at the wrong time (#215) 2021-06-19 22:01:11 -07:00
GeometricallyandGitHub d2c2503cfa Final V2 Changes (#212)
* Redo dependencies, add rejection reasons, make notifications more readable

* Fix errors, add dependency route, finish PR

* Fix clippy errors
2021-06-16 09:05:35 -07:00
GeometricallyandGitHub 2a4caa856e More APIv2 Fixes (#210)
* Refactor search to not spam the database with queries, new utility routes for V2

* Run prepare

* More V2 Fixes

* Run prepare + formatter
2021-06-05 20:59:21 -07:00
GeometricallyandGitHub 157962e42a Improve peformance of search indexing, v2 fixes + new routes (#205)
* Refactor search to not spam the database with queries, new utility routes for V2

* Run prepare
2021-06-02 18:33:11 -07:00
16db28060c Project Types, Code Cleanup, and Rename Mods -> Projects (#192)
* Initial work for modpacks and project types

* Code cleanup, fix some issues

* Username route getting, remove pointless tests

* Base validator types + fixes

* Fix strange IML generation

* Multiple hash requests for version files

* Fix docker build (hopefully)

* Legacy routes

* Finish validator architecture

* Update rust version in dockerfile

* Added caching and fixed typo (#203)

* Added caching and fixed typo

* Fixed clippy error

* Removed log for cache

* Add final validators, fix how loaders are handled and add icons to tags

* Fix search module

* Fix parts of legacy API not working

Co-authored-by: Redblueflame <contact@redblueflame.com>
2021-05-30 15:02:07 -07:00
Mysterious_DevandGitHub 712424c339 Add alphabetically sorting (#190) 2021-04-24 19:44:11 -07:00
BasiqueEvangelistandGitHub 15c56dfcb8 Maven endpoint support (#180)
* Basic maven endpoint

* Clean up maven endpoint

* cargo sqlx prepare

* Minor cleanup

* Remove indentation

* Borrow &str instead of &String

* Refactor mod_data-getting
2021-03-29 10:36:55 +02:00
b98ad47618 Fixed spacing (#181)
Co-authored-by: 0SoggyMustache0 <george@georgekazan.dev>

Co-authored-by: 0SoggyMustache0 <george@georgekazan.dev>
Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
2021-03-28 22:30:56 -07:00
RedblueflameandGitHub 4778c5f5e8 Github container repository support (#183)
* Add GHCR support

* Add layer caching
2021-03-28 14:01:19 -07:00
d041671dc5 Update banner URL & Fix spelling (#105)
* Update banner URL

From https://gist.github.com/MulverineX/6f0a27bc67692f14567816ce1c3f7710 instead of a discord attachment

* Fix spelling of proper laboratory

Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
Co-authored-by: Redblueflame <contact@redblueflame.com>
2021-03-28 12:40:21 -07:00
GeometricallyandGitHub 5cab65d197 Fix #178 (#179) 2021-03-26 22:10:30 -07:00
Jai A b5bf627fb1 Fix ID serialization being broken on report create route 2021-03-11 15:52:38 -07:00
Jai A 6104150b77 Fix users not being able to see their own unapproved mods 2021-03-11 10:32:47 -07:00
Jai A a13bae2f39 Fix report create route 2021-03-10 20:20:37 -07:00
Jai A fd80e98207 Register report type routes 2021-03-09 21:18:04 -07:00
Jai A f43b95f001 Fix primary files not showing when requesting multiple versions 2021-03-09 13:40:19 -07:00
GeometricallyandGitHub 38802d3522 Fix primary files, file deletion, checks for mod following, fix user following route (#175) 2021-03-08 12:52:48 -07:00
Jai A 9f7813622d Fix team invites 2021-03-07 20:46:04 -07:00
Jai A 75d67207aa Register users notification route 2021-03-07 18:53:38 -07:00
Jai A e596a8f731 Fix search 2021-03-07 18:44:24 -07:00
Jai A 5b0cc73792 Change server side and client side fields to optionals 2021-03-07 17:52:04 -07:00
Jai A 2163d4465f Fix license field not working: 2021-03-07 17:07:32 -07:00
Jai A 8becf45714 Fix broken search 2021-03-07 16:39:47 -07:00
Jai A 853ead26ca Register notification routes, add action method for notifications, and fix auto-featuring versions 2021-03-06 13:47:49 -07:00
GeometricallyandGitHub 0ccb6cb873 Follows (#172)
* Follows initial

* Fix #171, Fix #170, Fix #169, Fix #164

* More work on follows

* Fix compile error

* Upgrade meili version, add follows to search
2021-03-04 20:35:23 -07:00
BasiqueEvangelistandGitHub e46ff3de8b Add slug validation (#168) 2021-02-23 08:04:10 -07:00
a02e08a879 Fix featured versions sorting (#166)
* Reports WIP

* Finish reports

* Clippy fixes

* Fix featured versions sorting

Co-authored-by: Geometrically <geometrically@pop-os.localdomain>
2021-02-13 22:38:00 -07:00
109d7d87bd Reports (#165)
* Reports WIP

* Finish reports

* Clippy fixes

Co-authored-by: Geometrically <geometrically@pop-os.localdomain>
2021-02-13 12:11:13 -07:00
GeometricallyandGitHub 3df740702c Fix version hash (#154) 2021-01-30 16:51:32 -07:00
GeometricallyandGitHub 06bb6f7bff Dependencies, fix panic on version get, Filtered Versions route (#153) 2021-01-29 20:35:29 -07:00
GeometricallyandGitHub e33738a876 Fix queries, also add error message for database (#152) 2021-01-28 14:36:53 -07:00
Geometrically f887f5dca3 Return database error so I can actually debug this 2021-01-28 11:15:29 -07:00
GeometricallyandGitHub 257c16690a Merge pull request #151 from modrinth/fix-duplicates
Fix some mods and versions 'dissapearing'
2021-01-28 09:36:23 -07:00
GeometricallyandGitHub 7114e88992 Merge branch 'master' into fix-duplicates 2021-01-28 09:08:08 -07:00
Geometrically de7e869ca9 Fix some mods and versions 'dissapearing' 2021-01-28 09:07:10 -07:00
GeometricallyandGitHub eaee9c9522 Merge pull request #150 from modrinth/fix-duplicates
Fix several bugs
2021-01-27 16:01:03 -07:00
Geometrically bee11a6d41 Fix several bugs 2021-01-27 15:43:44 -07:00
GeometricallyandGitHub 647e44147f Merge pull request #149 from modrinth/fix-version-get
Fix version get
2021-01-27 09:18:08 -07:00
Geometrically 4a5d46915d Fix version get 2021-01-27 08:46:38 -07:00
GeometricallyandGitHub 8ad19585cb Merge pull request #148 from modrinth/sentry
Sentry Integration
2021-01-27 07:27:21 -07:00
AppleTheGolden 951a33fae9 Add Sentry integration 2021-01-27 14:38:18 +01:00
GeometricallyandGitHub bcf174cd1e Delete labelsync.yml 2021-01-25 15:27:04 -07:00
GeometricallyandGitHub 1e82909f58 Update issue templates 2021-01-25 15:23:07 -07:00
GeometricallyandGitHub 33e83b8414 Merge pull request #144 from modrinth/query-optimizations
Fixes
2021-01-25 14:43:43 -07:00
GeometricallyandGitHub 592c56cb20 Merge branch 'master' into query-optimizations 2021-01-25 14:43:36 -07:00
Geometrically 710528c01a Fixes 2021-01-25 14:42:26 -07:00
GeometricallyandGitHub 03cadc2604 Delete labels.yml 2021-01-25 14:35:57 -07:00
GeometricallyandGitHub dd73bb95a4 Merge pull request #143 from modrinth/query-optimizations
Query Optimization
2021-01-25 14:14:42 -07:00
GeometricallyandGitHub 40e0a55378 Merge branch 'master' into query-optimizations 2021-01-25 13:51:48 -07:00
Geometrically 269884d9f3 Fix docker build failing 2021-01-25 13:15:32 -07:00
Geometrically fcd548c313 Fix queries not working 2021-01-25 13:00:04 -07:00
Geometrically 140a8b6804 Fix docker image build failing 2021-01-24 22:49:28 -07:00
Geometrically b5378c1296 Query Optimization 2021-01-24 21:33:32 -07:00
GeometricallyandGitHub 1445d6ea8c Merge pull request #142 from modrinth/follows
Hotfix everything broken
2021-01-23 11:49:12 -07:00
Geometrically 5385431051 make compile 2021-01-23 11:37:25 -07:00
Geometrically f14f4498fb Hotfix everything broken 2021-01-23 11:32:32 -07:00
GeometricallyandGitHub fc2786f5e8 Merge pull request #138 from modrinth/fixes
A number of fixes
2021-01-18 10:46:17 -07:00
Geometrically 174dbb5e74 Fix auth params, Add params to indexing, Order version game versions, Remove version moderation, Return donation platforms in get routes 2021-01-18 10:10:45 -07:00
GeometricallyandGitHub 68517c15f2 Merge pull request #136 from modrinth/small-fixes
Fix version number editing
2021-01-15 08:18:15 -07:00
GeometricallyandGitHub 11ee142e4b Merge branch 'master' into small-fixes 2021-01-15 07:59:39 -07:00
Geometrically 1e1d047e07 Run prepare 2021-01-15 07:48:37 -07:00
Geometrically 62f1e39e6e Fix version number editing 2021-01-15 07:45:59 -07:00
GeometricallyandGitHub 05756da495 Merge pull request #135 from modrinth/fix/file-upload-permissions
Fix #134
2021-01-15 07:36:43 -07:00
Mikhail OleynikovandGitHub fa35b2a66f Fix #134 2021-01-15 16:13:08 +03:00
GeometricallyandGitHub d2094e2b68 Merge pull request #132 from modrinth/move-descriptions
Bug Fixes
2021-01-14 13:41:09 -07:00
Geometrically 075b2df738 Fix lint failures 2021-01-14 12:23:39 -07:00
Geometrically ec3c31a106 Move descriptions to database, switch to SHA-512 hashes, fix declining invites not working, allow user deletion, fix broken permission checks for many things, security fixes 2021-01-14 10:08:38 -07:00
GeometricallyandGitHub e2183c2214 Merge pull request #123 from modrinth/fix-ratelimit
Bump ratelimit to 200 RPM, allow specified IPs to have lax ratelimit …
2021-01-01 11:19:14 -07:00
Geometrically 4994064e6e Fix lint 2021-01-01 09:27:37 -07:00
Geometrically a40b9f4054 Fix clippy error (?) 2021-01-01 09:13:27 -07:00
Geometrically ee8d65977d Fix YAML syntax error 2020-12-31 21:47:17 -07:00
Geometrically b4ea11e55e Fix actions to use build cache 2020-12-31 19:25:19 -07:00
Geometrically b8d2ef1eb5 Fix SQLX errors 2020-12-31 19:09:23 -07:00
Geometrically 0efeffeaa3 Bump ratelimit to 200 RPM, allow specified IPs to have lax ratelimit restrictions, and allow wildcard for CORS 2020-12-31 18:54:58 -07:00
GeometricallyandGitHub 7a86d272bb Merge pull request #121 from modrinth/fix-description-cache
Fix description cache
2020-12-31 10:58:40 -07:00
Geometrically 4d780904d1 Fix formatting, remove failing backblaze test due to expired token 2020-12-31 10:16:32 -07:00
Geometrically 0a6fa65075 Fix clippy 2020-12-31 10:09:57 -07:00
Geometrically 6440220640 Fix again 2020-12-29 22:25:52 -07:00
Geometrically 4350c9a72b Fix again 2020-12-29 22:19:03 -07:00
Geometrically 77feae0ab9 Merge branch 'fix-description-cache' of https://github.com/modrinth/labrinth into fix-description-cache 2020-12-29 22:12:00 -07:00
Geometrically 1261696ba2 Make it compile 2020-12-29 22:11:23 -07:00
GeometricallyandGitHub 6b35a0a527 Merge branch 'master' into fix-description-cache 2020-12-29 21:57:54 -07:00
Geometrically fd33ff81c9 Fix description cache 2020-12-29 21:49:01 -07:00
GeometricallyandGitHub 178b1e8bb0 Merge pull request #120 from modrinth/team-member-fixes
Fix revocation of invites, allow for /user_id/teams to be useful
2020-12-28 10:51:16 -07:00
Geometrically 9d50f03cb1 Fix game version editing for versions 2020-12-27 18:58:27 -07:00
Geometrically 8c1688657a Fix revocation of invites, allow for /user_id/teams to be useful 2020-12-27 18:50:10 -07:00
GeometricallyandGitHub 833cb99f41 Merge pull request #119 from modrinth/team-member-fixes
Add 'accepted' field to TeamMember
2020-12-27 08:18:34 -07:00
Geometrically bd5d84abcd Add 'accepted' field to TeamMember 2020-12-26 22:49:07 -07:00
GeometricallyandGitHub d451ba9b6e Merge pull request #118 from modrinth/fix-invites
Fix invites
2020-12-26 15:21:41 -07:00
GeometricallyandGitHub 14bd02a569 Merge branch 'master' into fix-invites 2020-12-26 15:18:47 -07:00
Geometrically 4beace1bb0 Fix inites (again) 2020-12-26 15:17:51 -07:00
GeometricallyandGitHub 6996dfcd3b Fix incorrect route for team fetching (#117)
* Fix invites failing

* Fix incorrect route for team fetching
2020-12-26 12:25:27 -07:00
Geometrically 42c46d7d5c Fix incorrect route for team fetching 2020-12-26 12:20:23 -07:00
GeometricallyandGitHub 9b31ce83c5 Fix invites failing (#116) 2020-12-26 12:13:09 -07:00
Geometrically cb5250527b Fix invites failing 2020-12-26 12:08:28 -07:00
GeometricallyandGitHub f0b73fd696 Change index interval, add slug to search documents (#110)
* Change index interval, add slug to search documents

* Allow the removal of '@' for slug get

* Fix

* Remove name and rename side type

* Run prepare
2020-12-13 18:10:58 -07:00
GeometricallyandGitHub df5684a9f8 Fix access controls (#109)
* Fix access controls

* Remove CF indexing, fix some stuff
2020-12-02 10:24:20 -07:00
AeledfyrandGitHub b3f724c799 Hotfix: fix version delete permissions and CORS allowed methods (#107) 2020-11-30 10:45:59 -07:00
GeometricallyandGitHub a7be6504a2 Fix hash (#106)
* More mod info

* Downloading mods

* Run prepare

* User editing + icon editing

* Finish

* Some fixes

* Fix clippy errors

* Fix hash lookup

* Run prepare

* Run formatter
2020-11-29 14:27:40 -07:00
GeometricallyandGitHub 1da5357df6 More mod info (#104)
* More mod info

* Downloading mods

* Run prepare

* User editing + icon editing

* Finish

* Some fixes

* Fix clippy errors
2020-11-27 10:57:04 -07:00
AeledfyrandGitHub 92e1847c59 Hotfix: route new moderation routes (#103) 2020-11-15 20:47:45 -07:00
GeometricallyandGitHub 0500994def Moderation + Mod Editing (#101)
* Moderation + Mod Editing WIP

* Run prepare, fix perms

* Make it compile

* Finish moderation and edit routes

* More fixes

* Use better queries

* Final Fixes
2020-11-15 19:58:11 -07:00
AeledfyrandGitHub da911bfeb8 Minor fixes to orderings and permission serialization (#102)
* Fix latest_version in search results

* Handle users with invalid permissions instead of skipping them

* Specify order of some queries, fix serialization of permissions

* Run sqlx prepare
2020-11-10 09:27:36 -07:00
578d673a4e Team routes (#92)
* Team routes template

* More work on teams

* Updating routes WIP

* Edit routes

* Fixes

* Run prepare, prevent non-members from seeing perms

* More fixes

* Finish team routes

* More fixes

* Unpushed changes

* Some more fixes and error handling

* Fix sqlx prepare, formatting

Co-authored-by: Aeledfyr <aeledfyr@gmail.com>
2020-11-09 19:39:23 -07:00
AeledfyrandGitHub c8e58a1e5b Fix indexing, upgrade MeiliSearch sdk (#100)
* Hotfixes for indexing

* Handles missing INDEX_CACHE_PATH environment variable
* Exits on startup if environment variables are missing. The flag
  --allow-missing-vars disables this, but that is generally a bad
  idea, since most environment variables are required (and the ones
  that aren't should be marked as such).
* Disables the query loggers

* Upgrade meilisearch-sdk to 0.4.0 for MeiliSearch 0.16 support

* Fix swap of Forge and Fabric labeling
2020-11-05 08:38:03 -07:00
AeledfyrandGitHub d477874535 Optimize and fix some bugs in indexing (#98)
* Improve curseforge and local indexing

This should make curseforge indexing more efficient, and reuses
some of the normal local indexing for the queued indexing of
recently created mods.

* Unify impls for single and multiple routes for mods and versions

This uses the same backend for the single and multiple query
routes so that they no longer return inconsistent information.

* Cache valid curseforge mod ids to reduce request load

This caches the ids of minecraft mods and reuses them on indexing
to reduce the amount of unused addons that are returned.
2020-11-03 17:55:50 -07:00
AeledfyrandGitHub da79386cc3 Track and sort by release date of game_versions tags (#95) 2020-10-31 21:06:47 -07:00
AeledfyrandGitHub a4ba6d1444 Game Version types, indexing, and bugfixes (#91)
* Add types to game_versions, allow filtering by version type

- Fixes an issue with version numbers in the initial mod indexing
  queue
- Modifies the /api/v1/categories/game_versions route to take an
  optional query parameter `type` to filter the listed game versions
- Creating tags is now idempotent
- Creating game_versions now requires a JSON body that specifies
  the version type

* Implement automatic indexing of new Minecraft versions

It's currently set to run every 6 hours and isn't configurable; we
could add config for it, but it doesn't seem likely to be rate
limited or have issues with frequency.
2020-10-28 09:11:49 -07:00
GeometricallyandGitHub ef28459b61 Rate limiting + version fixes (#90)
* Rate limiting + version fixes

* Move patch to proper place

* More fixes

* Fix commit hash pin
2020-10-25 13:51:07 -07:00
GeometricallyandGitHub 1ff8c908b8 Add way to fetch team members (#89)
* Add way to fetch team members, fix files not being returned with version route

* Make it compile

* Fixes

* Use default error handling
2020-10-21 12:30:35 -07:00
e966ef96e5 Await an unused future and change #![allow(unused)] to dead_code (#88)
Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
2020-10-19 22:24:40 -07:00
GeometricallyandGitHub b05b38b269 Add files to initial versions/mods (#84)
* Add files to initial versions/mods

* Remove useless code, fix actual problem

* Remove debug text

* Rename body to description
2020-10-19 22:08:47 -07:00
GeometricallyandGitHub 8e1f1ff2e6 Fix another bug (#87) 2020-10-19 20:04:26 -07:00
GeometricallyandGitHub 680d6c20ca Search hotfix (#86) 2020-10-19 19:46:33 -07:00
c886e7949e Support for using a master key (#83)
* Support for using a master key

* Expand scope of PR, add wrapper struct, add files to intitial versions/mods

* Change changelog path, run formatter

* Split file changes into different PR

* Formatting, rename main variable

Co-authored-by: Aeledfyr <aeledfyr@gmail.com>
2020-10-19 14:23:05 -07:00
GeometricallyandGitHub e0b972f6d6 Add S3 File Host (#81)
* Add S3 File Host

* Fix tests, set default ACL level to public

* Refactor

* Fix merge conflicts

* Env fixes

* Run formatter

* Remove extra allocations
2020-10-18 13:26:13 -07:00
AeledfyrandGitHub 25daa9f2da Update actix-web to 3.0, update deps (#82) 2020-10-18 10:50:37 -07:00
AeledfyrandGitHub d0fb5c3bd5 Refactor mod creation route, add more checks (#80)
This also removes the `team_members` field of `InitialModData`, as
team members are no longer specified at mod creation.
2020-10-17 19:34:23 -07:00
520b12e56b Make mod creation always create initial versions & don't require mod id for mod creation versions (#79)
* Make mod creation always create initial versions, other fixes

* Fix sqlx prepare

Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
2020-10-16 13:28:53 -07:00
GeometricallyandGitHub 5c8ffe961e Fix local indexing (#78) 2020-10-16 11:21:07 -07:00
AeledfyrandGitHub 7983e82b60 Fix some issues with search and mod creation (#77) 2020-10-16 10:04:38 -07:00
GeometricallyandGitHub 77d35b61a9 Fix indexing (#76) 2020-10-15 09:56:41 -07:00
GeometricallyandGitHub 285a97aaf8 Creation fix (#74)
* Lots of little fixes

* Change + Add TODOs back that were incomplete

* Fix migrations

* Run prepare

* Minor fixes

* Fix formatting

* SQLX Prepare

* Add status to creation query
2020-10-14 20:43:40 -07:00
GeometricallyandGitHub ad29f2477e Lots of little fixes (#73)
* Lots of little fixes

* Change + Add TODOs back that were incomplete

* Fix migrations

* Run prepare

* Minor fixes

* Fix formatting

* SQLX Prepare
2020-10-14 13:19:38 -07:00
AeledfyrandGitHub 1072d1306b Make indexing date format consistent (#72) 2020-10-12 14:08:43 -07:00
GeometricallyandGitHub b8eda40937 Fix date format (#71) 2020-10-11 11:35:50 -07:00
GeometricallyandGitHub 2719ae5df2 Add API routes to request multiple of an item (#70)
* Change header name

* Add default bio value

* Remove default

* Make name null

* Run prepare

* Add new API Routes for requesting multiple of an item

* Run formatter

* Simplify get mods query

* Run prepare

* Refactor to use one query for most routes, change version create route to have mod_id in data

* More fixes
2020-10-05 14:25:32 -07:00
GeometricallyandGitHub 68ee2bdcdc Fix another GitHub OAuth Bug, allow users to register with null names. (#69)
* Change header name

* Add default bio value

* Remove default

* Make name null

* Run prepare
2020-10-03 16:31:10 -07:00
GeometricallyandGitHub da654fdff5 Add default bio value, to fix GitHub integration errors (#68)
* Change header name

* Add default bio value

* Remove default
2020-10-03 12:11:36 -07:00
GeometricallyandGitHub d7f9d5a66f Change header name (#67) 2020-10-02 15:55:47 -07:00
AeledfyrandGitHub c4fb7b7928 General cleanup: fix some bugs, some refactoring (#65)
* Merged mod file upload in version creation, mod creation and
  version file add to one function;  This makes sure that they are
  consistent
* Made some fields on `User` optional: `github_id`, `avatar_url`, `bio`.
    * We may not want to publicly show the `github_id` to everyone
      with access to the API
    * If we allow non-github users, some of those fields would be
      invalid; some oauth providers may not have avatars or bios
* Made CORS origins should configurable
* Made `--reconfigure-indices` and `--reset-indices` exit after
  completion instead of starting the server
2020-09-30 22:07:52 -07:00
GeometricallyandGitHub 43a791db65 Merge pull request #64 from modrinth/auth-fix
Make scopes safe for browser
2020-09-29 12:56:58 -07:00
Jai A 217311211a Remove org read scope 2020-09-29 11:55:12 -07:00
Jai A ca55890ad2 Make scopes safe for browser 2020-09-29 11:46:11 -07:00
GeometricallyandGitHub d6ecf5b8a9 Merge pull request #63 from modrinth/ghauth
GitHub Authentication
2020-09-29 11:27:56 -07:00
Jai A e52edde11f Run prepare scripts 2020-09-29 07:36:28 -07:00
Jai A 2e514735ec User retrieval routes 2020-09-28 22:30:13 -07:00
Jai A 3d32c30d2d Authenticate protected routes 2020-09-28 21:05:42 -07:00
Jai A 05235f8385 Implement users in API 2020-09-28 10:48:15 -07:00
Jai A cd28a75c86 Authentication workflow complete, add database link 2020-09-27 22:49:38 -07:00
Jai A 34075738ea Basic GitHub integration 2020-09-26 22:49:16 -07:00
GeometricallyandGitHub 88c0b8a8f0 Search fixes (#62) 2020-09-12 18:16:05 -07:00
GeometricallyandGitHub e8bbc117e1 Allow for API user to change the amount of mods responded with in search (#61)
* Add more info to search route:

* Run formatter

* Allow for API user to change the amount of mods responded with in search

* Refactor SearchResults

* Fix searchresults usage
2020-09-07 11:44:21 -07:00
GeometricallyandGitHub b99f45874f Add more info to search route (#60)
* Add more info to search route:

* Run formatter
2020-09-06 08:19:53 -07:00
0dfa378e38 Add modrinth.com to CORS (#59)
Co-authored-by: Redblueflame <redblueflame1@gmail.Com>
2020-09-02 08:24:42 -07:00
RedblueflameandGitHub 3da0c07bcd fix: Fixed ssl error with the docker container. (#58) 2020-08-29 20:24:15 +02:00
RedblueflameandGitHub 2196b53075 Switched base container for the docker build (#57) 2020-08-29 07:56:50 -07:00
RedblueflameandGitHub a8340f37bb Update Dockerfile (#56)
* Update Dockerfile

* Update Dockerfile
2020-08-28 12:03:00 -07:00
RedblueflameandGitHub 7b1710ee63 Update Dockerfile (#55) 2020-08-28 20:24:43 +02:00
RedblueflameandGitHub 38b7d9724e feat(migration): Added automatic migration (#54) 2020-08-28 08:48:01 -07:00
GeometricallyandGitHub 2b1ed49e9a Update Meilisearch SDK (#53)
* Update Meilisearch SDK

* Run Formatter

* Fixes
2020-08-27 17:53:40 -07:00
GeometricallyandGitHub 017cf9e464 Make CORS work (#52)
* Make CORS work

* Add use statement

* Add to TOML
2020-08-27 08:47:18 -07:00
AeledfyrandGitHub 781f0c843e Implement more database methods and basic API routes (#50)
* feat: Implement more database methods & add mod and version routes

* feat: Implement deleting mods/versions & implement categories

* feat: Implement routes for categories, game versions & loaders

* feat: Reorganize API routes in a (hopefully) usable way
2020-08-12 12:54:03 -07:00
e2bf474332 Version Creation (#47)
* Creation Stuff

* Make it work

* Response structs + Mod ID validation

* Run code formatter

* Push local changes

* Finish up version creation - fix comments, impl file creation

* fix: Add sqlx prepare data

Co-authored-by: Aeledfyr <aeledfyr@gmail.com>
2020-08-12 11:05:49 -07:00
AppleTheGoldenandGitHub 7e2f1c9a8b Update rust version in dockerfile (#49) 2020-08-02 09:48:02 -07:00
AeledfyrandGitHub 8e798dde48 feat(search): Faceted search based on mod host (curse/modrinth) (#48)
This also adds a commandline argument library (gumdrop) for dealing
with indices - reseting, reconfiguring, and skipping them. I don't
know which library is best for this case, but gumdrop has shorter
compile times and many fewer dependencies than clap, which is why
I chose it.
2020-07-31 18:18:23 -07:00
AeledfyrandGitHub c05ae6e94c fix(postgres): Fix sqlx's misinterpretation of Ids, update sqlx (#46) 2020-07-30 20:45:22 -07:00
ff28ea8fa8 Refactor Meilisearch, update to latest SDK, and implement faceted search (#44)
* feat(indexing): Reindex curseforge & local database at an interval

* fix(indexing): Use strings for meilisearch primary key

Fixes #17 by prefixing curseforge ids with "curse-" and local ids
with "local-".

* feat(indexing): Add newly created mods to the index more quickly

* feat(indexing): Implement faceted search, update to meilisearch master

Fixes #9, but only uses faceted search for categories.  It should
be reasonably simple to add support for versions, but it may not
be as useful due to the large number of versions and the large
number of supported versions for each mod.

* feat(indexing): Allow skipping initial indexing

Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
2020-07-27 16:54:10 -07:00
Charalampos FanoulisandGitHub 7914e89212 fix: quote the numbers on the github label (#43) 2020-07-23 23:26:53 +02:00
Valentin RicardandGitHub 558ff90e27 fix/ci: Fix build matrix (#42)
* ci: Test #1

* ci: Test #2

* ci: Fix an unrelated issue

* ci: Restore the branch filter
2020-07-23 23:18:36 +02:00
ee69653a83 Switch to Postgres (#39)
* WIP Switch to Postgres

* feat(postgres): more work on porting to postgres, now compiles

* feat(docker-compose): Changed the docker-compose.yml file to use postgres.

* Update docker, documentation, gh actions...

* Remove bson dependency

* Remove bson import

* feat: move mock filehost to trait rather than cargo feature

* feat(postgres): transactions for mod creation, multipart refactor

* fix: Add Cargo.lock so that sqlx functions

* Update sqlx offline build data

* fix: Use SQLX_OFFLINE to force sqlx into offline mode for CI

* Default release channels

* feat(postgres): refactor database models to fit postgres models

* fix: Fix sqlx prepare, fix double allocation in indexing

* Add dockerfile (#40)

Co-authored-by: Charalampos Fanoulis <charalampos.fanoulis@gmail.com>

Co-authored-by: Aeledfyr <aeledfyr@gmail.com>
Co-authored-by: redblueflame <contact@redblueflame.com>
Co-authored-by: Jai A <jai.a@tuta.io>
Co-authored-by: Valentin Ricard <redblueflame1@gmail.Com>
Co-authored-by: Charalampos Fanoulis <charalampos.fanoulis@gmail.com>
2020-07-23 22:46:33 +02:00
AeledfyrandGitHub 95339a8338 Create a mock file host for dev, Fix mod creation route (#38)
* fix(mod-creation): fix actix server data & mod creation route

* feat(file-host): implement mock file hosting

This implements a mock file hosting system backed by the system's
filesystem.  It mirrors the API of the backblaze integration, but
puts the files directly on disk in the path specified by the
MOCK_FILE_PATH environment variable (defaults to /tmp/modrinth).

The mock file hosting is enabled by default using cargo features
to allow people to work on modrinth without access to a valid
backblaze account and setup.  To enable backblaze, specify the
cargo feature "backblaze" when running, ex. `cargo run --features
backblaze`.

* feat(file-hosting): implement basic backblaze API error handling

* fix(mod-creation): fix extension parsing, use base62 ids for paths
fix(file-hosting): reduce unnecessary allocations

* fix: fix auth with docker mongodb

* fix: fix failing checks

* fix: remove testing files
2020-07-16 21:06:58 -07:00
39b1435725 Mod Creation (#34)
* Inital creation stuff

* File Reader

* Upload bodies

* Major rework:

* Finish Multiple Files

* Proper Error Handling

* Switch to database models

* Run formatter

* Make dependencies dependent on Versions over mods

* Fixes

* Fix clippy

* Run lint one last time

* Update src/models/mods.rs

Co-authored-by: AppleTheGolden <scotsbox@protonmail.com>

Co-authored-by: AppleTheGolden <scotsbox@protonmail.com>
2020-07-16 10:16:35 -07:00
AeledfyrandGitHub b1d3e258bd fix(indexing): chunk adding documents to indexing server (#36)
This should prevent adding too many mods and going over
meilisearch's request size limit by attempting to add all mods in
one request.
2020-07-15 15:01:49 -07:00
AeledfyrandGitHub 6ff7fa74e2 Improve error handling (#33)
* refactor: improve error handling

* fix: specify bind address instead of port

* fix: remove temporary testing file

* fix(errors): change error names to snake_case

* refactor(errors): split indexing error types, remove unused errors

* feat: add env variable checking at program start

This just checks whether the enviroment variables exist and can
parse to the given type and gives a warning if they can't. This
should prevent cases where the program fails at runtime due to
checking an environment variable that doesn't exist.
2020-07-03 10:44:39 -07:00
GeometricallyandGitHub 91305262f1 Add Backblaze Driver (#32)
* Backblaze Driver

* Update action to work with new tests

* Fix minor issues

* Run Formatter + Switch to reqwest json parser
2020-07-02 14:00:04 +02:00
AeledfyrandGitHub 6d16b68f11 Create schema for the API (#28)
* feat(schema): add basic structs for schema

* feat(schema): implement base62 id parsing

* docs(schema): add documentation for schema structs
fix(schema): prevent integer overflow in base62 decoding

* refactor(schema): move ids into submodules, reexport from ids mod

* feat(schema): add random generation of base62 ids
style: run rustfmt
2020-07-01 22:24:42 +02:00
Charalampos FanoulisandGitHub f22e4f1cc7 feat: Add label syncing. (#30)
* ci: Add label syncing

* ci: add more label sprinkles
2020-07-01 11:32:23 -07:00
MulverineXandGitHub 2851d12357 readme edits (#29)
* Update README.md

* Update README.md
2020-07-01 08:43:44 +02:00
Valentin RicardandGitHub 73968e4277 fix(env): Hotfixed the env name (#27) 2020-06-30 20:00:41 +02:00
7a6ecd86c6 Rewrite the app (#23)
* chore: Removed everything not needed, and added base for rewrite
feat(error_handling): Added 404 general cache
feat(index): Added informations about the app in the / route.

* feat(indexing): Brought back the indexing, with conditions to make it easier

* fix: Fixed build error with a forgotten call

* feat: Add Docker development enviroment (#19)

* ci: add a *lot* of new actions

* fix: rename linting action

* fix: invalid yaml begone(?)

* ci: Added cache to speed up build times

* fix(ci): 🦀ed the yaml errors

* fix(ci): fixed a missing hyphen

* ci: Added matrix of rust versions, and changed way to install rust toolchain

* fix(ci): Added names to build with the matrix so it's easier to find the source of the problem

* style(ci): Added eof lines

* refactor: Finished moving the search.rs file to a separate module.

* Search Endpoint

* refactor: Moved around functions and struct for a better understanding of what it does.

* chore: Change env default settings to resolve conversation

* refactor: Removed #[use_macros]
fix: Fixed meilisearch address from env

* chore: Added email to Aeledfyr

* fix: Brought back the dotenv variables

* style: Ran `cargo fmt`

Co-authored-by: Charalampos Fanoulis <charalampos.fanoulis@gmail.com>
Co-authored-by: Jai A <jai.a@tuta.io>
2020-06-30 19:23:52 +02:00
Jai A 1ff2a08d19 Final push before rewrite 2020-06-28 13:53:03 -07:00
GeometricallyandGitHub d1efd62e7b Merge pull request #14 from Aeledfyr/master
Minify icons and implement SVG spritesheets
2020-06-27 20:18:01 -07:00
Aeledfyr 366c95cd3c Fix a few display bugs 2020-06-27 17:30:35 -05:00
Aeledfyr 7e03f3958e Implement SVG sprite sheet for icons, use thumbnails for mod icons 2020-06-27 17:30:34 -05:00
Aeledfyr e069184721 Compress/Minify icons 2020-06-27 17:26:59 -05:00
GeometricallyandGitHub 5182441cb3 Merge pull request #12 from joaoh1/master
Make category badges more generic
2020-06-27 15:06:02 -07:00
joaoh1 0de55a8ff5 Make category badges more generic 2020-06-27 16:22:09 -03:00
Jai A 0900d7c764 Add staging popup 2020-06-27 10:29:49 -07:00
Jai A 8540e09ba7 Fix #11 and Cleanup dependencies 2020-06-26 22:35:20 -07:00
Jai A 6e301601f9 Fix minor styling issues 2020-06-25 22:40:29 -07:00
GeometricallyandGitHub 1bf0eab2d9 Merge pull request #7 from Aeledfyr/master
Improve styling for narrower screens
2020-06-25 19:23:16 -07:00
Aeledfyr d560f656f4 Reduce duplication of Forge logo using svg <use>
This uses svg <use href="#..."> to avoid having to duplicate the
logo at every use.  Surprisingly this just works with the current
theme css, and doesn't require any changes.
2020-06-25 21:14:50 -05:00
Aeledfyr 0bc256aa23 Miscellaneous fixes and requested changes 2020-06-25 20:19:07 -05:00
Aeledfyr ebc073a52e Tweak styles for narrower screens, add indicator for no results
Makes the styles of search results work better with narrower
screens.  The category badges on each result collapse to just an
icon (with title text) when the screen is too narrow.

Adds a text label for the Forge/Fabric icons.

Adds a message for when a query returns no results.
2020-06-25 17:14:12 -05:00
Jai A 23503dc439 Base creation page 2020-06-22 21:04:17 -07:00
GeometricallyandGitHub 1f4985c7dd Merge pull request #6 from emilyploszaj/master
Implemented a dark theme
2020-06-22 19:57:37 -07:00
emilyploszaj ed88d9e10d Implemented a dark theme 2020-06-22 21:48:42 -05:00
Jai A 906196bac3 Staging Test 2020-06-20 18:19:19 -07:00
Jai A cb9751be04 Add CONTRIBUTING.md 2020-06-19 20:19:19 -07:00
GeometricallyandGitHub 09e03d579b Merge pull request #4 from Geometrically/dependabot/add-v2-config-file
Create Dependabot config file
2020-06-18 22:21:47 -07:00
dependabot-preview[bot]andGitHub 9fcca2698e Create Dependabot config file 2020-06-19 05:21:30 +00:00
GeometricallyandGitHub bba8cd9b58 Merge pull request #2 from Geometrically/dependabot/cargo/bson-1.0.0
Update bson requirement from 0.14.1 to 1.0.0
2020-06-18 22:21:07 -07:00
dependabot-preview[bot]andGitHub c8bb30289a Update bson requirement from 0.14.1 to 1.0.0
Updates the requirements on [bson](https://github.com/mongodb/bson-rust) to permit the latest version.
- [Release notes](https://github.com/mongodb/bson-rust/releases)
- [Commits](https://github.com/mongodb/bson-rust/compare/v0.14.1...v1.0.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-06-19 05:13:06 +00:00
GeometricallyandGitHub 69f56c1eb7 Merge pull request #3 from Geometrically/dependabot/cargo/mongodb-1.0.0
Update mongodb requirement from 0.10.0 to 1.0.0
2020-06-18 22:11:42 -07:00
dependabot-preview[bot]andGitHub adc8e23356 Update mongodb requirement from 0.10.0 to 1.0.0
Updates the requirements on [mongodb](https://github.com/mongodb/mongo-rust-driver) to permit the latest version.
- [Release notes](https://github.com/mongodb/mongo-rust-driver/releases)
- [Commits](https://github.com/mongodb/mongo-rust-driver/compare/v0.10.0...v1.0.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-06-19 05:09:44 +00:00
Jai A 88005c6603 API Page 2020-06-09 21:57:52 -07:00
Jai A 33ee4c36b4 API Page base 2020-06-05 20:19:19 -07:00
Jai A 1a142b7fe6 Versions Search 2020-06-04 20:19:19 -07:00
Jai A 90373806c0 Main mod page 2020-06-03 20:19:19 -07:00
Jai A a47634bf49 Static content serving from non-root routes 2020-06-02 20:59:20 -07:00
Jai A e03e58323b Cleanup + Database code 2020-06-02 12:03:45 -07:00
Jai A ab1e31c0e7 Finish search 2020-06-01 17:01:13 -07:00
Jai A aa5505d693 Edit CF indexer 2020-05-31 22:20:16 -07:00
Jai A 74f8f687cf Github Actions checks 2020-05-30 17:09:24 -07:00
Jai A d54500bad5 Sort Types 2020-05-30 16:53:56 -07:00
Jai A 4966b4d58d Sort by WIP 2020-05-29 22:21:53 -07:00
Jai A b75a4667c2 Migrate to MongoDB 2020-05-28 13:28:58 -07:00
GeometricallyandGitHub 14579a9320 Merge pull request #1 from Scotsguy/master
Fix clippy warnings & rustfmt
2020-05-27 12:28:57 -07:00
AppleTheGolden 1d92eff974 Fix serde attributes 2020-05-27 21:25:39 +02:00
AppleTheGolden 91274267e5 Run rustfmt on everything 2020-05-27 20:51:28 +02:00
AppleTheGolden c49f0ede16 Fix clippy warnings 2020-05-27 20:46:59 +02:00
Jai A 42a0f452b1 FOSS 2020-05-27 11:03:19 -07:00
Jai A c24ab9831a Add categories + Infinite Scroll 2020-05-23 21:06:36 -07:00
Jai A 506a68ee6a Fixes + Finish Indexer 2020-05-22 22:48:17 -07:00
Jai A 1291f792ab Curseforge Indexer 2020-05-21 22:38:39 -07:00
Jai A 51cfb1903d CF Indexer start 2020-05-20 22:32:57 -07:00
Jai A 1b1c15694a Fix Mod page styling 2020-05-19 22:39:05 -07:00
Jai A 251f1941a9 Finish mod main page 2020-05-18 22:35:03 -07:00
Jai A 625617bb20 Mod navigation bar 2020-05-18 22:20:34 -07:00
Jai A f80f161886 Start mods page 2020-05-16 22:37:02 -07:00
Jai A 4b4889d5f2 Finish database code 2020-05-15 22:00:47 -07:00
Jai A fee34ba257 Basic Database 2020-05-14 22:53:22 -07:00
Jai A c29ab25dd2 Version Selector 2020-05-13 22:34:19 -07:00
Jai A e0308a11c9 Search filters 2020-05-12 22:27:31 -07:00
Jai A a738998c2c Badges + Refactoring 2020-05-11 22:53:08 -07:00
Jai A 6be22c474d Working Search 2020-05-10 22:30:28 -07:00
Jai A da19743ba5 Initial Commit 2020-05-09 22:11:48 -07:00
1262 changed files with 143841 additions and 26922 deletions
+1
View File
@@ -0,0 +1 @@
* text=auto eol=lf
+1 -1
View File
@@ -56,4 +56,4 @@ body:
label: Additional context
description: Add any other context about the problem here.
validations:
required: false
required: false
+2 -2
View File
@@ -1,6 +1,6 @@
name: 🛠️ API issue (api.modrinth.com)
description: Report an issue regarding the Modrinth API.
labels: [bug, api]
labels: [bug, backend]
body:
- type: checkboxes
attributes:
@@ -38,4 +38,4 @@ body:
label: Additional context
description: Add any other context about the problem here.
validations:
required: false
required: false
@@ -9,8 +9,6 @@ body:
options:
- label: I checked the [existing issues](https://github.com/modrinth/code/issues) for duplicate feature requests
required: true
- label: I checked the [existing discussions](https://github.com/orgs/modrinth/discussions) for duplicate feature requests
required: true
- label: I have checked that this feature request is not on our [roadmap](https://roadmap.modrinth.com)
required: true
- type: dropdown
+1 -1
View File
@@ -11,4 +11,4 @@ contact_links:
url: https://roadmap.modrinth.com
- name: 📚 Documentation
about: Useful documentation about Modrinth's API
url: https://docs.modrinth.com
url: https://docs.modrinth.com
+42
View File
@@ -0,0 +1,42 @@
name: daedalus-docker-build
on:
push:
branches:
- '**'
paths:
- .github/workflows/daedalus-docker.yml
- 'apps/daedalus_client/**'
pull_request:
types: [opened, synchronize]
paths:
- .github/workflows/daedalus-docker.yml
- 'apps/daedalus_client/**'
merge_group:
types: [checks_requested]
jobs:
docker:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Fetch docker metadata
id: docker_meta
uses: docker/metadata-action@v3
with:
images: ghcr.io/modrinth/daedalus
- name: Login to GitHub Images
uses: docker/login-action@v1
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push
id: docker_build
uses: docker/build-push-action@v2
with:
file: ./apps/daedalus_client/Dockerfile
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.docker_meta.outputs.tags }}
labels: ${{ steps.docker_meta.outputs.labels }}
+51
View File
@@ -0,0 +1,51 @@
name: Run Meta
on:
schedule:
- cron: '*/5 * * * *'
workflow_dispatch:
jobs:
run-docker:
if: github.repository_owner == 'modrinth'
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v3
- name: Log in to GitHub Container Registry
uses: docker/login-action@v2
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Pull Docker image from GHCR
run: docker pull ghcr.io/modrinth/daedalus:main
- name: Run Docker container
env:
BASE_URL: ${{ secrets.BASE_URL }}
S3_ACCESS_TOKEN: ${{ secrets.S3_ACCESS_TOKEN }}
S3_SECRET: ${{ secrets.S3_SECRET }}
S3_URL: ${{ secrets.S3_URL }}
S3_REGION: ${{ secrets.S3_REGION }}
S3_BUCKET_NAME: ${{ secrets.S3_BUCKET_NAME }}
CLOUDFLARE_INTEGRATION: ${{ secrets.CLOUDFLARE_INTEGRATION }}
CLOUDFLARE_TOKEN: ${{ secrets.CLOUDFLARE_TOKEN }}
CLOUDFLARE_ZONE_ID: ${{ secrets.CLOUDFLARE_ZONE_ID }}
run: |
docker run \
--name daedalus \
-e RUST_LOG=warn,daedalus_client=trace \
-e BASE_URL=$BASE_URL \
-e S3_ACCESS_TOKEN=$S3_ACCESS_TOKEN \
-e S3_SECRET=$S3_SECRET \
-e S3_URL=$S3_URL \
-e S3_REGION=$S3_REGION \
-e S3_BUCKET_NAME=$S3_BUCKET_NAME \
-e CLOUDFLARE_INTEGRATION=$CLOUDFLARE_INTEGRATION \
-e CLOUDFLARE_TOKEN=$CLOUDFLARE_TOKEN \
-e CLOUDFLARE_ZONE_ID=$CLOUDFLARE_ZONE_ID \
ghcr.io/modrinth/daedalus:main
+5 -3
View File
@@ -1,6 +1,9 @@
name: Deploy frontend
name: Clear pages cache
on: push
on:
push:
branches:
- prod
jobs:
wait:
@@ -16,7 +19,6 @@ jobs:
apiToken: ${{ secrets.CF_API_TOKEN }}
accountId: '9ddae624c98677d68d93df6e524a6061'
project: 'frontend'
githubToken: ${{ secrets.GITHUB_TOKEN }}
commitHash: ${{ steps.push-changes.outputs.commit-hash }}
- name: Purge cache
if: github.ref == 'refs/heads/prod'
+45
View File
@@ -0,0 +1,45 @@
name: docker-build
on:
push:
branches:
- '**'
paths:
- .github/workflows/labrinth-docker.yml
- 'apps/labrinth/**'
pull_request:
types: [opened, synchronize]
paths:
- .github/workflows/labrinth-docker.yml
- 'apps/labrinth/**'
merge_group:
types: [checks_requested]
jobs:
docker:
runs-on: ubuntu-latest
defaults:
run:
working-directory: ./apps/labrinth
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Fetch docker metadata
id: docker_meta
uses: docker/metadata-action@v3
with:
images: ghcr.io/modrinth/labrinth
- name: Login to GitHub Images
uses: docker/login-action@v1
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push
id: docker_build
uses: docker/build-push-action@v2
with:
context: ./apps/labrinth
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.docker_meta.outputs.tags }}
labels: ${{ steps.docker_meta.outputs.labels }}
@@ -21,7 +21,7 @@ jobs:
strategy:
fail-fast: false
matrix:
platform: [macos-latest, windows-latest, ubuntu-22.04]
platform: [macos-latest, self-hosted, ubuntu-22.04]
runs-on: ${{ matrix.platform }}
@@ -44,7 +44,28 @@ jobs:
- name: Setup rust cache
uses: actions/cache@v4
with:
path: target/**
path: |
target/**
!target/*/release/bundle/*/*.dmg
!target/*/release/bundle/*/*.app.tar.gz
!target/*/release/bundle/*/*.app.tar.gz.sig
!target/release/bundle/*/*.dmg
!target/release/bundle/*/*.app.tar.gz
!target/release/bundle/*/*.app.tar.gz.sig
!target/release/bundle/appimage/*.AppImage
!target/release/bundle/appimage/*.AppImage.tar.gz
!target/release/bundle/appimage/*.AppImage.tar.gz.sig
!target/release/bundle/deb/*.deb
!target/release/bundle/rpm/*.rpm
!target/release/bundle/msi/*.msi
!target/release/bundle/msi/*.msi.zip
!target/release/bundle/msi/*.msi.zip.sig
!target/release/bundle/nsis/*.exe
!target/release/bundle/nsis/*.nsis.zip
!target/release/bundle/nsis/*.nsis.zip.sig
key: ${{ runner.os }}-rust-target-${{ hashFiles('**/Cargo.lock') }}
restore-keys: |
${{ runner.os }}-rust-target-
@@ -78,7 +99,7 @@ jobs:
if: startsWith(matrix.platform, 'ubuntu')
run: |
sudo apt-get update
sudo apt-get install -y libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf libssl-dev sqlite3
sudo apt-get install -y libwebkit2gtk-4.1-dev build-essential curl wget file libxdo-dev libssl-dev pkg-config libayatana-appindicator3-dev librsvg2-dev
- name: Install frontend dependencies
run: pnpm install
@@ -106,6 +127,7 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
DIGICERT_PASSWORD: ${{ secrets.DIGICERT_PASSWORD }}
- name: upload ${{ matrix.platform }}
uses: actions/upload-artifact@v4
@@ -124,8 +146,11 @@ jobs:
target/release/bundle/*/*.AppImage.tar.gz.sig
target/release/bundle/*/*.deb
target/release/bundle/*/*.rpm
target/release/bundle/*/*.msi
target/release/bundle/*/*.msi.zip
target/release/bundle/*/*.msi.zip.sig
target/release/bundle/msi/*.msi
target/release/bundle/msi/*.msi.zip
target/release/bundle/msi/*.msi.zip.sig
target/release/bundle/nsis/*.exe
target/release/bundle/nsis/*.nsis.zip
target/release/bundle/nsis/*.nsis.zip.sig
@@ -2,11 +2,11 @@ name: CI
on:
push:
branches: ["main"]
branches: ['main']
pull_request:
types: [opened, synchronize]
merge_group:
types: [ checks_requested ]
types: [checks_requested]
jobs:
build:
@@ -62,9 +62,19 @@ jobs:
- name: Build
run: pnpm build
env:
SQLX_OFFLINE: true
- name: Lint
run: pnpm lint
env:
SQLX_OFFLINE: true
- name: Start docker compose
run: docker compose up -d
- name: Test
run: pnpm test
env:
SQLX_OFFLINE: true
DATABASE_URL: postgresql://labrinth:labrinth@localhost/postgres
+6
View File
@@ -55,3 +55,9 @@ generated
# app testing dir
app-playground-data/*
# soley because i need the PORT to be 3002 due to WSL stuff
.env
apps/frontend/.env
.astro
+8
View File
@@ -0,0 +1,8 @@
# Default ignored files
/shelf/
/workspace.xml
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml
# Editor-based HTTP Client requests
/httpRequests/
+18
View File
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/apps/daedalus_client/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/packages/daedalus/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/apps/app-playground/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/apps/app/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/apps/labrinth/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/apps/labrinth/tests" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/packages/app-lib/src" isTestSource="false" />
<excludeFolder url="file://$MODULE_DIR$/target" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>
+7
View File
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DiscordProjectSettings">
<option name="show" value="PROJECT_FILES" />
<option name="description" value="" />
</component>
</project>
+26
View File
@@ -0,0 +1,26 @@
<component name="libraryTable">
<library name="KotlinJavaRuntime" type="repository">
<properties maven-id="org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.8.0" />
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/1.8.0/kotlin-stdlib-jdk8-1.8.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.8.0/kotlin-stdlib-1.8.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-common/1.8.0/kotlin-stdlib-common-1.8.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/1.8.0/kotlin-stdlib-jdk7-1.8.0.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/1.8.0/kotlin-stdlib-jdk8-1.8.0-javadoc.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.8.0/kotlin-stdlib-1.8.0-javadoc.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-common/1.8.0/kotlin-stdlib-common-1.8.0-javadoc.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0-javadoc.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/1.8.0/kotlin-stdlib-jdk7-1.8.0-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/1.8.0/kotlin-stdlib-jdk8-1.8.0-sources.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.8.0/kotlin-stdlib-1.8.0-sources.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-common/1.8.0/kotlin-stdlib-common-1.8.0-sources.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0-sources.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/1.8.0/kotlin-stdlib-jdk7-1.8.0-sources.jar!/" />
</SOURCES>
</library>
</component>
+8
View File
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/code.iml" filepath="$PROJECT_DIR$/.idea/code.iml" />
</modules>
</component>
</project>
Generated
+6
View File
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>
+1 -6
View File
@@ -1,12 +1,7 @@
{
"prettier.endOfLine": "lf",
"editor.formatOnSave": true,
"eslint.validate": [
"javascript",
"javascriptreact",
"typescript",
"typescriptreact"
],
"eslint.validate": ["javascript", "javascriptreact", "typescript", "typescriptreact"],
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit"
}
Generated
+4934 -767
View File
File diff suppressed because it is too large Load Diff
+7 -1
View File
@@ -3,7 +3,10 @@ resolver = '2'
members = [
'./packages/app-lib',
'./apps/app-playground',
'./apps/app'
'./apps/app',
'./apps/labrinth',
'./apps/daedalus_client',
'./packages/daedalus',
]
# Optimize for speed and reduce size on release builds
@@ -16,3 +19,6 @@ strip = true # Remove debug symbols
[profile.dev.package.sqlx-macros]
opt-level = 3
[patch.crates-io]
wry = { git = "https://github.com/modrinth/wry", rev ="cdbf938" }
+1 -1
View File
@@ -22,7 +22,7 @@ This repository contains two primary packages. For detailed development informat
## Contributing
We welcome contributions! Before submitting any contributions, please read our [contributing guidelines](https://support.modrinth.com/en/articles/8802215-contributing-to-modrinth).
We welcome contributions! Before submitting any contributions, please read our [contributing guidelines](https://docs.modrinth.com/contributing/getting-started/).
If you plan to fork this repository for your own purposes, please review our [copying guidelines](COPYING.md).
-4
View File
@@ -1,4 +0,0 @@
module.exports = {
root: true,
extends: ['custom/vue'],
}
+22
View File
@@ -0,0 +1,22 @@
import { createConfigForNuxt } from '@nuxt/eslint-config/flat'
import { fixupPluginRules } from '@eslint/compat'
import turboPlugin from 'eslint-plugin-turbo'
export default createConfigForNuxt().append([
{
name: 'turbo',
plugins: {
turbo: fixupPluginRules(turboPlugin),
},
rules: {
'turbo/no-undeclared-env-vars': 'error',
},
},
{
name: 'modrinth',
rules: {
'vue/html-self-closing': 'off',
'vue/multi-word-component-names': 'off',
},
},
])
+1 -1
View File
@@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Modrinth App</title>
<link rel="stylesheet" href="/src/assets/stylesheets/global.scss" />
+25 -16
View File
@@ -1,48 +1,57 @@
{
"name": "@modrinth/app-frontend",
"private": true,
"version": "0.8.3-1",
"version": "0.9.2",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"build": "vue-tsc --noEmit && vite build",
"tsc:check": "vue-tsc --noEmit",
"lint": "eslint . && prettier --check .",
"fix": "eslint . --fix && prettier --write ."
"fix": "eslint . --fix && prettier --write .",
"intl:extract": "formatjs extract \"{,src/components,src/composables,src/helpers,src/pages,src/store}/**/*.{vue,ts,tsx,js,jsx,mts,cts,mjs,cjs}\" --ignore '**/*.d.ts' --ignore 'node_modules' --out-file src/locales/en-US/index.json --format crowdin --preserve-whitespace"
},
"dependencies": {
"@modrinth/assets": "workspace:*",
"@modrinth/ui": "workspace:*",
"@modrinth/utils": "workspace:*",
"@tauri-apps/api": "^2.0.0-rc.3",
"@tauri-apps/plugin-dialog": "^2.0.0-rc.0",
"@tauri-apps/plugin-os": "^2.0.0-rc.0",
"@tauri-apps/plugin-window-state": "^2.0.0-rc.0",
"@tauri-apps/plugin-shell": "^2.0.0-rc.0",
"@sentry/vue": "^8.27.0",
"@tauri-apps/api": "^2.1.1",
"@tauri-apps/plugin-dialog": "^2.2.0",
"@tauri-apps/plugin-os": "^2.2.0",
"@tauri-apps/plugin-opener": "^2.2.1",
"@tauri-apps/plugin-updater": "^2.3.0",
"@tauri-apps/plugin-window-state": "^2.2.0",
"@vintl/vintl": "^4.4.1",
"dayjs": "^1.11.10",
"floating-vue": "^5.2.2",
"ofetch": "^1.3.4",
"pinia": "^2.1.7",
"posthog-js": "^1.158.2",
"vite-svg-loader": "^5.1.0",
"vue": "^3.4.21",
"vue": "^3.5.13",
"vue-multiselect": "3.0.0",
"vue-router": "4.3.0",
"vue-virtual-scroller": "v2.0.0-beta.8",
"posthog-js": "^1.158.2",
"@sentry/vue": "^8.27.0"
"vue-virtual-scroller": "v2.0.0-beta.8"
},
"devDependencies": {
"@tauri-apps/cli": "^2.0.0-rc",
"@eslint/compat": "^1.1.1",
"@formatjs/cli": "^6.2.12",
"@nuxt/eslint-config": "^0.5.6",
"@vitejs/plugin-vue": "^5.0.4",
"autoprefixer": "^10.4.19",
"eslint": "^8.57.0",
"eslint": "^9.9.1",
"eslint-config-custom": "workspace:*",
"eslint-plugin-turbo": "^2.1.1",
"postcss": "^8.4.39",
"prettier": "^3.2.5",
"sass": "^1.74.1",
"tailwindcss": "^3.4.4",
"tsconfig": "workspace:*",
"vite": "^5.2.8"
"typescript": "^5.5.4",
"vite": "^5.4.6",
"vue-tsc": "^2.1.6"
},
"packageManager": "pnpm@9.4.0"
"packageManager": "pnpm@9.4.0",
"web-types": "../../web-types.json"
}
+543 -233
View File
@@ -1,9 +1,26 @@
<script setup>
import { computed, ref, onMounted } from 'vue'
import { RouterView, RouterLink, useRouter, useRoute } from 'vue-router'
import { HomeIcon, SearchIcon, LibraryIcon, PlusIcon, SettingsIcon, XIcon } from '@modrinth/assets'
import { Button, Notifications } from '@modrinth/ui'
import { computed, onMounted, onUnmounted, ref, watch } from 'vue'
import { RouterView, useRoute, useRouter } from 'vue-router'
import {
ArrowBigUpDashIcon,
CompassIcon,
DownloadIcon,
HomeIcon,
LeftArrowIcon,
LibraryIcon,
LogInIcon,
LogOutIcon,
MaximizeIcon,
MinimizeIcon,
PlusIcon,
RestoreIcon,
RightArrowIcon,
SettingsIcon,
XIcon,
} from '@modrinth/assets'
import { Avatar, Button, ButtonStyled, Notifications, OverflowMenu } from '@modrinth/ui'
import { useLoading, useTheming } from '@/store/state'
import ModrinthAppLogo from '@/assets/modrinth_app.svg?component'
import AccountsCard from '@/components/ui/AccountsCard.vue'
import InstanceCreationModal from '@/components/ui/InstanceCreationModal.vue'
import { get } from '@/helpers/settings'
@@ -11,17 +28,16 @@ import Breadcrumbs from '@/components/ui/Breadcrumbs.vue'
import RunningAppBar from '@/components/ui/RunningAppBar.vue'
import SplashScreen from '@/components/ui/SplashScreen.vue'
import ErrorModal from '@/components/ui/ErrorModal.vue'
import ModrinthLoadingIndicator from '@/components/modrinth-loading-indicator'
import ModrinthLoadingIndicator from '@/components/LoadingIndicatorBar.vue'
import { handleError, useNotifications } from '@/store/notifications.js'
import { command_listener, warning_listener } from '@/helpers/events.js'
import { MinimizeIcon, MaximizeIcon } from '@/assets/icons'
import { type } from '@tauri-apps/plugin-os'
import { isDev, getOS } from '@/helpers/utils.js'
import { initAnalytics, debugAnalytics, optOutAnalytics, trackEvent } from '@/helpers/analytics'
import { getOS, isDev, restartApp } from '@/helpers/utils.js'
import { debugAnalytics, initAnalytics, optOutAnalytics, trackEvent } from '@/helpers/analytics'
import { getCurrentWindow } from '@tauri-apps/api/window'
import { getVersion } from '@tauri-apps/api/app'
import URLConfirmModal from '@/components/ui/URLConfirmModal.vue'
import { install_from_file } from './helpers/pack'
import { create_profile_and_install_from_file } from './helpers/pack'
import { useError } from '@/store/error.js'
import { useCheckDisableMouseover } from '@/composables/macCssFix.js'
import ModInstallModal from '@/components/ui/install_flow/ModInstallModal.vue'
@@ -29,11 +45,26 @@ import IncompatibilityWarningModal from '@/components/ui/install_flow/Incompatib
import InstallConfirmModal from '@/components/ui/install_flow/InstallConfirmModal.vue'
import { useInstall } from '@/store/install.js'
import { invoke } from '@tauri-apps/api/core'
import { open } from '@tauri-apps/plugin-shell'
import { get_opening_command, initialize_state } from '@/helpers/state'
import { saveWindowState, StateFlags } from '@tauri-apps/plugin-window-state'
import { renderString } from '@modrinth/utils'
import { useFetch } from '@/helpers/fetch.js'
import { check } from '@tauri-apps/plugin-updater'
import NavButton from '@/components/ui/NavButton.vue'
import { get as getCreds, login, logout } from '@/helpers/mr_auth.js'
import { get_user } from '@/helpers/cache.js'
import AppSettingsModal from '@/components/ui/modal/AppSettingsModal.vue'
import dayjs from 'dayjs'
import PromotionWrapper from '@/components/ui/PromotionWrapper.vue'
import { hide_ads_window, init_ads_window } from '@/helpers/ads.js'
import FriendsList from '@/components/ui/friends/FriendsList.vue'
import { openUrl } from '@tauri-apps/plugin-opener'
import QuickInstanceSwitcher from '@/components/ui/QuickInstanceSwitcher.vue'
const themeStore = useTheming()
const news = ref([])
const urlModal = ref(null)
const offline = ref(!navigator.onLine)
@@ -51,8 +82,20 @@ const os = ref('')
const stateInitialized = ref(false)
const criticalErrorMessage = ref()
const isMaximized = ref(false)
onMounted(async () => {
await useCheckDisableMouseover()
document.querySelector('body').addEventListener('click', handleClick)
document.querySelector('body').addEventListener('auxclick', handleAuxClick)
})
onUnmounted(() => {
document.querySelector('body').removeEventListener('click', handleClick)
document.querySelector('body').removeEventListener('auxclick', handleAuxClick)
})
async function setupApp() {
@@ -65,10 +108,13 @@ async function setupApp() {
advanced_rendering,
onboarded,
default_page,
toggle_sidebar,
developer_mode,
feature_flags,
} = await get()
if (default_page && default_page !== 'Home') {
await router.push({ name: default_page })
if (default_page === 'Library') {
await router.push('/library')
}
os.value = await getOS()
@@ -82,6 +128,15 @@ async function setupApp() {
themeStore.setThemeState(theme)
themeStore.collapsedNavigation = collapsed_navigation
themeStore.advancedRendering = advanced_rendering
themeStore.toggleSidebar = toggle_sidebar
themeStore.devMode = developer_mode
themeStore.featureFlags = feature_flags
isMaximized.value = await getCurrentWindow().isMaximized()
await getCurrentWindow().onResized(async () => {
isMaximized.value = await getCurrentWindow().isMaximized()
})
initAnalytics()
if (!telemetry) {
@@ -107,7 +162,25 @@ async function setupApp() {
}),
)
useFetch(
`https://api.modrinth.com/appCriticalAnnouncement.json?version=${version}`,
'criticalAnnouncements',
true,
).then((res) => {
if (res && res.header && res.body) {
criticalErrorMessage.value = res
}
})
useFetch(`https://modrinth.com/blog/news.json`, 'news', true).then((res) => {
if (res && res.articles) {
news.value = res.articles
}
})
get_opening_command().then(handleCommand)
checkUpdates()
fetchCredentials()
}
const stateFailed = ref(false)
@@ -126,6 +199,7 @@ initialize_state()
})
const handleClose = async () => {
await saveWindowState(StateFlags.ALL)
await getCurrentWindow().close()
}
@@ -134,7 +208,6 @@ router.afterEach((to, from, failure) => {
trackEvent('PageView', { path: to.path, fromPath: from.path, failed: failure })
})
const route = useRoute()
const isOnBrowse = computed(() => route.path.startsWith('/browse'))
const loading = useLoading()
loading.setEnabled(false)
@@ -150,6 +223,58 @@ const modInstallModal = ref()
const installConfirmModal = ref()
const incompatibilityWarningModal = ref()
const credentials = ref()
async function fetchCredentials() {
const creds = await getCreds().catch(handleError)
if (creds && creds.user_id) {
creds.user = await get_user(creds.user_id).catch(handleError)
}
credentials.value = creds
}
async function signIn() {
await login().catch(handleError)
await fetchCredentials()
}
async function logOut() {
await logout().catch(handleError)
await fetchCredentials()
}
const MIDAS_BITFLAG = 1 << 0
const hasPlus = computed(
() =>
credentials.value &&
credentials.value.user &&
(credentials.value.user.badges & MIDAS_BITFLAG) === MIDAS_BITFLAG,
)
const sidebarToggled = ref(true)
themeStore.$subscribe(() => {
sidebarToggled.value = !themeStore.toggleSidebar
})
const forceSidebar = computed(
() => route.path.startsWith('/browse') || route.path.startsWith('/project'),
)
const sidebarVisible = computed(() => sidebarToggled.value || forceSidebar.value)
const showAd = computed(() => !(!sidebarVisible.value || hasPlus.value))
watch(
showAd,
() => {
if (!showAd.value) {
hide_ads_window(true)
} else {
init_ads_window(true)
}
},
{ immediate: true },
)
onMounted(() => {
invoke('show_window')
@@ -162,7 +287,40 @@ onMounted(() => {
install.setModInstallModal(modInstallModal)
})
document.querySelector('body').addEventListener('click', function (e) {
const accounts = ref(null)
command_listener(handleCommand)
async function handleCommand(e) {
if (!e) return
if (e.event === 'RunMRPack') {
// RunMRPack should directly install a local mrpack given a path
if (e.path.endsWith('.mrpack')) {
await create_profile_and_install_from_file(e.path).catch(handleError)
trackEvent('InstanceCreate', {
source: 'CreationModalFileDrop',
})
}
} else {
// Other commands are URL-based (deep linking)
urlModal.value.show(e)
}
}
const updateAvailable = ref(false)
async function checkUpdates() {
const update = await check()
updateAvailable.value = !!update
setTimeout(
() => {
checkUpdates()
},
5 * 1000 * 60,
)
}
function handleClick(e) {
let target = e.target
while (target != null) {
if (target.matches('a')) {
@@ -171,18 +329,19 @@ document.querySelector('body').addEventListener('click', function (e) {
['http://', 'https://', 'mailto:', 'tel:'].some((v) => target.href.startsWith(v)) &&
!target.classList.contains('router-link-active') &&
!target.href.startsWith('http://localhost') &&
!target.href.startsWith('https://tauri.localhost')
!target.href.startsWith('https://tauri.localhost') &&
!target.href.startsWith('http://tauri.localhost')
) {
open(target.href)
openUrl(target.href)
}
e.preventDefault()
break
}
target = target.parentElement
}
})
}
document.querySelector('body').addEventListener('auxclick', function (e) {
function handleAuxClick(e) {
// disables middle click -> new tab
if (e.button === 1) {
e.preventDefault()
@@ -194,85 +353,125 @@ document.querySelector('body').addEventListener('auxclick', function (e) {
})
e.target.dispatchEvent(event)
}
})
const accounts = ref(null)
command_listener(handleCommand)
async function handleCommand(e) {
if (!e) return
if (e.event === 'RunMRPack') {
// RunMRPack should directly install a local mrpack given a path
if (e.path.endsWith('.mrpack')) {
await install_from_file(e.path).catch(handleError)
trackEvent('InstanceCreate', {
source: 'CreationModalFileDrop',
})
}
} else {
// Other commands are URL-based (deep linking)
urlModal.value.show(e)
}
}
</script>
<template>
<SplashScreen v-if="!stateFailed" ref="splashScreen" data-tauri-drag-region />
<div v-if="stateInitialized" class="app-container">
<div class="nav-container">
<div class="nav-section">
<suspense>
<AccountsCard ref="accounts" mode="small" />
</suspense>
<div class="pages-list">
<RouterLink v-tooltip="'Home'" to="/" class="btn icon-only collapsed-button">
<HomeIcon />
</RouterLink>
<RouterLink
v-tooltip="'Browse'"
to="/browse/modpack"
class="btn icon-only collapsed-button"
:class="{
'router-link-active': isOnBrowse,
}"
>
<SearchIcon />
</RouterLink>
<RouterLink v-tooltip="'Library'" to="/library" class="btn icon-only collapsed-button">
<LibraryIcon />
</RouterLink>
<Suspense>
<InstanceCreationModal ref="installationModal" />
</Suspense>
</div>
</div>
<div class="settings pages-list">
<Button
v-tooltip="'Create profile'"
class="sleek-primary collapsed-button"
icon-only
:disabled="offline"
@click="() => $refs.installationModal.show()"
<div id="teleports"></div>
<div v-if="stateInitialized" class="app-grid-layout relative">
<Suspense>
<AppSettingsModal ref="settingsModal" />
</Suspense>
<Suspense>
<InstanceCreationModal ref="installationModal" />
</Suspense>
<div
class="app-grid-navbar bg-bg-raised flex flex-col p-[0.5rem] pt-0 gap-[0.5rem] w-[--left-bar-width]"
>
<NavButton v-tooltip.right="'Home'" to="/">
<HomeIcon />
</NavButton>
<NavButton
v-tooltip.right="'Discover content'"
to="/browse/modpack"
:is-primary="() => route.path.startsWith('/browse') && !route.query.i"
:is-subpage="(route) => route.path.startsWith('/project') && !route.query.i"
>
<CompassIcon />
</NavButton>
<NavButton
v-tooltip.right="'Library'"
to="/library"
:is-subpage="
() =>
route.path.startsWith('/instance') ||
((route.path.startsWith('/browse') || route.path.startsWith('/project')) &&
route.query.i)
"
>
<LibraryIcon />
</NavButton>
<div class="h-px w-6 mx-auto my-2 bg-button-bg"></div>
<suspense>
<QuickInstanceSwitcher />
</suspense>
<NavButton
v-tooltip.right="'Create new instance'"
:to="() => $refs.installationModal.show()"
:disabled="offline"
>
<PlusIcon />
</NavButton>
<div class="flex flex-grow"></div>
<NavButton v-if="updateAvailable" v-tooltip.right="'Install update'" :to="() => restartApp()">
<DownloadIcon />
</NavButton>
<NavButton v-tooltip.right="'Settings'" :to="() => $refs.settingsModal.show()">
<SettingsIcon />
</NavButton>
<ButtonStyled v-if="credentials" type="transparent" circular>
<OverflowMenu
:options="[
{
id: 'sign-out',
action: () => logOut(),
color: 'danger',
},
]"
direction="left"
>
<PlusIcon />
</Button>
<RouterLink v-tooltip="'Settings'" to="/settings" class="btn icon-only collapsed-button">
<SettingsIcon />
</RouterLink>
</div>
<Avatar
:src="credentials.user.avatar_url"
:alt="credentials.user.username"
size="32px"
circle
/>
<template #sign-out> <LogOutIcon /> Sign out </template>
</OverflowMenu>
</ButtonStyled>
<NavButton v-else v-tooltip.right="'Sign in'" :to="() => signIn()">
<LogInIcon />
<template #label>Sign in</template>
</NavButton>
</div>
<div class="view">
<div class="appbar-row">
<div data-tauri-drag-region class="appbar">
<section class="navigation-controls">
<Breadcrumbs data-tauri-drag-region />
</section>
<section class="mod-stats">
<Suspense>
<RunningAppBar />
</Suspense>
</section>
<div data-tauri-drag-region class="app-grid-statusbar bg-bg-raised h-[--top-bar-height] flex">
<div data-tauri-drag-region class="flex p-3">
<ModrinthAppLogo class="h-full w-auto text-contrast pointer-events-none" />
<div class="flex items-center gap-1 ml-3">
<button
class="cursor-pointer p-0 m-0 border-none outline-none bg-button-bg rounded-full flex items-center justify-center w-6 h-6 hover:brightness-75 transition-all"
@click="router.back()"
>
<LeftArrowIcon />
</button>
<button
class="cursor-pointer p-0 m-0 border-none outline-none bg-button-bg rounded-full flex items-center justify-center w-6 h-6 hover:brightness-75 transition-all"
@click="router.forward()"
>
<RightArrowIcon />
</button>
</div>
<Breadcrumbs class="pt-[2px]" />
</div>
<section class="flex ml-auto items-center">
<ButtonStyled
v-if="!forceSidebar && themeStore.toggleSidebar"
:type="sidebarToggled ? 'standard' : 'transparent'"
circular
>
<button
class="mr-3 transition-transform"
:class="{ 'rotate-180': !sidebarToggled }"
@click="sidebarToggled = !sidebarToggled"
>
<RightArrowIcon />
</button>
</ButtonStyled>
<div class="flex mr-3">
<Suspense>
<RunningAppBar />
</Suspense>
</div>
<section v-if="!nativeDecorations" class="window-controls">
<Button class="titlebar-button" icon-only @click="() => getCurrentWindow().minimize()">
@@ -283,30 +482,125 @@ async function handleCommand(e) {
icon-only
@click="() => getCurrentWindow().toggleMaximize()"
>
<MaximizeIcon />
<RestoreIcon v-if="isMaximized" />
<MaximizeIcon v-else />
</Button>
<Button class="titlebar-button close" icon-only @click="handleClose">
<XIcon />
</Button>
</section>
</section>
</div>
</div>
<div
v-if="stateInitialized"
class="app-contents experimental-styles-within"
:class="{ 'sidebar-enabled': sidebarVisible }"
>
<div class="app-viewport flex-grow router-view">
<div
class="loading-indicator-container h-8 fixed z-50"
:style="{
top: 'calc(var(--top-bar-height))',
left: 'calc(var(--left-bar-width))',
width: 'calc(100% - var(--left-bar-width) - var(--right-bar-width))',
}"
>
<ModrinthLoadingIndicator />
</div>
<div class="router-view">
<ModrinthLoadingIndicator
offset-height="var(--appbar-height)"
offset-width="var(--sidebar-width)"
/>
<RouterView v-slot="{ Component }">
<template v-if="Component">
<Suspense @pending="loading.startLoading()" @resolve="loading.stopLoading()">
<component :is="Component"></component>
</Suspense>
</template>
</RouterView>
<div
v-if="themeStore.featureFlags.page_path"
class="absolute bottom-0 left-0 m-2 bg-tooltip-bg text-tooltip-text font-semibold rounded-full px-2 py-1 text-xs z-50"
>
{{ route.fullPath }}
</div>
<div
id="background-teleport-target"
class="absolute h-full -z-10 rounded-tl-[--radius-xl] overflow-hidden"
:style="{
width: 'calc(100% - var(--right-bar-width))',
}"
></div>
<RouterView v-slot="{ Component }">
<template v-if="Component">
<Suspense @pending="loading.startLoading()" @resolve="loading.stopLoading()">
<component :is="Component"></component>
</Suspense>
</template>
</RouterView>
</div>
<div
class="app-sidebar mt-px shrink-0 flex flex-col border-0 border-l-[1px] border-[--brand-gradient-border] border-solid overflow-auto"
:class="{ 'has-plus': hasPlus }"
>
<div
class="app-sidebar-scrollable flex-grow shrink overflow-y-auto relative"
:class="{ 'pb-12': !hasPlus }"
>
<div id="sidebar-teleport-target" class="sidebar-teleport-content"></div>
<div class="sidebar-default-content" :class="{ 'sidebar-enabled': sidebarVisible }">
<div class="p-4 border-0 border-b-[1px] border-[--brand-gradient-border] border-solid">
<h3 class="text-lg m-0">Playing as</h3>
<suspense>
<AccountsCard ref="accounts" mode="small" />
</suspense>
</div>
<div class="p-4 border-0 border-b-[1px] border-[--brand-gradient-border] border-solid">
<suspense>
<FriendsList :credentials="credentials" :sign-in="() => signIn()" />
</suspense>
</div>
<div v-if="news && news.length > 0" class="pt-4 flex flex-col">
<h3 class="px-4 text-lg m-0">News</h3>
<template v-for="(item, index) in news" :key="`news-${index}`">
<a
:class="`flex flex-col outline-offset-[-4px] hover:bg-[--brand-gradient-border] focus:bg-[--brand-gradient-border] px-4 transition-colors ${index === 0 ? 'pt-2 pb-4' : 'py-4'}`"
:href="item.link"
target="_blank"
rel="external"
>
<img
:src="item.thumbnail"
alt="News thumbnail"
aria-hidden="true"
class="w-full aspect-[3/1] object-cover rounded-2xl border-[1px] border-solid border-[--brand-gradient-border]"
/>
<h4 class="mt-2 mb-0 text-sm leading-none text-contrast font-semibold">
{{ item.title }}
</h4>
<p class="my-1 text-sm text-secondary leading-tight">{{ item.summary }}</p>
<p class="text-right text-sm text-secondary opacity-60 leading-tight m-0">
{{ dayjs(item.date).fromNow() }}
</p>
</a>
<hr
v-if="index !== news.length - 1"
class="h-px my-[-2px] mx-4 border-0 m-0 bg-[--brand-gradient-border]"
/>
</template>
</div>
</div>
</div>
<template v-if="showAd">
<a
href="https://modrinth.plus?app"
class="absolute bottom-[250px] w-full flex justify-center items-center gap-1 px-4 py-3 text-purple font-medium hover:underline z-10"
target="_blank"
>
<ArrowBigUpDashIcon class="text-2xl" /> Upgrade to Modrinth+
</a>
<PromotionWrapper />
</template>
</div>
<div class="view">
<div v-if="criticalErrorMessage" class="critical-error-banner" data-tauri-drag-region>
<h1>{{ criticalErrorMessage.header }}</h1>
<div class="markdown-body" v-html="renderString(criticalErrorMessage.body ?? '')"></div>
</div>
</div>
</div>
<URLConfirmModal ref="urlModal" />
<Notifications ref="notificationsWrapper" />
<Notifications ref="notificationsWrapper" sidebar />
<ErrorModal ref="errorModal" />
<ModInstallModal ref="modInstallModal" />
<IncompatibilityWarningModal ref="incompatibilityWarningModal" />
@@ -314,21 +608,6 @@ async function handleCommand(e) {
</template>
<style lang="scss" scoped>
.sleek-primary {
background-color: var(--color-brand-highlight);
transition: all ease-in-out 0.1s;
}
.navigation-controls {
flex-grow: 1;
width: min-content;
}
.appbar-row {
display: flex;
flex-direction: row;
}
.window-controls {
z-index: 20;
display: none;
@@ -341,158 +620,188 @@ async function handleCommand(e) {
justify-content: center;
cursor: pointer;
transition: all ease-in-out 0.1s;
background-color: var(--color-raised-bg);
background-color: transparent;
color: var(--color-base);
border-radius: 0;
height: 3.25rem;
height: 100%;
width: 3rem;
position: relative;
box-shadow: none;
&:last-child {
padding-right: 0.75rem;
width: 3.75rem;
}
svg {
width: 1.25rem;
height: 1.25rem;
}
&::before {
content: '';
border-radius: 999999px;
width: 3rem;
height: 3rem;
aspect-ratio: 1 / 1;
margin-block: auto;
position: absolute;
background-color: transparent;
scale: 0.9;
transition: all ease-in-out 0.2s;
z-index: -1;
}
&.close {
&:hover,
&:active {
background-color: var(--color-red);
color: var(--color-accent-contrast);
&::before {
background-color: var(--color-red);
}
}
}
&:hover,
&:active {
background-color: var(--color-button-bg);
color: var(--color-contrast);
&::before {
background-color: var(--color-button-bg);
scale: 1;
}
}
}
}
.app-container {
--appbar-height: 3.25rem;
--sidebar-width: 4.5rem;
height: 100vh;
display: flex;
flex-direction: row;
overflow: hidden;
.view {
width: calc(100% - var(--sidebar-width));
background-color: var(--color-raised-bg);
.appbar {
display: flex;
align-items: center;
flex-grow: 1;
background: var(--color-raised-bg);
text-align: center;
padding: var(--gap-md);
height: 3.25rem;
gap: var(--gap-sm);
//no select
user-select: none;
-webkit-user-select: none;
}
.router-view {
width: 100%;
height: calc(100% - 3.125rem);
overflow: auto;
overflow-x: hidden;
background-color: var(--color-bg);
border-top-left-radius: var(--radius-xl);
}
}
.app-grid-layout,
.app-contents {
--top-bar-height: 3rem;
--left-bar-width: 4rem;
--right-bar-width: 300px;
}
.nav-container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-between;
height: 100%;
.app-grid-layout {
display: grid;
grid-template: 'status status' 'nav dummy';
grid-template-columns: auto 1fr;
grid-template-rows: auto 1fr;
position: relative;
//z-index: 0;
background-color: var(--color-raised-bg);
box-shadow: var(--shadow-inset-sm), var(--shadow-floating);
padding: var(--gap-md);
height: 100vh;
}
.pages-list {
display: flex;
flex-direction: column;
align-items: flex-start;
justify-content: flex-start;
width: 100%;
gap: 0.5rem;
.app-grid-navbar {
grid-area: nav;
}
a {
display: flex;
align-items: center;
word-spacing: 3px;
background: inherit;
transition: all ease-in-out 0.1s;
color: var(--color-base);
box-shadow: none;
.app-grid-statusbar {
grid-area: status;
}
&.router-link-active {
color: var(--color-contrast);
background: var(--color-button-bg);
box-shadow: var(--shadow-floating);
}
.app-contents {
position: absolute;
z-index: 1;
left: var(--left-bar-width);
top: var(--top-bar-height);
right: 0;
bottom: 0;
height: calc(100vh - var(--top-bar-height));
background-color: var(--color-bg);
border-top-left-radius: var(--radius-xl);
&:hover {
background-color: var(--color-button-bg);
color: var(--color-contrast);
box-shadow: 0px 4px 4px rgba(0, 0, 0, 0.25);
text-decoration: none;
}
}
display: grid;
grid-template-columns: 1fr 0px;
// transition: grid-template-columns 0.4s ease-in-out;
&.primary {
color: var(--color-accent-contrast);
background-color: var(--color-brand);
&.sidebar-enabled {
grid-template-columns: 1fr 300px;
}
}
.collapsed-button {
height: 3rem !important;
width: 3rem !important;
padding: 0.75rem;
border-radius: var(--radius-md);
box-shadow: none;
svg {
width: 1.5rem !important;
height: 1.5rem !important;
max-width: 1.5rem !important;
max-height: 1.5rem !important;
}
.loading-indicator-container {
border-top-left-radius: var(--radius-xl);
overflow: hidden;
}
.nav-section {
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: center;
width: 100%;
.app-sidebar {
overflow: visible;
width: 300px;
position: relative;
height: calc(100vh - var(--top-bar-height));
background: var(--brand-gradient-bg);
--color-button-bg: var(--brand-gradient-button);
--color-button-bg-hover: var(--brand-gradient-border);
--color-divider: var(--brand-gradient-border);
--color-divider-dark: var(--brand-gradient-border);
}
.app-sidebar::after {
content: '';
position: absolute;
bottom: 250px;
left: 0;
right: 0;
height: 5rem;
background: var(--brand-gradient-fade-out-color);
pointer-events: none;
}
.app-sidebar.has-plus::after {
display: none;
}
.app-sidebar::before {
content: '';
box-shadow: -15px 0 15px -15px rgba(0, 0, 0, 0.2) inset;
top: 0;
bottom: 0;
left: -2rem;
width: 2rem;
position: absolute;
pointer-events: none;
}
.app-viewport {
flex-grow: 1;
height: 100%;
gap: 1rem;
overflow: auto;
overflow-x: hidden;
}
.button-row {
display: flex;
flex-direction: row;
justify-content: space-between;
gap: var(--gap-md);
.app-contents::before {
z-index: 1;
content: '';
position: fixed;
left: var(--left-bar-width);
top: var(--top-bar-height);
right: calc(-1 * var(--left-bar-width));
bottom: calc(-1 * var(--left-bar-width));
border-radius: var(--radius-xl);
box-shadow:
1px 1px 15px rgba(0, 0, 0, 0.2) inset,
inset 1px 1px 1px rgba(255, 255, 255, 0.23);
pointer-events: none;
}
.transparent {
padding: var(--gap-sm) 0;
}
.sidebar-teleport-content {
display: contents;
}
.sidebar-default-content {
display: none;
}
.sidebar-teleport-content:empty + .sidebar-default-content.sidebar-enabled {
display: contents;
}
</style>
<style>
.mac {
.nav-container {
padding-top: calc(var(--gap-md) + 1.75rem);
}
.account-card,
.card-section {
top: calc(var(--gap-md) + 1.75rem);
.app-grid-statusbar {
padding-left: 5rem;
}
}
@@ -514,3 +823,4 @@ async function handleCommand(e) {
}
}
</style>
<style src="vue-multiselect/dist/vue-multiselect.css"></style>
@@ -1,14 +1,9 @@
export { default as ServerIcon } from './server.svg'
export { default as MinimizeIcon } from './minimize.svg'
export { default as MaximizeIcon } from './maximize.svg'
export { default as SwapIcon } from './arrow-left-right.svg'
export { default as ToggleIcon } from './toggle.svg'
export { default as PackageIcon } from './package.svg'
export { default as VersionIcon } from './milestone.svg'
export { default as MoreIcon } from './more.svg'
export { default as TextInputIcon } from './text-cursor-input.svg'
export { default as AddProjectImage } from './add-project.svg'
export { default as NewInstanceImage } from './new-instance.svg'
export { default as MenuIcon } from './menu.svg'
export { default as BugIcon } from './bug.svg'
export { default as ChatIcon } from './messages-square.svg'
File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 12 KiB

@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:serif="http://www.serif.com/" version="1.1" viewBox="0 0 1793 199">
<g>
<g id="Layer_1">
<g id="green" fill="var(--color-brand)">
<path d="M1184.1,166.6c-8,0-15.6-1-22.9-3.1s-13.1-4.6-17.4-7.6l8.5-16.9c4.3,2.7,9.4,5,15.3,6.8,5.9,1.8,11.9,2.7,17.8,2.7s12.1-.9,15.2-2.8c3.1-1.9,4.7-4.5,4.7-7.7s-1.1-4.6-3.2-6c-2.1-1.4-4.9-2.4-8.4-3.1-3.4-.7-7.3-1.4-11.5-2-4.2-.6-8.4-1.4-12.6-2.4-4.2-1-8-2.5-11.5-4.5-3.4-2-6.2-4.6-8.4-7.9-2.1-3.3-3.2-7.7-3.2-13.2s1.7-11.3,5.2-15.8c3.4-4.5,8.3-7.9,14.5-10.3,6.2-2.4,13.6-3.7,22.2-3.7s12.9.7,19.4,2.1c6.5,1.4,11.9,3.4,16.2,6.1l-8.5,16.9c-4.5-2.7-9.1-4.6-13.6-5.6-4.6-1-9.1-1.5-13.6-1.5-6.8,0-11.8,1-15,3-3.3,2-4.9,4.6-4.9,7.7s1.1,5,3.2,6.4c2.1,1.4,4.9,2.6,8.4,3.4,3.4.8,7.3,1.5,11.5,2,4.2.5,8.4,1.3,12.6,2.4,4.2,1.1,8,2.5,11.5,4.4,3.5,1.8,6.3,4.4,8.5,7.7,2.1,3.3,3.2,7.7,3.2,13s-1.8,11.1-5.3,15.5c-3.5,4.4-8.5,7.8-14.9,10.2-6.4,2.4-14.1,3.7-23,3.7Z"/>
<path d="M1291.1,166.6c-10.6,0-19.8-2.1-27.7-6.3-7.9-4.2-14-10-18.3-17.4-4.3-7.4-6.5-15.7-6.5-25.1s2.1-17.9,6.3-25.2c4.2-7.3,10-13,17.5-17.2,7.4-4.2,15.9-6.2,25.4-6.2s17.5,2,24.8,6.1c7.2,4,12.9,9.7,17.1,17.1,4.2,7.4,6.2,16,6.2,26s0,2,0,3.2c0,1.2-.2,2.3-.3,3.4h-79.3v-14.8h67.5l-8.7,4.6c.1-5.5-1-10.3-3.4-14.4-2.4-4.2-5.6-7.4-9.7-9.8-4.1-2.4-8.8-3.6-14.2-3.6s-10.2,1.2-14.3,3.6c-4.1,2.4-7.3,5.7-9.6,9.9-2.3,4.2-3.5,9.2-3.5,14.9v3.6c0,5.7,1.3,10.7,3.9,15.1,2.6,4.4,6.3,7.8,11,10.2,4.7,2.4,10.2,3.6,16.4,3.6s10.2-.8,14.4-2.5c4.3-1.7,8.1-4.3,11.4-7.8l11.9,13.7c-4.3,5-9.6,8.8-16.1,11.5-6.5,2.7-13.9,4-22.2,4Z"/>
<path d="M1357.2,165.3v-95.1h21.2v26.2l-2.5-7.7c2.8-6.4,7.3-11.3,13.4-14.6,6.1-3.3,13.7-5,22.9-5v21.2c-1-.2-1.8-.4-2.7-.4-.8,0-1.7,0-2.5,0-8.4,0-15.1,2.5-20.1,7.4-5,4.9-7.5,12.3-7.5,22v46.1h-22.3Z"/>
<path d="M1460,165.3l-40.8-95.1h23.2l35.1,83.9h-11.4l36.3-83.9h21.4l-40.8,95.1h-23Z"/>
<path d="M1579.6,166.6c-10.6,0-19.8-2.1-27.7-6.3-7.9-4.2-14-10-18.3-17.4-4.3-7.4-6.5-15.7-6.5-25.1s2.1-17.9,6.3-25.2c4.2-7.3,10-13,17.5-17.2,7.4-4.2,15.9-6.2,25.4-6.2s17.5,2,24.8,6.1c7.2,4,12.9,9.7,17.1,17.1,4.2,7.4,6.2,16,6.2,26s0,2,0,3.2c0,1.2-.2,2.3-.3,3.4h-79.3v-14.8h67.5l-8.7,4.6c.1-5.5-1-10.3-3.4-14.4-2.4-4.2-5.6-7.4-9.7-9.8-4.1-2.4-8.8-3.6-14.2-3.6s-10.2,1.2-14.3,3.6c-4.1,2.4-7.3,5.7-9.6,9.9-2.3,4.2-3.5,9.2-3.5,14.9v3.6c0,5.7,1.3,10.7,3.9,15.1,2.6,4.4,6.3,7.8,11,10.2,4.7,2.4,10.2,3.6,16.4,3.6s10.2-.8,14.4-2.5c4.3-1.7,8.1-4.3,11.4-7.8l11.9,13.7c-4.3,5-9.6,8.8-16.1,11.5-6.5,2.7-13.9,4-22.2,4Z"/>
<path d="M1645.7,165.3v-95.1h21.2v26.2l-2.5-7.7c2.8-6.4,7.3-11.3,13.4-14.6,6.1-3.3,13.7-5,22.9-5v21.2c-1-.2-1.8-.4-2.7-.4-.8,0-1.7,0-2.5,0-8.4,0-15.1,2.5-20.1,7.4-5,4.9-7.5,12.3-7.5,22v46.1h-22.3Z"/>
<path d="M1749.9,166.6c-8,0-15.6-1-22.9-3.1s-13.1-4.6-17.4-7.6l8.5-16.9c4.3,2.7,9.4,5,15.3,6.8,5.9,1.8,11.9,2.7,17.8,2.7s12.1-.9,15.2-2.8c3.1-1.9,4.7-4.5,4.7-7.7s-1.1-4.6-3.2-6c-2.1-1.4-4.9-2.4-8.4-3.1-3.4-.7-7.3-1.4-11.5-2-4.2-.6-8.4-1.4-12.6-2.4-4.2-1-8-2.5-11.5-4.5-3.4-2-6.2-4.6-8.4-7.9-2.1-3.3-3.2-7.7-3.2-13.2s1.7-11.3,5.2-15.8c3.4-4.5,8.3-7.9,14.5-10.3,6.2-2.4,13.6-3.7,22.2-3.7s12.9.7,19.4,2.1c6.5,1.4,11.9,3.4,16.2,6.1l-8.5,16.9c-4.5-2.7-9.1-4.6-13.6-5.6-4.6-1-9.1-1.5-13.6-1.5-6.8,0-11.8,1-15,3-3.3,2-4.9,4.6-4.9,7.7s1.1,5,3.2,6.4c2.1,1.4,4.9,2.6,8.4,3.4,3.4.8,7.3,1.5,11.5,2,4.2.5,8.4,1.3,12.6,2.4,4.2,1.1,8,2.5,11.5,4.4,3.5,1.8,6.3,4.4,8.5,7.7,2.1,3.3,3.2,7.7,3.2,13s-1.8,11.1-5.3,15.5c-3.5,4.4-8.5,7.8-14.9,10.2-6.4,2.4-14.1,3.7-23,3.7Z"/>
<g>
<path d="M9.8,143l63.4-38.1-5.8-15.3,18.1-18.6,22.9-4.9,6.6,8.2-10.6,10.7-9.2,2.9-6.6,6.8,3.2,9,6.5,6.9,9.2-2.5,6.6-7.2,14.3-4.5,4.3,9.6-14.8,18.1-24.8,7.8-11.1-12.4-63.6,38.2c-3-3.9-6.5-9.4-8.8-14.7ZM192.8,65.4l-50.4,13.6c2.8,7.4,3.7,11.7,4.5,16.5l50.3-13.6c-.8-5.4-2.2-10.8-4.4-16.5Z" fill-rule="evenodd"/>
<path d="M17.3,106.5c3.6,42.1,38.9,75.2,82,75.2s60.7-18.9,74-46.3l16.4,5.7c-15.8,34.1-50.3,57.9-90.4,57.9S3.6,158.2,0,106.5h17.3ZM.3,89.4C5.3,39.2,47.8,0,99.3,0s99.5,44.6,99.5,99.5-1.1,17.4-3.3,25.5l-16.3-5.7c1.6-6.5,2.4-13.1,2.4-19.8,0-45.4-36.9-82.3-82.3-82.3S22.6,48.7,17.6,89.4H.3Z" fill-rule="evenodd"/>
<path d="M99,51.6c-26.4,0-47.9,21.5-47.9,48s21.5,48,48,48,2.7,0,4-.2l4.8,16.8c-2.9.4-5.8.6-8.8.6-36,0-65.2-29.2-65.2-65.2S63.1,34.4,99,34.4s1.8,0,2.7,0l-2.7,17.1ZM118.6,37.4c26.4,8.3,45.6,33,45.6,62.2s-16.4,50.2-39.8,60l-4.8-16.7c16.2-7.7,27.4-24.2,27.4-43.3s-13-38.1-31.1-44.9l2.7-17.2Z" fill-rule="evenodd"/>
</g>
</g>
<g id="black" fill="currentColor">
<path d="M354.8,69.2c12,0,21.7,3.4,28.6,10.4,7,7.2,10.6,17.5,10.6,31.5v54.8h-22.4v-51.9c0-8.4-1.8-14.7-5.5-19-3.8-4.1-8.9-6.3-15.9-6.3s-13.6,2.5-18.1,7.3c-4.5,5-6.8,12.2-6.8,21.3v48.5h-22.4v-51.9c0-8.4-1.8-14.7-5.5-19-3.8-4.1-8.9-6.3-15.9-6.3s-13.6,2.5-18.1,7.3c-4.5,4.8-6.8,12-6.8,21.3v48.5h-22.4v-95.6h21.3v12.2c3.6-4.3,8.1-7.5,13.4-9.8,5.4-2.3,11.3-3.4,17.9-3.4s13.6,1.3,19.2,3.9c5.5,2.9,9.8,6.8,13.1,12,3.9-5,8.9-8.9,15.2-11.8,6.3-2.7,13.1-4.1,20.6-4.1ZM466,167.2c-9.7,0-18.4-2.1-26.1-6.3-7.6-4-13.8-10.1-18.1-17.5-4.5-7.3-6.6-15.7-6.6-25.2s2.1-17.9,6.6-25.2c4.3-7.4,10.6-13.4,18.1-17.4,7.7-4.1,16.5-6.3,26.1-6.3s18.6,2.1,26.3,6.3c7.7,4.1,13.8,10,18.3,17.4,4.3,7.3,6.4,15.7,6.4,25.2s-2.1,17.9-6.4,25.2c-4.5,7.5-10.6,13.4-18.3,17.5-7.7,4.1-16.5,6.3-26.3,6.3h0ZM466,148c8.2,0,15-2.7,20.4-8.2,5.4-5.5,8.1-12.7,8.1-21.7s-2.7-16.1-8.1-21.7c-5.4-5.5-12.2-8.2-20.4-8.2s-15,2.7-20.2,8.2c-5.4,5.5-8.1,12.7-8.1,21.7s2.7,16.1,8.1,21.7c5.2,5.5,12,8.2,20.2,8.2ZM631.5,33.1v132.8h-21.5v-12.3c-3.7,4.4-8.3,7.9-13.6,10.2-5.5,2.3-11.5,3.4-18.1,3.4s-17.4-2-24.7-6.1c-7.3-4.1-13.2-9.8-17.4-17.4-4.1-7.3-6.3-15.9-6.3-25.6s2.1-18.3,6.3-25.6c4.1-7.3,10-13.1,17.4-17.2,7.3-4.1,15.6-6.1,24.7-6.1s12.2,1.1,17.4,3.2c5.2,2.1,9.8,5.4,13.4,9.7v-49h22.4ZM581.1,148c5.4,0,10.2-1.3,14.5-3.8,4.3-2.3,7.7-5.9,10.2-10.4,2.5-4.5,3.8-9.8,3.8-15.7s-1.3-11.3-3.8-15.7c-2.5-4.5-5.9-8.1-10.2-10.6-4.3-2.3-9.1-3.6-14.5-3.6s-10.2,1.3-14.5,3.6c-4.3,2.5-7.7,6.1-10.2,10.6-2.5,4.5-3.8,9.8-3.8,15.7s1.3,11.3,3.8,15.7c2.5,4.5,5.9,8.1,10.2,10.4,4.3,2.5,9.1,3.8,14.5,3.8ZM681.6,84.3c6.4-10,17.7-15,34-15v21.3c-1.7-.3-3.4-.5-5.2-.5-8.8,0-15.6,2.5-20.4,7.5-4.8,5.2-7.3,12.5-7.3,22v46.4h-22.4v-95.6h21.3v14h0ZM734.1,70.3h22.4v95.6h-22.4v-95.6ZM745.4,54.6c-4.1,0-7.5-1.3-10.2-3.9-2.7-2.4-4.2-5.9-4.1-9.5,0-3.8,1.4-7,4.1-9.7,2.7-2.5,6.1-3.8,10.2-3.8s7.5,1.3,10.2,3.6c2.7,2.5,4.1,5.5,4.1,9.3s-1.3,7.2-3.9,9.8c-2.7,2.7-6.3,4.1-10.4,4.1ZM839.5,69.2c12,0,21.7,3.6,29,10.6,7.3,7,10.9,17.5,10.9,31.3v54.8h-22.4v-51.9c0-8.4-2-14.7-5.9-19-3.9-4.1-9.5-6.3-16.8-6.3s-14.7,2.5-19.5,7.3c-4.8,5-7.2,12.2-7.2,21.5v48.3h-22.4v-95.6h21.3v12.3c3.8-4.5,8.4-7.7,14-10,5.5-2.3,12-3.4,19-3.4ZM964.8,160.7c-2.8,2.2-6,3.9-9.5,4.8-3.9,1.1-7.9,1.6-12,1.6-10.6,0-18.6-2.7-24.3-8.2-5.7-5.5-8.6-13.4-8.6-24v-46h-15.7v-17.9h15.7v-21.8h22.4v21.8h25.6v17.9h-25.6v45.5c0,4.7,1.1,8.2,3.4,10.6,2.3,2.5,5.5,3.8,9.8,3.8s9.1-1.3,12.5-3.9l6.3,15.9ZM1036.9,69.2c12,0,21.7,3.6,29,10.6,7.3,7,10.9,17.5,10.9,31.3v54.8h-22.4v-51.9c0-8.4-2-14.7-5.9-19-3.9-4.1-9.5-6.3-16.8-6.3s-14.7,2.5-19.5,7.3c-4.8,5-7.2,12.2-7.2,21.5v48.3h-22.4V33.1h22.4v48.3c3.8-3.9,8.2-7,13.8-9.1,5.4-2,11.5-3,18.1-3Z"/>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 163 KiB

@@ -80,19 +80,25 @@ input {
/* Chrome, Edge, and Safari */
*::-webkit-scrollbar {
width: var(--gap-md);
border: 3px solid var(--color-scrollbar);
width: 16px;
border: 3px solid transparent;
opacity: 0.5;
transition: opacity 0.2s ease-in-out;
}
*::-webkit-scrollbar:hover {
opacity: 1;
}
*::-webkit-scrollbar-track {
background: var(--color-bg);
border: 3px solid var(--color-bg);
background: transparent;
}
*::-webkit-scrollbar-thumb {
background-color: var(--color-scrollbar);
border-radius: var(--radius-lg);
border: 3px solid var(--color-bg);
border: 5px solid transparent;
background-clip: content-box;
}
.highlighted {
@@ -111,4 +117,8 @@ img {
-ms-user-select: none;
}
.card-shadow {
box-shadow: var(--shadow-card);
}
@import '@modrinth/assets/omorphia.scss';
@@ -12,13 +12,13 @@ import {
SearchIcon,
XIcon,
} from '@modrinth/assets'
import { ConfirmModal, Button, Card, DropdownSelect } from '@modrinth/ui'
import { Button, DropdownSelect } from '@modrinth/ui'
import { formatCategoryHeader } from '@modrinth/utils'
import ContextMenu from '@/components/ui/ContextMenu.vue'
import dayjs from 'dayjs'
import { useTheming } from '@/store/theme.js'
import { duplicate, remove } from '@/helpers/profile.js'
import { handleError } from '@/store/notifications.js'
import ConfirmModalWrapper from '@/components/ui/modal/ConfirmModalWrapper.vue'
const props = defineProps({
instances: {
@@ -35,7 +35,6 @@ const props = defineProps({
const instanceOptions = ref(null)
const instanceComponents = ref(null)
const themeStore = useTheming()
const currentDeleteInstance = ref(null)
const confirmModal = ref(null)
@@ -121,12 +120,11 @@ const handleOptionsClick = async (args) => {
}
const search = ref('')
const group = ref('Category')
const filters = ref('All profiles')
const group = ref('Group')
const sortBy = ref('Name')
const filteredResults = computed(() => {
let instances = props.instances.filter((instance) => {
const instances = props.instances.filter((instance) => {
return instance.name.toLowerCase().includes(search.value.toLowerCase())
})
@@ -160,16 +158,6 @@ const filteredResults = computed(() => {
})
}
if (filters.value === 'Custom instances') {
instances = instances.filter((instance) => {
return !instance.linked_data
})
} else if (filters.value === 'Downloaded modpacks') {
instances = instances.filter((instance) => {
return instance.linked_data
})
}
const instanceMap = new Map()
if (group.value === 'Loader') {
@@ -189,7 +177,7 @@ const filteredResults = computed(() => {
instanceMap.get(instance.game_version).push(instance)
})
} else if (group.value === 'Category') {
} else if (group.value === 'Group') {
instances.forEach((instance) => {
if (instance.groups.length === 0) {
instance.groups.push('None')
@@ -230,54 +218,37 @@ const filteredResults = computed(() => {
})
</script>
<template>
<ConfirmModal
ref="confirmModal"
title="Are you sure you want to delete this instance?"
description="If you proceed, all data for your instance will be removed. You will not be able to recover it."
:has-to-type="false"
proceed-label="Delete"
:noblur="!themeStore.advancedRendering"
@proceed="deleteProfile"
/>
<Card class="header">
<div class="iconified-input">
<div class="flex gap-2">
<div class="iconified-input flex-1">
<SearchIcon />
<input v-model="search" type="text" placeholder="Search" class="search-input" />
<input v-model="search" type="text" placeholder="Search" />
<Button class="r-btn" @click="() => (search = '')">
<XIcon />
</Button>
</div>
<div class="labeled_button">
<span>Sort by</span>
<DropdownSelect
v-model="sortBy"
class="sort-dropdown"
name="Sort Dropdown"
:options="['Name', 'Last played', 'Date created', 'Date modified', 'Game version']"
placeholder="Select..."
/>
</div>
<div class="labeled_button">
<span>Filter by</span>
<DropdownSelect
v-model="filters"
class="filter-dropdown"
name="Filter Dropdown"
:options="['All profiles', 'Custom instances', 'Downloaded modpacks']"
placeholder="Select..."
/>
</div>
<div class="labeled_button">
<span>Group by</span>
<DropdownSelect
v-model="group"
class="group-dropdown"
name="Group Dropdown"
:options="['Category', 'Loader', 'Game version', 'None']"
placeholder="Select..."
/>
</div>
</Card>
<DropdownSelect
v-slot="{ selected }"
v-model="sortBy"
name="Sort Dropdown"
class="max-w-[16rem]"
:options="['Name', 'Last played', 'Date created', 'Date modified', 'Game version']"
placeholder="Select..."
>
<span class="font-semibold text-primary">Sort by: </span>
<span class="font-semibold text-secondary">{{ selected }}</span>
</DropdownSelect>
<DropdownSelect
v-slot="{ selected }"
v-model="group"
class="max-w-[16rem]"
name="Group Dropdown"
:options="['Group', 'Loader', 'Game version', 'None']"
placeholder="Select..."
>
<span class="font-semibold text-primary">Group by: </span>
<span class="font-semibold text-secondary">{{ selected }}</span>
</DropdownSelect>
</div>
<div
v-for="instanceSection in Array.from(filteredResults, ([key, value]) => ({
key,
@@ -300,6 +271,14 @@ const filteredResults = computed(() => {
/>
</section>
</div>
<ConfirmModalWrapper
ref="confirmModal"
title="Are you sure you want to delete this instance?"
description="If you proceed, all data for your instance will be removed. You will not be able to recover it."
:has-to-type="false"
proceed-label="Delete"
@proceed="deleteProfile"
/>
<ContextMenu ref="instanceOptions" @option-clicked="handleOptionsClick">
<template #play> <PlayIcon /> Play </template>
<template #stop> <StopCircleIcon /> Stop </template>
@@ -317,7 +296,6 @@ const filteredResults = computed(() => {
flex-direction: column;
align-items: flex-start;
width: 100%;
padding: 1rem;
.divider {
display: flex;
@@ -385,9 +363,9 @@ const filteredResults = computed(() => {
.instances {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(10rem, 1fr));
grid-template-columns: repeat(auto-fill, minmax(16rem, 1fr));
width: 100%;
gap: 1rem;
gap: 0.75rem;
margin-right: auto;
scroll-behavior: smooth;
overflow-y: auto;
@@ -0,0 +1,141 @@
<script setup>
import { computed, onBeforeUnmount, ref, watch } from 'vue'
import { useLoading } from '@/store/state.js'
const props = defineProps({
throttle: {
type: Number,
default: 0,
},
duration: {
type: Number,
default: 1000,
},
height: {
type: Number,
default: 2,
},
color: {
type: String,
default: 'var(--loading-bar-gradient)',
},
})
const indicator = useLoadingIndicator({
duration: props.duration,
throttle: props.throttle,
})
onBeforeUnmount(() => indicator.clear)
const loading = useLoading()
watch(loading, (newValue) => {
if (newValue.barEnabled) {
if (newValue.loading) {
indicator.start()
} else {
indicator.finish()
}
}
})
function useLoadingIndicator(opts) {
const progress = ref(0)
const isLoading = ref(false)
const step = computed(() => 10000 / opts.duration)
let _timer = null
let _throttle = null
function start() {
clear()
progress.value = 0
if (opts.throttle) {
_throttle = setTimeout(() => {
isLoading.value = true
_startTimer()
}, opts.throttle)
} else {
isLoading.value = true
_startTimer()
}
}
function finish() {
progress.value = 100
_hide()
}
function clear() {
clearInterval(_timer)
clearTimeout(_throttle)
_timer = null
_throttle = null
}
function _increase(num) {
progress.value = Math.min(100, progress.value + num)
}
function _hide() {
clear()
setTimeout(() => {
isLoading.value = false
setTimeout(() => {
progress.value = 0
}, 400)
}, 500)
}
function _startTimer() {
_timer = setInterval(() => {
_increase(step.value)
}, 100)
}
return { progress, isLoading, start, finish, clear }
}
</script>
<template>
<div
class="loading-indicator-bar"
:style="{
'--_width': `${indicator.progress.value}%`,
'--_height': `${indicator.isLoading.value ? props.height : 0}px`,
'--_opacity': `${indicator.isLoading.value ? 1 : 0}`,
top: `0`,
right: `0`,
left: `${props.offsetWidth}`,
pointerEvents: 'none',
width: `var(--_width)`,
height: `var(--_height)`,
borderRadius: `var(--_height)`,
// opacity: `var(--_opacity)`,
background: `${props.color}`,
backgroundSize: `${(100 / indicator.progress.value) * 100}% auto`,
transition: 'width 0.1s ease-in-out, height 0.1s ease-out',
zIndex: 6,
}"
>
<slot />
</div>
</template>
<style lang="scss" scoped>
.loading-indicator-bar::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
width: var(--_width);
bottom: 0;
background-image: radial-gradient(80% 100% at 20% 0%, var(--color-brand) 0%, transparent 80%);
opacity: calc(var(--_opacity) * 0.1);
z-index: 5;
transition:
width 0.1s ease-in-out,
opacity 0.1s ease-out;
}
</style>
+62 -32
View File
@@ -12,7 +12,7 @@ import {
EyeIcon,
ChevronRightIcon,
} from '@modrinth/assets'
import { ConfirmModal } from '@modrinth/ui'
import ConfirmModalWrapper from '@/components/ui/modal/ConfirmModalWrapper.vue'
import Instance from '@/components/ui/Instance.vue'
import { computed, onMounted, onUnmounted, ref } from 'vue'
import ContextMenu from '@/components/ui/ContextMenu.vue'
@@ -22,10 +22,10 @@ import { handleError } from '@/store/notifications.js'
import { duplicate, kill, remove, run } from '@/helpers/profile.js'
import { useRouter } from 'vue-router'
import { showProfileInFolder } from '@/helpers/utils.js'
import { useTheming } from '@/store/state.js'
import { trackEvent } from '@/helpers/analytics'
import { handleSevereError } from '@/store/error.js'
import { install as installVersion } from '@/store/install.js'
import { openUrl } from '@tauri-apps/plugin-opener'
const router = useRouter()
@@ -53,7 +53,6 @@ const instanceComponents = ref(null)
const rows = ref(null)
const deleteConfirmModal = ref(null)
const themeStore = useTheming()
const currentDeleteInstance = ref(null)
async function deleteProfile() {
@@ -167,13 +166,7 @@ const handleOptionsClick = async (args) => {
break
}
case 'open_link':
window.__TAURI_INVOKE__('tauri', {
__tauriModule: 'Shell',
message: {
cmd: 'open',
path: `https://modrinth.com/${args.item.project_type}/${args.item.slug}`,
},
})
openUrl(`https://modrinth.com/${args.item.project_type}/${args.item.slug}`)
break
case 'copy_link':
await navigator.clipboard.writeText(
@@ -183,6 +176,7 @@ const handleOptionsClick = async (args) => {
}
}
const maxInstancesPerCompactRow = ref(1)
const maxInstancesPerRow = ref(1)
const maxProjectsPerRow = ref(1)
@@ -192,41 +186,73 @@ const calculateCardsPerRow = () => {
// Convert container width from pixels to rem
const containerWidthInRem =
containerWidth / parseFloat(getComputedStyle(document.documentElement).fontSize)
maxInstancesPerRow.value = Math.floor((containerWidthInRem + 1) / 11)
maxProjectsPerRow.value = Math.floor((containerWidthInRem + 1) / 19)
maxInstancesPerCompactRow.value = Math.floor((containerWidthInRem + 0.75) / 18.75)
maxInstancesPerRow.value = Math.floor((containerWidthInRem + 0.75) / 20.75)
maxProjectsPerRow.value = Math.floor((containerWidthInRem + 0.75) / 18.75)
if (maxInstancesPerRow.value < 5) {
maxInstancesPerRow.value *= 2
}
if (maxInstancesPerCompactRow.value < 5) {
maxInstancesPerCompactRow.value *= 2
}
if (maxProjectsPerRow.value < 3) {
maxProjectsPerRow.value *= 2
}
}
const rowContainer = ref(null)
const resizeObserver = ref(null)
onMounted(() => {
calculateCardsPerRow()
resizeObserver.value = new ResizeObserver(calculateCardsPerRow)
resizeObserver.value.observe(rowContainer.value)
window.addEventListener('resize', calculateCardsPerRow)
})
onUnmounted(() => {
window.removeEventListener('resize', calculateCardsPerRow)
resizeObserver.value.unobserve(rowContainer.value)
})
</script>
<template>
<ConfirmModal
<ConfirmModalWrapper
ref="deleteConfirmModal"
title="Are you sure you want to delete this instance?"
description="If you proceed, all data for your instance will be removed. You will not be able to recover it."
:has-to-type="false"
proceed-label="Delete"
:noblur="!themeStore.advancedRendering"
@proceed="deleteProfile"
/>
<div class="content">
<div v-for="row in actualInstances" ref="rows" :key="row.label" class="row">
<div class="header">
<router-link :to="row.route">{{ row.label }}</router-link>
<ChevronRightIcon />
</div>
<section v-if="row.instance" ref="modsRow" class="instances">
<div ref="rowContainer" class="flex flex-col gap-4">
<div v-for="(row, rowIndex) in actualInstances" ref="rows" :key="row.label" class="row">
<router-link
class="flex mb-3 leading-none items-center gap-1 text-primary text-lg font-bold hover:underline group"
:class="{ 'mt-1': rowIndex > 0 }"
:to="row.route"
>
{{ row.label }}
<ChevronRightIcon
class="h-5 w-5 stroke-[3px] group-hover:translate-x-1 transition-transform group-hover:text-brand"
/>
</router-link>
<section
v-if="row.instance"
ref="modsRow"
class="instances"
:class="{ compact: row.compact }"
>
<Instance
v-for="instance in row.instances.slice(0, maxInstancesPerRow)"
:key="(instance?.project_id || instance?.id) + instance.install_stage"
v-for="(instance, instanceIndex) in row.instances.slice(
0,
row.compact ? maxInstancesPerCompactRow : maxInstancesPerRow,
)"
:key="row.label + instance.path"
:instance="instance"
:compact="row.compact"
:first="instanceIndex === 0"
@contextmenu.prevent.stop="(event) => handleInstanceRightClick(event, instance)"
/>
</section>
@@ -263,7 +289,6 @@ onUnmounted(() => {
align-items: center;
justify-content: center;
width: 100%;
padding: 1rem;
gap: 1rem;
-ms-overflow-style: none;
@@ -297,31 +322,36 @@ onUnmounted(() => {
a {
margin: 0;
font-size: var(--font-size-lg);
font-size: var(--font-size-md);
font-weight: bolder;
white-space: nowrap;
color: var(--color-contrast);
color: var(--color-base);
}
svg {
height: 1.5rem;
width: 1.5rem;
color: var(--color-contrast);
height: 1.25rem;
width: 1.25rem;
color: var(--color-base);
}
}
.instances {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(10rem, 1fr));
grid-gap: 1rem;
grid-template-columns: repeat(auto-fill, minmax(20rem, 1fr));
grid-gap: 0.75rem;
width: 100%;
&.compact {
grid-template-columns: repeat(auto-fill, minmax(18rem, 1fr));
gap: 0.75rem;
}
}
.projects {
display: grid;
width: 100%;
grid-template-columns: repeat(auto-fill, minmax(18rem, 1fr));
grid-gap: 1rem;
grid-gap: 0.75rem;
.item {
width: 100%;
@@ -1,136 +0,0 @@
import { computed, defineComponent, h, onBeforeUnmount, ref, watch } from 'vue'
import { useLoading } from '@/store/state.js'
export default defineComponent({
props: {
throttle: {
type: Number,
default: 50,
},
duration: {
type: Number,
default: 500,
},
height: {
type: Number,
default: 3,
},
color: {
type: [String, Boolean],
default:
'repeating-linear-gradient(to right, var(--color-brand) 0%, var(--color-brand) 100%)',
},
offsetWidth: {
type: String,
default: '208px',
},
offsetHeight: {
type: String,
default: '52px',
},
},
setup(props, { slots }) {
const indicator = useLoadingIndicator({
duration: props.duration,
throttle: props.throttle,
})
onBeforeUnmount(() => indicator.clear)
const loading = useLoading()
watch(loading, (newValue) => {
if (newValue.barEnabled) {
if (newValue.loading) {
indicator.start()
} else {
indicator.finish()
}
}
})
return () =>
h(
'div',
{
style: {
position: 'fixed',
top: props.offsetHeight,
right: 0,
left: props.offsetWidth,
pointerEvents: 'none',
width: `calc((100vw - ${props.offsetWidth}) * ${indicator.progress.value / 100})`,
height: `${props.height}px`,
opacity: indicator.isLoading.value ? 1 : 0,
background: props.color || undefined,
backgroundSize: `${(100 / indicator.progress.value) * 100}% auto`,
transition: 'width 0.1s, height 0.4s, opacity 0.4s',
zIndex: 6,
},
},
slots,
)
},
})
function useLoadingIndicator(opts) {
const progress = ref(0)
const isLoading = ref(false)
const step = computed(() => 10000 / opts.duration)
let _timer = null
let _throttle = null
function start() {
clear()
progress.value = 0
if (opts.throttle) {
_throttle = setTimeout(() => {
isLoading.value = true
_startTimer()
}, opts.throttle)
} else {
isLoading.value = true
_startTimer()
}
}
function finish() {
progress.value = 100
_hide()
}
function clear() {
clearInterval(_timer)
clearTimeout(_throttle)
_timer = null
_throttle = null
}
function _increase(num) {
progress.value = Math.min(100, progress.value + num)
}
function _hide() {
clear()
setTimeout(() => {
isLoading.value = false
setTimeout(() => {
progress.value = 0
}, 400)
}, 500)
}
function _startTimer() {
_timer = setInterval(() => {
_increase(step.value)
}, 100)
}
return {
progress,
isLoading,
start,
finish,
clear,
}
}
@@ -2,19 +2,23 @@
<div
v-if="mode !== 'isolated'"
ref="button"
v-tooltip.right="'Minecraft accounts'"
class="button-base avatar-button"
class="button-base mt-2 px-3 py-2 bg-button-bg rounded-xl flex items-center gap-2"
:class="{ expanded: mode === 'expanded' }"
@click="showCard = !showCard"
@click="toggleMenu"
>
<Avatar
:size="mode === 'expanded' ? 'xs' : 'sm'"
size="36px"
:src="
selectedAccount
? `https://mc-heads.net/avatar/${selectedAccount.id}/128`
: 'https://launcher-files.modrinth.com/assets/steve_head.png'
"
/>
<div class="flex flex-col w-full">
<span>{{ selectedAccount ? selectedAccount.username : 'Select account' }}</span>
<span class="text-secondary text-xs">Minecraft account</span>
</div>
<DropdownIcon class="w-5 h-5 shrink-0" />
</div>
<transition name="fade">
<Card
@@ -59,7 +63,7 @@
</template>
<script setup>
import { PlusIcon, TrashIcon, LogInIcon } from '@modrinth/assets'
import { DropdownIcon, PlusIcon, TrashIcon, LogInIcon } from '@modrinth/assets'
import { Avatar, Button, Card } from '@modrinth/ui'
import { ref, computed, onMounted, onBeforeUnmount, onUnmounted } from 'vue'
import {
@@ -133,9 +137,9 @@ const logout = async (id) => {
trackEvent('AccountLogOut')
}
let showCard = ref(false)
let card = ref(null)
let button = ref(null)
const showCard = ref(false)
const card = ref(null)
const button = ref(null)
const handleClickOutside = (event) => {
const elements = document.elementsFromPoint(event.clientX, event.clientY)
if (
@@ -144,7 +148,15 @@ const handleClickOutside = (event) => {
!elements.includes(card.value.$el) &&
!button.value.contains(event.target)
) {
toggleMenu(false)
}
}
function toggleMenu(override = true) {
if (showCard.value || !override) {
showCard.value = false
} else {
showCard.value = true
}
}
@@ -195,11 +207,11 @@ onUnmounted(() => {
}
.account-card {
position: absolute;
position: fixed;
display: flex;
flex-direction: column;
top: 0.5rem;
left: 5.5rem;
margin-top: 0.5rem;
right: 2rem;
z-index: 11;
gap: 0.5rem;
padding: 1rem;
@@ -274,12 +286,17 @@ onUnmounted(() => {
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.3s ease;
transition:
opacity 0.25s ease,
translate 0.25s ease,
scale 0.25s ease;
}
.fade-enter-from,
.fade-leave-to {
opacity: 0;
translate: 0 -2rem;
scale: 0.9;
}
.avatar-button {
@@ -287,9 +304,10 @@ onUnmounted(() => {
align-items: center;
gap: 0.5rem;
color: var(--color-base);
background-color: var(--color-raised-bg);
background-color: var(--color-button-bg);
border-radius: var(--radius-md);
width: 100%;
padding: 0.5rem 0.75rem;
text-align: left;
&.expanded {
@@ -1,6 +1,6 @@
<script setup lang="ts">
import { DropdownIcon, FolderOpenIcon, SearchIcon } from '@modrinth/assets'
import { Button, OverflowMenu } from '@modrinth/ui'
import { DropdownIcon, PlusIcon, FolderOpenIcon } from '@modrinth/assets'
import { ButtonStyled, OverflowMenu } from '@modrinth/ui'
import { open } from '@tauri-apps/plugin-dialog'
import { add_project_from_path } from '@/helpers/profile.js'
import { handleError } from '@/store/notifications.js'
@@ -20,13 +20,13 @@ const handleAddContentFromFile = async () => {
if (!newProject) return
for (const project of newProject) {
await add_project_from_path(props.instance.path, project).catch(handleError)
await add_project_from_path(props.instance.path, project.path ?? project).catch(handleError)
}
}
const handleSearchContent = async () => {
await router.push({
path: `/browse/${props.instance.loader === 'vanilla' ? 'datapack' : 'mod'}`,
path: `/browse/${props.instance.loader === 'vanilla' ? 'resourcepack' : 'mod'}`,
query: { i: props.instance.path },
})
}
@@ -34,30 +34,27 @@ const handleSearchContent = async () => {
<template>
<div class="joined-buttons">
<Button color="primary" @click="handleSearchContent"><SearchIcon /> Add content </Button>
<OverflowMenu
:options="[
{
id: 'search',
action: handleSearchContent,
},
{
id: 'from_file',
action: handleAddContentFromFile,
},
]"
class="btn btn-primary btn-dropdown-animation icon-only"
>
<DropdownIcon />
<template #search>
<SearchIcon />
<span class="no-wrap"> Search </span>
</template>
<template #from_file>
<FolderOpenIcon />
<span class="no-wrap"> Add from file </span>
</template>
</OverflowMenu>
<ButtonStyled>
<button @click="handleSearchContent">
<PlusIcon />
Install content
</button>
</ButtonStyled>
<ButtonStyled>
<OverflowMenu
:options="[
{
id: 'from_file',
action: handleAddContentFromFile,
},
]"
>
<DropdownIcon />
<template #from_file>
<FolderOpenIcon />
<span class="no-wrap"> Add from file </span>
</template>
</OverflowMenu>
</ButtonStyled>
</div>
</template>
@@ -1,13 +1,18 @@
<template>
<div class="breadcrumbs">
<Button class="breadcrumbs__back transparent" icon-only @click="$router.back()">
<div data-tauri-drag-region class="flex items-center gap-1 pl-3">
<Button v-if="false" class="breadcrumbs__back transparent" icon-only @click="$router.back()">
<ChevronLeftIcon />
</Button>
<Button class="breadcrumbs__forward transparent" icon-only @click="$router.forward()">
<Button
v-if="false"
class="breadcrumbs__forward transparent"
icon-only
@click="$router.forward()"
>
<ChevronRightIcon />
</Button>
{{ breadcrumbData.resetToNames(breadcrumbs) }}
<div v-for="breadcrumb in breadcrumbs" :key="breadcrumb.name" class="breadcrumbs__item">
<template v-for="breadcrumb in breadcrumbs" :key="breadcrumb.name">
<router-link
v-if="breadcrumb.link"
:to="{
@@ -20,13 +25,18 @@
: breadcrumb.name
}}
</router-link>
<span v-else class="selected">{{
breadcrumb.name.charAt(0) === '?'
? breadcrumbData.getName(breadcrumb.name.slice(1))
: breadcrumb.name
}}</span>
<ChevronRightIcon v-if="breadcrumb.link" class="chevron" />
</div>
<span
v-else
data-tauri-drag-region
class="text-contrast font-semibold cursor-default select-none"
>{{
breadcrumb.name.charAt(0) === '?'
? breadcrumbData.getName(breadcrumb.name.slice(1))
: breadcrumb.name
}}</span
>
<ChevronRightIcon v-if="breadcrumb.link" data-tauri-drag-region class="w-5 h-5" />
</template>
</div>
</template>
@@ -50,38 +60,3 @@ const breadcrumbs = computed(() => {
return additionalContext ? [additionalContext, ...route.meta.breadcrumb] : route.meta.breadcrumb
})
</script>
<style scoped lang="scss">
.breadcrumbs {
display: flex;
flex-direction: row;
.breadcrumbs__item {
display: flex;
flex-direction: row;
vertical-align: center;
margin: auto 0;
.chevron,
a {
margin: auto 0;
}
}
.breadcrumbs__back,
.breadcrumbs__forward {
margin: auto 0;
color: var(--color-base);
height: unset;
width: unset;
}
.breadcrumbs__forward {
margin-right: 1rem;
}
}
.selected {
color: var(--color-contrast);
}
</style>
@@ -1,6 +1,5 @@
<script setup>
import { XIcon, HammerIcon, LogInIcon, UpdatedIcon } from '@modrinth/assets'
import { Modal } from '@modrinth/ui'
import { ChatIcon } from '@/assets/icons'
import { ref } from 'vue'
import { login as login_flow, set_default_user } from '@/helpers/auth.js'
@@ -9,6 +8,7 @@ import { handleSevereError } from '@/store/error.js'
import { cancel_directory_change } from '@/helpers/settings.js'
import { install } from '@/helpers/profile.js'
import { trackEvent } from '@/helpers/analytics'
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
const errorModal = ref()
const error = ref()
@@ -121,7 +121,7 @@ async function repairInstance() {
</script>
<template>
<Modal ref="errorModal" :header="title" :closable="closable">
<ModalWrapper ref="errorModal" :header="title" :closable="closable">
<div class="modal-body">
<div class="markdown-body">
<template v-if="errorType === 'minecraft_auth'">
@@ -230,7 +230,7 @@ async function repairInstance() {
</p>
<p>You may be able to fix it through one of the following ways:</p>
<ul>
<li>Ennsuring you are connected to the internet, then try restarting the app.</li>
<li>Ensuring you are connected to the internet, then try restarting the app.</li>
<li>Redownloading the app.</li>
</ul>
</template>
@@ -272,7 +272,7 @@ async function repairInstance() {
<button v-if="closable" class="btn" @click="errorModal.hide()"><XIcon /> Close</button>
</div>
</div>
</Modal>
</ModalWrapper>
</template>
<style>
@@ -323,7 +323,6 @@ async function repairInstance() {
display: flex;
flex-direction: column;
gap: var(--gap-md);
padding: var(--gap-lg);
}
.markdown-body {
@@ -1,12 +1,12 @@
<script setup>
import { XIcon, PlusIcon } from '@modrinth/assets'
import { Button, Checkbox, Modal } from '@modrinth/ui'
import { Button, Checkbox } from '@modrinth/ui'
import { PackageIcon, VersionIcon } from '@/assets/icons'
import { ref } from 'vue'
import { export_profile_mrpack, get_pack_export_candidates } from '@/helpers/profile.js'
import { open } from '@tauri-apps/plugin-dialog'
import { handleError } from '@/store/notifications.js'
import { useTheming } from '@/store/theme'
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
const props = defineProps({
instance: {
@@ -30,8 +30,6 @@ const files = ref([])
const folders = ref([])
const showingFiles = ref(false)
const themeStore = useTheming()
const initFiles = async () => {
const newFolders = new Map()
const sep = '/'
@@ -106,7 +104,7 @@ const exportPack = async () => {
</script>
<template>
<Modal ref="exportModal" header="Export modpack" :noblur="!themeStore.advancedRendering">
<ModalWrapper ref="exportModal" header="Export modpack">
<div class="modal-body">
<div class="labeled_input">
<p>Modpack Name</p>
@@ -208,12 +206,11 @@ const exportPack = async () => {
</Button>
</div>
</div>
</Modal>
</ModalWrapper>
</template>
<style scoped lang="scss">
.modal-body {
padding: var(--gap-xl);
display: flex;
flex-direction: column;
gap: var(--gap-md);
@@ -288,6 +285,7 @@ const exportPack = async () => {
flex-direction: row;
justify-content: space-between;
align-items: center;
gap: 1rem;
}
.textarea-wrapper {
+164 -177
View File
@@ -1,16 +1,28 @@
<script setup>
import { onUnmounted, ref, computed } from 'vue'
import { computed, onMounted, onUnmounted, ref } from 'vue'
import { useRouter } from 'vue-router'
import { StopCircleIcon, PlayIcon } from '@modrinth/assets'
import { Card, Avatar, AnimatedLogo } from '@modrinth/ui'
import {
DownloadIcon,
GameIcon,
PlayIcon,
SpinnerIcon,
StopCircleIcon,
TimerIcon,
} from '@modrinth/assets'
import { Avatar, ButtonStyled } from '@modrinth/ui'
import { convertFileSrc } from '@tauri-apps/api/core'
import { kill, run } from '@/helpers/profile'
import { finish_install, kill, run } from '@/helpers/profile'
import { get_by_profile_path } from '@/helpers/process'
import { process_listener } from '@/helpers/events'
import { handleError } from '@/store/state.js'
import { showProfileInFolder } from '@/helpers/utils.js'
import { handleSevereError } from '@/store/error.js'
import { trackEvent } from '@/helpers/analytics'
import dayjs from 'dayjs'
import relativeTime from 'dayjs/plugin/relativeTime'
import { formatCategory } from '@modrinth/utils'
dayjs.extend(relativeTime)
const props = defineProps({
instance: {
@@ -19,16 +31,31 @@ const props = defineProps({
return {}
},
},
compact: {
type: Boolean,
default: false,
},
first: {
type: Boolean,
default: false,
},
})
const playing = ref(false)
const modLoading = computed(() => props.instance.install_stage !== 'installed')
const loading = ref(false)
const modLoading = computed(
() =>
loading.value ||
currentEvent.value === 'installing' ||
(currentEvent.value === 'launched' && !playing.value),
)
const installing = computed(() => props.instance.install_stage.includes('installing'))
const installed = computed(() => props.instance.install_stage === 'installed')
const router = useRouter()
const seeInstance = async () => {
await router.push(`/instance/${encodeURIComponent(props.instance.path)}/`)
await router.push(`/instance/${encodeURIComponent(props.instance.path)}`)
}
const checkProcess = async () => {
@@ -39,17 +66,17 @@ const checkProcess = async () => {
const play = async (e, context) => {
e?.stopPropagation()
modLoading.value = true
await run(props.instance.path).catch((err) =>
handleSevereError(err, { profilePath: props.instance.path }),
)
modLoading.value = false
trackEvent('InstancePlay', {
loader: props.instance.loader,
game_version: props.instance.game_version,
source: context,
})
loading.value = true
await run(props.instance.path)
.catch((err) => handleSevereError(err, { profilePath: props.instance.path }))
.finally(() => {
trackEvent('InstancePlay', {
loader: props.instance.loader,
game_version: props.instance.game_version,
source: context,
})
})
loading.value = false
}
const stop = async (e, context) => {
@@ -65,6 +92,12 @@ const stop = async (e, context) => {
})
}
const repair = async (e) => {
e?.stopPropagation()
await finish_install(props.instance)
}
const openFolder = async () => {
await showProfileInFolder(props.instance.path)
}
@@ -85,175 +118,129 @@ defineExpose({
instance: props.instance,
})
const currentEvent = ref(null)
const unlisten = await process_listener((e) => {
if (e.event === 'finished' && e.profile_path_id === props.instance.path) playing.value = false
if (e.profile_path_id === props.instance.path) {
currentEvent.value = e.event
if (e.event === 'finished') {
playing.value = false
}
}
})
onMounted(() => checkProcess())
onUnmounted(() => unlisten())
</script>
<template>
<div class="instance">
<Card class="instance-card-item button-base" @click="seeInstance" @mouseenter="checkProcess">
<Avatar
size="lg"
:src="props.instance.icon_path ? convertFileSrc(props.instance.icon_path) : null"
alt="Mod card"
class="mod-image"
/>
<div class="project-info">
<p class="title">{{ props.instance.name }}</p>
<p
v-if="
props.instance.install_stage === 'installing' ||
props.instance.install_stage === 'not_installed' ||
props.instance.install_stage === 'pack_installing'
"
class="description"
>
Installing...
</p>
<p v-else class="description">
{{ props.instance.loader }}
{{ props.instance.game_version }}
</p>
</div>
</Card>
<template v-if="compact">
<div
v-if="playing === true"
class="stop cta button-base"
@click="(e) => stop(e, 'InstanceCard')"
@mousehover="checkProcess"
class="card-shadow grid grid-cols-[auto_1fr_auto] bg-bg-raised rounded-xl p-3 pl-4 gap-2 cursor-pointer hover:brightness-90 transition-all"
@click="seeInstance"
@mouseenter="checkProcess"
>
<StopCircleIcon />
<Avatar
size="48px"
:src="instance.icon_path ? convertFileSrc(instance.icon_path) : null"
:tint-by="instance.path"
alt="Mod card"
/>
<div class="h-full flex items-center font-bold text-contrast leading-normal">
<span class="line-clamp-2">{{ instance.name }}</span>
</div>
<div class="flex items-center">
<ButtonStyled v-if="playing" color="red" circular @mousehover="checkProcess">
<button v-tooltip="'Stop'" @click="(e) => stop(e, 'InstanceCard')">
<StopCircleIcon />
</button>
</ButtonStyled>
<ButtonStyled v-else-if="modLoading" color="standard" circular>
<button v-tooltip="'Instance is loading...'" disabled>
<SpinnerIcon class="animate-spin" />
</button>
</ButtonStyled>
<ButtonStyled v-else :color="first ? 'brand' : 'standard'" circular>
<button
v-tooltip="'Play'"
@click="(e) => play(e, 'InstanceCard')"
@mousehover="checkProcess"
>
<!-- Translate for optical centering -->
<PlayIcon class="translate-x-[1px]" />
</button>
</ButtonStyled>
</div>
<div class="flex items-center col-span-3 gap-1 text-secondary font-semibold">
<TimerIcon />
<span class="text-sm"> Played {{ dayjs(instance.last_played).fromNow() }} </span>
</div>
</div>
<div v-else-if="modLoading === true && playing === false" class="cta loading-cta">
<AnimatedLogo class="loading-indicator" />
</div>
<div v-else class="install cta button-base" @click="(e) => play(e, 'InstanceCard')">
<PlayIcon />
</template>
<div v-else>
<div
class="button-base bg-bg-raised p-4 rounded-xl flex gap-3 group"
@click="seeInstance"
@mouseenter="checkProcess"
>
<div class="relative flex items-center justify-center">
<Avatar
size="48px"
:src="instance.icon_path ? convertFileSrc(instance.icon_path) : null"
:tint-by="instance.path"
alt="Mod card"
:class="`transition-all ${modLoading || installing ? `brightness-[0.25] scale-[0.85]` : `group-hover:brightness-75`}`"
/>
<div class="absolute inset-0 flex items-center justify-center">
<ButtonStyled v-if="playing" size="large" color="red" circular>
<button
v-tooltip="'Stop'"
:class="{ 'scale-100 opacity-100': playing }"
class="transition-all scale-75 origin-bottom opacity-0 card-shadow"
@click="(e) => stop(e, 'InstanceCard')"
@mousehover="checkProcess"
>
<StopCircleIcon />
</button>
</ButtonStyled>
<SpinnerIcon
v-else-if="modLoading || installing"
v-tooltip="modLoading ? 'Instance is loading...' : 'Installing...'"
class="animate-spin w-8 h-8"
tabindex="-1"
/>
<ButtonStyled v-else-if="!installed" size="large" color="brand" circular>
<button
v-tooltip="'Repair'"
class="transition-all scale-75 group-hover:scale-100 group-focus-within:scale-100 origin-bottom opacity-0 group-hover:opacity-100 group-focus-within:opacity-100 card-shadow"
@click="(e) => repair(e)"
>
<DownloadIcon />
</button>
</ButtonStyled>
<ButtonStyled v-else size="large" color="brand" circular>
<button
v-tooltip="'Play'"
class="transition-all scale-75 group-hover:scale-100 group-focus-within:scale-100 origin-bottom opacity-0 group-hover:opacity-100 group-focus-within:opacity-100 card-shadow"
@click="(e) => play(e, 'InstanceCard')"
@mousehover="checkProcess"
>
<PlayIcon class="translate-x-[2px]" />
</button>
</ButtonStyled>
</div>
</div>
<div class="flex flex-col gap-1">
<p class="m-0 text-md font-bold text-contrast leading-tight line-clamp-1">
{{ instance.name }}
</p>
<div class="flex items-center col-span-3 gap-1 text-secondary font-semibold mt-auto">
<GameIcon class="shrink-0" />
<span class="text-sm">
{{ formatCategory(instance.loader) }} {{ instance.game_version }}
</span>
</div>
</div>
</div>
</div>
</template>
<style lang="scss">
.loading-indicator {
width: 2.5rem !important;
height: 2.5rem !important;
svg {
width: 2.5rem !important;
height: 2.5rem !important;
}
}
</style>
<style lang="scss" scoped>
.instance {
position: relative;
&:hover {
.cta {
opacity: 1;
bottom: calc(var(--gap-md) + 4.25rem);
}
}
}
.cta {
position: absolute;
display: flex;
align-items: center;
justify-content: center;
border-radius: var(--radius-md);
z-index: 1;
width: 3rem;
height: 3rem;
right: calc(var(--gap-md) * 2);
bottom: 3.25rem;
opacity: 0;
transition:
0.2s ease-in-out bottom,
0.2s ease-in-out opacity,
0.1s ease-in-out filter !important;
cursor: pointer;
box-shadow: var(--shadow-floating);
svg {
color: var(--color-accent-contrast);
width: 1.5rem !important;
height: 1.5rem !important;
}
&.install {
background: var(--color-brand);
display: flex;
}
&.stop {
background: var(--color-red);
display: flex;
}
&.loading-cta {
background: hsl(220, 11%, 10%) !important;
display: flex;
justify-content: center;
align-items: center;
}
}
.instance-card-item {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
cursor: pointer;
padding: var(--gap-md);
transition: 0.1s ease-in-out all !important; /* overrides Omorphia defaults */
margin-bottom: 0;
.mod-image {
--size: 100%;
width: 100% !important;
height: auto !important;
max-width: unset !important;
max-height: unset !important;
aspect-ratio: 1 / 1 !important;
}
.project-info {
margin-top: 1rem;
width: 100%;
.title {
color: var(--color-contrast);
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
width: 100%;
margin: 0;
font-weight: 600;
font-size: 1rem;
line-height: 110%;
display: inline-block;
}
.description {
color: var(--color-base);
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
font-weight: 500;
font-size: 0.775rem;
line-height: 125%;
margin: 0.25rem 0 0;
text-transform: capitalize;
white-space: nowrap;
text-overflow: ellipsis;
}
}
}
</style>
@@ -1,5 +1,5 @@
<template>
<Modal ref="modal" header="Create instance" :noblur="!themeStore.advancedRendering">
<ModalWrapper ref="modal" header="Create instance">
<div class="modal-header">
<Chips v-model="creationType" :items="['custom', 'from file', 'import from launcher']" />
</div>
@@ -193,21 +193,22 @@
/>
</div>
</div>
</Modal>
</ModalWrapper>
</template>
<script setup>
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
import {
PlusIcon,
UploadIcon,
XIcon,
CodeIcon,
FolderOpenIcon,
InfoIcon,
FolderSearchIcon,
InfoIcon,
PlusIcon,
UpdatedIcon,
UploadIcon,
XIcon,
} from '@modrinth/assets'
import { Avatar, Button, Chips, Modal, Checkbox } from '@modrinth/ui'
import { Avatar, Button, Checkbox, Chips } from '@modrinth/ui'
import { computed, onUnmounted, ref, shallowRef } from 'vue'
import { get_loaders } from '@/helpers/tags'
import { create } from '@/helpers/profile'
@@ -217,17 +218,14 @@ import { get_game_versions, get_loader_versions } from '@/helpers/metadata'
import { handleError } from '@/store/notifications.js'
import Multiselect from 'vue-multiselect'
import { trackEvent } from '@/helpers/analytics'
import { useTheming } from '@/store/state.js'
import { listen } from '@tauri-apps/api/event'
import { install_from_file } from '@/helpers/pack.js'
import { create_profile_and_install_from_file } from '@/helpers/pack.js'
import {
get_default_launcher_path,
get_importable_instances,
import_instance,
} from '@/helpers/import.js'
import ProgressBar from '@/components/ui/ProgressBar.vue'
const themeStore = useTheming()
import { getCurrentWebview } from '@tauri-apps/api/webview'
const profile_name = ref('')
const game_version = ref('')
@@ -239,7 +237,7 @@ const display_icon = ref(null)
const showAdvanced = ref(false)
const creating = ref(false)
const showSnapshots = ref(false)
const creationType = ref('from file')
const creationType = ref('custom')
const isShowing = ref(false)
defineExpose({
@@ -257,13 +255,15 @@ defineExpose({
isShowing.value = true
modal.value.show()
unlistener.value = await listen('tauri://file-drop', async (event) => {
unlistener.value = await getCurrentWebview().onDragDropEvent(async (event) => {
// Only if modal is showing
if (!isShowing.value) return
if (event.payload.type !== 'drop') return
if (creationType.value !== 'from file') return
hide()
if (event.payload && event.payload.length > 0 && event.payload[0].endsWith('.mrpack')) {
await install_from_file(event.payload[0]).catch(handleError)
const { paths } = event.payload
if (paths && paths.length > 0 && paths[0].endsWith('.mrpack')) {
await create_profile_and_install_from_file(paths[0]).catch(handleError)
trackEvent('InstanceCreate', {
source: 'CreationModalFileDrop',
})
@@ -371,7 +371,7 @@ const create_instance = async () => {
}
const upload_icon = async () => {
icon.value = await open({
const res = await open({
multiple: false,
filters: [
{
@@ -381,6 +381,8 @@ const upload_icon = async () => {
],
})
icon.value = res.path ?? res
if (!icon.value) return
display_icon.value = convertFileSrc(icon.value)
}
@@ -417,7 +419,7 @@ const openFile = async () => {
const newProject = await open({ multiple: false })
if (!newProject) return
hide()
await install_from_file(newProject).catch(handleError)
await create_profile_and_install_from_file(newProject.path ?? newProject).catch(handleError)
trackEvent('InstanceCreate', {
source: 'CreationModalFileOpen',
@@ -462,7 +464,7 @@ const promises = profileOptions.value.map(async (option) => {
option.name,
instances.map((name) => ({ name, selected: false })),
)
} catch (error) {
} catch {
// Allow failure silently
}
})
@@ -523,8 +525,8 @@ const next = async () => {
.modal-body {
display: flex;
flex-direction: column;
padding: var(--gap-lg);
gap: var(--gap-md);
margin-top: var(--gap-lg);
}
.input-label {
@@ -593,7 +595,6 @@ const next = async () => {
flex-direction: row;
justify-content: space-between;
align-items: center;
padding: var(--gap-lg);
padding-bottom: 0;
}
@@ -0,0 +1,53 @@
<script setup lang="ts">
import { convertFileSrc } from '@tauri-apps/api/core'
import { formatCategory } from '@modrinth/utils'
import { GameIcon, LeftArrowIcon } from '@modrinth/assets'
import { Avatar, ButtonStyled } from '@modrinth/ui'
type Instance = {
game_version: string
loader: string
path: string
install_stage: string
icon_path?: string
name: string
}
defineProps<{
instance: Instance
}>()
</script>
<template>
<div class="flex justify-between items-center border-0 border-b border-solid border-divider pb-4">
<router-link
:to="`/instance/${encodeURIComponent(instance.path)}`"
tabindex="-1"
class="flex flex-col gap-4 text-primary"
>
<span class="flex items-center gap-2">
<Avatar
:src="instance.icon_path ? convertFileSrc(instance.icon_path) : undefined"
:alt="instance.name"
size="48px"
/>
<span class="flex flex-col gap-2">
<span class="font-extrabold bold text-contrast">
{{ instance.name }}
</span>
<span class="text-secondary flex items-center gap-2 font-semibold">
<GameIcon class="h-5 w-5 text-secondary" />
{{ formatCategory(instance.loader) }} {{ instance.game_version }}
</span>
</span>
</span>
</router-link>
<ButtonStyled>
<router-link :to="`/instance/${encodeURIComponent(instance.path)}`">
<LeftArrowIcon /> Back to instance
</router-link>
</ButtonStyled>
</div>
</template>
<style scoped lang="scss"></style>
@@ -1,5 +1,5 @@
<template>
<Modal ref="detectJavaModal" header="Select java version" :noblur="!themeStore.advancedRendering">
<ModalWrapper ref="detectJavaModal" header="Select java version" :show-ad-on-close="false">
<div class="auto-detect-modal">
<div class="table">
<div class="table-row table-head">
@@ -32,18 +32,16 @@
</Button>
</div>
</div>
</Modal>
</ModalWrapper>
</template>
<script setup>
import { PlusIcon, CheckIcon, XIcon } from '@modrinth/assets'
import { Modal, Button } from '@modrinth/ui'
import { Button } from '@modrinth/ui'
import { ref } from 'vue'
import { find_filtered_jres } from '@/helpers/jre.js'
import { handleError } from '@/store/notifications.js'
import { useTheming } from '@/store/theme.js'
import { trackEvent } from '@/helpers/analytics'
const themeStore = useTheming()
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
const chosenInstallOptions = ref([])
const detectJavaModal = ref(null)
@@ -75,8 +73,6 @@ function setJavaInstall(javaInstall) {
</script>
<style lang="scss" scoped>
.auto-detect-modal {
padding: 1rem;
.table {
.table-row {
grid-template-columns: 1fr 4fr min-content;
@@ -28,7 +28,7 @@
</Button>
<Button :disabled="props.disabled" @click="autoDetect">
<SearchIcon />
Auto detect
Detect
</Button>
<Button :disabled="props.disabled" @click="handleJavaFileInput()">
<FolderSearchIcon />
@@ -124,20 +124,19 @@ async function testJava() {
}
async function handleJavaFileInput() {
let filePath = await open()
const filePath = await open()
if (filePath) {
let result = await get_jre(filePath)
let result = await get_jre(filePath.path ?? filePath)
if (!result) {
result = {
path: filePath,
path: filePath.path ?? filePath,
version: props.version.toString(),
architecture: 'x86',
}
}
trackEvent('JavaManualSelect', {
path: filePath,
version: props.version,
})
@@ -150,7 +149,7 @@ async function autoDetect() {
if (!props.compact) {
detectJavaModal.value.show(props.version, props.modelValue)
} else {
let versions = await find_filtered_jres(props.version).catch(handleError)
const versions = await find_filtered_jres(props.version).catch(handleError)
if (versions.length > 0) {
emit('update:modelValue', versions[0])
}
@@ -188,6 +187,7 @@ async function reinstallJava() {
.toggle-setting {
display: flex;
flex-wrap: wrap;
flex-direction: row;
justify-content: space-between;
align-items: center;
@@ -1,11 +1,11 @@
<script setup>
import { CheckIcon } from '@modrinth/assets'
import { Button, Modal, Badge } from '@modrinth/ui'
import { Button, Badge } from '@modrinth/ui'
import { computed, ref } from 'vue'
import { useTheming } from '@/store/theme'
import { update_managed_modrinth_version } from '@/helpers/profile'
import { releaseColor } from '@/helpers/utils'
import { SwapIcon } from '@/assets/icons/index.js'
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
const props = defineProps({
versions: {
@@ -24,6 +24,8 @@ defineExpose({
},
})
const emit = defineEmits(['finish-install'])
const filteredVersions = computed(() => {
return props.versions
})
@@ -33,24 +35,30 @@ const installedVersion = computed(() => props.instance?.linked_data?.version_id)
const installing = computed(() => props.instance.install_stage !== 'installed')
const inProgress = ref(false)
const themeStore = useTheming()
const switchVersion = async (versionId) => {
modpackVersionModal.value.hide()
inProgress.value = true
await update_managed_modrinth_version(props.instance.path, versionId)
inProgress.value = false
emit('finish-install')
}
const onHide = () => {
if (!inProgress.value) {
emit('finish-install')
}
}
</script>
<template>
<Modal
<ModalWrapper
ref="modpackVersionModal"
class="modpack-version-modal"
header="Change modpack version"
:noblur="!themeStore.advancedRendering"
:on-hide="onHide"
>
<div class="modal-body">
<Card v-if="instance.linked_data" class="mod-card">
<div v-if="instance.linked_data" class="mod-card">
<div class="table">
<div class="table-row with-columns table-head">
<div class="table-cell table-text download-cell" />
@@ -109,9 +117,9 @@ const switchVersion = async (versionId) => {
</div>
</div>
</div>
</Card>
</div>
</div>
</Modal>
</ModalWrapper>
</template>
<style scoped lang="scss">
@@ -176,7 +184,6 @@ const switchVersion = async (versionId) => {
}
.modal-body {
padding: var(--gap-xl);
display: flex;
flex-direction: column;
gap: var(--gap-md);
@@ -0,0 +1,59 @@
<template>
<RouterLink
v-if="typeof to === 'string'"
:to="to"
v-bind="$attrs"
:class="{
'router-link-active': isPrimary && isPrimary(route),
'subpage-active': isSubpage && isSubpage(route),
}"
class="w-12 h-12 rounded-full flex items-center justify-center text-2xl transition-all bg-transparent hover:bg-button-bg hover:text-contrast"
>
<slot />
</RouterLink>
<button
v-else
v-bind="$attrs"
class="button-animation border-none text-primary cursor-pointer w-12 h-12 rounded-full flex items-center justify-center text-2xl transition-all bg-transparent hover:bg-button-bg hover:text-contrast"
@click="to"
>
<slot />
</button>
</template>
<script setup lang="ts">
import type { RouteLocationNormalizedLoaded } from 'vue-router'
import { RouterLink, useRoute } from 'vue-router'
const route = useRoute()
type RouteFunction = (route: RouteLocationNormalizedLoaded) => boolean
defineProps<{
to: (() => void) | string
isPrimary?: RouteFunction
isSubpage?: RouteFunction
highlightOverride?: boolean
}>()
defineOptions({
inheritAttrs: false,
})
</script>
<style lang="scss" scoped>
.router-link-active,
.subpage-active {
svg {
filter: drop-shadow(0 0 0.5rem black);
}
}
.router-link-active {
@apply text-[--color-button-text-selected] bg-[--color-button-bg-selected];
}
.subpage-active {
@apply text-contrast bg-button-bg;
}
</style>
@@ -0,0 +1,160 @@
<template>
<nav
class="card-shadow experimental-styles-within relative flex w-fit overflow-clip rounded-full bg-bg-raised p-1 text-sm font-bold"
>
<RouterLink
v-for="(link, index) in filteredLinks"
v-show="link.shown === undefined ? true : link.shown"
:key="index"
ref="tabLinkElements"
:to="query ? (link.href ? `?${query}=${link.href}` : '?') : link.href"
:class="`button-animation z-[1] flex flex-row items-center gap-2 px-4 py-2 focus:rounded-full ${activeIndex === index && !subpageSelected ? 'text-button-textSelected' : activeIndex === index && subpageSelected ? 'text-contrast' : 'text-primary'}`"
>
<component :is="link.icon" v-if="link.icon" class="size-5" />
<span class="text-nowrap">{{ link.label }}</span>
</RouterLink>
<div
:class="`navtabs-transition pointer-events-none absolute h-[calc(100%-0.5rem)] overflow-hidden rounded-full p-1 ${subpageSelected ? 'bg-button-bg' : 'bg-button-bgSelected'}`"
:style="{
left: sliderLeftPx,
top: sliderTopPx,
right: sliderRightPx,
bottom: sliderBottomPx,
opacity: sliderLeft === 4 && sliderLeft === sliderRight ? 0 : activeIndex === -1 ? 0 : 1,
}"
aria-hidden="true"
></div>
</nav>
</template>
<script setup lang="ts">
import { computed, onMounted, onUnmounted, ref, watch } from 'vue'
import type { RouteLocationRaw } from 'vue-router'
import { useRoute, RouterLink } from 'vue-router'
const route = useRoute()
interface Tab {
label: string
href: string | RouteLocationRaw
shown?: boolean
icon?: unknown
subpages?: string[]
}
const props = defineProps<{
links: Tab[]
query?: string
}>()
const sliderLeft = ref(4)
const sliderTop = ref(4)
const sliderRight = ref(4)
const sliderBottom = ref(4)
const activeIndex = ref(-1)
const oldIndex = ref(-1)
const subpageSelected = ref(false)
const filteredLinks = computed(() =>
props.links.filter((x) => (x.shown === undefined ? true : x.shown)),
)
const sliderLeftPx = computed(() => `${sliderLeft.value}px`)
const sliderTopPx = computed(() => `${sliderTop.value}px`)
const sliderRightPx = computed(() => `${sliderRight.value}px`)
const sliderBottomPx = computed(() => `${sliderBottom.value}px`)
function pickLink() {
let index = -1
subpageSelected.value = false
for (let i = filteredLinks.value.length - 1; i >= 0; i--) {
const link = filteredLinks.value[i]
if (route.path === (typeof link.href === 'string' ? link.href : link.href.path)) {
index = i
break
} else if (link.subpages && link.subpages.some((subpage) => route.path.includes(subpage))) {
index = i
subpageSelected.value = true
break
}
}
activeIndex.value = index
if (activeIndex.value !== -1) {
startAnimation()
} else {
oldIndex.value = -1
sliderLeft.value = 0
sliderRight.value = 0
}
}
const tabLinkElements = ref()
function startAnimation() {
const el = tabLinkElements.value[activeIndex.value].$el
if (!el || !el.offsetParent) return
const newValues = {
left: el.offsetLeft,
top: el.offsetTop,
right: el.offsetParent.offsetWidth - el.offsetLeft - el.offsetWidth,
bottom: el.offsetParent.offsetHeight - el.offsetTop - el.offsetHeight,
}
if (sliderLeft.value === 4 && sliderRight.value === 4) {
sliderLeft.value = newValues.left
sliderRight.value = newValues.right
sliderTop.value = newValues.top
sliderBottom.value = newValues.bottom
} else {
const delay = 200
if (newValues.left < sliderLeft.value) {
sliderLeft.value = newValues.left
setTimeout(() => {
sliderRight.value = newValues.right
}, delay)
} else {
sliderRight.value = newValues.right
setTimeout(() => {
sliderLeft.value = newValues.left
}, delay)
}
if (newValues.top < sliderTop.value) {
sliderTop.value = newValues.top
setTimeout(() => {
sliderBottom.value = newValues.bottom
}, delay)
} else {
sliderBottom.value = newValues.bottom
setTimeout(() => {
sliderTop.value = newValues.top
}, delay)
}
}
}
onMounted(() => {
window.addEventListener('resize', pickLink)
pickLink()
})
onUnmounted(() => {
window.removeEventListener('resize', pickLink)
})
watch(route, () => {
pickLink()
})
</script>
<style scoped>
.navtabs-transition {
/* Delay on opacity is to hide any jankiness as the page loads */
transition:
all 150ms cubic-bezier(0.4, 0, 0.2, 1) 0s,
opacity 250ms cubic-bezier(0.5, 0, 0.2, 1) 50ms;
}
</style>
@@ -1,17 +1,15 @@
<script setup>
import { Card, Avatar, Button } from '@modrinth/ui'
import { DownloadIcon, HeartIcon, CalendarIcon } from '@modrinth/assets'
import { Avatar, TagItem } from '@modrinth/ui'
import { DownloadIcon, HeartIcon, TagIcon } from '@modrinth/assets'
import { formatNumber, formatCategory } from '@modrinth/utils'
import { computed, ref } from 'vue'
import { computed } from 'vue'
import dayjs from 'dayjs'
import relativeTime from 'dayjs/plugin/relativeTime'
import { useRouter } from 'vue-router'
import { install as installVersion } from '@/store/install.js'
dayjs.extend(relativeTime)
const router = useRouter()
const installing = ref(false)
const props = defineProps({
project: {
@@ -22,6 +20,17 @@ const props = defineProps({
},
})
const featuredCategory = computed(() => {
if (props.project.categories.includes('optimization')) {
return 'optimization'
}
if (props.project.categories.length > 0) {
return props.project.categories[0]
}
return undefined
})
const toColor = computed(() => {
let color = props.project.color
@@ -47,197 +56,66 @@ const toTransparent = computed(() => {
'))'
)
})
const install = async (e) => {
e?.stopPropagation()
installing.value = true
await installVersion(
props.project.project_id,
null,
props.instance ? props.instance.path : null,
'ProjectCard',
() => {
installing.value = false
},
)
}
</script>
<template>
<div class="wrapper">
<Card class="project-card button-base" @click="router.push(`/project/${project.slug}`)">
<div
class="card-shadow bg-bg-raised rounded-xl overflow-clip cursor-pointer hover:brightness-90 transition-all"
@click="router.push(`/project/${project.slug}`)"
>
<div
class="w-full aspect-[2/1] bg-cover bg-center bg-no-repeat"
:style="{
'background-color': project.featured_gallery ?? project.gallery[0] ? null : toColor,
'background-image': `url(${
project.featured_gallery ??
project.gallery[0] ??
'https://launcher-files.modrinth.com/assets/maze-bg.png'
})`,
}"
>
<div
class="banner"
:style="{
'background-color': project.featured_gallery ?? project.gallery[0] ? null : toColor,
'background-image': `url(${
project.featured_gallery ??
project.gallery[0] ??
'https://launcher-files.modrinth.com/assets/maze-bg.png'
})`,
class="badges-wrapper"
:class="{
'no-image': !project.featured_gallery && !project.gallery[0],
}"
>
<div class="badges">
<div class="badge">
<DownloadIcon />
{{ formatNumber(project.downloads) }}
</div>
<div class="badge">
<HeartIcon />
{{ formatNumber(project.follows) }}
</div>
<div class="badge">
<CalendarIcon />
{{ formatCategory(dayjs(project.date_modified).fromNow()) }}
</div>
:style="{
background: !project.featured_gallery && !project.gallery[0] ? toTransparent : null,
}"
></div>
</div>
<div class="flex flex-col justify-center gap-2 px-4 py-3">
<div class="flex gap-2 items-center">
<Avatar size="48px" :src="project.icon_url" />
<div class="h-full flex items-center font-bold text-contrast leading-normal">
<span class="line-clamp-2">{{ project.title }}</span>
</div>
</div>
<p class="m-0 text-sm font-medium line-clamp-3 leading-tight h-[3.25rem]">
{{ project.description }}
</p>
<div class="flex items-center gap-2 text-sm text-secondary font-semibold mt-auto">
<div
class="flex items-center gap-1 pr-2 border-0 border-r-[1px] border-solid border-button-border"
>
<DownloadIcon />
{{ formatNumber(project.downloads) }}
</div>
<div
class="badges-wrapper"
:class="{
'no-image': !project.featured_gallery && !project.gallery[0],
}"
:style="{
background: !project.featured_gallery && !project.gallery[0] ? toTransparent : null,
}"
></div>
</div>
<Avatar class="icon" size="sm" :src="project.icon_url" />
<div class="title">
<div class="title-text">
{{ project.title }}
class="flex items-center gap-1 pr-2 border-0 border-r-[1px] border-solid border-button-border"
>
<HeartIcon />
{{ formatNumber(project.follows) }}
</div>
<div class="flex items-center gap-1 pr-2">
<TagIcon />
<TagItem>
{{ formatCategory(featuredCategory) }}
</TagItem>
</div>
<div class="author">by {{ project.author }}</div>
</div>
<div class="description">
{{ project.description }}
</div>
</Card>
<Button color="primary" class="install" :disabled="installing" @click="install">
<DownloadIcon />
{{ installing ? 'Installing' : 'Install' }}
</Button>
</div>
</div>
</template>
<style scoped lang="scss">
.wrapper {
position: relative;
aspect-ratio: 1;
&:hover {
.install:enabled {
opacity: 1;
}
}
}
.project-card {
display: grid;
grid-gap: 1rem;
grid-template:
'. . . .' 0
'. icon title .' 3rem
'banner banner banner banner' auto
'. description description .' 3.5rem
'. . . .' 0 / 0 3rem minmax(0, 1fr) 0;
max-width: 100%;
height: 100%;
padding: 0;
margin: 0;
.icon {
grid-area: icon;
}
.title {
max-width: 100%;
display: flex;
flex-direction: column;
justify-content: center;
grid-area: title;
white-space: nowrap;
.title-text {
width: 100%;
overflow: hidden;
text-overflow: ellipsis;
font-size: var(--font-size-md);
font-weight: bold;
}
}
.author {
font-size: var(--font-size-sm);
grid-area: author;
}
.banner {
grid-area: banner;
background-size: cover;
background-position: center;
position: relative;
.badges-wrapper {
width: 100%;
height: 100%;
display: flex;
position: absolute;
top: 0;
left: 0;
mix-blend-mode: hard-light;
}
.badges {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
padding: var(--gap-sm);
gap: var(--gap-xs);
display: flex;
z-index: 10;
flex-direction: row;
justify-content: flex-end;
align-items: flex-end;
}
}
.description {
grid-area: description;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
}
}
.badge {
background-color: var(--color-raised-bg);
font-size: var(--font-size-xs);
padding: var(--gap-xs) var(--gap-sm);
border-radius: var(--radius-sm);
svg {
width: 1rem;
height: 1rem;
margin-right: var(--gap-xs);
}
}
.install {
position: absolute;
top: calc(5rem + var(--gap-sm));
right: var(--gap-sm);
z-index: 10;
opacity: 0;
transition: opacity 0.2s ease-in-out;
svg {
width: 1rem;
height: 1rem;
}
}
</style>
<style scoped lang="scss"></style>
@@ -1,23 +1,39 @@
<script setup lang="ts">
import { ref } from 'vue'
import { Promotion } from '@modrinth/ui'
import { get as getCreds } from '@/helpers/mr_auth.js'
import { handleError } from '@/store/notifications.js'
import { get_user } from '@/helpers/cache.js'
<script setup>
import { ref, onMounted } from 'vue'
import { init_ads_window } from '@/helpers/ads.js'
const showAd = ref(true)
const adsWrapper = ref(null)
const creds = await getCreds().catch(handleError)
if (creds && creds.user_id) {
const user = await get_user(creds.user_id).catch(handleError)
let devicePixelRatioWatcher = null
const MIDAS_BITFLAG = 1 << 0
if (user && (user.badges & MIDAS_BITFLAG) === MIDAS_BITFLAG) {
showAd.value = false
function initDevicePixelRatioWatcher() {
if (devicePixelRatioWatcher) {
devicePixelRatioWatcher.removeEventListener('change', updateAdPosition)
}
devicePixelRatioWatcher = window.matchMedia(`(resolution: ${window.devicePixelRatio}dppx)`)
devicePixelRatioWatcher.addEventListener('change', updateAdPosition)
}
onMounted(() => {
updateAdPosition()
window.addEventListener('resize', updateAdPosition)
initDevicePixelRatioWatcher()
})
function updateAdPosition() {
if (adsWrapper.value) {
init_ads_window()
initDevicePixelRatioWatcher()
}
}
</script>
<template>
<Promotion v-if="showAd" :external="false" query-param="?r=launcher" />
<div ref="adsWrapper" class="ad-parent relative flex w-full justify-center cursor-pointer bg-bg">
<div class="flex max-h-[250px] min-h-[250px] min-w-[300px] max-w-[300px] flex-col gap-4 p-6">
<p class="m-0 text-2xl font-bold text-contrast">75% of ad revenue goes to creators</p>
</div>
</div>
</template>
@@ -0,0 +1,73 @@
<script setup>
import { list } from '@/helpers/profile'
import { handleError } from '@/store/notifications'
import dayjs from 'dayjs'
import { onUnmounted, ref } from 'vue'
import { profile_listener } from '@/helpers/events.js'
import NavButton from '@/components/ui/NavButton.vue'
import { Avatar } from '@modrinth/ui'
import { convertFileSrc } from '@tauri-apps/api/core'
import { SpinnerIcon } from '@modrinth/assets'
const recentInstances = ref([])
const getInstances = async () => {
const profiles = await list().catch(handleError)
recentInstances.value = profiles
.sort((a, b) => {
const dateACreated = dayjs(a.created)
const dateAPlayed = a.last_played ? dayjs(a.last_played) : dayjs(0)
const dateBCreated = dayjs(b.created)
const dateBPlayed = b.last_played ? dayjs(b.last_played) : dayjs(0)
const dateA = dateACreated.isAfter(dateAPlayed) ? dateACreated : dateAPlayed
const dateB = dateBCreated.isAfter(dateBPlayed) ? dateBCreated : dateBPlayed
if (dateA.isSame(dateB)) {
return a.name.localeCompare(b.name)
}
return dateB - dateA
})
.slice(0, 4)
}
await getInstances()
const unlistenProfile = await profile_listener(async (event) => {
if (event.event !== 'synced') {
await getInstances()
}
})
onUnmounted(() => {
unlistenProfile()
})
</script>
<template>
<NavButton
v-for="instance in recentInstances"
:key="instance.id"
v-tooltip.right="instance.name"
:to="`/instance/${encodeURIComponent(instance.path)}`"
class="relative"
>
<Avatar
:src="instance.icon_path ? convertFileSrc(instance.icon_path) : null"
size="28px"
:tint-by="instance.path"
:class="`transition-all ${instance.install_stage !== 'installed' ? `brightness-[0.25] scale-[0.85]` : `group-hover:brightness-75`}`"
/>
<div
v-if="instance.install_stage !== 'installed'"
class="absolute inset-0 flex items-center justify-center z-10"
>
<SpinnerIcon class="animate-spin w-4 h-4" />
</div>
</NavButton>
<div v-if="recentInstances.length > 0" class="h-px w-6 mx-auto my-2 bg-button-bg"></div>
</template>
<style scoped lang="scss"></style>
@@ -1,20 +1,12 @@
<template>
<div class="action-groups">
<a href="https://support.modrinth.com" class="link">
<ChatIcon />
<span> Get support </span>
</a>
<Button
v-if="currentLoadingBars.length > 0"
ref="infoButton"
icon-only
class="icon-button show-card-icon"
@click="toggleCard()"
>
<DownloadIcon />
</Button>
<ButtonStyled v-if="currentLoadingBars.length > 0" color="brand" type="transparent" circular>
<button ref="infoButton" @click="toggleCard()">
<DownloadIcon />
</button>
</ButtonStyled>
<div v-if="offline" class="status">
<span class="circle stopped" />
<UnplugIcon />
<div class="running-text">
<span> Offline </span>
</div>
@@ -45,15 +37,6 @@
<Button v-tooltip="'View logs'" icon-only class="icon-button" @click="goToTerminal()">
<TerminalSquareIcon />
</Button>
<Button
v-if="currentLoadingBars.length > 0"
ref="infoButton"
icon-only
class="icon-button show-card-icon"
@click="toggleCard()"
>
<DownloadIcon />
</Button>
</div>
<div v-else class="status">
<span class="circle stopped" />
@@ -108,8 +91,14 @@
</template>
<script setup>
import { DownloadIcon, StopCircleIcon, TerminalSquareIcon, DropdownIcon } from '@modrinth/assets'
import { Button, Card } from '@modrinth/ui'
import {
DownloadIcon,
StopCircleIcon,
TerminalSquareIcon,
DropdownIcon,
UnplugIcon,
} from '@modrinth/assets'
import { Button, ButtonStyled, Card } from '@modrinth/ui'
import { onBeforeUnmount, onMounted, ref } from 'vue'
import { get_all as getRunningProcesses, kill as killProcess } from '@/helpers/process'
import { loading_listener, process_listener } from '@/helpers/events'
@@ -117,7 +106,6 @@ import { useRouter } from 'vue-router'
import { progress_bars_list } from '@/helpers/state.js'
import ProgressBar from '@/components/ui/ProgressBar.vue'
import { handleError } from '@/store/notifications.js'
import { ChatIcon } from '@/assets/icons'
import { get_many } from '@/helpers/profile.js'
import { trackEvent } from '@/helpers/analytics'
@@ -408,10 +396,6 @@ onBeforeUnmount(() => {
}
}
.show-card-icon {
color: var(--color-brand);
}
.download-enter-active,
.download-leave-active {
transition: opacity 0.3s ease;
+106 -128
View File
@@ -1,74 +1,128 @@
<template>
<Card
class="card button-base"
<div
class="card-shadow p-4 bg-bg-raised rounded-xl flex gap-3 group cursor-pointer hover:brightness-90 transition-all"
@click="
() => {
emits('open')
emit('open')
$router.push({
path: `/project/${project.project_id ?? project.id}/`,
path: `/project/${project.project_id ?? project.id}`,
query: { i: props.instance ? props.instance.path : undefined },
})
}
"
>
<div class="icon">
<Avatar :src="project.icon_url" size="md" class="search-icon" />
<div class="icon w-[96px] h-[96px] relative">
<Avatar :src="project.icon_url" size="96px" class="search-icon origin-top transition-all" />
</div>
<div class="content-wrapper">
<div class="title joined-text">
<h2>{{ project.title }}</h2>
<span v-if="project.author">by {{ project.author }}</span>
<div class="flex flex-col gap-2 overflow-hidden">
<div class="gap-2 overflow-hidden no-wrap text-ellipsis">
<span class="text-lg font-extrabold text-contrast m-0 leading-none">
{{ project.title }}
</span>
<span v-if="project.author" class="text-secondary"> by {{ project.author }}</span>
</div>
<div class="description">
<div class="m-0 line-clamp-2">
{{ project.description }}
</div>
<div class="tags">
<Categories :categories="categories" :type="project.project_type">
<EnvironmentIndicator
:type-only="project.moderation"
:client-side="project.client_side"
:server-side="project.server_side"
:type="project.project_type"
:search="true"
/>
</Categories>
<div v-if="categories.length > 0" class="mt-auto flex items-center gap-1 no-wrap">
<TagsIcon class="h-4 w-4 shrink-0" />
<div
v-if="project.project_type === 'mod' || project.project_type === 'modpack'"
class="text-sm font-semibold text-secondary flex gap-1 px-[0.375rem] py-0.5 bg-button-bg rounded-full"
>
<template v-if="project.client_side === 'optional' && project.server_side === 'optional'">
Client or server
</template>
<template
v-else-if="
(project.client_side === 'optional' || project.client_side === 'required') &&
(project.server_side === 'optional' || project.server_side === 'unsupported')
"
>
Client
</template>
<template
v-else-if="
(project.server_side === 'optional' || project.server_side === 'required') &&
(project.client_side === 'optional' || project.client_side === 'unsupported')
"
>
Server
</template>
<template
v-else-if="
project.client_side === 'unsupported' && project.server_side === 'unsupported'
"
>
Unsupported
</template>
<template
v-else-if="project.client_side === 'required' && project.server_side === 'required'"
>
Client and server
</template>
</div>
<div
v-for="tag in categories"
:key="tag"
class="text-sm font-semibold text-secondary flex gap-1 px-[0.375rem] py-0.5 bg-button-bg rounded-full"
>
{{ formatCategory(tag.name) }}
</div>
</div>
</div>
<div class="stats button-group">
<div v-if="featured" class="badge">
<StarIcon />
Featured
<div class="flex flex-col gap-2 items-end shrink-0 ml-auto">
<div class="flex items-center gap-2">
<DownloadIcon class="shrink-0" />
<span>
{{ formatNumber(project.downloads) }}
<span class="text-secondary">downloads</span>
</span>
</div>
<div class="badge">
<DownloadIcon />
{{ formatNumber(project.downloads) }}
<div class="flex items-center gap-2">
<HeartIcon class="shrink-0" />
<span>
{{ formatNumber(project.follows ?? project.followers) }}
<span class="text-secondary">followers</span>
</span>
</div>
<div class="badge">
<HeartIcon />
{{ formatNumber(project.follows ?? project.followers) }}
</div>
<div class="badge">
<CalendarIcon />
{{ formatCategory(dayjs(project.date_modified ?? project.updated).fromNow()) }}
<div class="mt-auto relative">
<div class="absolute bottom-0 right-0 w-fit">
<ButtonStyled color="brand" type="outlined">
<button
:disabled="installed || installing"
class="shrink-0 no-wrap"
@click.stop="install()"
>
<template v-if="!installed">
<DownloadIcon v-if="modpack || instance" />
<PlusIcon v-else />
</template>
<CheckIcon v-else />
{{
installing
? 'Installing'
: installed
? 'Installed'
: modpack || instance
? 'Install'
: 'Add to an instance'
}}
</button>
</ButtonStyled>
</div>
</div>
</div>
<div v-if="project.author" class="install">
<Button color="primary" :disabled="installed || installing" @click.stop="install()">
<DownloadIcon v-if="!installed" />
<CheckIcon v-else />
{{ installing ? 'Installing' : installed ? 'Installed' : 'Install' }}
</Button>
</div>
</Card>
</div>
</template>
<script setup>
import { DownloadIcon, HeartIcon, CalendarIcon, CheckIcon, StarIcon } from '@modrinth/assets'
import { Avatar, Card, Categories, EnvironmentIndicator, Button } from '@modrinth/ui'
import { TagsIcon, DownloadIcon, HeartIcon, PlusIcon, CheckIcon } from '@modrinth/assets'
import { ButtonStyled, Avatar } from '@modrinth/ui'
import { formatNumber, formatCategory } from '@modrinth/utils'
import dayjs from 'dayjs'
import relativeTime from 'dayjs/plugin/relativeTime'
import { ref } from 'vue'
import { ref, computed } from 'vue'
import { install as installVersion } from '@/store/install.js'
dayjs.extend(relativeTime)
@@ -99,99 +153,23 @@ const props = defineProps({
},
})
const emits = defineEmits(['open'])
const emit = defineEmits(['open', 'install'])
const installing = ref(false)
const installed = ref(props.installed)
async function install() {
installing.value = true
await installVersion(
props.project.project_id,
props.project.project_id ?? props.project.id,
null,
props.instance ? props.instance.path : null,
'SearchCard',
(version) => {
() => {
installing.value = false
if (props.instance && version) {
installed.value = true
}
emit('install', props.project.project_id ?? props.project.id)
},
)
}
const modpack = computed(() => props.project.project_type === 'modpack')
</script>
<style scoped lang="scss">
.icon {
grid-column: 1;
grid-row: 1;
align-self: center;
height: 6rem;
}
.content-wrapper {
display: flex;
justify-content: space-between;
grid-column: 2 / 4;
flex-direction: column;
grid-row: 1;
gap: 0.5rem;
.description {
word-wrap: break-word;
overflow-wrap: anywhere;
}
}
.stats {
grid-column: 1 / 3;
grid-row: 2;
justify-self: stretch;
align-self: start;
}
.install {
grid-column: 3 / 4;
grid-row: 2;
justify-self: end;
align-self: start;
}
.card {
margin-bottom: 0;
display: grid;
grid-template-columns: 6rem auto 7rem;
gap: 0.75rem;
padding: 1rem;
&:active:not(&:disabled) {
scale: 0.98 !important;
}
}
.joined-text {
display: inline-flex;
flex-wrap: wrap;
flex-direction: row;
column-gap: 0.5rem;
align-items: baseline;
overflow: hidden;
text-overflow: ellipsis;
h2 {
margin-bottom: 0 !important;
word-wrap: break-word;
overflow-wrap: anywhere;
}
}
.button-group {
display: inline-flex;
flex-direction: row;
gap: 0.5rem;
align-items: flex-start;
flex-wrap: wrap;
justify-content: flex-start;
}
</style>
@@ -72,7 +72,7 @@
</g>
</g>
</svg>
<ProgressBar class="loading-bar" :progress="loadingProgress" />
<ProgressBar class="loading-bar" :progress="Math.min(loadingProgress, 100)" />
<span v-if="message">{{ message }}</span>
</div>
<div class="gradient-bg" data-tauri-drag-region></div>
@@ -86,10 +86,7 @@ import { ref, watch } from 'vue'
import ProgressBar from '@/components/ui/ProgressBar.vue'
import { loading_listener } from '@/helpers/events.js'
import { getCurrentWindow } from '@tauri-apps/api/window'
import { XIcon } from '@modrinth/assets'
import { MaximizeIcon, MinimizeIcon } from '@/assets/icons/index.js'
import { TauriEvent } from '@tauri-apps/api/event'
import { saveWindowState, StateFlags } from '@tauri-apps/plugin-window-state'
import { XIcon, MaximizeIcon, MinimizeIcon } from '@modrinth/assets'
import { getOS } from '@/helpers/utils.js'
import { useLoading } from '@/store/loading.js'
@@ -133,6 +130,12 @@ loading_listener(async (e) => {
if (e.event.type === 'directory_move') {
loadingProgress.value = 100 * (e.fraction ?? 1)
message.value = 'Updating app directory...'
} else if (e.event.type === 'launcher_update') {
loadingProgress.value = 100 * (e.fraction ?? 1)
message.value = 'Updating Modrinth App...'
} else if (e.event.type === 'checking_for_updates') {
loadingProgress.value = 100 * (e.fraction ?? 1)
message.value = 'Checking for updates...'
}
})
@@ -1,11 +1,12 @@
<script setup>
import { Modal, Button } from '@modrinth/ui'
import { Button } from '@modrinth/ui'
import { ref } from 'vue'
import SearchCard from '@/components/ui/SearchCard.vue'
import { get_categories } from '@/helpers/tags.js'
import { handleError } from '@/store/notifications.js'
import { get_version, get_project } from '@/helpers/cache.js'
import { install as installVersion } from '@/store/install.js'
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
const confirmModal = ref(null)
const project = ref(null)
@@ -41,7 +42,7 @@ async function install() {
</script>
<template>
<Modal ref="confirmModal" :header="`Install ${project?.title}`">
<ModalWrapper ref="confirmModal" :header="`Install ${project?.title}`">
<div class="modal-body">
<SearchCard
:project="project"
@@ -60,7 +61,7 @@ async function install() {
</div>
</div>
</div>
</Modal>
</ModalWrapper>
</template>
<style scoped lang="scss">
@@ -70,7 +71,6 @@ async function install() {
align-items: center;
justify-content: center;
gap: var(--gap-md);
padding: var(--gap-lg);
}
.button-row {
@@ -0,0 +1,357 @@
<script setup lang="ts">
import { Avatar, ButtonStyled, OverflowMenu } from '@modrinth/ui'
import {
UserPlusIcon,
MoreVerticalIcon,
MailIcon,
SettingsIcon,
TrashIcon,
XIcon,
} from '@modrinth/assets'
import { ref, onUnmounted, watch, computed } from 'vue'
import { friend_listener } from '@/helpers/events'
import { friends, friend_statuses, add_friend, remove_friend } from '@/helpers/friends'
import { get_user_many } from '@/helpers/cache'
import { handleError } from '@/store/notifications.js'
import ContextMenu from '@/components/ui/ContextMenu.vue'
import type { Dayjs } from 'dayjs'
import dayjs from 'dayjs'
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
const props = defineProps<{
credentials: unknown | null
signIn: () => void
}>()
const userCredentials = computed(() => props.credentials)
const search = ref('')
const manageFriendsModal = ref()
const friendInvitesModal = ref()
const username = ref('')
const addFriendModal = ref()
async function addFriendFromModal() {
addFriendModal.value.hide()
await add_friend(username.value).catch(handleError)
username.value = ''
await loadFriends()
}
const friendOptions = ref()
async function handleFriendOptions(args) {
switch (args.option) {
case 'remove-friend':
await removeFriend(args.item)
break
}
}
async function addFriend(friend: Friend) {
await add_friend(
friend.id === userCredentials.value.user_id ? friend.friend_id : friend.id,
).catch(handleError)
await loadFriends()
}
async function removeFriend(friend: Friend) {
await remove_friend(
friend.id === userCredentials.value.user_id ? friend.friend_id : friend.id,
).catch(handleError)
await loadFriends()
}
type Friend = {
id: string
friend_id: string | null
status: string | null
last_updated: Dayjs | null
created: Dayjs
username: string
accepted: boolean
online: boolean
avatar: string
}
const userFriends = ref<Friend[]>([])
const acceptedFriends = computed(() =>
userFriends.value
.filter((x) => x.accepted)
.toSorted((a, b) => {
if (a.last_updated === null && b.last_updated === null) {
return 0 // Both are null, equal in sorting
}
if (a.last_updated === null) {
return 1 // `a` is null, move it after `b`
}
if (b.last_updated === null) {
return -1 // `b` is null, move it after `a`
}
// Both are non-null, sort by date
return b.last_updated.diff(a.last_updated)
}),
)
const pendingFriends = computed(() =>
userFriends.value.filter((x) => !x.accepted).toSorted((a, b) => b.created.diff(a.created)),
)
const loading = ref(true)
async function loadFriends(timeout = false) {
loading.value = timeout
try {
const friendsList = await friends()
if (friendsList.length === 0) {
userFriends.value = []
} else {
const friendStatuses = await friend_statuses()
const users = await get_user_many(
friendsList.map((x) => (x.id === userCredentials.value.user_id ? x.friend_id : x.id)),
)
userFriends.value = friendsList.map((friend) => {
const user = users.find((x) => x.id === friend.id || x.id === friend.friend_id)
const status = friendStatuses.find(
(x) => x.user_id === friend.id || x.user_id === friend.friend_id,
)
return {
id: friend.id,
friend_id: friend.friend_id,
status: status?.profile_name,
last_updated: status && status.last_update ? dayjs(status.last_update) : null,
created: dayjs(friend.created),
avatar: user?.avatar_url,
username: user?.username,
online: !!status,
accepted: friend.accepted,
}
})
}
loading.value = false
} catch (e) {
console.error('Error loading friends', e)
if (timeout) {
setTimeout(() => loadFriends(), 15 * 1000)
}
}
}
watch(
userCredentials,
() => {
if (userCredentials.value === undefined) {
userFriends.value = []
} else if (userCredentials.value === null) {
userFriends.value = []
loading.value = false
} else {
loadFriends(true)
}
},
{ immediate: true },
)
const unlisten = await friend_listener(() => loadFriends())
onUnmounted(() => {
unlisten()
})
</script>
<template>
<ModalWrapper ref="manageFriendsModal" header="Manage friends">
<p v-if="acceptedFriends.length === 0">Add friends to share what you're playing!</p>
<div v-else class="flex flex-col gap-4 min-w-[20rem]">
<input v-model="search" type="text" placeholder="Search friends..." class="w-full" />
<div
v-for="friend in acceptedFriends.filter(
(x) => !search || x.username.toLowerCase().includes(search),
)"
:key="friend.username"
class="flex gap-2 items-center"
>
<div class="relative">
<Avatar :src="friend.avatar" class="w-12 h-12 rounded-full" size="2.25rem" circle />
<span
v-if="friend.online"
class="bottom-1 right-0 absolute w-3 h-3 bg-brand border-2 border-black border-solid rounded-full"
/>
</div>
<div>{{ friend.username }}</div>
<div class="ml-auto">
<ButtonStyled>
<button @click="removeFriend(friend)">
<XIcon />
Remove
</button>
</ButtonStyled>
</div>
</div>
</div>
</ModalWrapper>
<ModalWrapper ref="friendInvitesModal" header="View friend requests">
<p v-if="pendingFriends.length === 0">You have no pending friend requests :C</p>
<div v-else class="flex flex-col gap-4">
<div v-for="friend in pendingFriends" :key="friend.username" class="flex gap-2">
<Avatar :src="friend.avatar" class="w-12 h-12 rounded-full" size="2.25rem" circle />
<div class="flex flex-col gap-2">
<div>
<p class="m-0">
<template v-if="friend.id === userCredentials.user_id">
<span class="font-bold">{{ friend.username }}</span> sent you a friend request
</template>
<template v-else>
You sent <span class="font-bold">{{ friend.username }}</span> a friend request
</template>
</p>
<p class="m-0 text-sm text-secondary">{{ friend.created.fromNow() }}</p>
</div>
<div class="flex gap-2">
<template v-if="friend.id === userCredentials.user_id">
<ButtonStyled color="brand">
<button @click="addFriend(friend)">
<UserPlusIcon />
Accept
</button>
</ButtonStyled>
<ButtonStyled>
<button @click="removeFriend(friend)">
<XIcon />
Ignore
</button>
</ButtonStyled>
</template>
<template v-else>
<ButtonStyled>
<button @click="removeFriend(friend)">
<XIcon />
Cancel
</button>
</ButtonStyled>
</template>
</div>
</div>
</div>
</div>
</ModalWrapper>
<ModalWrapper ref="addFriendModal" header="Add a friend">
<div class="mb-4">
<h2 class="m-0 text-lg font-extrabold text-contrast">Username</h2>
<p class="m-0 mt-1 leading-tight">You can add friends with their Modrinth username.</p>
<input v-model="username" class="mt-2 w-full" type="text" placeholder="Enter username..." />
</div>
<ButtonStyled color="brand">
<button :disabled="username.length === 0" @click="addFriendFromModal">
<UserPlusIcon />
Add friend
</button>
</ButtonStyled>
</ModalWrapper>
<div class="flex justify-between items-center">
<h3 class="text-lg m-0">Friends</h3>
<ButtonStyled v-if="userCredentials" type="transparent" circular>
<OverflowMenu
:options="[
{
id: 'add-friend',
action: () => addFriendModal.show(),
},
{
id: 'manage-friends',
action: () => manageFriendsModal.show(),
shown: acceptedFriends.length > 0,
},
{
id: 'view-requests',
action: () => friendInvitesModal.show(),
shown: pendingFriends.length > 0,
},
]"
aria-label="More options"
>
<MoreVerticalIcon aria-hidden="true" />
<template #add-friend>
<UserPlusIcon aria-hidden="true" />
Add friend
</template>
<template #manage-friends>
<SettingsIcon aria-hidden="true" />
Manage friends
<div
v-if="acceptedFriends.length > 0"
class="bg-button-bg w-6 h-6 rounded-full flex items-center justify-center"
>
{{ acceptedFriends.length }}
</div>
</template>
<template #view-requests>
<MailIcon aria-hidden="true" />
View friend requests
<div
v-if="pendingFriends.length > 0"
class="bg-button-bg w-6 h-6 rounded-full flex items-center justify-center"
>
{{ pendingFriends.length }}
</div>
</template>
</OverflowMenu>
</ButtonStyled>
</div>
<div class="flex flex-col gap-2 mt-2">
<template v-if="loading">
<div v-for="n in 5" :key="n" class="flex gap-2 items-center animate-pulse">
<div class="min-w-9 min-h-9 bg-button-bg rounded-full"></div>
<div class="flex flex-col w-full">
<div class="h-3 bg-button-bg rounded-full w-1/2 mb-1"></div>
<div class="h-2.5 bg-button-bg rounded-full w-3/4"></div>
</div>
</div>
</template>
<template v-else-if="acceptedFriends.length === 0">
<div class="text-sm">
<div v-if="!userCredentials">
<span class="text-link cursor-pointer" @click="signIn">Sign in</span> to add friends!
</div>
<div v-else>
<span class="text-link cursor-pointer" @click="addFriendModal.show()">Add friends</span>
to share what you're playing!
</div>
</div>
</template>
<template v-else>
<ContextMenu ref="friendOptions" @option-clicked="handleFriendOptions">
<template #remove-friend> <TrashIcon /> Remove friend </template>
</ContextMenu>
<div
v-for="friend in acceptedFriends.slice(0, 5)"
:key="friend.username"
class="flex gap-2 items-center"
:class="{ grayscale: !friend.online }"
@contextmenu.prevent.stop="
(event) =>
friendOptions.showMenu(event, friend, [
{
name: 'remove-friend',
color: 'danger',
},
])
"
>
<div class="relative">
<Avatar :src="friend.avatar" class="w-12 h-12 rounded-full" size="2.25rem" circle />
<span
v-if="friend.online"
class="bottom-1 right-0 absolute w-3 h-3 bg-brand border-2 border-black border-solid rounded-full"
/>
</div>
<div class="flex flex-col">
<span class="text-md m-0 font-medium" :class="{ 'text-secondary': !friend.online }">
{{ friend.username }}
</span>
<span v-if="friend.status" class="m-0 text-xs">{{ friend.status }}</span>
</div>
</div>
</template>
</div>
</template>
@@ -1,10 +1,5 @@
<template>
<Modal
ref="incompatibleModal"
header="Incompatibility warning"
:noblur="!themeStore.advancedRendering"
:on-hide="onInstall"
>
<ModalWrapper ref="incompatibleModal" header="Incompatibility warning" :on-hide="onInstall">
<div class="modal-body">
<p>
This {{ versions?.length > 0 ? 'project' : 'version' }} is not compatible with the instance
@@ -12,37 +7,43 @@
installed.
</p>
<table>
<tr class="header">
<th>{{ instance?.name }}</th>
<th>{{ project.title }}</th>
</tr>
<tr class="content">
<td class="data">{{ instance?.loader }} {{ instance?.game_version }}</td>
<td>
<DropdownSelect
v-if="versions?.length > 1"
v-model="selectedVersion"
:options="versions"
placeholder="Select version"
name="Version select"
:display-name="
(version) =>
`${version?.name} (${version?.loaders
.map((name) => formatCategory(name))
.join(', ')} - ${version?.game_versions.join(', ')})`
"
render-up
/>
<span v-else>
<span>
{{ selectedVersion?.name }} ({{
selectedVersion?.loaders.map((name) => formatCategory(name)).join(', ')
}}
- {{ selectedVersion?.game_versions.join(', ') }})
<thead>
<tr class="header">
<th>{{ instance?.name }}</th>
<th>{{ project.title }}</th>
</tr>
</thead>
<tbody>
<tr class="content">
<td class="data">{{ instance?.loader }} {{ instance?.game_version }}</td>
<td>
<multiselect
v-if="versions?.length > 1"
v-model="selectedVersion"
:options="versions"
:searchable="true"
placeholder="Select version"
open-direction="top"
:show-labels="false"
:custom-label="
(version) =>
`${version?.name} (${version?.loaders
.map((name) => formatCategory(name))
.join(', ')} - ${version?.game_versions.join(', ')})`
"
:max-height="150"
/>
<span v-else>
<span>
{{ selectedVersion?.name }} ({{
selectedVersion?.loaders.map((name) => formatCategory(name)).join(', ')
}}
- {{ selectedVersion?.game_versions.join(', ') }})
</span>
</span>
</span>
</td>
</tr>
</td>
</tr>
</tbody>
</table>
<div class="button-group">
<Button @click="() => incompatibleModal.hide()"><XIcon />Cancel</Button>
@@ -51,19 +52,19 @@
</Button>
</div>
</div>
</Modal>
</ModalWrapper>
</template>
<script setup>
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
import { XIcon, DownloadIcon } from '@modrinth/assets'
import { Button, Modal, DropdownSelect } from '@modrinth/ui'
import { Button } from '@modrinth/ui'
import { formatCategory } from '@modrinth/utils'
import { add_project_from_version as installMod } from '@/helpers/profile'
import { ref } from 'vue'
import { handleError, useTheming } from '@/store/state.js'
import { handleError } from '@/store/state.js'
import { trackEvent } from '@/helpers/analytics'
const themeStore = useTheming()
import Multiselect from 'vue-multiselect'
const instance = ref(null)
const project = ref(null)
@@ -72,7 +73,7 @@ const selectedVersion = ref(null)
const incompatibleModal = ref(null)
const installing = ref(false)
let onInstall = ref(() => {})
const onInstall = ref(() => {})
defineExpose({
show: (instanceVal, projectVal, projectVersions, callback) => {
@@ -157,7 +158,6 @@ td:first-child {
display: flex;
flex-direction: column;
gap: 1rem;
padding: 1rem;
:deep(.animated-dropdown .options) {
max-height: 13.375rem;
@@ -1,20 +1,18 @@
<script setup>
import { XIcon, DownloadIcon } from '@modrinth/assets'
import { Button, Modal } from '@modrinth/ui'
import { install as pack_install } from '@/helpers/pack'
import { DownloadIcon, XIcon } from '@modrinth/assets'
import { Button } from '@modrinth/ui'
import { create_profile_and_install as pack_install } from '@/helpers/pack'
import { ref } from 'vue'
import { trackEvent } from '@/helpers/analytics'
import { useTheming } from '@/store/theme.js'
import { handleError } from '@/store/state.js'
const themeStore = useTheming()
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
const versionId = ref()
const project = ref()
const confirmModal = ref(null)
const installing = ref(false)
let onInstall = ref(() => {})
const onInstall = ref(() => {})
defineExpose({
show: (projectVal, versionIdVal, callback) => {
@@ -52,12 +50,7 @@ async function install() {
</script>
<template>
<Modal
ref="confirmModal"
header="Are you sure?"
:noblur="!themeStore.advancedRendering"
:on-hide="onInstall"
>
<ModalWrapper ref="confirmModal" header="Are you sure?" :on-hide="onInstall">
<div class="modal-body">
<p>You already have this modpack installed. Are you sure you want to install it again?</p>
<div class="input-group push-right">
@@ -67,7 +60,7 @@ async function install() {
>
</div>
</div>
</Modal>
</ModalWrapper>
</template>
<style lang="scss" scoped>
@@ -75,6 +68,5 @@ async function install() {
display: flex;
flex-direction: column;
gap: 1rem;
padding: 1rem;
}
</style>
@@ -7,7 +7,7 @@ import {
RightArrowIcon,
CheckIcon,
} from '@modrinth/assets'
import { Avatar, Modal, Button, Card } from '@modrinth/ui'
import { Avatar, Button, Card } from '@modrinth/ui'
import { computed, ref } from 'vue'
import {
add_project_from_version as installMod,
@@ -19,12 +19,11 @@ import {
import { open } from '@tauri-apps/plugin-dialog'
import { installVersionDependencies } from '@/store/install.js'
import { handleError } from '@/store/notifications.js'
import { useTheming } from '@/store/theme.js'
import { useRouter } from 'vue-router'
import { convertFileSrc } from '@tauri-apps/api/core'
import { trackEvent } from '@/helpers/analytics'
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
const themeStore = useTheming()
const router = useRouter()
const versions = ref()
@@ -49,7 +48,7 @@ const shownProfiles = computed(() =>
return profile.name.toLowerCase().includes(searchFilter.value.toLowerCase())
})
.filter((profile) => {
let loaders = versions.value.flatMap((v) => v.loaders)
const loaders = versions.value.flatMap((v) => v.loaders)
return (
versions.value.flatMap((v) => v.game_versions).includes(profile.game_version) &&
@@ -60,7 +59,7 @@ const shownProfiles = computed(() =>
}),
)
let onInstall = ref(() => {})
const onInstall = ref(() => {})
defineExpose({
show: async (projectVal, versionsVal, callback) => {
@@ -78,7 +77,7 @@ defineExpose({
onInstall.value = callback
const profilesVal = await list().catch(handleError)
for (let profile of profilesVal) {
for (const profile of profilesVal) {
profile.installing = false
profile.installedMod = await check_installed(profile.path, project.value.id).catch(
handleError,
@@ -142,7 +141,7 @@ const toggleCreation = () => {
}
const upload_icon = async () => {
icon.value = await open({
const res = await open({
multiple: false,
filters: [
{
@@ -151,6 +150,7 @@ const upload_icon = async () => {
},
],
})
icon.value = res.path ?? res
if (!icon.value) return
display_icon.value = convertFileSrc(icon.value)
@@ -213,12 +213,7 @@ const createInstance = async () => {
</script>
<template>
<Modal
ref="installModal"
header="Install project to instance"
:noblur="!themeStore.advancedRendering"
:on-hide="onInstall"
>
<ModalWrapper ref="installModal" header="Install project to instance" :on-hide="onInstall">
<div class="modal-body">
<input
v-model="searchFilter"
@@ -235,7 +230,7 @@ const createInstance = async () => {
@click="installModal.hide()"
>
<Avatar
:src="profile.icon_path ? tauri.convertFileSrc(profile.icon_path) : null"
:src="profile.icon_path ? convertFileSrc(profile.icon_path) : null"
class="profile-image"
/>
{{ profile.name }}
@@ -248,7 +243,7 @@ const createInstance = async () => {
"
>
<Button
:disabled="profile.installedMod || profile.installing || profile.linked_data?.locked"
:disabled="profile.installedMod || profile.installing"
@click="install(profile)"
>
<DownloadIcon v-if="!profile.installedMod && !profile.installing" />
@@ -258,9 +253,7 @@ const createInstance = async () => {
? 'Installing...'
: profile.installedMod
? 'Installed'
: profile.linked_data && profile.linked_data.locked
? 'Paired'
: 'Install'
: 'Install'
}}
</Button>
</div>
@@ -304,7 +297,7 @@ const createInstance = async () => {
<Button @click="installModal.hide()">Cancel</Button>
</div>
</div>
</Modal>
</ModalWrapper>
</template>
<style scoped lang="scss">
@@ -313,7 +306,6 @@ const createInstance = async () => {
flex-direction: column;
gap: 1rem;
margin: 0;
padding: 1rem;
background-color: var(--color-bg);
}
@@ -365,7 +357,7 @@ const createInstance = async () => {
display: flex;
flex-direction: column;
gap: 1rem;
padding: 1rem;
min-width: 350px;
}
.profiles {
@@ -386,7 +378,6 @@ const createInstance = async () => {
flex-direction: row;
justify-content: space-between;
align-items: center;
padding: 0 0.5rem;
gap: 0.5rem;
img {
@@ -0,0 +1,326 @@
<script setup lang="ts">
import { convertFileSrc } from '@tauri-apps/api/core'
import { SpinnerIcon, TrashIcon, UploadIcon, PlusIcon, EditIcon, CopyIcon } from '@modrinth/assets'
import { Avatar, ButtonStyled, OverflowMenu, Checkbox } from '@modrinth/ui'
import { computed, ref, type Ref, watch } from 'vue'
import { duplicate, edit, edit_icon, list, remove } from '@/helpers/profile'
import { handleError } from '@/store/notifications'
import { trackEvent } from '@/helpers/analytics'
import { open } from '@tauri-apps/plugin-dialog'
import { defineMessages, useVIntl } from '@vintl/vintl'
import { useRouter } from 'vue-router'
import ConfirmModalWrapper from '@/components/ui/modal/ConfirmModalWrapper.vue'
import type { InstanceSettingsTabProps, GameInstance } from '../../../helpers/types'
const { formatMessage } = useVIntl()
const router = useRouter()
const deleteConfirmModal = ref()
const props = defineProps<InstanceSettingsTabProps>()
const title = ref(props.instance.name)
const icon: Ref<string | undefined> = ref(props.instance.icon_path)
const groups = ref(props.instance.groups)
const newCategoryInput = ref('')
const installing = computed(() => props.instance.install_stage !== 'installed')
async function duplicateProfile() {
await duplicate(props.instance.path).catch(handleError)
trackEvent('InstanceDuplicate', {
loader: props.instance.loader,
game_version: props.instance.game_version,
})
}
const allInstances = ref((await list()) as GameInstance[])
const availableGroups = computed(() => [
...new Set([...allInstances.value.flatMap((instance) => instance.groups), ...groups.value]),
])
async function resetIcon() {
icon.value = undefined
await edit_icon(props.instance.path, null).catch(handleError)
trackEvent('InstanceRemoveIcon')
}
async function setIcon() {
const value = await open({
multiple: false,
filters: [
{
name: 'Image',
extensions: ['png', 'jpeg', 'svg', 'webp', 'gif', 'jpg'],
},
],
})
if (!value) return
icon.value = value
await edit_icon(props.instance.path, icon.value).catch(handleError)
trackEvent('InstanceSetIcon')
}
const editProfileObject = computed(() => ({
name: title.value.trim().substring(0, 32) ?? 'Instance',
groups: groups.value.map((x) => x.trim().substring(0, 32)).filter((x) => x.length > 0),
}))
const toggleGroup = (group: string) => {
if (groups.value.includes(group)) {
groups.value = groups.value.filter((x) => x !== group)
} else {
groups.value.push(group)
}
}
const addCategory = () => {
const text = newCategoryInput.value.trim()
if (text.length > 0) {
groups.value.push(text.substring(0, 32))
newCategoryInput.value = ''
}
}
watch(
[title, groups, groups],
async () => {
await edit(props.instance.path, editProfileObject.value)
},
{ deep: true },
)
const removing = ref(false)
async function removeProfile() {
removing.value = true
await remove(props.instance.path).catch(handleError)
removing.value = false
trackEvent('InstanceRemove', {
loader: props.instance.loader,
game_version: props.instance.game_version,
})
await router.push({ path: '/' })
}
const messages = defineMessages({
name: {
id: 'instance.settings.tabs.general.name',
defaultMessage: 'Name',
},
libraryGroups: {
id: 'instance.settings.tabs.general.library-groups',
defaultMessage: 'Library groups',
},
libraryGroupsDescription: {
id: 'instance.settings.tabs.general.library-groups.description',
defaultMessage:
'Library groups allow you to organize your instances into different sections in your library.',
},
libraryGroupsEnterName: {
id: 'instance.settings.tabs.general.library-groups.enter-name',
defaultMessage: 'Enter group name',
},
libraryGroupsCreate: {
id: 'instance.settings.tabs.general.library-groups.create',
defaultMessage: 'Create new group',
},
editIcon: {
id: 'instance.settings.tabs.general.edit-icon',
defaultMessage: 'Edit icon',
},
selectIcon: {
id: 'instance.settings.tabs.general.edit-icon.select',
defaultMessage: 'Select icon',
},
replaceIcon: {
id: 'instance.settings.tabs.general.edit-icon.replace',
defaultMessage: 'Replace icon',
},
removeIcon: {
id: 'instance.settings.tabs.general.edit-icon.remove',
defaultMessage: 'Remove icon',
},
duplicateInstance: {
id: 'instance.settings.tabs.general.duplicate-instance',
defaultMessage: 'Duplicate instance',
},
duplicateInstanceDescription: {
id: 'instance.settings.tabs.general.duplicate-instance.description',
defaultMessage: 'Creates a copy of this instance, including worlds, configs, mods, etc.',
},
duplicateButtonTooltipInstalling: {
id: 'instance.settings.tabs.general.duplicate-button.tooltip.installing',
defaultMessage: 'Cannot duplicate while installing.',
},
duplicateButton: {
id: 'instance.settings.tabs.general.duplicate-button',
defaultMessage: 'Duplicate',
},
deleteInstance: {
id: 'instance.settings.tabs.general.delete',
defaultMessage: 'Delete instance',
},
deleteInstanceDescription: {
id: 'instance.settings.tabs.general.delete.description',
defaultMessage:
'Permanently deletes an instance from your device, including your worlds, configs, and all installed content. Be careful, as once you delete a instance there is no way to recover it.',
},
deleteInstanceButton: {
id: 'instance.settings.tabs.general.delete.button',
defaultMessage: 'Delete instance',
},
deletingInstanceButton: {
id: 'instance.settings.tabs.general.deleting.button',
defaultMessage: 'Deleting...',
},
})
</script>
<template>
<ConfirmModalWrapper
ref="deleteConfirmModal"
title="Are you sure you want to delete this instance?"
description="If you proceed, all data for your instance will be permanently erased, including your worlds. You will not be able to recover it."
:has-to-type="false"
proceed-label="Delete"
:show-ad-on-close="false"
@proceed="removeProfile"
/>
<div class="block">
<div class="float-end ml-4 relative group">
<OverflowMenu
v-tooltip="formatMessage(messages.editIcon)"
class="bg-transparent border-none appearance-none p-0 m-0 cursor-pointer group-active:scale-95 transition-transform"
:options="[
{
id: 'select',
action: () => setIcon(),
},
{
id: 'remove',
color: 'danger',
action: () => resetIcon(),
shown: !!icon,
},
]"
>
<Avatar
:src="icon ? convertFileSrc(icon) : icon"
size="108px"
class="!border-4 group-hover:brightness-75"
:tint-by="props.instance.path"
no-shadow
/>
<div class="absolute top-0 right-0 m-2">
<div
class="p-2 m-0 text-primary flex items-center justify-center aspect-square bg-button-bg rounded-full border-button-border border-solid border-[1px] hovering-icon-shadow"
>
<EditIcon aria-hidden="true" class="h-4 w-4 text-primary" />
</div>
</div>
<template #select>
<UploadIcon />
{{ icon ? formatMessage(messages.replaceIcon) : formatMessage(messages.selectIcon) }}
</template>
<template #remove> <TrashIcon /> {{ formatMessage(messages.removeIcon) }} </template>
</OverflowMenu>
</div>
<label for="instance-name" class="m-0 mb-1 text-lg font-extrabold text-contrast block">
{{ formatMessage(messages.name) }}
</label>
<div class="flex">
<input
id="instance-name"
v-model="title"
autocomplete="off"
maxlength="80"
class="flex-grow"
type="text"
/>
</div>
<template v-if="instance.install_stage == 'installed'">
<div>
<h2
id="duplicate-instance-label"
class="m-0 mt-4 mb-1 text-lg font-extrabold text-contrast block"
>
{{ formatMessage(messages.duplicateInstance) }}
</h2>
<p class="m-0 mb-2">
{{ formatMessage(messages.duplicateInstanceDescription) }}
</p>
</div>
<ButtonStyled>
<button
v-tooltip="installing ? formatMessage(messages.duplicateButtonTooltipInstalling) : null"
aria-labelledby="duplicate-instance-label"
:disabled="installing"
@click="duplicateProfile"
>
<CopyIcon /> {{ formatMessage(messages.duplicateButton) }}
</button>
</ButtonStyled>
</template>
<h2 class="m-0 mt-4 mb-1 text-lg font-extrabold text-contrast block">
{{ formatMessage(messages.libraryGroups) }}
</h2>
<p class="m-0 mb-2">
{{ formatMessage(messages.libraryGroupsDescription) }}
</p>
<div class="flex flex-col gap-1">
<Checkbox
v-for="group in availableGroups"
:key="group"
:model-value="groups.includes(group)"
:label="group"
@click="toggleGroup(group)"
/>
<div class="flex gap-2 items-center">
<input
v-model="newCategoryInput"
type="text"
:placeholder="formatMessage(messages.libraryGroupsEnterName)"
@submit="() => addCategory"
/>
<ButtonStyled>
<button class="w-fit" @click="() => addCategory()">
<PlusIcon /> {{ formatMessage(messages.libraryGroupsCreate) }}
</button>
</ButtonStyled>
</div>
</div>
<h2 id="delete-instance-label" class="m-0 mt-4 mb-1 text-lg font-extrabold text-contrast block">
{{ formatMessage(messages.deleteInstance) }}
</h2>
<p class="m-0 mb-2">
{{ formatMessage(messages.deleteInstanceDescription) }}
</p>
<ButtonStyled color="red">
<button
aria-labelledby="delete-instance-label"
:disabled="removing"
@click="deleteConfirmModal.show()"
>
<SpinnerIcon v-if="removing" class="animate-spin" />
<TrashIcon v-else />
{{
removing
? formatMessage(messages.deletingInstanceButton)
: formatMessage(messages.deleteInstanceButton)
}}
</button>
</ButtonStyled>
</div>
</template>
<style scoped lang="scss">
.hovering-icon-shadow {
box-shadow: var(--shadow-inset-sm), var(--shadow-raised);
}
</style>
@@ -0,0 +1,153 @@
<script setup lang="ts">
import { Checkbox } from '@modrinth/ui'
import { computed, ref, watch } from 'vue'
import { handleError } from '@/store/notifications'
import { defineMessages, useVIntl } from '@vintl/vintl'
import { get } from '@/helpers/settings'
import { edit } from '@/helpers/profile'
import type { InstanceSettingsTabProps, AppSettings, Hooks } from '../../../helpers/types'
const { formatMessage } = useVIntl()
const props = defineProps<InstanceSettingsTabProps>()
const globalSettings = (await get().catch(handleError)) as AppSettings
const overrideHooks = ref(
!!props.instance.hooks.pre_launch ||
!!props.instance.hooks.wrapper ||
!!props.instance.hooks.post_exit,
)
const hooks = ref(props.instance.hooks ?? globalSettings.hooks)
const editProfileObject = computed(() => {
const editProfile: {
hooks?: Hooks
} = {}
if (overrideHooks.value) {
editProfile.hooks = hooks.value
}
return editProfile
})
watch(
[overrideHooks, hooks],
async () => {
await edit(props.instance.path, editProfileObject.value)
},
{ deep: true },
)
const messages = defineMessages({
hooks: {
id: 'instance.settings.tabs.hooks.title',
defaultMessage: 'Game launch hooks',
},
hooksDescription: {
id: 'instance.settings.tabs.hooks.description',
defaultMessage:
'Hooks allow advanced users to run certain system commands before and after launching the game.',
},
customHooks: {
id: 'instance.settings.tabs.hooks.custom-hooks',
defaultMessage: 'Custom launch hooks',
},
preLaunch: {
id: 'instance.settings.tabs.hooks.pre-launch',
defaultMessage: 'Pre-launch',
},
preLaunchDescription: {
id: 'instance.settings.tabs.hooks.pre-launch.description',
defaultMessage: 'Ran before the instance is launched.',
},
preLaunchEnter: {
id: 'instance.settings.tabs.hooks.pre-launch.enter',
defaultMessage: 'Enter pre-launch command...',
},
wrapper: {
id: 'instance.settings.tabs.hooks.wrapper',
defaultMessage: 'Wrapper',
},
wrapperDescription: {
id: 'instance.settings.tabs.hooks.wrapper.description',
defaultMessage: 'Wrapper command for launching Minecraft.',
},
wrapperEnter: {
id: 'instance.settings.tabs.hooks.wrapper.enter',
defaultMessage: 'Enter wrapper command...',
},
postExit: {
id: 'instance.settings.tabs.hooks.post-exit',
defaultMessage: 'Post-exit',
},
postExitDescription: {
id: 'instance.settings.tabs.hooks.post-exit.description',
defaultMessage: 'Ran after the game closes.',
},
postExitEnter: {
id: 'instance.settings.tabs.hooks.post-exit.enter',
defaultMessage: 'Enter post-exit command...',
},
})
</script>
<template>
<div>
<h2 class="m-0 mb-1 text-lg font-extrabold text-contrast">
{{ formatMessage(messages.hooks) }}
</h2>
<p class="m-0">
{{ formatMessage(messages.hooksDescription) }}
</p>
<Checkbox v-model="overrideHooks" :label="formatMessage(messages.customHooks)" class="mt-2" />
<h2 class="mt-2 mb-1 text-lg font-extrabold text-contrast">
{{ formatMessage(messages.preLaunch) }}
</h2>
<p class="m-0">
{{ formatMessage(messages.preLaunchDescription) }}
</p>
<input
id="pre-launch"
v-model="hooks.pre_launch"
autocomplete="off"
:disabled="!overrideHooks"
type="text"
:placeholder="formatMessage(messages.preLaunchEnter)"
class="w-full mt-2"
/>
<h2 class="mt-4 mb-1 text-lg font-extrabold text-contrast">
{{ formatMessage(messages.wrapper) }}
</h2>
<p class="m-0">
{{ formatMessage(messages.wrapperDescription) }}
</p>
<input
id="wrapper"
v-model="hooks.wrapper"
autocomplete="off"
:disabled="!overrideHooks"
type="text"
:placeholder="formatMessage(messages.wrapperEnter)"
class="w-full mt-2"
/>
<h2 class="mt-4 mb-1 text-lg font-extrabold text-contrast">
{{ formatMessage(messages.postExit) }}
</h2>
<p class="m-0">
{{ formatMessage(messages.postExitDescription) }}
</p>
<input
id="post-exit"
v-model="hooks.post_exit"
autocomplete="off"
:disabled="!overrideHooks"
type="text"
:placeholder="formatMessage(messages.postExitEnter)"
class="w-full mt-2"
/>
</div>
</template>
@@ -0,0 +1,789 @@
<script setup lang="ts">
import {
TransferIcon,
IssuesIcon,
HammerIcon,
DownloadIcon,
WrenchIcon,
UndoIcon,
SpinnerIcon,
UnplugIcon,
UnlinkIcon,
} from '@modrinth/assets'
import { Avatar, Checkbox, Chips, ButtonStyled, TeleportDropdownMenu } from '@modrinth/ui'
import { computed, type ComputedRef, type Ref, ref, shallowRef, watch } from 'vue'
import { edit, install, update_repair_modrinth } from '@/helpers/profile'
import { handleError } from '@/store/notifications'
import { trackEvent } from '@/helpers/analytics'
import { defineMessages, useVIntl } from '@vintl/vintl'
import { get_loader_versions } from '@/helpers/metadata'
import { get_game_versions, get_loaders } from '@/helpers/tags'
import {
formatCategory,
type GameVersionTag,
type PlatformTag,
type Project,
type Version,
} from '@modrinth/utils'
import ConfirmModalWrapper from '@/components/ui/modal/ConfirmModalWrapper.vue'
import { get_project, get_version_many } from '@/helpers/cache'
import ModpackVersionModal from '@/components/ui/ModpackVersionModal.vue'
import dayjs from 'dayjs'
import type {
InstanceSettingsTabProps,
ManifestLoaderVersion,
Manifest,
} from '../../../helpers/types'
const { formatMessage } = useVIntl()
const repairConfirmModal = ref()
const modpackVersionModal = ref()
const modalConfirmUnpair = ref()
const modalConfirmReinstall = ref()
const props = defineProps<InstanceSettingsTabProps>()
const loader = ref(props.instance.loader)
const gameVersion = ref(props.instance.game_version)
const showSnapshots = ref(false)
const [
fabric_versions,
forge_versions,
quilt_versions,
neoforge_versions,
all_game_versions,
loaders,
] = await Promise.all([
get_loader_versions('fabric')
.then((manifest: Manifest) => shallowRef(manifest))
.catch(handleError),
get_loader_versions('forge')
.then((manifest: Manifest) => shallowRef(manifest))
.catch(handleError),
get_loader_versions('quilt')
.then((manifest: Manifest) => shallowRef(manifest))
.catch(handleError),
get_loader_versions('neo')
.then((manifest: Manifest) => shallowRef(manifest))
.catch(handleError),
get_game_versions()
.then((gameVersions: GameVersionTag[]) => shallowRef(gameVersions))
.catch(handleError),
get_loaders()
.then((value: PlatformTag[]) =>
value
.filter(
(item) => item.supported_project_types.includes('modpack') || item.name === 'vanilla',
)
.sort((a, b) => (a.name === 'vanilla' ? -1 : b.name === 'vanilla' ? 1 : 0)),
)
.then((loader: PlatformTag[]) => ref(loader))
.catch(handleError),
])
const modpackProject: Ref<Project | null> = ref(null)
const modpackVersion: Ref<Version | null> = ref(null)
const modpackVersions: Ref<Version[] | null> = ref(null)
const fetching = ref(true)
if (props.instance.linked_data && props.instance.linked_data.project_id && !props.offline) {
get_project(props.instance.linked_data.project_id, 'must_revalidate')
.then((project) => {
modpackProject.value = project
if (project && project.versions) {
get_version_many(project.versions, 'must_revalidate')
.then((versions: Version[]) => {
modpackVersions.value = versions.sort((a, b) =>
dayjs(b.date_published).diff(dayjs(a.date_published)),
)
modpackVersion.value =
versions.find(
(version: Version) => version.id === props.instance.linked_data?.version_id,
) ?? null
})
.catch(handleError)
.finally(() => {
fetching.value = false
})
}
})
.catch((err) => {
handleError(err)
fetching.value = false
})
} else {
fetching.value = false
}
const currentLoaderIcon = computed(
() => loaders?.value.find((x) => x.name === props.instance.loader)?.icon,
)
const gameVersionsForLoader = computed(() => {
return all_game_versions?.value.filter((item) => {
if (loader.value === 'fabric') {
return !!fabric_versions?.value.gameVersions.some((x) => item.version === x.id)
} else if (loader.value === 'forge') {
return !!forge_versions?.value.gameVersions.some((x) => item.version === x.id)
} else if (loader.value === 'quilt') {
return !!quilt_versions?.value.gameVersions.some((x) => item.version === x.id)
} else if (loader.value === 'neoforge') {
return !!neoforge_versions?.value.gameVersions.some((x) => item.version === x.id)
}
return []
})
})
const hasSnapshots = computed(() =>
gameVersionsForLoader.value?.some((x) => x.version_type !== 'release'),
)
const selectableGameVersionNumbers = computed(() => {
return gameVersionsForLoader.value
?.filter((x) => x.version_type === 'release' || showSnapshots.value)
.map((x) => x.version)
})
const selectableLoaderVersions: ComputedRef<ManifestLoaderVersion[] | undefined> = computed(() => {
if (gameVersion.value) {
if (loader.value === 'fabric') {
return fabric_versions?.value.gameVersions[0].loaders
} else if (loader.value === 'forge') {
return forge_versions?.value?.gameVersions?.find((item) => item.id === gameVersion.value)
?.loaders
} else if (loader.value === 'quilt') {
return quilt_versions?.value.gameVersions[0].loaders
} else if (loader.value === 'neoforge') {
return neoforge_versions?.value?.gameVersions?.find((item) => item.id === gameVersion.value)
?.loaders
}
}
return []
})
const loaderVersionIndex: Ref<number> = ref(-1)
resetLoaderVersionIndex()
function resetLoaderVersionIndex() {
loaderVersionIndex.value =
selectableLoaderVersions.value?.findIndex((x) => x.id === props.instance.loader_version) ?? -1
}
const isValid = computed(() => {
return (
selectableGameVersionNumbers.value?.includes(gameVersion.value) &&
((loaderVersionIndex.value !== undefined && loaderVersionIndex.value >= 0) ||
loader.value === 'vanilla')
)
})
const isChanged = computed(() => {
return (
loader.value !== props.instance.loader ||
gameVersion.value !== props.instance.game_version ||
(loader.value !== 'vanilla' &&
loaderVersionIndex.value !== undefined &&
loaderVersionIndex.value >= 0 &&
selectableLoaderVersions.value?.[loaderVersionIndex.value].id !==
props.instance.loader_version)
)
})
watch(loader, () => {
loaderVersionIndex.value = 0
})
const editing = ref(false)
async function saveGvLoaderEdits() {
editing.value = true
const editProfile: { loader?: string; game_version?: string; loader_version?: string } = {}
editProfile.loader = loader.value
editProfile.game_version = gameVersion.value
if (loader.value !== 'vanilla' && loaderVersionIndex.value !== undefined) {
editProfile.loader_version = selectableLoaderVersions.value?.[loaderVersionIndex.value].id
} else {
loaderVersionIndex.value = -1
}
console.log('Editing:')
console.log(loader.value)
await edit(props.instance.path, editProfile).catch(handleError)
await repairProfile(false)
editing.value = false
}
const installing = computed(() => props.instance.install_stage !== 'installed')
const repairing = ref(false)
const reinstalling = ref(false)
const changingVersion = ref(false)
async function repairProfile(force: boolean) {
if (force) {
repairing.value = true
}
await install(props.instance.path, force).catch(handleError)
if (force) {
repairing.value = false
}
trackEvent('InstanceRepair', {
loader: props.instance.loader,
game_version: props.instance.game_version,
})
}
async function unpairProfile() {
await edit(props.instance.path, {
linked_data: null,
})
modpackProject.value = null
modpackVersion.value = null
modpackVersions.value = null
modalConfirmUnpair.value.hide()
}
async function repairModpack() {
reinstalling.value = true
await update_repair_modrinth(props.instance.path).catch(handleError)
reinstalling.value = false
trackEvent('InstanceRepair', {
loader: props.instance.loader,
game_version: props.instance.game_version,
})
}
const messages = defineMessages({
cannotWhileInstalling: {
id: 'instance.settings.tabs.installation.tooltip.cannot-while-installing',
defaultMessage: 'Cannot {action} while installing',
},
cannotWhileOffline: {
id: 'instance.settings.tabs.installation.tooltip.cannot-while-offline',
defaultMessage: 'Cannot {action} while offline',
},
cannotWhileRepairing: {
id: 'instance.settings.tabs.installation.tooltip.cannot-while-repairing',
defaultMessage: 'Cannot {action} while repairing',
},
currentlyInstalled: {
id: 'instance.settings.tabs.installation.currently-installed',
defaultMessage: 'Currently installed',
},
platform: {
id: 'instance.settings.tabs.installation.platform',
defaultMessage: 'Platform',
},
gameVersion: {
id: 'instance.settings.tabs.installation.game-version',
defaultMessage: 'Game version',
},
loaderVersion: {
id: 'instance.settings.tabs.installation.loader-version',
defaultMessage: '{loader} version',
},
showAllVersions: {
id: 'instance.settings.tabs.installation.show-all-versions',
defaultMessage: 'Show all versions',
},
install: {
id: 'instance.settings.tabs.installation.install',
defaultMessage: 'Install',
},
resetSelections: {
id: 'instance.settings.tabs.installation.reset-selections',
defaultMessage: 'Reset to current',
},
unknownVersion: {
id: 'instance.settings.tabs.installation.unknown-version',
defaultMessage: '(unknown version)',
},
repairConfirmTitle: {
id: 'instance.settings.tabs.installation.repair.confirm.title',
defaultMessage: 'Repair instance?',
},
repairConfirmDescription: {
id: 'instance.settings.tabs.installation.repair.confirm.description',
defaultMessage:
'Repairing reinstalls Minecraft dependencies and checks for corruption. This may resolve issues if your game is not launching due to launcher-related errors, but will not resolve issues or crashes related to installed mods.',
},
repairButton: {
id: 'instance.settings.tabs.installation.repair.button',
defaultMessage: 'Repair',
},
repairingButton: {
id: 'instance.settings.tabs.installation.repair.button.repairing',
defaultMessage: 'Repairing',
},
repairInProgress: {
id: 'instance.settings.tabs.installation.repair.in-progress',
defaultMessage: 'Repair in progress',
},
repairAction: {
id: 'instance.settings.tabs.installation.tooltip.action.repair',
defaultMessage: 'repair',
},
changeVersionCannotWhileFetching: {
id: 'instance.settings.tabs.installation.change-version.cannot-while-fetching',
defaultMessage: 'Fetching modpack versions',
},
changeVersionButton: {
id: 'instance.settings.tabs.installation.change-version.button',
defaultMessage: 'Change version',
},
changeVersionAction: {
id: 'instance.settings.tabs.installation.tooltip.action.change-version',
defaultMessage: 'change version',
},
installingButton: {
id: 'instance.settings.tabs.installation.change-version.button.installing',
defaultMessage: 'Installing',
},
installInProgress: {
id: 'instance.settings.tabs.installation.install.in-progress',
defaultMessage: 'Installation in progress',
},
installButton: {
id: 'instance.settings.tabs.installation.change-version.button.install',
defaultMessage: 'Install',
},
alreadyInstalledVanilla: {
id: 'instance.settings.tabs.installation.change-version.already-installed.vanilla',
defaultMessage: 'Vanilla {game_version} already installed',
},
alreadyInstalledModded: {
id: 'instance.settings.tabs.installation.change-version.already-installed.modded',
defaultMessage: '{platform} {version} for Minecraft {game_version} already installed',
},
installAction: {
id: 'instance.settings.tabs.installation.tooltip.action.install',
defaultMessage: 'install',
},
installingNewVersion: {
id: 'instance.settings.tabs.installation.change-version.in-progress',
defaultMessage: 'Installing new version',
},
minecraftVersion: {
id: 'instance.settings.tabs.installation.minecraft-version',
defaultMessage: 'Minecraft {version}',
},
noLoaderVersions: {
id: 'instance.settings.tabs.installation.no-loader-versions',
defaultMessage: '{loader} is not available for Minecraft {version}. Try another mod loader.',
},
noConnection: {
id: 'instance.settings.tabs.installation.no-connection',
defaultMessage: 'Cannot fetch linked modpack details. Please check your internet connection.',
},
noModpackFound: {
id: 'instance.settings.tabs.installation.no-modpack-found',
defaultMessage:
'This instance is linked to a modpack, but the modpack could not be found on Modrinth.',
},
debugInformation: {
id: 'instance.settings.tabs.installation.debug-information',
defaultMessage: 'Debug information:',
},
fetchingModpackDetails: {
id: 'instance.settings.tabs.installation.fetching-modpack-details',
defaultMessage: 'Fetching modpack details',
},
unlinkInstanceTitle: {
id: 'instance.settings.tabs.installation.unlink.title',
defaultMessage: 'Unlink from modpack',
},
unlinkInstanceDescription: {
id: 'instance.settings.tabs.installation.unlink.description',
defaultMessage: `This instance is linked to a modpack, which means mods can't be updated and you can't change the mod loader or Minecraft version. Unlinking will permanently disconnect this instance from the modpack.`,
},
unlinkInstanceButton: {
id: 'instance.settings.tabs.installation.unlink.button',
defaultMessage: 'Unlink instance',
},
unlinkInstanceConfirmTitle: {
id: 'instance.settings.tabs.installation.unlink.confirm.title',
defaultMessage: 'Are you sure you want to unlink this instance?',
},
unlinkInstanceConfirmDescription: {
id: 'instance.settings.tabs.installation.unlink.confirm.description',
defaultMessage:
'If you proceed, you will not be able to re-link it without creating an entirely new instance. You will no longer receive modpack updates and it will become a normal.',
},
reinstallModpackConfirmTitle: {
id: 'instance.settings.tabs.installation.reinstall.confirm.title',
defaultMessage: 'Are you sure you want to reinstall this instance?',
},
reinstallModpackConfirmDescription: {
id: 'instance.settings.tabs.installation.reinstall.confirm.description',
defaultMessage: `Reinstalling will reset all installed or modified content to what is provided by the modpack, removing any mods or content you have added on top of the original installation. This may fix unexpected behavior if changes have been made to the instance, but if your worlds now depend on additional installed content, it may break existing worlds.`,
},
reinstallModpackTitle: {
id: 'instance.settings.tabs.installation.reinstall.title',
defaultMessage: 'Reinstall modpack',
},
reinstallModpackDescription: {
id: 'instance.settings.tabs.installation.reinstall.description',
defaultMessage: `Resets the instance's content to its original state, removing any mods or content you have added on top of the original modpack.`,
},
reinstallModpackButton: {
id: 'instance.settings.tabs.installation.reinstall.button',
defaultMessage: 'Reinstall modpack',
},
reinstallingModpackButton: {
id: 'instance.settings.tabs.installation.reinstall.button.reinstalling',
defaultMessage: 'Reinstalling modpack',
},
reinstallAction: {
id: 'instance.settings.tabs.installation.tooltip.action.reinstall',
defaultMessage: 'reinstall',
},
})
</script>
<template>
<ConfirmModalWrapper
ref="repairConfirmModal"
:title="formatMessage(messages.repairConfirmTitle)"
:description="formatMessage(messages.repairConfirmDescription)"
:proceed-icon="HammerIcon"
:proceed-label="formatMessage(messages.repairButton)"
:danger="false"
:show-ad-on-close="false"
@proceed="() => repairProfile(true)"
/>
<ModpackVersionModal
v-if="instance.linked_data && modpackVersions"
ref="modpackVersionModal"
:instance="instance"
:versions="modpackVersions"
@finish-install="
() => {
changingVersion = false
modpackVersion =
modpackVersions?.find(
(version: Version) => version.id === props.instance.linked_data?.version_id,
) ?? null
}
"
/>
<ConfirmModalWrapper
ref="modalConfirmUnpair"
:title="formatMessage(messages.unlinkInstanceConfirmTitle)"
:description="formatMessage(messages.unlinkInstanceConfirmDescription)"
:proceed-icon="UnlinkIcon"
:proceed-label="formatMessage(messages.unlinkInstanceButton)"
:show-ad-on-close="false"
@proceed="() => unpairProfile()"
/>
<ConfirmModalWrapper
ref="modalConfirmReinstall"
:title="formatMessage(messages.reinstallModpackConfirmTitle)"
:description="formatMessage(messages.reinstallModpackConfirmDescription)"
:proceed-icon="DownloadIcon"
:proceed-label="formatMessage(messages.reinstallModpackButton)"
:show-ad-on-close="false"
@proceed="() => repairModpack()"
/>
<div>
<h2 id="project-name" class="m-0 mb-1 text-lg font-extrabold text-contrast block">
{{ formatMessage(messages.currentlyInstalled) }}
</h2>
<div
v-if="!modpackProject && instance.linked_data && offline && !fetching"
class="text-secondary font-medium mb-2"
>
<UnplugIcon class="top-[3px] relative" /> {{ formatMessage(messages.noConnection) }}
</div>
<div v-else-if="!modpackProject && instance.linked_data && !fetching" class="mb-2">
<p class="text-brand-red font-medium mt-0">
<IssuesIcon class="top-[3px] relative" /> {{ formatMessage(messages.noModpackFound) }}
</p>
<p>{{ formatMessage(messages.debugInformation) }}</p>
<div class="bg-bg p-6 rounded-2xl mt-2 text-sm text-secondary">
{{ instance.linked_data }}
</div>
</div>
<div class="flex gap-4 items-center justify-between p-4 bg-bg rounded-2xl">
<div v-if="fetching" class="flex items-center gap-2 h-10">
<SpinnerIcon class="animate-spin" />
{{ formatMessage(messages.fetchingModpackDetails) }}
</div>
<template v-else>
<div class="flex gap-2 items-center">
<Avatar v-if="modpackProject" :src="modpackProject?.icon_url" size="40px" />
<div
v-else
class="w-10 h-10 flex items-center justify-center rounded-full bg-button-bg border-solid border-[1px] border-button-border p-2 [&_svg]:h-full [&_svg]:w-full"
>
<div v-if="!!currentLoaderIcon" class="contents" v-html="currentLoaderIcon" />
<WrenchIcon v-else />
</div>
<div class="flex flex-col gap-2 justify-center">
<span class="font-semibold leading-none">
{{
modpackProject
? modpackProject.title
: formatMessage(messages.minecraftVersion, { version: instance.game_version })
}}
</span>
<span class="text-sm text-secondary leading-none">
{{
modpackProject
? modpackVersion
? modpackVersion?.version_number
: 'Unknown version'
: formatCategory(instance.loader)
}}
<template v-if="instance.loader !== 'vanilla' && !modpackProject">
{{ instance.loader_version || formatMessage(messages.unknownVersion) }}
</template>
</span>
</div>
</div>
<div class="flex gap-1">
<ButtonStyled color="orange" type="transparent" hover-color-fill="background">
<button
v-tooltip="
repairing
? formatMessage(messages.repairInProgress)
: installing || reinstalling
? formatMessage(messages.cannotWhileInstalling, {
action: formatMessage(messages.repairAction),
})
: offline
? formatMessage(messages.cannotWhileOffline, {
action: formatMessage(messages.repairAction),
})
: null
"
:disabled="installing || repairing || reinstalling || offline"
@click="repairConfirmModal.show()"
>
<SpinnerIcon v-if="repairing" class="animate-spin" />
<HammerIcon v-else />
{{
repairing
? formatMessage(messages.repairingButton)
: formatMessage(messages.repairButton)
}}
</button>
</ButtonStyled>
<ButtonStyled v-if="modpackProject" hover-color-fill="background">
<button
v-tooltip="
changingVersion
? formatMessage(messages.installingNewVersion)
: repairing
? formatMessage(messages.cannotWhileRepairing, {
action: formatMessage(messages.changeVersionAction),
})
: installing || reinstalling
? formatMessage(messages.cannotWhileInstalling, {
action: formatMessage(messages.changeVersionAction),
})
: fetching && !modpackVersions
? formatMessage(messages.changeVersionCannotWhileFetching)
: offline
? formatMessage(messages.cannotWhileOffline, {
action: formatMessage(messages.changeVersionAction),
})
: null
"
:disabled="
changingVersion ||
repairing ||
installing ||
reinstalling ||
offline ||
fetching ||
!modpackVersions
"
@click="
() => {
changingVersion = true
modpackVersionModal.show()
}
"
>
<SpinnerIcon v-if="changingVersion" class="animate-spin" />
<TransferIcon v-else />
{{
changingVersion
? formatMessage(messages.installingButton)
: formatMessage(messages.changeVersionButton)
}}
</button>
</ButtonStyled>
</div>
</template>
</div>
<template v-if="!instance.linked_data || !instance.linked_data.locked">
<h2 class="m-0 mt-4 text-lg font-extrabold text-contrast block">
{{ formatMessage(messages.platform) }}
</h2>
<Chips v-if="loaders" v-model="loader" :items="loaders.map((x) => x.name)" class="mt-2" />
<h2 class="m-0 mt-4 text-lg font-extrabold text-contrast block">
{{ formatMessage(messages.gameVersion) }}
</h2>
<div class="flex flex-wrap mt-2 gap-2">
<TeleportDropdownMenu
v-if="selectableGameVersionNumbers !== undefined"
v-model="gameVersion"
:options="selectableGameVersionNumbers"
name="Game Version Dropdown"
/>
<Checkbox
v-if="hasSnapshots"
v-model="showSnapshots"
:label="formatMessage(messages.showAllVersions)"
/>
</div>
<template v-if="loader !== 'vanilla'">
<h2 class="m-0 mt-4 text-lg font-extrabold text-contrast block">
{{ formatMessage(messages.loaderVersion, { loader: formatCategory(loader) }) }}
</h2>
<TeleportDropdownMenu
v-if="selectableLoaderVersions"
:model-value="selectableLoaderVersions[loaderVersionIndex]"
:options="selectableLoaderVersions"
:display-name="(option: ManifestLoaderVersion) => option?.id"
name="Version selector"
class="mt-2"
@change="(value) => (loaderVersionIndex = value.index)"
/>
<div v-else class="mt-2 text-brand-red flex gap-2 items-center">
<IssuesIcon />
{{ formatMessage(messages.noLoaderVersions, { loader: loader, version: gameVersion }) }}
</div>
</template>
<div class="mt-4 flex flex-wrap gap-2">
<ButtonStyled color="brand">
<button
v-tooltip="
installing || reinstalling
? formatMessage(messages.installInProgress)
: !isChanged
? formatMessage(
loader === 'vanilla'
? messages.alreadyInstalledVanilla
: messages.alreadyInstalledModded,
{
platform: formatCategory(loader),
version: instance.loader_version,
game_version: gameVersion,
},
)
: repairing
? formatMessage(messages.cannotWhileRepairing, {
action: formatMessage(messages.installAction),
})
: offline
? formatMessage(messages.cannotWhileOffline, {
action: formatMessage(messages.installAction),
})
: null
"
:disabled="!isValid || !isChanged || editing || offline || repairing"
@click="saveGvLoaderEdits()"
>
<SpinnerIcon v-if="editing" class="animate-spin" />
<DownloadIcon v-else />
{{
editing
? formatMessage(messages.installingButton)
: formatMessage(messages.installButton)
}}
</button>
</ButtonStyled>
<ButtonStyled>
<button
:disabled="!isChanged"
@click="
() => {
loader = instance.loader
gameVersion = instance.game_version
resetLoaderVersionIndex()
}
"
>
<UndoIcon />
{{ formatMessage(messages.resetSelections) }}
</button>
</ButtonStyled>
</div>
</template>
<template v-else>
<template v-if="instance.linked_data && instance.linked_data.locked">
<h2 class="mt-4 mb-1 text-lg font-extrabold text-contrast block">
{{ formatMessage(messages.unlinkInstanceTitle) }}
</h2>
<p class="m-0">
{{ formatMessage(messages.unlinkInstanceDescription) }}
</p>
<ButtonStyled>
<button class="mt-2" @click="modalConfirmUnpair.show()">
<UnlinkIcon /> {{ formatMessage(messages.unlinkInstanceButton) }}
</button>
</ButtonStyled>
<template v-if="modpackProject">
<div>
<h2 class="m-0 mb-1 text-lg font-extrabold text-contrast block mt-4">
{{ formatMessage(messages.reinstallModpackTitle) }}
</h2>
<p class="m-0">
{{ formatMessage(messages.reinstallModpackDescription) }}
</p>
</div>
<ButtonStyled color="red" type="outlined">
<button
v-tooltip="
reinstalling
? formatMessage(messages.reinstallingModpackButton)
: repairing
? formatMessage(messages.cannotWhileRepairing, {
action: formatMessage(messages.reinstallAction),
})
: installing
? formatMessage(messages.cannotWhileInstalling, {
action: formatMessage(messages.reinstallAction),
})
: offline
? formatMessage(messages.cannotWhileOffline, {
action: formatMessage(messages.reinstallAction),
})
: null
"
class="mt-2"
:disabled="
changingVersion ||
repairing ||
installing ||
offline ||
fetching ||
!modpackVersions
"
@click="modalConfirmReinstall.show()"
>
<SpinnerIcon v-if="reinstalling" class="animate-spin" />
<DownloadIcon v-else />
{{
reinstalling
? formatMessage(messages.reinstallingModpackButton)
: formatMessage(messages.reinstallModpackButton)
}}
</button>
</ButtonStyled>
</template>
</template>
</template>
</div>
</template>
@@ -0,0 +1,188 @@
<script setup lang="ts">
import { Checkbox, Slider } from '@modrinth/ui'
import { CheckCircleIcon, XCircleIcon } from '@modrinth/assets'
import { computed, readonly, ref, watch } from 'vue'
import { edit, get_optimal_jre_key } from '@/helpers/profile'
import { handleError } from '@/store/notifications'
import { defineMessages, useVIntl } from '@vintl/vintl'
import JavaSelector from '@/components/ui/JavaSelector.vue'
import { get_max_memory } from '@/helpers/jre'
import { get } from '@/helpers/settings'
import type { InstanceSettingsTabProps, AppSettings, MemorySettings } from '../../../helpers/types'
const { formatMessage } = useVIntl()
const props = defineProps<InstanceSettingsTabProps>()
const globalSettings = (await get().catch(handleError)) as AppSettings
const overrideJavaInstall = ref(!!props.instance.java_path)
const optimalJava = readonly(await get_optimal_jre_key(props.instance.path).catch(handleError))
const javaInstall = ref({ path: optimalJava.path ?? props.instance.java_path })
const overrideJavaArgs = ref(props.instance.extra_launch_args?.length !== undefined)
const javaArgs = ref(
(props.instance.extra_launch_args ?? globalSettings.extra_launch_args).join(' '),
)
const overrideEnvVars = ref(props.instance.custom_env_vars?.length !== undefined)
const envVars = ref(
(props.instance.custom_env_vars ?? globalSettings.custom_env_vars)
.map((x) => x.join('='))
.join(' '),
)
const overrideMemorySettings = ref(!!props.instance.memory)
const memory = ref(props.instance.memory ?? globalSettings.memory)
const maxMemory = Math.floor((await get_max_memory().catch(handleError)) / 1024)
const editProfileObject = computed(() => {
const editProfile: {
java_path?: string
extra_launch_args?: string[]
custom_env_vars?: string[][]
memory?: MemorySettings
} = {}
if (overrideJavaInstall.value) {
if (javaInstall.value.path !== '') {
editProfile.java_path = javaInstall.value.path.replace('java.exe', 'javaw.exe')
}
}
if (overrideJavaArgs.value) {
editProfile.extra_launch_args = javaArgs.value.trim().split(/\s+/).filter(Boolean)
}
if (overrideEnvVars.value) {
editProfile.custom_env_vars = envVars.value
.trim()
.split(/\s+/)
.filter(Boolean)
.map((x) => x.split('=').filter(Boolean))
}
if (overrideMemorySettings.value) {
editProfile.memory = memory.value
}
return editProfile
})
watch(
[
overrideJavaInstall,
javaInstall,
overrideJavaArgs,
javaArgs,
overrideEnvVars,
envVars,
overrideMemorySettings,
memory,
],
async () => {
await edit(props.instance.path, editProfileObject.value)
},
{ deep: true },
)
const messages = defineMessages({
javaInstallation: {
id: 'instance.settings.tabs.java.java-installation',
defaultMessage: 'Java installation',
},
javaArguments: {
id: 'instance.settings.tabs.java.java-arguments',
defaultMessage: 'Java arguments',
},
javaEnvironmentVariables: {
id: 'instance.settings.tabs.java.environment-variables',
defaultMessage: 'Environment variables',
},
javaMemory: {
id: 'instance.settings.tabs.java.java-memory',
defaultMessage: 'Memory allocated',
},
hooks: {
id: 'instance.settings.tabs.java.hooks',
defaultMessage: 'Hooks',
},
})
</script>
<template>
<div>
<h2 id="project-name" class="m-0 mb-1 text-lg font-extrabold text-contrast block">
{{ formatMessage(messages.javaInstallation) }}
</h2>
<Checkbox v-model="overrideJavaInstall" label="Custom Java installation" class="mb-2" />
<template v-if="!overrideJavaInstall">
<div class="flex my-2 items-center gap-2 font-semibold">
<template v-if="javaInstall">
<CheckCircleIcon class="text-brand-green h-4 w-4" />
<span>Using default Java {{ optimalJava.major_version }} installation:</span>
</template>
<template v-else-if="optimalJava">
<XCircleIcon class="text-brand-red h-5 w-5" />
<span
>Could not find a default Java {{ optimalJava.major_version }} installation. Please set
one below:</span
>
</template>
<template v-else>
<XCircleIcon class="text-brand-red h-5 w-5" />
<span
>Could not automatically determine a Java installation to use. Please set one
below:</span
>
</template>
</div>
<div
v-if="javaInstall && !overrideJavaInstall"
class="p-4 bg-bg rounded-xl text-xs text-secondary leading-none font-mono"
>
{{ javaInstall.path }}
</div>
</template>
<JavaSelector v-if="overrideJavaInstall || !javaInstall" v-model="javaInstall" />
<h2 id="project-name" class="mt-4 mb-1 text-lg font-extrabold text-contrast block">
{{ formatMessage(messages.javaMemory) }}
</h2>
<Checkbox v-model="overrideMemorySettings" label="Custom memory allocation" class="mb-2" />
<Slider
id="max-memory"
v-model="memory.maximum"
:disabled="!overrideMemorySettings"
:min="512"
:max="maxMemory"
:step="64"
unit="MB"
/>
<h2 id="project-name" class="mt-4 mb-1 text-lg font-extrabold text-contrast block">
{{ formatMessage(messages.javaArguments) }}
</h2>
<Checkbox v-model="overrideJavaArgs" label="Custom java arguments" class="my-2" />
<input
id="java-args"
v-model="javaArgs"
autocomplete="off"
:disabled="!overrideJavaArgs"
type="text"
class="w-full"
placeholder="Enter java arguments..."
/>
<h2 id="project-name" class="mt-4 mb-1 text-lg font-extrabold text-contrast block">
{{ formatMessage(messages.javaEnvironmentVariables) }}
</h2>
<Checkbox v-model="overrideEnvVars" label="Custom environment variables" class="mb-2" />
<input
id="env-vars"
v-model="envVars"
autocomplete="off"
:disabled="!overrideEnvVars"
type="text"
class="w-full"
placeholder="Enter environmental variables..."
/>
</div>
</template>
@@ -0,0 +1,165 @@
<script setup lang="ts">
import { Checkbox, Toggle } from '@modrinth/ui'
import { computed, ref, type Ref, watch } from 'vue'
import { handleError } from '@/store/notifications'
import { defineMessages, useVIntl } from '@vintl/vintl'
import { get } from '@/helpers/settings'
import { edit } from '@/helpers/profile'
import type { InstanceSettingsTabProps, AppSettings } from '../../../helpers/types'
const { formatMessage } = useVIntl()
const props = defineProps<InstanceSettingsTabProps>()
const globalSettings = (await get().catch(handleError)) as AppSettings
const overrideWindowSettings = ref(
!!props.instance.game_resolution || !!props.instance.force_fullscreen,
)
const resolution: Ref<[number, number]> = ref(
props.instance.game_resolution ?? (globalSettings.game_resolution.slice() as [number, number]),
)
const fullscreenSetting: Ref<boolean> = ref(
props.instance.force_fullscreen ?? globalSettings.force_fullscreen,
)
const editProfileObject = computed(() => {
const editProfile: {
force_fullscreen?: boolean
game_resolution?: [number, number]
} = {}
if (overrideWindowSettings.value) {
editProfile.force_fullscreen = fullscreenSetting.value
if (!fullscreenSetting.value) {
editProfile.game_resolution = resolution.value
}
}
return editProfile
})
watch(
[overrideWindowSettings, resolution, fullscreenSetting],
async () => {
await edit(props.instance.path, editProfileObject.value)
},
{ deep: true },
)
const messages = defineMessages({
customWindowSettings: {
id: 'instance.settings.tabs.window.custom-window-settings',
defaultMessage: 'Custom window settings',
},
fullscreen: {
id: 'instance.settings.tabs.window.fullscreen',
defaultMessage: 'Fullscreen',
},
fullscreenDescription: {
id: 'instance.settings.tabs.window.fullscreen.description',
defaultMessage: 'Make the game start in full screen when launched (using options.txt).',
},
width: {
id: 'instance.settings.tabs.window.width',
defaultMessage: 'Width',
},
widthDescription: {
id: 'instance.settings.tabs.window.width.description',
defaultMessage: 'The width of the game window when launched.',
},
enterWidth: {
id: 'instance.settings.tabs.window.width.enter',
defaultMessage: 'Enter width...',
},
height: {
id: 'instance.settings.tabs.window.height',
defaultMessage: 'Height',
},
heightDescription: {
id: 'instance.settings.tabs.window.height.description',
defaultMessage: 'The height of the game window when launched.',
},
enterHeight: {
id: 'instance.settings.tabs.window.height.enter',
defaultMessage: 'Enter height...',
},
})
</script>
<template>
<div>
<Checkbox
v-model="overrideWindowSettings"
:label="formatMessage(messages.customWindowSettings)"
@update:model-value="
(value) => {
if (!value) {
resolution = globalSettings.game_resolution
fullscreenSetting = globalSettings.force_fullscreen
}
}
"
/>
<div class="mt-2 flex items-center gap-4 justify-between">
<div>
<h2 class="m-0 mb-1 text-lg font-extrabold text-contrast">
{{ formatMessage(messages.fullscreen) }}
</h2>
<p class="m-0">
{{ formatMessage(messages.fullscreenDescription) }}
</p>
</div>
<Toggle
id="fullscreen"
:model-value="overrideWindowSettings ? fullscreenSetting : globalSettings.force_fullscreen"
:checked="fullscreenSetting"
:disabled="!overrideWindowSettings"
@update:model-value="
(e) => {
fullscreenSetting = e
}
"
/>
</div>
<div class="mt-4 flex items-center gap-4 justify-between">
<div>
<h2 class="m-0 mb-1 text-lg font-extrabold text-contrast">
{{ formatMessage(messages.width) }}
</h2>
<p class="m-0">
{{ formatMessage(messages.widthDescription) }}
</p>
</div>
<input
id="width"
v-model="resolution[0]"
autocomplete="off"
:disabled="!overrideWindowSettings || fullscreenSetting"
type="number"
:placeholder="formatMessage(messages.enterWidth)"
/>
</div>
<div class="mt-4 flex items-center gap-4 justify-between">
<div>
<h2 class="m-0 mb-1 text-lg font-extrabold text-contrast">
{{ formatMessage(messages.height) }}
</h2>
<p class="m-0">
{{ formatMessage(messages.heightDescription) }}
</p>
</div>
<input
id="height"
v-model="resolution[1]"
autocomplete="off"
:disabled="!overrideWindowSettings || fullscreenSetting"
type="number"
:placeholder="formatMessage(messages.enterHeight)"
/>
</div>
</div>
</template>
@@ -0,0 +1,161 @@
<script setup lang="ts">
import {
ReportIcon,
ModrinthIcon,
ShieldIcon,
SettingsIcon,
GaugeIcon,
PaintBrushIcon,
GameIcon,
CoffeeIcon,
} from '@modrinth/assets'
import { TabbedModal } from '@modrinth/ui'
import { computed, ref, watch } from 'vue'
import { useVIntl, defineMessage } from '@vintl/vintl'
import AppearanceSettings from '@/components/ui/settings/AppearanceSettings.vue'
import JavaSettings from '@/components/ui/settings/JavaSettings.vue'
import ResourceManagementSettings from '@/components/ui/settings/ResourceManagementSettings.vue'
import PrivacySettings from '@/components/ui/settings/PrivacySettings.vue'
import DefaultInstanceSettings from '@/components/ui/settings/DefaultInstanceSettings.vue'
import { getVersion } from '@tauri-apps/api/app'
import { version as getOsVersion, platform as getOsPlatform } from '@tauri-apps/plugin-os'
import { useTheming } from '@/store/state'
import FeatureFlagSettings from '@/components/ui/settings/FeatureFlagSettings.vue'
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
import { get, set } from '@/helpers/settings'
const themeStore = useTheming()
const { formatMessage } = useVIntl()
const devModeCounter = ref(0)
const developerModeEnabled = defineMessage({
id: 'app.settings.developer-mode-enabled',
defaultMessage: 'Developer mode enabled.',
})
const tabs = [
{
name: defineMessage({
id: 'app.settings.tabs.appearance',
defaultMessage: 'Appearance',
}),
icon: PaintBrushIcon,
content: AppearanceSettings,
},
{
name: defineMessage({
id: 'app.settings.tabs.privacy',
defaultMessage: 'Privacy',
}),
icon: ShieldIcon,
content: PrivacySettings,
},
{
name: defineMessage({
id: 'app.settings.tabs.java-installations',
defaultMessage: 'Java installations',
}),
icon: CoffeeIcon,
content: JavaSettings,
},
{
name: defineMessage({
id: 'app.settings.tabs.default-instance-options',
defaultMessage: 'Default instance options',
}),
icon: GameIcon,
content: DefaultInstanceSettings,
},
{
name: defineMessage({
id: 'app.settings.tabs.resource-management',
defaultMessage: 'Resource management',
}),
icon: GaugeIcon,
content: ResourceManagementSettings,
},
{
name: defineMessage({
id: 'app.settings.tabs.feature-flags',
defaultMessage: 'Feature flags',
}),
icon: ReportIcon,
content: FeatureFlagSettings,
developerOnly: true,
},
]
const modal = ref()
function show() {
modal.value.show()
}
const isOpen = computed(() => modal.value?.isOpen)
defineExpose({ show, isOpen })
const version = await getVersion()
const osPlatform = getOsPlatform()
const osVersion = getOsVersion()
const settings = ref(await get())
watch(
settings,
async () => {
await set(settings.value)
},
{ deep: true },
)
function devModeCount() {
devModeCounter.value++
if (devModeCounter.value > 5) {
themeStore.devMode = !themeStore.devMode
settings.value.developer_mode = !!themeStore.devMode
devModeCounter.value = 0
if (!themeStore.devMode && tabs[modal.value.selectedTab].developerOnly) {
modal.value.setTab(0)
}
}
}
</script>
<template>
<ModalWrapper ref="modal">
<template #title>
<span class="flex items-center gap-2 text-lg font-extrabold text-contrast">
<SettingsIcon /> Settings
</span>
</template>
<TabbedModal :tabs="tabs.filter((t) => !t.developerOnly || themeStore.devMode)">
<template #footer>
<div class="mt-auto text-secondary text-sm">
<p v-if="themeStore.devMode" class="text-brand font-semibold m-0 mb-2">
{{ formatMessage(developerModeEnabled) }}
</p>
<div class="flex items-center gap-3">
<button
class="p-0 m-0 bg-transparent border-none cursor-pointer button-animation"
:class="{ 'text-brand': themeStore.devMode, 'text-secondary': !themeStore.devMode }"
@click="devModeCount"
>
<ModrinthIcon class="w-6 h-6" />
</button>
<div>
<p class="m-0">Modrinth App {{ version }}</p>
<p class="m-0">
<span v-if="osPlatform === 'macos'">MacOS</span>
<span v-else class="capitalize">{{ osPlatform }}</span>
{{ osVersion }}
</p>
</div>
</div>
</div>
</template>
</TabbedModal>
</ModalWrapper>
</template>
@@ -0,0 +1,85 @@
<script setup lang="ts">
import { ref } from 'vue'
import { ConfirmModal } from '@modrinth/ui'
import { show_ads_window, hide_ads_window } from '@/helpers/ads.js'
import { useTheming } from '@/store/theme.js'
const themeStore = useTheming()
const props = defineProps({
confirmationText: {
type: String,
default: '',
},
hasToType: {
type: Boolean,
default: false,
},
title: {
type: String,
default: 'No title defined',
required: true,
},
description: {
type: String,
default: 'No description defined',
required: true,
},
proceedIcon: {
type: Object,
default: undefined,
},
proceedLabel: {
type: String,
default: 'Proceed',
},
danger: {
type: Boolean,
default: true,
},
showAdOnClose: {
type: Boolean,
default: true,
},
})
const emit = defineEmits(['proceed'])
const modal = ref(null)
defineExpose({
show: () => {
hide_ads_window()
modal.value.show()
},
hide: () => {
onModalHide()
modal.value.hide()
},
})
function onModalHide() {
if (props.showAdOnClose) {
show_ads_window()
}
}
function proceed() {
emit('proceed')
}
</script>
<template>
<ConfirmModal
ref="modal"
:confirmation-text="confirmationText"
:has-to-type="hasToType"
:title="title"
:description="description"
:proceed-icon="proceedIcon"
:proceed-label="proceedLabel"
:on-hide="onModalHide"
:noblur="!themeStore.advancedRendering"
:danger="danger"
@proceed="proceed"
/>
</template>
@@ -0,0 +1,98 @@
<script setup lang="ts">
import {
ChevronRightIcon,
CoffeeIcon,
InfoIcon,
WrenchIcon,
MonitorIcon,
CodeIcon,
} from '@modrinth/assets'
import { Avatar, TabbedModal, type TabbedModalTab } from '@modrinth/ui'
import { ref } from 'vue'
import { defineMessage, useVIntl } from '@vintl/vintl'
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
import GeneralSettings from '@/components/ui/instance_settings/GeneralSettings.vue'
import { convertFileSrc } from '@tauri-apps/api/core'
import InstallationSettings from '@/components/ui/instance_settings/InstallationSettings.vue'
import JavaSettings from '@/components/ui/instance_settings/JavaSettings.vue'
import WindowSettings from '@/components/ui/instance_settings/WindowSettings.vue'
import HooksSettings from '@/components/ui/instance_settings/HooksSettings.vue'
import type { InstanceSettingsTabProps } from '../../../helpers/types'
const { formatMessage } = useVIntl()
const props = defineProps<InstanceSettingsTabProps>()
const tabs: TabbedModalTab<InstanceSettingsTabProps>[] = [
{
name: defineMessage({
id: 'instance.settings.tabs.general',
defaultMessage: 'General',
}),
icon: InfoIcon,
content: GeneralSettings,
},
{
name: defineMessage({
id: 'instance.settings.tabs.installation',
defaultMessage: 'Installation',
}),
icon: WrenchIcon,
content: InstallationSettings,
},
{
name: defineMessage({
id: 'instance.settings.tabs.window',
defaultMessage: 'Window',
}),
icon: MonitorIcon,
content: WindowSettings,
},
{
name: defineMessage({
id: 'instance.settings.tabs.java',
defaultMessage: 'Java and memory',
}),
icon: CoffeeIcon,
content: JavaSettings,
},
{
name: defineMessage({
id: 'instance.settings.tabs.hooks',
defaultMessage: 'Launch hooks',
}),
icon: CodeIcon,
content: HooksSettings,
},
]
const modal = ref()
function show() {
modal.value.show()
}
defineExpose({ show })
const titleMessage = defineMessage({
id: 'instance.settings.title',
defaultMessage: 'Settings',
})
</script>
<template>
<ModalWrapper ref="modal">
<template #title>
<span class="flex items-center gap-2 text-lg font-semibold text-primary">
<Avatar
:src="instance.icon_path ? convertFileSrc(instance.icon_path) : undefined"
size="24px"
:tint-by="props.instance.path"
/>
{{ instance.name }} <ChevronRightIcon />
<span class="font-extrabold text-contrast">{{ formatMessage(titleMessage) }}</span>
</span>
</template>
<TabbedModal :tabs="tabs.map((tab) => ({ ...tab, props }))" />
</ModalWrapper>
</template>
@@ -0,0 +1,57 @@
<script setup lang="ts">
import { ref } from 'vue'
import { NewModal as Modal } from '@modrinth/ui'
import { show_ads_window, hide_ads_window } from '@/helpers/ads.js'
import { useTheming } from '@/store/theme.js'
const themeStore = useTheming()
const props = defineProps({
header: {
type: String,
default: null,
},
closable: {
type: Boolean,
default: true,
},
onHide: {
type: Function,
default() {
return () => {}
},
},
showAdOnClose: {
type: Boolean,
default: true,
},
})
const modal = ref(null)
defineExpose({
show: () => {
hide_ads_window()
modal.value.show()
},
hide: () => {
onModalHide()
modal.value.hide()
},
})
function onModalHide() {
if (props.showAdOnClose) {
show_ads_window()
}
props.onHide()
}
</script>
<template>
<Modal ref="modal" :header="header" :noblur="!themeStore.advancedRendering" @hide="onModalHide">
<template #title>
<slot name="title" />
</template>
<slot />
</Modal>
</template>
@@ -0,0 +1,61 @@
<script setup lang="ts">
import { ref } from 'vue'
import { ShareModal } from '@modrinth/ui'
import { show_ads_window, hide_ads_window } from '@/helpers/ads.js'
import { useTheming } from '@/store/theme.js'
const themeStore = useTheming()
defineProps({
header: {
type: String,
default: 'Share',
},
shareTitle: {
type: String,
default: 'Modrinth',
},
shareText: {
type: String,
default: null,
},
link: {
type: Boolean,
default: false,
},
openInNewTab: {
type: Boolean,
default: true,
},
})
const modal = ref(null)
defineExpose({
show: (passedContent) => {
hide_ads_window()
modal.value.show(passedContent)
},
hide: () => {
onModalHide()
modal.value.hide()
},
})
function onModalHide() {
show_ads_window()
}
</script>
<template>
<ShareModal
ref="modal"
:header="header"
:share-title="shareTitle"
:share-text="shareText"
:link="link"
:open-in-new-tab="openInNewTab"
:on-hide="onModalHide"
:noblur="!themeStore.advancedRendering"
/>
</template>
@@ -0,0 +1,123 @@
<script setup lang="ts">
import { Toggle, ThemeSelector, TeleportDropdownMenu } from '@modrinth/ui'
import { useTheming } from '@/store/state'
import { get, set } from '@/helpers/settings'
import { watch, ref } from 'vue'
import { getOS } from '@/helpers/utils'
const themeStore = useTheming()
const os = ref(await getOS())
const settings = ref(await get())
watch(
settings,
async () => {
await set(settings.value)
},
{ deep: true },
)
</script>
<template>
<h2 class="m-0 text-lg font-extrabold text-contrast">Color theme</h2>
<p class="m-0 mt-1">Select your preferred color theme for Modrinth App.</p>
<ThemeSelector
:update-color-theme="
(theme) => {
themeStore.setThemeState(theme)
settings.theme = theme
}
"
:current-theme="settings.theme"
:theme-options="themeStore.themeOptions"
system-theme-color="system"
/>
<div class="mt-4 flex items-center justify-between">
<div>
<h2 class="m-0 text-lg font-extrabold text-contrast">Advanced rendering</h2>
<p class="m-0 mt-1">
Enables advanced rendering such as blur effects that may cause performance issues without
hardware-accelerated rendering.
</p>
</div>
<Toggle
id="advanced-rendering"
:model-value="themeStore.advancedRendering"
:checked="themeStore.advancedRendering"
@update:model-value="
(e) => {
themeStore.advancedRendering = e
settings.advanced_rendering = themeStore.advancedRendering
}
"
/>
</div>
<div v-if="os !== 'MacOS'" class="mt-4 flex items-center justify-between gap-4">
<div>
<h2 class="m-0 text-lg font-extrabold text-contrast">Native Decorations</h2>
<p class="m-0 mt-1">Use system window frame (app restart required).</p>
</div>
<Toggle
id="native-decorations"
:model-value="settings.native_decorations"
:checked="settings.native_decorations"
@update:model-value="
(e) => {
settings.native_decorations = e
}
"
/>
</div>
<div class="mt-4 flex items-center justify-between">
<div>
<h2 class="m-0 text-lg font-extrabold text-contrast">Minimize launcher</h2>
<p class="m-0 mt-1">Minimize the launcher when a Minecraft process starts.</p>
</div>
<Toggle
id="minimize-launcher"
:model-value="settings.hide_on_process_start"
:checked="settings.hide_on_process_start"
@update:model-value="
(e) => {
settings.hide_on_process_start = e
}
"
/>
</div>
<div class="mt-4 flex items-center justify-between">
<div>
<h2 class="m-0 text-lg font-extrabold text-contrast">Default landing page</h2>
<p class="m-0 mt-1">Change the page to which the launcher opens on.</p>
</div>
<TeleportDropdownMenu
id="opening-page"
v-model="settings.default_page"
name="Opening page dropdown"
:options="['Home', 'Library']"
/>
</div>
<div class="mt-4 flex items-center justify-between">
<div>
<h2 class="m-0 text-lg font-extrabold text-contrast">Toggle sidebar</h2>
<p class="m-0 mt-1">Enables the ability to toggle the sidebar.</p>
</div>
<Toggle
id="toggle-sidebar"
:model-value="settings.toggle_sidebar"
:checked="settings.toggle_sidebar"
@update:model-value="
(e) => {
settings.toggle_sidebar = e
themeStore.toggleSidebar = settings.toggle_sidebar
}
"
/>
</div>
</template>
@@ -0,0 +1,181 @@
<script setup lang="ts">
import { get, set } from '@/helpers/settings'
import { ref, watch } from 'vue'
import { get_max_memory } from '@/helpers/jre'
import { handleError } from '@/store/notifications'
import { Slider, Toggle } from '@modrinth/ui'
const fetchSettings = await get()
fetchSettings.launchArgs = fetchSettings.extra_launch_args.join(' ')
fetchSettings.envVars = fetchSettings.custom_env_vars.map((x) => x.join('=')).join(' ')
const settings = ref(fetchSettings)
const maxMemory = ref(Math.floor((await get_max_memory().catch(handleError)) / 1024))
watch(
settings,
async () => {
const setSettings = JSON.parse(JSON.stringify(settings.value))
setSettings.extra_launch_args = setSettings.launchArgs.trim().split(/\s+/).filter(Boolean)
setSettings.custom_env_vars = setSettings.envVars
.trim()
.split(/\s+/)
.filter(Boolean)
.map((x) => x.split('=').filter(Boolean))
if (!setSettings.hooks.pre_launch) {
setSettings.hooks.pre_launch = null
}
if (!setSettings.hooks.wrapper) {
setSettings.hooks.wrapper = null
}
if (!setSettings.hooks.post_exit) {
setSettings.hooks.post_exit = null
}
if (!setSettings.custom_dir) {
setSettings.custom_dir = null
}
await set(setSettings)
},
{ deep: true },
)
</script>
<template>
<div>
<h2 class="m-0 text-lg font-extrabold text-contrast">Window size</h2>
<div class="flex items-center justify-between gap-4">
<div>
<h3 class="mt-2 m-0 text-base font-extrabold text-primary">Fullscreen</h3>
<p class="m-0 mt-1 mb-2 leading-tight text-secondary">
Overwrites the options.txt file to start in full screen when launched.
</p>
</div>
<Toggle
id="fullscreen"
:model-value="settings.force_fullscreen"
:checked="settings.force_fullscreen"
@update:model-value="
(e) => {
settings.force_fullscreen = e
}
"
/>
</div>
<div class="flex items-center justify-between gap-4">
<div>
<h3 class="mt-2 m-0 text-base font-extrabold text-primary">Width</h3>
<p class="m-0 mt-1 mb-2 leading-tight text-secondary">
The width of the game window when launched.
</p>
</div>
<input
id="width"
v-model="settings.game_resolution[0]"
:disabled="settings.force_fullscreen"
autocomplete="off"
type="number"
placeholder="Enter width..."
/>
</div>
<div class="flex items-center justify-between gap-4">
<div>
<h3 class="mt-2 m-0 text-base font-extrabold text-primary">Height</h3>
<p class="m-0 mt-1 mb-2 leading-tight text-secondary">
The height of the game window when launched.
</p>
</div>
<input
id="height"
v-model="settings.game_resolution[1]"
:disabled="settings.force_fullscreen"
autocomplete="off"
type="number"
class="input"
placeholder="Enter height..."
/>
</div>
<hr class="mt-4 bg-button-border border-none h-[1px]" />
<h2 class="mt-4 m-0 text-lg font-extrabold text-contrast">Memory allocated</h2>
<p class="m-0 mt-1 leading-tight">The memory allocated to each instance when it is ran.</p>
<Slider
id="max-memory"
v-model="settings.memory.maximum"
:min="512"
:max="maxMemory"
:step="64"
unit="MB"
/>
<h2 class="mt-4 mb-2 text-lg font-extrabold text-contrast">Java arguments</h2>
<input
id="java-args"
v-model="settings.launchArgs"
autocomplete="off"
type="text"
placeholder="Enter java arguments..."
class="w-full"
/>
<h2 class="mt-4 mb-2 text-lg font-extrabold text-contrast">Environmental variables</h2>
<input
id="env-vars"
v-model="settings.envVars"
autocomplete="off"
type="text"
placeholder="Enter environmental variables..."
class="w-full"
/>
<hr class="mt-4 bg-button-border border-none h-[1px]" />
<h2 class="mt-4 m-0 text-lg font-extrabold text-contrast">Hooks</h2>
<h3 class="mt-2 m-0 text-base font-extrabold text-primary">Pre launch</h3>
<p class="m-0 mt-1 mb-2 leading-tight text-secondary">Ran before the instance is launched.</p>
<input
id="pre-launch"
v-model="settings.hooks.pre_launch"
autocomplete="off"
type="text"
placeholder="Enter pre-launch command..."
class="w-full"
/>
<h3 class="mt-2 m-0 text-base font-extrabold text-primary">Wrapper</h3>
<p class="m-0 mt-1 mb-2 leading-tight text-secondary">
Wrapper command for launching Minecraft.
</p>
<input
id="wrapper"
v-model="settings.hooks.wrapper"
autocomplete="off"
type="text"
placeholder="Enter wrapper command..."
class="w-full"
/>
<h3 class="mt-2 m-0 text-base font-extrabold text-primary">Post exit</h3>
<p class="m-0 mt-1 mb-2 leading-tight text-secondary">Ran after the game closes.</p>
<input
id="post-exit"
v-model="settings.hooks.post_exit"
autocomplete="off"
type="text"
placeholder="Enter post-exit command..."
class="w-full"
/>
</div>
</template>
@@ -0,0 +1,44 @@
<script setup lang="ts">
import { Toggle } from '@modrinth/ui'
import { useTheming } from '@/store/state'
import { ref, watch } from 'vue'
import { get, set } from '@/helpers/settings'
const themeStore = useTheming()
const settings = ref(await get())
const options = ref(['project_background', 'page_path'])
function getStoreValue(key: string) {
return themeStore.featureFlags[key] ?? false
}
function setStoreValue(key: string, value: boolean) {
themeStore.featureFlags[key] = value
settings.value.feature_flags[key] = value
}
watch(
settings,
async () => {
await set(settings.value)
},
{ deep: true },
)
</script>
<template>
<div v-for="option in options" :key="option" class="mt-4 flex items-center justify-between">
<div>
<h2 class="m-0 text-lg font-extrabold text-contrast capitalize">
{{ option }}
</h2>
</div>
<Toggle
id="advanced-rendering"
:model-value="getStoreValue(option)"
:checked="getStoreValue(option)"
@update:model-value="() => setStoreValue(option, !themeStore.featureFlags[option])"
/>
</div>
</template>
@@ -0,0 +1,32 @@
<script setup>
import { ref } from 'vue'
import { get_java_versions, set_java_version } from '@/helpers/jre'
import { handleError } from '@/store/notifications'
import JavaSelector from '@/components/ui/JavaSelector.vue'
const javaVersions = ref(await get_java_versions().catch(handleError))
async function updateJavaVersion(version) {
if (version?.path === '') {
version.path = undefined
}
if (version?.path) {
version.path = version.path.replace('java.exe', 'javaw.exe')
}
await set_java_version(version).catch(handleError)
}
</script>
<template>
<div v-for="(javaVersion, index) in [21, 17, 8]" :key="`java-${javaVersion}`">
<h2 class="m-0 text-lg font-extrabold text-contrast" :class="{ 'mt-4': index !== 0 }">
Java {{ javaVersion }} location
</h2>
<JavaSelector
:id="'java-selector-' + javaVersion"
v-model="javaVersions[javaVersion]"
:version="javaVersion"
@update:model-value="updateJavaVersion"
/>
</div>
</template>
@@ -0,0 +1,84 @@
<script setup lang="ts">
import { ref, watch } from 'vue'
import { get, set } from '@/helpers/settings'
import { Toggle } from '@modrinth/ui'
import { optInAnalytics, optOutAnalytics } from '@/helpers/analytics'
const settings = ref(await get())
watch(
settings,
async () => {
if (settings.value.telemetry) {
optInAnalytics()
} else {
optOutAnalytics()
}
await set(settings.value)
},
{ deep: true },
)
</script>
<template>
<div class="flex items-center justify-between gap-4">
<div>
<h2 class="m-0 text-lg font-extrabold text-contrast">Personalized ads</h2>
<p class="m-0 text-sm">
Modrinth's ad provider, Aditude, shows ads based on your preferences. By disabling this
option, you opt out and ads will no longer be shown based on your interests.
</p>
</div>
<Toggle
id="personalized-ads"
:model-value="settings.personalized_ads"
:checked="settings.personalized_ads"
@update:model-value="
(e) => {
settings.personalized_ads = e
}
"
/>
</div>
<div class="mt-4 flex items-center justify-between gap-4">
<div>
<h2 class="m-0 text-lg font-extrabold text-contrast">Telemetry</h2>
<p class="m-0 text-sm">
Modrinth collects anonymized analytics and usage data to improve our user experience and
customize your experience. By disabling this option, you opt out and your data will no
longer be collected.
</p>
</div>
<Toggle
id="opt-out-analytics"
:model-value="settings.telemetry"
:checked="settings.telemetry"
@update:model-value="
(e) => {
settings.telemetry = e
}
"
/>
</div>
<div class="mt-4 flex items-center justify-between gap-4">
<div>
<h2 class="m-0 text-lg font-extrabold text-contrast">Discord RPC</h2>
<p class="m-0 text-sm">
Manages the Discord Rich Presence integration. Disabling this will cause 'Modrinth' to no
longer show up as a game or app you are using on your Discord profile.
</p>
<p class="m-0 mt-2 text-sm">
Note: This will not prevent any instance-specific Discord Rich Presence integrations, such
as those added by mods. (app restart required to take effect)
</p>
</div>
<Toggle
id="disable-discord-rpc"
v-model="settings.discord_rpc"
:checked="settings.discord_rpc"
/>
</div>
</template>
@@ -0,0 +1,117 @@
<script setup>
import { Button, Slider } from '@modrinth/ui'
import { ref, watch } from 'vue'
import { get, set } from '@/helpers/settings.js'
import { purge_cache_types } from '@/helpers/cache.js'
import { handleError } from '@/store/notifications.js'
import { BoxIcon, FolderSearchIcon, TrashIcon } from '@modrinth/assets'
import ConfirmModalWrapper from '@/components/ui/modal/ConfirmModalWrapper.vue'
import { open } from '@tauri-apps/plugin-dialog'
const settings = ref(await get())
watch(
settings,
async () => {
const setSettings = JSON.parse(JSON.stringify(settings.value))
if (!setSettings.custom_dir) {
setSettings.custom_dir = null
}
await set(setSettings)
},
{ deep: true },
)
async function purgeCache() {
await purge_cache_types([
'project',
'version',
'user',
'team',
'organization',
'loader_manifest',
'minecraft_manifest',
'categories',
'report_types',
'loaders',
'game_versions',
'donation_platforms',
'file_update',
'search_results',
]).catch(handleError)
}
async function findLauncherDir() {
const newDir = await open({
multiple: false,
directory: true,
title: 'Select a new app directory',
})
if (newDir) {
settings.value.custom_dir = newDir
}
}
</script>
<template>
<h2 class="m-0 text-lg font-extrabold text-contrast">App directory</h2>
<p class="m-0 mt-1 mb-2 leading-tight text-secondary">
The directory where the launcher stores all of its files. Changes will be applied after
restarting the launcher.
</p>
<div class="m-1 my-2">
<div class="iconified-input w-full">
<BoxIcon />
<input id="appDir" v-model="settings.custom_dir" type="text" class="input" />
<Button class="r-btn" @click="findLauncherDir">
<FolderSearchIcon />
</Button>
</div>
</div>
<div>
<ConfirmModalWrapper
ref="purgeCacheConfirmModal"
title="Are you sure you want to purge the cache?"
description="If you proceed, your entire cache will be purged. This may slow down the app temporarily."
:has-to-type="false"
proceed-label="Purge cache"
:show-ad-on-close="false"
@proceed="purgeCache"
/>
<h2 class="m-0 text-lg font-extrabold text-contrast">App cache</h2>
<p class="m-0 mt-1 mb-2 leading-tight text-secondary">
The Modrinth app stores a cache of data to speed up loading. This can be purged to force the
app to reload data. This may slow down the app temporarily.
</p>
</div>
<button id="purge-cache" class="btn min-w-max" @click="$refs.purgeCacheConfirmModal.show()">
<TrashIcon />
Purge cache
</button>
<h2 class="m-0 text-lg font-extrabold text-contrast mt-4">Maximum concurrent downloads</h2>
<p class="m-0 mt-1 mb-2 leading-tight text-secondary">
The maximum amount of files the launcher can download at the same time. Set this to a lower
value if you have a poor internet connection. (app restart required to take effect)
</p>
<Slider
id="max-downloads"
v-model="settings.max_concurrent_downloads"
:min="1"
:max="10"
:step="1"
/>
<h2 class="mt-4 m-0 text-lg font-extrabold text-contrast">Maximum concurrent writes</h2>
<p class="m-0 mt-1 mb-2 leading-tight text-secondary">
The maximum amount of files the launcher can write to the disk at once. Set this to a lower
value if you are frequently getting I/O errors. (app restart required to take effect)
</p>
<Slider id="max-writes" v-model="settings.max_concurrent_writes" :min="1" :max="50" :step="1" />
</template>
@@ -1,367 +0,0 @@
<script setup>
import { UserIcon, LockIcon, MailIcon } from '@modrinth/assets'
import { Button, Card, Checkbox, Modal } from '@modrinth/ui'
import {
DiscordIcon,
GithubIcon,
MicrosoftIcon,
GoogleIcon,
SteamIcon,
GitLabIcon,
} from '@/assets/external'
import { login, login_2fa, create_account, login_pass } from '@/helpers/mr_auth.js'
import { handleError, useNotifications } from '@/store/state.js'
import { ref } from 'vue'
import { handleSevereError } from '@/store/error.js'
const props = defineProps({
callback: {
type: Function,
required: true,
},
})
const modal = ref()
const turnstileToken = ref()
const widgetId = ref()
defineExpose({
show: () => {
modal.value.show()
if (window.turnstile === null || !window.turnstile) {
const script = document.createElement('script')
script.src =
'https://challenges.cloudflare.com/turnstile/v0/api.js?onload=onloadTurnstileCallback'
script.async = true
script.defer = true
document.head.appendChild(script)
window.onloadTurnstileCallback = loadWidget
} else {
loadWidget()
}
},
})
function loadWidget() {
widgetId.value = window.turnstile.render('#turnstile-container', {
sitekey: '0x4AAAAAAAW3guHM6Eunbgwu',
callback: (token) => (turnstileToken.value = token),
expiredCallback: () => (turnstileToken.value = null),
})
}
function removeWidget() {
if (widgetId.value) {
window.turnstile.remove(widgetId.value)
widgetId.value = null
turnstileToken.value = null
}
}
const loggingIn = ref(true)
const twoFactorFlow = ref(null)
const twoFactorCode = ref('')
const email = ref('')
const username = ref('')
const password = ref('')
const confirmPassword = ref('')
const subscribe = ref(true)
async function signInOauth(provider) {
const creds = await login(provider).catch(handleSevereError)
if (creds && creds.type === 'two_factor_required') {
twoFactorFlow.value = creds.flow
} else if (creds && creds.session) {
props.callback()
modal.value.hide()
}
}
async function signIn2fa() {
const creds = await login_2fa(twoFactorCode.value, twoFactorFlow.value).catch(handleError)
if (creds && creds.session) {
props.callback()
modal.value.hide()
}
}
async function signIn() {
const creds = await login_pass(username.value, password.value, turnstileToken.value).catch(
handleError,
)
window.turnstile.reset(widgetId.value)
if (creds && creds.type === 'two_factor_required') {
twoFactorFlow.value = creds.flow
} else if (creds && creds.session) {
props.callback()
modal.value.hide()
}
}
async function createAccount() {
if (password.value !== confirmPassword.value) {
const notifs = useNotifications()
notifs.addNotification({
title: 'An error occurred',
text: 'Passwords do not match!',
type: 'error',
})
return
}
const creds = await create_account(
username.value,
email.value,
password.value,
turnstileToken.value,
subscribe.value,
).catch(handleError)
window.turnstile.reset(widgetId.value)
if (creds && creds.session) {
props.callback()
modal.value.hide()
}
}
</script>
<template>
<Modal ref="modal" :on-hide="removeWidget">
<Card>
<template v-if="twoFactorFlow">
<h1>Enter two-factor code</h1>
<p>Please enter a two-factor code to proceed.</p>
<input v-model="twoFactorCode" maxlength="11" type="text" placeholder="Enter code..." />
</template>
<template v-else>
<h1 v-if="loggingIn">Login to Modrinth</h1>
<h1 v-else>Create an account</h1>
<div class="button-grid">
<Button class="discord" large @click="signInOauth('discord')">
<DiscordIcon />
Discord
</Button>
<Button class="github" large @click="signInOauth('github')">
<GithubIcon />
Github
</Button>
<Button class="white" large @click="signInOauth('microsoft')">
<MicrosoftIcon />
Microsoft
</Button>
<Button class="google" large @click="signInOauth('google')">
<GoogleIcon />
Google
</Button>
<Button class="white" large @click="signInOauth('steam')">
<SteamIcon />
Steam
</Button>
<Button class="gitlab" large @click="signInOauth('gitlab')">
<GitLabIcon />
GitLab
</Button>
</div>
<div class="divider">
<hr />
<p>Or</p>
</div>
<div v-if="!loggingIn" class="iconified-input username">
<MailIcon />
<input v-model="email" type="text" placeholder="Email" />
</div>
<div class="iconified-input username">
<UserIcon />
<input
v-model="username"
type="text"
:placeholder="loggingIn ? 'Email or username' : 'Username'"
/>
</div>
<div class="iconified-input" :class="{ username: !loggingIn }">
<LockIcon />
<input v-model="password" type="password" placeholder="Password" />
</div>
<div v-if="!loggingIn" class="iconified-input username">
<LockIcon />
<input v-model="confirmPassword" type="password" placeholder="Confirm password" />
</div>
<div class="turnstile">
<div id="turnstile-container"></div>
<div id="turnstile-container-2"></div>
</div>
<Checkbox
v-if="!loggingIn"
v-model="subscribe"
class="subscribe-btn"
label="Subscribe to updates about Modrinth"
/>
<div class="link-row">
<a v-if="loggingIn" class="button-base" @click="loggingIn = false"> Create account </a>
<a v-else class="button-base" @click="loggingIn = true">Sign in</a>
<a class="button-base" href="https://modrinth.com/auth/reset-password">
Forgot password?
</a>
</div>
</template>
<div class="button-row">
<Button class="transparent" large>Close</Button>
<Button v-if="twoFactorCode" color="primary" large @click="signIn2fa"> Login </Button>
<Button
v-else-if="loggingIn"
color="primary"
large
@click="signIn"
:disabled="!turnstileToken"
>
Login
</Button>
<Button v-else color="primary" large @click="createAccount" :disabled="!turnstileToken">
Create account
</Button>
</div>
</Card>
</Modal>
</template>
<style scoped lang="scss">
:deep(.modal-container) {
.modal-body {
width: auto;
.content {
background: none;
}
}
}
.card {
width: 25rem;
}
.button-grid {
display: grid;
grid-template-columns: 1fr 1fr;
grid-gap: var(--gap-md);
.btn {
width: 100%;
justify-content: center;
}
.discord {
background-color: #5865f2;
color: var(--color-contrast);
}
.github {
background-color: #8740f1;
color: var(--color-contrast);
}
.white {
background-color: var(--color-contrast);
color: var(--color-accent-contrast);
}
.google {
background-color: #4285f4;
color: var(--color-contrast);
}
.gitlab {
background-color: #fc6d26;
color: var(--color-contrast);
}
}
.divider {
position: relative;
display: flex;
align-items: center;
justify-content: center;
margin: var(--gap-md) 0;
p {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background-color: var(--color-raised-bg);
padding: 0 1rem;
margin: 0;
}
hr {
border: none;
width: 100%;
border-top: 2px solid var(--color-button-bg);
}
}
.iconified-input {
width: 100%;
input {
width: 100%;
flex-basis: auto;
}
}
.username {
margin-bottom: var(--gap-sm);
}
.link-row {
display: flex;
justify-content: space-between;
margin: var(--gap-md) 0;
a {
color: var(--color-blue);
text-decoration: underline;
&:hover {
cursor: pointer;
}
}
}
.button-row {
display: flex;
justify-content: space-between;
.btn {
flex-basis: auto;
}
.transparent {
padding: var(--gap-md) 0;
}
}
:deep(.checkbox) {
border: none;
}
.turnstile {
display: flex;
justify-content: center;
overflow: hidden;
border-radius: var(--radius-md);
border: 2px solid var(--color-button-bg);
height: 66px;
margin-top: var(--gap-md);
iframe {
margin: -1px;
min-width: calc(100% + 2px);
}
}
</style>
+21
View File
@@ -0,0 +1,21 @@
import { invoke } from '@tauri-apps/api/core'
export async function init_ads_window(overrideShown = false) {
return await invoke('plugin:ads|init_ads_window', { overrideShown, dpr: window.devicePixelRatio })
}
export async function show_ads_window() {
return await invoke('plugin:ads|show_ads_window', { dpr: window.devicePixelRatio })
}
export async function hide_ads_window(reset) {
return await invoke('plugin:ads|hide_ads_window', { reset })
}
export async function record_ads_click() {
return await invoke('plugin:ads|record_ads_click')
}
export async function open_ads_link(path, origin) {
return await invoke('plugin:ads|open_link', { path, origin })
}
+4
View File
@@ -93,3 +93,7 @@ export async function command_listener(callback) {
export async function warning_listener(callback) {
return await listen('warning', (event) => callback(event.payload))
}
export async function friend_listener(callback) {
return await listen('friend', (event) => callback(event.payload))
}
+17
View File
@@ -0,0 +1,17 @@
import { invoke } from '@tauri-apps/api/core'
export async function friends() {
return await invoke('plugin:friends|friends')
}
export async function friend_statuses() {
return await invoke('plugin:friends|friend_statuses')
}
export async function add_friend(userId) {
return await invoke('plugin:friends|add_friend', { userId })
}
export async function remove_friend(userId) {
return await invoke('plugin:friends|remove_friend', { userId })
}
+2 -20
View File
@@ -5,26 +5,8 @@
*/
import { invoke } from '@tauri-apps/api/core'
export async function login(provider) {
return await invoke('modrinth_auth_login', { provider })
}
export async function login_pass(username, password, challenge) {
return await invoke('plugin:mr-auth|login_pass', { username, password, challenge })
}
export async function login_2fa(code, flow) {
return await invoke('plugin:mr-auth|login_2fa', { code, flow })
}
export async function create_account(username, email, password, challenge, signUpNewsletter) {
return await invoke('plugin:mr-auth|create_account', {
username,
email,
password,
challenge,
signUpNewsletter,
})
export async function login() {
return await invoke('plugin:mr-auth|modrinth_login')
}
export async function logout() {
+12 -2
View File
@@ -7,7 +7,7 @@ import { invoke } from '@tauri-apps/api/core'
import { create } from './profile'
// Installs pack from a version ID
export async function install(projectId, versionId, packTitle, iconUrl) {
export async function create_profile_and_install(projectId, versionId, packTitle, iconUrl) {
const location = {
type: 'fromVersionId',
project_id: projectId,
@@ -28,8 +28,18 @@ export async function install(projectId, versionId, packTitle, iconUrl) {
return await invoke('plugin:pack|pack_install', { location, profile })
}
export async function install_to_existing_profile(projectId, versionId, title, profilePath) {
const location = {
type: 'fromVersionId',
project_id: projectId,
version_id: versionId,
title,
}
return await invoke('plugin:pack|pack_install', { location, profile: profilePath })
}
// Installs pack from a path
export async function install_from_file(path) {
export async function create_profile_and_install_from_file(path) {
const location = {
type: 'fromFile',
path: path,
+16
View File
@@ -4,6 +4,8 @@
* and deserialized into a usable JS object.
*/
import { invoke } from '@tauri-apps/api/core'
import { install_to_existing_profile } from '@/helpers/pack.js'
import { handleError } from '@/store/notifications.js'
/// Add instance
/*
@@ -186,3 +188,17 @@ export async function edit(path, editProfile) {
export async function edit_icon(path, iconPath) {
return await invoke('plugin:profile|profile_edit_icon', { path, iconPath })
}
export async function finish_install(instance) {
if (instance.install_stage !== 'pack_installed') {
let linkedData = instance.linked_data
await install_to_existing_profile(
linkedData.project_id,
linkedData.version_id,
instance.name,
instance.path,
).catch(handleError)
} else {
await install(instance.path, false).catch(handleError)
}
}
+115
View File
@@ -0,0 +1,115 @@
import type { ModrinthId } from '@modrinth/utils'
type GameInstance = {
path: string
install_stage: InstallStage
name: string
icon_path?: string
game_version: string
loader: InstanceLoader
loader_version?: string
groups: string[]
linked_data?: LinkedData
created: Date
modified: Date
last_played?: Date
submitted_time_played: number
recent_time_played: number
java_path?: string
extra_launch_args?: string[]
custom_env_vars?: [string, string][]
memory?: MemorySettings
force_fullscreen?: boolean
game_resolution?: [number, number]
hooks: Hooks
}
type InstallStage =
| 'installed'
| 'minecraft_installing'
| 'pack_installed'
| 'pack_installing'
| 'not_installed'
type LinkedData = {
project_id: ModrinthId
version_id: ModrinthId
locked: boolean
}
type InstanceLoader = 'vanilla' | 'forge' | 'fabric' | 'quilt' | 'neoforge'
type MemorySettings = {
maximum: number
}
type WindowSize = {
width: number
height: number
}
type Hooks = {
pre_launch?: string
wrapper?: string
post_exit?: string
}
type Manifest = {
gameVersions: ManifestGameVersion[]
}
type ManifestGameVersion = {
id: string
stable: boolean
loaders: ManifestLoaderVersion[]
}
type ManifestLoaderVersion = {
id: string
url: string
stable: boolean
}
type AppSettings = {
max_concurrent_downloads: number
max_concurrent_writes: number
theme: 'dark' | 'light' | 'oled'
default_page: 'Home' | 'Library'
collapsed_navigation: boolean
advanced_rendering: boolean
native_decorations: boolean
telemetry: boolean
discord_rpc: boolean
developer_mode: boolean
personalized_ads: boolean
onboarded: boolean
extra_launch_args: string[]
custom_env_vars: [string, string][]
memory: MemorySettings
force_fullscreen: boolean
game_resolution: [number, number]
hide_on_process_start: boolean
hooks: Hooks
custom_dir?: string
prev_custom_dir?: string
migrated: boolean
}
export type InstanceSettingsTabProps = {
instance: GameInstance
offline?: boolean
}
+4 -14
View File
@@ -33,6 +33,10 @@ export async function highlightModInProfile(profilePath, projectPath) {
return await highlightInFolder(fullPath)
}
export async function restartApp() {
return await invoke('restart_app')
}
export const releaseColor = (releaseType) => {
switch (releaseType) {
case 'release':
@@ -45,17 +49,3 @@ export const releaseColor = (releaseType) => {
return ''
}
}
export function debounce(fn, wait) {
let timer
return function (...args) {
if (timer) {
clearTimeout(timer) // clear any pre-existing timer
}
// eslint-disable-next-line @typescript-eslint/no-this-alias
const context = this // get the current context
timer = setTimeout(() => {
fn.apply(context, args) // call the function if time expires
}, wait)
}
}
@@ -0,0 +1,320 @@
{
"app.settings.developer-mode-enabled": {
"message": "Developer mode enabled."
},
"app.settings.tabs.appearance": {
"message": "Appearance"
},
"app.settings.tabs.default-instance-options": {
"message": "Default instance options"
},
"app.settings.tabs.feature-flags": {
"message": "Feature flags"
},
"app.settings.tabs.java-installations": {
"message": "Java installations"
},
"app.settings.tabs.privacy": {
"message": "Privacy"
},
"app.settings.tabs.resource-management": {
"message": "Resource management"
},
"instance.filter.updates-available": {
"message": "Updates available"
},
"instance.settings.tabs.general": {
"message": "General"
},
"instance.settings.tabs.general.delete": {
"message": "Delete instance"
},
"instance.settings.tabs.general.delete.button": {
"message": "Delete instance"
},
"instance.settings.tabs.general.delete.description": {
"message": "Permanently deletes an instance from your device, including your worlds, configs, and all installed content. Be careful, as once you delete a instance there is no way to recover it."
},
"instance.settings.tabs.general.deleting.button": {
"message": "Deleting..."
},
"instance.settings.tabs.general.duplicate-button": {
"message": "Duplicate"
},
"instance.settings.tabs.general.duplicate-button.tooltip.installing": {
"message": "Cannot duplicate while installing."
},
"instance.settings.tabs.general.duplicate-instance": {
"message": "Duplicate instance"
},
"instance.settings.tabs.general.duplicate-instance.description": {
"message": "Creates a copy of this instance, including worlds, configs, mods, etc."
},
"instance.settings.tabs.general.edit-icon": {
"message": "Edit icon"
},
"instance.settings.tabs.general.edit-icon.remove": {
"message": "Remove icon"
},
"instance.settings.tabs.general.edit-icon.replace": {
"message": "Replace icon"
},
"instance.settings.tabs.general.edit-icon.select": {
"message": "Select icon"
},
"instance.settings.tabs.general.library-groups": {
"message": "Library groups"
},
"instance.settings.tabs.general.library-groups.create": {
"message": "Create new group"
},
"instance.settings.tabs.general.library-groups.description": {
"message": "Library groups allow you to organize your instances into different sections in your library."
},
"instance.settings.tabs.general.library-groups.enter-name": {
"message": "Enter group name"
},
"instance.settings.tabs.general.name": {
"message": "Name"
},
"instance.settings.tabs.hooks": {
"message": "Launch hooks"
},
"instance.settings.tabs.hooks.custom-hooks": {
"message": "Custom launch hooks"
},
"instance.settings.tabs.hooks.description": {
"message": "Hooks allow advanced users to run certain system commands before and after launching the game."
},
"instance.settings.tabs.hooks.post-exit": {
"message": "Post-exit"
},
"instance.settings.tabs.hooks.post-exit.description": {
"message": "Ran after the game closes."
},
"instance.settings.tabs.hooks.post-exit.enter": {
"message": "Enter post-exit command..."
},
"instance.settings.tabs.hooks.pre-launch": {
"message": "Pre-launch"
},
"instance.settings.tabs.hooks.pre-launch.description": {
"message": "Ran before the instance is launched."
},
"instance.settings.tabs.hooks.pre-launch.enter": {
"message": "Enter pre-launch command..."
},
"instance.settings.tabs.hooks.title": {
"message": "Game launch hooks"
},
"instance.settings.tabs.hooks.wrapper": {
"message": "Wrapper"
},
"instance.settings.tabs.hooks.wrapper.description": {
"message": "Wrapper command for launching Minecraft."
},
"instance.settings.tabs.hooks.wrapper.enter": {
"message": "Enter wrapper command..."
},
"instance.settings.tabs.installation": {
"message": "Installation"
},
"instance.settings.tabs.installation.change-version.already-installed.modded": {
"message": "{platform} {version} for Minecraft {game_version} already installed"
},
"instance.settings.tabs.installation.change-version.already-installed.vanilla": {
"message": "Vanilla {game_version} already installed"
},
"instance.settings.tabs.installation.change-version.button": {
"message": "Change version"
},
"instance.settings.tabs.installation.change-version.button.install": {
"message": "Install"
},
"instance.settings.tabs.installation.change-version.button.installing": {
"message": "Installing"
},
"instance.settings.tabs.installation.change-version.cannot-while-fetching": {
"message": "Fetching modpack versions"
},
"instance.settings.tabs.installation.change-version.in-progress": {
"message": "Installing new version"
},
"instance.settings.tabs.installation.currently-installed": {
"message": "Currently installed"
},
"instance.settings.tabs.installation.debug-information": {
"message": "Debug information:"
},
"instance.settings.tabs.installation.fetching-modpack-details": {
"message": "Fetching modpack details"
},
"instance.settings.tabs.installation.game-version": {
"message": "Game version"
},
"instance.settings.tabs.installation.install": {
"message": "Install"
},
"instance.settings.tabs.installation.install.in-progress": {
"message": "Installation in progress"
},
"instance.settings.tabs.installation.loader-version": {
"message": "{loader} version"
},
"instance.settings.tabs.installation.minecraft-version": {
"message": "Minecraft {version}"
},
"instance.settings.tabs.installation.no-connection": {
"message": "Cannot fetch linked modpack details. Please check your internet connection."
},
"instance.settings.tabs.installation.no-loader-versions": {
"message": "{loader} is not available for Minecraft {version}. Try another mod loader."
},
"instance.settings.tabs.installation.no-modpack-found": {
"message": "This instance is linked to a modpack, but the modpack could not be found on Modrinth."
},
"instance.settings.tabs.installation.platform": {
"message": "Platform"
},
"instance.settings.tabs.installation.reinstall.button": {
"message": "Reinstall modpack"
},
"instance.settings.tabs.installation.reinstall.button.reinstalling": {
"message": "Reinstalling modpack"
},
"instance.settings.tabs.installation.reinstall.confirm.description": {
"message": "Reinstalling will reset all installed or modified content to what is provided by the modpack, removing any mods or content you have added on top of the original installation. This may fix unexpected behavior if changes have been made to the instance, but if your worlds now depend on additional installed content, it may break existing worlds."
},
"instance.settings.tabs.installation.reinstall.confirm.title": {
"message": "Are you sure you want to reinstall this instance?"
},
"instance.settings.tabs.installation.reinstall.description": {
"message": "Resets the instance's content to its original state, removing any mods or content you have added on top of the original modpack."
},
"instance.settings.tabs.installation.reinstall.title": {
"message": "Reinstall modpack"
},
"instance.settings.tabs.installation.repair.button": {
"message": "Repair"
},
"instance.settings.tabs.installation.repair.button.repairing": {
"message": "Repairing"
},
"instance.settings.tabs.installation.repair.confirm.description": {
"message": "Repairing reinstalls Minecraft dependencies and checks for corruption. This may resolve issues if your game is not launching due to launcher-related errors, but will not resolve issues or crashes related to installed mods."
},
"instance.settings.tabs.installation.repair.confirm.title": {
"message": "Repair instance?"
},
"instance.settings.tabs.installation.repair.in-progress": {
"message": "Repair in progress"
},
"instance.settings.tabs.installation.reset-selections": {
"message": "Reset to current"
},
"instance.settings.tabs.installation.show-all-versions": {
"message": "Show all versions"
},
"instance.settings.tabs.installation.tooltip.action.change-version": {
"message": "change version"
},
"instance.settings.tabs.installation.tooltip.action.install": {
"message": "install"
},
"instance.settings.tabs.installation.tooltip.action.reinstall": {
"message": "reinstall"
},
"instance.settings.tabs.installation.tooltip.action.repair": {
"message": "repair"
},
"instance.settings.tabs.installation.tooltip.cannot-while-installing": {
"message": "Cannot {action} while installing"
},
"instance.settings.tabs.installation.tooltip.cannot-while-offline": {
"message": "Cannot {action} while offline"
},
"instance.settings.tabs.installation.tooltip.cannot-while-repairing": {
"message": "Cannot {action} while repairing"
},
"instance.settings.tabs.installation.unknown-version": {
"message": "(unknown version)"
},
"instance.settings.tabs.installation.unlink.button": {
"message": "Unlink instance"
},
"instance.settings.tabs.installation.unlink.confirm.description": {
"message": "If you proceed, you will not be able to re-link it without creating an entirely new instance. You will no longer receive modpack updates and it will become a normal."
},
"instance.settings.tabs.installation.unlink.confirm.title": {
"message": "Are you sure you want to unlink this instance?"
},
"instance.settings.tabs.installation.unlink.description": {
"message": "This instance is linked to a modpack, which means mods can't be updated and you can't change the mod loader or Minecraft version. Unlinking will permanently disconnect this instance from the modpack."
},
"instance.settings.tabs.installation.unlink.title": {
"message": "Unlink from modpack"
},
"instance.settings.tabs.java": {
"message": "Java and memory"
},
"instance.settings.tabs.java.environment-variables": {
"message": "Environment variables"
},
"instance.settings.tabs.java.hooks": {
"message": "Hooks"
},
"instance.settings.tabs.java.java-arguments": {
"message": "Java arguments"
},
"instance.settings.tabs.java.java-installation": {
"message": "Java installation"
},
"instance.settings.tabs.java.java-memory": {
"message": "Memory allocated"
},
"instance.settings.tabs.window": {
"message": "Window"
},
"instance.settings.tabs.window.custom-window-settings": {
"message": "Custom window settings"
},
"instance.settings.tabs.window.fullscreen": {
"message": "Fullscreen"
},
"instance.settings.tabs.window.fullscreen.description": {
"message": "Make the game start in full screen when launched (using options.txt)."
},
"instance.settings.tabs.window.height": {
"message": "Height"
},
"instance.settings.tabs.window.height.description": {
"message": "The height of the game window when launched."
},
"instance.settings.tabs.window.height.enter": {
"message": "Enter height..."
},
"instance.settings.tabs.window.width": {
"message": "Width"
},
"instance.settings.tabs.window.width.description": {
"message": "The width of the game window when launched."
},
"instance.settings.tabs.window.width.enter": {
"message": "Enter width..."
},
"instance.settings.title": {
"message": "Settings"
},
"search.filter.locked.instance": {
"message": "Provided by the instance"
},
"search.filter.locked.instance-game-version.title": {
"message": "Game version is provided by the instance"
},
"search.filter.locked.instance-loader.title": {
"message": "Loader is provided by the instance"
},
"search.filter.locked.instance.sync": {
"message": "Sync with instance"
}
}
+10 -1
View File
@@ -37,7 +37,16 @@ Sentry.init({
app.use(router)
app.use(pinia)
app.use(FloatingVue)
app.use(FloatingVue, {
themes: {
'ribbit-popout': {
$extend: 'dropdown',
placement: 'bottom-end',
instantMove: true,
distance: 8,
},
},
})
app.use(VIntlPlugin)
app.mount('#app')
File diff suppressed because it is too large Load Diff
+19 -24
View File
@@ -31,19 +31,21 @@ window.addEventListener('online', () => {
const getInstances = async () => {
const profiles = await list().catch(handleError)
recentInstances.value = profiles.sort((a, b) => {
const dateA = dayjs(a.last_played ?? 0)
const dateB = dayjs(b.last_played ?? 0)
recentInstances.value = profiles
.filter((x) => x.last_played)
.sort((a, b) => {
const dateA = dayjs(a.last_played)
const dateB = dayjs(b.last_played)
if (dateA.isSame(dateB)) {
return a.name.localeCompare(b.name)
}
if (dateA.isSame(dateB)) {
return a.name.localeCompare(b.name)
}
return dateB - dateA
})
return dateB - dateA
})
let filters = []
for (const instance of recentInstances.value) {
const filters = []
for (const instance of profiles) {
if (instance.linked_data && instance.linked_data.project_id) {
filters.push(`NOT"project_id"="${instance.linked_data.project_id}"`)
}
@@ -99,25 +101,28 @@ onUnmounted(() => {
</script>
<template>
<div class="page-container">
<div class="p-6 flex flex-col gap-2">
<h1 v-if="recentInstances" class="m-0 text-2xl">Welcome back!</h1>
<h1 v-else class="m-0 text-2xl">Welcome to Modrinth App!</h1>
<RowDisplay
v-if="total > 0"
:instances="[
{
label: 'Jump back in',
label: 'Recently played',
route: '/library',
instances: recentInstances,
instance: true,
downloaded: true,
compact: true,
},
{
label: 'Popular packs',
label: 'Discover a modpack',
route: '/browse/modpack',
instances: featuredModpacks,
downloaded: false,
},
{
label: 'Popular mods',
label: 'Discover mods',
route: '/browse/mod',
instances: featuredMods,
downloaded: false,
@@ -127,13 +132,3 @@ onUnmounted(() => {
/>
</div>
</template>
<style lang="scss" scoped>
.page-container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 100%;
}
</style>
-604
View File
@@ -1,604 +0,0 @@
<script setup>
import { ref, watch } from 'vue'
import { LogOutIcon, LogInIcon, BoxIcon, FolderSearchIcon, TrashIcon } from '@modrinth/assets'
import { Card, Slider, DropdownSelect, Toggle, ConfirmModal, Button } from '@modrinth/ui'
import { handleError, useTheming } from '@/store/state'
import { get, set } from '@/helpers/settings'
import { get_java_versions, get_max_memory, set_java_version } from '@/helpers/jre'
import { get as getCreds, logout } from '@/helpers/mr_auth.js'
import JavaSelector from '@/components/ui/JavaSelector.vue'
import ModrinthLoginScreen from '@/components/ui/tutorial/ModrinthLoginScreen.vue'
import { optOutAnalytics, optInAnalytics } from '@/helpers/analytics'
import { open } from '@tauri-apps/plugin-dialog'
import { getOS } from '@/helpers/utils.js'
import { getVersion } from '@tauri-apps/api/app'
import { get_user, purge_cache_types } from '@/helpers/cache.js'
const pageOptions = ['Home', 'Library']
const themeStore = useTheming()
const version = await getVersion()
const accessSettings = async () => {
const settings = await get()
settings.launchArgs = settings.extra_launch_args.join(' ')
settings.envVars = settings.custom_env_vars.map((x) => x.join('=')).join(' ')
return settings
}
const fetchSettings = await accessSettings().catch(handleError)
const settings = ref(fetchSettings)
const maxMemory = ref(Math.floor((await get_max_memory().catch(handleError)) / 1024))
watch(
settings,
async (oldSettings, newSettings) => {
if (oldSettings.loaded_config_dir !== newSettings.loaded_config_dir) {
return
}
const setSettings = JSON.parse(JSON.stringify(newSettings))
if (setSettings.telemetry) {
optInAnalytics()
} else {
optOutAnalytics()
}
setSettings.extra_launch_args = setSettings.launchArgs.trim().split(/\s+/).filter(Boolean)
setSettings.custom_env_vars = setSettings.envVars
.trim()
.split(/\s+/)
.filter(Boolean)
.map((x) => x.split('=').filter(Boolean))
if (!setSettings.hooks.pre_launch) {
setSettings.hooks.pre_launch = null
}
if (!setSettings.hooks.wrapper) {
setSettings.hooks.wrapper = null
}
if (!setSettings.hooks.post_exit) {
setSettings.hooks.post_exit = null
}
if (!setSettings.custom_dir) {
setSettings.custom_dir = null
}
await set(setSettings)
},
{ deep: true },
)
const javaVersions = ref(await get_java_versions().catch(handleError))
async function updateJavaVersion(version) {
if (version?.path === '') {
version.path = undefined
}
if (version?.path) {
version.path = version.path.replace('java.exe', 'javaw.exe')
}
await set_java_version(version).catch(handleError)
}
async function fetchCredentials() {
const creds = await getCreds().catch(handleError)
if (creds && creds.user_id) {
creds.user = await get_user(creds.user_id).catch(handleError)
}
credentials.value = creds
}
const credentials = ref()
await fetchCredentials()
const loginScreenModal = ref()
async function logOut() {
await logout().catch(handleError)
await fetchCredentials()
}
async function signInAfter() {
await fetchCredentials()
}
async function findLauncherDir() {
const newDir = await open({
multiple: false,
directory: true,
title: 'Select a new app directory',
})
if (newDir) {
settings.value.custom_dir = newDir
}
}
async function purgeCache() {
await purge_cache_types([
'project',
'version',
'user',
'team',
'organization',
'loader_manifest',
'minecraft_manifest',
'categories',
'report_types',
'loaders',
'game_versions',
'donation_platforms',
'file_update',
'search_results',
]).catch(handleError)
}
</script>
<template>
<div class="settings-page">
<Card>
<div class="label">
<h3>
<span class="label__title size-card-header">General settings</span>
</h3>
</div>
<ModrinthLoginScreen ref="loginScreenModal" :callback="signInAfter" />
<div class="adjacent-input">
<label for="sign-in">
<span class="label__title">Manage account</span>
<span v-if="credentials" class="label__description">
You are currently logged in as {{ credentials.user.username }}.
</span>
<span v-else> Sign in to your Modrinth account. </span>
</label>
<button v-if="credentials" id="sign-in" class="btn" @click="logOut">
<LogOutIcon />
Sign out
</button>
<button v-else id="sign-in" class="btn" @click="$refs.loginScreenModal.show()">
<LogInIcon />
Sign in
</button>
</div>
<ConfirmModal
ref="purgeCacheConfirmModal"
title="Are you sure you want to purge the cache?"
description="If you proceed, your entire cache will be purged. This may slow down the app temporarily."
:has-to-type="false"
proceed-label="Purge cache"
:noblur="!themeStore.advancedRendering"
@proceed="purgeCache"
/>
<div class="adjacent-input">
<label for="purge-cache">
<span class="label__title">App cache</span>
<span class="label__description">
The Modrinth app stores a cache of data to speed up loading. This can be purged to force
the app to reload data. <br />
This may slow down the app temporarily.
</span>
</label>
<button id="purge-cache" class="btn" @click="$refs.purgeCacheConfirmModal.show()">
<TrashIcon />
Purge cache
</button>
</div>
<label for="appDir">
<span class="label__title">App directory</span>
<span class="label__description">
The directory where the launcher stores all of its files. Changes will be applied after
restarting the launcher.
</span>
</label>
<div class="app-directory">
<div class="iconified-input">
<BoxIcon />
<input id="appDir" v-model="settings.custom_dir" type="text" class="input" />
<Button class="r-btn" @click="findLauncherDir">
<FolderSearchIcon />
</Button>
</div>
</div>
</Card>
<Card>
<div class="label">
<h3>
<span class="label__title size-card-header">Display</span>
</h3>
</div>
<div class="adjacent-input">
<label for="theme">
<span class="label__title">Color theme</span>
<span class="label__description">Change the global launcher color theme.</span>
</label>
<DropdownSelect
id="theme"
name="Theme dropdown"
:options="themeStore.themeOptions"
:default-value="settings.theme"
:model-value="settings.theme"
class="theme-dropdown"
@change="
(e) => {
themeStore.setThemeState(e.option.toLowerCase())
settings.theme = themeStore.selectedTheme
}
"
/>
</div>
<div class="adjacent-input">
<label for="advanced-rendering">
<span class="label__title">Advanced rendering</span>
<span class="label__description">
Enables advanced rendering such as blur effects that may cause performance issues
without hardware-accelerated rendering.
</span>
</label>
<Toggle
id="advanced-rendering"
:model-value="themeStore.advancedRendering"
:checked="themeStore.advancedRendering"
@update:model-value="
(e) => {
themeStore.advancedRendering = e
settings.advanced_rendering = themeStore.advancedRendering
}
"
/>
</div>
<div class="adjacent-input">
<label for="minimize-launcher">
<span class="label__title">Minimize launcher</span>
<span class="label__description"
>Minimize the launcher when a Minecraft process starts.</span
>
</label>
<Toggle
id="minimize-launcher"
:model-value="settings.hide_on_process_start"
:checked="settings.hide_on_process_start"
@update:model-value="
(e) => {
settings.hide_on_process_start = e
}
"
/>
</div>
<div v-if="getOS() != 'MacOS'" class="adjacent-input">
<label for="native-decorations">
<span class="label__title">Native decorations</span>
<span class="label__description">Use system window frame (app restart required).</span>
</label>
<Toggle
id="native-decorations"
:model-value="settings.native_decorations"
:checked="settings.native_decorations"
@update:model-value="
(e) => {
settings.native_decorations = e
}
"
/>
</div>
<div class="adjacent-input">
<label for="opening-page">
<span class="label__title">Default landing page</span>
<span class="label__description">Change the page to which the launcher opens on.</span>
</label>
<DropdownSelect
id="opening-page"
name="Opening page dropdown"
:options="pageOptions"
:default-value="settings.default_page"
:model-value="settings.default_page"
class="opening-page"
@change="
(e) => {
settings.default_page = e.option
}
"
/>
</div>
</Card>
<Card>
<div class="label">
<h3>
<span class="label__title size-card-header">Resource management</span>
</h3>
</div>
<div class="adjacent-input">
<label for="max-downloads">
<span class="label__title">Maximum concurrent downloads</span>
<span class="label__description">
The maximum amount of files the launcher can download at the same time. Set this to a
lower value if you have a poor internet connection. (app restart required to take
effect)
</span>
</label>
<Slider
id="max-downloads"
v-model="settings.max_concurrent_downloads"
:min="1"
:max="10"
:step="1"
/>
</div>
<div class="adjacent-input">
<label for="max-writes">
<span class="label__title">Maximum concurrent writes</span>
<span class="label__description">
The maximum amount of files the launcher can write to the disk at once. Set this to a
lower value if you are frequently getting I/O errors. (app restart required to take
effect)
</span>
</label>
<Slider
id="max-writes"
v-model="settings.max_concurrent_writes"
:min="1"
:max="50"
:step="1"
/>
</div>
</Card>
<Card>
<div class="label">
<h3>
<span class="label__title size-card-header">Privacy</span>
</h3>
</div>
<div class="adjacent-input">
<label for="opt-out-analytics">
<span class="label__title">Telemetry</span>
<span class="label__description">
Modrinth collects anonymized analytics and usage data to improve our user experience and
customize your experience. By disabling this option, you opt out and your data will no
longer be collected.
</span>
</label>
<Toggle
id="opt-out-analytics"
:model-value="settings.telemetry"
:checked="settings.telemetry"
@update:model-value="
(e) => {
settings.telemetry = e
}
"
/>
</div>
<div class="adjacent-input">
<label for="disable-discord-rpc">
<span class="label__title">Discord RPC</span>
<span class="label__description">
Manages the Discord Rich Presence integration. Disabling this will cause 'Modrinth' to
no longer show up as a game or app you are using on your Discord profile. This does not
disable any instance-specific Discord Rich Presence integrations, such as those added by
mods. (app restart required to take effect)
</span>
</label>
<Toggle
id="disable-discord-rpc"
v-model="settings.discord_rpc"
:checked="settings.discord_rpc"
/>
</div>
</Card>
<Card>
<div class="label">
<h3>
<span class="label__title size-card-header">Java settings</span>
</h3>
</div>
<template v-for="version in [21, 17, 8]">
<label :for="'java-' + version">
<span class="label__title">Java {{ version }} location</span>
</label>
<JavaSelector
:id="'java-selector-' + version"
v-model="javaVersions[version]"
:version="version"
@update:model-value="updateJavaVersion"
/>
</template>
<hr class="card-divider" />
<label for="java-args">
<span class="label__title">Java arguments</span>
</label>
<input
id="java-args"
v-model="settings.launchArgs"
autocomplete="off"
type="text"
class="installation-input"
placeholder="Enter java arguments..."
/>
<label for="env-vars">
<span class="label__title">Environmental variables</span>
</label>
<input
id="env-vars"
v-model="settings.envVars"
autocomplete="off"
type="text"
class="installation-input"
placeholder="Enter environmental variables..."
/>
<hr class="card-divider" />
<div class="adjacent-input">
<label for="max-memory">
<span class="label__title">Java memory</span>
<span class="label__description">
The memory allocated to each instance when it is ran.
</span>
</label>
<Slider
id="max-memory"
v-model="settings.memory.maximum"
:min="8"
:max="maxMemory"
:step="64"
unit="mb"
/>
</div>
</Card>
<Card>
<div class="label">
<h3>
<span class="label__title size-card-header">Hooks</span>
</h3>
</div>
<div class="adjacent-input">
<label for="pre-launch">
<span class="label__title">Pre launch</span>
<span class="label__description"> Ran before the instance is launched. </span>
</label>
<input
id="pre-launch"
v-model="settings.hooks.pre_launch"
autocomplete="off"
type="text"
placeholder="Enter pre-launch command..."
/>
</div>
<div class="adjacent-input">
<label for="wrapper">
<span class="label__title">Wrapper</span>
<span class="label__description"> Wrapper command for launching Minecraft. </span>
</label>
<input
id="wrapper"
v-model="settings.hooks.wrapper"
autocomplete="off"
type="text"
placeholder="Enter wrapper command..."
/>
</div>
<div class="adjacent-input">
<label for="post-exit">
<span class="label__title">Post exit</span>
<span class="label__description"> Ran after the game closes. </span>
</label>
<input
id="post-exit"
v-model="settings.hooks.post_exit"
autocomplete="off"
type="text"
placeholder="Enter post-exit command..."
/>
</div>
</Card>
<Card>
<div class="label">
<h3>
<span class="label__title size-card-header">Window size</span>
</h3>
</div>
<div class="adjacent-input">
<label for="fullscreen">
<span class="label__title">Fullscreen</span>
<span class="label__description">
Overwrites the options.txt file to start in full screen when launched.
</span>
</label>
<Toggle
id="fullscreen"
:model-value="settings.force_fullscreen"
:checked="settings.force_fullscreen"
@update:model-value="
(e) => {
settings.force_fullscreen = e
}
"
/>
</div>
<div class="adjacent-input">
<label for="width">
<span class="label__title">Width</span>
<span class="label__description"> The width of the game window when launched. </span>
</label>
<input
id="width"
v-model="settings.game_resolution[0]"
:disabled="settings.force_fullscreen"
autocomplete="off"
type="number"
placeholder="Enter width..."
/>
</div>
<div class="adjacent-input">
<label for="height">
<span class="label__title">Height</span>
<span class="label__description"> The height of the game window when launched. </span>
</label>
<input
id="height"
v-model="settings.game_resolution[1]"
:disabled="settings.force_fullscreen"
autocomplete="off"
type="number"
class="input"
placeholder="Enter height..."
/>
</div>
</Card>
<Card>
<div class="label">
<h3>
<span class="label__title size-card-header">About</span>
</h3>
</div>
<div>
<label>
<span class="label__title">App version</span>
<span class="label__description">Modrinth App v{{ version }} </span>
</label>
</div>
</Card>
</div>
</template>
<style lang="scss" scoped>
.settings-page {
margin: 1rem;
}
.installation-input {
width: 100% !important;
flex-grow: 1;
}
.theme-dropdown {
text-transform: capitalize;
}
.card-divider {
margin: 1rem 0;
}
.app-directory {
display: flex;
flex-direction: row;
align-items: center;
gap: var(--gap-sm);
.iconified-input {
flex-grow: 1;
input {
flex-basis: auto;
}
}
}
</style>
+1 -3
View File
@@ -1,6 +1,4 @@
import Index from './Index.vue'
import Browse from './Browse.vue'
import Library from './Library.vue'
import Settings from './Settings.vue'
export { Index, Browse, Library, Settings }
export { Index, Browse }
+263 -149
View File
@@ -1,98 +1,143 @@
<template>
<div class="instance-container">
<div class="side-cards">
<Card class="instance-card" @contextmenu.prevent.stop="handleRightClick">
<Avatar size="lg" :src="instance.icon_path ? convertFileSrc(instance.icon_path) : null" />
<div class="instance-info">
<h2 class="name">{{ instance.name }}</h2>
<span class="metadata"> {{ instance.loader }} {{ instance.game_version }} </span>
<div
class="p-6 pr-2 pb-4"
@contextmenu.prevent.stop="(event) => handleRightClick(event, instance.path)"
>
<ExportModal ref="exportModal" :instance="instance" />
<InstanceSettingsModal ref="settingsModal" :instance="instance" :offline="offline" />
<ContentPageHeader>
<template #icon>
<Avatar :src="icon" :alt="instance.name" size="96px" :tint-by="instance.path" />
</template>
<template #title>
{{ instance.name }}
</template>
<template #summary> </template>
<template #stats>
<div
class="flex items-center gap-2 font-semibold transform capitalize border-0 border-solid border-divider pr-4 md:border-r"
>
<GameIcon class="h-6 w-6 text-secondary" />
{{ instance.loader }} {{ instance.game_version }}
</div>
<span class="button-group">
<Button v-if="instance.install_stage !== 'installed'" disabled class="instance-button">
Installing...
</Button>
<Button
v-else-if="playing === true"
color="danger"
class="instance-button"
@click="stopInstance('InstancePage')"
<div class="flex items-center gap-2 font-semibold">
<TimerIcon class="h-6 w-6 text-secondary" />
<template v-if="timePlayed > 0">
{{ timePlayedHumanized }}
</template>
<template v-else> Never played </template>
</div>
</template>
<template #actions>
<div class="flex gap-2">
<ButtonStyled
v-if="instance.install_stage.includes('installing')"
color="brand"
size="large"
>
<StopCircleIcon />
Stop
</Button>
<Button
<button disabled>Installing...</button>
</ButtonStyled>
<ButtonStyled
v-else-if="instance.install_stage !== 'installed'"
color="brand"
size="large"
>
<button @click="repairInstance()">
<DownloadIcon />
Repair
</button>
</ButtonStyled>
<ButtonStyled v-else-if="playing === true" color="red" size="large">
<button @click="stopInstance('InstancePage')">
<StopCircleIcon />
Stop
</button>
</ButtonStyled>
<ButtonStyled
v-else-if="playing === false && loading === false"
color="primary"
class="instance-button"
@click="startInstance('InstancePage')"
color="brand"
size="large"
>
<PlayIcon />
Play
</Button>
<Button
<button @click="startInstance('InstancePage')">
<PlayIcon />
Play
</button>
</ButtonStyled>
<ButtonStyled
v-else-if="loading === true && playing === false"
disabled
class="instance-button"
color="brand"
size="large"
>
Loading...
</Button>
<Button
v-tooltip="'Open instance folder'"
class="instance-button"
@click="showProfileInFolder(instance.path)"
>
<FolderOpenIcon />
Folder
</Button>
</span>
<hr class="card-divider" />
<div class="pages-list">
<RouterLink :to="`/instance/${encodeURIComponent($route.params.id)}/`" class="btn">
<BoxIcon />
Content
</RouterLink>
<RouterLink :to="`/instance/${encodeURIComponent($route.params.id)}/logs`" class="btn">
<FileIcon />
Logs
</RouterLink>
<RouterLink :to="`/instance/${encodeURIComponent($route.params.id)}/options`" class="btn">
<SettingsIcon />
Options
</RouterLink>
<button disabled>Loading...</button>
</ButtonStyled>
<ButtonStyled size="large" circular>
<button v-tooltip="'Instance settings'" @click="settingsModal.show()">
<SettingsIcon />
</button>
</ButtonStyled>
<ButtonStyled size="large" type="transparent" circular>
<OverflowMenu
:options="[
{
id: 'open-folder',
action: () => showProfileInFolder(instance.path),
},
{
id: 'export-mrpack',
action: () => $refs.exportModal.show(),
},
]"
>
<MoreVerticalIcon />
<template #share-instance> <UserPlusIcon /> Share instance </template>
<template #host-a-server> <ServerIcon /> Create a server </template>
<template #open-folder> <FolderOpenIcon /> Open folder </template>
<template #export-mrpack> <PackageIcon /> Export modpack </template>
</OverflowMenu>
</ButtonStyled>
</div>
</Card>
</div>
<div class="content">
<PromotionWrapper />
<RouterView v-slot="{ Component }">
<template v-if="Component">
<Suspense @pending="loadingBar.startLoading()" @resolve="loadingBar.stopLoading()">
<component
:is="Component"
:instance="instance"
:options="options"
:offline="offline"
:playing="playing"
:versions="modrinthVersions"
:installed="instance.install_stage !== 'installed'"
></component>
</Suspense>
</template>
</RouterView>
</div>
</template>
</ContentPageHeader>
</div>
<div class="px-6">
<NavTabs :links="tabs" />
</div>
<div class="p-6 pt-4">
<RouterView v-slot="{ Component }" :key="instance.path">
<template v-if="Component">
<Suspense
:key="instance.path"
@pending="loadingBar.startLoading()"
@resolve="loadingBar.stopLoading()"
>
<component
:is="Component"
:instance="instance"
:options="options"
:offline="offline"
:playing="playing"
:versions="modrinthVersions"
:installed="instance.install_stage !== 'installed'"
></component>
<template #fallback>
<LoadingIndicator />
</template>
</Suspense>
</template>
</RouterView>
</div>
<ContextMenu ref="options" @option-clicked="handleOptionsClick">
<template #play> <PlayIcon /> Play </template>
<template #stop> <StopCircleIcon /> Stop </template>
<template #add_content> <PlusIcon /> Add Content </template>
<template #add_content> <PlusIcon /> Add content </template>
<template #edit> <EditIcon /> Edit </template>
<template #copy_path> <ClipboardCopyIcon /> Copy Path </template>
<template #open_folder> <ClipboardCopyIcon /> Open Folder </template>
<template #copy_link> <ClipboardCopyIcon /> Copy Link </template>
<template #open_link> <ClipboardCopyIcon /> Open In Modrinth <ExternalIcon /> </template>
<template #copy_path> <ClipboardCopyIcon /> Copy path </template>
<template #open_folder> <ClipboardCopyIcon /> Open folder </template>
<template #copy_link> <ClipboardCopyIcon /> Copy link </template>
<template #open_link> <ClipboardCopyIcon /> Open in Modrinth <ExternalIcon /> </template>
<template #copy_names><EditIcon />Copy names</template>
<template #copy_slugs><HashIcon />Copy slugs</template>
<template #copy_links><GlobeIcon />Copy Links</template>
<template #copy_links><GlobeIcon />Copy links</template>
<template #toggle><EditIcon />Toggle selected</template>
<template #disable><XIcon />Disable selected</template>
<template #enable><CheckCircleIcon />Enable selected</template>
@@ -104,46 +149,120 @@
</ContextMenu>
</template>
<script setup>
import { Button, Avatar, Card } from '@modrinth/ui'
import {
BoxIcon,
SettingsIcon,
FileIcon,
PlayIcon,
StopCircleIcon,
EditIcon,
FolderOpenIcon,
ClipboardCopyIcon,
PlusIcon,
ExternalIcon,
HashIcon,
GlobeIcon,
EyeIcon,
XIcon,
Avatar,
ButtonStyled,
ContentPageHeader,
LoadingIndicator,
OverflowMenu,
} from '@modrinth/ui'
import {
CheckCircleIcon,
ClipboardCopyIcon,
DownloadIcon,
EditIcon,
ExternalIcon,
EyeIcon,
FolderOpenIcon,
GameIcon,
GlobeIcon,
HashIcon,
MoreVerticalIcon,
PackageIcon,
PlayIcon,
PlusIcon,
ServerIcon,
SettingsIcon,
StopCircleIcon,
TimerIcon,
UpdatedIcon,
UserPlusIcon,
XIcon,
} from '@modrinth/assets'
import { get, kill, run } from '@/helpers/profile'
import { finish_install, get, get_full_path, kill, run } from '@/helpers/profile'
import { get_by_profile_path } from '@/helpers/process'
import { process_listener, profile_listener } from '@/helpers/events'
import { useRoute, useRouter } from 'vue-router'
import { ref, onUnmounted } from 'vue'
import { computed, onUnmounted, ref, watch } from 'vue'
import { handleError, useBreadcrumbs, useLoading } from '@/store/state'
import { showProfileInFolder } from '@/helpers/utils.js'
import ContextMenu from '@/components/ui/ContextMenu.vue'
import NavTabs from '@/components/ui/NavTabs.vue'
import { trackEvent } from '@/helpers/analytics'
import { convertFileSrc } from '@tauri-apps/api/core'
import { handleSevereError } from '@/store/error.js'
import { get_project, get_version_many } from '@/helpers/cache.js'
import dayjs from 'dayjs'
import PromotionWrapper from '@/components/ui/PromotionWrapper.vue'
import duration from 'dayjs/plugin/duration'
import relativeTime from 'dayjs/plugin/relativeTime'
import ExportModal from '@/components/ui/ExportModal.vue'
import InstanceSettingsModal from '@/components/ui/modal/InstanceSettingsModal.vue'
dayjs.extend(duration)
dayjs.extend(relativeTime)
const route = useRoute()
const router = useRouter()
const breadcrumbs = useBreadcrumbs()
const instance = ref(await get(route.params.id).catch(handleError))
const offline = ref(!navigator.onLine)
window.addEventListener('offline', () => {
offline.value = true
})
window.addEventListener('online', () => {
offline.value = false
})
const instance = ref()
const modrinthVersions = ref([])
const playing = ref(false)
const loading = ref(false)
async function fetchInstance() {
instance.value = await get(route.params.id).catch(handleError)
if (!offline.value && instance.value.linked_data && instance.value.linked_data.project_id) {
get_project(instance.value.linked_data.project_id, 'must_revalidate')
.catch(handleError)
.then((project) => {
if (project && project.versions) {
get_version_many(project.versions, 'must_revalidate')
.catch(handleError)
.then((versions) => {
modrinthVersions.value = versions.sort(
(a, b) => dayjs(b.date_published) - dayjs(a.date_published),
)
})
}
})
}
const runningProcesses = await get_by_profile_path(route.params.id).catch(handleError)
playing.value = runningProcesses.length > 0
}
await fetchInstance()
watch(
() => route.params.id,
async () => {
if (route.params.id && route.path.startsWith('/instance')) {
await fetchInstance()
}
},
)
const tabs = computed(() => [
{
label: 'Content',
href: `/instance/${encodeURIComponent(route.params.id)}`,
},
{
label: 'Logs',
href: `/instance/${encodeURIComponent(route.params.id)}/logs`,
},
])
breadcrumbs.setName(
'Instance',
@@ -158,18 +277,8 @@ breadcrumbs.setContext({
query: route.query,
})
const offline = ref(!navigator.onLine)
window.addEventListener('offline', () => {
offline.value = true
})
window.addEventListener('online', () => {
offline.value = false
})
const loadingBar = useLoading()
const playing = ref(false)
const loading = ref(false)
const options = ref(null)
const startInstance = async (context) => {
@@ -189,32 +298,6 @@ const startInstance = async (context) => {
})
}
const checkProcess = async () => {
const runningProcesses = await get_by_profile_path(route.params.id).catch(handleError)
playing.value = runningProcesses.length > 0
}
// Get information on associated modrinth versions, if any
const modrinthVersions = ref([])
if (!offline.value && instance.value.linked_data && instance.value.linked_data.project_id) {
get_project(instance.value.linked_data.project_id, 'must_revalidate')
.catch(handleError)
.then((project) => {
if (project && project.versions) {
get_version_many(project.versions, 'must_revalidate')
.catch(handleError)
.then((versions) => {
modrinthVersions.value = versions.sort(
(a, b) => dayjs(b.date_published) - dayjs(a.date_published),
)
})
}
})
}
await checkProcess()
const stopInstance = async (context) => {
playing.value = false
await kill(route.params.id).catch(handleError)
@@ -226,6 +309,10 @@ const stopInstance = async (context) => {
})
}
const repairInstance = async () => {
await finish_install(instance.value)
}
const handleRightClick = (event) => {
const baseOptions = [
{ name: 'add_content' },
@@ -278,9 +365,11 @@ const handleOptionsClick = async (args) => {
case 'open_folder':
await showProfileInFolder(instance.value.path)
break
case 'copy_path':
await navigator.clipboard.writeText(instance.value.path)
case 'copy_path': {
const fullPath = await get_full_path(instance.value.path)
await navigator.clipboard.writeText(fullPath)
break
}
}
}
@@ -297,7 +386,35 @@ const unlistenProfiles = await profile_listener(async (event) => {
})
const unlistenProcesses = await process_listener((e) => {
if (e.event === 'finished' && e.profile_path_id === route.params.id) playing.value = false
if (e.event === 'finished' && e.profile_path_id === route.params.id) {
playing.value = false
}
})
const icon = computed(() =>
instance.value.icon_path ? convertFileSrc(instance.value.icon_path) : null,
)
const settingsModal = ref()
const timePlayed = computed(() => {
return instance.value.recent_time_played + instance.value.submitted_time_played
})
const timePlayedHumanized = computed(() => {
const duration = dayjs.duration(timePlayed.value, 'seconds')
const hours = Math.floor(duration.asHours())
if (hours >= 1) {
return hours + ' hour' + (hours > 1 ? 's' : '')
}
const minutes = Math.floor(duration.asMinutes())
if (minutes >= 1) {
return minutes + ' minute' + (minutes > 1 ? 's' : '')
}
const seconds = Math.floor(duration.asSeconds())
return seconds + ' second' + (seconds > 1 ? 's' : '')
})
onUnmounted(() => {
@@ -311,7 +428,6 @@ onUnmounted(() => {
display: flex;
flex-direction: column;
gap: 1rem;
width: 17rem;
}
Button {
@@ -325,12 +441,13 @@ Button {
}
.side-cards {
position: absolute;
position: fixed;
width: 300px;
display: flex;
flex-direction: column;
padding: 1rem;
min-height: calc(100% - 3.25rem);
max-height: calc(100% - 3.25rem);
min-height: calc(100vh - 3.25rem);
max-height: calc(100vh - 3.25rem);
overflow-y: auto;
-ms-overflow-style: none;
scrollbar-width: none;
@@ -374,10 +491,7 @@ Button {
overflow: auto;
gap: 1rem;
min-height: 100%;
}
.content {
margin-left: 19rem;
padding: 1rem;
}
.instance-info {
@@ -451,10 +565,10 @@ Button {
}
.content {
width: 100%;
margin: 0 1rem 0.5rem 20rem;
width: calc(100% - 20rem);
display: flex;
flex-direction: column;
padding: 1rem 1rem 0 0;
overflow: auto;
}
@@ -76,7 +76,7 @@
</div>
</RecycleScroller>
</div>
<ShareModal
<ShareModalWrapper
ref="shareModal"
header="Share Log"
share-title="Instance Log"
@@ -89,7 +89,7 @@
<script setup>
import { CheckIcon, ClipboardCopyIcon, ShareIcon, TrashIcon } from '@modrinth/assets'
import { Button, Card, ShareModal, Checkbox, DropdownSelect } from '@modrinth/ui'
import { Button, Card, Checkbox, DropdownSelect } from '@modrinth/ui'
import {
delete_logs_by_filename,
get_logs,
@@ -107,6 +107,7 @@ import { handleError } from '@/store/notifications.js'
import { ofetch } from 'ofetch'
import { RecycleScroller } from 'vue-virtual-scroller'
import 'vue-virtual-scroller/dist/vue-virtual-scroller.css'
import ShareModalWrapper from '@/components/ui/modal/ShareModalWrapper.vue'
dayjs.extend(isToday)
dayjs.extend(isYesterday)
@@ -295,7 +296,7 @@ if (logs.value.length > 1 && !props.playing) {
const deleteLog = async () => {
if (logs.value[selectedLogIndex.value] && selectedLogIndex.value !== 0) {
let deleteIndex = selectedLogIndex.value
const deleteIndex = selectedLogIndex.value
selectedLogIndex.value = deleteIndex - 1
await delete_logs_by_filename(
props.instance.path,
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -1,6 +1,5 @@
import Index from './Index.vue'
import Mods from './Mods.vue'
import Options from './Options.vue'
import Logs from './Logs.vue'
export { Index, Mods, Options, Logs }
export { Index, Mods, Logs }
@@ -0,0 +1,17 @@
<script setup>
import GridDisplay from '@/components/GridDisplay.vue'
defineProps({
instances: {
type: Array,
required: true,
},
})
</script>
<template>
<GridDisplay
v-if="instances.length > 0"
label="Instances"
:instances="instances.filter((i) => !i.linked_data)"
/>
</template>
@@ -0,0 +1,17 @@
<script setup>
import GridDisplay from '@/components/GridDisplay.vue'
defineProps({
instances: {
type: Array,
required: true,
},
})
</script>
<template>
<GridDisplay
v-if="instances.length > 0"
label="Instances"
:instances="instances.filter((i) => i.linked_data)"
/>
</template>
@@ -1,15 +1,15 @@
<script setup>
import { onUnmounted, ref, shallowRef } from 'vue'
import GridDisplay from '@/components/GridDisplay.vue'
import { list } from '@/helpers/profile.js'
import { useRoute } from 'vue-router'
import { useBreadcrumbs } from '@/store/breadcrumbs'
import { useBreadcrumbs } from '@/store/breadcrumbs.js'
import { profile_listener } from '@/helpers/events.js'
import { handleError } from '@/store/notifications.js'
import { Button } from '@modrinth/ui'
import { PlusIcon } from '@modrinth/assets'
import InstanceCreationModal from '@/components/ui/InstanceCreationModal.vue'
import { NewInstanceImage } from '@/assets/icons'
import NavTabs from '@/components/ui/NavTabs.vue'
const route = useRoute()
const breadcrumbs = useBreadcrumbs()
@@ -35,17 +35,31 @@ onUnmounted(() => {
</script>
<template>
<GridDisplay v-if="instances.length > 0" label="Instances" :instances="instances" />
<div v-else class="no-instance">
<div class="icon">
<NewInstanceImage />
<div class="p-6 flex flex-col gap-3">
<h1 class="m-0 text-2xl hidden">Library</h1>
<NavTabs
:links="[
{ label: 'All instances', href: `/library` },
{ label: 'Downloaded', href: `/library/downloaded` },
{ label: 'Custom', href: `/library/custom` },
{ label: 'Shared with me', href: `/library/shared`, shown: false },
{ label: 'Saved', href: `/library/saved`, shown: false },
]"
/>
<template v-if="instances.length > 0">
<RouterView :instances="instances" />
</template>
<div v-else class="no-instance">
<div class="icon">
<NewInstanceImage />
</div>
<h3>No instances found</h3>
<Button color="primary" :disabled="offline" @click="$refs.installationModal.show()">
<PlusIcon />
Create new instance
</Button>
<InstanceCreationModal ref="installationModal" />
</div>
<h3>No instances found</h3>
<Button color="primary" :disabled="offline" @click="$refs.installationModal.show()">
<PlusIcon />
Create new instance
</Button>
<InstanceCreationModal ref="installationModal" />
</div>
</template>
@@ -0,0 +1,13 @@
<script setup>
import GridDisplay from '@/components/GridDisplay.vue'
defineProps({
instances: {
type: Array,
required: true,
},
})
</script>
<template>
<GridDisplay v-if="instances.length > 0" label="Instances" :instances="instances" />
</template>

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