Compare commits

...
25 Commits
Author SHA1 Message Date
Prospector a9de420d57 update date + changelog 2026-08-13 11:09:05 -07:00
Prospector ab7359a4c5 minor fixes to disclosures, basic markdown support (#7133) 2026-08-13 10:58:47 -07:00
Truman GaoandCalum H. 97b5138672 fix: modpack export (#7119)
* fix: stale function call set_webview_visible

* fix: modpack export progress bar and add notification when done

* remove: force_no_zip64

* feat: switch to zip crate

* add typo exception

* fix: remove unit test (dont do)

---------

Signed-off-by: Truman Gao <106889354+tdgao@users.noreply.github.com>
Co-authored-by: Calum H. (IMB11) <contact@cal.engineer>
2026-08-13 16:37:39 +00:00
Calum H. 579109a478 refactor: app event bus (#6985)
* refactor: app event bus

* fix: use messagepack + cleanup

* fix: use postcard

* fix: lint

* fix: import gate

* fix: prettierignore + lint

* fix: lint

* fix: rev
2026-08-13 16:33:06 +00:00
Prospector 5f7493491f changelog 2026-08-13 09:01:59 -07:00
755825b09a project disclosures frontend (#6955)
* begin project disclosures

* new project settings header

* begin disclosure settings, edit content rules

* togglecards

* update phrasing of the rules, prepr

* more disclosure settings structuring

* add functionality toggle

* implement functionality with staging api

* improve project settings head titles

* feat(labrinth): project disclosures model

* feat(labrinth): project disclosures database model

* feat(labrinth): project disclosures get endpoint

* feat(labrinth): censor user ids if set by moderator

* feat(labrinth): wrap disclosures in struct

* feat(labrinth): edit project disclosures endpoint

* style(labrinth): cargo fmt

* style(labrinth): fix typo

* feat(labrinth): add fields for ai content disclosure

* fix(labrinth): field typo

* chore(labrinth): update query cache

* feat(labrinth): index disclosures in search

* refactor(labrinth): use enum instead of bools

* feat(labrinth): trigger incremental index on disclosure change

* feat(labrinth): change archived status to disclosure

* feat(labrinth): use disclosure for archival status

* fix(labrinth): error type for deserialization

* fix(labrinth): migration timestamp

* fix(labrinth): add disclosures to elasticsearch schema

* disclosures in search

* update photosensitivity warning copy

* update archiving, move general project settings to v3 (start redesign there), use project id lookups for stable cache keys, advanced in server search

* blue archive banner

* display AI use types

* improve labels

* add modrinth to link

* add placeholder missing disclosure report type

* add AI metadata checking to block image uploads with notice

* update copy

* update section 4 of rules

* Update rule 6 layout

* fix(labrinth): don't remove archival disclosure when changing status via v3 api

* feat(labrinth): derive str for ai usages and telemtry consent

* feat(labrinth): include ai usages and telemetry consent in disclosure types

* Update moderation checklist (#7056)

* fix: missing delphi severity (#7054)

* Utils nav, new disclosures stage, ai button in rules

* add prefix to collect

* Finish up new disclosures stage

* update r4 messages

* new r4 msg, update showcase clarity message

* fix: version upload failing to detect mrpack loader (#7063)

* fix: mrpack exporting with zip64 (#7064)

* fix: action bar max width (#7048)

* fix: action bar max width

* fix: width

* changelog

* Rule placeholders with subsections & anchors, update messages.

* update new messages to account for new rule and placeholder layout

* Add nag for content disclosures

---------

Co-authored-by: ThatGravyBoat <gravy@thatgravyboat.tech>
Co-authored-by: chyzman <chyzalt@gmail.com>
Co-authored-by: Truman Gao <106889354+tdgao@users.noreply.github.com>
Co-authored-by: Prospector <6166773+Prospector@users.noreply.github.com>

* prepr

* refactor(labrinth): move disclosure parsing to document creation

* fix(labrinth): prevent removing moderator added archival disclosures

* fix(labrinth): dedupe ai usages

* fix(labrinth): auth logic on archived disclosure removal

* feat(labrinth): add interactions field

* style(labrinth): cargo fmt

* feat(labrinth): granular disclosure locking

* add 5.8, move 5.9, update messages and placeholders accordingly.

* move disclosures stage earlier

* include consent model info in telemetry disclosure msgs

* prepr + update date

* typo

* qa pass

* search suboptions

* add empty state

* checkboxes in column

* persistent advanced filters

* blog draft

* prepr

* support soft deletion

* reload instead of optimistically updating

* update blog

* TOS update + minor verbiage adjustment

* 45 day grace

---------

Co-authored-by: sychic <47618543+Sychic@users.noreply.github.com>
Co-authored-by: coolbot <76798835+coolbot100s@users.noreply.github.com>
Co-authored-by: ThatGravyBoat <gravy@thatgravyboat.tech>
Co-authored-by: chyzman <chyzalt@gmail.com>
Co-authored-by: Truman Gao <106889354+tdgao@users.noreply.github.com>
2026-08-13 09:01:18 -07:00
Calum H. 06b7c44dcc refactor: content tab styling (#7091)
* refactor: managed content card

* fix: update filter pill styling to be chips-like

* fix: modpack exporting

* fixed: vanilla server projs + header

* refactor: filters

* refactor: detect external file icons + other metadata inside

* refactor: support json5 metadata

* fix: qa

* fix: qa

* fix: fmt + prepr

* fix: fmt

* fix: qa

* fix: headers

* feat: locking

* fix: clippy

* fix: qa

* fix: frozen loc

* fix: shared instances diff calc break

* fix: env filter + remove categories
2026-08-13 14:55:35 +00:00
Sychic e087e571f3 fix(labrinth): disclosure modification attribution (#7121) 2026-08-13 14:51:22 +00:00
Calum H. fbdde8f3d8 feat: update required empty state for modrinth hosting (#7125) 2026-08-13 14:46:10 +00:00
Sychic 290bd2b934 chore: more idea sources (#7122) 2026-08-13 02:41:31 +02:00
Sychic 9ecbe8eaa6 feat(labrinth): granular disclosure locking (#7108)
* feat(labrinth): granular disclosure locking

* chore(labrinth): update query cache
2026-08-12 17:26:15 -07:00
Sychic 549b333eb2 feat(labrinth): project disclosures (#6945)
* feat(labrinth): project disclosures model

* feat(labrinth): project disclosures database model

* feat(labrinth): project disclosures get endpoint

* feat(labrinth): censor user ids if set by moderator

* feat(labrinth): wrap disclosures in struct

* feat(labrinth): edit project disclosures endpoint

* style(labrinth): cargo fmt

* style(labrinth): fix typo

* feat(labrinth): add fields for ai content disclosure

* fix(labrinth): field typo

* chore(labrinth): update query cache

* feat(labrinth): index disclosures in search

* refactor(labrinth): use enum instead of bools

* feat(labrinth): trigger incremental index on disclosure change

* feat(labrinth): change archived status to disclosure

* feat(labrinth): use disclosure for archival status

* fix(labrinth): error type for deserialization

* fix(labrinth): migration timestamp

* fix(labrinth): add disclosures to elasticsearch schema

* fix(labrinth): don't remove archival disclosure when changing status via v3 api

* feat(labrinth): derive str for ai usages and telemtry consent

* feat(labrinth): include ai usages and telemetry consent in disclosure types

* refactor(labrinth): move disclosure parsing to document creation

* fix(labrinth): prevent removing moderator added archival disclosures

* fix(labrinth): dedupe ai usages

* fix(labrinth): auth logic on archived disclosure removal

* feat(labrinth): add interactions field

* style(labrinth): cargo fmt

* feat(labrinth): soft delete disclosures

* feat(labrinth): return soft-deleted disclosures

* fix(labrinth): update disclosure on removal

---------

Co-authored-by: Prospector <6166773+Prospector@users.noreply.github.com>
2026-08-12 17:26:14 -07:00
Prospector 4b82cca3bf fix sidebar not showing on user pages (#7097)
* fix sidebar not showing on user pages

* prepr
2026-08-12 17:25:26 -07:00
Prospector 734fd9cdb3 fix app build on windows & mac (#7118) 2026-08-12 17:25:15 -07:00
Prospector 2fd4495104 rejected projects go to ghost on delete (#7107)
* draft: rejected projects go to ghost on delete

* don't free slug

* fix sqlx

* free slugs but different

* add auto reject for processing items given to ghost, as well as send thread messages with a little info on who it came from
2026-08-12 23:54:57 +00:00
François-Xavier Talbot 552bc3f739 fix(labrinth): webhook scopes (#7114)
fix(labrinth): scope paypal & tremendous webhooks in payout scope
2026-08-12 18:34:33 +02:00
Truman Gao 4758cabbf9 feat: add hide and show download items (#7106)
* feat: add hide and show download items

* pnpm prepr
2026-08-12 16:18:02 +00:00
aecsocket 6ec979a076 fix: hide ad webview when showing fullscreen content (#7110)
* fix: hide ad webview when showing fullscreen content

* fixes
2026-08-12 16:02:02 +00:00
aecsocket c34bd26f87 fix: search indexing and schema changes (#7090)
* fix: incremental indexing uses latest db data

* dependent search fields
2026-08-12 11:54:35 -04:00
aecsocket 2a43792fd9 fix: app pinging servers over IPv6 (#7100)
fix: app pinging servers opver IPv6
2026-08-11 12:58:59 -04:00
Calum H. 2d567b93ce feat: imgur proxying for blocked countries (#7104) 2026-08-11 16:40:03 +00:00
Prospector 26e05ee9e5 update API docs to add environment fields (#7096)
* update API docs to add environment fields

* fix unknown on create/patch routes
2026-08-10 14:30:00 -07:00
jade ca8c5a717f fix: unique preview toggle ID in MarkdownEditor (#7093)
fix: preview toggle in MD editor targets other MD editors
2026-08-10 18:47:33 +00:00
Prospector 759f67a551 changelog 2026-08-10 11:17:45 -07:00
Prospector 66628fd69d add reddit and youtube as socials (#7092) 2026-08-10 11:17:13 -07:00
469 changed files with 15602 additions and 6515 deletions
+8
View File
@@ -59,6 +59,14 @@ apps/frontend/src/generated
.turbo
target
generated
!apps/app-frontend/src/generated/
!apps/app-frontend/src/generated/app-events/
!apps/app-frontend/src/generated/app-events/*.ts
!apps/app-frontend/src/generated/app-events/README.md
!apps/app-frontend/src/generated/app-events/postcard/
!apps/app-frontend/src/generated/app-events/postcard/index.d.ts
!apps/app-frontend/src/generated/app-events/postcard/index.js
!apps/app-frontend/src/generated/app-events/postcard/package.json
.env
# app testing dir
+11
View File
@@ -17,6 +17,17 @@
<sourceFolder url="file://$MODULE_DIR$/packages/modrinth-maxmind/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/packages/modrinth-util/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/packages/muralpay/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/packages/async-minecraft-ping/examples" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/packages/async-minecraft-ping/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/packages/async-minecraft-ping/tests" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/packages/labrinth-derive/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/packages/modrinth-content-management/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/packages/modrinth-content-management/tests" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/packages/neverbounce/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/packages/serde-binhum/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/packages/sqlx-tracing/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/packages/sqlx-tracing/tests" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/packages/xredis/src" isTestSource="false" />
<excludeFolder url="file://$MODULE_DIR$/target" />
</content>
<orderEntry type="inheritedJdk" />
Generated
+190 -2
View File
@@ -2116,7 +2116,7 @@ checksum = "ff6669899e23cb87b43daf7996f0ea3b9c07d0fb933d745bb7b815b052515ae3"
dependencies = [
"proc-macro2",
"quote",
"serde_derive_internals",
"serde_derive_internals 0.29.1",
"syn 2.0.106",
]
@@ -2340,6 +2340,15 @@ dependencies = [
"unicode-segmentation",
]
[[package]]
name = "convert_case"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "affbf0190ed2caf063e3def54ff444b449371d55c58e513a95ab98eca50adb49"
dependencies = [
"unicode-segmentation",
]
[[package]]
name = "cookie"
version = "0.16.2"
@@ -3991,6 +4000,28 @@ dependencies = [
"x11",
]
[[package]]
name = "genco"
version = "0.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77ab846431e5d637791b3279e7242fe2b21e11c3d8b4cf6a99f645c5f16ba7c0"
dependencies = [
"genco-macros",
"relative-path",
"smallvec",
]
[[package]]
name = "genco-macros"
version = "0.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c42a1fe5a699c7f1d36ea6e04ed680a5c787cabff4b610ae3b8954ea3bcefec1"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.106",
]
[[package]]
name = "generic-array"
version = "0.14.9"
@@ -5347,6 +5378,16 @@ dependencies = [
"thiserror 1.0.69",
]
[[package]]
name = "json5"
version = "1.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "733a844dbd6fef128e98cb4487b887cb55454d92cd9994b1bafe004fabbe670c"
dependencies = [
"serde",
"ucd-trie",
]
[[package]]
name = "jsonptr"
version = "0.6.3"
@@ -7499,6 +7540,45 @@ dependencies = [
"serde",
]
[[package]]
name = "postcard-bindgen"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e709af61573ae2563a00e760ff7db47f5c70b9d96c6dcb28993526fb8479f68"
dependencies = [
"postcard-bindgen-core",
"postcard-bindgen-derive",
]
[[package]]
name = "postcard-bindgen-core"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f19cebcb145fdccb24b63ad410785aa7c2ff9364551646ca7a927422d2d0386"
dependencies = [
"convert_case 0.11.0",
"genco",
"tree-ds",
]
[[package]]
name = "postcard-bindgen-derive"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0372f55dd7c15d73f7b0f7ca507ac3db5653f346b2b44a36537e2ad39f55441b"
dependencies = [
"convert_case 0.11.0",
"genco",
"postcard-bindgen-core",
"proc-macro2",
"quote",
"regex",
"regex-macro",
"serde",
"serde_derive_internals 0.30.0",
"syn 3.0.3",
]
[[package]]
name = "potential_utf"
version = "0.1.3"
@@ -8381,12 +8461,27 @@ version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d942b98df5e658f56f20d592c7f868833fe38115e65c33003d8cd224b0155da"
[[package]]
name = "regex-macro"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d306632607af6ec61c0b117971d57a96381b6317cf18ae419b5558048fe016e"
dependencies = [
"regex",
]
[[package]]
name = "regex-syntax"
version = "0.8.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58"
[[package]]
name = "relative-path"
version = "1.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba39f3699c378cd8970968dcbff9c43159ea4cfbd88d43c00b22f2ef10a435d2"
[[package]]
name = "rend"
version = "0.4.2"
@@ -9052,7 +9147,7 @@ checksum = "32e265784ad618884abaea0600a9adf15393368d840e0222d101a072f3f7534d"
dependencies = [
"proc-macro2",
"quote",
"serde_derive_internals",
"serde_derive_internals 0.29.1",
"syn 2.0.106",
]
@@ -9295,6 +9390,16 @@ dependencies = [
"uuid 1.23.3",
]
[[package]]
name = "sequential_gen"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d630b1418311e028ceb45e1e567845501a9d9c770a258b80d2edc025ffad17c"
dependencies = [
"lazy_static",
"uuid 1.23.3",
]
[[package]]
name = "serde"
version = "1.0.228"
@@ -9400,6 +9505,17 @@ dependencies = [
"syn 2.0.106",
]
[[package]]
name = "serde_derive_internals"
version = "0.30.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f852137cce035d6a4df67ccce505ff6b3e9fd3a10e3e52b24dc71e650bb1a9bd"
dependencies = [
"proc-macro2",
"quote",
"syn 3.0.3",
]
[[package]]
name = "serde_ini"
version = "0.2.0"
@@ -9840,6 +9956,9 @@ name = "spin"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d5fe4ccb98d9c292d56fec89a5e07da7fc4cf0dc11e156b41793132775d3e591"
dependencies = [
"lock_api",
]
[[package]]
name = "spinning_top"
@@ -10267,6 +10386,17 @@ dependencies = [
"unicode-ident",
]
[[package]]
name = "syn"
version = "3.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "sync_wrapper"
version = "1.0.2"
@@ -10880,6 +11010,15 @@ dependencies = [
"winapi",
]
[[package]]
name = "termcolor"
version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755"
dependencies = [
"winapi-util",
]
[[package]]
name = "testcontainers"
version = "0.25.2"
@@ -10959,6 +11098,7 @@ dependencies = [
"image",
"indicatif",
"itertools 0.14.0",
"json5",
"modrinth-content-management",
"notify",
"notify-debouncer-mini",
@@ -10968,6 +11108,8 @@ dependencies = [
"path-util",
"phf 0.13.1",
"png 0.18.0",
"postcard",
"postcard-bindgen",
"quartz_nbt",
"quick-xml 0.38.3",
"rand 0.8.5",
@@ -10975,6 +11117,7 @@ dependencies = [
"reqwest 0.12.24",
"rgb",
"serde",
"serde-binhum",
"serde_ini",
"serde_json",
"serde_with",
@@ -10988,9 +11131,11 @@ dependencies = [
"thiserror 2.0.17",
"tokio",
"tokio-util",
"toml 0.9.8",
"tracing",
"tracing-error",
"tracing-subscriber",
"ts-rs",
"url",
"urlencoding",
"uuid 1.23.3",
@@ -11667,12 +11812,49 @@ dependencies = [
"windows-sys 0.59.0",
]
[[package]]
name = "tree-ds"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "710c388a2bb52d1a7186ae4aa2e92c6fae0606426bf4bdfb4106fa1e8e5c602e"
dependencies = [
"lazy_static",
"sequential_gen",
"serde",
"spin 0.10.0",
"thiserror 2.0.17",
]
[[package]]
name = "try-lock"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
[[package]]
name = "ts-rs"
version = "12.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "756050066659291d47a554a9f558125db17428b073c5ffce1daf5dcb0f7231d8"
dependencies = [
"chrono",
"thiserror 2.0.17",
"ts-rs-macros",
"uuid 1.23.3",
]
[[package]]
name = "ts-rs-macros"
version = "12.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38d90eea51bc7988ef9e674bf80a85ba6804739e535e9cab48e4bb34a8b652aa"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.106",
"termcolor",
]
[[package]]
name = "tungstenite"
version = "0.27.0"
@@ -11710,6 +11892,12 @@ version = "1.20.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20"
[[package]]
name = "ucd-trie"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971"
[[package]]
name = "uds_windows"
version = "1.1.0"
+4
View File
@@ -113,6 +113,7 @@ indicatif = "0.18.0"
itertools = "0.14.0"
jemalloc_pprof = "0.8.1"
json-patch = { version = "4.1.0", default-features = false }
json5 = "1.3.1"
lettre = { version = "0.11.19", default-features = false, features = [
"aws-lc-rs",
"builder",
@@ -148,6 +149,7 @@ path-util = { path = "packages/path-util" }
phf = { version = "0.13.1", features = ["macros"] }
png = "0.18.0"
postcard = { version = "1.1.3", default-features = false, features = ["alloc"] }
postcard-bindgen = "0.8.0"
proc-macro2 = { version = "1.0" }
prometheus = "0.14.0"
quartz_nbt = "0.2.9"
@@ -224,12 +226,14 @@ tikv-jemallocator = "0.6.0"
tokio = "1.47.1"
tokio-stream = "0.1.17"
tokio-util = "0.7.16"
toml = "0.9.8"
totp-rs = "5.7.0"
tracing = "0.1.41"
tracing-actix-web = { version = "0.7.19", default-features = false }
tracing-ecs = "0.5.0"
tracing-error = "0.2.1"
tracing-subscriber = "0.3.20"
ts-rs = "12.0.1"
typed-path = "0.12.0"
url = "2.5.7"
urlencoding = "2.1.3"
+4
View File
@@ -24,3 +24,7 @@ gam = "gam"
consts = "consts"
# short for "Copy"
Cpy = "Cpy"
[default.extend-identifiers]
# Constant from the `zip` crate
ZIP64_BYTES_THR = "ZIP64_BYTES_THR"
+4
View File
@@ -2,3 +2,7 @@
*.gltf
src/locales/
src/assets/**/*.svg
# Generated app-event bindings
src/generated/app-events/*.ts
src/generated/app-events/postcard/**
+6 -1
View File
@@ -1,2 +1,7 @@
import config from '@modrinth/tooling-config/eslint/nuxt.mjs'
export default config
export default config.append([
{
ignores: ['src/generated/app-events/*.ts', 'src/generated/app-events/postcard/**'],
},
])
+72 -25
View File
@@ -71,6 +71,7 @@ import AppActionBar from '@/components/ui/AppActionBar.vue'
import Breadcrumbs from '@/components/ui/Breadcrumbs.vue'
import ErrorModal from '@/components/ui/ErrorModal.vue'
import FriendsList from '@/components/ui/friends/FriendsList.vue'
import HostingUpdateRequired from '@/components/ui/HostingUpdateRequired.vue'
import AddServerToInstanceModal from '@/components/ui/install_flow/AddServerToInstanceModal.vue'
import UnknownPackWarningModal from '@/components/ui/install_flow/UnknownPackWarningModal.vue'
import MinecraftAuthErrorModal from '@/components/ui/minecraft-auth-error-modal/MinecraftAuthErrorModal.vue'
@@ -89,19 +90,19 @@ import SplashScreen from '@/components/ui/SplashScreen.vue'
import SurveyPopup from '@/components/ui/SurveyPopup.vue'
import WindowControls from '@/components/ui/WindowControls.vue'
import { useCheckDisableMouseover } from '@/composables/macCssFix.js'
import { useAppEvent } from '@/composables/use-app-event'
import { config } from '@/config'
import {
ads_consent_listener,
hide_ads_window,
init_ads_window,
perform_ads_consent_action,
release_ads_window_hold,
should_show_ads_consent_popup,
show_ads_window,
take_ads_window_hold,
} from '@/helpers/ads.js'
import { debugAnalytics, initAnalytics, trackEvent } from '@/helpers/analytics'
import { check_reachable } from '@/helpers/auth.js'
import { get_user, get_version } from '@/helpers/cache.js'
import { command_listener, notification_listener, warning_listener } from '@/helpers/events.js'
import { install_create_modpack_instance, install_get_modpack_preview } from '@/helpers/install'
import { can_current_user_use_shared_instances, get as getInstance, run } from '@/helpers/instance'
import { get as getCreds, login, logout } from '@/helpers/mr_auth.ts'
@@ -140,6 +141,7 @@ import {
} from '@/providers/download-progress.ts'
import { createServerInstall, provideServerInstall } from '@/providers/server-install'
import { setupProviders } from '@/providers/setup'
import { setupAppEventsProvider } from '@/providers/setup/app-events'
import { setupAuthProvider } from '@/providers/setup/auth'
import { setupLoadingStateProvider } from '@/providers/setup/loading-state'
import { useError } from '@/store/error.js'
@@ -155,6 +157,7 @@ import { appSettingsModalOpenProfileKey } from './providers/app-settings-modal'
const themeStore = useTheming()
const router = useRouter()
const route = useRoute()
const { channel: appEventChannel, events: appEvents } = setupAppEventsProvider()
const breadcrumbManager = createBreadcrumbManager()
provideBreadcrumbManager(breadcrumbManager)
const canNavigateBack = ref(false)
@@ -166,6 +169,25 @@ function updateHistoryNavigationState() {
canNavigateForward.value = historyState?.forward != null
}
let fullscreenAdsWindowHold = false
async function handleFullscreenChange() {
const fullscreen = document.fullscreenElement !== null
if (fullscreen === fullscreenAdsWindowHold) return
fullscreenAdsWindowHold = fullscreen
try {
if (fullscreen) {
await take_ads_window_hold()
} else {
await release_ads_window_hold()
}
} catch (error) {
fullscreenAdsWindowHold = !fullscreen
handleError(error)
}
}
updateHistoryNavigationState()
const APP_LEFT_NAV_WIDTH = '4rem'
@@ -182,10 +204,19 @@ watch(
},
)
const forceSidebar = computed(
() => route.path.startsWith('/browse') || route.path.startsWith('/project'),
() =>
route.path.startsWith('/browse') ||
route.path.startsWith('/project') ||
route.path.startsWith('/user'),
)
const sidebarVisible = computed(() => sidebarToggled.value || forceSidebar.value)
const hostingRouteActive = computed(() => route.path.startsWith('/hosting'))
const hostingUpdateRequired = computed(
() =>
hostingRouteActive.value &&
!!appUpdateState.availableUpdate.value &&
appUpdateState.updatesEnabled.value,
)
const prideFundraiserEnabled = computed(
() => themeStore.getFeatureFlag('pride_fundraiser') && Date.now() < PRIDE_FUNDRAISER_END_DATE,
)
@@ -196,7 +227,9 @@ const hostingIntercomIdentityKey = computed(() => {
return `${userId}:${serverId ?? 'hosting'}`
})
const hostingIntercom = useHostingIntercom({
enabled: computed(() => hostingRouteActive.value && !!credentials.value?.session),
enabled: computed(
() => hostingRouteActive.value && !hostingUpdateRequired.value && !!credentials.value?.session,
),
appId: 'ykeritl9',
fetchToken: fetchIntercomToken,
identityKey: hostingIntercomIdentityKey,
@@ -211,11 +244,22 @@ const notificationManager = new AppNotificationManager()
provideNotificationManager(notificationManager)
const { handleError, addNotification } = notificationManager
useAppEvent(
'warning',
(event) =>
addNotification({
title: 'Warning',
text: event.message,
type: 'warning',
}),
appEvents,
)
const popupNotificationManager = new AppPopupNotificationManager()
providePopupNotificationManager(popupNotificationManager)
const { addPopupNotification } = popupNotificationManager
let adsConsentPopupId = null
let unlistenAdsConsent
useAppEvent('ads_consent_required', handleAdsConsentRequired, appEvents)
const appVersion = getVersion()
const tauriApiClient = new TauriModrinthClient({
@@ -284,8 +328,8 @@ providePageContext({
})
provideModalBehavior({
noblur: computed(() => !themeStore.advancedRendering),
onShow: () => hide_ads_window(),
onHide: () => show_ads_window(),
onShow: () => take_ads_window_hold(),
onHide: () => release_ads_window_hold(),
})
const {
@@ -300,7 +344,7 @@ const {
setModpackAlreadyInstalledModal,
handleModpackDuplicateCreateAnyway,
handleModpackDuplicateGoToInstance,
} = setupProviders(notificationManager, popupNotificationManager)
} = setupProviders(tauriApiClient, notificationManager, popupNotificationManager)
const news = ref([])
const displayedServerInviteNotifications = new Set()
@@ -352,7 +396,6 @@ const authUnreachable = computed(() => {
onMounted(async () => {
await useCheckDisableMouseover()
try {
unlistenAdsConsent = await ads_consent_listener(handleAdsConsentRequired)
handleAdsConsentRequired(await should_show_ads_consent_popup())
} catch (error) {
handleError(error)
@@ -360,6 +403,7 @@ onMounted(async () => {
document.querySelector('body').addEventListener('click', handleClick)
document.querySelector('body').addEventListener('auxclick', handleAuxClick)
document.addEventListener('fullscreenchange', handleFullscreenChange)
checkUpdates()
})
@@ -367,9 +411,13 @@ onMounted(async () => {
onUnmounted(async () => {
document.querySelector('body').removeEventListener('click', handleClick)
document.querySelector('body').removeEventListener('auxclick', handleAuxClick)
document.removeEventListener('fullscreenchange', handleFullscreenChange)
clearDelayedUpdatePopup()
await unlistenAdsConsent?.()
if (fullscreenAdsWindowHold) {
fullscreenAdsWindowHold = false
await release_ads_window_hold().catch(handleError)
}
await unlistenUpdateDownload?.()
})
@@ -578,14 +626,6 @@ async function setupApp() {
document.getElementsByTagName('html')[0].classList.add('windows')
}
await warning_listener((e) =>
addNotification({
title: 'Warning',
text: e.message,
type: 'warning',
}),
)
fetch(`https://api.modrinth.com/appCriticalAnnouncement.json?version=${version}`)
.then((response) => response.json())
.then((res) => {
@@ -634,7 +674,7 @@ async function setupApp() {
}
const stateFailed = ref(false)
initialize_state()
initialize_state(appEventChannel)
.then(() => {
setupApp().catch((err) => {
stateFailed.value = true
@@ -764,7 +804,7 @@ const errorModal = ref()
const minecraftAuthErrorModal = ref()
const minecraftRequiredModal = ref()
const contentInstall = createContentInstall({ router, handleError })
const contentInstall = createContentInstall({ router, handleError, appEvents })
provideContentInstall(contentInstall)
const {
instances: contentInstallInstances,
@@ -797,7 +837,12 @@ const {
handleIncompatibilityWarningCancel: handleContentInstallIncompatibilityWarningCancel,
} = contentInstall
const serverInstall = createServerInstall({ router, handleError, popupNotificationManager })
const serverInstall = createServerInstall({
router,
handleError,
popupNotificationManager,
appEvents,
})
provideServerInstall(serverInstall)
const {
setInstallToPlayModal: setServerInstallToPlayModal,
@@ -973,8 +1018,8 @@ onMounted(() => {
const accounts = ref(null)
provide('accountsCard', accounts)
command_listener(handleCommand)
notification_listener(handleLiveNotification)
useAppEvent('command', handleCommand, appEvents)
useAppEvent('notification', handleLiveNotification, appEvents)
async function markLiveNotificationRead(notification) {
try {
@@ -1400,6 +1445,7 @@ async function downloadUpdate(versionToDownload) {
handleError(e)
})
unlistenUpdateDownload = await subscribeToDownloadProgress(
appEvents,
appUpdateDownload,
versionToDownload.version,
)
@@ -1758,7 +1804,8 @@ provideAppUpdateDownloadProgress(appUpdateDownload)
>
{{ formatMessage(messages.authUnreachableBody) }}
</Admonition>
<RouterView v-slot="{ Component }">
<HostingUpdateRequired v-if="hostingUpdateRequired" />
<RouterView v-else v-slot="{ Component }">
<template v-if="Component">
<Suspense @pending="onSuspensePending" @resolve="onSuspenseResolve">
<component :is="Component"></component>
@@ -104,8 +104,9 @@ import {
useVIntl,
} from '@modrinth/ui'
import type { Ref } from 'vue'
import { computed, onUnmounted, ref } from 'vue'
import { computed, ref } from 'vue'
import { useAppEvent } from '@/composables/use-app-event'
import { trackEvent } from '@/helpers/analytics'
import {
get_default_user,
@@ -114,7 +115,6 @@ import {
set_default_user,
users,
} from '@/helpers/auth'
import { process_listener } from '@/helpers/events'
import { getPlayerHeadUrl } from '@/helpers/rendering/batch-skin-renderer.ts'
import type { Skin } from '@/helpers/skins'
import { get_available_skins } from '@/helpers/skins'
@@ -251,16 +251,12 @@ async function logout(id: string) {
trackEvent('AccountLogOut')
}
const unlisten = await process_listener(async (e) => {
useAppEvent('process', async (e) => {
if (e.event === 'launched') {
await refreshValues()
}
})
onUnmounted(() => {
unlisten()
})
const messages = defineMessages({
notSignedIn: {
id: 'minecraft-account.not-signed-in',
@@ -1,15 +1,16 @@
<template>
<div class="flex gap-2 items-center">
<IconButton
v-if="hasActiveLoadingBars && !hasVisibleActiveDownloadToasts"
v-tooltip="formatMessage(messages.viewActiveDownloads)"
type="quiet"
color="brand"
:label="formatMessage(messages.viewActiveDownloads)"
@click="openDownloadToast()"
>
<DownloadIcon />
</IconButton>
<div v-if="downloadState.total > 0 || hasActiveLoadingBars" class="relative">
<IconButton
v-tooltip="downloadToggleLabel"
:color="downloadState.hidden > 0 ? 'brand' : undefined"
type="quiet"
:label="downloadToggleLabel"
@click="toggleDownloadNotifications"
>
<DownloadIcon />
</IconButton>
</div>
<div v-if="offline" class="flex items-center gap-1">
<UnplugIcon class="text-secondary" />
<span class="text-sm text-contrast"> {{ formatMessage(messages.offline) }} </span>
@@ -147,8 +148,8 @@ import { useRouter } from 'vue-router'
import AppUpdateButton from '@/components/ui/app-update-button/index.vue'
import { useInstallJobNotifications } from '@/composables/browse/install-job-notifications'
import { useAppEvent } from '@/composables/use-app-event'
import { trackEvent } from '@/helpers/analytics'
import { loading_listener, process_listener } from '@/helpers/events'
import { get_many as getInstances } from '@/helpers/instance'
import { get_all as getRunningProcesses, kill as killProcess } from '@/helpers/process'
import type { LoadingBar } from '@/helpers/state'
@@ -218,8 +219,35 @@ const messages = defineMessages({
id: 'app.action-bar.view-active-downloads',
defaultMessage: 'View active downloads',
},
hideDownloads: {
id: 'app.action-bar.hide-downloads',
defaultMessage: 'Hide active downloads',
},
showDownloads: {
id: 'app.action-bar.show-downloads',
defaultMessage: 'Show active downloads',
},
})
const downloadState = computed(() => popupNotificationManager.getDownloadState())
const downloadToggleLabel = computed(() =>
formatMessage(
downloadState.value.hidden > 0
? messages.showDownloads
: downloadState.value.total > 0
? messages.hideDownloads
: messages.viewActiveDownloads,
),
)
function toggleDownloadNotifications(): void {
if (downloadState.value.total > 0) {
popupNotificationManager.toggleDownloadNotifications()
} else if (hasActiveLoadingBars.value) {
openDownloadToast()
}
}
const currentProcesses = ref<RunningProcess[]>([])
const selectedProcess = ref<RunningProcess | undefined>()
@@ -266,7 +294,7 @@ onMounted(() => {
window.addEventListener('online', handleOnline)
})
const unlistenProcess = await process_listener(async () => {
useAppEvent('process', async () => {
await refresh()
})
@@ -296,6 +324,7 @@ function goToTerminal(instanceId?: string) {
const currentLoadingBars = ref<LoadingBar[]>([])
const currentLoadingBarIconUrls = ref<Record<string, string | null>>({})
const notificationId = ref<string | number | null>(null)
const terminalNotificationIds = new Map<string, string | number>()
const dismissed = ref(false)
function getLoadingBarKey(loadingBar: LoadingBar): string {
@@ -341,6 +370,44 @@ function removeNotification(): void {
notificationId.value = null
}
function syncTerminalNotifications(): void {
const terminalNotifications = installJobNotifications.terminalNotifications.value
const currentJobIds = new Set(terminalNotifications.map((notification) => notification.id))
for (const terminal of terminalNotifications) {
const popupId = terminalNotificationIds.get(terminal.id)
let notification = popupId
? popupNotificationManager.getNotifications().find((candidate) => candidate.id === popupId)
: undefined
if (!notification) {
notification = popupNotificationManager.addPopupNotification({
title: terminal.title,
text: terminal.text,
type: terminal.type,
buttons: terminal.buttons,
onDismiss: terminal.onDismiss,
autoCloseMs: null,
})
terminalNotificationIds.set(terminal.id, notification.id)
continue
}
notification.title = terminal.title
notification.text = terminal.text
notification.type = terminal.type
notification.buttons = terminal.buttons
notification.onDismiss = terminal.onDismiss
}
for (const [jobId, popupId] of terminalNotificationIds) {
if (!currentJobIds.has(jobId)) {
popupNotificationManager.removeNotification(popupId)
terminalNotificationIds.delete(jobId)
}
}
}
function buildDownloadItems(): PopupNotificationProgressItem[] {
return [
...installJobNotifications.progressItems.value,
@@ -358,12 +425,13 @@ function buildDownloadItems(): PopupNotificationProgressItem[] {
]
}
const hasVisibleActiveDownloadToasts = computed(() => !!getNotification())
const hasActiveLoadingBars = computed(
() => currentLoadingBars.value.length > 0 || installJobNotifications.active.value,
)
function updateNotification(resummon = false): void {
syncTerminalNotifications()
if (resummon) {
dismissed.value = false
}
@@ -392,7 +460,6 @@ function updateNotification(resummon = false): void {
: formatMessage(messages.downloads)
notif.text = undefined
notif.progressItems = progressItems
notif.buttons = installJobNotifications.buttons.value
notif.progress = undefined
notif.waiting = undefined
} else {
@@ -403,7 +470,6 @@ function updateNotification(resummon = false): void {
type: 'download',
autoCloseMs: null,
progressItems,
buttons: installJobNotifications.buttons.value,
})
notificationId.value = notif.id
}
@@ -488,7 +554,7 @@ const installJobNotifications = await useInstallJobNotifications({
await refreshLoadingBars()
const unlistenLoading = await loading_listener(async () => {
useAppEvent('loading', async () => {
await refreshLoadingBars()
})
@@ -502,11 +568,11 @@ function selectProcess(process: RunningProcess) {
onBeforeUnmount(() => {
removeNotification()
terminalNotificationIds.forEach((id) => popupNotificationManager.removeNotification(id))
terminalNotificationIds.clear()
dismissed.value = false
window.removeEventListener('offline', handleOffline)
window.removeEventListener('online', handleOnline)
unlistenProcess()
unlistenLoading()
installJobNotifications.dispose()
})
</script>
@@ -1,11 +1,12 @@
<script setup>
import { XIcon } from '@modrinth/assets'
import { FolderOpenIcon, XIcon } from '@modrinth/assets'
import {
Button,
commonMessages,
defineMessages,
FileTreeSelect,
injectNotificationManager,
injectPopupNotificationManager,
NewModal,
StyledInput,
useVIntl,
@@ -15,8 +16,10 @@ import { ref, shallowRef } from 'vue'
import { PackageIcon } from '@/assets/icons'
import { export_instance_mrpack, get_pack_export_candidates } from '@/helpers/instance'
import { highlightInFolder } from '@/helpers/utils'
const { handleError } = injectNotificationManager()
const popupNotificationManager = injectPopupNotificationManager()
const { formatMessage } = useVIntl()
const messages = defineMessages({
@@ -39,6 +42,14 @@ const messages = defineMessages({
defaultMessage: 'Enter modpack description...',
},
exportButton: { id: 'app.export-modal.export-button', defaultMessage: 'Export' },
exportComplete: {
id: 'app.export-modal.export-complete',
defaultMessage: 'Export complete',
},
exportCompleteDescription: {
id: 'app.export-modal.export-complete-description',
defaultMessage: '{name} was exported successfully.',
},
})
const props = defineProps({
@@ -93,16 +104,35 @@ const exportPack = async () => {
})
if (outputPath) {
export_instance_mrpack(
props.instance.id,
outputPath,
includedFilePaths.value,
excludedFilePaths.value,
versionInput.value,
exportDescription.value,
nameInput.value,
).catch((err) => handleError(err))
exportModal.value.hide()
try {
await export_instance_mrpack(
props.instance.id,
outputPath,
includedFilePaths.value,
excludedFilePaths.value,
versionInput.value,
exportDescription.value,
nameInput.value,
)
const fileName = outputPath.split(/[\\/]/).pop() ?? outputPath
popupNotificationManager.addPopupNotification({
title: formatMessage(messages.exportComplete),
text: formatMessage(messages.exportCompleteDescription, { name: fileName }),
type: 'success',
buttons: [
{
label: formatMessage(commonMessages.openInFolderButton),
icon: FolderOpenIcon,
action: () => highlightInFolder(outputPath).catch(handleError),
},
],
})
} catch (error) {
handleError(error)
}
}
}
@@ -0,0 +1,124 @@
<script setup lang="ts">
import { DownloadIcon, ExcitedRinthbot, RefreshCwIcon, ServerStackIcon } from '@modrinth/assets'
import { Button, commonMessages, defineMessages, useVIntl } from '@modrinth/ui'
import { computed } from 'vue'
import {
appUpdateState,
downloadAvailableAppUpdate,
installAvailableAppUpdate,
} from '@/providers/app-update'
import { useRootBreadcrumb } from '@/providers/breadcrumbs'
const { formatMessage } = useVIntl()
const messages = defineMessages({
title: {
id: 'app.hosting.update-required.title',
defaultMessage: 'Modrinth App update required',
},
description: {
id: 'app.hosting.update-required.description',
defaultMessage: 'You need to update to use Modrinth Hosting through the Modrinth App',
},
downloadToUpdate: {
id: 'app.hosting.update-required.download',
defaultMessage: 'Download to update',
},
downloadingUpdate: {
id: 'app.action-bar.downloading-update',
defaultMessage: 'Downloading update',
},
reloadToUpdate: {
id: 'app.action-bar.reload-to-update',
defaultMessage: 'Reload to update',
},
excitedRinthbotAlt: {
id: 'app.hosting.update-required.rinthbot-alt',
defaultMessage: 'Excited Modrinth Bot',
},
})
useRootBreadcrumb({
slot: 'root',
id: 'servers',
label: () => formatMessage(commonMessages.serversLabel),
to: '/hosting/manage/',
visual: { type: 'icon', component: ServerStackIcon },
})
const { downloading, downloadPercent, downloadProgress, finishedDownloading } = appUpdateState
const isUpdateDownloading = computed(
() =>
downloading.value ||
(downloadProgress.value > 0 && downloadProgress.value < 1 && !finishedDownloading.value),
)
async function handleUpdateClick() {
if (isUpdateDownloading.value) {
return
}
if (finishedDownloading.value) {
await installAvailableAppUpdate()
} else {
await downloadAvailableAppUpdate()
}
}
</script>
<template>
<div class="box-border flex min-h-full items-center justify-center p-4">
<div class="relative mx-auto w-full max-w-xl pt-28">
<img
:src="ExcitedRinthbot"
:alt="formatMessage(messages.excitedRinthbotAlt)"
class="absolute right-8 top-0 h-28 w-auto md:right-20"
/>
<div class="relative flex flex-col gap-5 rounded-lg bg-bg-raised p-7 shadow-lg">
<div
class="absolute left-0 top-0 h-px w-full bg-gradient-to-r from-transparent via-green-500 to-transparent opacity-40"
style="
background: linear-gradient(
to right,
transparent 2rem,
var(--color-green) calc(100% - 13rem),
var(--color-green) calc(100% - 5rem),
transparent calc(100% - 2rem)
);
"
></div>
<div class="flex flex-col gap-5">
<h1 class="m-0 text-3xl font-extrabold">
{{ formatMessage(messages.title) }}
</h1>
<p class="m-0 text-lg">
{{ formatMessage(messages.description) }}
</p>
<Button
type="colored"
color="brand"
:disabled="isUpdateDownloading"
:aria-busy="isUpdateDownloading"
@click="handleUpdateClick"
>
<RefreshCwIcon v-if="finishedDownloading" />
<DownloadIcon v-else />
<span v-if="isUpdateDownloading">
{{ formatMessage(messages.downloadingUpdate) }}
<span class="inline-block w-[3ch] text-right tabular-nums">
{{ downloadPercent }}%
</span>
</span>
<span v-else-if="finishedDownloading">
{{ formatMessage(messages.reloadToUpdate) }}
</span>
<span v-else>{{ formatMessage(messages.downloadToUpdate) }}</span>
</Button>
</div>
</div>
</div>
</div>
</template>
@@ -10,11 +10,11 @@ import {
import { Avatar, IconButton, injectNotificationManager, useRelativeTime } from '@modrinth/ui'
import { convertFileSrc } from '@tauri-apps/api/core'
import dayjs from 'dayjs'
import { computed, onMounted, onUnmounted, ref } from 'vue'
import { computed, onMounted, ref } from 'vue'
import { useRouter } from 'vue-router'
import { useAppEvent } from '@/composables/use-app-event'
import { trackEvent } from '@/helpers/analytics'
import { process_listener } from '@/helpers/events'
import { install_existing_instance, install_pack_to_existing_instance } from '@/helpers/install'
import { kill, run } from '@/helpers/instance'
import { get_by_instance_id } from '@/helpers/process'
@@ -136,7 +136,7 @@ defineExpose({
const currentEvent = ref(null)
const unlisten = await process_listener((e) => {
useAppEvent('process', (e) => {
if (e.instance_id === props.instance.id) {
currentEvent.value = e.event
if (e.event === 'finished') {
@@ -148,7 +148,6 @@ const unlisten = await process_listener((e) => {
onMounted(() => {
checkProcess()
})
onUnmounted(() => unlisten())
</script>
<template>
@@ -7,7 +7,7 @@ import dayjs from 'dayjs'
import { computed, onMounted, onUnmounted, ref } from 'vue'
import NavButton from '@/components/ui/NavButton.vue'
import { instance_listener } from '@/helpers/events.js'
import { useAppEvent } from '@/composables/use-app-event'
import { list } from '@/helpers/instance'
import { instanceKeys } from '@/pages/instance/query-options'
@@ -148,7 +148,7 @@ const getInstances = async () => {
await getInstances()
updateMaxAuto()
const unlistenInstance = await instance_listener(async (event) => {
useAppEvent('instance', async (event) => {
if (event.event !== 'synced') {
await getInstances()
}
@@ -162,7 +162,6 @@ onUnmounted(() => {
window.removeEventListener('resize', updateMaxAuto)
document.body.classList.remove('quick-instance-dragging')
clearOverdragFlash()
unlistenInstance()
})
const messages = defineMessages({
@@ -82,7 +82,7 @@ import { injectLoadingState } from '@modrinth/ui'
import { ref, watch } from 'vue'
import ProgressBar from '@/components/ui/ProgressBar.vue'
import { loading_listener } from '@/helpers/events.js'
import { useAppEvent } from '@/composables/use-app-event'
const doneLoading = ref(false)
const loadingProgress = ref(0)
@@ -132,13 +132,10 @@ function fakeLoadingIncrease() {
}
}
loading_listener(async (e) => {
useAppEvent('loading', (e) => {
if (e.event.type === 'directory_move') {
loadingProgress.value = 100 * (e.fraction ?? 1)
message.value = 'Updating app directory...'
} else if (e.event.type === 'checking_for_updates') {
loadingProgress.value = 100 * (e.fraction ?? 1)
message.value = 'Checking for updates...'
}
})
</script>
@@ -3,12 +3,14 @@ import { NotepadTextIcon, XIcon } from '@modrinth/assets'
import { Button, defineMessages, injectNotificationManager, useVIntl } from '@modrinth/ui'
import { type } from '@tauri-apps/plugin-os'
import { $fetch } from 'ofetch'
import { onMounted, ref } from 'vue'
import { onMounted, onUnmounted, ref } from 'vue'
import { hide_ads_window, show_ads_window } from '@/helpers/ads.js'
import { release_ads_window_hold, take_ads_window_hold } from '@/helpers/ads.js'
import { list } from '@/helpers/instance'
import { get as getCreds } from '@/helpers/mr_auth.ts'
let adsWindowHold = false
type Survey = {
id: string
tally_id: string
@@ -92,30 +94,44 @@ async function openSurvey() {
onOpen: () => console.info('Opened user survey'),
onClose: () => {
console.info('Closed user survey')
show_ads_window()
if (adsWindowHold) {
adsWindowHold = false
release_ads_window_hold()
}
},
onSubmit: () => console.info('Active user survey submitted'),
}
try {
hide_ads_window()
await take_ads_window_hold()
adsWindowHold = true
if (tallyWindow.Tally?.openPopup) {
console.info(`Opening Tally popup for user survey (form ID: ${formId})`)
dismissSurvey()
tallyWindow.Tally.openPopup(formId, popupOptions)
} else {
console.warn('Tally script not yet loaded')
show_ads_window()
adsWindowHold = false
await release_ads_window_hold()
}
} catch (e) {
console.error('Error opening Tally popup:', e)
show_ads_window()
if (adsWindowHold) {
adsWindowHold = false
await release_ads_window_hold()
}
}
console.info(`Found user survey to show with tally_id: ${formId}`)
tallyWindow.Tally?.openPopup(formId, popupOptions)
}
onUnmounted(() => {
if (adsWindowHold) {
adsWindowHold = false
release_ads_window_hold()
}
})
function dismissSurvey() {
if (!availableSurvey.value) return
localStorage.setItem(`survey-${availableSurvey.value.id}-display`, String(new Date()))
@@ -10,7 +10,6 @@ import {
import { Admonition, Button, ButtonLink, Collapsible, IconButton, NewModal } from '@modrinth/ui'
import { computed, ref } from 'vue'
import { hide_ads_window, show_ads_window } from '@/helpers/ads.js'
import { login as login_flow, set_default_user } from '@/helpers/auth.js'
import { handleSevereError } from '@/store/error.js'
@@ -29,19 +28,13 @@ function show(errorVal: { message?: string }) {
matchedError.value = findMinecraftAuthError(rawError.value)
debugCollapsed.value = true
hide_ads_window()
modal.value?.show()
}
function hide() {
onModalHide()
modal.value?.hide()
}
function onModalHide() {
show_ads_window()
}
defineExpose({
show,
hide,
@@ -74,7 +67,7 @@ async function copyToClipboard(text: string) {
</script>
<template>
<NewModal ref="modal" header="Sign in Failed" :max-width="'548px'" @hide="onModalHide">
<NewModal ref="modal" header="Sign in Failed" :max-width="'548px'">
<div class="flex flex-col gap-6">
<Admonition
type="warning"
@@ -3,7 +3,6 @@
ref="modal"
:header="formatMessage(messages.installToPlay)"
:closable="true"
:on-hide="show_ads_window"
max-width="544px"
width="544px"
>
@@ -141,10 +140,11 @@
</div>
</NewModal>
<ModpackContentModal
ref="modpackContentModal"
:modpack-name="project?.name ?? ''"
:modpack-icon-url="project?.icon_url ?? undefined"
<ManagedContentModal
ref="managedContentModal"
:header="formatMessage(messages.modpackContent)"
:source-name="project?.name ?? ''"
:source-icon-url="project?.icon_url ?? undefined"
/>
</template>
@@ -159,7 +159,7 @@ import {
type ContentItem,
defineMessages,
formatLoader,
ModpackContentModal,
ManagedContentModal,
NewModal,
Table,
type TableColumn,
@@ -169,7 +169,6 @@ import {
import { openUrl } from '@tauri-apps/plugin-opener'
import { computed, nextTick, ref } from 'vue'
import { hide_ads_window, show_ads_window } from '@/helpers/ads'
import { get_project, get_project_many, get_version, get_version_many } from '@/helpers/cache.js'
import { injectServerInstall } from '@/providers/server-install'
@@ -269,10 +268,10 @@ function handleReport() {
}
}
const modpackContentModal = ref<InstanceType<typeof ModpackContentModal>>()
const managedContentModal = ref<InstanceType<typeof ManagedContentModal>>()
async function openViewContents() {
modpackContentModal.value?.showLoading()
managedContentModal.value?.showLoading()
try {
// Ensure version data is available — the useQuery may not have resolved yet
const versionId = modpackVersionId.value
@@ -330,10 +329,10 @@ async function openViewContents() {
}
},
)
modpackContentModal.value?.show(contentItems)
managedContentModal.value?.show(contentItems)
} catch (err) {
console.error('Failed to load modpack contents:', err)
modpackContentModal.value?.show([])
managedContentModal.value?.show([])
}
}
@@ -352,7 +351,6 @@ async function show(
if (modpackVersionIdVal) await fetchData(modpackVersionIdVal)
hide_ads_window()
modal.value?.show(e)
await nextTick()
forceCheckTableScroll()
@@ -363,6 +361,10 @@ function hide() {
}
const messages = defineMessages({
modpackContent: {
id: 'app.modal.install-to-play.managed-content.modpack-header',
defaultMessage: 'Modpack content',
},
installToPlay: {
id: 'app.modal.install-to-play.header',
defaultMessage: 'Install to play',
@@ -34,6 +34,7 @@ import { get_project_many, get_version, get_version_many } from '@/helpers/cache
import { wait_for_install_job } from '@/helpers/install'
import { update_managed_modrinth_version } from '@/helpers/instance'
import type { GameInstance } from '@/helpers/types'
import { injectAppEvents } from '@/providers/app-events'
import { injectServerInstall } from '@/providers/server-install'
type Dependency = Labrinth.Versions.v3.Dependency
@@ -74,6 +75,7 @@ type ProjectInfo = {
}
const { formatMessage } = useVIntl()
const appEvents = injectAppEvents()
const { startInstallingServer, stopInstallingServer } = injectServerInstall()
type UpdateCompleteCallback = () => void | Promise<void>
@@ -253,7 +255,7 @@ async function handleUpdate() {
try {
if (modpackVersionId.value && instance.value) {
const job = await update_managed_modrinth_version(instance.value.id, modpackVersionId.value)
await wait_for_install_job(job.job_id)
await wait_for_install_job(appEvents, job.job_id)
await onUpdateComplete.value()
}
} catch (error) {
@@ -39,6 +39,7 @@ import {
} from '@/helpers/install'
import { useSharedInstanceErrors } from '@/helpers/shared-instance-errors'
import type { GameInstance } from '@/helpers/types'
import { injectAppEvents } from '@/providers/app-events'
type UpdateCompleteCallback = () => void | Promise<void>
@@ -54,6 +55,7 @@ const instance = ref<GameInstance | null>(null)
const preview = ref<SharedInstanceUpdatePreview | null>(null)
const onComplete = ref<UpdateCompleteCallback>(() => {})
const { formatMessage } = useVIntl()
const appEvents = injectAppEvents()
const { notifySharedInstanceError } = useSharedInstanceErrors()
const diffs = computed<ContentDiffItem[]>(
() =>
@@ -78,7 +80,7 @@ async function update() {
try {
if (instance.value) {
const job = await install_update_shared_instance(instance.value.id)
await wait_for_install_job(job.job_id)
await wait_for_install_job(appEvents, job.job_id)
await onComplete.value()
successful = true
}
@@ -229,11 +229,11 @@
</div>
</template>
</NewModal>
<ModpackContentModal
<ManagedContentModal
ref="contentModal"
:header="formatMessage(messages.sharedInstanceContent)"
:modpack-name="preview?.name ?? ''"
:modpack-icon-url="preview?.iconUrl ?? undefined"
:source-name="preview?.name ?? ''"
:source-icon-url="preview?.iconUrl ?? undefined"
/>
</template>
@@ -251,12 +251,13 @@ import {
type ComboboxOption,
commonMessages,
defineMessages,
formatReportType,
injectAuth,
injectModrinthClient,
injectNotificationManager,
IntlFormatted,
ManagedContentModal,
MarkdownEditor,
ModpackContentModal,
NewModal,
Table,
type TableColumn,
@@ -268,7 +269,6 @@ import { openUrl } from '@tauri-apps/plugin-opener'
import { computed, nextTick, ref } from 'vue'
import { config } from '@/config'
import { hide_ads_window, show_ads_window } from '@/helpers/ads'
import { toError } from '@/helpers/errors'
import type { SharedInstanceInstallPreview } from '@/helpers/install'
import { create_report } from '@/helpers/reports'
@@ -289,7 +289,7 @@ type SharedInstanceCreator = {
}
const modal = ref<InstanceType<typeof NewModal>>()
const contentModal = ref<InstanceType<typeof ModpackContentModal>>()
const contentModal = ref<InstanceType<typeof ManagedContentModal>>()
const externalFileTable = ref<HTMLElement | null>(null)
const preview = ref<SharedInstanceInstallPreview | null>(null)
const creator = ref<SharedInstanceCreator | null>(null)
@@ -329,9 +329,9 @@ const externalFileRows = computed<ExternalFileRow[]>(() =>
.sort((left, right) => left.name.localeCompare(right.name)),
)
const reportReasonOptions = computed<ComboboxOption<ReportReason>[]>(() => [
{ value: 'malicious', label: formatMessage(messages.maliciousReason) },
{ value: 'inappropriate', label: formatMessage(messages.inappropriateReason) },
{ value: 'spam', label: formatMessage(messages.spamReason) },
{ value: 'malicious', label: formatReportType(formatMessage, 'malicious') },
{ value: 'inappropriate', label: formatReportType(formatMessage, 'inappropriate') },
{ value: 'spam', label: formatReportType(formatMessage, 'spam') },
])
const canSubmitReport = computed(
() => Boolean(preview.value && additionalContext.value.trim()) && !submitLoading.value,
@@ -448,7 +448,6 @@ function handleCancel() {
function handleHide() {
resetReportState()
creator.value = null
show_ads_window()
}
function resetReportState() {
reportMode.value = false
@@ -488,7 +487,6 @@ function showReport(
}
function showPreview(previewValue: SharedInstanceInstallPreview, event?: MouseEvent) {
preview.value = previewValue
hide_ads_window()
modal.value?.show(event)
void nextTick(() => forceCheckTableScroll())
}
@@ -543,18 +541,6 @@ const messages = defineMessages({
id: 'app.modal.install-to-play.report-reason',
defaultMessage: 'Which rule does this instance violate?',
},
maliciousReason: {
id: 'app.modal.install-to-play.report-reason.malicious',
defaultMessage: 'Malicious',
},
inappropriateReason: {
id: 'app.modal.install-to-play.report-reason.inappropriate',
defaultMessage: 'Inappropriate',
},
spamReason: {
id: 'app.modal.install-to-play.report-reason.spam',
defaultMessage: 'Spam',
},
additionalContext: {
id: 'app.modal.install-to-play.additional-context',
defaultMessage: 'Additional context',
@@ -21,12 +21,12 @@ import {
import { capitalizeString } from '@modrinth/utils'
import { convertFileSrc } from '@tauri-apps/api/core'
import type { Dayjs } from 'dayjs'
import { computed, nextTick, onMounted, onUnmounted, ref } from 'vue'
import { computed, nextTick, onMounted, ref } from 'vue'
import { useRouter } from 'vue-router'
import { useAppEvent } from '@/composables/use-app-event'
import { trackEvent } from '@/helpers/analytics'
import { get_project } from '@/helpers/cache'
import { process_listener } from '@/helpers/events'
import { kill, run } from '@/helpers/instance'
import { get_by_instance_id } from '@/helpers/process'
import type { GameInstance } from '@/helpers/types'
@@ -108,7 +108,7 @@ const stop = async (event: MouseEvent) => {
loading.value = false
}
const unlistenProcesses = await process_listener(async () => {
useAppEvent('process', async () => {
await checkProcess()
})
@@ -121,10 +121,6 @@ const checkProcess = async () => {
onMounted(() => {
checkProcess()
})
onUnmounted(() => {
unlistenProcesses()
})
</script>
<template>
<SmartClickable>
@@ -5,12 +5,12 @@ import { GAME_MODES, injectNotificationManager } from '@modrinth/ui'
import { platform } from '@tauri-apps/plugin-os'
import type { Dayjs } from 'dayjs'
import dayjs from 'dayjs'
import { computed, onMounted, onUnmounted, ref, watch } from 'vue'
import { computed, onMounted, ref, watch } from 'vue'
import InstanceItem from '@/components/ui/world/InstanceItem.vue'
import WorldItem from '@/components/ui/world/WorldItem.vue'
import { useAppEvent } from '@/composables/use-app-event'
import { trackEvent } from '@/helpers/analytics'
import { instance_listener, process_listener } from '@/helpers/events'
import { kill, run } from '@/helpers/instance'
import { get_all } from '@/helpers/process'
import { get_game_versions } from '@/helpers/tags'
@@ -215,11 +215,11 @@ async function stopInstance(path: string) {
const currentInstance = ref<string>()
const currentWorld = ref<string>()
const unlistenProcesses = await process_listener(async () => {
useAppEvent('process', async () => {
await checkProcesses()
})
const unlistenInstances = await instance_listener(async () => {
useAppEvent('instance', async () => {
await populateJumpBackIn().catch(() => {
console.error('Failed to populate jump back in')
})
@@ -251,11 +251,6 @@ onMounted(() => {
checkProcesses()
linuxPopulateCount.value = 0
})
onUnmounted(() => {
unlistenProcesses()
unlistenInstances()
})
</script>
<template>
@@ -10,7 +10,6 @@ import { convertFileSrc } from '@tauri-apps/api/core'
import { computed, ref } from 'vue'
import type { Router } from 'vue-router'
import { install_job_listener } from '@/helpers/events'
import {
install_job_dismiss,
install_job_list,
@@ -23,6 +22,7 @@ import {
type InstallProgress,
} from '@/helpers/install'
import { get_many as getInstances } from '@/helpers/instance'
import { injectAppEvents } from '@/providers/app-events'
import { useTheming } from '@/store/state'
const messages = defineMessages({
@@ -234,6 +234,7 @@ export async function useInstallJobNotifications(opts: {
handleError: (err: unknown) => void
onChange: () => void
}) {
const appEvents = injectAppEvents()
const { formatMessage } = useVIntl()
const themeStore = useTheming()
const jobs = ref<InstallJobSnapshot[]>([])
@@ -404,8 +405,6 @@ export async function useInstallJobNotifications(opts: {
}
function getProgress(job: InstallJobSnapshot): number {
if (job.status === 'succeeded') return 1
if (job.status === 'failed' || job.status === 'interrupted') return 0
const progress = getEffectiveProgress(job)
if (!progress || progress.total <= 0) return 0
return Math.max(0, Math.min(1, progress.current / progress.total))
@@ -526,8 +525,12 @@ export async function useInstallJobNotifications(opts: {
)
}
const activeJobs = computed(() =>
jobs.value.filter((job) => job.status === 'queued' || job.status === 'running'),
)
const progressItems = computed<PopupNotificationProgressItem[]>(() =>
jobs.value.map((job) => {
activeJobs.value.map((job) => {
const progress = getEffectiveProgress(job)
return {
@@ -536,20 +539,26 @@ export async function useInstallJobNotifications(opts: {
text: getText(job),
iconUrl: iconUrls.value[job.job_id] ?? null,
progress: getProgress(job),
waiting: !job.progress && ['queued', 'running'].includes(job.status),
showProgress: !isTerminalJob(job),
wrapText: isTerminalJob(job),
progressType: isTerminalJob(job) ? undefined : getProgressType(job),
progressCurrent: isTerminalJob(job) ? undefined : progress?.current,
progressTotal: isTerminalJob(job) ? undefined : progress?.total,
waiting: !job.progress && job.status === 'running',
showProgress: job.status === 'running',
progressType: getProgressType(job),
progressCurrent: progress?.current,
progressTotal: progress?.total,
buttons: getButtons(job),
dismissible: isTerminalJob(job),
onDismiss: getDismissHandler(job),
}
}),
)
const buttons = computed<PopupNotificationButton[] | undefined>(() => undefined)
const terminalNotifications = computed(() =>
jobs.value.filter(isTerminalJob).map((job) => ({
id: job.job_id,
title: getTitle(job),
text: getText(job),
type: job.status === 'failed' ? ('error' as const) : ('warning' as const),
buttons: getButtons(job),
onDismiss: getDismissHandler(job),
})),
)
async function refreshMetadata(notify = true) {
const request = ++metadataRequest
@@ -627,14 +636,14 @@ export async function useInstallJobNotifications(opts: {
void refreshMetadata()
}
const unlisten = await install_job_listener((job: InstallJobSnapshot) => applyJobUpdate(job))
const unlisten = appEvents.on('install_job', applyJobUpdate)
await refresh(false)
return {
active: computed(() => jobs.value.length > 0),
active: computed(() => activeJobs.value.length > 0),
title: computed(() => formatMessage(messages.installs)),
progressItems,
buttons,
terminalNotifications,
refresh,
dispose: () => {
for (const timeout of copiedResetTimeouts.values()) {
@@ -12,7 +12,7 @@ import {
fetchCachedServerStatus,
getFreshCachedServerStatus,
} from '@/composables/instances/use-server-status-query'
import { process_listener } from '@/helpers/events'
import { useAppEvent } from '@/composables/use-app-event'
import { kill, list as listInstances } from '@/helpers/instance'
import { get_by_instance_id } from '@/helpers/process'
import type { GameInstance } from '@/helpers/types'
@@ -78,7 +78,6 @@ export function useAppServerBrowse(options: UseAppServerBrowseOptions) {
const lastServerHits = shallowRef<Labrinth.Search.v3.ResultSearchProject[]>([])
const contextMenuRef = ref<ContextMenuHandle | null>(null)
let serverPingsActive = true
let unlistenProcesses: (() => void) | null = null
async function checkServerRunningStates(hits: Labrinth.Search.v3.ResultSearchProject[]) {
debugLog('checkServerRunningStates', { hitCount: hits.length })
@@ -280,7 +279,7 @@ export function useAppServerBrowse(options: UseAppServerBrowseOptions) {
}
}
process_listener((event: { event: string; instance_id: string }) => {
useAppEvent('process', (event) => {
debugLog('process event', event)
if (event.event === 'finished') {
const projectId = Object.entries(runningServerProjects.value).find(
@@ -292,14 +291,9 @@ export function useAppServerBrowse(options: UseAppServerBrowseOptions) {
}
}
})
.then((unlisten) => {
unlistenProcesses = unlisten
})
.catch(options.handleError)
onUnmounted(() => {
serverPingsActive = false
unlistenProcesses?.()
})
return {
@@ -0,0 +1,18 @@
import { onScopeDispose } from 'vue'
import {
type AppEventHandler,
type AppEvents,
type AppEventType,
injectAppEvents,
} from '@/providers/app-events'
export function useAppEvent<Type extends AppEventType>(
type: Type,
handler: AppEventHandler<Type>,
events: AppEvents = injectAppEvents(),
) {
const unsubscribe = events.on(type, handler)
onScopeDispose(unsubscribe)
return unsubscribe
}
@@ -1,8 +1,8 @@
import { useMutation, useQuery, useQueryClient } from '@tanstack/vue-query'
import { computed, type MaybeRefOrGetter, onUnmounted, toValue } from 'vue'
import { computed, type MaybeRefOrGetter, toValue } from 'vue'
import { useAppEvent } from '@/composables/use-app-event'
import { toError } from '@/helpers/errors'
import { friend_listener } from '@/helpers/events.js'
import {
acceptCachedFriend,
add_friend,
@@ -127,13 +127,9 @@ export function useFriends(options: {
)
}
let unlisten: (() => void) | undefined
void friend_listener(() => {
useAppEvent('friend', () => {
void queryClient.invalidateQueries({ queryKey: queryKey.value })
}).then((listener) => {
unlisten = listener
})
onUnmounted(() => unlisten?.())
return {
query,
@@ -0,0 +1,12 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { CommandPayload } from "./CommandPayload";
import type { FriendPayload } from "./FriendPayload";
import type { InstallJobSnapshot } from "./InstallJobSnapshot";
import type { InstanceBulkUpdateProgressPayload } from "./InstanceBulkUpdateProgressPayload";
import type { InstancePayload } from "./InstancePayload";
import type { LoadingPayload } from "./LoadingPayload";
import type { LogPayload } from "./LogPayload";
import type { ProcessPayload } from "./ProcessPayload";
import type { WarningPayload } from "./WarningPayload";
export type AppEvent = { "type": "loading", "payload": LoadingPayload } | { "type": "process", "payload": ProcessPayload } | { "type": "instance", "payload": InstancePayload } | { "type": "instance_bulk_update_progress", "payload": InstanceBulkUpdateProgressPayload } | { "type": "install_job", "payload": InstallJobSnapshot } | { "type": "command", "payload": CommandPayload } | { "type": "warning", "payload": WarningPayload } | { "type": "friend", "payload": FriendPayload } | { "type": "notification", "payload": unknown } | { "type": "log", "payload": LogPayload } | { "type": "ads_consent_required", "payload": boolean };
@@ -0,0 +1,3 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type CommandPayload = { "event": "InstallMod", id: string, } | { "event": "InstallVersion", id: string, } | { "event": "InstallModpack", id: string, } | { "event": "InstallServer", id: string, } | { "event": "LaunchInstance", id: string, server: string | null, singleplayer_world: string | null, } | { "event": "InstallSharedInstanceInvite", invite_id: string, } | { "event": "RunMRPack", path: string, };
@@ -0,0 +1,4 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { FriendStatusPayload } from "./FriendStatusPayload";
export type FriendPayload = { "event": "friend_request", from: string, } | { "event": "user_offline", id: string, } | { "event": "status_update", user_status: FriendStatusPayload, } | { "event": "status_sync" };
@@ -0,0 +1,3 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type FriendStatusPayload = { user_id: string, profile_name: string | null, last_update: string, };
@@ -0,0 +1,3 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type ImportLauncherType = "MultiMC" | "PrismLauncher" | "ATLauncher" | "GDLauncher" | "Curseforge" | "Unknown";
@@ -0,0 +1,3 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type InstallApiErrorDetails = { error: string, status?: number, method?: string, url?: string, route?: string, };
@@ -0,0 +1,3 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type InstallErrorContext = { operation: string, source_path?: string, target_path?: string, file_path?: string, entry_path?: string, urls: Array<string>, expected_hash?: string, expected_size?: number, project_id?: string, version_id?: string, minecraft_version?: string, loader?: string, java_version?: number, os?: string, arch?: string, };
@@ -0,0 +1,7 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { InstallApiErrorDetails } from "./InstallApiErrorDetails";
import type { InstallErrorContext } from "./InstallErrorContext";
import type { InstallPhaseId } from "./InstallPhaseId";
import type { SharedInstanceUnavailableReason } from "./SharedInstanceUnavailableReason";
export type InstallErrorView = { code: string, phase?: InstallPhaseId, message: string, reason?: SharedInstanceUnavailableReason, api?: InstallApiErrorDetails, context?: InstallErrorContext, };
@@ -0,0 +1,3 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type InstallJavaStep = "resolving" | "fetching_metadata" | "downloading" | "extracting" | "validating";
@@ -0,0 +1,3 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type InstallJobDisplay = { title: string, icon: string | null, };
@@ -0,0 +1,3 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type InstallJobKind = "create_instance" | "create_modpack_instance" | "create_shared_instance" | "import_instance" | "duplicate_instance" | "install_existing_instance" | "install_pack_to_existing_instance" | "update_shared_instance";
@@ -0,0 +1,11 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { InstallErrorView } from "./InstallErrorView";
import type { InstallJobDisplay } from "./InstallJobDisplay";
import type { InstallJobKind } from "./InstallJobKind";
import type { InstallJobStatus } from "./InstallJobStatus";
import type { InstallPhaseDetails } from "./InstallPhaseDetails";
import type { InstallPhaseId } from "./InstallPhaseId";
import type { InstallProgress } from "./InstallProgress";
import type { InstallTarget } from "./InstallTarget";
export type InstallJobSnapshot = { job_id: string, instance_id: string | null, kind: InstallJobKind, status: InstallJobStatus, target: InstallTarget, phase: InstallPhaseId, progress: InstallProgress | null, details: InstallPhaseDetails, display: InstallJobDisplay | null, error: InstallErrorView | null, rollback_error: InstallErrorView | null, created: string, modified: string, finished: string | null, };
@@ -0,0 +1,3 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type InstallJobStatus = "queued" | "running" | "succeeded" | "failed" | "interrupted" | "canceled";
@@ -0,0 +1,6 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { ImportLauncherType } from "./ImportLauncherType";
import type { InstallJavaStep } from "./InstallJavaStep";
import type { ModLoader } from "./ModLoader";
export type InstallPhaseDetails = { "type": "empty" } | { "type": "instance", name: string, } | { "type": "minecraft", game_version: string, loader: ModLoader, } | { "type": "java", major_version: number, step: InstallJavaStep, } | { "type": "modpack", project_id: string | null, version_id: string | null, title: string | null, } | { "type": "import", launcher_type: ImportLauncherType, instance_folder: string, };
@@ -0,0 +1,3 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type InstallPhaseId = "preparing_instance" | "resolving_pack" | "downloading_pack_file" | "reading_pack_manifest" | "downloading_content" | "extracting_overrides" | "resolving_minecraft" | "resolving_loader" | "preparing_java" | "downloading_minecraft" | "running_loader_processors" | "finalizing" | "rolling_back";
@@ -0,0 +1,4 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { InstallProgressSecondary } from "./InstallProgressSecondary";
export type InstallProgress = { current: number, total: number, secondary?: InstallProgressSecondary, };
@@ -0,0 +1,3 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type InstallProgressSecondary = { current: number, total: number, };
@@ -0,0 +1,3 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type InstallTarget = { "type": "new_instance", instance_id: string | null, } | { "type": "existing_instance", instance_id: string, };
@@ -0,0 +1,4 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { InstanceBulkUpdateProgressStage } from "./InstanceBulkUpdateProgressStage";
export type InstanceBulkUpdateProgressPayload = { instanceId: string, stage: InstanceBulkUpdateProgressStage, current: number, total: number, };
@@ -0,0 +1,3 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type InstanceBulkUpdateProgressStage = "resolving_versions" | "downloading" | "finishing";
@@ -0,0 +1,3 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type InstancePayload = { instance_id: string, } & ({ "event": "created" } | { "event": "synced" } | { "event": "servers_updated" } | { "event": "world_updated", world: string, } | { "event": "server_joined", host: string, port: number, timestamp: string, } | { "event": "edited" } | { "event": "content_install_finished", project_ids: Array<string>, } | { "event": "content_install_failed", project_ids: Array<string>, message: string, } | { "event": "removed" });
@@ -0,0 +1,3 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type LoadingBarType = { "type": "legacy_data_migration" } | { "type": "directory_move", old: string, new: string, } | { "type": "java_download", version: number, } | { "type": "pack_file_download", instance_id: string, pack_name: string, icon: string | null, pack_version: string, } | { "type": "pack_download", instance_id: string, pack_name: string, icon: string | null, pack_id: string | null, pack_version: string | null, } | { "type": "minecraft_download", instance_id: string, instance_name: string, } | { "type": "instance_update", instance_id: string, instance_name: string, } | { "type": "zip_extract", instance_id: string, instance_name: string, } | { "type": "config_change", new_path: string, } | { "type": "copy_instance", import_location: string, instance_name: string, } | { "type": "launcher_update", version: string, current_version: string, };
@@ -0,0 +1,4 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { LoadingBarType } from "./LoadingBarType";
export type LoadingPayload = { event: LoadingBarType, loader_uuid: string, fraction: number | null, message: string, };
@@ -0,0 +1,3 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type Log4jEvent = { timestamp_millis: number | null, logger_name: string | null, level: string | null, thread_name: string | null, message: string | null, throwable: string | null, };
@@ -0,0 +1,4 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { Log4jEvent } from "./Log4jEvent";
export type LogPayload = { instance_id: string, } & ({ "type": "log4j" } & Log4jEvent | { "type": "legacy", message: string, });
@@ -0,0 +1,3 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type ModLoader = "vanilla" | "forge" | "fabric" | "quilt" | "neoforge";
@@ -0,0 +1,4 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { ProcessPayloadType } from "./ProcessPayloadType";
export type ProcessPayload = { instance_id: string, uuid: string, event: ProcessPayloadType, message: string, };
@@ -0,0 +1,3 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type ProcessPayloadType = "launched" | "finished";
@@ -0,0 +1,8 @@
# App event bindings
> [!WARNING]
> Do not edit the generated TypeScript or Postcard binding files in this directory manually.
The event bus types are determined by the Rust [`AppEvent`](../../../../../packages/app-lib/src/event/mod.rs) contract.
They are regenerated automatically when you run `pnpm app:dev` from the workspace root. Commit any generated changes with the Rust contract change.
@@ -0,0 +1,3 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type SharedInstanceUnavailableReason = "deleted" | "access_revoked" | "quarantined";
@@ -0,0 +1,3 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type WarningPayload = { message: string, };
@@ -0,0 +1,78 @@
declare type u8 = number
declare type u16 = number
declare type u32 = number
declare type u64 = bigint
declare type u128 = bigint
declare type usize = bigint
declare type i8 = number
declare type i16 = number
declare type i32 = number
declare type i64 = bigint
declare type i128 = bigint
declare type isize = bigint
declare type NonZeroU8 = number
declare type NonZeroU16 = number
declare type NonZeroU32 = number
declare type NonZeroU64 = bigint
declare type NonZeroU128 = bigint
declare type NonZeroUsize = bigint
declare type NonZeroI8 = number
declare type NonZeroI16 = number
declare type NonZeroI32 = number
declare type NonZeroI64 = bigint
declare type NonZeroI128 = bigint
declare type NonZeroIsize = bigint
declare type f32 = number
declare type f64 = number
declare type ArrayLengthMutationKeys = "splice" | "push" | "pop" | "shift" | "unshift"
declare type FixedLengthArray<T, L extends number, TObj = [T, ...Array<T>]> =
Pick<TObj, Exclude<keyof TObj, ArrayLengthMutationKeys>>
& {
readonly length: L
[ I : number ] : T
[Symbol.iterator]: () => IterableIterator<T>
}
export type AppEvent = { tag: "loading", value: LoadingPayload } | { tag: "process", value: ProcessPayload } | { tag: "instance", value: InstancePayload } | { tag: "instance_bulk_update_progress", value: InstanceBulkUpdateProgressPayload } | { tag: "install_job", value: InstallJobSnapshot } | { tag: "command", value: CommandPayload } | { tag: "warning", value: WarningPayload } | { tag: "friend", value: FriendPayload } | { tag: "notification", value: string } | { tag: "log", value: LogPayload } | { tag: "ads_consent_required", value: boolean }
export type LoadingBarType = { tag: "legacy_data_migration" } | { tag: "directory_move", value: { old: string, new: string } } | { tag: "java_download", value: { version: u32 } } | { tag: "pack_file_download", value: { instance_id: string, pack_name: string, icon: string | undefined, pack_version: string } } | { tag: "pack_download", value: { instance_id: string, pack_name: string, icon: string | undefined, pack_id: string | undefined, pack_version: string | undefined } } | { tag: "minecraft_download", value: { instance_id: string, instance_name: string } } | { tag: "instance_update", value: { instance_id: string, instance_name: string } } | { tag: "zip_extract", value: { instance_id: string, instance_name: string } } | { tag: "config_change", value: { new_path: string } } | { tag: "copy_instance", value: { import_location: string, instance_name: string } } | { tag: "launcher_update", value: { version: string, current_version: string } }
export type LoadingPayload = { event: LoadingBarType, loader_uuid: string, fraction: f64 | undefined, message: string }
export type WarningPayload = { message: string }
export type InstanceBulkUpdateProgressPayload = { instanceId: string, stage: InstanceBulkUpdateProgressStage, current: u64, total: u64 }
export type InstanceBulkUpdateProgressStage = { tag: "resolving_versions" } | { tag: "downloading" } | { tag: "finishing" }
export type CommandPayload = { tag: "InstallMod", value: { id: string } } | { tag: "InstallVersion", value: { id: string } } | { tag: "InstallModpack", value: { id: string } } | { tag: "InstallServer", value: { id: string } } | { tag: "LaunchInstance", value: { id: string, server: string | undefined, singleplayer_world: string | undefined } } | { tag: "InstallSharedInstanceInvite", value: { invite_id: string } } | { tag: "RunMRPack", value: { path: string } }
export type ProcessPayload = { instance_id: string, uuid: string, event: ProcessPayloadType, message: string }
export type ProcessPayloadType = { tag: "launched" } | { tag: "finished" }
export type InstancePayload = { instance_id: string, event: InstancePayloadType }
export type InstancePayloadType = { tag: "created" } | { tag: "synced" } | { tag: "servers_updated" } | { tag: "world_updated", value: { world: string } } | { tag: "server_joined", value: { host: string, port: u16, timestamp: string } } | { tag: "edited" } | { tag: "content_install_finished", value: { project_ids: string[] } } | { tag: "content_install_failed", value: { project_ids: string[], message: string } } | { tag: "removed" }
export type FriendPayload = { tag: "friend_request", value: { from: string } } | { tag: "user_offline", value: { id: string } } | { tag: "status_update", value: { user_status: FriendStatusPayload } } | { tag: "status_sync" }
export type FriendStatusPayload = { user_id: string, profile_name: string | undefined, last_update: string }
export type SharedInstanceUnavailableReason = { tag: "deleted" } | { tag: "access_revoked" } | { tag: "quarantined" }
export type LogEvent = { tag: "log4j", value: Log4jEvent } | { tag: "legacy", value: { message: string } }
export type LogPayload = { instance_id: string, event: LogEvent }
export type Log4jEvent = { timestamp_millis: i64 | undefined, logger_name: string | undefined, level: string | undefined, thread_name: string | undefined, message: string | undefined, throwable: string | undefined }
export type ModLoader = { tag: "vanilla" } | { tag: "forge" } | { tag: "fabric" } | { tag: "quilt" } | { tag: "neoforge" }
export type InstallJobSnapshot = { job_id: string, instance_id: string | undefined, kind: InstallJobKind, status: InstallJobStatus, target: InstallTarget, phase: InstallPhaseId, progress: InstallProgress | undefined, details: InstallPhaseDetails, display: InstallJobDisplay | undefined, error: InstallErrorView | undefined, rollback_error: InstallErrorView | undefined, created: string, modified: string, finished: string | undefined }
export type InstallJobKind = { tag: "create_instance" } | { tag: "create_modpack_instance" } | { tag: "create_shared_instance" } | { tag: "import_instance" } | { tag: "duplicate_instance" } | { tag: "install_existing_instance" } | { tag: "install_pack_to_existing_instance" } | { tag: "update_shared_instance" }
export type InstallJobStatus = { tag: "queued" } | { tag: "running" } | { tag: "succeeded" } | { tag: "failed" } | { tag: "interrupted" } | { tag: "canceled" }
export type InstallTarget = { tag: "new_instance", value: { instance_id: string | undefined } } | { tag: "existing_instance", value: { instance_id: string } }
export type InstallPhaseId = { tag: "preparing_instance" } | { tag: "resolving_pack" } | { tag: "downloading_pack_file" } | { tag: "reading_pack_manifest" } | { tag: "downloading_content" } | { tag: "extracting_overrides" } | { tag: "resolving_minecraft" } | { tag: "resolving_loader" } | { tag: "preparing_java" } | { tag: "downloading_minecraft" } | { tag: "running_loader_processors" } | { tag: "finalizing" } | { tag: "rolling_back" }
export type InstallProgress = { current: u64, total: u64, secondary: InstallProgressSecondary | undefined }
export type InstallProgressSecondary = { current: u64, total: u64 }
export type InstallPhaseDetails = { tag: "empty" } | { tag: "instance", value: { name: string } } | { tag: "minecraft", value: { game_version: string, loader: ModLoader } } | { tag: "java", value: { major_version: u32, step: InstallJavaStep } } | { tag: "modpack", value: { project_id: string | undefined, version_id: string | undefined, title: string | undefined } } | { tag: "import", value: { launcher_type: ImportLauncherType, instance_folder: string } }
export type InstallJavaStep = { tag: "resolving" } | { tag: "fetching_metadata" } | { tag: "downloading" } | { tag: "extracting" } | { tag: "validating" }
export type InstallJobDisplay = { title: string, icon: string | undefined }
export type InstallErrorView = { code: string, phase: InstallPhaseId | undefined, message: string, reason: SharedInstanceUnavailableReason | undefined, api: InstallApiErrorDetails | undefined, context: InstallErrorContext | undefined }
export type InstallApiErrorDetails = { error: string, status: u16 | undefined, method: string | undefined, url: string | undefined, route: string | undefined }
export type InstallErrorContext = { operation: string, source_path: string | undefined, target_path: string | undefined, file_path: string | undefined, entry_path: string | undefined, urls: string[], expected_hash: string | undefined, expected_size: u64 | undefined, project_id: string | undefined, version_id: string | undefined, minecraft_version: string | undefined, loader: string | undefined, java_version: u32 | undefined, os: string | undefined, arch: string | undefined }
export type ImportLauncherType = { tag: "MultiMC" } | { tag: "PrismLauncher" } | { tag: "ATLauncher" } | { tag: "GDLauncher" } | { tag: "Curseforge" } | { tag: "Unknown" }
export type Type = "AppEvent" | "LoadingBarType" | "LoadingPayload" | "WarningPayload" | "InstanceBulkUpdateProgressPayload" | "InstanceBulkUpdateProgressStage" | "CommandPayload" | "ProcessPayload" | "ProcessPayloadType" | "InstancePayload" | "InstancePayloadType" | "FriendPayload" | "FriendStatusPayload" | "LogEvent" | "LogPayload" | "Log4jEvent" | "InstallJobSnapshot" | "InstallJobKind" | "InstallJobStatus" | "InstallTarget" | "InstallPhaseId" | "InstallProgress" | "InstallProgressSecondary" | "InstallPhaseDetails" | "InstallJavaStep" | "InstallJobDisplay" | "InstallErrorView" | "InstallApiErrorDetails" | "InstallErrorContext" | "ImportLauncherType" | "ModLoader" | "SharedInstanceUnavailableReason"
declare type ValueType<T extends Type> = T extends "AppEvent" ? AppEvent : T extends "LoadingBarType" ? LoadingBarType : T extends "LoadingPayload" ? LoadingPayload : T extends "WarningPayload" ? WarningPayload : T extends "InstanceBulkUpdateProgressPayload" ? InstanceBulkUpdateProgressPayload : T extends "InstanceBulkUpdateProgressStage" ? InstanceBulkUpdateProgressStage : T extends "CommandPayload" ? CommandPayload : T extends "ProcessPayload" ? ProcessPayload : T extends "ProcessPayloadType" ? ProcessPayloadType : T extends "InstancePayload" ? InstancePayload : T extends "InstancePayloadType" ? InstancePayloadType : T extends "FriendPayload" ? FriendPayload : T extends "FriendStatusPayload" ? FriendStatusPayload : T extends "LogEvent" ? LogEvent : T extends "LogPayload" ? LogPayload : T extends "Log4jEvent" ? Log4jEvent : T extends "InstallJobSnapshot" ? InstallJobSnapshot : T extends "InstallJobKind" ? InstallJobKind : T extends "InstallJobStatus" ? InstallJobStatus : T extends "InstallTarget" ? InstallTarget : T extends "InstallPhaseId" ? InstallPhaseId : T extends "InstallProgress" ? InstallProgress : T extends "InstallProgressSecondary" ? InstallProgressSecondary : T extends "InstallPhaseDetails" ? InstallPhaseDetails : T extends "InstallJavaStep" ? InstallJavaStep : T extends "InstallJobDisplay" ? InstallJobDisplay : T extends "InstallErrorView" ? InstallErrorView : T extends "InstallApiErrorDetails" ? InstallApiErrorDetails : T extends "InstallErrorContext" ? InstallErrorContext : T extends "ImportLauncherType" ? ImportLauncherType : T extends "ModLoader" ? ModLoader : T extends "SharedInstanceUnavailableReason" ? SharedInstanceUnavailableReason : void
export interface Result<T extends Type> {
value: ValueType<T>;
bytes: Uint8Array;
}
export function deserialize<T extends Type>(type: T, bytes: Uint8Array): Result<T>
@@ -0,0 +1,953 @@
const BITS_PER_BYTE = 8, BITS_PER_VARINT_BYTE = 7, U8_BYTES = 1, U16_BYTES = 2, U32_BYTES = 4, U64_BYTES = 8, U128_BYTES = 16
const de_zig_zag_signed = (n) => (n >> 1n) ^ (-(n & 0b1n))
const zig_zag = (n_bytes, n) => (n << 1n) ^ (n >> BigInt(n_bytes * BITS_PER_BYTE - 1))
const varint_max = (n_bytes) => Math.floor((n_bytes * BITS_PER_BYTE + (BITS_PER_BYTE - 1)) / BITS_PER_VARINT_BYTE)
const max_of_last_byte = (n_bytes) => (1 << (n_bytes * BITS_PER_BYTE) % 7) - 1
const to_number_if_safe = (n) => Number.MAX_SAFE_INTEGER < ((n < 0n) ? -n : n) ? n : Number(n)
const varint = (n_bytes, n) => { let value = BigInt(n), out = []; for (let i = 0; i < varint_max(n_bytes); i++) { out.push(Number(value & 0xFFn)); if (value < 128n) { return out } out[i] |= 0x80; value >>= 7n } }
class Deserializer {
constructor(bytes_in) { this.bytes = Array.from(bytes_in); }
pop_next = () => { const next = this.bytes.shift(); if (next === undefined) { throw "input buffer too small" } return next }
pop_n = (n) => { const bytes = Array(); for (let i = 0; i < n; i++) { bytes.push(this.bytes.shift()) } return bytes }
get_int8 = (signed) => signed ? new Int8Array([this.pop_next()])[0] : this.pop_next();
try_take = (n_bytes) => { let out = 0n, v_max = varint_max(n_bytes); for (let i = 0; i < v_max; i++) { const val = this.pop_next(), carry = BigInt(val & 0x7F); out |= carry << BigInt(7 * i); if ((val & 0x80) === 0) { if (i === v_max - 1 && val > max_of_last_byte(n_bytes)) { throw "Bad Variant" } else return out } } throw "Bad Variant"; }
deserialize_bool = () => { const byte = this.pop_next(); return byte === undefined ? undefined : byte > 0 ? true : false }
deserialize_number = (n_bytes, signed) => { if (n_bytes === U8_BYTES) { return this.get_int8(signed) } else if (n_bytes === U16_BYTES || n_bytes === U32_BYTES || n_bytes === U64_BYTES || n_bytes === U128_BYTES) { const val = this.try_take(n_bytes); return to_number_if_safe(signed ? de_zig_zag_signed(val) : val) } else { throw "byte count not supported" } }
deserialize_number_float = (n_bytes) => { const b_buffer = new ArrayBuffer(n_bytes), b_view = new DataView(b_buffer); this.pop_n(n_bytes).forEach((b, i) => b_view.setUint8(i, b)); if (n_bytes === U32_BYTES) { return b_view.getFloat32(0, true) } else if (n_bytes === U64_BYTES) { return b_view.getFloat64(0, true) } else { throw "byte count not supported" } }
deserialize_string = () => new TextDecoder().decode(new Uint8Array(this.pop_n(Number(this.try_take(U32_BYTES)))))
deserialize_array = (des, len) => Array.from({length: len === undefined ? Number(this.try_take(U32_BYTES)) : len}, (v, i) => des(this))
deserialize_string_key_map = (des) => { return [...Array(Number(this.try_take(U32_BYTES)))].reduce((prev) => { prev[this.deserialize_string()] = des(this); return prev }, {}) }
deserialize_map = (des) => { return [...Array(Number(this.try_take(U32_BYTES)))].reduce((prev) => { const d = des(this); prev.set(d[0], d[1]); return prev }, new Map()) }
release_bytes = () => { return new Uint8Array(this.bytes); }
}
function deserialize_APP_EVENT(d) {
switch (d.deserialize_number(U32_BYTES, false)) {
case 0:
return {
tag: "loading",
value: deserialize_LOADING_PAYLOAD(d)
};
case 1:
return {
tag: "process",
value: deserialize_PROCESS_PAYLOAD(d)
};
case 2:
return {
tag: "instance",
value: deserialize_INSTANCE_PAYLOAD(d)
};
case 3:
return {
tag: "instance_bulk_update_progress",
value: deserialize_INSTANCE_BULK_UPDATE_PROGRESS_PAYLOAD(d)
};
case 4:
return {
tag: "install_job",
value: deserialize_INSTALL_JOB_SNAPSHOT(d)
};
case 5:
return {
tag: "command",
value: deserialize_COMMAND_PAYLOAD(d)
};
case 6:
return {
tag: "warning",
value: deserialize_WARNING_PAYLOAD(d)
};
case 7:
return {
tag: "friend",
value: deserialize_FRIEND_PAYLOAD(d)
};
case 8:
return {
tag: "notification",
value: d.deserialize_string()
};
case 9:
return {
tag: "log",
value: deserialize_LOG_PAYLOAD(d)
};
case 10:
return {
tag: "ads_consent_required",
value: d.deserialize_bool()
};
default:
throw "variant not implemented"
}
}
function deserialize_LOADING_BAR_TYPE(d) {
switch (d.deserialize_number(U32_BYTES, false)) {
case 0:
return {
tag: "legacy_data_migration"
};
case 1:
return {
tag: "directory_move",
value: {
old: d.deserialize_string(),
new: d.deserialize_string()
}
};
case 2:
return {
tag: "java_download",
value: {
version: d.deserialize_number(U32_BYTES, false)
}
};
case 3:
return {
tag: "pack_file_download",
value: {
instance_id: d.deserialize_string(),
pack_name: d.deserialize_string(),
icon: (d.deserialize_number(U32_BYTES, false) === 0) ? undefined : d.deserialize_string(),
pack_version: d.deserialize_string()
}
};
case 4:
return {
tag: "pack_download",
value: {
instance_id: d.deserialize_string(),
pack_name: d.deserialize_string(),
icon: (d.deserialize_number(U32_BYTES, false) === 0) ? undefined : d.deserialize_string(),
pack_id: (d.deserialize_number(U32_BYTES, false) === 0) ? undefined : d.deserialize_string(),
pack_version: (d.deserialize_number(U32_BYTES, false) === 0) ? undefined : d.deserialize_string()
}
};
case 5:
return {
tag: "minecraft_download",
value: {
instance_id: d.deserialize_string(),
instance_name: d.deserialize_string()
}
};
case 6:
return {
tag: "instance_update",
value: {
instance_id: d.deserialize_string(),
instance_name: d.deserialize_string()
}
};
case 7:
return {
tag: "zip_extract",
value: {
instance_id: d.deserialize_string(),
instance_name: d.deserialize_string()
}
};
case 8:
return {
tag: "config_change",
value: {
new_path: d.deserialize_string()
}
};
case 9:
return {
tag: "copy_instance",
value: {
import_location: d.deserialize_string(),
instance_name: d.deserialize_string()
}
};
case 10:
return {
tag: "launcher_update",
value: {
version: d.deserialize_string(),
current_version: d.deserialize_string()
}
};
default:
throw "variant not implemented"
}
}
function deserialize_LOADING_PAYLOAD(d) {
return {
event: deserialize_LOADING_BAR_TYPE(d),
loader_uuid: d.deserialize_string(),
fraction: (d.deserialize_number(U32_BYTES, false) === 0) ? undefined : d.deserialize_number_float(U64_BYTES),
message: d.deserialize_string()
};
}
function deserialize_WARNING_PAYLOAD(d) {
return {
message: d.deserialize_string()
};
}
function deserialize_INSTANCE_BULK_UPDATE_PROGRESS_PAYLOAD(d) {
return {
instanceId: d.deserialize_string(),
stage: deserialize_INSTANCE_BULK_UPDATE_PROGRESS_STAGE(d),
current: d.deserialize_number(U64_BYTES, false),
total: d.deserialize_number(U64_BYTES, false)
};
}
function deserialize_INSTANCE_BULK_UPDATE_PROGRESS_STAGE(d) {
switch (d.deserialize_number(U32_BYTES, false)) {
case 0:
return {
tag: "resolving_versions"
};
case 1:
return {
tag: "downloading"
};
case 2:
return {
tag: "finishing"
};
default:
throw "variant not implemented"
}
}
function deserialize_COMMAND_PAYLOAD(d) {
switch (d.deserialize_number(U32_BYTES, false)) {
case 0:
return {
tag: "InstallMod",
value: {
id: d.deserialize_string()
}
};
case 1:
return {
tag: "InstallVersion",
value: {
id: d.deserialize_string()
}
};
case 2:
return {
tag: "InstallModpack",
value: {
id: d.deserialize_string()
}
};
case 3:
return {
tag: "InstallServer",
value: {
id: d.deserialize_string()
}
};
case 4:
return {
tag: "LaunchInstance",
value: {
id: d.deserialize_string(),
server: (d.deserialize_number(U32_BYTES, false) === 0) ? undefined : d.deserialize_string(),
singleplayer_world: (d.deserialize_number(U32_BYTES, false) === 0) ? undefined : d.deserialize_string()
}
};
case 5:
return {
tag: "InstallSharedInstanceInvite",
value: {
invite_id: d.deserialize_string()
}
};
case 6:
return {
tag: "RunMRPack",
value: {
path: d.deserialize_string()
}
};
default:
throw "variant not implemented"
}
}
function deserialize_PROCESS_PAYLOAD(d) {
return {
instance_id: d.deserialize_string(),
uuid: d.deserialize_string(),
event: deserialize_PROCESS_PAYLOAD_TYPE(d),
message: d.deserialize_string()
};
}
function deserialize_PROCESS_PAYLOAD_TYPE(d) {
switch (d.deserialize_number(U32_BYTES, false)) {
case 0:
return {
tag: "launched"
};
case 1:
return {
tag: "finished"
};
default:
throw "variant not implemented"
}
}
function deserialize_INSTANCE_PAYLOAD(d) {
return {
instance_id: d.deserialize_string(),
event: deserialize_INSTANCE_PAYLOAD_TYPE(d)
};
}
function deserialize_INSTANCE_PAYLOAD_TYPE(d) {
switch (d.deserialize_number(U32_BYTES, false)) {
case 0:
return {
tag: "created"
};
case 1:
return {
tag: "synced"
};
case 2:
return {
tag: "servers_updated"
};
case 3:
return {
tag: "world_updated",
value: {
world: d.deserialize_string()
}
};
case 4:
return {
tag: "server_joined",
value: {
host: d.deserialize_string(),
port: d.deserialize_number(U16_BYTES, false),
timestamp: d.deserialize_string()
}
};
case 5:
return {
tag: "edited"
};
case 6:
return {
tag: "content_install_finished",
value: {
project_ids: d.deserialize_array(() => d.deserialize_string())
}
};
case 7:
return {
tag: "content_install_failed",
value: {
project_ids: d.deserialize_array(() => d.deserialize_string()),
message: d.deserialize_string()
}
};
case 8:
return {
tag: "removed"
};
default:
throw "variant not implemented"
}
}
function deserialize_FRIEND_PAYLOAD(d) {
switch (d.deserialize_number(U32_BYTES, false)) {
case 0:
return {
tag: "friend_request",
value: {
from: d.deserialize_string()
}
};
case 1:
return {
tag: "user_offline",
value: {
id: d.deserialize_string()
}
};
case 2:
return {
tag: "status_update",
value: {
user_status: deserialize_FRIEND_STATUS_PAYLOAD(d)
}
};
case 3:
return {
tag: "status_sync"
};
default:
throw "variant not implemented"
}
}
function deserialize_FRIEND_STATUS_PAYLOAD(d) {
return {
user_id: d.deserialize_string(),
profile_name: (d.deserialize_number(U32_BYTES, false) === 0) ? undefined : d.deserialize_string(),
last_update: d.deserialize_string()
};
}
function deserialize_LOG_EVENT(d) {
switch (d.deserialize_number(U32_BYTES, false)) {
case 0:
return {
tag: "log4j",
value: deserialize_LOG4J_EVENT(d)
};
case 1:
return {
tag: "legacy",
value: {
message: d.deserialize_string()
}
};
default:
throw "variant not implemented"
}
}
function deserialize_LOG_PAYLOAD(d) {
return {
instance_id: d.deserialize_string(),
event: deserialize_LOG_EVENT(d)
};
}
function deserialize_LOG4J_EVENT(d) {
return {
timestamp_millis: (d.deserialize_number(U32_BYTES, false) === 0) ? undefined : d.deserialize_number(U64_BYTES, true),
logger_name: (d.deserialize_number(U32_BYTES, false) === 0) ? undefined : d.deserialize_string(),
level: (d.deserialize_number(U32_BYTES, false) === 0) ? undefined : d.deserialize_string(),
thread_name: (d.deserialize_number(U32_BYTES, false) === 0) ? undefined : d.deserialize_string(),
message: (d.deserialize_number(U32_BYTES, false) === 0) ? undefined : d.deserialize_string(),
throwable: (d.deserialize_number(U32_BYTES, false) === 0) ? undefined : d.deserialize_string()
};
}
function deserialize_INSTALL_JOB_SNAPSHOT(d) {
return {
job_id: d.deserialize_string(),
instance_id: (d.deserialize_number(U32_BYTES, false) === 0) ? undefined : d.deserialize_string(),
kind: deserialize_INSTALL_JOB_KIND(d),
status: deserialize_INSTALL_JOB_STATUS(d),
target: deserialize_INSTALL_TARGET(d),
phase: deserialize_INSTALL_PHASE_ID(d),
progress: (d.deserialize_number(U32_BYTES, false) === 0) ? undefined : deserialize_INSTALL_PROGRESS(d),
details: deserialize_INSTALL_PHASE_DETAILS(d),
display: (d.deserialize_number(U32_BYTES, false) === 0) ? undefined : deserialize_INSTALL_JOB_DISPLAY(d),
error: (d.deserialize_number(U32_BYTES, false) === 0) ? undefined : deserialize_INSTALL_ERROR_VIEW(d),
rollback_error: (d.deserialize_number(U32_BYTES, false) === 0) ? undefined : deserialize_INSTALL_ERROR_VIEW(d),
created: d.deserialize_string(),
modified: d.deserialize_string(),
finished: (d.deserialize_number(U32_BYTES, false) === 0) ? undefined : d.deserialize_string()
};
}
function deserialize_INSTALL_JOB_KIND(d) {
switch (d.deserialize_number(U32_BYTES, false)) {
case 0:
return {
tag: "create_instance"
};
case 1:
return {
tag: "create_modpack_instance"
};
case 2:
return {
tag: "create_shared_instance"
};
case 3:
return {
tag: "import_instance"
};
case 4:
return {
tag: "duplicate_instance"
};
case 5:
return {
tag: "install_existing_instance"
};
case 6:
return {
tag: "install_pack_to_existing_instance"
};
case 7:
return {
tag: "update_shared_instance"
};
default:
throw "variant not implemented"
}
}
function deserialize_INSTALL_JOB_STATUS(d) {
switch (d.deserialize_number(U32_BYTES, false)) {
case 0:
return {
tag: "queued"
};
case 1:
return {
tag: "running"
};
case 2:
return {
tag: "succeeded"
};
case 3:
return {
tag: "failed"
};
case 4:
return {
tag: "interrupted"
};
case 5:
return {
tag: "canceled"
};
default:
throw "variant not implemented"
}
}
function deserialize_INSTALL_TARGET(d) {
switch (d.deserialize_number(U32_BYTES, false)) {
case 0:
return {
tag: "new_instance",
value: {
instance_id: (d.deserialize_number(U32_BYTES, false) === 0) ? undefined : d.deserialize_string()
}
};
case 1:
return {
tag: "existing_instance",
value: {
instance_id: d.deserialize_string()
}
};
default:
throw "variant not implemented"
}
}
function deserialize_INSTALL_PHASE_ID(d) {
switch (d.deserialize_number(U32_BYTES, false)) {
case 0:
return {
tag: "preparing_instance"
};
case 1:
return {
tag: "resolving_pack"
};
case 2:
return {
tag: "downloading_pack_file"
};
case 3:
return {
tag: "reading_pack_manifest"
};
case 4:
return {
tag: "downloading_content"
};
case 5:
return {
tag: "extracting_overrides"
};
case 6:
return {
tag: "resolving_minecraft"
};
case 7:
return {
tag: "resolving_loader"
};
case 8:
return {
tag: "preparing_java"
};
case 9:
return {
tag: "downloading_minecraft"
};
case 10:
return {
tag: "running_loader_processors"
};
case 11:
return {
tag: "finalizing"
};
case 12:
return {
tag: "rolling_back"
};
default:
throw "variant not implemented"
}
}
function deserialize_INSTALL_PROGRESS(d) {
return {
current: d.deserialize_number(U64_BYTES, false),
total: d.deserialize_number(U64_BYTES, false),
secondary: (d.deserialize_number(U32_BYTES, false) === 0) ? undefined : deserialize_INSTALL_PROGRESS_SECONDARY(d)
};
}
function deserialize_INSTALL_PROGRESS_SECONDARY(d) {
return {
current: d.deserialize_number(U64_BYTES, false),
total: d.deserialize_number(U64_BYTES, false)
};
}
function deserialize_INSTALL_PHASE_DETAILS(d) {
switch (d.deserialize_number(U32_BYTES, false)) {
case 0:
return {
tag: "empty"
};
case 1:
return {
tag: "instance",
value: {
name: d.deserialize_string()
}
};
case 2:
return {
tag: "minecraft",
value: {
game_version: d.deserialize_string(),
loader: deserialize_MOD_LOADER(d)
}
};
case 3:
return {
tag: "java",
value: {
major_version: d.deserialize_number(U32_BYTES, false),
step: deserialize_INSTALL_JAVA_STEP(d)
}
};
case 4:
return {
tag: "modpack",
value: {
project_id: (d.deserialize_number(U32_BYTES, false) === 0) ? undefined : d.deserialize_string(),
version_id: (d.deserialize_number(U32_BYTES, false) === 0) ? undefined : d.deserialize_string(),
title: (d.deserialize_number(U32_BYTES, false) === 0) ? undefined : d.deserialize_string()
}
};
case 5:
return {
tag: "import",
value: {
launcher_type: deserialize_IMPORT_LAUNCHER_TYPE(d),
instance_folder: d.deserialize_string()
}
};
default:
throw "variant not implemented"
}
}
function deserialize_INSTALL_JAVA_STEP(d) {
switch (d.deserialize_number(U32_BYTES, false)) {
case 0:
return {
tag: "resolving"
};
case 1:
return {
tag: "fetching_metadata"
};
case 2:
return {
tag: "downloading"
};
case 3:
return {
tag: "extracting"
};
case 4:
return {
tag: "validating"
};
default:
throw "variant not implemented"
}
}
function deserialize_INSTALL_JOB_DISPLAY(d) {
return {
title: d.deserialize_string(),
icon: (d.deserialize_number(U32_BYTES, false) === 0) ? undefined : d.deserialize_string()
};
}
function deserialize_INSTALL_ERROR_VIEW(d) {
return {
code: d.deserialize_string(),
phase: (d.deserialize_number(U32_BYTES, false) === 0) ? undefined : deserialize_INSTALL_PHASE_ID(d),
message: d.deserialize_string(),
reason: (d.deserialize_number(U32_BYTES, false) === 0) ? undefined : deserialize_SHARED_INSTANCE_UNAVAILABLE_REASON(d),
api: (d.deserialize_number(U32_BYTES, false) === 0) ? undefined : deserialize_INSTALL_API_ERROR_DETAILS(d),
context: (d.deserialize_number(U32_BYTES, false) === 0) ? undefined : deserialize_INSTALL_ERROR_CONTEXT(d)
};
}
function deserialize_INSTALL_API_ERROR_DETAILS(d) {
return {
error: d.deserialize_string(),
status: (d.deserialize_number(U32_BYTES, false) === 0) ? undefined : d.deserialize_number(U16_BYTES, false),
method: (d.deserialize_number(U32_BYTES, false) === 0) ? undefined : d.deserialize_string(),
url: (d.deserialize_number(U32_BYTES, false) === 0) ? undefined : d.deserialize_string(),
route: (d.deserialize_number(U32_BYTES, false) === 0) ? undefined : d.deserialize_string()
};
}
function deserialize_INSTALL_ERROR_CONTEXT(d) {
return {
operation: d.deserialize_string(),
source_path: (d.deserialize_number(U32_BYTES, false) === 0) ? undefined : d.deserialize_string(),
target_path: (d.deserialize_number(U32_BYTES, false) === 0) ? undefined : d.deserialize_string(),
file_path: (d.deserialize_number(U32_BYTES, false) === 0) ? undefined : d.deserialize_string(),
entry_path: (d.deserialize_number(U32_BYTES, false) === 0) ? undefined : d.deserialize_string(),
urls: d.deserialize_array(() => d.deserialize_string()),
expected_hash: (d.deserialize_number(U32_BYTES, false) === 0) ? undefined : d.deserialize_string(),
expected_size: (d.deserialize_number(U32_BYTES, false) === 0) ? undefined : d.deserialize_number(U64_BYTES, false),
project_id: (d.deserialize_number(U32_BYTES, false) === 0) ? undefined : d.deserialize_string(),
version_id: (d.deserialize_number(U32_BYTES, false) === 0) ? undefined : d.deserialize_string(),
minecraft_version: (d.deserialize_number(U32_BYTES, false) === 0) ? undefined : d.deserialize_string(),
loader: (d.deserialize_number(U32_BYTES, false) === 0) ? undefined : d.deserialize_string(),
java_version: (d.deserialize_number(U32_BYTES, false) === 0) ? undefined : d.deserialize_number(U32_BYTES, false),
os: (d.deserialize_number(U32_BYTES, false) === 0) ? undefined : d.deserialize_string(),
arch: (d.deserialize_number(U32_BYTES, false) === 0) ? undefined : d.deserialize_string()
};
}
function deserialize_IMPORT_LAUNCHER_TYPE(d) {
switch (d.deserialize_number(U32_BYTES, false)) {
case 0:
return {
tag: "MultiMC"
};
case 1:
return {
tag: "PrismLauncher"
};
case 2:
return {
tag: "ATLauncher"
};
case 3:
return {
tag: "GDLauncher"
};
case 4:
return {
tag: "Curseforge"
};
case 5:
return {
tag: "Unknown"
};
default:
throw "variant not implemented"
}
}
function deserialize_MOD_LOADER(d) {
switch (d.deserialize_number(U32_BYTES, false)) {
case 0:
return {
tag: "vanilla"
};
case 1:
return {
tag: "forge"
};
case 2:
return {
tag: "fabric"
};
case 3:
return {
tag: "quilt"
};
case 4:
return {
tag: "neoforge"
};
default:
throw "variant not implemented"
}
}
function deserialize_SHARED_INSTANCE_UNAVAILABLE_REASON(d) {
switch (d.deserialize_number(U32_BYTES, false)) {
case 0:
return {
tag: "deleted"
};
case 1:
return {
tag: "access_revoked"
};
case 2:
return {
tag: "quarantined"
};
default:
throw "variant not implemented"
}
}
/**
* Deserialize a value from an array of bytes.
* @param {string} type - The type of the value to deserialize.
* @param {Uint8Array} bytes - The byte array to deserialize from.
* @return {Object} The deserialized value and remaining bytes.
*/
function deserialize(type, bytes) {
if (!(typeof type === "string")) {
throw "type must be a string";
}
const d = new Deserializer(bytes);
var return_value = undefined;
switch (type) {
case "AppEvent":
return_value = deserialize_APP_EVENT(d);
break;
case "LoadingBarType":
return_value = deserialize_LOADING_BAR_TYPE(d);
break;
case "LoadingPayload":
return_value = deserialize_LOADING_PAYLOAD(d);
break;
case "WarningPayload":
return_value = deserialize_WARNING_PAYLOAD(d);
break;
case "InstanceBulkUpdateProgressPayload":
return_value = deserialize_INSTANCE_BULK_UPDATE_PROGRESS_PAYLOAD(d);
break;
case "InstanceBulkUpdateProgressStage":
return_value = deserialize_INSTANCE_BULK_UPDATE_PROGRESS_STAGE(d);
break;
case "CommandPayload":
return_value = deserialize_COMMAND_PAYLOAD(d);
break;
case "ProcessPayload":
return_value = deserialize_PROCESS_PAYLOAD(d);
break;
case "ProcessPayloadType":
return_value = deserialize_PROCESS_PAYLOAD_TYPE(d);
break;
case "InstancePayload":
return_value = deserialize_INSTANCE_PAYLOAD(d);
break;
case "InstancePayloadType":
return_value = deserialize_INSTANCE_PAYLOAD_TYPE(d);
break;
case "FriendPayload":
return_value = deserialize_FRIEND_PAYLOAD(d);
break;
case "FriendStatusPayload":
return_value = deserialize_FRIEND_STATUS_PAYLOAD(d);
break;
case "LogEvent":
return_value = deserialize_LOG_EVENT(d);
break;
case "LogPayload":
return_value = deserialize_LOG_PAYLOAD(d);
break;
case "Log4jEvent":
return_value = deserialize_LOG4J_EVENT(d);
break;
case "InstallJobSnapshot":
return_value = deserialize_INSTALL_JOB_SNAPSHOT(d);
break;
case "InstallJobKind":
return_value = deserialize_INSTALL_JOB_KIND(d);
break;
case "InstallJobStatus":
return_value = deserialize_INSTALL_JOB_STATUS(d);
break;
case "InstallTarget":
return_value = deserialize_INSTALL_TARGET(d);
break;
case "InstallPhaseId":
return_value = deserialize_INSTALL_PHASE_ID(d);
break;
case "InstallProgress":
return_value = deserialize_INSTALL_PROGRESS(d);
break;
case "InstallProgressSecondary":
return_value = deserialize_INSTALL_PROGRESS_SECONDARY(d);
break;
case "InstallPhaseDetails":
return_value = deserialize_INSTALL_PHASE_DETAILS(d);
break;
case "InstallJavaStep":
return_value = deserialize_INSTALL_JAVA_STEP(d);
break;
case "InstallJobDisplay":
return_value = deserialize_INSTALL_JOB_DISPLAY(d);
break;
case "InstallErrorView":
return_value = deserialize_INSTALL_ERROR_VIEW(d);
break;
case "InstallApiErrorDetails":
return_value = deserialize_INSTALL_API_ERROR_DETAILS(d);
break;
case "InstallErrorContext":
return_value = deserialize_INSTALL_ERROR_CONTEXT(d);
break;
case "ImportLauncherType":
return_value = deserialize_IMPORT_LAUNCHER_TYPE(d);
break;
case "ModLoader":
return_value = deserialize_MOD_LOADER(d);
break;
case "SharedInstanceUnavailableReason":
return_value = deserialize_SHARED_INSTANCE_UNAVAILABLE_REASON(d);
break;
default:
throw "type not implemented";
}
return { value: return_value, bytes: d.release_bytes() };
}
export {
deserialize
};
@@ -0,0 +1,8 @@
{
"name": "postcard",
"description": "Auto generated bindings for postcard format serializing and deserializing javascript to and from bytes.",
"version": "0.0.0",
"main": "index.js",
"types": "index.d.ts",
"type": "module"
}
+21 -7
View File
@@ -1,5 +1,4 @@
import { invoke } from '@tauri-apps/api/core'
import { listen } from '@tauri-apps/api/event'
export async function init_ads_window(overrideShown = false) {
return await invoke('plugin:ads|init_ads_window', {
@@ -8,8 +7,27 @@ export async function init_ads_window(overrideShown = false) {
})
}
export async function show_ads_window() {
return await invoke('plugin:ads|show_ads_window', { dpr: window.devicePixelRatio })
let adsWindowHoldUpdate = Promise.resolve()
async function update_ads_window_hold(acquire) {
adsWindowHoldUpdate = adsWindowHoldUpdate
.catch(() => {})
.then(() =>
invoke('plugin:ads|update_ads_window_hold', {
acquire,
dpr: window.devicePixelRatio,
}),
)
return await adsWindowHoldUpdate
}
export async function take_ads_window_hold() {
return await update_ads_window_hold(true)
}
export async function release_ads_window_hold() {
return await update_ads_window_hold(false)
}
export async function hide_ads_window(reset) {
@@ -28,10 +46,6 @@ export async function open_ads_consent_preferences() {
return await invoke('plugin:ads|open_ads_consent_preferences')
}
export async function ads_consent_listener(callback) {
return await listen('ads-consent-required', (event) => callback(event.payload))
}
export async function record_ads_click() {
return await invoke('plugin:ads|record_ads_click')
}
-137
View File
@@ -1,137 +0,0 @@
/*
Event listeners for interacting with the Rust api
These are all async functions that return a promise that resolves to the payload object (whatever Rust is trying to deliver)
*/
/*
callback is a function that takes a single argument, which is the payload object (whatever Rust is trying to deliver)
You can call these to await any kind of emitted signal from Rust, and then do something with the payload object
An example place to put this is at the start of main.js before the state is initialized- that way
you can listen for any emitted signal from Rust and do something with it as the state is being initialized
Example:
import { loading_listener } from '@/helpers/events'
await loading_listener((event) => {
// event.event is the event name (useful if you want to use a single callback fn for multiple event types)
// event.payload is the payload object
console.log(event)
})
Putting that in a script will print any emitted signal from rust
*/
import { listen } from '@tauri-apps/api/event'
/// Payload for the 'loading' event
/*
LoadingPayload {
event: {
type: string, one of "StateInit", "PackDownload", etc
(Optional fields depending on event type)
pack_name: name of the pack
pack_id, optional, the id of the modpack
pack_version, optional, the version of the modpack
instance_name: name of the instance
instance_id: unique identification of the instance
}
loader_uuid: unique identification of the loading bar
fraction: number, (as a fraction of 1, how much we've loaded so far). If null, by convention, loading is finished
message: message to display to the user
}
*/
export async function loading_listener(callback) {
return await listen('loading', (event) => callback(event.payload))
}
/// Payload for the 'process' event
/*
ProcessPayload {
uuid: unique identification of the process in the state (currently identified by PID, but that will change)
pid: process ID
event: event type ("Launched", "Finished")
message: message to display to the user
}
*/
export async function process_listener(callback) {
return await listen('process', (event) => callback(event.payload))
}
/// Payload for the 'instance' event
/*
InstancePayload {
instance_id: unique identification of the instance
event: event type ("Created", "Added", "Edited", "Removed")
}
*/
export async function instance_listener(callback) {
return await listen('instance', (event) => callback(event.payload))
}
/// Payload for the 'instance_bulk_update_progress' event
/*
InstanceBulkUpdateProgress {
instanceId: string
stage: "resolving_versions" | "downloading" | "finishing"
current: number
total: number
}
*/
export async function instance_bulk_update_progress_listener(callback) {
return await listen('instance_bulk_update_progress', (event) => callback(event.payload))
}
export async function install_job_listener(callback) {
return await listen('install_job', (event) => callback(event.payload))
}
/// Payload for the 'command' event
/*
CommandPayload {
event: event type ("InstallMod", "InstallModpack", "InstallVersion"),
id: string id of the mod/modpack/version to install
}
*/
export async function command_listener(callback) {
return await listen('command', (event) => {
callback(event.payload)
})
}
/// Payload for the 'warning' event
/*
WarningPayload {
message: message to display to the user
}
*/
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))
}
export async function notification_listener(callback) {
return await listen('notification', (event) => callback(event.payload))
}
/// Payload for the 'log' event
/*
LogPayload {
instance_id: string,
type: "log4j" | "legacy",
// log4j fields (when type === "log4j"):
timestamp_millis?: number,
logger_name?: string,
level?: string,
thread_name?: string,
message?: string,
throwable?: string,
// legacy fields (when type === "legacy"):
message?: string,
}
*/
export async function log_listener(callback) {
return await listen('log', (event) => callback(event.payload))
}
+24 -125
View File
@@ -1,8 +1,28 @@
import { invoke } from '@tauri-apps/api/core'
import { install_job_listener } from './events'
import type { InstallErrorView } from '@/generated/app-events/InstallErrorView'
import type { InstallJavaStep } from '@/generated/app-events/InstallJavaStep'
import type { InstallJobSnapshot } from '@/generated/app-events/InstallJobSnapshot'
import type { InstallJobStatus } from '@/generated/app-events/InstallJobStatus'
import type { InstallPhaseId } from '@/generated/app-events/InstallPhaseId'
import type { InstallProgress } from '@/generated/app-events/InstallProgress'
import type { InstallProgressSecondary } from '@/generated/app-events/InstallProgressSecondary'
import type { SharedInstanceUnavailableReason } from '@/generated/app-events/SharedInstanceUnavailableReason'
import type { AppEvents } from '@/providers/app-events'
import type { InstanceLink, InstanceLoader } from './types'
export type {
InstallErrorView,
InstallJavaStep,
InstallJobSnapshot,
InstallJobStatus,
InstallPhaseId,
InstallProgress,
InstallProgressSecondary,
SharedInstanceUnavailableReason,
}
export interface PackLocationVersionId {
type: 'fromVersionId'
project_id: string
@@ -104,8 +124,6 @@ export interface SharedInstanceUpdateDiff {
export const SHARED_INSTANCE_UNAVAILABLE_ERROR_CODE = 'shared_instance_unavailable'
export const SHARED_INSTANCES_API_ERROR_CODE = 'shared_instances_api_error'
export type SharedInstanceUnavailableReason = 'deleted' | 'access_revoked' | 'quarantined'
function isRecord(value: unknown): value is Record<string, unknown> {
return typeof value === 'object' && value !== null
}
@@ -136,116 +154,6 @@ export function getErrorMessage(error: unknown): string {
return 'Unknown error'
}
export type InstallJobStatus =
| 'queued'
| 'running'
| 'succeeded'
| 'failed'
| 'interrupted'
| 'canceled'
export type InstallPhaseId =
| 'preparing_instance'
| 'resolving_pack'
| 'downloading_pack_file'
| 'reading_pack_manifest'
| 'downloading_content'
| 'extracting_overrides'
| 'resolving_minecraft'
| 'resolving_loader'
| 'preparing_java'
| 'downloading_minecraft'
| 'running_loader_processors'
| 'finalizing'
| 'rolling_back'
export interface InstallProgress {
current: number
total: number
secondary?: InstallProgressSecondary | null
}
export interface InstallProgressSecondary {
current: number
total: number
}
export type InstallJavaStep =
| 'resolving'
| 'fetching_metadata'
| 'downloading'
| 'extracting'
| 'validating'
export interface InstallErrorView {
code: string
phase?: InstallPhaseId | null
message: string
reason?: SharedInstanceUnavailableReason | null
api?: {
error: string
status?: number | null
method?: string | null
url?: string | null
route?: string | null
} | null
context?: {
operation: string
source_path?: string | null
target_path?: string | null
file_path?: string | null
entry_path?: string | null
urls?: string[]
expected_hash?: string | null
expected_size?: number | null
project_id?: string | null
version_id?: string | null
minecraft_version?: string | null
loader?: string | null
java_version?: number | null
os?: string | null
arch?: string | null
} | null
}
export interface InstallJobSnapshot {
job_id: string
instance_id?: string | null
kind:
| 'create_instance'
| 'create_modpack_instance'
| 'create_shared_instance'
| 'update_shared_instance'
| 'import_instance'
| 'duplicate_instance'
| 'install_existing_instance'
| 'install_pack_to_existing_instance'
status: InstallJobStatus
target:
| { type: 'new_instance'; instance_id?: string | null }
| { type: 'existing_instance'; instance_id: string }
phase: InstallPhaseId
progress?: InstallProgress | null
details:
| { type: 'empty' }
| { type: 'instance'; name: string }
| { type: 'minecraft'; game_version: string; loader: InstanceLoader }
| { type: 'java'; major_version: number; step: InstallJavaStep }
| {
type: 'modpack'
project_id?: string | null
version_id?: string | null
title?: string | null
}
| { type: 'import'; launcher_type: string; instance_folder: string }
display?: { title: string; icon?: string | null } | null
error?: InstallErrorView | null
rollback_error?: InstallErrorView | null
created: string
modified: string
finished?: string | null
}
export async function install_get_modpack_preview(location: CreatePackLocation) {
return await invoke<InstallModpackPreview>('plugin:install|install_get_modpack_preview', {
location,
@@ -399,7 +307,7 @@ function settleInstallJob(job: InstallJobSnapshot) {
throw new Error(`Install job ${job.job_id} ${job.status}`)
}
export async function wait_for_install_job(jobId: string) {
export async function wait_for_install_job(events: AppEvents, jobId: string) {
const current = await install_job_get(jobId)
if (isInstallJobFinished(current.status)) return settleInstallJob(current)
@@ -434,16 +342,7 @@ export async function wait_for_install_job(jobId: string) {
reject(err)
}
install_job_listener(resolveJob)
.then((listener) => {
if (finished) {
listener()
return
}
unlisten = listener
install_job_get(jobId).then(resolveJob).catch(rejectWait)
})
.catch(rejectWait)
unlisten = events.on('install_job', resolveJob)
install_job_get(jobId).then(resolveJob).catch(rejectWait)
})
}
@@ -1,17 +1,10 @@
import type {
ContentItem,
ContentModpackCardCategory,
ContentModpackCardProject,
ContentModpackCardVersion,
ContentOwner,
} from '@modrinth/ui'
import type { ContentItem, ManagedContentProject, ManagedContentVersion } from '@modrinth/ui'
import {
get_content_items,
get_linked_modpack_info,
type LinkedModpackInfo,
} from '@/helpers/instance'
import { get_categories } from '@/helpers/tags.js'
import type { CacheBehaviour } from '@/helpers/types'
export type InstanceContentData = {
@@ -21,11 +14,8 @@ export type InstanceContentData = {
}
export type InstanceContentModpackData = {
project: ContentModpackCardProject
version: ContentModpackCardVersion
owner: ContentOwner | null
categories: ContentModpackCardCategory[]
hasUpdate: boolean
project: ManagedContentProject
version: ManagedContentVersion
updateVersionId: string | null
}
@@ -34,19 +24,15 @@ export async function loadInstanceContentData(
cacheBehaviour?: CacheBehaviour,
onError?: (error: Error) => unknown,
): Promise<InstanceContentData> {
const [contentItems, modpackInfo, allCategories] = await Promise.all([
const [contentItems, modpackInfo] = await Promise.all([
get_content_items(path, cacheBehaviour).catch((error) => handleLoadError(error, onError)),
get_linked_modpack_info(path, cacheBehaviour).catch((error) => handleLoadError(error, onError)),
get_categories().catch((error) => handleLoadError(error, onError)),
])
return {
path,
contentItems: (contentItems as ContentItem[] | null | undefined) ?? null,
modpack: normalizeLinkedModpackInfo(
modpackInfo as LinkedModpackInfo | null | undefined,
allCategories as ContentModpackCardCategory[] | null | undefined,
),
modpack: normalizeLinkedModpackInfo(modpackInfo as LinkedModpackInfo | null | undefined),
}
}
@@ -58,7 +44,6 @@ function handleLoadError(error: unknown, onError?: (error: Error) => unknown) {
function normalizeLinkedModpackInfo(
modpackInfo: LinkedModpackInfo | null | undefined,
allCategories: ContentModpackCardCategory[] | null | undefined,
): InstanceContentModpackData | null {
if (!modpackInfo) return null
@@ -72,30 +57,6 @@ function normalizeLinkedModpackInfo(
...modpackInfo.version,
date_published: modpackInfo.version.date_published.toString(),
},
owner: modpackInfo.owner
? {
...modpackInfo.owner,
avatar_url: modpackInfo.owner.avatar_url ?? undefined,
}
: null,
categories: resolveLinkedModpackCategories(modpackInfo, allCategories),
hasUpdate: modpackInfo.has_update,
updateVersionId: modpackInfo.update_version_id,
}
}
function resolveLinkedModpackCategories(
modpackInfo: LinkedModpackInfo,
allCategories: ContentModpackCardCategory[] | null | undefined,
) {
if (!allCategories || !modpackInfo.project.categories) return []
const seen = new Set<string>()
return allCategories.filter((category) => {
if (modpackInfo.project.categories.includes(category.name) && !seen.has(category.name)) {
seen.add(category.name)
return true
}
return false
})
}
+42 -6
View File
@@ -5,7 +5,7 @@
*/
import type { Labrinth } from '@modrinth/api-client'
import type { ContentItem, ContentOwner } from '@modrinth/ui'
import { invoke } from '@tauri-apps/api/core'
import { convertFileSrc, invoke } from '@tauri-apps/api/core'
import type { InstallJobSnapshot, SharedInstanceUpdateDiff } from './install'
import type {
@@ -74,7 +74,11 @@ export async function get_content_items(
instanceId: string,
cacheBehaviour?: CacheBehaviour,
): Promise<ContentItem[]> {
return await invoke('plugin:instance|instance_get_content_items', { instanceId, cacheBehaviour })
const items = await invoke<ContentItem[]>('plugin:instance|instance_get_content_items', {
instanceId,
cacheBehaviour,
})
return adaptContentItems(items)
}
export async function refresh_content_updates(instanceId: string): Promise<void> {
@@ -111,10 +115,11 @@ export async function get_linked_modpack_content(
instanceId: string,
cacheBehaviour?: CacheBehaviour,
): Promise<ContentItem[]> {
return await invoke('plugin:instance|instance_get_linked_modpack_content', {
const items = await invoke<ContentItem[]>('plugin:instance|instance_get_linked_modpack_content', {
instanceId,
cacheBehaviour,
})
return adaptContentItems(items)
}
// Convert a list of dependencies into ContentItems with rich metadata
@@ -122,9 +127,28 @@ export async function get_dependencies_as_content_items(
dependencies: Labrinth.Versions.v3.Dependency[],
cacheBehaviour?: CacheBehaviour,
): Promise<ContentItem[]> {
return await invoke('plugin:instance|instance_get_dependencies_as_content_items', {
dependencies,
cacheBehaviour,
const items = await invoke<ContentItem[]>(
'plugin:instance|instance_get_dependencies_as_content_items',
{
dependencies,
cacheBehaviour,
},
)
return adaptContentItems(items)
}
function adaptContentItems(items: ContentItem[]): ContentItem[] {
return items.map((item) => {
const embeddedMetadata = item.embedded_metadata
if (!embeddedMetadata?.icon_path) return item
return {
...item,
embedded_metadata: {
...embeddedMetadata,
icon_url: convertFileSrc(embeddedMetadata.icon_path),
},
}
})
}
@@ -264,6 +288,18 @@ export async function toggle_disable_project(
})
}
export async function set_project_locked(
instanceId: string,
projectPath: string,
locked: boolean,
): Promise<void> {
return await invoke('plugin:instance|instance_set_project_locked', {
instanceId,
projectPath,
locked,
})
}
// Remove a project
export async function remove_project(instanceId: string, projectPath: string): Promise<void> {
return await invoke('plugin:instance|instance_remove_project', { instanceId, projectPath })
+6 -16
View File
@@ -3,7 +3,9 @@
* So, for example, addDefaultInstance creates a blank instance object, where the Rust struct is serialized,
* and deserialized into a usable JS object.
*/
import { invoke } from '@tauri-apps/api/core'
import { type Channel, invoke } from '@tauri-apps/api/core'
import type { AppEvent } from '@/generated/app-events/AppEvent'
export interface LoadingBarType {
type?: string
@@ -24,24 +26,12 @@ export interface LoadingBar {
bar_type?: LoadingBarType
}
export type OpeningCommandEvent =
| 'RunMRPack'
| 'InstallServer'
| 'InstallVersion'
| 'InstallMod'
| 'InstallModpack'
| string
export interface OpeningCommand {
event: OpeningCommandEvent
id?: string
path?: string
}
export type OpeningCommand = Extract<AppEvent, { type: 'command' }>['payload']
// Initialize the theseus API state
// This should be called during the initializion/opening of the launcher
export async function initialize_state() {
return await invoke<void>('initialize_state')
export async function initialize_state(events: Channel<ArrayBuffer>) {
return await invoke<void>('initialize_state', { events })
}
// Gets active progress bars
+1
View File
@@ -128,6 +128,7 @@ export type ContentSourceKind =
type ContentFile = {
enabled: boolean
locked: boolean
source_kind?: ContentSourceKind | null
metadata?: {
project_id: string
+2 -15
View File
@@ -3,6 +3,7 @@ import { autoToHTML } from '@sfirew/minecraft-motd-parser'
import { invoke } from '@tauri-apps/api/core'
import dayjs from 'dayjs'
import type { InstancePayload } from '@/generated/app-events/InstancePayload'
import { get_full_path } from '@/helpers/instance'
import { openPath } from '@/helpers/utils'
@@ -532,18 +533,4 @@ export function hasWorldQuickPlaySupport(gameVersions: GameVersion[], currentVer
return versionIndex !== -1 && targetIndex !== -1 && versionIndex <= targetIndex
}
export type InstanceEvent = { instance_id: string } & (
| {
event: 'servers_updated'
}
| {
event: 'world_updated'
world: string
}
| {
event: 'server_joined'
host: string
port: number
timestamp: string
}
)
export type InstanceEvent = InstancePayload
@@ -1073,16 +1073,7 @@
"search.filter.locked.instance.sync": {
"message": "مزامنة مع النسخة"
},
"search.filter.locked.server": {
"message": "يقدمها الخادم"
},
"search.filter.locked.server-environment.title": {
"message": "يمكن إضافة التعديلات **المحليه** فقط إلى نموذج الخادم"
},
"search.filter.locked.server-game-version.title": {
"message": "يتم توفير نسخة اللعبة من قبل الخادم"
},
"search.filter.locked.server-loader.title": {
"message": "يتم توفير المحمّل من قبل الخادم"
}
}
@@ -965,16 +965,7 @@
"search.filter.locked.instance.sync": {
"message": "Synchronizováno s instancí"
},
"search.filter.locked.server": {
"message": "Poskytováno serverem"
},
"search.filter.locked.server-environment.title": {
"message": "Pouze módy ze strany klientu mohou být přidány na server"
},
"search.filter.locked.server-game-version.title": {
"message": "Verze hry je poskytována serverem"
},
"search.filter.locked.server-loader.title": {
"message": "Loader zprostředkovává server"
}
}
@@ -296,18 +296,9 @@
"app.instance.admonitions.shared-instance.review-header": {
"message": "Gennemså ændringer"
},
"app.instance.admonitions.shared-instance.review-update-button": {
"message": "Gennemgå opdatering"
},
"app.instance.admonitions.shared-instance.reviewing-button": {
"message": "Gennemgår..."
},
"app.instance.admonitions.shared-instance.update-available-body": {
"message": "En opdatering er krævet for at spille {name}. Venligst opdater til den seneste version for at køre spillet."
},
"app.instance.admonitions.shared-instance.update-available-header": {
"message": "En opdatering er tilgængelig"
},
"app.instance.confirm-delete.admonition-body": {
"message": "Al' data for din instance vil blive permanent slettet, dette inkludere dine verdener, konfigurationer, og alt installeret indhold."
},
@@ -470,9 +461,6 @@
"app.modal.install-to-play.report-image-too-large": {
"message": "Filen overskrider 1MiB størrelse begrænsning"
},
"app.modal.install-to-play.report-reason.spam": {
"message": "Spam"
},
"app.modal.install-to-play.shared-instance": {
"message": "Delt instance"
},
@@ -1181,16 +1169,7 @@
"search.filter.locked.instance.sync": {
"message": "Synkroniser med instance"
},
"search.filter.locked.server": {
"message": "Givet af serveren"
},
"search.filter.locked.server-environment.title": {
"message": "Kun klient-sided mods kan blive tilføjet til denne server instance"
},
"search.filter.locked.server-game-version.title": {
"message": "Spille version er givet af serveren"
},
"search.filter.locked.server-loader.title": {
"message": "Loader er givet af serveren"
}
}
@@ -377,18 +377,9 @@
"app.instance.admonitions.shared-instance.review-header": {
"message": "Änderungen überprüfen"
},
"app.instance.admonitions.shared-instance.review-update-button": {
"message": "Update überprüfen"
},
"app.instance.admonitions.shared-instance.reviewing-button": {
"message": "Wird überprüft..."
},
"app.instance.admonitions.shared-instance.update-available-body": {
"message": "Ein Update ist erforderlich, um {name} zu spielen. Bitte aktualisiere auf die neueste Version, um das Spiel zu starten."
},
"app.instance.admonitions.shared-instance.update-available-header": {
"message": "Ein Update ist verfügbar"
},
"app.instance.confirm-delete.admonition-body": {
"message": "Alle Daten deiner Instanz werden permanent gelöscht, inlusive deiner Welten, Konfigurationen und allen installierten Inhalten."
},
@@ -704,15 +695,6 @@
"app.modal.install-to-play.report-reason": {
"message": "Gegen welche Regel verstößt diese Instanz?"
},
"app.modal.install-to-play.report-reason.inappropriate": {
"message": "Unangemessen"
},
"app.modal.install-to-play.report-reason.malicious": {
"message": "Bösartig"
},
"app.modal.install-to-play.report-reason.spam": {
"message": "Spam"
},
"app.modal.install-to-play.report-shared-instance-header": {
"message": "Geteilte Instanz melden"
},
@@ -1922,18 +1904,9 @@
"search.filter.locked.instance.sync": {
"message": "Mit Instanz synchronisieren"
},
"search.filter.locked.server": {
"message": "Vom Server bereitgestellt"
},
"search.filter.locked.server-environment.title": {
"message": "Nur Clientseitige Mods können der Serverinstanz hinzugefügt werden"
},
"search.filter.locked.server-game-version.title": {
"message": "Spielversion wird vom Server bereitgestellt"
},
"search.filter.locked.server-loader.title": {
"message": "Loader wird vom Server bereitgestellt"
},
"settings.sidebar.label.account": {
"message": "Konto"
},
@@ -377,18 +377,9 @@
"app.instance.admonitions.shared-instance.review-header": {
"message": "Änderungen überprüfen"
},
"app.instance.admonitions.shared-instance.review-update-button": {
"message": "Update überprüfen"
},
"app.instance.admonitions.shared-instance.reviewing-button": {
"message": "Wird überprüft..."
},
"app.instance.admonitions.shared-instance.update-available-body": {
"message": "Ein Update ist erforderlich, um {name} zu spielen. Bitte aktualisiere auf die neueste Version, um das Spiel zu starten."
},
"app.instance.admonitions.shared-instance.update-available-header": {
"message": "Ein Update ist verfügbar"
},
"app.instance.confirm-delete.admonition-body": {
"message": "Alle Daten deiner Instanz werden permanent gelöscht, einschließlich deiner Welten, Konfigurationen und allen installierten Inhalten."
},
@@ -704,15 +695,6 @@
"app.modal.install-to-play.report-reason": {
"message": "Gegen welche Regel verstößt diese Instanz?"
},
"app.modal.install-to-play.report-reason.inappropriate": {
"message": "Unangemessen"
},
"app.modal.install-to-play.report-reason.malicious": {
"message": "Bösartig"
},
"app.modal.install-to-play.report-reason.spam": {
"message": "Spam"
},
"app.modal.install-to-play.report-shared-instance-header": {
"message": "Geteilte Instanz melden"
},
@@ -1922,18 +1904,9 @@
"search.filter.locked.instance.sync": {
"message": "Mit Instanz synchronisieren"
},
"search.filter.locked.server": {
"message": "Vom Server vorgegeben"
},
"search.filter.locked.server-environment.title": {
"message": "Nur clientseitige Mods können der Serverinstanz hinzugefügt werden"
},
"search.filter.locked.server-game-version.title": {
"message": "Spielversion vom Server vorgegeben"
},
"search.filter.locked.server-loader.title": {
"message": "Loader vom Server vorgegeben"
},
"settings.sidebar.label.account": {
"message": "Konto"
},
+43 -28
View File
@@ -8,6 +8,9 @@
"app.action-bar.downloads": {
"message": "Downloads"
},
"app.action-bar.hide-downloads": {
"message": "Hide active downloads"
},
"app.action-bar.hide-more-running-instances": {
"message": "Hide more running instances"
},
@@ -110,6 +113,9 @@
"app.action-bar.reload-to-update": {
"message": "Reload to update"
},
"app.action-bar.show-downloads": {
"message": "Show active downloads"
},
"app.action-bar.show-more-running-instances": {
"message": "Show more running instances"
},
@@ -267,7 +273,7 @@
"message": "Modpacks"
},
"app.browse.server-instance-content-warning": {
"message": "Adding content can break compatibility when joining the server. Any added content will also be lost when you update the server instance content."
"message": "Adding content may prevent you from joining this server. Any content you add will be removed when the managed server content is updated."
},
"app.browse.server.installing": {
"message": "Installing"
@@ -281,6 +287,12 @@
"app.export-modal.export-button": {
"message": "Export"
},
"app.export-modal.export-complete": {
"message": "Export complete"
},
"app.export-modal.export-complete-description": {
"message": "{name} was exported successfully."
},
"app.export-modal.header": {
"message": "Export modpack"
},
@@ -296,6 +308,18 @@
"app.export-modal.version-number-placeholder": {
"message": "1.0.0"
},
"app.hosting.update-required.description": {
"message": "You need to update to use Modrinth Hosting through the Modrinth App"
},
"app.hosting.update-required.download": {
"message": "Download to update"
},
"app.hosting.update-required.rinthbot-alt": {
"message": "Excited Modrinth Bot"
},
"app.hosting.update-required.title": {
"message": "Modrinth App update required"
},
"app.install.phase.downloading_content": {
"message": "Downloading content"
},
@@ -377,18 +401,9 @@
"app.instance.admonitions.shared-instance.review-header": {
"message": "Review changes"
},
"app.instance.admonitions.shared-instance.review-update-button": {
"message": "Review update"
},
"app.instance.admonitions.shared-instance.reviewing-button": {
"message": "Reviewing..."
},
"app.instance.admonitions.shared-instance.update-available-body": {
"message": "An update is required to play {name}. Please update to latest version to launch the game."
},
"app.instance.admonitions.shared-instance.update-available-header": {
"message": "An update is available"
},
"app.instance.confirm-delete.admonition-body": {
"message": "All data for your instance will be permanently deleted, including your worlds, configs, and all installed content."
},
@@ -401,6 +416,12 @@
"app.instance.confirm-delete.header": {
"message": "Delete instance"
},
"app.instance.content.managed-content.modpack-header": {
"message": "Modpack content"
},
"app.instance.content.managed-content.shared-header": {
"message": "Shared content"
},
"app.instance.modpack-already-installed.body": {
"message": "This modpack is already installed in the <bold>{instanceName}</bold> instance. Are you sure you want to duplicate it?"
},
@@ -425,6 +446,9 @@
"app.instance.mods.content-type-project": {
"message": "project"
},
"app.instance.mods.freeze-content": {
"message": "Freeze version"
},
"app.instance.mods.locked-content": {
"message": "Content in locked instances cannot be changed."
},
@@ -443,6 +467,9 @@
"app.instance.mods.successfully-uploaded": {
"message": "Successfully uploaded"
},
"app.instance.mods.unfreeze-content": {
"message": "Unfreeze version"
},
"app.instance.share.empty.description": {
"message": "You can share this instance with your friends!"
},
@@ -686,6 +713,9 @@
"app.modal.install-to-play.invite-warning-with-creator": {
"message": "This invite was created by <creator>{username}</creator>, not Modrinth. Only accept invites from people you trust."
},
"app.modal.install-to-play.managed-content.modpack-header": {
"message": "Modpack content"
},
"app.modal.install-to-play.mod-count": {
"message": "{count, plural, one {# mod} other {# mods}}"
},
@@ -704,15 +734,6 @@
"app.modal.install-to-play.report-reason": {
"message": "Which rule does this instance violate?"
},
"app.modal.install-to-play.report-reason.inappropriate": {
"message": "Inappropriate"
},
"app.modal.install-to-play.report-reason.malicious": {
"message": "Malicious"
},
"app.modal.install-to-play.report-reason.spam": {
"message": "Spam"
},
"app.modal.install-to-play.report-shared-instance-header": {
"message": "Report shared instance"
},
@@ -1457,6 +1478,9 @@
"instance.files.save-as": {
"message": "Save as..."
},
"instance.last-played": {
"message": "Last played"
},
"instance.locked.delete-button": {
"message": "Delete instance"
},
@@ -1922,18 +1946,9 @@
"search.filter.locked.instance.sync": {
"message": "Sync with instance"
},
"search.filter.locked.server": {
"message": "Provided by the server"
},
"search.filter.locked.server-environment.title": {
"message": "Only client-side mods can be added to the server instance"
},
"search.filter.locked.server-game-version.title": {
"message": "Game version is provided by the server"
},
"search.filter.locked.server-loader.title": {
"message": "Loader is provided by the server"
},
"settings.sidebar.label.account": {
"message": "Account"
},
@@ -377,18 +377,9 @@
"app.instance.admonitions.shared-instance.review-header": {
"message": "Revisar cambios"
},
"app.instance.admonitions.shared-instance.review-update-button": {
"message": "Actualizar"
},
"app.instance.admonitions.shared-instance.reviewing-button": {
"message": "Actualizando..."
},
"app.instance.admonitions.shared-instance.update-available-body": {
"message": "Se necesita una actualización para jugar a {name}. Por favor actualiza a la versión más reciente para iniciar el juego."
},
"app.instance.admonitions.shared-instance.update-available-header": {
"message": "Hay una actualización disponible"
},
"app.instance.confirm-delete.admonition-body": {
"message": "Todos los datos de tu instancia se eliminarán permanentemente, incluidos tus mundos, configuraciones y todo el contenido instalado."
},
@@ -704,15 +695,6 @@
"app.modal.install-to-play.report-reason": {
"message": "¿Qué regla rompe esta instancia?"
},
"app.modal.install-to-play.report-reason.inappropriate": {
"message": "Inapropiado"
},
"app.modal.install-to-play.report-reason.malicious": {
"message": "Malicioso"
},
"app.modal.install-to-play.report-reason.spam": {
"message": "Spam"
},
"app.modal.install-to-play.report-shared-instance-header": {
"message": "Reportar instancia compartida"
},
@@ -1922,18 +1904,9 @@
"search.filter.locked.instance.sync": {
"message": "Sincronizar con la instancia"
},
"search.filter.locked.server": {
"message": "Proporcionado por el servidor"
},
"search.filter.locked.server-environment.title": {
"message": "Solo se pueden añadir mods que sean del lado del cliente a la instancia del servidor"
},
"search.filter.locked.server-game-version.title": {
"message": "La versión del juego es proporcionada por el servidor"
},
"search.filter.locked.server-loader.title": {
"message": "El loader es proporcionado por el servidor"
},
"settings.sidebar.label.account": {
"message": "Cuenta"
},
@@ -878,16 +878,7 @@
"search.filter.locked.instance.sync": {
"message": "Sincronizar con la instancia"
},
"search.filter.locked.server": {
"message": "Proporcionado por el servidor"
},
"search.filter.locked.server-environment.title": {
"message": "Solo se pueden añadir a la instancia del servidor modificaciones del lado del cliente"
},
"search.filter.locked.server-game-version.title": {
"message": "La versión del juego es proporcionada por el servidor"
},
"search.filter.locked.server-loader.title": {
"message": "Loader proporcionado por el servidor"
}
}
@@ -959,16 +959,7 @@
"search.filter.locked.instance.sync": {
"message": "Synkronoi instanssin kanssa"
},
"search.filter.locked.server": {
"message": "Palvelimen tarjoama"
},
"search.filter.locked.server-environment.title": {
"message": "Voit lisätä vain paikallisia modeja palvelin instanssiin"
},
"search.filter.locked.server-game-version.title": {
"message": "Peliversio on palvelimen tarjoama"
},
"search.filter.locked.server-loader.title": {
"message": "Modialusta on palvelimen tarjoama"
}
}
@@ -659,16 +659,7 @@
"search.filter.locked.instance.sync": {
"message": "Maki-sync sa instansiya"
},
"search.filter.locked.server": {
"message": "Handog ng server"
},
"search.filter.locked.server-environment.title": {
"message": "Mga mod sa panig ng client lamang ang maidadaragdag sa instansiyang server"
},
"search.filter.locked.server-game-version.title": {
"message": "Ang bersiyon ng laro ay handog ng server"
},
"search.filter.locked.server-loader.title": {
"message": "Ang loader ay handog na ng server"
}
}
@@ -377,18 +377,9 @@
"app.instance.admonitions.shared-instance.review-header": {
"message": "Examiner les modifications"
},
"app.instance.admonitions.shared-instance.review-update-button": {
"message": "Télécharger la mise à jour"
},
"app.instance.admonitions.shared-instance.reviewing-button": {
"message": "Examination..."
},
"app.instance.admonitions.shared-instance.update-available-body": {
"message": "Une mise à jour est nécessaire pour jouer à {name}. Veuillez mettre à jour vers la dernière version pour lancer le jeu."
},
"app.instance.admonitions.shared-instance.update-available-header": {
"message": "Une mise à jour est disponible"
},
"app.instance.confirm-delete.admonition-body": {
"message": "Toutes les données pour votre instance seront supprimées à jamais, y compris vos mondes, vos configurations, et le contenu installé."
},
@@ -704,15 +695,6 @@
"app.modal.install-to-play.report-reason": {
"message": "Quelle règle cette instance enfreint-elle ?"
},
"app.modal.install-to-play.report-reason.inappropriate": {
"message": "Inapproprié"
},
"app.modal.install-to-play.report-reason.malicious": {
"message": "Malicieux"
},
"app.modal.install-to-play.report-reason.spam": {
"message": "Spam"
},
"app.modal.install-to-play.report-shared-instance-header": {
"message": "Signaler une instance partagée"
},
@@ -1919,18 +1901,9 @@
"search.filter.locked.instance.sync": {
"message": "Synchroniser avec l'instance"
},
"search.filter.locked.server": {
"message": "Fournis par le serveur"
},
"search.filter.locked.server-environment.title": {
"message": "Seuls les mods client peuvent être ajoutés à l'instance du serveur"
},
"search.filter.locked.server-game-version.title": {
"message": "Version du jeu est procurée par le serveur"
},
"search.filter.locked.server-loader.title": {
"message": "Le loader est procuré par le serveur"
},
"settings.sidebar.label.account": {
"message": "Compte"
},
@@ -533,16 +533,7 @@
"search.filter.locked.instance.sync": {
"message": "סנכרן עם התקנה"
},
"search.filter.locked.server": {
"message": "מסופק על ידי השרת"
},
"search.filter.locked.server-environment.title": {
"message": "ניתן להוסיף רק מודים בצד הלקוח לשרת"
},
"search.filter.locked.server-game-version.title": {
"message": "גרסת המשחק מסופקת על ידי השרת"
},
"search.filter.locked.server-loader.title": {
"message": "הטוען מסופק על ידי השרת"
}
}
@@ -377,18 +377,9 @@
"app.instance.admonitions.shared-instance.review-header": {
"message": "Változtatások áttekintése"
},
"app.instance.admonitions.shared-instance.review-update-button": {
"message": "Frissítés áttekintése"
},
"app.instance.admonitions.shared-instance.reviewing-button": {
"message": "Áttekintés..."
},
"app.instance.admonitions.shared-instance.update-available-body": {
"message": "Egy frssítés szükséges a(z) {name} példányhoz. Kérjük, frissítsd a játékot a legújabb verzióra az indításhoz."
},
"app.instance.admonitions.shared-instance.update-available-header": {
"message": "Frissítés érhető el"
},
"app.instance.confirm-delete.admonition-body": {
"message": "A példányodhoz tartozó összes adat véglegesen törlődik, beleértve a világjaidat, a beállításaidat és az összes telepített tartalmat."
},
@@ -704,15 +695,6 @@
"app.modal.install-to-play.report-reason": {
"message": "Melyik szabályt sérti ez az adott példány?"
},
"app.modal.install-to-play.report-reason.inappropriate": {
"message": "Nem megfelelő"
},
"app.modal.install-to-play.report-reason.malicious": {
"message": "Káros"
},
"app.modal.install-to-play.report-reason.spam": {
"message": "Spam"
},
"app.modal.install-to-play.report-shared-instance-header": {
"message": "Megosztott példány jelentése"
},
@@ -1907,18 +1889,9 @@
"search.filter.locked.instance.sync": {
"message": "Szinkronizálás a példánnyal"
},
"search.filter.locked.server": {
"message": "A szerver által van megadva"
},
"search.filter.locked.server-environment.title": {
"message": "Szerverpéldányokhoz csak szerveroldali modok adhatók hozzá"
},
"search.filter.locked.server-game-version.title": {
"message": "A játékverzió a szerver által van megadva"
},
"search.filter.locked.server-loader.title": {
"message": "A betöltőt az adott szerver biztosítja"
},
"settings.sidebar.label.account": {
"message": "Fiók"
},
@@ -821,16 +821,7 @@
"search.filter.locked.instance.sync": {
"message": "Sinkronkan dengan instans"
},
"search.filter.locked.server": {
"message": "Disediakan oleh server"
},
"search.filter.locked.server-environment.title": {
"message": "Anda hanya dapat menambahkan mod sisi klien pada instans server"
},
"search.filter.locked.server-game-version.title": {
"message": "Versi permainan disediakan oleh server"
},
"search.filter.locked.server-loader.title": {
"message": "Pemuat disediakan oleh server"
}
}
@@ -377,18 +377,9 @@
"app.instance.admonitions.shared-instance.review-header": {
"message": "Revisiona modifiche"
},
"app.instance.admonitions.shared-instance.review-update-button": {
"message": "Revisiona aggiornamento"
},
"app.instance.admonitions.shared-instance.reviewing-button": {
"message": "Revisione..."
},
"app.instance.admonitions.shared-instance.update-available-body": {
"message": "{name} richiede degli aggiornamenti. Installa l'ultima versione per poter giocare."
},
"app.instance.admonitions.shared-instance.update-available-header": {
"message": "Aggiornamento disponibile"
},
"app.instance.confirm-delete.admonition-body": {
"message": "Tutti i dati della tua istanza verranno eliminati permanentemente, inclusi i tuoi mondi, configurazioni e tutti i contenuti installati."
},
@@ -701,15 +692,6 @@
"app.modal.install-to-play.report-reason": {
"message": "Quale regola è violata da quest'istanza?"
},
"app.modal.install-to-play.report-reason.inappropriate": {
"message": "Inappropriato"
},
"app.modal.install-to-play.report-reason.malicious": {
"message": "Dannoso"
},
"app.modal.install-to-play.report-reason.spam": {
"message": "Spam"
},
"app.modal.install-to-play.report-shared-instance-header": {
"message": "Segnala istanza condivisa"
},
@@ -1919,18 +1901,9 @@
"search.filter.locked.instance.sync": {
"message": "Sincronizza con l'istanza"
},
"search.filter.locked.server": {
"message": "Determinato dal server"
},
"search.filter.locked.server-environment.title": {
"message": "Solo mod lato client possono essere aggiunte all'istanza del server"
},
"search.filter.locked.server-game-version.title": {
"message": "La versione del gioco è determinata dal server"
},
"search.filter.locked.server-loader.title": {
"message": "Il loader è determinato dal server"
},
"settings.sidebar.label.account": {
"message": "Account"
},
@@ -377,18 +377,9 @@
"app.instance.admonitions.shared-instance.review-header": {
"message": "変更点を確認"
},
"app.instance.admonitions.shared-instance.review-update-button": {
"message": "アップデート内容を確認"
},
"app.instance.admonitions.shared-instance.reviewing-button": {
"message": "審査中…"
},
"app.instance.admonitions.shared-instance.update-available-body": {
"message": "{name}をプレイするにはアップデートが必要です。ゲームを起動するには最新バージョンにアップデートしてください。"
},
"app.instance.admonitions.shared-instance.update-available-header": {
"message": "アップデートが利用可能です"
},
"app.instance.confirm-delete.admonition-body": {
"message": "インスタンス内のすべてのデータは、ワールド、設定、インストール済みのコンテンツを含め、完全に削除されます。"
},
@@ -704,15 +695,6 @@
"app.modal.install-to-play.report-reason": {
"message": "この起動構成はどの規則に違反していますか?"
},
"app.modal.install-to-play.report-reason.inappropriate": {
"message": "不適切なコンテンツ"
},
"app.modal.install-to-play.report-reason.malicious": {
"message": "悪意がある"
},
"app.modal.install-to-play.report-reason.spam": {
"message": "スパム"
},
"app.modal.install-to-play.report-shared-instance-header": {
"message": "共有された起動構成を通報する"
},
@@ -1922,18 +1904,9 @@
"search.filter.locked.instance.sync": {
"message": "インスタンスと同期"
},
"search.filter.locked.server": {
"message": "サーバーによる条件"
},
"search.filter.locked.server-environment.title": {
"message": "サーバーインスタンスにはクライアント側Modのみ追加可能"
},
"search.filter.locked.server-game-version.title": {
"message": "ゲームバージョンはサーバーによる条件です"
},
"search.filter.locked.server-loader.title": {
"message": "ローダーはサーバーによる条件です"
},
"settings.sidebar.label.account": {
"message": "アカウント"
},
@@ -377,18 +377,9 @@
"app.instance.admonitions.shared-instance.review-header": {
"message": "Review changes"
},
"app.instance.admonitions.shared-instance.review-update-button": {
"message": "업데이트 검토"
},
"app.instance.admonitions.shared-instance.reviewing-button": {
"message": "검토중..."
},
"app.instance.admonitions.shared-instance.update-available-body": {
"message": "{name} 플레이를 위해 업데이트가 필요합니다. 게임을 실행하려면 최신 버전 업데이트를 진행하세요."
},
"app.instance.admonitions.shared-instance.update-available-header": {
"message": "업데이트가 있습니다"
},
"app.instance.confirm-delete.admonition-body": {
"message": "인스턴스의 모든 데이터가 삭제됩니다. 세계 폴더, 설정 폴더 등 설치된 모든 컨텐츠가 포함됩니다."
},
@@ -701,15 +692,6 @@
"app.modal.install-to-play.report-reason": {
"message": "이 인스턴스가 위반한 규칙은 무엇인가요?"
},
"app.modal.install-to-play.report-reason.inappropriate": {
"message": "부적절함"
},
"app.modal.install-to-play.report-reason.malicious": {
"message": "악성/유해함"
},
"app.modal.install-to-play.report-reason.spam": {
"message": "스팸"
},
"app.modal.install-to-play.report-shared-instance-header": {
"message": "공유 인스턴스 신고"
},
@@ -1895,18 +1877,9 @@
"search.filter.locked.instance.sync": {
"message": "인스턴스와 동기화"
},
"search.filter.locked.server": {
"message": "서버에 의해 관리됨"
},
"search.filter.locked.server-environment.title": {
"message": "서버 인스턴스에는 클라이언트 전용 모드만 추가할 수 있습니다"
},
"search.filter.locked.server-game-version.title": {
"message": "게임 버전이 서버에 의해 제공됩니다"
},
"search.filter.locked.server-loader.title": {
"message": "로더가 서버에 의해 제공됩니다"
},
"settings.sidebar.label.account": {
"message": "계정"
},
@@ -245,9 +245,6 @@
"app.modal.install-to-play.mod-count": {
"message": "{count, plural, other {# mod}}"
},
"app.modal.install-to-play.report-reason.spam": {
"message": "Spam"
},
"app.modal.install-to-play.shared-instance": {
"message": "Pemasangan yang dikongsi"
},
@@ -818,16 +815,7 @@
"search.filter.locked.instance.sync": {
"message": "Selaraskan dengan pemasangan"
},
"search.filter.locked.server": {
"message": "Disediakan oleh pelayan"
},
"search.filter.locked.server-environment.title": {
"message": "Hanya mod bahagian pelanggan sahaja yang boleh ditambah pada pemasangan pelayan"
},
"search.filter.locked.server-game-version.title": {
"message": "Versi permainan adalah disediakan oleh pelayan"
},
"search.filter.locked.server-loader.title": {
"message": "Pemuat adalah disediakan oleh pelayan"
}
}
@@ -377,18 +377,9 @@
"app.instance.admonitions.shared-instance.review-header": {
"message": "Wijzigingen bekijken"
},
"app.instance.admonitions.shared-instance.review-update-button": {
"message": "Update bekijken"
},
"app.instance.admonitions.shared-instance.reviewing-button": {
"message": "Controleren..."
},
"app.instance.admonitions.shared-instance.update-available-body": {
"message": "Er is een update nodig om {name} te kunnen spelen. Werk het spel bij naar de nieuwste versie om het te kunnen starten."
},
"app.instance.admonitions.shared-instance.update-available-header": {
"message": "Update is beschikbaar"
},
"app.instance.confirm-delete.admonition-body": {
"message": "Alle gegevens van je instantie worden definitief verwijderd, inclusief je werelden, configuraties en alle geïnstalleerde inhoud."
},
@@ -704,15 +695,6 @@
"app.modal.install-to-play.report-reason": {
"message": "Welke regel wordt in deze instantie overtreden?"
},
"app.modal.install-to-play.report-reason.inappropriate": {
"message": "Ongepast"
},
"app.modal.install-to-play.report-reason.malicious": {
"message": "Kwaadaardig"
},
"app.modal.install-to-play.report-reason.spam": {
"message": "Spam"
},
"app.modal.install-to-play.report-shared-instance-header": {
"message": "Gedeelde instantie melden"
},
@@ -1922,18 +1904,9 @@
"search.filter.locked.instance.sync": {
"message": "Synchroniseer installatie"
},
"search.filter.locked.server": {
"message": "Aangeleverd door de server"
},
"search.filter.locked.server-environment.title": {
"message": "Alleen client-side mods kunnen toegevoegd worden aan de server instantie"
},
"search.filter.locked.server-game-version.title": {
"message": "Spel versie is gegeven door de server"
},
"search.filter.locked.server-loader.title": {
"message": "Loader is gegeven door de server"
},
"settings.sidebar.label.account": {
"message": "Account"
},
@@ -371,15 +371,9 @@
"app.instance.admonitions.shared-instance.review-header": {
"message": "Sprawdź zmiany"
},
"app.instance.admonitions.shared-instance.review-update-button": {
"message": "Sprawdź aktualizację"
},
"app.instance.admonitions.shared-instance.reviewing-button": {
"message": "Trwa przegląd..."
},
"app.instance.admonitions.shared-instance.update-available-header": {
"message": "Dostępna aktualizacja"
},
"app.instance.confirm-delete.admonition-body": {
"message": "Wszystkie dane z Twojej instancji zostaną trwale usunięte, w tym Twoje światy, pliki konfiguracji i jakakolwiek dodana zawartość."
},
@@ -668,15 +662,6 @@
"app.modal.install-to-play.report-reason": {
"message": "Którą zasadę narusza ta instancja?"
},
"app.modal.install-to-play.report-reason.inappropriate": {
"message": "Treści nieodpowiednie"
},
"app.modal.install-to-play.report-reason.malicious": {
"message": "Zawiera Złośliwy kod"
},
"app.modal.install-to-play.report-reason.spam": {
"message": "Spam"
},
"app.modal.install-to-play.report-shared-instance-header": {
"message": "Zgłoś przesłaną Instalacje"
},
@@ -1703,18 +1688,9 @@
"search.filter.locked.instance.sync": {
"message": "Synchronizuj z instancją"
},
"search.filter.locked.server": {
"message": "Dostarczone przez serwer"
},
"search.filter.locked.server-environment.title": {
"message": "Tylko mody po stronie klienta mogą być dodane do instancji serwera"
},
"search.filter.locked.server-game-version.title": {
"message": "Wersja gry jest dostarczona przez serwer"
},
"search.filter.locked.server-loader.title": {
"message": "Loader jest dostarczony przez serwer"
},
"settings.sidebar.label.account": {
"message": "Konto"
},
@@ -377,18 +377,9 @@
"app.instance.admonitions.shared-instance.review-header": {
"message": "Revisar alterações"
},
"app.instance.admonitions.shared-instance.review-update-button": {
"message": "Revisar atualização"
},
"app.instance.admonitions.shared-instance.reviewing-button": {
"message": "Revisando..."
},
"app.instance.admonitions.shared-instance.update-available-body": {
"message": "Uma atualização é necessária para jogar {name}. Atualize para a versão mais recente para iniciar o jogo."
},
"app.instance.admonitions.shared-instance.update-available-header": {
"message": "Uma atualização está disponível"
},
"app.instance.confirm-delete.admonition-body": {
"message": "Todos os dados para sua instância serão excluídos permanentemente, incluindo seus mundos, configs e todo o conteúdo instalado."
},
@@ -704,15 +695,6 @@
"app.modal.install-to-play.report-reason": {
"message": "Qual regra esta instância viola?"
},
"app.modal.install-to-play.report-reason.inappropriate": {
"message": "Inapropriado"
},
"app.modal.install-to-play.report-reason.malicious": {
"message": "Malicioso"
},
"app.modal.install-to-play.report-reason.spam": {
"message": "Spam"
},
"app.modal.install-to-play.report-shared-instance-header": {
"message": "Denunciar instância compartilhada"
},
@@ -1922,18 +1904,9 @@
"search.filter.locked.instance.sync": {
"message": "Sincronizar com a instância"
},
"search.filter.locked.server": {
"message": "Fornecido pelo servidor"
},
"search.filter.locked.server-environment.title": {
"message": "Somente mods do cliente podem ser adicionados à instância do servidor"
},
"search.filter.locked.server-game-version.title": {
"message": "A versão do jogo é fornecida pelo servidor"
},
"search.filter.locked.server-loader.title": {
"message": "O loader é fornecido pelo servidor"
},
"settings.sidebar.label.account": {
"message": "Conta"
},
@@ -872,16 +872,7 @@
"search.filter.locked.instance.sync": {
"message": "Sincronizar com a instância"
},
"search.filter.locked.server": {
"message": "Fornecido pelo servidor"
},
"search.filter.locked.server-environment.title": {
"message": "Apenas mods no cliente podem ser adicionados à instância de servidor"
},
"search.filter.locked.server-game-version.title": {
"message": "Versão do jogo é fornecida pelo servidor"
},
"search.filter.locked.server-loader.title": {
"message": "Carregador é fornecido pelo servidor"
}
}
@@ -506,16 +506,7 @@
"search.filter.locked.instance.sync": {
"message": "Sincronizează cu instanța"
},
"search.filter.locked.server": {
"message": "Furnizat de server"
},
"search.filter.locked.server-environment.title": {
"message": "Numai modurile de pe partea clientului pot fi adăugate la instanța serverului"
},
"search.filter.locked.server-game-version.title": {
"message": "Versiunea jocului este furnizată de server"
},
"search.filter.locked.server-loader.title": {
"message": "Încărcătorul este furnizat de server"
}
}
@@ -377,18 +377,9 @@
"app.instance.admonitions.shared-instance.review-header": {
"message": "Проверьте изменения"
},
"app.instance.admonitions.shared-instance.review-update-button": {
"message": "Обзор обновления"
},
"app.instance.admonitions.shared-instance.reviewing-button": {
"message": "Проверка..."
},
"app.instance.admonitions.shared-instance.update-available-body": {
"message": "Необходимо обновление для игры в {name}. Обновитесь до последней версии и запустите игру."
},
"app.instance.admonitions.shared-instance.update-available-header": {
"message": "Доступно обновление"
},
"app.instance.confirm-delete.admonition-body": {
"message": "Все данные сборки будут удалены навсегда, в том числе миры, настройки и весь установленный контент."
},
@@ -695,15 +686,6 @@
"app.modal.install-to-play.report-reason": {
"message": "Какое правило нарушает данная сборка?"
},
"app.modal.install-to-play.report-reason.inappropriate": {
"message": "Неприемлемое содержимое"
},
"app.modal.install-to-play.report-reason.malicious": {
"message": "Вредоносное содержимое"
},
"app.modal.install-to-play.report-reason.spam": {
"message": "Спам"
},
"app.modal.install-to-play.report-shared-instance-header": {
"message": "Сообщить об общем экземпляре"
},
@@ -1910,18 +1892,9 @@
"search.filter.locked.instance.sync": {
"message": "Использовать из сборки"
},
"search.filter.locked.server": {
"message": "Управляется сервером"
},
"search.filter.locked.server-environment.title": {
"message": "В серверной сборке доступны только клиентские моды"
},
"search.filter.locked.server-game-version.title": {
"message": "Версия игры управляется сервером"
},
"search.filter.locked.server-loader.title": {
"message": "Загрузчик управляется сервером"
},
"settings.sidebar.label.account": {
"message": "Аккаунт"
},
@@ -1100,16 +1100,7 @@
"search.filter.locked.instance.sync": {
"message": "Sinhroniziraj sa instancom"
},
"search.filter.locked.server": {
"message": "Obezbeđeno od servera"
},
"search.filter.locked.server-environment.title": {
"message": "Samo modovi za kliente mogu biti dodani na instancu servera"
},
"search.filter.locked.server-game-version.title": {
"message": "Verzije igre je obezbeđena od servera"
},
"search.filter.locked.server-loader.title": {
"message": "Učitavač je obezbeđen od servera"
}
}
@@ -359,18 +359,9 @@
"app.instance.admonitions.shared-instance.review-header": {
"message": "Granskar ändringar"
},
"app.instance.admonitions.shared-instance.review-update-button": {
"message": "Granska uppdatering"
},
"app.instance.admonitions.shared-instance.reviewing-button": {
"message": "Granskar..."
},
"app.instance.admonitions.shared-instance.update-available-body": {
"message": "En uppdatering krävs för att spela {name}. Vänligen uppdatera till den senaste verisoner för att köra spelet."
},
"app.instance.admonitions.shared-instance.update-available-header": {
"message": "En uppdatering finns tillgänglig"
},
"app.instance.confirm-delete.admonition-body": {
"message": "All data från din instans kommer permanent raderas, däribland dina världar, konfigurationer samt allt installerat innehåll."
},
@@ -683,15 +674,6 @@
"app.modal.install-to-play.report-reason": {
"message": "Vilken regel bryter instansen mot?"
},
"app.modal.install-to-play.report-reason.inappropriate": {
"message": "Olämplig"
},
"app.modal.install-to-play.report-reason.malicious": {
"message": "Skadlig"
},
"app.modal.install-to-play.report-reason.spam": {
"message": "Spam"
},
"app.modal.install-to-play.report-shared-instance-header": {
"message": "Rapportera delad instans"
},
@@ -1718,18 +1700,9 @@
"search.filter.locked.instance.sync": {
"message": "Synkronisera med instansen"
},
"search.filter.locked.server": {
"message": "Tillhandahållet av servern"
},
"search.filter.locked.server-environment.title": {
"message": "Endast klient-sido moddar kan läggas till i serverinstansen"
},
"search.filter.locked.server-game-version.title": {
"message": "Spelversion tillhandahålls av servern"
},
"search.filter.locked.server-loader.title": {
"message": "Loader tillhandahålls av servern"
},
"settings.sidebar.label.account": {
"message": "Konto"
},
@@ -785,16 +785,7 @@
"search.filter.locked.instance.sync": {
"message": "เชื่อมต่อกับโปรแกรม"
},
"search.filter.locked.server": {
"message": "เซิร์ฟเวอร์เป็นผู้กำหนด"
},
"search.filter.locked.server-environment.title": {
"message": "มีเพียงม็อดสำหรับฝั่งเครื่องของผู้เล่นเท่านั้นที่สามารถเพิ่มลงในโปรแกรมเซิร์ฟเวอร์ดังกล่าวได้"
},
"search.filter.locked.server-game-version.title": {
"message": "เวอร์ชันเกมถูกกำหนดโดยเซิร์ฟเวอร์แล้ว"
},
"search.filter.locked.server-loader.title": {
"message": "ตัวรันถูกกำหนดโดยเซิร์ฟเวอร์แล้ว"
}
}
@@ -377,18 +377,9 @@
"app.instance.admonitions.shared-instance.review-header": {
"message": "Değişimleri inceleniyor"
},
"app.instance.admonitions.shared-instance.review-update-button": {
"message": "Güncellemeyi gözden geçir"
},
"app.instance.admonitions.shared-instance.reviewing-button": {
"message": "İnceleniyor..."
},
"app.instance.admonitions.shared-instance.update-available-body": {
"message": "{name}'i oynamak için bir güncelleme gerekli. Oyunu açabilmek için lütfen son sürüme güncelleyin."
},
"app.instance.admonitions.shared-instance.update-available-header": {
"message": "Bir güncelleme mevcut"
},
"app.instance.confirm-delete.admonition-body": {
"message": "Dünyalarınız, yapılandırmalarınız ve yüklü tüm içerikler ve tüm veriler kalıcı olarak silinecek."
},
@@ -665,15 +656,6 @@
"app.modal.install-to-play.report-image-too-large": {
"message": "Dosya 1 MiB boyut sınırını aşıyor"
},
"app.modal.install-to-play.report-reason.inappropriate": {
"message": "Uygunsuz"
},
"app.modal.install-to-play.report-reason.malicious": {
"message": "Kötü niyetli"
},
"app.modal.install-to-play.report-reason.spam": {
"message": "Spam"
},
"app.modal.install-to-play.report-submitted": {
"message": "Rapor sunuldu"
},
@@ -1508,18 +1490,9 @@
"search.filter.locked.instance.sync": {
"message": "Kurulum ile eşitle"
},
"search.filter.locked.server": {
"message": "Sunucu tarafından sağlanmıştır"
},
"search.filter.locked.server-environment.title": {
"message": "Sunucu örneğine yalnızca istemci tarafında çalışan modlar eklenebilir"
},
"search.filter.locked.server-game-version.title": {
"message": "Oyun sürümü sunucu tarafından sağlanıyor"
},
"search.filter.locked.server-loader.title": {
"message": "Yükleyici sunucu tarafından sağlanıyor"
},
"settings.sidebar.label.account": {
"message": "Hesap"
},
@@ -377,18 +377,9 @@
"app.instance.admonitions.shared-instance.review-header": {
"message": "Огляд змін"
},
"app.instance.admonitions.shared-instance.review-update-button": {
"message": "Переглянути оновлення"
},
"app.instance.admonitions.shared-instance.reviewing-button": {
"message": "Оглядаємо…"
},
"app.instance.admonitions.shared-instance.update-available-body": {
"message": "Для гри в {name} потрібне оновлення. Будь ласка, оновіть до найновішої версії, щоб запустити гру."
},
"app.instance.admonitions.shared-instance.update-available-header": {
"message": "Доступне оновлення"
},
"app.instance.confirm-delete.admonition-body": {
"message": "Усі дані вашого профілю будуть видалені назавжди, включно з вашими світами, налаштуваннями та всім установленим умістом."
},
@@ -704,15 +695,6 @@
"app.modal.install-to-play.report-reason": {
"message": "Яке правило порушує цей профіль?"
},
"app.modal.install-to-play.report-reason.inappropriate": {
"message": "Неприпустимий уміст"
},
"app.modal.install-to-play.report-reason.malicious": {
"message": "Шкідливий уміст"
},
"app.modal.install-to-play.report-reason.spam": {
"message": "Спам"
},
"app.modal.install-to-play.report-shared-instance-header": {
"message": "Скарга на спільний профіль"
},
@@ -1922,18 +1904,9 @@
"search.filter.locked.instance.sync": {
"message": "Синхронізувати з профілем"
},
"search.filter.locked.server": {
"message": "Надано сервером"
},
"search.filter.locked.server-environment.title": {
"message": "До профілю сервера можна додавати лише клієнтські моди"
},
"search.filter.locked.server-game-version.title": {
"message": "Версія гри надана сервером"
},
"search.filter.locked.server-loader.title": {
"message": "Завантажувач наданий сервером"
},
"settings.sidebar.label.account": {
"message": "Обліковий запис"
},
@@ -998,16 +998,7 @@
"search.filter.locked.instance.sync": {
"message": "Đồng bộ với phiên bản"
},
"search.filter.locked.server": {
"message": "Do máy chủ cung cấp"
},
"search.filter.locked.server-environment.title": {
"message": "Chỉ các mod chạy phía client mới có thể thêm hồ sơ máy chủ"
},
"search.filter.locked.server-game-version.title": {
"message": "Phiên bản trò chơi được cung cấp bởi máy chủ"
},
"search.filter.locked.server-loader.title": {
"message": "Loader được cung cấp bởi máy chủ"
}
}
@@ -377,18 +377,9 @@
"app.instance.admonitions.shared-instance.review-header": {
"message": "查看变更"
},
"app.instance.admonitions.shared-instance.review-update-button": {
"message": "查看更新"
},
"app.instance.admonitions.shared-instance.reviewing-button": {
"message": "正在审查……"
},
"app.instance.admonitions.shared-instance.update-available-body": {
"message": "{name}需要更新。请更新到最新版本以启动游戏。"
},
"app.instance.admonitions.shared-instance.update-available-header": {
"message": "有更新可用"
},
"app.instance.confirm-delete.admonition-body": {
"message": "你实例的所有数据将被永久删除,包括你的世界、配置和所有已安装的内容。"
},
@@ -704,15 +695,6 @@
"app.modal.install-to-play.report-reason": {
"message": "该实例违反了哪项规则?"
},
"app.modal.install-to-play.report-reason.inappropriate": {
"message": "不当内容"
},
"app.modal.install-to-play.report-reason.malicious": {
"message": "恶意内容"
},
"app.modal.install-to-play.report-reason.spam": {
"message": "垃圾内容"
},
"app.modal.install-to-play.report-shared-instance-header": {
"message": "举报共享实例"
},
@@ -1922,18 +1904,9 @@
"search.filter.locked.instance.sync": {
"message": "与实例同步"
},
"search.filter.locked.server": {
"message": "由该服务器提供"
},
"search.filter.locked.server-environment.title": {
"message": "只能将客户端模组添加到服务器实例中"
},
"search.filter.locked.server-game-version.title": {
"message": "游戏版本由服务器提供"
},
"search.filter.locked.server-loader.title": {
"message": "加载器由服务器提供"
},
"settings.sidebar.label.account": {
"message": "账户"
},

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