Compare commits

...
Author SHA1 Message Date
aecsocket 2781270417 remove unneeded recursion lmit 2026-03-09 23:22:14 +00:00
aecsocket ee03399fc6 fix theseus_gui 2026-03-09 17:46:19 +00:00
aecsocket e6ef671d0c Fix ping impl overriding port 2026-03-09 17:31:47 +00:00
aecsocket 2c10a2afcc wip: why do servers not provide server info.. 2026-03-09 16:56:16 +00:00
aecsocket ba6ac40574 fix labrinth compile 2026-03-09 16:45:40 +00:00
aecsocket 0856070d26 Fix issue with new impl 2026-03-09 16:22:28 +00:00
aecsocket 19da9b7d89 Improve ping impl to bring parity to app lib impl 2026-03-09 15:36:19 +00:00
JerozgenandGitHub 913dee9090 Improve language selector (#5487)
* Improve language selector

* Add Germany to German

* Run prepr:frontend

* Remove `'` in icon map

* Add Italy to Italian
2026-03-08 21:33:41 +00:00
Calum H.andGitHub 43eb53eda5 fix: posthog discrepancies linked server instances + other (#5504) 2026-03-08 21:30:30 +00:00
coolbotandGitHub c381adff85 fix moderation for projects with no type (#5503) 2026-03-08 20:25:56 +00:00
aecsocketandGitHub ace2659861 Server projects post launch fixes (#5481)
* Vendor async-minecraft-ping and fix servers returning protocol version -1

* Don't have automod reject server projects

* fmt

* Add region to search facets

* remove AMP .github
2026-03-08 00:17:38 +00:00
NitrrineandGitHub 507d03eeba fix: servers tab is breaking legacy navbar (#5496) 2026-03-07 14:52:29 +00:00
d4932d3089 Add UI module translations to Modrinth App (#5489)
* Add UI module translations to Modrinth App

* Replace `await` with `eager: true`

---------

Co-authored-by: Calum H. <calum@modrinth.com>
2026-03-07 10:13:24 +00:00
Prospector 4b6de7526c 0.11.3 changelog 2026-03-06 19:07:30 -08:00
Prospector b95e4ced22 moved app stuff to new changelog entry 2026-03-06 18:35:49 -08:00
Prospector 200b4f56c6 changelog 2026-03-06 18:34:20 -08:00
Truman GaoandGitHub 83d53dafe7 fix: servers misc fixes (#5475)
* fix: tags in project settings to have icons and ordered correctly

* fix copy in project list layout settings

* fix tag item in header navigation

* adjust ping ranges

* add handle click tag

* fix: dont show offline in project page for draft status

* move tags above creators in app

* preload server project page on load and optimize queries

* add server project card to organization page

* fix minecraft_java_server label

* pnpm prepr

* have user option in project create modal be circle

* feat: implement better mobile project page view

* disable summary line clamp for servers

* fix: unlink instance doesnt update instance

* increase icon upload size

* small fix on button size

* improve how server ping info loads

* remove unnecessary pings for instance page

* fix order of computing dependency diff

* remove linked_project_id from world, use name+address to match for managed world instead

* pnpm prepr

* hide duplicate worlds with same domain name in worlds list

* add install content warning for server instance

* increase summary max width

* add handling for server projects for bulk editing links

* implement include user unlisted projects in published modpack select

* pnpm prepr

* filter to only user unlisted status

* add bad link warnings

* fix modpack tags appearing in server

* cargo fmt
2026-03-07 02:11:45 +00:00
98175a58a6 Moderation improvements post server projects launch (#5485)
* identical links nag + use V3 more

* updater status alerts

* identity verification msg for servers

* private use msg for server projects

* fix newlines in some messages

* Tweak + add description messages

* tweak status alerts

* flinks for summary messages

* Rule 4 msgs for servers

* account for some jank

* fixes

* Project Type placeholder

* update locales

* add button for rejecting pay to play servers

* update country to region

* add lowercase option for project type placeholders

* update link of article to the published url

* prepr

---------

Co-authored-by: Prospector <6166773+Prospector@users.noreply.github.com>
2026-03-07 01:58:28 +00:00
JerozgenandGitHub 20cbe1ad8f Set team_members to unknown[] (#5490) 2026-03-06 14:13:42 +00:00
NitrrineandGitHub 9d5d34fde8 feat: backport servers tab to legacy navbar (#5471)
* feat: backport servers tab to legacy navbar

* style: make gap between projects types tighter
2026-03-04 22:51:58 +00:00
Prospector 2d5c26896f fix blog date + app changelog 2026-03-04 14:41:52 -08:00
ea3bb334a8 Fix muralpay pix payouts in backend (#5463)
* Fix fields for pix payout

* Fix muralpay pix backend

---------

Co-authored-by: Creeperkatze <178587183+Creeperkatze@users.noreply.github.com>
2026-03-04 13:10:31 +00:00
024e079a7d chore: the blog (#5453)
* feat: start on blogpost

* feat: images for post

* fix: grammar + prerender news + changelog

* feat: add discovery in app vid

* fix lint

* rename new blog md to match title

* fix assets directories

* remove left over compiled files

* update thumbnail

---------

Co-authored-by: tdgao <mr.trumgao@gmail.com>
Co-authored-by: Prospector <6166773+Prospector@users.noreply.github.com>
2026-03-04 02:18:08 +01:00
aecsocketandGitHub d902b281f7 comment out impl (#5468) 2026-03-04 01:03:20 +01:00
Truman GaoandGitHub c4a0008708 fix: invalid args path (#5467)
* fix port in server address

* fix: invalid args for path

* fix page loading

* hide labels
2026-03-04 01:03:12 +01:00
Truman GaoandGitHub 835f80ee50 fix port in server address (#5466) 2026-03-04 00:20:52 +01:00
155f4091a6 Tweak search sorting (#5464)
* Tweak search sorting

* Tweak search sorting

* fix ping impl

* remove port field, add server regions

* fix compile

* fix tests

* update frontend banner upload size limit

* feat: use server project region instead of country

* remove java and bedrock port in frontend

* add helper text

* allow filtering by if server is online

* add server status online offline filter

* use region in instance

* pre-collapse status in app discovery

* pnpm prepr

* remove server discovery flag

* add servers into mobile nav tabs

* parse port from address if present

---------

Co-authored-by: tdgao <mr.trumgao@gmail.com>
2026-03-03 23:20:48 +01:00
Truman GaoandGitHub e1ee9c364b disable update modpack for server project (#5465) 2026-03-03 20:24:53 +00:00
0029a22569 fix: app cache and other issues (#5460)
* fixes

* #[serde(untagged)] my BEHATED (still kinda broken)

* remove unused hasContent ref

* clean up code in fetch instance

* ping 3 times for average latency

* fix: pinging to be more accurate

TCP_NODELAY — Set on the TCP stream right after connect, preventing Nagle's algorithm from buffering the small ping packet (could save up to ~40ms)

Instant over Utc::now() — Switched to monotonic std::time::Instant for timing, which is more precise and designed for measuring elapsed time (still using chrono just for the ping magic value)

* delete useFetch util and just use native fetch

* rename worlds until functions for more clarity

* fix lint

* fix cache.rs logic

* make backend ping use both impls

* Add optional timeout to server ping

* fix gallery appearing in nav with no items

* remove EU countries and add EU option for server country

* add uk to europe

---------

Co-authored-by: aecsocket <aecsocket@tutanota.com>
2026-03-03 18:41:12 +00:00
211ec20970 fix: remove tax compliance env var (#5445)
* fix: remove tax compliance env var

* improve tax compliance year logic

* bit more tests

---------

Co-authored-by: aecsocket <aecsocket@tutanota.com>
2026-03-03 17:45:28 +00:00
aecsocketandGitHub 34997bada5 Rescan tech review reports when a new version of Delphi is ran (#5433)
* Delphi rescan when version changes

* Fix inserting duplicate reports when rescanning

* upsert report issue details instead of deleting

* fix up rescan stuff
2026-03-03 17:44:30 +00:00
aecsocketandGitHub 63daac917b Fix Mural API response schema (#5454)
* Fix Mural API response schema

* Fix clippy

* fix compile

* fix compile
2026-03-03 17:17:44 +00:00
245 changed files with 5243 additions and 3223 deletions
-12
View File
@@ -1,13 +1 @@
See [CLAUDE.md](./CLAUDE.md) for all project instructions and guidelines.
## Skills
Project-specific skills (patterns, conventions, and implementation guides) are located in [`.claude/skills/`](./.claude/skills/). Each skill has a `SKILL.md` describing the pattern:
- **[Dependency Injection](./.claude/skills/dependency-injection/SKILL.md)** — Vue provide/inject DI layer using `createContext`
- **[Cross-Platform Pages](./.claude/skills/cross-platform-pages/SKILL.md)** — Shared component architecture across Nuxt and Tauri frontends
- **[Multistage Modals](./.claude/skills/multistage-modals/SKILL.md)** — Wizard-like modal flows with `MultiStageModal`
- **[Figma MCP](./.claude/skills/figma-mcp/SKILL.md)** — Translating Figma designs to Modrinth Vue components
- **[i18n Convert](./.claude/skills/i18n-convert/SKILL.md)** — Converting hard-coded strings to vue-i18n localization
- **[API Module](./.claude/skills/api-module/SKILL.md)** — Adding new endpoint modules to `@modrinth/api-client`
- **[TanStack Query](./.claude/skills/tanstack-query/SKILL.md)** — Server state management with `@tanstack/vue-query` v5
+12
View File
@@ -84,3 +84,15 @@ Each project may have its own `CLAUDE.md` with detailed instructions:
### General
- Do not create new non-source code files (e.g. Bash scripts, SQL scripts) unless explicitly prompted to
## Skills
Project-specific skills (patterns, conventions, and implementation guides) are located in [`.claude/skills/`](./.claude/skills/). Each skill has a `SKILL.md` describing the pattern:
- **[Dependency Injection](./.claude/skills/dependency-injection/SKILL.md)** — Vue provide/inject DI layer using `createContext`
- **[Cross-Platform Pages](./.claude/skills/cross-platform-pages/SKILL.md)** — Shared component architecture across Nuxt and Tauri frontends
- **[Multistage Modals](./.claude/skills/multistage-modals/SKILL.md)** — Wizard-like modal flows with `MultiStageModal`
- **[Figma MCP](./.claude/skills/figma-mcp/SKILL.md)** — Translating Figma designs to Modrinth Vue components
- **[i18n Convert](./.claude/skills/i18n-convert/SKILL.md)** — Converting hard-coded strings to vue-i18n localization
- **[API Module](./.claude/skills/api-module/SKILL.md)** — Adding new endpoint modules to `@modrinth/api-client`
- **[TanStack Query](./.claude/skills/tanstack-query/SKILL.md)** — Server state management with `@tanstack/vue-query` v5
Generated
+275 -199
View File
@@ -378,6 +378,15 @@ dependencies = [
"libc",
]
[[package]]
name = "ansi_term"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2"
dependencies = [
"winapi",
]
[[package]]
name = "anstream"
version = "0.6.21"
@@ -643,12 +652,13 @@ dependencies = [
[[package]]
name = "async-minecraft-ping"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "668b459c14dd8d9ef21e296af3f2a3651ff7dc3536e092fb0b09e528daaa6d89"
dependencies = [
"anyhow",
"async-trait",
"hickory-resolver 0.24.4",
"serde",
"serde_json",
"structopt",
"thiserror 1.0.69",
"tokio",
]
@@ -864,6 +874,17 @@ dependencies = [
"webpki-roots 1.0.3",
]
[[package]]
name = "atty"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
dependencies = [
"hermit-abi 0.1.19",
"libc",
"winapi",
]
[[package]]
name = "autocfg"
version = "1.5.0"
@@ -1581,6 +1602,21 @@ dependencies = [
"libloading 0.8.8",
]
[[package]]
name = "clap"
version = "2.34.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c"
dependencies = [
"ansi_term",
"atty",
"bitflags 1.3.2",
"strsim 0.8.0",
"textwrap",
"unicode-width 0.1.14",
"vec_map",
]
[[package]]
name = "clap"
version = "4.5.48"
@@ -1600,7 +1636,7 @@ dependencies = [
"anstream",
"anstyle",
"clap_lex",
"strsim",
"strsim 0.11.1",
]
[[package]]
@@ -1896,7 +1932,7 @@ checksum = "2eac901828f88a5241ee0600950ab981148a18f2f756900ffba1b125ca6a3ef9"
dependencies = [
"cookie 0.18.1",
"document-features",
"idna 1.1.0",
"idna",
"log",
"publicsuffix",
"serde",
@@ -2147,7 +2183,7 @@ dependencies = [
"futures",
"indexmap 2.11.4",
"itertools 0.14.0",
"reqwest",
"reqwest 0.12.24",
"rust-s3",
"serde",
"serde-xml-rs",
@@ -2200,7 +2236,7 @@ dependencies = [
"ident_case",
"proc-macro2",
"quote",
"strsim",
"strsim 0.11.1",
"syn 2.0.106",
]
@@ -2214,7 +2250,7 @@ dependencies = [
"ident_case",
"proc-macro2",
"quote",
"strsim",
"strsim 0.11.1",
"syn 2.0.106",
]
@@ -2227,7 +2263,7 @@ dependencies = [
"ident_case",
"proc-macro2",
"quote",
"strsim",
"strsim 0.11.1",
"syn 2.0.106",
]
@@ -2698,24 +2734,6 @@ dependencies = [
"zeroize",
]
[[package]]
name = "elytra-ping"
version = "6.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "086ee116d28d0eb35ae108d1059d135e585dd1254e5df881c5b2505a6d67c445"
dependencies = [
"bytes",
"chrono",
"mc-varint",
"rand 0.9.2",
"serde",
"serde_json",
"snafu",
"tokio",
"tracing",
"trust-dns-resolver",
]
[[package]]
name = "email-encoding"
version = "0.4.1"
@@ -3782,6 +3800,15 @@ dependencies = [
"hashbrown 0.15.5",
]
[[package]]
name = "heck"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c"
dependencies = [
"unicode-segmentation",
]
[[package]]
name = "heck"
version = "0.4.1"
@@ -3794,6 +3821,15 @@ version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]]
name = "hermit-abi"
version = "0.1.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
dependencies = [
"libc",
]
[[package]]
name = "hermit-abi"
version = "0.5.2"
@@ -3806,6 +3842,30 @@ version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
[[package]]
name = "hickory-proto"
version = "0.24.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "92652067c9ce6f66ce53cc38d1169daa36e6e7eb7dd3b63b5103bd9d97117248"
dependencies = [
"async-trait",
"cfg-if",
"data-encoding",
"enum-as-inner",
"futures-channel",
"futures-io",
"futures-util",
"idna",
"ipnet",
"once_cell",
"rand 0.8.5",
"thiserror 1.0.69",
"tinyvec",
"tokio",
"tracing",
"url",
]
[[package]]
name = "hickory-proto"
version = "0.25.2"
@@ -3819,7 +3879,7 @@ dependencies = [
"futures-channel",
"futures-io",
"futures-util",
"idna 1.1.0",
"idna",
"ipnet",
"once_cell",
"rand 0.9.2",
@@ -3831,6 +3891,27 @@ dependencies = [
"url",
]
[[package]]
name = "hickory-resolver"
version = "0.24.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cbb117a1ca520e111743ab2f6688eddee69db4e0ea242545a604dce8a66fd22e"
dependencies = [
"cfg-if",
"futures-util",
"hickory-proto 0.24.4",
"ipconfig",
"lru-cache",
"once_cell",
"parking_lot",
"rand 0.8.5",
"resolv-conf",
"smallvec",
"thiserror 1.0.69",
"tokio",
"tracing",
]
[[package]]
name = "hickory-resolver"
version = "0.25.2"
@@ -3839,7 +3920,7 @@ checksum = "dc62a9a99b0bfb44d2ab95a7208ac952d31060efc16241c87eaf36406fecf87a"
dependencies = [
"cfg-if",
"futures-util",
"hickory-proto",
"hickory-proto 0.25.2",
"ipconfig",
"moka",
"once_cell",
@@ -4189,9 +4270,9 @@ dependencies = [
[[package]]
name = "ico"
version = "0.4.0"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc50b891e4acf8fe0e71ef88ec43ad82ee07b3810ad09de10f1d01f072ed4b98"
checksum = "3e795dff5605e0f04bff85ca41b51a96b83e80b281e96231bcaaf1ac35103371"
dependencies = [
"byteorder",
"png 0.17.16",
@@ -4289,16 +4370,6 @@ version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
[[package]]
name = "idna"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c"
dependencies = [
"unicode-bidi",
"unicode-normalization",
]
[[package]]
name = "idna"
version = "1.1.0"
@@ -4430,7 +4501,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e96d2465363ed2d81857759fc864cf6bb7997f79327aec028d65bd7989393685"
dependencies = [
"ahash 0.8.12",
"clap",
"clap 4.5.48",
"crossbeam-channel",
"crossbeam-utils",
"dashmap",
@@ -4545,7 +4616,7 @@ version = "0.4.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9"
dependencies = [
"hermit-abi",
"hermit-abi 0.5.2",
"libc",
"windows-sys 0.59.0",
]
@@ -4682,9 +4753,9 @@ dependencies = [
[[package]]
name = "js-sys"
version = "0.3.81"
version = "0.3.91"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec48937a97411dcb524a265206ccd4c90bb711fca92b2792c407f268825b9305"
checksum = "b49715b7073f385ba4bc528e5747d02e66cb39c6146efb66b781f131f0fb399c"
dependencies = [
"once_cell",
"wasm-bindgen",
@@ -4813,7 +4884,7 @@ dependencies = [
"bytes",
"censor",
"chrono",
"clap",
"clap 4.5.48",
"clickhouse",
"color-eyre",
"color-thief",
@@ -4824,7 +4895,6 @@ dependencies = [
"dotenv-build",
"dotenvy",
"either",
"elytra-ping",
"eyre",
"futures",
"futures-util",
@@ -4850,7 +4920,7 @@ dependencies = [
"rand_chacha 0.3.1",
"redis",
"regex",
"reqwest",
"reqwest 0.12.24",
"rust-s3",
"rust_decimal",
"rust_iso3166",
@@ -4935,7 +5005,7 @@ dependencies = [
"futures-util",
"hostname",
"httpdate",
"idna 1.1.0",
"idna",
"mime",
"nom 8.0.0",
"percent-encoding",
@@ -5252,12 +5322,6 @@ dependencies = [
"rayon",
]
[[package]]
name = "mc-varint"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6407d50d4e371d5f450a4a5f1abd4531e48e9a1627409c051b5d4c7c84f6bb09"
[[package]]
name = "md-5"
version = "0.10.6"
@@ -5305,7 +5369,7 @@ dependencies = [
"log",
"meilisearch-index-setting-macro",
"pin-project-lite",
"reqwest",
"reqwest 0.12.24",
"serde",
"serde_json",
"thiserror 2.0.17",
@@ -5408,13 +5472,13 @@ name = "modrinth-maxmind"
version = "0.0.0"
dependencies = [
"bytes",
"clap",
"clap 4.5.48",
"directories",
"eyre",
"flate2",
"maxminddb",
"modrinth-util",
"reqwest",
"reqwest 0.12.24",
"tar",
"tokio",
"tracing",
@@ -5493,7 +5557,7 @@ dependencies = [
"bytes",
"chrono",
"derive_more 2.0.1",
"reqwest",
"reqwest 0.12.24",
"rust_decimal",
"rust_iso3166",
"secrecy",
@@ -5816,7 +5880,7 @@ version = "1.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b"
dependencies = [
"hermit-abi",
"hermit-abi 0.5.2",
"libc",
]
@@ -6247,7 +6311,7 @@ dependencies = [
"bytes",
"http 1.3.1",
"opentelemetry",
"reqwest",
"reqwest 0.12.24",
]
[[package]]
@@ -6262,7 +6326,7 @@ dependencies = [
"opentelemetry-proto",
"opentelemetry_sdk",
"prost 0.13.5",
"reqwest",
"reqwest 0.12.24",
"thiserror 2.0.17",
"tokio",
"tonic 0.13.1",
@@ -6824,7 +6888,7 @@ checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218"
dependencies = [
"cfg-if",
"concurrent-queue",
"hermit-abi",
"hermit-abi 0.5.2",
"pin-project-lite",
"rustix 1.1.2",
"windows-sys 0.61.2",
@@ -7178,7 +7242,7 @@ version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f42ea446cab60335f76979ec15e12619a2165b5ae2c12166bef27d283a9fadf"
dependencies = [
"idna 1.1.0",
"idna",
"psl-types",
]
@@ -7703,11 +7767,45 @@ dependencies = [
"url",
"wasm-bindgen",
"wasm-bindgen-futures",
"wasm-streams",
"wasm-streams 0.4.2",
"web-sys",
"webpki-roots 1.0.3",
]
[[package]]
name = "reqwest"
version = "0.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab3f43e3283ab1488b624b44b0e988d0acea0b3214e694730a055cb6b2efa801"
dependencies = [
"base64 0.22.1",
"bytes",
"futures-core",
"futures-util",
"http 1.3.1",
"http-body 1.0.1",
"http-body-util",
"hyper 1.7.0",
"hyper-util",
"js-sys",
"log",
"percent-encoding",
"pin-project-lite",
"serde",
"serde_json",
"sync_wrapper",
"tokio",
"tokio-util",
"tower",
"tower-http",
"tower-service",
"url",
"wasm-bindgen",
"wasm-bindgen-futures",
"wasm-streams 0.5.0",
"web-sys",
]
[[package]]
name = "resolv-conf"
version = "0.7.5"
@@ -7893,7 +7991,7 @@ dependencies = [
"minidom",
"percent-encoding",
"quick-xml 0.38.3",
"reqwest",
"reqwest 0.12.24",
"serde",
"serde_derive",
"serde_json",
@@ -8361,7 +8459,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48b85e25e8a1fc13928885e8bf13abe8a09e15c46993aed05d6405f7755d6e20"
dependencies = [
"httpdate",
"reqwest",
"reqwest 0.12.24",
"rustls 0.23.32",
"sentry-backtrace",
"sentry-contexts",
@@ -8884,28 +8982,6 @@ dependencies = [
"serde",
]
[[package]]
name = "snafu"
version = "0.8.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e84b3f4eacbf3a1ce05eac6763b4d629d60cbc94d632e4092c54ade71f1e1a2"
dependencies = [
"backtrace",
"snafu-derive",
]
[[package]]
name = "snafu-derive"
version = "0.8.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1c97747dbf44bb1ca44a561ece23508e99cb592e862f22222dcf42f51d1e451"
dependencies = [
"heck 0.5.0",
"proc-macro2",
"quote",
"syn 2.0.106",
]
[[package]]
name = "socket2"
version = "0.5.10"
@@ -9290,6 +9366,12 @@ dependencies = [
"unicode-properties",
]
[[package]]
name = "strsim"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
[[package]]
name = "strsim"
version = "0.11.1"
@@ -9319,6 +9401,30 @@ dependencies = [
"syn 2.0.106",
]
[[package]]
name = "structopt"
version = "0.3.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c6b5c64445ba8094a6ab0c3cd2ad323e07171012d9c98b0b15651daf1787a10"
dependencies = [
"clap 2.34.0",
"lazy_static",
"structopt-derive",
]
[[package]]
name = "structopt-derive"
version = "0.4.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dcb5ae327f9cc13b68763b5749770cb9e048a99bd9dfdfa58d0cf05d5f64afe0"
dependencies = [
"heck 0.3.3",
"proc-macro-error",
"proc-macro2",
"quote",
"syn 1.0.109",
]
[[package]]
name = "strum"
version = "0.27.2"
@@ -9464,9 +9570,9 @@ checksum = "7b2093cf4c8eb1e67749a6762251bc9cd836b6fc171623bd0a9d324d37af2417"
[[package]]
name = "tao"
version = "0.34.3"
version = "0.34.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "959469667dbcea91e5485fc48ba7dd6023face91bb0f1a14681a70f99847c3f7"
checksum = "f3a753bdc39c07b192151523a3f77cd0394aa75413802c883a0f6f6a0e5ee2e7"
dependencies = [
"bitflags 2.9.4",
"block2 0.6.2",
@@ -9538,9 +9644,9 @@ checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1"
[[package]]
name = "tauri"
version = "2.8.5"
version = "2.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4d1d3b3dc4c101ac989fd7db77e045cc6d91a25349cd410455cb5c57d510c1c"
checksum = "463ae8677aa6d0f063a900b9c41ecd4ac2b7ca82f0b058cc4491540e55b20129"
dependencies = [
"anyhow",
"bytes",
@@ -9567,7 +9673,7 @@ dependencies = [
"percent-encoding",
"plist",
"raw-window-handle",
"reqwest",
"reqwest 0.13.2",
"serde",
"serde_json",
"serde_repr",
@@ -9582,7 +9688,6 @@ dependencies = [
"tokio",
"tray-icon",
"url",
"urlpattern",
"webkit2gtk",
"webview2-com",
"window-vibrancy",
@@ -9591,9 +9696,9 @@ dependencies = [
[[package]]
name = "tauri-build"
version = "2.4.1"
version = "2.5.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c432ccc9ff661803dab74c6cd78de11026a578a9307610bbc39d3c55be7943f"
checksum = "ca7bd893329425df750813e95bd2b643d5369d929438da96d5bbb7cc2c918f74"
dependencies = [
"anyhow",
"cargo_toml",
@@ -9615,9 +9720,9 @@ dependencies = [
[[package]]
name = "tauri-codegen"
version = "2.4.0"
version = "2.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ab3a62cf2e6253936a8b267c2e95839674e7439f104fa96ad0025e149d54d8a"
checksum = "aac423e5859d9f9ccdd32e3cf6a5866a15bedbf25aa6630bcb2acde9468f6ae3"
dependencies = [
"base64 0.22.1",
"brotli",
@@ -9642,9 +9747,9 @@ dependencies = [
[[package]]
name = "tauri-macros"
version = "2.4.0"
version = "2.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4368ea8094e7045217edb690f493b55b30caf9f3e61f79b4c24b6db91f07995e"
checksum = "1b6a1bd2861ff0c8766b1d38b32a6a410f6dc6532d4ef534c47cfb2236092f59"
dependencies = [
"heck 0.5.0",
"proc-macro2",
@@ -9656,9 +9761,9 @@ dependencies = [
[[package]]
name = "tauri-plugin"
version = "2.4.0"
version = "2.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9946a3cede302eac0c6eb6c6070ac47b1768e326092d32efbb91f21ed58d978f"
checksum = "692a77abd8b8773e107a42ec0e05b767b8d2b7ece76ab36c6c3947e34df9f53f"
dependencies = [
"anyhow",
"glob",
@@ -9712,9 +9817,9 @@ dependencies = [
[[package]]
name = "tauri-plugin-fs"
version = "2.4.2"
version = "2.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "315784ec4be45e90a987687bae7235e6be3d6e9e350d2b75c16b8a4bf22c1db7"
checksum = "ed390cc669f937afeb8b28032ce837bac8ea023d975a2e207375ec05afaf1804"
dependencies = [
"anyhow",
"dunce",
@@ -9734,16 +9839,16 @@ dependencies = [
[[package]]
name = "tauri-plugin-http"
version = "2.5.2"
version = "2.5.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "938a3d7051c9a82b431e3a0f3468f85715b3442b3c3a3913095e9fa509e2652c"
checksum = "d8f069451c4e87e7e2636b7f065a4c52866c4ce5e60e2d53fa1038edb6d184dc"
dependencies = [
"bytes",
"cookie_store",
"data-url",
"http 1.3.1",
"regex",
"reqwest",
"reqwest 0.12.24",
"schemars 0.8.22",
"serde",
"serde_json",
@@ -9827,7 +9932,7 @@ dependencies = [
"minisign-verify",
"osakit",
"percent-encoding",
"reqwest",
"reqwest 0.12.24",
"semver",
"serde",
"serde_json",
@@ -9860,9 +9965,9 @@ dependencies = [
[[package]]
name = "tauri-runtime"
version = "2.8.0"
version = "2.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4cfc9ad45b487d3fded5a4731a567872a4812e9552e3964161b08edabf93846"
checksum = "b885ffeac82b00f1f6fd292b6e5aabfa7435d537cef57d11e38a489956535651"
dependencies = [
"cookie 0.18.1",
"dpi",
@@ -9885,9 +9990,9 @@ dependencies = [
[[package]]
name = "tauri-runtime-wry"
version = "2.8.1"
version = "2.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1fe9d48bd122ff002064e88cfcd7027090d789c4302714e68fcccba0f4b7807"
checksum = "5204682391625e867d16584fedc83fc292fb998814c9f7918605c789cd876314"
dependencies = [
"gtk",
"http 1.3.1",
@@ -9912,9 +10017,9 @@ dependencies = [
[[package]]
name = "tauri-utils"
version = "2.7.0"
version = "2.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41a3852fdf9a4f8fbeaa63dc3e9a85284dd6ef7200751f0bd66ceee30c93f212"
checksum = "fcd169fccdff05eff2c1033210b9b94acd07a47e6fa9a3431cf09cfd4f01c87e"
dependencies = [
"anyhow",
"brotli",
@@ -10022,12 +10127,22 @@ dependencies = [
"url",
]
[[package]]
name = "textwrap"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
dependencies = [
"unicode-width 0.1.14",
]
[[package]]
name = "theseus"
version = "1.0.0-local"
dependencies = [
"ariadne",
"async-compression",
"async-minecraft-ping",
"async-recursion",
"async-tungstenite",
"async-walkdir",
@@ -10049,11 +10164,12 @@ dependencies = [
"either",
"encoding_rs",
"enumset",
"eyre",
"flate2",
"fs4",
"futures",
"heck 0.5.0",
"hickory-resolver",
"hickory-resolver 0.25.2",
"indicatif",
"itertools 0.14.0",
"notify",
@@ -10068,7 +10184,7 @@ dependencies = [
"quick-xml 0.38.3",
"rand 0.8.5",
"regex",
"reqwest",
"reqwest 0.12.24",
"rgb",
"serde",
"serde_ini",
@@ -10589,9 +10705,9 @@ dependencies = [
[[package]]
name = "tower-http"
version = "0.6.6"
version = "0.6.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2"
checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8"
dependencies = [
"bitflags 2.9.4",
"bytes",
@@ -10759,52 +10875,6 @@ dependencies = [
"windows-sys 0.59.0",
]
[[package]]
name = "trust-dns-proto"
version = "0.23.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3119112651c157f4488931a01e586aa459736e9d6046d3bd9105ffb69352d374"
dependencies = [
"async-trait",
"cfg-if",
"data-encoding",
"enum-as-inner",
"futures-channel",
"futures-io",
"futures-util",
"idna 0.4.0",
"ipnet",
"once_cell",
"rand 0.8.5",
"smallvec",
"thiserror 1.0.69",
"tinyvec",
"tokio",
"tracing",
"url",
]
[[package]]
name = "trust-dns-resolver"
version = "0.23.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "10a3e6c3aff1718b3c73e395d1f35202ba2ffa847c6a62eea0db8fb4cfe30be6"
dependencies = [
"cfg-if",
"futures-util",
"ipconfig",
"lru-cache",
"once_cell",
"parking_lot",
"rand 0.8.5",
"resolv-conf",
"smallvec",
"thiserror 1.0.69",
"tokio",
"tracing",
"trust-dns-proto",
]
[[package]]
name = "try-lock"
version = "0.2.5"
@@ -11024,7 +11094,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b"
dependencies = [
"form_urlencoded",
"idna 1.1.0",
"idna",
"percent-encoding",
"serde",
]
@@ -11172,7 +11242,7 @@ version = "0.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43fb22e1a008ece370ce08a3e9e4447a910e92621bb49b85d6e48a45397e7cfa"
dependencies = [
"idna 1.1.0",
"idna",
"once_cell",
"regex",
"serde",
@@ -11208,6 +11278,12 @@ version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
[[package]]
name = "vec_map"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
[[package]]
name = "version-compare"
version = "0.2.0"
@@ -11319,9 +11395,9 @@ checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b"
[[package]]
name = "wasm-bindgen"
version = "0.2.104"
version = "0.2.114"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1da10c01ae9f1ae40cbfac0bac3b1e724b320abfcf52229f80b547c0d250e2d"
checksum = "6532f9a5c1ece3798cb1c2cfdba640b9b3ba884f5db45973a6f442510a87d38e"
dependencies = [
"cfg-if",
"once_cell",
@@ -11330,27 +11406,14 @@ dependencies = [
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-backend"
version = "0.2.104"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "671c9a5a66f49d8a47345ab942e2cb93c7d1d0339065d4f8139c486121b43b19"
dependencies = [
"bumpalo",
"log",
"proc-macro2",
"quote",
"syn 2.0.106",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-futures"
version = "0.4.54"
version = "0.4.64"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e038d41e478cc73bae0ff9b36c60cff1c98b8f38f8d7e8061e79ee63608ac5c"
checksum = "e9c5522b3a28661442748e09d40924dfb9ca614b21c00d3fd135720e48b67db8"
dependencies = [
"cfg-if",
"futures-util",
"js-sys",
"once_cell",
"wasm-bindgen",
@@ -11359,9 +11422,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.104"
version = "0.2.114"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ca60477e4c59f5f2986c50191cd972e3a50d8a95603bc9434501cf156a9a119"
checksum = "18a2d50fcf105fb33bb15f00e7a77b772945a2ee45dcf454961fd843e74c18e6"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
@@ -11369,22 +11432,22 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.104"
version = "0.2.114"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f07d2f20d4da7b26400c9f4a0511e6e0345b040694e8a75bd41d578fa4421d7"
checksum = "03ce4caeaac547cdf713d280eda22a730824dd11e6b8c3ca9e42247b25c631e3"
dependencies = [
"bumpalo",
"proc-macro2",
"quote",
"syn 2.0.106",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.104"
version = "0.2.114"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bad67dc8b2a1a6e5448428adec4c3e84c43e561d8c9ee8a9e5aabeb193ec41d1"
checksum = "75a326b8c223ee17883a4251907455a2431acc2791c98c26279376490c378c16"
dependencies = [
"unicode-ident",
]
@@ -11402,6 +11465,19 @@ dependencies = [
"web-sys",
]
[[package]]
name = "wasm-streams"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d1ec4f6517c9e11ae630e200b2b65d193279042e28edd4a2cda233e46670bbb"
dependencies = [
"futures-util",
"js-sys",
"wasm-bindgen",
"wasm-bindgen-futures",
"web-sys",
]
[[package]]
name = "wayland-backend"
version = "0.3.11"
@@ -11464,9 +11540,9 @@ dependencies = [
[[package]]
name = "web-sys"
version = "0.3.81"
version = "0.3.91"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9367c417a924a74cae129e6a2ae3b47fabb1f8995595ab474029da749a8be120"
checksum = "854ba17bb104abfb26ba36da9729addc7ce7f06f5c0f90f3c391f8461cca21f9"
dependencies = [
"js-sys",
"wasm-bindgen",
@@ -11484,9 +11560,9 @@ dependencies = [
[[package]]
name = "webkit2gtk"
version = "2.0.1"
version = "2.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "76b1bc1e54c581da1e9f179d0b38512ba358fb1af2d634a1affe42e37172361a"
checksum = "a1027150013530fb2eaf806408df88461ae4815a45c541c8975e61d6f2fc4793"
dependencies = [
"bitflags 1.3.2",
"cairo-rs",
@@ -11508,9 +11584,9 @@ dependencies = [
[[package]]
name = "webkit2gtk-sys"
version = "2.0.1"
version = "2.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62daa38afc514d1f8f12b8693d30d5993ff77ced33ce30cd04deebc267a6d57c"
checksum = "916a5f65c2ef0dfe12fff695960a2ec3d4565359fdbb2e9943c974e06c734ea5"
dependencies = [
"bitflags 1.3.2",
"cairo-sys-rs",
@@ -12213,9 +12289,9 @@ checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb"
[[package]]
name = "wry"
version = "0.53.4"
version = "0.54.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d78ec082b80fa088569a970d043bb3050abaabf4454101d44514ee8d9a8c9f6"
checksum = "bb26159b420aa77684589a744ae9a9461a95395b848764ad12290a14d960a11a"
dependencies = [
"base64 0.22.1",
"block2 0.6.2",
+2 -3
View File
@@ -33,7 +33,7 @@ arc-swap = "1.7.1"
argon2 = { version = "0.5.3", features = ["std"] }
ariadne = { path = "packages/ariadne" }
async-compression = { version = "0.4.32", default-features = false }
async-minecraft-ping = { version = "0.8.0" }
async-minecraft-ping = { path = "packages/async-minecraft-ping" }
async-recursion = "1.1.1"
async-stripe = { version = "0.41.0", default-features = false, features = [
"runtime-tokio-hyper-rustls",
@@ -72,7 +72,6 @@ dotenv-build = "0.1.1"
dotenvy = "0.15.7"
dunce = "1.0.5"
either = "1.15.0"
elytra-ping = "6.0.1"
encoding_rs = "0.8.35"
enumset = "1.1.10"
eyre = "0.6.12"
@@ -179,7 +178,7 @@ tauri = "2.8.5"
tauri-build = "2.4.1"
tauri-plugin-deep-link = "2.4.3"
tauri-plugin-dialog = "2.4.0"
tauri-plugin-http = "2.5.2"
tauri-plugin-http = "2.5.7"
tauri-plugin-opener = "2.5.0"
tauri-plugin-os = "2.3.1"
tauri-plugin-single-instance = "2.3.4"
+5 -12
View File
@@ -85,7 +85,6 @@ import { debugAnalytics, initAnalytics, trackEvent } from '@/helpers/analytics'
import { check_reachable } from '@/helpers/auth.js'
import { get_user } from '@/helpers/cache.js'
import { command_listener, warning_listener } from '@/helpers/events.js'
import { useFetch } from '@/helpers/fetch.js'
import { cancelLogin, get as getCreds, login, logout } from '@/helpers/mr_auth.ts'
import { list } from '@/helpers/profile.js'
import { get as getSettings, set as setSettings } from '@/helpers/settings.ts'
@@ -303,11 +302,7 @@ async function setupApp() {
}),
)
useFetch(
`https://api.modrinth.com/appCriticalAnnouncement.json?version=${version}`,
'criticalAnnouncements',
true,
)
fetch(`https://api.modrinth.com/appCriticalAnnouncement.json?version=${version}`)
.then((response) => response.json())
.then((res) => {
if (res && res.header && res.body) {
@@ -320,23 +315,21 @@ async function setupApp() {
)
})
useFetch(`https://modrinth.com/news/feed/articles.json`, 'news', true)
fetch(`https://modrinth.com/news/feed/articles.json`)
.then((response) => response.json())
.then((res) => {
if (res && res.articles) {
// Format expected by NewsArticleCard component.
news.value = res.articles
.map((article) => ({
...article,
path: article.link,
thumbnail: article.thumbnail,
title: article.title,
summary: article.summary,
date: article.date,
}))
.slice(0, 4)
}
})
.catch((error) => {
console.error('Failed to fetch news articles', error)
})
get_opening_command().then(handleCommand)
fetchCredentials()
@@ -155,4 +155,23 @@ img {
box-shadow: var(--shadow-card);
}
// From the Bootstrap project
// The MIT License (MIT)
// Copyright (c) 2011-2023 The Bootstrap Authors
// https://github.com/twbs/bootstrap/blob/2f617215755b066904248525a8c56ea425dde871/scss/mixins/_visually-hidden.scss#L8
.visually-hidden {
width: 1px !important;
height: 1px !important;
padding: 0 !important;
margin: -1px !important;
overflow: hidden !important;
clip: rect(0, 0, 0, 0) !important;
white-space: nowrap !important;
border: 0 !important;
&:not(caption) {
position: absolute !important;
}
}
@import '@modrinth/assets/omorphia.scss';
@@ -69,7 +69,7 @@ const play = async (e, context) => {
await run(props.instance.path)
.catch((err) => handleSevereError(err, { profilePath: props.instance.path }))
.finally(() => {
trackEvent('InstancePlay', {
trackEvent('InstanceStart', {
loader: props.instance.loader,
game_version: props.instance.game_version,
source: context,
@@ -49,6 +49,9 @@ const modalConfirmUnpair = ref()
const modalConfirmReinstall = ref()
const props = defineProps<InstanceSettingsTabProps>()
const emit = defineEmits<{
unlinked: []
}>()
const loader = ref(props.instance.loader)
const gameVersion = ref(props.instance.game_version)
@@ -273,7 +276,7 @@ async function unpairProfile() {
modpackProject.value = null
modpackVersion.value = null
modpackVersions.value = null
modalConfirmUnpair.value.hide()
emit('unlinked')
}
async function repairModpack() {
@@ -1,6 +1,6 @@
<script setup lang="ts">
import { ConfirmModal } from '@modrinth/ui'
import { ref } from 'vue'
import { useTemplateRef } from 'vue'
import { hide_ads_window, show_ads_window } from '@/helpers/ads.js'
import { useTheming } from '@/store/theme.ts'
@@ -49,16 +49,16 @@ const props = defineProps({
})
const emit = defineEmits(['proceed'])
const modal = ref(null)
const modal = useTemplateRef('modal')
defineExpose({
show: () => {
hide_ads_window()
modal.value.show()
modal.value?.show()
},
hide: () => {
onModalHide()
modal.value.hide()
modal.value?.hide()
},
})
@@ -32,8 +32,12 @@ import type { InstanceSettingsTabProps } from '../../../helpers/types'
const { formatMessage } = useVIntl()
const props = defineProps<InstanceSettingsTabProps>()
const emit = defineEmits<{
unlinked: []
}>()
const isMinecraftServer = ref(false)
const handleUnlinked = () => emit('unlinked')
watch(
() => props.instance,
@@ -121,7 +125,14 @@ defineExpose({ show })
<TabbedModal
:tabs="
tabs.map((tab) => ({ ...tab, props: { ...props, isMinecraftServer: isMinecraftServer } }))
tabs.map((tab) => ({
...tab,
props: {
...props,
isMinecraftServer,
onUnlinked: handleUnlinked,
},
}))
"
/>
</ModalWrapper>
@@ -187,10 +187,11 @@ type ProjectInfo = {
const { formatMessage } = useVIntl()
const installStore = useInstall()
type UpdateCompleteCallback = () => void | Promise<void>
const modal = ref<InstanceType<typeof NewModal>>()
const instance = ref<GameInstance | null>(null)
const onUpdateComplete = ref<() => void>(() => {})
const onUpdateComplete = ref<UpdateCompleteCallback>(() => {})
const diffs = ref<DependencyDiff[]>([])
const modpackVersionId = ref<string | null>(null)
const modpackVersion = ref<Version | null>(null)
@@ -316,6 +317,7 @@ async function computeDependencyDiffs(
async function checkUpdateAvailable(inst: GameInstance): Promise<DependencyDiff[] | null> {
if (!inst.linked_data) return null
if (!modpackVersionId.value || !inst.linked_data.version_id) return null
try {
// For server projects, linked_data.project_id is the server project but
@@ -327,8 +329,8 @@ async function checkUpdateAvailable(inst: GameInstance): Promise<DependencyDiff[
// Compute dependency diffs between current and latest version
if (instanceModpackVersion && modpackVersion.value) {
return await computeDependencyDiffs(
modpackVersion.value.dependencies || [],
instanceModpackVersion.dependencies || [],
modpackVersion.value.dependencies || [],
)
}
} catch (error) {
@@ -355,7 +357,7 @@ async function handleUpdate() {
try {
if (modpackVersionId.value && instance.value) {
await update_managed_modrinth_version(instance.value.path, modpackVersionId.value)
onUpdateComplete.value()
await onUpdateComplete.value()
}
} catch (error) {
console.error('Error updating instance:', error)
@@ -379,7 +381,7 @@ function handleDecline() {
function show(
instanceVal: GameInstance,
modpackVersionIdVal: string | null = null,
callback: () => void = () => {},
callback: UpdateCompleteCallback = () => {},
e?: MouseEvent,
) {
instance.value = instanceVal
@@ -8,14 +8,14 @@ import {
LOCALES,
useVIntl,
} from '@modrinth/ui'
import { ref, watch } from 'vue'
import { computed, ref, watch } from 'vue'
import { get, set } from '@/helpers/settings.ts'
import i18n from '@/i18n.config'
const { formatMessage } = useVIntl()
const platform = formatMessage(languageSelectorMessages.platformApp)
const platform = computed(() => formatMessage(languageSelectorMessages.platformApp))
const settings = ref(await get())
@@ -33,6 +33,7 @@ async function purgeCache() {
'user',
'team',
'organization',
'file',
'loader_manifest',
'minecraft_manifest',
'categories',
@@ -40,8 +41,10 @@ async function purgeCache() {
'loaders',
'game_versions',
'donation_platforms',
'file_hash',
'file_update',
'search_results',
'search_results_v3',
]).catch(handleError)
}
@@ -80,7 +80,7 @@ const play = async (event: MouseEvent) => {
await run(props.instance.path)
.catch((err) => handleSevereError(err, { profilePath: props.instance.path }))
.finally(() => {
trackEvent('InstancePlay', {
trackEvent('InstanceStart', {
loader: props.instance.loader,
game_version: props.instance.game_version,
source: 'InstanceItem',
@@ -175,10 +175,17 @@ function refreshServer(address: string, instancePath: string) {
refreshServerData(serverData.value[address], protocolVersions.value[instancePath], address)
}
async function joinWorld(world: WorldWithProfile) {
async function joinWorld(world: WorldWithProfile, instance?: GameInstance) {
console.log(`Joining world ${getWorldIdentifier(world)}`)
if (world.type === 'server') {
await start_join_server(world.profile, world.address).catch(handleError)
if (instance) {
trackEvent('InstanceStart', {
loader: instance.loader,
game_version: instance.game_version,
source: 'WorldItem',
})
}
} else if (world.type === 'singleplayer') {
await start_join_singleplayer_world(world.profile, world.path).catch(handleError)
}
@@ -188,7 +195,7 @@ async function playInstance(instance: GameInstance) {
await run(instance.path)
.catch((err) => handleSevereError(err, { profilePath: instance.path }))
.finally(() => {
trackEvent('InstancePlay', {
trackEvent('InstanceStart', {
loader: instance.loader,
game_version: instance.game_version,
source: 'WorldItem',
@@ -317,7 +324,7 @@ onUnmounted(() => {
() => {
currentProfile = item.instance.path
currentWorld = getWorldIdentifier(item.world)
joinWorld(item.world)
joinWorld(item.world, item.instance)
}
"
@play-instance="
@@ -45,7 +45,7 @@ import type {
SingleplayerWorld,
World,
} from '@/helpers/worlds.ts'
import { getWorldIdentifier, isLinkedWorld, set_world_display_status } from '@/helpers/worlds.ts'
import { getWorldIdentifier, set_world_display_status } from '@/helpers/worlds.ts'
import { LockIcon } from '../../../../../../packages/assets/generated-icons'
@@ -81,6 +81,8 @@ const props = withDefaults(
message: MessageDescriptor
}
managed?: boolean
// Instance
instancePath?: string
instanceName?: string
@@ -99,6 +101,7 @@ const props = withDefaults(
renderedMotd: undefined,
gameMode: undefined,
managed: false,
instancePath: undefined,
instanceName: undefined,
@@ -120,7 +123,7 @@ const serverIncompatible = computed(
)
const locked = computed(() => props.world.type === 'singleplayer' && props.world.locked)
const linked = computed(() => isLinkedWorld(props.world))
const managed = computed(() => props.managed)
const messages = defineMessages({
hardcore: {
@@ -209,7 +212,7 @@ const messages = defineMessages({
{{ world.name }}
</div>
<TagItem
v-if="linked"
v-if="managed"
v-tooltip="formatMessage(messages.linkedServer)"
class="border !border-solid border-blue bg-highlight-blue text-xs"
:style="`--_color: var(--color-blue)`"
@@ -412,10 +415,10 @@ const messages = defineMessages({
id: 'edit',
action: () => emit('edit'),
shown: !instancePath,
disabled: locked || linked,
disabled: locked || managed,
tooltip: locked
? formatMessage(messages.worldInUse)
: linked
: managed
? formatMessage(messages.linkedServer)
: undefined,
},
@@ -452,10 +455,10 @@ const messages = defineMessages({
hoverFilled: true,
action: () => emit('delete'),
shown: !instancePath,
disabled: locked || linked,
disabled: locked || managed,
tooltip: locked
? formatMessage(messages.worldInUse)
: linked
: managed
? formatMessage(messages.linkedServer)
: undefined,
},
+1 -1
View File
@@ -15,7 +15,7 @@ type AnalyticsEventMap = {
PageView: { path: string; fromPath: string; failed: unknown }
InstanceCreate: { source: string }
InstanceCreateStart: { source: string }
InstancePlay: InstanceProperties & { source: string }
InstanceStart: InstanceProperties & { source: string }
InstanceStop: Partial<InstanceProperties> & { source?: string }
InstanceDuplicate: InstanceProperties
InstanceRepair: InstanceProperties
-18
View File
@@ -1,18 +0,0 @@
import { getVersion } from '@tauri-apps/api/app'
import { fetch } from '@tauri-apps/plugin-http'
export const useFetch = async (url, item, isSilent) => {
try {
const version = await getVersion()
return await fetch(url, {
method: 'GET',
headers: { 'User-Agent': `modrinth/theseus/${version} (support@modrinth.com)` },
})
} catch (err) {
if (!isSilent) {
throw err
} else {
console.error(err)
}
}
}
+142 -12
View File
@@ -30,7 +30,6 @@ export type ServerWorld = BaseWorld & {
index: number
address: string
pack_status: ServerPackStatus
linked_project_id?: string
}
export type World = SingleplayerWorld | ServerWorld
@@ -141,14 +140,12 @@ export async function add_server_to_profile(
name: string,
address: string,
packStatus: ServerPackStatus,
linkedProjectId?: string,
): Promise<number> {
return await invoke('plugin:worlds|add_server_to_profile', {
path,
name,
address,
packStatus,
linkedProjectId,
})
}
@@ -158,7 +155,6 @@ export async function edit_server_in_profile(
name: string,
address: string,
packStatus: ServerPackStatus,
linkedProjectId?: string,
): Promise<void> {
return await invoke('plugin:worlds|edit_server_in_profile', {
path,
@@ -166,7 +162,6 @@ export async function edit_server_in_profile(
name,
address,
packStatus,
linkedProjectId,
})
}
@@ -204,11 +199,6 @@ export function getWorldIdentifier(world: World) {
export function sortWorlds(worlds: World[]) {
worlds.sort((a, b) => {
const aLinked = isLinkedWorld(a)
const bLinked = isLinkedWorld(b)
if (aLinked !== bLinked) {
return aLinked ? -1 : 1
}
if (!a.last_played) {
return 1
}
@@ -227,8 +217,148 @@ export function isServerWorld(world: World): world is ServerWorld {
return world.type === 'server'
}
export function isLinkedWorld(world: World): boolean {
return world.type === 'server' && !!world.linked_project_id
const DEFAULT_MINECRAFT_SERVER_PORT = 25565
function parseServerPort(port: string): number | null {
const parsed = Number.parseInt(port, 10)
return Number.isInteger(parsed) && parsed > 0 && parsed <= 65535 ? parsed : null
}
function parseServerHost(address: string): string {
const trimmedAddress = address.trim()
if (!trimmedAddress) return ''
if (trimmedAddress.startsWith('[')) {
const closingBracket = trimmedAddress.indexOf(']')
if (closingBracket > 0) {
return trimmedAddress.slice(1, closingBracket).trim().toLowerCase()
}
}
const firstColon = trimmedAddress.indexOf(':')
const lastColon = trimmedAddress.lastIndexOf(':')
if (firstColon !== -1 && firstColon === lastColon) {
return trimmedAddress.slice(0, firstColon).trim().toLowerCase()
}
return trimmedAddress.toLowerCase()
}
function isIPv4Host(host: string): boolean {
const segments = host.split('.')
if (segments.length !== 4) return false
return segments.every((segment) => {
if (!/^\d+$/.test(segment)) return false
const value = Number.parseInt(segment, 10)
return value >= 0 && value <= 255
})
}
/**
* Normalization converts addresses to a canonical form (lowercase-host:port, default port 25565)
*/
export function normalizeServerAddress(address: string): string {
const trimmedAddress = address.trim()
const host = parseServerHost(trimmedAddress)
if (!host) return ''
let port = DEFAULT_MINECRAFT_SERVER_PORT
// ipv6 address
if (trimmedAddress.startsWith('[')) {
const closingBracket = trimmedAddress.indexOf(']')
if (closingBracket > 0) {
const suffix = trimmedAddress.slice(closingBracket + 1)
if (suffix.startsWith(':')) {
const parsedPort = parseServerPort(suffix.slice(1))
if (parsedPort != null) {
port = parsedPort
}
}
}
// ipv4 address or hostname
} else {
const firstColon = trimmedAddress.indexOf(':')
const lastColon = trimmedAddress.lastIndexOf(':')
if (firstColon !== -1 && firstColon === lastColon) {
const parsedPort = parseServerPort(trimmedAddress.slice(firstColon + 1))
if (parsedPort != null) {
port = parsedPort
}
}
}
return `${host}:${port}`
}
/**
* Domain key used for deduping server entries by removing a single leading subdomain.
* Example: test.cobblemon.gg and cobblemon.gg map to cobblemon.gg
*/
export function getServerDomainKey(address: string): string {
const normalizedAddress = normalizeServerAddress(address)
if (!normalizedAddress) return ''
const separator = normalizedAddress.lastIndexOf(':')
if (separator <= 0 || separator === normalizedAddress.length - 1) return normalizedAddress
const host = normalizedAddress.slice(0, separator).replace(/\.+$/, '')
if (!host) return normalizedAddress
if (host.includes(':') || isIPv4Host(host)) return normalizedAddress
const segments = host.split('.').filter(Boolean)
if (segments.length <= 2) return host
return segments.slice(1).join('.')
}
export function resolveManagedServerWorld(
worlds: World[],
managedName: string | null | undefined,
managedAddress: string | null | undefined,
): ServerWorld | null {
if (!managedName || !managedAddress) return null
const normalizedManagedAddress = normalizeServerAddress(managedAddress)
if (!normalizedManagedAddress) return null
const servers = worlds
.filter(isServerWorld)
.slice()
.sort((a, b) => a.index - b.index)
const exactMatch = servers.find(
(server) =>
server.name === managedName &&
normalizeServerAddress(server.address) === normalizedManagedAddress,
)
if (exactMatch) return exactMatch
return (
servers.find((server) => normalizeServerAddress(server.address) === normalizedManagedAddress) ??
null
)
}
export async function getServerLatency(
address: string,
protocolVersion: ProtocolVersion | null = null,
): Promise<number | undefined> {
const pings: number[] = []
for (let i = 0; i < 3; i++) {
try {
const status = await get_server_status(address, protocolVersion)
if (status.ping != null) {
pings.push(status.ping)
}
} catch {
// Ignore individual ping failures
}
}
if (pings.length === 0) return undefined
return Math.round(pings.reduce((sum, p) => sum + p, 0) / pings.length)
}
export async function refreshServerData(
+2 -1
View File
@@ -1,4 +1,5 @@
import { buildLocaleMessages, createMessageCompiler, type CrowdinMessages } from '@modrinth/ui'
import { uiLocaleModulesEager } from '@modrinth/ui/src/locales.eager.ts'
import { createI18n } from 'vue-i18n'
const localeModules = import.meta.glob<{ default: CrowdinMessages }>('./locales/*/index.json', {
@@ -12,7 +13,7 @@ const i18n = createI18n({
messageCompiler: createMessageCompiler(),
missingWarn: false,
fallbackWarn: false,
messages: buildLocaleMessages(localeModules),
messages: buildLocaleMessages(localeModules, uiLocaleModulesEager),
})
export default i18n
+20 -13
View File
@@ -11,6 +11,7 @@ import {
} from '@modrinth/assets'
import type { ProjectType, SortType, Tags } from '@modrinth/ui'
import {
Admonition,
ButtonStyled,
Checkbox,
defineMessages,
@@ -49,7 +50,7 @@ import {
} from '@/helpers/profile.js'
import { get_categories, get_game_versions, get_loaders } from '@/helpers/tags'
import type { GameInstance } from '@/helpers/types'
import { get_server_status } from '@/helpers/worlds'
import { getServerLatency } from '@/helpers/worlds'
import { useBreadcrumbs } from '@/store/breadcrumbs'
import { getServerAddress, playServerProject, useInstall } from '@/store/install.js'
@@ -287,17 +288,18 @@ const {
} = useServerSearch({ tags, query, maxResults, currentPage })
async function pingServerHits(hits: Labrinth.Search.v3.ResultSearchProject[]) {
for (const hit of hits) {
const address = hit.minecraft_java_server?.address
if (!address) continue
get_server_status(address)
.then((status) => {
serverPings.value = { ...serverPings.value, [hit.project_id]: status.ping }
})
.catch((err) => {
const pingsToFetch = hits.filter((hit) => hit.minecraft_java_server?.address)
await Promise.all(
pingsToFetch.map(async (hit) => {
const address = hit.minecraft_java_server!.address!
try {
const latency = await getServerLatency(address)
serverPings.value = { ...serverPings.value, [hit.project_id]: latency }
} catch (err) {
console.error(`Failed to ping server ${address}:`, err)
})
}
}
}),
)
}
const previousFilterState = ref('')
@@ -665,6 +667,7 @@ previousFilterState.value = JSON.stringify({
'server_category_minecraft_server_meta',
'server_category_minecraft_server_community',
'server_game_version',
'server_status',
].includes(filterType.id)
"
>
@@ -722,6 +725,10 @@ previousFilterState.value = JSON.stringify({
<template v-if="instance">
<InstanceIndicator :instance="instance" />
<h1 class="m-0 mb-1 text-xl">Install content to instance</h1>
<Admonition v-if="isServerInstance" type="warning" class="mb-1">
Adding content can break compatibility when joining the server. Any added content will also
be lost when you update the server instance content.
</Admonition>
</template>
<NavTabs :links="selectableProjectTypes" />
<StyledInput
@@ -809,8 +816,8 @@ previousFilterState.value = JSON.stringify({
:tags="project.categories"
:link="`/project/${project.slug ?? project.project_id}`"
:server-online-players="project.minecraft_java_server?.ping?.data?.players_online ?? 0"
:server-region-code="project.minecraft_server?.country"
:server-recent-plays="project.minecraft_java_server?.verified_plays_4w ?? 0"
:server-region="project.minecraft_server?.region"
:server-recent-plays="project.minecraft_java_server?.verified_plays_2w ?? 0"
:server-modpack-content="getServerModpackContent(project)"
:server-ping="serverPings[project.project_id]"
:server-status-online="!!project.minecraft_java_server?.ping?.data"
+64 -54
View File
@@ -2,7 +2,12 @@
<div v-if="instance">
<div class="p-6 pr-2 pb-4" @contextmenu.prevent.stop="(event) => handleRightClick(event)">
<ExportModal ref="exportModal" :instance="instance" />
<InstanceSettingsModal ref="settingsModal" :instance="instance" :offline="offline" />
<InstanceSettingsModal
ref="settingsModal"
:instance="instance"
:offline="offline"
@unlinked="fetchInstance"
/>
<UpdateToPlayModal ref="updateToPlayModal" :instance="instance" />
<ContentPageHeader>
<template #icon>
@@ -47,7 +52,7 @@
/>
<router-link
:to="`/project/${linkedProjectV3.slug ?? linkedProjectV3.id}`"
class="hover:underline text-primary"
class="hover:underline text-primary truncate"
>
{{ linkedProjectV3.name }}
</router-link>
@@ -55,19 +60,28 @@
</template>
<template v-else>
<ServerOnlinePlayers :online="playersOnline ?? 0" :status-online="statusOnline" />
<template v-if="loadingServerPing">
<ServerOnlinePlayers
v-if="playersOnline !== undefined"
:online="playersOnline"
:status-online="statusOnline"
hide-label
/>
<ServerRecentPlays :recent-plays="recentPlays ?? 0" hide-label />
<div
v-if="
(playersOnline !== undefined || recentPlays !== undefined) &&
(minecraftServer?.region || ping)
"
class="w-1.5 h-1.5 rounded-full bg-surface-5"
></div>
<ServerPing v-if="ping" :ping="ping" />
</template>
<ServerRegion v-if="minecraftServer?.region" :region="minecraftServer?.region" />
<div
v-if="playersOnline !== undefined && (minecraftServer?.country || ping)"
class="w-1.5 h-1.5 rounded-full bg-surface-5"
></div>
<ServerRegion v-if="minecraftServer?.country" :region="minecraftServer?.country" />
<ServerPing v-if="ping" :ping="ping" />
<div
v-if="modpackContentProjectV3 && (minecraftServer?.country || ping)"
v-if="minecraftServer?.region || ping"
class="w-1.5 h-1.5 rounded-full bg-surface-5"
></div>
@@ -84,7 +98,7 @@
/>
<router-link
:to="`/project/${linkedProjectV3.slug ?? linkedProjectV3.id}`"
class="hover:underline text-primary"
class="hover:underline text-primary truncate"
>
{{ linkedProjectV3.name }}
</router-link>
@@ -297,6 +311,7 @@ import {
OverflowMenu,
ServerOnlinePlayers,
ServerPing,
ServerRecentPlays,
ServerRegion,
} from '@modrinth/ui'
import { convertFileSrc } from '@tauri-apps/api/core'
@@ -312,10 +327,10 @@ import InstanceSettingsModal from '@/components/ui/modal/InstanceSettingsModal.v
import UpdateToPlayModal from '@/components/ui/modal/UpdateToPlayModal.vue'
import NavTabs from '@/components/ui/NavTabs.vue'
import { trackEvent } from '@/helpers/analytics'
import { get_project_v3, get_version, get_version_many } from '@/helpers/cache.js'
import { get_project_v3, get_version_many } from '@/helpers/cache.js'
import { process_listener, profile_listener } from '@/helpers/events'
import { get_by_profile_path } from '@/helpers/process'
import { finish_install, get, get_full_path, get_projects, kill, run } from '@/helpers/profile'
import { finish_install, get, get_full_path, kill, run } from '@/helpers/profile'
import type { GameInstance } from '@/helpers/types'
import { showProfileInFolder } from '@/helpers/utils.js'
import { get_server_status } from '@/helpers/worlds'
@@ -348,24 +363,26 @@ const exportModal = ref<InstanceType<typeof ExportModal>>()
const updateToPlayModal = ref<InstanceType<typeof UpdateToPlayModal>>()
const isServerInstance = ref(false)
const hasContent = ref(true)
const linkedProjectV3 = ref<Labrinth.Projects.v3.Project>()
const modpackContentProjectV3 = ref<Labrinth.Projects.v3.Project | null>(null)
const selected = ref<unknown[]>([])
const minecraftServer = computed(() => linkedProjectV3.value?.minecraft_server)
const javaServerPingData = computed(() => linkedProjectV3.value?.minecraft_java_server?.ping?.data)
const statusOnline = computed(() => !!javaServerPingData.value)
const recentPlays = computed(
() => linkedProjectV3.value?.minecraft_java_server?.verified_plays_2w ?? undefined,
)
const playersOnline = ref<number | undefined>(undefined)
const ping = ref<number | undefined>(undefined)
const loadingServerPing = ref(false)
async function fetchInstance() {
isServerInstance.value = false
linkedProjectV3.value = undefined
modpackContentProjectV3.value = null
modrinthVersions.value = []
hasContent.value = true
ping.value = undefined
playersOnline.value = undefined
loadingServerPing.value = false
instance.value = await get(route.params.id as string).catch(handleError)
@@ -376,55 +393,48 @@ async function fetchInstance() {
'must_revalidate',
)
if (linkedProjectV3.value?.minecraft_server != null) {
isServerInstance.value = true
}
if (linkedProjectV3.value && linkedProjectV3.value.versions) {
const versions = await get_version_many(linkedProjectV3.value.versions, 'must_revalidate')
modrinthVersions.value = versions.sort(
(a: Labrinth.Versions.v2.Version, b: Labrinth.Versions.v2.Version) =>
dayjs(b.date_published).valueOf() - dayjs(a.date_published).valueOf(),
)
if (linkedProjectV3.value?.minecraft_server != null) {
isServerInstance.value = true
const serverAddress = linkedProjectV3.value?.minecraft_java_server?.address
if (serverAddress) {
get_server_status(serverAddress)
.then((status) => {
if (status.ping != null) {
ping.value = status.ping
playersOnline.value = status.players?.online
}
})
.catch((err) => {
console.error(`Failed to ping server ${serverAddress}:`, err)
})
}
await fetchModpackContent()
const projects = await get_projects(instance.value!.path).catch(() => ({}))
hasContent.value = Object.keys(projects).length > 0
}
}
} catch (error: Error) {
handleError(error)
} catch (error) {
handleError(error as Error)
}
}
await updatePlayState()
fetchDeferredData()
}
async function fetchModpackContent() {
modpackContentProjectV3.value = null
const versionId = instance.value?.linked_data?.version_id
if (!versionId) return
const contentVersion = await get_version(versionId, 'must_revalidate')
const projectId = contentVersion?.project_id
if (projectId) {
modpackContentProjectV3.value = await get_project_v3(projectId, 'must_revalidate')
function fetchDeferredData() {
const serverAddress = linkedProjectV3.value?.minecraft_java_server?.address
if (isServerInstance.value && serverAddress) {
get_server_status(serverAddress)
.then((status) => {
playersOnline.value = status.players?.online
ping.value = status.ping
})
.catch((error) => {
console.error(`Failed to fetch server status for ${serverAddress}:`, error)
})
.finally(() => {
loadingServerPing.value = true
})
} else {
loadingServerPing.value = true
}
updatePlayState()
}
async function updatePlayState() {
if (!route.params.id) return
const runningProcesses = await get_by_profile_path(route.params.id as string).catch(handleError)
playing.value = Array.isArray(runningProcesses) && runningProcesses.length > 0
@@ -491,7 +501,7 @@ const startInstance = async (context: string) => {
}
loading.value = false
trackEvent('InstancePlay', {
trackEvent('InstanceStart', {
loader: instance.value.loader,
game_version: instance.value.game_version,
source: context,
@@ -355,6 +355,7 @@ const isPackLocked = computed(() => {
})
const canUpdatePack = computed(() => {
if (props.isServerInstance) return false
if (!props.instance.linked_data || !props.versions || !props.versions[0]) return false
return props.instance.linked_data.version_id !== props.versions[0].id
})
+138 -16
View File
@@ -26,7 +26,7 @@
:description="`'${worldToDelete?.name}' will be **permanently deleted**, and there will be no way to recover it.`"
@proceed="proceedDeleteWorld"
/>
<div v-if="worlds.length > 0" class="flex flex-col gap-4">
<div v-if="dedupedWorlds.length > 0" class="flex flex-col gap-4">
<div class="flex flex-wrap gap-2 items-center">
<StyledInput
v-model="searchFilter"
@@ -62,6 +62,7 @@
v-for="world in filteredWorlds"
:key="`world-${world.type}-${world.type == 'singleplayer' ? world.path : `${world.address}-${world.index}`}`"
:world="world"
:managed="world.type === 'server' ? isManagedServerWorld(world) : false"
:highlighted="highlightedWorld === getWorldIdentifier(world)"
:supports-server-quick-play="supportsServerQuickPlay"
:supports-world-quick-play="supportsWorldQuickPlay"
@@ -80,13 +81,13 @@
@refresh="() => refreshServer((world as ServerWorld).address)"
@edit="
() =>
isLinkedWorld(world)
? undefined
: world.type === 'server'
? editServerModal?.show(world)
: editWorldModal?.show(world)
world.type === 'singleplayer'
? editWorldModal?.show(world)
: isManagedServerWorld(world)
? undefined
: editServerModal?.show(world)
"
@delete="() => !isLinkedWorld(world) && promptToRemoveWorld(world)"
@delete="() => !isManagedServerWorld(world) && promptToRemoveWorld(world)"
@open-folder="(world: SingleplayerWorld) => showWorldInFolder(instance.path, world.path)"
/>
</div>
@@ -144,17 +145,20 @@ import AddServerModal from '@/components/ui/world/modal/AddServerModal.vue'
import EditServerModal from '@/components/ui/world/modal/EditServerModal.vue'
import EditWorldModal from '@/components/ui/world/modal/EditSingleplayerWorldModal.vue'
import WorldItem from '@/components/ui/world/WorldItem.vue'
import { trackEvent } from '@/helpers/analytics'
import { get_project, get_project_v3 } from '@/helpers/cache.js'
import { profile_listener } from '@/helpers/events'
import { get_game_versions } from '@/helpers/tags'
import type { GameInstance } from '@/helpers/types'
import {
delete_world,
get_profile_protocol_version,
getServerDomainKey,
getWorldIdentifier,
handleDefaultProfileUpdateEvent,
hasServerQuickPlaySupport,
hasWorldQuickPlaySupport,
isLinkedWorld,
normalizeServerAddress,
type ProfileEvent,
type ProtocolVersion,
refreshServerData,
@@ -162,6 +166,7 @@ import {
refreshWorld,
refreshWorlds,
remove_server_from_profile,
resolveManagedServerWorld,
type ServerData,
type ServerWorld,
showWorldInFolder,
@@ -171,7 +176,11 @@ import {
start_join_singleplayer_world,
type World,
} from '@/helpers/worlds.ts'
import { playServerProject } from '@/store/install'
import {
ensureManagedServerWorldExists,
getServerAddress,
playServerProject,
} from '@/store/install'
const { handleError } = injectNotificationManager()
const route = useRoute()
@@ -225,6 +234,69 @@ const linuxRefreshCount = ref(0)
const protocolVersion = ref<ProtocolVersion | null>(
await get_profile_protocol_version(instance.value.path),
)
const managedServerName = ref<string | null>(null)
const managedServerAddress = ref<string | null>(null)
const managedServerWorld = computed(() =>
resolveManagedServerWorld(worlds.value, managedServerName.value, managedServerAddress.value),
)
function isManagedServerWorld(world: World): world is ServerWorld {
return world.type === 'server' && managedServerWorld.value?.index === world.index
}
async function refreshManagedServerMetadata() {
await ensureManagedServerWorldExists(
instance.value.path,
managedServerName.value,
managedServerAddress.value,
)
const projectId = instance.value.linked_data?.project_id
if (!projectId) {
managedServerName.value = null
managedServerAddress.value = null
return
}
try {
const [project, projectV3] = await Promise.all([
get_project(projectId, 'bypass'),
get_project_v3(projectId, 'bypass'),
])
if (projectV3?.minecraft_server == null) {
managedServerName.value = null
managedServerAddress.value = null
return
}
const serverAddress = getServerAddress(projectV3.minecraft_java_server)
if (!serverAddress) {
managedServerName.value = null
managedServerAddress.value = null
return
}
managedServerName.value = project.title
managedServerAddress.value = serverAddress
} catch (err) {
console.error(
`Failed to resolve managed server metadata for profile: ${instance.value.path}`,
err,
)
managedServerName.value = null
managedServerAddress.value = null
}
}
watch(
() => instance.value.linked_data?.project_id,
async () => {
await refreshManagedServerMetadata()
},
{ immediate: true },
)
const unlistenProfile = await profile_listener(async (e: ProfileEvent) => {
if (e.profile_path_id !== instance.value.path) return
@@ -257,6 +329,7 @@ async function refreshAllWorlds() {
console.log(`Already refreshing, cancelling refresh.`)
return
}
await refreshManagedServerMetadata()
refreshingAll.value = true
@@ -334,10 +407,23 @@ async function joinWorld(world: World) {
startingInstance.value = true
worldPlaying.value = world
if (world.type === 'server') {
if (isLinkedWorld(world)) {
playServerProject(world.linked_project_id)
const managedProjectId = instance.value.linked_data?.project_id
if (managedProjectId && isManagedServerWorld(world)) {
await playServerProject(managedProjectId).catch(handleJoinError)
trackEvent('InstanceStart', {
loader: instance.value.loader,
game_version: instance.value.game_version,
source: 'WorldsPage',
})
startingInstance.value = false
return
}
await start_join_server(instance.value.path, world.address).catch(handleJoinError)
trackEvent('InstanceStart', {
loader: instance.value.loader,
game_version: instance.value.game_version,
source: 'WorldsPage',
})
} else if (world.type === 'singleplayer') {
await start_join_singleplayer_world(instance.value.path, world.path).catch(handleJoinError)
}
@@ -379,12 +465,48 @@ const supportsWorldQuickPlay = computed(() =>
hasWorldQuickPlaySupport(gameVersions.value, instance.value.game_version),
)
const dedupedWorlds = computed(() => {
const visibleWorlds: World[] = []
const serverIndexByDomain = new Map<string, number>()
for (const world of worlds.value) {
if (world.type !== 'server') {
visibleWorlds.push(world)
continue
}
const domainKey =
getServerDomainKey(world.address) ||
normalizeServerAddress(world.address) ||
`server-${world.index}`
const existingIndex = serverIndexByDomain.get(domainKey)
if (existingIndex == null) {
serverIndexByDomain.set(domainKey, visibleWorlds.length)
visibleWorlds.push(world)
continue
}
// replace world with managed world if applicable
const existingWorld = visibleWorlds[existingIndex]
if (
existingWorld?.type === 'server' &&
!isManagedServerWorld(existingWorld) &&
isManagedServerWorld(world)
) {
visibleWorlds[existingIndex] = world
}
}
return visibleWorlds
})
const filterOptions = computed(() => {
const options: FilterBarOption[] = []
const hasServer = worlds.value.some((x) => x.type === 'server')
const hasServer = dedupedWorlds.value.some((x) => x.type === 'server')
if (worlds.value.some((x) => x.type === 'singleplayer') && hasServer) {
if (dedupedWorlds.value.some((x) => x.type === 'singleplayer') && hasServer) {
options.push({
id: 'singleplayer',
message: messages.singleplayer,
@@ -398,13 +520,13 @@ const filterOptions = computed(() => {
if (hasServer) {
// add available filter if there's any offline ("unavailable") servers AND there's any singleplayer worlds or available servers
if (
worlds.value.some(
dedupedWorlds.value.some(
(x) =>
x.type === 'server' &&
!serverData.value[x.address]?.status &&
!serverData.value[x.address]?.refreshing,
) &&
worlds.value.some(
dedupedWorlds.value.some(
(x) =>
x.type === 'singleplayer' ||
(x.type === 'server' &&
@@ -423,7 +545,7 @@ const filterOptions = computed(() => {
})
const filteredWorlds = computed(() =>
worlds.value.filter((x) => {
dedupedWorlds.value.filter((x) => {
const availableFilter = filters.value.includes('available')
const typeFilter = filters.value.includes('server') || filters.value.includes('singleplayer')
+57 -49
View File
@@ -26,6 +26,7 @@
:project-v3="projectV3"
class="project-sidebar-section"
/>
<ProjectSidebarTags :project="data" class="project-sidebar-section" />
<ProjectSidebarCreators
:organization="null"
:members="members"
@@ -34,7 +35,6 @@
link-target="_blank"
class="project-sidebar-section"
/>
<ProjectSidebarTags :project="data" class="project-sidebar-section" />
<ProjectSidebarDetails
:project="data"
:has-versions="versions.length > 0"
@@ -79,7 +79,7 @@
}}
</button>
</ButtonStyled>
<ButtonStyled size="large" circular type="transparent">
<ButtonStyled size="large" circular>
<button v-tooltip="'Add server to instance'" @click="handleAddServerToInstance">
<PlusIcon />
</button>
@@ -265,7 +265,7 @@ import {
list as listInstances,
} from '@/helpers/profile'
import { get_categories, get_game_versions, get_loaders } from '@/helpers/tags'
import { get_server_status } from '@/helpers/worlds'
import { getServerLatency } from '@/helpers/worlds'
import { useBreadcrumbs } from '@/store/breadcrumbs'
import {
getServerAddress,
@@ -361,8 +361,11 @@ function handleAddServerToInstance() {
}
async function fetchProjectData() {
const project = await get_project(route.params.id, 'must_revalidate').catch(handleError)
projectV3.value = await get_project_v3(route.params.id, 'must_revalidate').catch(handleError)
const [project, projectV3Result] = await Promise.all([
get_project(route.params.id, 'must_revalidate').catch(handleError),
get_project_v3(route.params.id, 'must_revalidate').catch(handleError),
])
projectV3.value = projectV3Result
if (!project) {
handleError('Error loading project')
@@ -392,71 +395,74 @@ async function fetchProjectData() {
}
isServerProject.value = projectV3.value?.minecraft_server != null
// Ping server for latency
const serverAddress = projectV3.value?.minecraft_java_server?.address
serverStatusOnline.value = !!projectV3.value?.minecraft_java_server?.ping?.data
breadcrumbs.setName('Project', data.value.title)
fetchDeferredServerData(project)
}
function fetchDeferredServerData(project) {
const serverAddress = projectV3.value?.minecraft_java_server?.address
if (serverAddress) {
serverPing.value = undefined
get_server_status(serverAddress)
.then((status) => {
if (status.ping != null) {
serverPing.value = status.ping
}
getServerLatency(serverAddress)
.then((latency) => {
serverPing.value = latency
})
.catch((err) => {
console.error(`Failed to ping server ${serverAddress}:`, err)
.catch((error) => {
console.error(`Failed to ping server ${serverAddress}:`, error)
})
}
// Fetch server sidebar data (modpack version + project)
const content = projectV3.value?.minecraft_java_server?.content
if (content?.kind === 'modpack' && content.version_id) {
const modpackVersion = await get_version(content.version_id, 'bypass').catch(handleError)
if (modpackVersion) {
serverRecommendedVersion.value = modpackVersion.game_versions?.[0] ?? null
serverModpackLoaders.value = modpackVersion.mrpack_loaders ?? []
if (modpackVersion.project_id) {
const modpackProject = await get_project_v3(
modpackVersion.project_id,
'must_revalidate',
).catch(handleError)
if (modpackProject) {
const primaryFile =
modpackVersion.files?.find((f) => f.primary) ?? modpackVersion.files?.[0]
get_version(content.version_id, 'bypass')
.catch(handleError)
.then(async (modpackVersion) => {
if (!modpackVersion) return
serverRecommendedVersion.value = modpackVersion.game_versions?.[0] ?? null
serverModpackLoaders.value = modpackVersion.mrpack_loaders ?? []
if (modpackVersion.project_id) {
const modpackProject = await get_project_v3(
modpackVersion.project_id,
'must_revalidate',
).catch(handleError)
if (modpackProject) {
const primaryFile =
modpackVersion.files?.find((f) => f.primary) ?? modpackVersion.files?.[0]
serverRequiredContent.value = {
name: modpackProject.name,
versionNumber: modpackVersion.version_number ?? '',
icon: modpackProject.icon_url,
onclickName:
modpackProject.id !== project.id
? () => router.push(`/project/${modpackProject.id}`)
: undefined,
onclickVersion:
modpackProject.id !== project.id
? () => router.push(`/project/${modpackProject.id}/version/${modpackVersion.id}`)
: undefined,
onclickDownload: primaryFile?.url ? () => openUrl(primaryFile.url) : undefined,
showCustomModpackTooltip: modpackProject.id === project.id,
serverRequiredContent.value = {
name: modpackProject.name,
versionNumber: modpackVersion.version_number ?? '',
icon: modpackProject.icon_url,
onclickName:
modpackProject.id !== project.id
? () => router.push(`/project/${modpackProject.id}`)
: undefined,
onclickVersion:
modpackProject.id !== project.id
? () => router.push(`/project/${modpackProject.id}/version/${modpackVersion.id}`)
: undefined,
onclickDownload: primaryFile?.url ? () => openUrl(primaryFile.url) : undefined,
showCustomModpackTooltip: modpackProject.id === project.id,
}
}
}
}
}
})
} else if (content?.kind === 'vanilla') {
serverRecommendedVersion.value = content.recommended_game_version ?? null
const supported = content.supported_game_versions ?? []
serverSupportedVersions.value = supported.filter((v) => !!v)
}
breadcrumbs.setName('Project', data.value.title)
await updateServerPlayState()
updateServerPlayState()
}
await fetchProjectData()
const unlistenProcesses = await process_listener((e) => {
let unlistenProcesses
process_listener((e) => {
if (
e.event === 'finished' &&
serverInstancePath.value &&
@@ -464,10 +470,12 @@ const unlistenProcesses = await process_listener((e) => {
) {
serverPlaying.value = false
}
}).then((unlisten) => {
unlistenProcesses = unlisten
})
onUnmounted(() => {
unlistenProcesses()
unlistenProcesses?.()
})
watch(
+29 -60
View File
@@ -21,8 +21,8 @@ import {
} from '@/helpers/profile.js'
import {
add_server_to_profile,
edit_server_in_profile,
get_profile_worlds,
resolveManagedServerWorld,
start_join_server,
} from '@/helpers/worlds.ts'
import router from '@/routes.js'
@@ -328,70 +328,25 @@ export const installServerProject = async (serverProjectId) => {
})
await edit_icon(profilePath, originalIconPath)
await syncServerAsWorld(profilePath, project.title, serverAddress, serverProjectId)
await ensureManagedServerWorldExists(profilePath, project.title, serverAddress)
}
export const getServerAddress = (javaServer) => {
if (!javaServer) return null
const { address, port } = javaServer
return port !== 25565 ? `${address}:${port}` : address
const { address } = javaServer
return address
}
const syncServerAsWorld = async (
profilePath,
serverName,
serverAddress,
serverProjectId = null,
) => {
export const ensureManagedServerWorldExists = async (profilePath, serverName, serverAddress) => {
if (!profilePath || !serverAddress) return
try {
const worlds = await get_profile_worlds(profilePath)
if (serverProjectId) {
// Check if a linked world for this project already exists
const linkedWorld = worlds.find(
(w) => w.type === 'server' && w.linked_project_id === serverProjectId,
)
if (linkedWorld) {
// Sync linked world data with project details
if (linkedWorld.address !== serverAddress || linkedWorld.name !== serverName) {
await edit_server_in_profile(
profilePath,
linkedWorld.index,
serverName,
serverAddress,
linkedWorld.pack_status,
serverProjectId,
)
}
return
}
}
const existingServer = worlds.find((w) => w.type === 'server' && w.address === serverAddress)
if (existingServer) {
// Re-link and sync existing server (link may have been lost by Minecraft rewriting servers.dat)
if (serverProjectId || existingServer.name !== serverName) {
await edit_server_in_profile(
profilePath,
existingServer.index,
serverName,
serverAddress,
existingServer.pack_status,
serverProjectId ?? undefined,
)
}
} else {
await add_server_to_profile(
profilePath,
serverName,
serverAddress,
'prompt',
serverProjectId ?? undefined,
)
const managedWorld = resolveManagedServerWorld(worlds, serverName, serverAddress)
if (!managedWorld) {
await add_server_to_profile(profilePath, serverName, serverAddress, 'prompt')
}
} catch (err) {
console.error('Failed to add server to instance worlds:', err)
console.error('Failed to ensure managed server world exists:', err)
}
}
@@ -405,7 +360,7 @@ const findInstalledInstance = async (projectId) => {
return packs.find((pack) => pack.linked_data?.project_id === projectId) ?? null
}
const createVanillaInstance = async (project, gameVersion, serverAddress) => {
const createVanillaServerInstance = async (project, gameVersion, serverAddress) => {
const profilePath = await create(
project.title,
gameVersion,
@@ -420,7 +375,7 @@ const createVanillaInstance = async (project, gameVersion, serverAddress) => {
},
)
await syncServerAsWorld(profilePath, project.title, serverAddress, project.id)
await ensureManagedServerWorldExists(profilePath, project.title, serverAddress)
return profilePath
}
@@ -449,6 +404,11 @@ const showModpackInstallSuccess = (installStore, project, serverAddress) => {
serverAddress,
project.linked_data?.project_id ?? null,
)
trackEvent('InstanceStart', {
loader: project.loader,
game_version: project.game_version,
source: 'ServerProject',
})
} catch (err) {
handleSevereError(err, { profilePath: project.path })
}
@@ -479,6 +439,11 @@ const showUpdateSuccess = (installStore, instance, serverAddress) => {
action: async () => {
try {
if (serverAddress) await start_join_server(instance.path, serverAddress)
trackEvent('InstanceStart', {
loader: instance.loader,
game_version: instance.game_version,
source: 'ServerProject',
})
} catch (err) {
handleSevereError(err, { profilePath: instance.path })
}
@@ -514,6 +479,7 @@ export const playServerProject = async (projectId) => {
if (projectV3?.minecraft_server == null) {
console.warn('playServerProject failed: project is not a server project')
return
}
const content = projectV3?.minecraft_java_server?.content
@@ -529,7 +495,7 @@ export const playServerProject = async (projectId) => {
if (installStore.installingServerProjects.includes(projectId)) return
installStore.startInstallingServer(projectId)
try {
const path = await createVanillaInstance(project, recommendedGameVersion, serverAddress)
const path = await createVanillaServerInstance(project, recommendedGameVersion, serverAddress)
if (path) {
instance = await get(path)
showModpackInstallSuccess(installStore, instance, serverAddress)
@@ -543,8 +509,6 @@ export const playServerProject = async (projectId) => {
installStore.showInstallToPlayModal(projectV3, modpackVersionId, async () => {
const newInstance = await findInstalledInstance(project.id)
if (!newInstance) return
// Ensure the server is in the worlds list after modpack install
await syncServerAsWorld(newInstance.path, project.title, serverAddress, project.id)
showModpackInstallSuccess(installStore, newInstance, serverAddress)
})
return
@@ -552,7 +516,7 @@ export const playServerProject = async (projectId) => {
if (!instance) return
await syncServerAsWorld(instance.path, project.title, serverAddress, project.id)
await ensureManagedServerWorldExists(instance.path, project.title, serverAddress)
// Update existing instance if needed
if (isModpack && instance.linked_data?.version_id !== modpackVersionId) {
@@ -576,6 +540,11 @@ export const playServerProject = async (projectId) => {
// join server
try {
await joinServer(instance.path, serverAddress, project.id)
trackEvent('InstanceStart', {
loader: instance.loader,
game_version: instance.game_version,
source: 'ServerProject',
})
} catch (err) {
handleSevereError(err, { profilePath: instance.path })
}
+5 -18
View File
@@ -151,17 +151,12 @@ pub async fn add_server_to_profile(
name: String,
address: String,
pack_status: ServerPackStatus,
linked_project_id: Option<String>,
) -> Result<usize> {
let path = get_full_path(path).await?;
Ok(worlds::add_server_to_profile(
&path,
name,
address,
pack_status,
linked_project_id,
Ok(
worlds::add_server_to_profile(&path, name, address, pack_status)
.await?,
)
.await?)
}
#[tauri::command]
@@ -171,18 +166,10 @@ pub async fn edit_server_in_profile(
name: String,
address: String,
pack_status: ServerPackStatus,
linked_project_id: Option<String>,
) -> Result<()> {
let path = get_full_path(path).await?;
worlds::edit_server_in_profile(
&path,
index,
name,
address,
pack_status,
linked_project_id,
)
.await?;
worlds::edit_server_in_profile(&path, index, name, address, pack_status)
.await?;
Ok(())
}
+11
View File
@@ -111,6 +111,8 @@ export default defineNuxtConfig({
const docTemplates = Object.keys(
await import('./src/templates/docs/index.ts').then((m) => m.default),
)
const blogArticles = await import('@modrinth/blog').then((m) => m.articles)
const { getChangelog } = await import('@modrinth/utils')
nitroConfig.prerender = nitroConfig.prerender || {}
nitroConfig.prerender.routes = nitroConfig.prerender.routes || []
@@ -120,6 +122,15 @@ export default defineNuxtConfig({
for (const template of docTemplates) {
nitroConfig.prerender.routes.push(`/_internal/templates/doc/${template}`)
}
nitroConfig.prerender.routes.push('/news')
for (const article of blogArticles) {
nitroConfig.prerender.routes.push(`/news/article/${article.slug}`)
}
nitroConfig.prerender.routes.push('/news/changelog')
for (const entry of getChangelog()) {
const id = entry.version ?? entry.date.unix()
nitroConfig.prerender.routes.push(`/news/changelog/${entry.product}/${id}`)
}
},
async 'build:before'() {
// 30 minutes
@@ -56,15 +56,17 @@ const search = async (query: string) => {
options.value = [...resultsByProjectId.hits, ...results.hits].map((hit) => ({
label: hit.title,
value: hit.project_id,
icon: defineAsyncComponent(() =>
Promise.resolve({
setup: () => () =>
h('img', {
src: hit.icon_url,
alt: hit.title,
class: 'h-5 w-5 rounded',
}),
}),
icon: markRaw(
defineAsyncComponent(() =>
Promise.resolve({
setup: () => () =>
h('img', {
src: hit.icon_url,
alt: hit.title,
class: 'h-5 w-5 rounded',
}),
}),
),
),
}))
} catch (error: any) {
@@ -323,15 +323,17 @@ const userOption = computed(() => ({
value: 'self',
label: auth.value.user?.username || 'Unknown user',
icon: auth.value.user?.avatar_url
? defineAsyncComponent(() =>
Promise.resolve({
setup: () => () =>
h('img', {
src: auth.value.user?.avatar_url,
alt: 'User Avatar',
class: 'h-5 w-5 rounded',
}),
}),
? markRaw(
defineAsyncComponent(() =>
Promise.resolve({
setup: () => () =>
h('img', {
src: auth.value.user?.avatar_url,
alt: 'User Avatar',
class: 'h-5 w-5 rounded-full',
}),
}),
),
)
: undefined,
}))
@@ -352,15 +354,17 @@ async function fetchOrganizations() {
value: org.id,
label: org.name,
icon: org.icon_url
? defineAsyncComponent(() =>
Promise.resolve({
setup: () => () =>
h('img', {
src: org.icon_url,
alt: `${org.name} Icon`,
class: 'h-5 w-5 rounded',
}),
}),
? markRaw(
defineAsyncComponent(() =>
Promise.resolve({
setup: () => () =>
h('img', {
src: org.icon_url,
alt: `${org.name} Icon`,
class: 'h-5 w-5 rounded',
}),
}),
),
)
: undefined,
}))
@@ -417,11 +421,9 @@ async function createProject() {
},
minecraft_java_server: {
address: '',
port: 25565,
},
minecraft_bedrock_server: {
address: '',
port: 19132,
},
})
createdProjectId = result.id
@@ -15,6 +15,8 @@
search-placeholder="Search by name or paste ID..."
loading-message="Loading..."
no-results-message="No results found"
include-user-unlisted-projects
:user-id="auth?.user?.id"
/>
</div>
@@ -85,6 +87,7 @@ const currentProjectId = computed(() => projectV3.value?.id)
const { selectedProjectId, selectedVersionId } = injectServerCompatibilityContext()
const { labrinth } = injectModrinthClient()
const { addNotification } = injectNotificationManager()
const auth = (await useAuth()) as { user?: { id: string } }
interface VersionInfo {
id: string
@@ -38,7 +38,6 @@ export const DEFAULT_FEATURE_FLAGS = validateValues({
newProjectGeneralSettings: false,
newProjectEnvironmentSettings: true,
hideRussiaCensorshipBanner: false,
serverDiscovery: true,
disablePrettyProjectUrlRedirects: false,
hidePreviewBanner: false,
i18nDebug: false,
@@ -0,0 +1,11 @@
import type { AbstractModrinthClient } from '@modrinth/api-client'
import { STALE_TIME } from './project'
export const versionQueryOptions = {
v3: (versionId: string, client: AbstractModrinthClient) => ({
queryKey: ['version', 'v3', versionId] as const,
queryFn: () => client.labrinth.versions_v3.getVersion(versionId),
staleTime: STALE_TIME,
}),
}
+18 -2
View File
@@ -69,7 +69,8 @@
</NuxtLink>
</div>
<div
:class="`col-span-2 row-start-2 flex flex-wrap justify-center ${flags.projectTypesPrimaryNav ? 'gap-2' : 'gap-4'} lg:col-span-1 lg:row-start-auto`"
class="col-span-2 row-start-2 flex flex-wrap justify-center lg:col-span-1 lg:row-start-auto"
:class="{ 'gap-4': !flags.projectTypesPrimaryNav }"
>
<template v-if="flags.projectTypesPrimaryNav">
<ButtonStyled
@@ -148,6 +149,18 @@
{{ formatMessage(commonProjectTypeCategoryMessages.plugin) }}
</nuxt-link>
</ButtonStyled>
<ButtonStyled
type="transparent"
:highlighted="route.name === 'discover-servers' || route.path.startsWith('/server/')"
:highlighted-style="
route.name === 'discover-servers' ? 'main-nav-primary' : 'main-nav-secondary'
"
>
<nuxt-link to="/discover/servers">
<ServerIcon aria-hidden="true" />
{{ formatMessage(commonProjectTypeCategoryMessages.server) }}
</nuxt-link>
</ButtonStyled>
</template>
<template v-else>
<ButtonStyled
@@ -184,7 +197,6 @@
{
id: 'servers',
action: '/discover/servers',
shown: flags.serverDiscovery,
},
]"
hoverable
@@ -1002,6 +1014,10 @@ const navRoutes = computed(() => [
label: formatMessage(getProjectTypeMessage('modpack', true)),
href: '/discover/modpacks',
},
{
label: formatMessage(getProjectTypeMessage('server', true)),
href: '/discover/servers',
},
])
const userMenuOptions = computed(() => {
@@ -1,58 +0,0 @@
{
"ar": "العربيّة",
"be": "البيلاروسيّة",
"bg": "البلغاريّة",
"bn": "البنغاليّة",
"ca": "الكاتالونية",
"cs": "التشيكية",
"da": "الدنماركية",
"de": "الألمانية",
"de-CH": "الألمانية (سويسرا)",
"el": "اليونانية",
"en-GB": "الإنجليزية (المملكة المتحدة)",
"en-US": "الإنجليزية (الولايات المتحدة)",
"en-x-lolcat": "القطة المضحكة",
"en-x-pirate": "الإنجليزية (القراصنة)",
"en-x-updown": "الإنجليزية (رأسا على عقب)",
"en-x-uwu": "الإنجليزية (UwU)",
"eo": "الإسبرانتو",
"es": "الإسبانية",
"et": "الإستونية",
"fi": "الفنلندية",
"fr": "الفرنسية",
"fr-BE": "الفرنسيّة (بلجيكيا)",
"fr-CA": "الفرنسيّة (كندا)",
"he": "العبريّة",
"hi": "الهنديّة",
"hr": "الكرواطيّة",
"hu": "الهنغارية",
"id": "الإندونيسيّة",
"it": "الإيطاليّة",
"ja": "اليابانيّة",
"kk": "الكازاخية",
"ko": "الكوريّة",
"ky": "القيرغيزية",
"lt": "الليتوانية",
"lv": "اللاتفيّة",
"ms": "الماليزية",
"nb": "البوكماول النرويجية",
"nl": "الهولنديّة",
"nn": "لغة نينورسك النرويجية",
"pes": "الفارسيّة",
"pl": "البولنديّة",
"pt": "البرتغاليّة",
"pt-BR": "البرتغاليّة (البرازيليّة)",
"ro": "الرومانيّة",
"ru": "الروسيّة",
"ru-x-bandit": "الروسيّة (بانديت)",
"sk": "السلوفاكية",
"sv": "السويديّة",
"th": "التايلنديّة",
"tok": "لغة التوكي بونا",
"tr": "التركيّة",
"tt": "لغة التتار",
"uk": "الأوكرانية",
"vi": "الفيتناميّة",
"zh-Hans": "الصينيّة (المبسّطة)",
"zh-Hant": "الصينيّة (القديمة)"
}
@@ -1,58 +0,0 @@
{
"ar": "Arabština",
"be": "Běloruština",
"bg": "Bulharština",
"bn": "Bengálština",
"ca": "Katalánština",
"cs": "Čeština",
"da": "Dánština",
"de": "Němčina",
"de-CH": "Němčina (Švýcarsko)",
"el": "Řečtina",
"en-GB": "Angličtina (Velká Británie)",
"en-US": "Angličtina (Spojené státy americké)",
"en-x-lolcat": "LOLCAT",
"en-x-pirate": "Angličtina (Pirátská)",
"en-x-updown": "Angličtina (Vzhůru nohama)",
"en-x-uwu": "Angličtina (UwU)",
"eo": "Esperanto",
"es": "Španělština",
"et": "Estonština",
"fi": "Finština",
"fr": "Francouzština",
"fr-BE": "Francouzština (Belgie)",
"fr-CA": "Francouzština (Kanada)",
"he": "Hebrejština",
"hi": "Hindština",
"hr": "Chorvatština",
"hu": "Maďarština",
"id": "Indonéština",
"it": "Italština",
"ja": "Japonština",
"kk": "Kazaština",
"ko": "Korejština",
"ky": "Kyrgyzština",
"lt": "Litevština",
"lv": "Lotyština",
"ms": "Malajština",
"nb": "Norština",
"nl": "Dánština",
"nn": "Norština",
"pes": "Perština",
"pl": "Polština",
"pt": "Portugalština",
"pt-BR": "Portugalština (Brazílie)",
"ro": "Rumunština",
"ru": "Ruština",
"ru-x-bandit": "Ruština (Bandit)",
"sk": "Slovenština",
"sv": "Švédština",
"th": "Thajština",
"tok": "Toki Ponština",
"tr": "Turečtina",
"tt": "Tatarština",
"uk": "Ukrajinština",
"vi": "Vietnamština",
"zh-Hans": "Čínština (zjednodušená)",
"zh-Hant": "Čínština (tradiční)"
}
@@ -1,58 +0,0 @@
{
"ar": "Arabisk",
"be": "Hviderussisk",
"bg": "Bulgarsk",
"bn": "Bengali",
"ca": "Catalansk",
"cs": "Tjekkisk",
"da": "Dansk",
"de": "Tysk",
"de-CH": "Tysk (Schweiz)",
"el": "Græsk",
"en-GB": "Engelsk (Storbritannien)",
"en-US": "Engelsk (USA)",
"en-x-lolcat": "LOLCAT",
"en-x-pirate": "Engelsk (Pirat)",
"en-x-updown": "Engelsk (På hovedet)",
"en-x-uwu": "Engelsk (UwU)",
"eo": "Esperanto",
"es": "Spansk",
"et": "Estisk",
"fi": "Finsk",
"fr": "Fransk",
"fr-BE": "Fransk (Belgien)",
"fr-CA": "Fransk (Canada)",
"he": "Hebraisk",
"hi": "Hindi",
"hr": "Kroatisk",
"hu": "Ungarsk",
"id": "Indonesisk",
"it": "Italiensk",
"ja": "Japansk",
"kk": "Kasakhisk",
"ko": "Koreansk",
"ky": "Kirgisisk",
"lt": "Litauisk",
"lv": "Lettisk",
"ms": "Malajisk",
"nb": "Norsk (Bokmål)",
"nl": "Hollandsk",
"nn": "Norsk (Nynorsk)",
"pes": "Persisk",
"pl": "Polsk",
"pt": "Portugisisk",
"pt-BR": "Portugisisk (Brasilien)",
"ro": "Rumænsk",
"ru": "Russisk",
"ru-x-bandit": "Russisk (Bandit)",
"sk": "Slovakisk",
"sv": "Svensk",
"th": "Thailandsk",
"tok": "Toki pona",
"tr": "Tyrkisk",
"tt": "Tatarisk",
"uk": "Ukrainsk",
"vi": "Vietnamesisk",
"zh-Hans": "Kinesisk (Forenklet)",
"zh-Hant": "Kinesisk (Traditionel)"
}
@@ -1,58 +0,0 @@
{
"ar": "Arabisch",
"be": "Belarussisch",
"bg": "Bulgarisch",
"bn": "Bangalisch",
"ca": "Katalanisch",
"cs": "Tschechisch",
"da": "Dänisch",
"de": "Deutsch",
"de-CH": "Deutsch (Schweiz)",
"el": "Griechisch",
"en-GB": "Englisch (Vereinigtes Königreich)",
"en-US": "Englisch (Vereinigte Staaten)",
"en-x-lolcat": "LOLCAT",
"en-x-pirate": "Englisch (Piraten)",
"en-x-updown": "Englisch (Kopfüber)",
"en-x-uwu": "Englisch (UwU)",
"eo": "Esperanto",
"es": "Spanisch",
"et": "Estnisch",
"fi": "Finnisch",
"fr": "Französisch",
"fr-BE": "Französisch (Belgien)",
"fr-CA": "Französisch (Kanada)",
"he": "Hebräisch",
"hi": "Hindi",
"hr": "Kroatisch",
"hu": "Ungarisch",
"id": "Indonesisch",
"it": "Italienisch",
"ja": "Japanisch",
"kk": "Kasachisch",
"ko": "Koreanisch",
"ky": "Kirgisisch",
"lt": "Litauisch",
"lv": "Lettisch",
"ms": "Malaiisch",
"nb": "Norwegisch, Bokmål",
"nl": "Niederländisch",
"nn": "Neues Norwegisch",
"pes": "Persisch",
"pl": "Polnisch",
"pt": "Portugiesisch",
"pt-BR": "Portugiesisch (Brasilien)",
"ro": "Rumänisch",
"ru": "Russisch",
"ru-x-bandit": "Russisch (Bandit)",
"sk": "Slowakisch",
"sv": "Schwedisch",
"th": "Thailändisch",
"tok": "Toki Pona",
"tr": "Türkisch",
"tt": "Tatarisch",
"uk": "Ukrainisch",
"vi": "Viernamesisch",
"zh-Hans": "Chinesisch (Vereinfacht)",
"zh-Hant": "Chinesisch (Traditionell)"
}
@@ -1,58 +0,0 @@
{
"ar": "Arabisch",
"be": "Belarussisch",
"bg": "Bulgarisch",
"bn": "Bengalisch",
"ca": "Katalanisch",
"cs": "Tschechisch",
"da": "Dänisch",
"de": "Deutsch",
"de-CH": "Deutsch (Schweiz)",
"el": "Griechisch",
"en-GB": "Englisch (Vereinigtes Königreich)",
"en-US": "Englisch (Vereinigte Staaten)",
"en-x-lolcat": "LOLCAT",
"en-x-pirate": "Englisch (Piraten)",
"en-x-updown": "Englisch (Kopfüber)",
"en-x-uwu": "Englisch (UwU)",
"eo": "Esperanto",
"es": "Spanisch",
"et": "Estnisch",
"fi": "Finnisch",
"fr": "Französisch",
"fr-BE": "Französisch (Belgien)",
"fr-CA": "Französisch (Kanada)",
"he": "Hebräisch",
"hi": "Hindi",
"hr": "Kroatisch",
"hu": "Ungarisch",
"id": "Indonesisch",
"it": "Italienisch",
"ja": "Japanisch",
"kk": "Kasachisch",
"ko": "Koreanisch",
"ky": "Kirgisisch",
"lt": "Litauisch",
"lv": "Lettisch",
"ms": "Malaiisch",
"nb": "Norwegisch Bokmål",
"nl": "Niederländisch",
"nn": "Norwegisch Nynorsk",
"pes": "Persisch",
"pl": "Polnisch",
"pt": "Portugiesisch",
"pt-BR": "Portugiesisch (Brasilien)",
"ro": "Rumänisch",
"ru": "Russisch",
"ru-x-bandit": "Russisch (Bandit)",
"sk": "Slowakisch",
"sv": "Schwedisch",
"th": "Thailändisch",
"tok": "Toki Pona",
"tr": "Türkisch",
"tt": "Tatarisch",
"uk": "Ukrainisch",
"vi": "Vietnamesisch",
"zh-Hans": "Chinesisch (Vereinfacht)",
"zh-Hant": "Chinesisch (Traditionell)"
}
+15 -6
View File
@@ -1793,9 +1793,6 @@
"muralpay.field.bank-account-number": {
"message": "Account number"
},
"muralpay.field.branch-code": {
"message": "Branch code"
},
"muralpay.field.clabe": {
"message": "CLABE"
},
@@ -1826,6 +1823,9 @@
"muralpay.field.pix-phone": {
"message": "PIX phone"
},
"muralpay.field.random-key": {
"message": "Random key"
},
"muralpay.field.routing-number": {
"message": "Routing number"
},
@@ -1886,9 +1886,6 @@
"muralpay.placeholder.enter-account-number": {
"message": "Enter account number"
},
"muralpay.placeholder.enter-branch-code": {
"message": "Enter branch code"
},
"muralpay.placeholder.enter-clabe": {
"message": "Enter 18-digit CLABE"
},
@@ -1901,6 +1898,9 @@
"muralpay.placeholder.enter-pix-email": {
"message": "Enter PIX email"
},
"muralpay.placeholder.enter-random-key": {
"message": "Enter random key"
},
"muralpay.placeholder.enter-routing-number": {
"message": "Enter 9-digit routing number"
},
@@ -2132,6 +2132,12 @@
"project-type.datapack.singular": {
"message": "Data Pack"
},
"project-type.minecraft_java_server.plural": {
"message": "Servers"
},
"project-type.minecraft_java_server.singular": {
"message": "Server"
},
"project-type.mod.plural": {
"message": "Mods"
},
@@ -2990,6 +2996,9 @@
"settings.display.project-list-layouts.resourcepack": {
"message": "Resource Packs page"
},
"settings.display.project-list-layouts.server": {
"message": "Servers page"
},
"settings.display.project-list-layouts.shader": {
"message": "Shaders page"
},
@@ -1,58 +0,0 @@
{
"ar": "Arabic",
"be": "Belarusian",
"bg": "Bulgarian",
"bn": "Bangla",
"ca": "Catalan",
"cs": "Czech",
"da": "Danish",
"de": "German",
"de-CH": "German (Switzerland)",
"el": "Greek",
"en-GB": "English (United Kingdom)",
"en-US": "English (United States)",
"en-x-lolcat": "LOLCAT",
"en-x-pirate": "English (Pirate)",
"en-x-updown": "English (Upside down)",
"en-x-uwu": "English (UwU)",
"eo": "Esperanto",
"es": "Spanish",
"et": "Estonian",
"fi": "Finnish",
"fr": "French",
"fr-BE": "French (Belgium)",
"fr-CA": "French (Canada)",
"he": "Hebrew",
"hi": "Hindi",
"hr": "Croatian",
"hu": "Hungarian",
"id": "Indonesian",
"it": "Italian",
"ja": "Japanese",
"kk": "Kazakh",
"ko": "Korean",
"ky": "Kyrgyz",
"lt": "Lithuanian",
"lv": "Latvian",
"ms": "Malay",
"nb": "Norwegian Bokmål",
"nl": "Dutch",
"nn": "Norwegian Nynorsk",
"pes": "Persian",
"pl": "Polish",
"pt": "Portuguese",
"pt-BR": "Portuguese (Brazil)",
"ro": "Romanian",
"ru": "Russian",
"ru-x-bandit": "Russian (Bandit)",
"sk": "Slovak",
"sv": "Swedish",
"th": "Thai",
"tok": "Toki Pona",
"tr": "Turkish",
"tt": "Tatar",
"uk": "Ukrainian",
"vi": "Vietnamese",
"zh-Hans": "Chinese (Simplified)",
"zh-Hant": "Chinese (Traditional)"
}
@@ -1,58 +0,0 @@
{
"ar": "Árabe",
"be": "Bielorruso",
"bg": "Búlgaro",
"bn": "Bengalí",
"ca": "Catalán",
"cs": "Checo",
"da": "Danés",
"de": "Alemán",
"de-CH": "Alemán (Suiza)",
"el": "Griego",
"en-GB": "Inglés (Reino Unido)",
"en-US": "Inglés (Estados Unidos)",
"en-x-lolcat": "LOLCAT",
"en-x-pirate": "Inglés (Pirata)",
"en-x-updown": "Inglés (Al revés)",
"en-x-uwu": "Inglés (UwU)",
"eo": "Esperanto",
"es": "Español",
"et": "Estonio",
"fi": "Finlandés",
"fr": "Francés",
"fr-BE": "Francés (Bélgica)",
"fr-CA": "Francés (Canadá)",
"he": "Hebreo",
"hi": "Hindi",
"hr": "Croata",
"hu": "Húngaro",
"id": "Indonesio",
"it": "Italiano",
"ja": "Japonés",
"kk": "Kazajo",
"ko": "Coreano",
"ky": "Kirguís",
"lt": "Lituano",
"lv": "Letón",
"ms": "Malayo",
"nb": "Noruego bokmål",
"nl": "Holandés",
"nn": "Nynorsk noruego",
"pes": "Persa",
"pl": "Polaco",
"pt": "Portugués",
"pt-BR": "Portugués (Brasil)",
"ro": "Rumano",
"ru": "Ruso",
"ru-x-bandit": "Ruso",
"sk": "Eslovaco",
"sv": "Sueco",
"th": "Tailandés",
"tok": "Toki Pona",
"tr": "Turco",
"tt": "Tártaro",
"uk": "Ucraniano",
"vi": "Vietnamita",
"zh-Hans": "Chino (Simplificado)",
"zh-Hant": "Chino (Tradicional)"
}
@@ -1,58 +0,0 @@
{
"ar": "Árabe",
"be": "Bielorruso",
"bg": "Búlgaro",
"bn": "Bengalí",
"ca": "Catalán",
"cs": "Checo",
"da": "Danés",
"de": "Alemán",
"de-CH": "Alemán (Suiza)",
"el": "Griego",
"en-GB": "Inglés (Reino Unido)",
"en-US": "Inglés (Estados Unidos)",
"en-x-lolcat": "Gatuno",
"en-x-pirate": "Inglés (Pirata)",
"en-x-updown": "Inglés (Al revés)",
"en-x-uwu": "Inglés (UwU)",
"eo": "Esperanto",
"es": "Español",
"et": "Estonio",
"fi": "Finlandés",
"fr": "Francés",
"fr-BE": "Francés (Bélgica)",
"fr-CA": "Francés (Canadá)",
"he": "Hebreo",
"hi": "Hindi",
"hr": "Croata",
"hu": "Húngaro",
"id": "Indonesio",
"it": "Italiano",
"ja": "Japonés",
"kk": "Kazajo",
"ko": "Coreano",
"ky": "Kirguís",
"lt": "Lituano",
"lv": "Letón",
"ms": "Malayo",
"nb": "Noruego Estándar",
"nl": "Holandés",
"nn": "Noruego Nuevo",
"pes": "Persa",
"pl": "Polaco",
"pt": "Portugués",
"pt-BR": "Portugués (Brasil)",
"ro": "Rumano",
"ru": "Ruso",
"ru-x-bandit": "Ruso (Bandido)",
"sk": "Eslovaco",
"sv": "Sueco",
"th": "Tailandés",
"tok": "Lenguaje Simple",
"tr": "Turco",
"tt": "Tártaro",
"uk": "Ucraniano",
"vi": "Vietnamita",
"zh-Hans": "Chino (Simplificado)",
"zh-Hant": "Chino (Tradicional)"
}
@@ -1,58 +0,0 @@
{
"ar": "Arabia",
"be": "Valkovenäjä",
"bg": "Bulgaria",
"bn": "Bangla",
"ca": "Katalaani",
"cs": "Tšekki",
"da": "Tanska",
"de": "Saksa",
"de-CH": "Saksa (Sveitsi)",
"el": "Kreikka",
"en-GB": "Englanti (Yhdistynyt Kuningaskunta)",
"en-US": "Englanti (Yhdysvallat)",
"en-x-lolcat": "LOLCAT",
"en-x-pirate": "Englanti (Merirosvo)",
"en-x-updown": "Englanti (ylösalaisin)",
"en-x-uwu": "Englanti (UwU)",
"eo": "Esperanto",
"es": "Espanja",
"et": "Viro",
"fi": "Suomi",
"fr": "Ranska",
"fr-BE": "Ranska (Belgia)",
"fr-CA": "Ranska (Kanada)",
"he": "Heprea",
"hi": "Hindi",
"hr": "Kroatia",
"hu": "Unkari",
"id": "Indonesia",
"it": "Italia",
"ja": "Japani",
"kk": "Kazakki",
"ko": "Korea",
"ky": "Kirgiisi",
"lt": "Liettua",
"lv": "Latvia",
"ms": "Malaiji",
"nb": "Norjan kirjakieli",
"nl": "Hollanti",
"nn": "Norja uusi",
"pes": "Persia",
"pl": "Puola",
"pt": "Portugali",
"pt-BR": "Portugali (Brasilia)",
"ro": "Romania",
"ru": "Venäjä",
"ru-x-bandit": "Venäjä (Bandit)",
"sk": "Slovakia",
"sv": "Ruotsi",
"th": "Thai",
"tok": "Toki Pona",
"tr": "Turkkilainen",
"tt": "Tataari",
"uk": "Ukraina",
"vi": "Vietnami",
"zh-Hans": "Kiina (yksinkertaistettu)",
"zh-Hant": "Kiina (Perinteinen)"
}
@@ -1,58 +0,0 @@
{
"ar": "Arabo",
"be": "Biyeloruso",
"bg": "Bulgaro",
"bn": "Bangla",
"ca": "Katalan",
"cs": "Tseko",
"da": "Danes",
"de": "Jerman",
"de-CH": "Jerman (Suwisa)",
"el": "Griyego",
"en-GB": "Ingles (Reyno Unido)",
"en-US": "Ingles (Estados Unidos)",
"en-x-lolcat": "LOLCAT",
"en-x-pirate": "Ingles (Pirata)",
"en-x-updown": "Ingles (Tumbalik)",
"en-x-uwu": "Ingles (UwU)",
"eo": "Esperanto",
"es": "Espanyol",
"et": "Estonyo",
"fi": "Finlandes",
"fr": "Pranses",
"fr-BE": "Pranses (Belhika)",
"fr-CA": "Pranses (Canada)",
"he": "Hebreo",
"hi": "Hindi",
"hr": "Kroato",
"hu": "Ungaro",
"id": "Indones",
"it": "Italyano",
"ja": "Hapon",
"kk": "Kasaho",
"ko": "Koreano",
"ky": "Kirgis",
"lt": "Litwano",
"lv": "Latbiyano",
"ms": "Malayo",
"nb": "Norwegong Bokmål",
"nl": "Olandes",
"nn": "Norwegong Nynorsk",
"pes": "Persa",
"pl": "Polako",
"pt": "Portuges",
"pt-BR": "Portuges (Brasil)",
"ro": "Rumano",
"ru": "Ruso",
"ru-x-bandit": "Ruso (Bandido)",
"sk": "Eslobako",
"sv": "Suweko",
"th": "Taylandes",
"tok": "Toki Pona",
"tr": "Turko",
"tt": "Tartaro",
"uk": "Ukranyo",
"vi": "Biyetnamita",
"zh-Hans": "Tsino (Simple)",
"zh-Hant": "Tsino (Tradisyonal)"
}
@@ -1,58 +0,0 @@
{
"ar": "Arabe",
"be": "Biélorusse",
"bg": "Bulgare",
"bn": "Bengali",
"ca": "Catalan",
"cs": "Tchèque",
"da": "Danois",
"de": "Allemand",
"de-CH": "Allemand (Suisse)",
"el": "Grec",
"en-GB": "Anglais (Royaume-Uni)",
"en-US": "Anglais (États-Unis)",
"en-x-lolcat": "LOLCAT",
"en-x-pirate": "Anglais (Pirate)",
"en-x-updown": "Anglais (À l'envers)",
"en-x-uwu": "Anglais (UwU)",
"eo": "Espéranto",
"es": "Espagnol",
"et": "Estonien",
"fi": "Finnois",
"fr": "Français",
"fr-BE": "Français (Belgique)",
"fr-CA": "Français (Canada)",
"he": "Hébreu",
"hi": "Hindi",
"hr": "Croate",
"hu": "Hongrois",
"id": "Indonésien",
"it": "Italien",
"ja": "Japonais",
"kk": "Kazakh",
"ko": "Coréen",
"ky": "Kirghize",
"lt": "Lituanien",
"lv": "Letton",
"ms": "Malais",
"nb": "Norvégien Bokmål",
"nl": "Néerlandais",
"nn": "Norvégien Nynorsk",
"pes": "Farsi",
"pl": "Polonais",
"pt": "Portugais",
"pt-BR": "Portugais (Brésil)",
"ro": "Roumain",
"ru": "Russe",
"ru-x-bandit": "Russe (Bandit)",
"sk": "Slovaque",
"sv": "Suédois",
"th": "Thaïlandais",
"tok": "Toki pona",
"tr": "Turc",
"tt": "Tatar",
"uk": "Ukrainien",
"vi": "Vietnamien",
"zh-Hans": "Chinois (simplifié)",
"zh-Hant": "Chinois (traditionnel)"
}
@@ -1,58 +0,0 @@
{
"ar": "ערבית",
"be": "בלרוסית",
"bg": "בולגרית",
"bn": "בנגלית",
"ca": "קטלאנית",
"cs": "צ'כית",
"da": "דנית",
"de": "גרמנית",
"de-CH": "גרמנית (שווייץ)",
"el": "יוונית",
"en-GB": "אנגלית (בריטניה)",
"en-US": "אנגלית (ארצות הברית)",
"en-x-lolcat": "חתולול",
"en-x-pirate": "אנגלית (פיראטים)",
"en-x-updown": "אנגלית (הפוכה)",
"en-x-uwu": "אנגלית (UwU)",
"eo": "אספרנטו",
"es": "ספרדית",
"et": "אסטונית",
"fi": "פינית",
"fr": "צרפתית",
"fr-BE": "צרפתית (בלגיה)",
"fr-CA": "צרפתית (קנדה)",
"he": "עברית",
"hi": "הודית",
"hr": "קרואטית",
"hu": "הונגרית",
"id": "אינדונזית",
"it": "איטלקית",
"ja": "יפנית",
"kk": "קזחית",
"ko": "קוריאנית",
"ky": "קירגיזית",
"lt": "ליטאית",
"lv": "לטבית",
"ms": "מלאית",
"nb": "נורבגית ספרותית",
"nl": "הולנדית",
"nn": "נורבגית נינורשק",
"pes": "פרסית",
"pl": "פולנית",
"pt": "פורטוגזית",
"pt-BR": "פורטוגזית (ברזיל)",
"ro": "רומנית",
"ru": "רוסית",
"ru-x-bandit": "רוסית (בנדיט)",
"sk": "סלובקית",
"sv": "שוודית",
"th": "תאילנדית",
"tok": "טוקי פונה",
"tr": "טורקית",
"tt": "טטרית",
"uk": "אוקראינית",
"vi": "ויאטנמית",
"zh-Hans": "סינית (פשוטה)",
"zh-Hant": "סינית (מסורתית)"
}
@@ -1,58 +0,0 @@
{
"ar": "Arab",
"be": "Fehérorosz",
"bg": "Bolgár",
"bn": "Bengáli",
"ca": "Katalán",
"cs": "Cseh",
"da": "Dán",
"de": "Német",
"de-CH": "Német (Svájc)",
"el": "Görög",
"en-GB": "Angol (Egyesült Királyság)",
"en-US": "Angol (Egyesült Államok)",
"en-x-lolcat": "LOLCAT",
"en-x-pirate": "Angol (Kalóz)",
"en-x-updown": "Angol (fejjel lefelé)",
"en-x-uwu": "Angol (UwU)",
"eo": "Eszperantó",
"es": "Spanyol",
"et": "Észt",
"fi": "Finn",
"fr": "Francia",
"fr-BE": "Francia (Belgium)",
"fr-CA": "Francia (Kanada)",
"he": "Héber",
"hi": "Hindi",
"hr": "Horvát",
"hu": "Magyar",
"id": "Indonéz",
"it": "Olasz",
"ja": "Japán",
"kk": "Kazak",
"ko": "Koreai",
"ky": "Kirgiz",
"lt": "Litván",
"lv": "Lett",
"ms": "Maláj",
"nb": "Norvég Bokmál",
"nl": "Holland",
"nn": "Norvég nynorsk",
"pes": "Perzsa",
"pl": "Lengyel",
"pt": "Portugál",
"pt-BR": "Portugál (Brazília)",
"ro": "Román",
"ru": "Orosz",
"ru-x-bandit": "Orosz (Bandita)",
"sk": "Szlovák",
"sv": "Svéd",
"th": "Thai",
"tok": "Toki Pona",
"tr": "Török",
"tt": "Tatár",
"uk": "Ukrán",
"vi": "Vietnámi",
"zh-Hans": "Kínai (Egyszerűsített)",
"zh-Hant": "Kínai (Hagyományos)"
}
@@ -1,58 +0,0 @@
{
"ar": "Bahasa Arab",
"be": "Bahasa Belarusia",
"bg": "Bahasa Bulgaria",
"bn": "Bahasa Bengali",
"ca": "Bahasa Katalan",
"cs": "Bahasa Ceko",
"da": "Bahasa Denmark",
"de": "Bahasa Jerman",
"de-CH": "Bahasa Jerman (Swiss)",
"el": "Bahasa Yunani",
"en-GB": "Bahasa Inggris (Britania Raya)",
"en-US": "Bahasa Inggris (Amerika Serikat)",
"en-x-lolcat": "LOLCAT",
"en-x-pirate": "Bahasa Inggris (Bajak Laut)",
"en-x-updown": "Bahasa Inggris (Terbalik)",
"en-x-uwu": "Bahasa Inggris (UwU)",
"eo": "Bahasa Esperanto",
"es": "Bahasa Spanyol",
"et": "Bahasa Estonia",
"fi": "Bahasa Finlandia",
"fr": "Bahasa Prancis",
"fr-BE": "Bahasa Prancis (Belgia)",
"fr-CA": "Bahasa Prancis (Kanada)",
"he": "Bahasa Ibrani",
"hi": "Bahasa Hindi",
"hr": "Bahasa Kroasia",
"hu": "Bahasa Hungaria",
"id": "Bahasa Indonesia",
"it": "Bahasa Italia",
"ja": "Bahasa Jepang",
"kk": "Bahasa Kazak",
"ko": "Bahasa Korea",
"ky": "Bahasa Kirgiz",
"lt": "Bahasa Lituania",
"lv": "Bahasa Latvia",
"ms": "Bahasa Melayu",
"nb": "Bahasa Norwegia (Bokmål)",
"nl": "Bahasa Belanda",
"nn": "Bahasa Norwegia (Nynorsk)",
"pes": "Bahasa Persia",
"pl": "Bahasa Polandia",
"pt": "Bahasa Portugis",
"pt-BR": "Bahasa Portugis (Brasil)",
"ro": "Bahasa Rumania",
"ru": "Bahasa Rusia",
"ru-x-bandit": "Bahasa Rusia (Bandit)",
"sk": "Bahasa Slowakia",
"sv": "Bahasa Swedia",
"th": "Bahasa Thai",
"tok": "Bahasa Toki Pona",
"tr": "Bahasa Turki",
"tt": "Bahasa Tatar",
"uk": "Bahasa Ukraina",
"vi": "Bahasa Vietnam",
"zh-Hans": "Bahasa China (Aksara Sederhana)",
"zh-Hant": "Bahasa China (Aksara Tradisional)"
}
@@ -1,58 +0,0 @@
{
"ar": "Arabo",
"be": "Bielorusso",
"bg": "Bulgaro",
"bn": "Bengalese",
"ca": "Catalano",
"cs": "Ceco",
"da": "Danese",
"de": "Tedesco",
"de-CH": "Tedesco (Svizzera)",
"el": "Greco",
"en-GB": "Inglese (Regno Unito)",
"en-US": "Inglese (Stati Uniti)",
"en-x-lolcat": "LOLCAT",
"en-x-pirate": "Inglese (Pirata)",
"en-x-updown": "Inglese (Capovolto)",
"en-x-uwu": "Inglese (UwU)",
"eo": "Esperanto",
"es": "Spagnolo",
"et": "Estone",
"fi": "Finlandese",
"fr": "Francese",
"fr-BE": "Francese (Belgio)",
"fr-CA": "Francese (Canada)",
"he": "Ebraico",
"hi": "Hindi",
"hr": "Croato",
"hu": "Ungherese",
"id": "Indonesiano",
"it": "Italiano",
"ja": "Giapponese",
"kk": "Kazako",
"ko": "Coreano",
"ky": "Kirghiso",
"lt": "Lituano",
"lv": "Lettone",
"ms": "Malese",
"nb": "Norvegese Bokmål",
"nl": "Olandese",
"nn": "Norvegese Nynorsk",
"pes": "Persiano",
"pl": "Polacco",
"pt": "Portoghese",
"pt-BR": "Portoghese (Brasile)",
"ro": "Rumeno",
"ru": "Russo",
"ru-x-bandit": "Russo (Bandit)",
"sk": "Slovacco",
"sv": "Svedese",
"th": "Thailandese",
"tok": "Toki Pona",
"tr": "Turco",
"tt": "Tataro",
"uk": "Ucraino",
"vi": "Vietnamita",
"zh-Hans": "Cinese (Semplificato)",
"zh-Hant": "Cinese (Tradizionale)"
}
@@ -1,58 +0,0 @@
{
"ar": "アラビア語",
"be": "ベラルーシ語",
"bg": "ブルガリア語",
"bn": "ベンガル語",
"ca": "カタルーニャ語",
"cs": "チェコ語",
"da": "デンマーク語",
"de": "ドイツ語",
"de-CH": "ドイツ語 (スイス)",
"el": "ギリシャ語",
"en-GB": "英語 (イギリス)",
"en-US": "英語 (アメリカ)",
"en-x-lolcat": "ロルキャット語",
"en-x-pirate": "英語 (海賊)",
"en-x-updown": "英語 (上下逆)",
"en-x-uwu": "英語 (UwU)",
"eo": "エスペラント",
"es": "スペイン語",
"et": "エストニア語",
"fi": "フィンランド語",
"fr": "フランス語",
"fr-BE": "フランス語 (ベルギー)",
"fr-CA": "フランス語 (カナダ)",
"he": "ヘブライ語",
"hi": "ヒンディー語",
"hr": "クロアチア語",
"hu": "ハンガリー語",
"id": "インドネシア語",
"it": "イタリア語",
"ja": "日本語",
"kk": "カザフ語",
"ko": "韓国語",
"ky": "キルギス語",
"lt": "リトアニア語",
"lv": "ラトビア語",
"ms": "マレー語",
"nb": "ノルウェー語 (ブークモール)",
"nl": "オランダ語",
"nn": "ノルウェー語 (ニーノシュク)",
"pes": "ペルシャ語",
"pl": "ポーランド語",
"pt": "ポルトガル語",
"pt-BR": "ポルトガル語 (ブラジル)",
"ro": "ルーマニア語",
"ru": "ロシア語",
"ru-x-bandit": "ロシア語 (ギャング)",
"sk": "スロバキア語",
"sv": "スウェーデン語",
"th": "タイ語",
"tok": "トキポナ",
"tr": "トルコ語",
"tt": "タタール語",
"uk": "ウクライナ語",
"vi": "ベトナム語",
"zh-Hans": "中国語 (簡体字)",
"zh-Hant": "中国語 (繁体字)"
}
@@ -1,58 +0,0 @@
{
"ar": "아랍어",
"be": "벨라루스어",
"bg": "불가리아어",
"bn": "벵골어",
"ca": "카탈루냐어",
"cs": "체코어",
"da": "덴마크어",
"de": "독일어",
"de-CH": "독일어 (스위스)",
"el": "그리스어",
"en-GB": "영어 (영국)",
"en-US": "영어 (미국)",
"en-x-lolcat": "LOLCAT",
"en-x-pirate": "영어 (해적)",
"en-x-updown": "영어 (거꾸로)",
"en-x-uwu": "영어 (UwU)",
"eo": "에스페란토",
"es": "스페인어",
"et": "에스토니아어",
"fi": "핀란드어",
"fr": "프랑스어",
"fr-BE": "프랑스어 (벨기에)",
"fr-CA": "프랑스어 (캐나다)",
"he": "히브리어",
"hi": "힌디어",
"hr": "크로아티아어",
"hu": "헝가리어",
"id": "인도네시아어",
"it": "이탈리아어",
"ja": "일본어",
"kk": "카자흐어",
"ko": "한국어",
"ky": "키르기스어",
"lt": "리투아니아어",
"lv": "라트비아어",
"ms": "말레이어",
"nb": "노르웨이어 보크몰",
"nl": "네덜란드어",
"nn": "노르웨이어 뉘노르스크",
"pes": "페르시아어",
"pl": "폴란드어",
"pt": "포르투갈어",
"pt-BR": "포르투갈어 (브라질)",
"ro": "루마니아어",
"ru": "러시아어",
"ru-x-bandit": "러시아어 (구어)",
"sk": "슬로바키아어",
"sv": "스웨덴어",
"th": "태국어",
"tok": "도기 보나",
"tr": "튀르키예어",
"tt": "타타르어",
"uk": "우크라이나어",
"vi": "베트남어",
"zh-Hans": "중국어 (간체)",
"zh-Hant": "중국어 (번체)"
}
@@ -1,58 +0,0 @@
{
"ar": "Bahasa Arab",
"be": "Bahasa Belarus",
"bg": "Bahasa Bulgaria",
"bn": "Bahasa Bengali",
"ca": "Bahasa Catalonia",
"cs": "Bahasa Czech",
"da": "Bahasa Denmark",
"de": "Bahasa Jerman",
"de-CH": "Bahasa Jerman (Switzerland)",
"el": "Bahasa Yunani",
"en-GB": "Bahasa Inggeris (United Kingdom)",
"en-US": "Bahasa Inggeris (Amerika Syarikat)",
"en-x-lolcat": "LOLCAT",
"en-x-pirate": "Bahasa Inggeris (Lanun)",
"en-x-updown": "Bahasa Inggeris (Terbalik)",
"en-x-uwu": "Bahasa Inggeris (UwU)",
"eo": "Bahasa Esperanto",
"es": "Bahasa Sepanyol",
"et": "Bahasa Estonia",
"fi": "Bahasa Finland",
"fr": "Bahasa Perancis",
"fr-BE": "Bahasa Perancis (Belgium)",
"fr-CA": "Bahasa Perancis (Kanada)",
"he": "Bahasa Ibrani",
"hi": "Bahasa Hindi",
"hr": "Bahasa Croatia",
"hu": "Bahasa Hungary",
"id": "Bahasa Indonesia",
"it": "Bahasa Itali",
"ja": "Bahasa Jepun",
"kk": "Bahasa Kazakh",
"ko": "Bahasa Korea",
"ky": "Bahasa Kyrgyz",
"lt": "Bahasa Lithuania",
"lv": "Bahasa Latvia",
"ms": "Bahasa Melayu",
"nb": "Bahasa Norway (Bokmål)",
"nl": "Bahasa Belanda",
"nn": "Bahasa Norway (Nynorsk)",
"pes": "Bahasa Farsi",
"pl": "Bahasa Poland",
"pt": "Bahasa Portugis",
"pt-BR": "Bahasa Portugis (Brazil)",
"ro": "Bahasa Romania",
"ru": "Bahasa Rusia",
"ru-x-bandit": "Bahasa Rusia (Bandit)",
"sk": "Bahasa Slovak",
"sv": "Bahasa Sweden",
"th": "Bahasa Thai",
"tok": "Bahasa Toki Pona",
"tr": "Bahasa Turki",
"tt": "Bahasa Tatar",
"uk": "Bahasa Ukraine",
"vi": "Bahasa Vietnam",
"zh-Hans": "Bahasa Cina (Mudah)",
"zh-Hant": "Bahasa Cina (Tradisional)"
}
@@ -1,58 +0,0 @@
{
"ar": "Arabisch",
"be": "Wit-Russisch",
"bg": "Bulgaars",
"bn": "Bengaals",
"ca": "Catalaans",
"cs": "Tsjechisch",
"da": "Deens",
"de": "Duits",
"de-CH": "Duits (Zwitserland)",
"el": "Grieks",
"en-GB": "Engels (Verenigd Koninkrijk)",
"en-US": "Engels (Verenigde Staten)",
"en-x-lolcat": "LOLCAT",
"en-x-pirate": "Engels (Piraat)",
"en-x-updown": "Engels (Ondersteboven)",
"en-x-uwu": "Engels (UwU)",
"eo": "Esperanto",
"es": "Spaans",
"et": "Estisch",
"fi": "Fins",
"fr": "Frans",
"fr-BE": "Frans (België)",
"fr-CA": "Frans (Canada)",
"he": "Hebreeuws",
"hi": "Hindi",
"hr": "Kroatisch",
"hu": "Hongaars",
"id": "Indonesisch",
"it": "Italiaans",
"ja": "Japans",
"kk": "Kazachs",
"ko": "Koreaans",
"ky": "Kirgizisch",
"lt": "Litouws",
"lv": "Lets",
"ms": "Maleis",
"nb": "Noors Bokmål",
"nl": "Nederlands",
"nn": "Noors Nynorsk",
"pes": "Perzisch",
"pl": "Pools",
"pt": "Portugees",
"pt-BR": "Portugees (Brazilië)",
"ro": "Roemeens",
"ru": "Russisch",
"ru-x-bandit": "Russisch (Bandit)",
"sk": "Slowaaks",
"sv": "Zweeds",
"th": "Thais",
"tok": "Toki Pona",
"tr": "Turks",
"tt": "Tataars",
"uk": "Oekraïens",
"vi": "Vietnamees",
"zh-Hans": "Chinees (Vereenvoudigd)",
"zh-Hant": "Chinees (Traditioneel)"
}
@@ -1,58 +0,0 @@
{
"ar": "Arabisk",
"be": "Belarusisk",
"bg": "Bulgarsk",
"bn": "Bangla",
"ca": "Katalansk",
"cs": "Tsjekkisk",
"da": "Dansk",
"de": "Tysk",
"de-CH": "Tysk (Sveits)",
"el": "Gresk",
"en-GB": "Engelsk (Storbritannia)",
"en-US": "Engelsk (USA)",
"en-x-lolcat": "LOLCAT",
"en-x-pirate": "Engelsk (Pirat)",
"en-x-updown": "Engelsk (Opp ned)",
"en-x-uwu": "Engelsk (UwU)",
"eo": "Esperanto",
"es": "Spansk",
"et": "Estisk",
"fi": "Finsk",
"fr": "Fransk",
"fr-BE": "Fransk (Belgia)",
"fr-CA": "Fransk (Kanada)",
"he": "Hebraisk",
"hi": "Hindi",
"hr": "Kroatisk",
"hu": "Ungarsk",
"id": "Indonesisk",
"it": "Italiensk",
"ja": "Japansk",
"kk": "Kasakhisk",
"ko": "Koreansk",
"ky": "Kirgisisk",
"lt": "Litauisk",
"lv": "Latvisk",
"ms": "Malaysisk",
"nb": "Norsk Bokmål",
"nl": "Nederlansk",
"nn": "Norsk Nynorsk",
"pes": "Persisk",
"pl": "Polsk",
"pt": "Portugisisk",
"pt-BR": "Portugisisk (Brasil)",
"ro": "Rumensk",
"ru": "Russisk",
"ru-x-bandit": "Russisk (Bandit)",
"sk": "Slovensk",
"sv": "Svensk",
"th": "Thai",
"tok": "Toki Pona",
"tr": "Tyrkisk",
"tt": "Tatar",
"uk": "Ukranisk",
"vi": "Vietnamesisk",
"zh-Hans": "Kinesisk (Forenklet)",
"zh-Hant": "Kinesisk (Tradisjonelt)"
}
@@ -1,58 +0,0 @@
{
"ar": "Arabski",
"be": "Białoruski",
"bg": "Bułgarski",
"bn": "Bengalski",
"ca": "Kataloński",
"cs": "Czeski",
"da": "Duński",
"de": "Niemiecki",
"de-CH": "Niemiecki (Szwajcaria)",
"el": "Grecki",
"en-GB": "Angielski (Wielka Brytania)",
"en-US": "Angielski (Stany Zjednoczone)",
"en-x-lolcat": "LOLCAT",
"en-x-pirate": "Angielski (Piracki)",
"en-x-updown": "Angielski (Do góry nogami)",
"en-x-uwu": "Angielski (UwU)",
"eo": "Esperanto",
"es": "Hiszpański",
"et": "Estoński",
"fi": "Fiński",
"fr": "Francuski",
"fr-BE": "Francuski (Belgia)",
"fr-CA": "Francuski (Kanada)",
"he": "Hebrajski",
"hi": "Hindi",
"hr": "Chorwacki",
"hu": "Węgierski",
"id": "Indonezyjski",
"it": "Włoski",
"ja": "Japoński",
"kk": "Kazachski",
"ko": "Koreański",
"ky": "Kirgiski",
"lt": "Litewski",
"lv": "Łotewski",
"ms": "Malajski",
"nb": "Norweski (Książkowy)",
"nl": "Holenderski",
"nn": "Norweski (Nowonorweski)",
"pes": "Perski",
"pl": "Polski",
"pt": "Portugalski",
"pt-BR": "Portugalski (Brazylia)",
"ro": "Rumuński",
"ru": "Rosyjski",
"ru-x-bandit": "Rosyjski (Bandycki)",
"sk": "Słowacki",
"sv": "Szwedzki",
"th": "Tajski",
"tok": "Toki Pona",
"tr": "Turecki",
"tt": "Tatarski",
"uk": "Ukraiński",
"vi": "Wietnamski",
"zh-Hans": "Chiński (Uproszczony)",
"zh-Hant": "Chiński (Tradycyjny)"
}
@@ -1,58 +0,0 @@
{
"ar": "Árabe",
"be": "Bielorrusso",
"bg": "Búlgaro",
"bn": "Bengali",
"ca": "Catalão",
"cs": "Tcheco",
"da": "Dinamarquês",
"de": "Alemão",
"de-CH": "Alemão (Suíça)",
"el": "Grego",
"en-GB": "Inglês (Reino Unido)",
"en-US": "Inglês (Estados Unidos)",
"en-x-lolcat": "LOLCAT",
"en-x-pirate": "Inglês (Pirata)",
"en-x-updown": "Inglês (Invertido)",
"en-x-uwu": "Inglês (Fofinho)",
"eo": "Esperanto",
"es": "Espanhol",
"et": "Estoniano",
"fi": "Finlandês",
"fr": "Francês",
"fr-BE": "Francês (Bélgica)",
"fr-CA": "Francês (Canadá)",
"he": "Hebraico",
"hi": "Indiano",
"hr": "Croata",
"hu": "Húngaro",
"id": "Indonésio",
"it": "Italiano",
"ja": "Japonês",
"kk": "Cazaque",
"ko": "Coreano",
"ky": "Quirguiz",
"lt": "Lituano",
"lv": "Letão",
"ms": "Malaio",
"nb": "Norueguês Bokmål",
"nl": "Holandês",
"nn": "Norueguês Nynorsk",
"pes": "Persa",
"pl": "Polonês",
"pt": "Português (Portugal)",
"pt-BR": "Português (Brasil)",
"ro": "Romeno",
"ru": "Russo",
"ru-x-bandit": "Russo (Informal)",
"sk": "Eslovaco",
"sv": "Sueco",
"th": "Tailandês",
"tok": "Toki Pona",
"tr": "Turco",
"tt": "Tártaro",
"uk": "Ucraniano",
"vi": "Vietnamita",
"zh-Hans": "Chinês (Simplificado)",
"zh-Hant": "Chinês (Tradicional)"
}
@@ -1,58 +0,0 @@
{
"ar": "Árabe",
"be": "Bielorrusso",
"bg": "Búlgaro",
"bn": "Bengali",
"ca": "Catalão",
"cs": "Checo",
"da": "Dinamarquês",
"de": "Alemão",
"de-CH": "Alemão (Suíça)",
"el": "Grego",
"en-GB": "Inglês (Reino Unido)",
"en-US": "Inglês (Estados Unidos)",
"en-x-lolcat": "LOLCAT",
"en-x-pirate": "Inglês (Pirata)",
"en-x-updown": "Inglês (Cabeça para baixo)",
"en-x-uwu": "Inglês (UwU)",
"eo": "Esperanto",
"es": "Espanhol",
"et": "Estoniano",
"fi": "Finlandês",
"fr": "Francês",
"fr-BE": "Francês (Bélgica)",
"fr-CA": "Francês (Canadá)",
"he": "Hebreu",
"hi": "Hindi",
"hr": "Croata",
"hu": "Húngaro",
"id": "Indonésio",
"it": "Italiano",
"ja": "Japonês",
"kk": "Cazaque",
"ko": "Coreano",
"ky": "Quirguiz",
"lt": "Lituano",
"lv": "Letão",
"ms": "Malaio",
"nb": "Norueguês Bokmål",
"nl": "Holandês",
"nn": "Norueguês Nynorsk",
"pes": "Persa",
"pl": "Polaco",
"pt": "Português",
"pt-BR": "Português (Brasil)",
"ro": "Romeno",
"ru": "Russo",
"ru-x-bandit": "Russo (Bandido)",
"sk": "Eslovaco",
"sv": "Sueco",
"th": "Tailandês",
"tok": "Toki Pona",
"tr": "Turco",
"tt": "Tártaro",
"uk": "Ucraniano",
"vi": "Vietnamita",
"zh-Hans": "Chinês (Simplificado)",
"zh-Hant": "Chinês (Tradicional)"
}
@@ -1,58 +0,0 @@
{
"ar": "Arabă",
"be": "Belarusă",
"bg": "Bulgară",
"bn": "Bengali",
"ca": "Catalană",
"cs": "Cehă",
"da": "Daneză",
"de": "Germană",
"de-CH": "Germană (Elveția)",
"el": "Greacă",
"en-GB": "Engleză (Marea Britanie)",
"en-US": "Engleză (Statele Unite)",
"en-x-lolcat": "LOLCAT",
"en-x-pirate": "Engleză (Pirat)",
"en-x-updown": "Engleză (Inversată)",
"en-x-uwu": "Engleză (UwU)",
"eo": "Esperanto",
"es": "Spaniolă",
"et": "Estonă",
"fi": "Finlandeză",
"fr": "Franceză",
"fr-BE": "Franceză (Belgia)",
"fr-CA": "Franceză (Canada)",
"he": "Ebraică",
"hi": "Hindi",
"hr": "Croată",
"hu": "Maghiară",
"id": "Indoneziană",
"it": "Italiană",
"ja": "Japoneză",
"kk": "Kazahă",
"ko": "Coreeană",
"ky": "Kirghiză",
"lt": "Lituaniană",
"lv": "Letonă",
"ms": "Malaieză",
"nb": "Norvegiană Bokmål",
"nl": "Olandeză",
"nn": "Norvegiană Nynorsk",
"pes": "Persană",
"pl": "Poloneză",
"pt": "Portugheză",
"pt-BR": "Portugheză (Brazilia)",
"ro": "Română",
"ru": "Rusă",
"ru-x-bandit": "Rusă (Bandit)",
"sk": "Slovacă",
"sv": "Suedeză",
"th": "Thailandeză",
"tok": "Toki Pona",
"tr": "Turcă",
"tt": "Tătară",
"uk": "Ucraineană",
"vi": "Vietnameză",
"zh-Hans": "Chineză (Simplificată)",
"zh-Hant": "Chineză (Tradițională)"
}
@@ -1,58 +0,0 @@
{
"ar": "Арабский",
"be": "Белорусский",
"bg": "Болгарский",
"bn": "Бенгальский",
"ca": "Каталанский",
"cs": "Чешский",
"da": "Датский",
"de": "Немецкий",
"de-CH": "Немецкий (Швейцария)",
"el": "Греческий",
"en-GB": "Английский (Великобритания)",
"en-US": "Английский (США)",
"en-x-lolcat": "LOLCAT",
"en-x-pirate": "Английский (пиратский)",
"en-x-updown": "Английский (перевёрнутый)",
"en-x-uwu": "Английский (UwU)",
"eo": "Эсперанто",
"es": "Испанский",
"et": "Эстонский",
"fi": "Финский",
"fr": "Французский",
"fr-BE": "Французский (Бельгия)",
"fr-CA": "Французский (Канада)",
"he": "Иврит",
"hi": "Хинди",
"hr": "Хорватский",
"hu": "Венгерский",
"id": "Индонезийский",
"it": "Итальянский",
"ja": "Японский",
"kk": "Казахский",
"ko": "Корейский",
"ky": "Киргизский",
"lt": "Литовский",
"lv": "Латышский",
"ms": "Малайский",
"nb": "Норвежский букмол",
"nl": "Нидерландский",
"nn": "Норвежский нюнорск",
"pes": "Персидский",
"pl": "Польский",
"pt": "Португальский",
"pt-BR": "Португальский (Бразилия)",
"ro": "Румынский",
"ru": "Русский",
"ru-x-bandit": "Русский (бандитский)",
"sk": "Словацкий",
"sv": "Шведский",
"th": "Тайский",
"tok": "Токипона",
"tr": "Турецкий",
"tt": "Татарский",
"uk": "Украинский",
"vi": "Вьетнамский",
"zh-Hans": "Китайский (упрощённый)",
"zh-Hant": "Китайский (традиционный)"
}
@@ -1 +0,0 @@
{}
@@ -1,58 +0,0 @@
{
"ar": "Arabiska",
"be": "Vitryska",
"bg": "Bulgariska",
"bn": "Bengali",
"ca": "Katalanska",
"cs": "Tjeckiska",
"da": "Danska",
"de": "Tyska",
"de-CH": "Tyska (Schweiz)",
"el": "Grekiska",
"en-GB": "Engelska (Storbritannien)",
"en-US": "Engelska (USA)",
"en-x-lolcat": "LOLCAT",
"en-x-pirate": "Engelska (Piratspråk)",
"en-x-updown": "Engelska (Upp och Ner)",
"en-x-uwu": "Engelska (UwU)",
"eo": "Esperanto",
"es": "Spanska",
"et": "Estniska",
"fi": "Finska",
"fr": "Franska",
"fr-BE": "Franska (Belgien)",
"fr-CA": "Franska (Kanada)",
"he": "Hebreiska",
"hi": "Hindi",
"hr": "Kroatiska",
"hu": "Ungerska",
"id": "Indonesiska",
"it": "Italienska",
"ja": "Japanska",
"kk": "Kazakiska",
"ko": "Koreanska",
"ky": "Kirgiziska",
"lt": "Litauiska",
"lv": "Lettiska",
"ms": "Malaysiska",
"nb": "Norska Bokmål",
"nl": "Nederländska",
"nn": "Norska Nynorsk",
"pes": "Persiska",
"pl": "Polska",
"pt": "Portugisiska",
"pt-BR": "Portugisiska (Brasilien)",
"ro": "Rumänska",
"ru": "Ryska",
"ru-x-bandit": "Ryska (Bandit)",
"sk": "Slovakiska",
"sv": "Svenska",
"th": "Thailändska",
"tok": "Toki Pona",
"tr": "Turkiska",
"tt": "Tatariska",
"uk": "Ukrainska",
"vi": "Vietnamesiska",
"zh-Hans": "Kinesiska (Förenklad)",
"zh-Hant": "Kinesiska (Traditionell)"
}
@@ -1,58 +0,0 @@
{
"ar": "ภาษาอาหรับ",
"be": "ภาษาเบลารุส",
"bg": "ภาษาบัลแกเรีย",
"bn": "ภาษาเบงกอล",
"ca": "ภาษาคาตาลัน",
"cs": "ภาษาเช็ก",
"da": "ภาษาเดนมาร์ก",
"de": "ภาษาเยอรมัน",
"de-CH": "ภาษาเยอรมัน (สวิตเซอร์แลนด์)",
"el": "ภาษากรีก",
"en-GB": "ภาษาอังกฤษ (สหราชอาณาจักร)",
"en-US": "ภาษาอังกฤษ (สหรัฐอเมริกา)",
"en-x-lolcat": "แมวเกรียน",
"en-x-pirate": "ภาษาอังกฤษ (โจรสลัด)",
"en-x-updown": "ภาษาอังกฤษ (กลับหัวกลับหาง)",
"en-x-uwu": "ภาษาอังกฤษ (ตะมุตะมิ UwU)",
"eo": "ภาษาเอสเปรันโต",
"es": "ภาษาสเปน",
"et": "ภาษาเอสโตเนีย",
"fi": "ภาษาฟินแลนด์",
"fr": "ภาษาฝรั่งเศส",
"fr-BE": "ภาษาฝรั่งเศส (เบลเยียม)",
"fr-CA": "ภาษาฝรั่งเศส (แคนาดา)",
"he": "ภาษาฮีบรู",
"hi": "ภาษาฮินดี",
"hr": "ภาษาโครเอเชีย",
"hu": "ภาษาฮังการี",
"id": "ภาษาอินโดนิเซีย",
"it": "ภาษาอิตาลี",
"ja": "ภาษาญี่ปุ่น",
"kk": "ภาษาคาซัค",
"ko": "ภาษาเกาหลี",
"ky": "ภาษาคีร์กีซ",
"lt": "ภาษาลิทัวเนีย",
"lv": "ภาษาลัตเวีย",
"ms": "ภาษามาเลเซีย",
"nb": "ภาษานอร์เวย์บอกมอล",
"nl": "ภาษาดัตช์",
"nn": "ภาษานอร์เวย์ไนนอรส์ก",
"pes": "ภาษาเปอร์เซีย",
"pl": "ภาษาโปแลนด์",
"pt": "ภาษาโปรตุเกส",
"pt-BR": "ภาษาโปรตุเกส (บราซิล)",
"ro": "ภาษาโรมาเนีย",
"ru": "ภาษารัสเซีย",
"ru-x-bandit": "ภาษารัสเซีย (แบนดิต)",
"sk": "ภาษาสโลวัก",
"sv": "ภาษาสวีเดน",
"th": "ภาษาไทย",
"tok": "ภาษาโตกีโปนา",
"tr": "ภาษาตุรกี",
"tt": "ภาษาตาตาร์",
"uk": "ภาษายูเครน",
"vi": "ภาษาเวียดนาม",
"zh-Hans": "ภาษาจีน (ตัวย่อ)",
"zh-Hant": "ภาษาจีน (ดั้งเดิม)"
}
@@ -1,58 +0,0 @@
{
"ar": "Arapça",
"be": "Belarusça",
"bg": "Bulgarca",
"bn": "Bengalce",
"ca": "Katalanca",
"cs": "Çekçe",
"da": "Danca",
"de": "Almanca",
"de-CH": "Almanca (İsviçre)",
"el": "Yunanca",
"en-GB": "İngilizce (Birleşik Krallık)",
"en-US": "İngilizce (ABD)",
"en-x-lolcat": "LOLCAT",
"en-x-pirate": "İngilizce (Korsan ağzı)",
"en-x-updown": "İngilizce (Baş aşağı)",
"en-x-uwu": "İngilizce (UwU)",
"eo": "Esperanto",
"es": "İspanyolca",
"et": "Estonca",
"fi": "Fince",
"fr": "Fransızca",
"fr-BE": "Fransızca (Belçika)",
"fr-CA": "Fransızca (Kanada)",
"he": "İbranice",
"hi": "Hintçe",
"hr": "Hırvatça",
"hu": "Macarca",
"id": "Endonezce",
"it": "İtalyanca",
"ja": "Japonca",
"kk": "Kazakça",
"ko": "Korece",
"ky": "Kırgızca",
"lt": "Litvanyaca",
"lv": "Letonca",
"ms": "Malayca",
"nb": "Norveççe (Bokmål)",
"nl": "Felemenkçe",
"nn": "Norveççe (Nynorsk)",
"pes": "Farsça",
"pl": "Lehçe",
"pt": "Portekizce",
"pt-BR": "Portekizce (Brezilya)",
"ro": "Rumence",
"ru": "Rusça",
"ru-x-bandit": "Rusça (Haydut ağzı)",
"sk": "Slovakça",
"sv": "İsveççe",
"th": "Tayca",
"tok": "Toki Pona",
"tr": "Türkçe",
"tt": "Tatarca",
"uk": "Ukraynaca",
"vi": "Vietnamca",
"zh-Hans": "Çince (Basitleştirilmiş)",
"zh-Hant": "Çince (Geleneksel)"
}
@@ -1,58 +0,0 @@
{
"ar": "Арабська",
"be": "Білоруська",
"bg": "Болгарська",
"bn": "Бенгальська",
"ca": "Каталонська",
"cs": "Чеська",
"da": "Данська",
"de": "Німецька",
"de-CH": "Німецька (Швейцарія)",
"el": "Грецька",
"en-GB": "Англійська (Велика Британія)",
"en-US": "Англійська (США)",
"en-x-lolcat": "LOLCAT",
"en-x-pirate": "Англійська (піратська)",
"en-x-updown": "Англійська (догори дриґом)",
"en-x-uwu": "Англійська (UwU)",
"eo": "Есперанто",
"es": "Іспанська",
"et": "Естонська",
"fi": "Фінська",
"fr": "Французька",
"fr-BE": "Французька (Бельгія)",
"fr-CA": "Французька (Канада)",
"he": "Іврит",
"hi": "Гінді",
"hr": "Хорватська",
"hu": "Угорська",
"id": "Індонезійська",
"it": "Італійська",
"ja": "Японська",
"kk": "Казахська",
"ko": "Корейська",
"ky": "Киргизька",
"lt": "Литовська",
"lv": "Латвійська",
"ms": "Малайська",
"nb": "Норвезька (букмол)",
"nl": "Нідерландська",
"nn": "Норвезька (нюношк)",
"pes": "Перська",
"pl": "Польська",
"pt": "Португальська",
"pt-BR": "Португальська (Бразилія)",
"ro": "Румунська",
"ru": "російська",
"ru-x-bandit": "російська (бандитська)",
"sk": "Словацька",
"sv": "Шведська",
"th": "Тайська",
"tok": "Токі-пона",
"tr": "Турецька",
"tt": "Татарська",
"uk": "Українська",
"vi": "Вʼєтнамська",
"zh-Hans": "Китайська (спрощена)",
"zh-Hant": "Китайська (традиційна)"
}
@@ -1,58 +0,0 @@
{
"ar": "Tiếng Ả Rập",
"be": "Tiếng Belarus",
"bg": "Tiếng Bulgaria",
"bn": "Tiếng Bangla",
"ca": "Tiếng Catalan",
"cs": "Tiếng Cộng hoà Séc",
"da": "Tiếng Đan Mạch",
"de": "Tiếng Đức",
"de-CH": "Tiếng Đức (Thụy Sĩ)",
"el": "Tiếng Hy Lạp",
"en-GB": "Tiếng Anh (Anh)",
"en-US": "Tiếng Anh (Hoa Kỳ)",
"en-x-lolcat": "LOLCAT",
"en-x-pirate": "Tiếng Anh (Pirate)",
"en-x-updown": "Tiếng Anh (lộn ngược)",
"en-x-uwu": "Tiếng Anh (UwU)",
"eo": "Quốc tế ngữ",
"es": "Tiếng Tây Ban Nha",
"et": "Tiếng Et-tô-ni-a",
"fi": "Tiếng Phần Lan",
"fr": "Tiếng Pháp",
"fr-BE": "Tiếng Pháp (Bỉ)",
"fr-CA": "Tiếng Pháp (Canada)",
"he": "Tiếng Do Thái",
"hi": "Tiếng Hin-đi",
"hr": "Tiếng Croatia",
"hu": "Tiếng Hungary",
"id": "Tiếng Indonesia",
"it": "Tiếng Ý",
"ja": "Tiếng Nhật",
"kk": "Tiếng Kazakhstan",
"ko": "Tiếng Hàn",
"ky": "Tiếng Kyrgyz",
"lt": "Tiếng Lithuania",
"lv": "Tiếng Latvia",
"ms": "Tiếng Malaysia",
"nb": "Tiếng Na Uy Bokh-man",
"nl": "Tiếng Hà Lan",
"nn": "Tiếng Na Uy Nynorsk",
"pes": "Tiếng Ba Tư",
"pl": "Tiếng Ba Lan",
"pt": "Tiếng Bồ Đào Nha",
"pt-BR": "Tiếng Bồ Đào Nha (Brazil)",
"ro": "Tiếng Romani",
"ru": "Tiếng Nga",
"ru-x-bandit": "Tiếng Nga (Bandit)",
"sk": "Tiếng Slovakia",
"sv": "Tiếng Thụy Điển",
"th": "Tiếng Thái",
"tok": "Tiếng Toki Pona",
"tr": "Tiếng Thổ Nhĩ Kỳ",
"tt": "Tiếng Tatar",
"uk": "Tiếng Ukraina",
"vi": "Tiếng Việt",
"zh-Hans": "Tiếng Trung (Giản thể)",
"zh-Hant": "Tiếng Trung (Phồn thể)"
}
@@ -1,58 +0,0 @@
{
"ar": "阿拉伯语",
"be": "白俄罗斯语",
"bg": "保加利亚语",
"bn": "孟加拉语",
"ca": "加泰罗尼亚语",
"cs": "捷克语",
"da": "丹麦语",
"de": "德语",
"de-CH": "德语(瑞士)",
"el": "希腊语",
"en-GB": "英语(英国)",
"en-US": "英语(美国)",
"en-x-lolcat": "萌猫语",
"en-x-pirate": "英语(海盗语)",
"en-x-updown": "英语(颠倒)",
"en-x-uwu": "英语(UwU",
"eo": "世界语",
"es": "西班牙语",
"et": "爱沙尼亚语",
"fi": "芬兰语",
"fr": "法语",
"fr-BE": "法语(比利时)",
"fr-CA": "法语(加拿大)",
"he": "希伯来语",
"hi": "印地语",
"hr": "克罗地亚语",
"hu": "匈牙利语",
"id": "印度尼西亚语",
"it": "意大利语",
"ja": "日语",
"kk": "哈萨克语",
"ko": "韩语",
"ky": "柯尔克孜语",
"lt": "立陶宛语",
"lv": "拉脱维亚语",
"ms": "马来语",
"nb": "书面挪威语",
"nl": "荷兰语",
"nn": "新挪威语",
"pes": "波斯语",
"pl": "波兰语",
"pt": "葡萄牙语",
"pt-BR": "葡萄牙语(巴西)",
"ro": "罗马尼亚语",
"ru": "俄语",
"ru-x-bandit": "俄语(黑帮俚语)",
"sk": "斯洛伐克语",
"sv": "瑞典语",
"th": "泰语",
"tok": "道本语",
"tr": "土耳其语",
"tt": "鞑靼语",
"uk": "乌克兰语",
"vi": "越南语",
"zh-Hans": "中文(简体)",
"zh-Hant": "中文(繁体)"
}
@@ -1,58 +0,0 @@
{
"ar": "阿拉伯文",
"be": "白俄羅斯文",
"bg": "保加利亞文",
"bn": "孟加拉文",
"ca": "加泰隆尼亞文",
"cs": "捷克文",
"da": "丹麥文",
"de": "德文",
"de-CH": "德文(瑞士)",
"el": "希臘文",
"en-GB": "英文(英國)",
"en-US": "英文(美國)",
"en-x-lolcat": "LOLCAT",
"en-x-pirate": "英文(海盜)",
"en-x-updown": "英文(顛倒)",
"en-x-uwu": "英文 (UwU)",
"eo": "世界語",
"es": "西班牙文",
"et": "愛沙尼亞文",
"fi": "芬蘭文",
"fr": "法文",
"fr-BE": "法文(比利時)",
"fr-CA": "法文(加拿大)",
"he": "希伯來文",
"hi": "印地文",
"hr": "克羅埃西亞文",
"hu": "匈牙利文",
"id": "印尼文",
"it": "義大利文",
"ja": "日文",
"kk": "哈薩克文",
"ko": "韓文",
"ky": "吉爾吉斯文",
"lt": "立陶宛文",
"lv": "拉脫維亞文",
"ms": "馬來文",
"nb": "書面挪威文",
"nl": "荷蘭文",
"nn": "新挪威文",
"pes": "波斯文",
"pl": "波蘭文",
"pt": "葡萄牙文",
"pt-BR": "葡萄牙文(巴西)",
"ro": "羅馬尼亞文",
"ru": "俄文",
"ru-x-bandit": "俄文(流氓腔)",
"sk": "斯洛伐克文",
"sv": "瑞典文",
"th": "泰文",
"tok": "道本文",
"tr": "土耳其文",
"tt": "韃靼文",
"uk": "烏克蘭文",
"vi": "越南文",
"zh-Hans": "簡體中文",
"zh-Hant": "正體中文"
}
+55 -27
View File
@@ -116,7 +116,7 @@
</div>
</template>
<template #default>
<div class="mx-auto flex max-w-[40rem] flex-col gap-4 md:w-[30rem]">
<div class="mx-auto flex max-w-[44rem] flex-col gap-4 md:w-[30rem]">
<div
v-if="
project.project_type !== 'plugin' ||
@@ -443,7 +443,27 @@
"
>
<template #actions>
<ButtonStyled v-if="auth.user && currentMember" size="large" color="brand">
<ButtonStyled
v-if="auth.user && currentMember"
size="large"
color="brand"
class="lg:!hidden"
circular
>
<nuxt-link
v-tooltip="'Edit project'"
:to="`/${project.project_type}/${project.slug ? project.slug : project.id}/settings`"
class="!font-bold"
>
<SettingsIcon aria-hidden="true" />
</nuxt-link>
</ButtonStyled>
<ButtonStyled
v-if="auth.user && currentMember"
size="large"
color="brand"
class="max-lg:!hidden"
>
<nuxt-link
:to="`/${project.project_type}/${project.slug ? project.slug : project.id}/settings`"
class="!font-bold"
@@ -501,7 +521,11 @@
v-if="!isServerProject"
size="large"
circular
:color="route.name === 'type-id-version-version' ? `standard` : `brand`"
:color="
route.name === 'type-id-version-version' || (auth.user && currentMember)
? `standard`
: `brand`
"
>
<button
:aria-label="formatMessage(commonMessages.downloadButton)"
@@ -515,7 +539,11 @@
v-else
size="large"
circular
:color="route.name === 'type-id-version-version' ? `standard` : `brand`"
:color="
route.name === 'type-id-version-version' || (auth.user && currentMember)
? `standard`
: `brand`
"
>
<button aria-label="Play" class="flex sm:hidden" @click="handlePlayServerProject">
<PlayIcon aria-hidden="true" />
@@ -1070,6 +1098,7 @@ import NavTabs from '~/components/ui/NavTabs.vue'
import ProjectMemberHeader from '~/components/ui/ProjectMemberHeader.vue'
import { saveFeatureFlags } from '~/composables/featureFlags.ts'
import { STALE_TIME, STALE_TIME_LONG } from '~/composables/queries/project'
import { versionQueryOptions } from '~/composables/queries/version'
import { userCollectProject, userFollowProject } from '~/composables/user.js'
import { useModerationStore } from '~/store/moderation.ts'
import { reportProject } from '~/utils/report-helpers.ts'
@@ -1188,7 +1217,7 @@ const serverProject = computed(() => ({
numPlayers: projectV3.value?.minecraft_java_server?.ping?.data?.players_online,
icon: project.value.icon_url,
statusOnline: !!projectV3.value?.minecraft_java_server?.ping?.data,
region: projectV3.value?.minecraft_server?.country,
region: projectV3.value?.minecraft_server?.region,
}))
function handlePlayServerProject() {
@@ -1630,52 +1659,41 @@ const serverModpackVersionId = computed(() => {
})
const { data: serverModpackVersion, isPending: serverModpackVersionPending } = useQuery({
queryKey: computed(() => ['sidebar-modpack-version', serverModpackVersionId.value]),
queryKey: computed(() => ['version', 'v3', serverModpackVersionId.value]),
queryFn: () => client.labrinth.versions_v3.getVersion(serverModpackVersionId.value),
staleTime: STALE_TIME,
enabled: computed(() => !!serverModpackVersionId.value),
})
const serverModpackProjectId = computed(() => serverModpackVersion.value?.project_id ?? null)
const { data: serverModpackProject, isPending: serverModpackProjectPending } = useQuery({
queryKey: computed(() => ['sidebar-modpack-project', serverModpackProjectId.value]),
queryFn: () => client.labrinth.projects_v3.get(serverModpackProjectId.value),
staleTime: STALE_TIME,
enabled: computed(() => !!serverModpackProjectId.value),
})
const serverDataLoaded = computed(() => {
if (!projectV3.value) return false
if (serverModpackVersionId.value && serverModpackVersionPending.value) return false
if (serverModpackProjectId.value && serverModpackProjectPending.value) return false
return true
})
const serverRequiredContent = computed(() => {
if (!serverModpackProject.value) return null
const content = projectV3.value?.minecraft_java_server?.content
if (!content || content.kind !== 'modpack') return null
const primaryFile =
serverModpackVersion.value?.files?.find((f) => f.primary) ??
serverModpackVersion.value?.files?.[0]
return {
name: serverModpackProject.value.name,
name: content.project_name ?? '',
versionNumber: serverModpackVersion.value?.version_number ?? '',
icon: serverModpackProject.value.icon_url,
icon: content.project_icon,
onclickName:
serverModpackProject.value.id !== projectId.value
? () => navigateTo(`/modpack/${serverModpackProject.value.slug}`)
content.project_id && content.project_id !== projectId.value
? () => navigateTo(`/project/${content.project_id}`)
: undefined,
onclickVersion:
serverModpackProject.value.id !== projectId.value
content.project_id && content.project_id !== projectId.value
? () =>
navigateTo(
`/modpack/${serverModpackProject.value.slug}/version/${serverModpackVersion.value?.id}`,
)
navigateTo(`/project/${content.project_id}/version/${serverModpackVersion.value?.id}`)
: undefined,
onclickDownload: primaryFile?.url
? () => navigateTo(primaryFile.url, { external: true })
: undefined,
showCustomModpackTooltip: serverModpackProject.value.id === projectId.value,
showCustomModpackTooltip: content.project_id === projectId.value,
}
})
@@ -1710,6 +1728,11 @@ const serverModpackLoaders = computed(() => {
return serverModpackVersion.value.mrpack_loaders ?? []
})
watch(serverModpackVersionId, (versionId) => {
if (!versionId) return
queryClient.prefetchQuery(versionQueryOptions.v3(versionId, client))
})
// Members
const { data: allMembersRaw, error: _membersError } = useQuery({
queryKey: computed(() => ['project', projectId.value, 'members']),
@@ -2523,6 +2546,11 @@ const navLinks = computed(() => {
const routeType = route.params.type || project.value.project_type
const projectUrl = `/${routeType}/${project.value.slug ? project.value.slug : project.value.id}`
const galleryCount =
routeType === 'server'
? project.value.gallery.filter((item) => item.name === '__mc_server_banner__').length
: project.value.gallery.length
return [
{
label: formatMessage(messages.descriptionTab),
@@ -2531,7 +2559,7 @@ const navLinks = computed(() => {
{
label: formatMessage(messages.galleryTab),
href: `${projectUrl}/gallery`,
shown: project.value.gallery.length > 0 || !!currentMember.value,
shown: galleryCount > 0 || !!currentMember.value,
},
{
label: formatMessage(messages.changelogTab),
@@ -84,7 +84,7 @@
<div class="input-stack">
<FileInput
id="project-icon"
:max-size="262144"
:max-size="262144000"
:show-icon="true"
accept="image/png,image/jpeg,image/gif,image/webp"
class="choose-image iconified-button"
@@ -146,7 +146,9 @@
(e) => {
const input = e.target
if (input.files?.length) {
if (fileIsValid(input.files[0], { maxSize: 524288, alertOnInvalid: true }))
if (
fileIsValid(input.files[0], { maxSize: 524288000, alertOnInvalid: true })
)
showBannerPreview(Array.from(input.files))
}
}
@@ -8,6 +8,16 @@
<span class="label__title">Website</span>
<span class="label__description">Your server's official website.</span>
</label>
<TriangleAlertIcon
v-if="isServerSiteLinkShortener"
v-tooltip="`Use of link shorteners is prohibited.`"
class="size-6 animate-pulse text-orange"
/>
<TriangleAlertIcon
v-else-if="isServerSiteDiscordUrl"
v-tooltip="`Discord invites are not appropriate for this link type.`"
class="size-6 animate-pulse text-orange"
/>
<input
id="server-website"
v-model="siteUrl"
@@ -22,6 +32,16 @@
<span class="label__title">Store</span>
<span class="label__description">A link to your server's store or shop.</span>
</label>
<TriangleAlertIcon
v-if="isServerStoreLinkShortener"
v-tooltip="`Use of link shorteners is prohibited.`"
class="size-6 animate-pulse text-orange"
/>
<TriangleAlertIcon
v-else-if="isServerStoreDiscordUrl"
v-tooltip="`Discord invites are not appropriate for this link type.`"
class="size-6 animate-pulse text-orange"
/>
<input
id="server-store"
v-model="storeUrl"
@@ -41,6 +61,16 @@
>A page containing information, documentation, and help for the server.</span
>
</label>
<TriangleAlertIcon
v-if="isServerWikiLinkShortener"
v-tooltip="`Use of link shorteners is prohibited.`"
class="size-6 animate-pulse text-orange"
/>
<TriangleAlertIcon
v-else-if="isServerWikiDiscordUrl"
v-tooltip="`Discord invites are not appropriate for this link type.`"
class="size-6 animate-pulse text-orange"
/>
<input
id="server-wiki"
v-model="serverWikiUrl"
@@ -55,6 +85,16 @@
<span class="label__title">Discord</span>
<span class="label__description">An invitation link to your Discord server.</span>
</label>
<TriangleAlertIcon
v-if="isServerDiscordLinkShortener"
v-tooltip="`Use of link shorteners is prohibited.`"
class="size-6 animate-pulse text-orange"
/>
<TriangleAlertIcon
v-else-if="!isServerDiscordUrlCommon"
v-tooltip="`You're using a link which isn't common for this link type.`"
class="size-6 animate-pulse text-orange"
/>
<input
id="server-discord"
v-model="serverDiscordUrl"
@@ -337,6 +377,32 @@ const isDiscordLinkShortener = computed(() => {
return isLinkShortener(discordUrl.value)
})
const isServerSiteDiscordUrl = computed(() => {
return isDiscordUrl(siteUrl.value)
})
const isServerStoreDiscordUrl = computed(() => {
return isDiscordUrl(storeUrl.value)
})
const isServerWikiDiscordUrl = computed(() => {
return isDiscordUrl(serverWikiUrl.value)
})
const isServerSiteLinkShortener = computed(() => {
return isLinkShortener(siteUrl.value)
})
const isServerStoreLinkShortener = computed(() => {
return isLinkShortener(storeUrl.value)
})
const isServerWikiLinkShortener = computed(() => {
return isLinkShortener(serverWikiUrl.value)
})
const isServerDiscordLinkShortener = computed(() => {
return isLinkShortener(serverDiscordUrl.value)
})
const isServerDiscordUrlCommon = computed(() => {
if (!serverDiscordUrl.value || serverDiscordUrl.value.trim().length === 0) return true
return isCommonUrl(serverDiscordUrl.value, commonLinkDomains.discord)
})
const rawDonationLinks = JSON.parse(JSON.stringify(project.value.donation_urls))
rawDonationLinks.push({
id: null,
@@ -4,17 +4,17 @@
<div class="flex flex-col gap-6">
<div class="text-2xl font-semibold text-contrast">Server details</div>
<!-- Country -->
<!-- Region -->
<div class="max-w-[600px]">
<label for="server-country">
<span class="label__title">Country</span>
<label for="server-region">
<span class="label__title">Region</span>
</label>
<Combobox
id="server-country"
v-model="country"
:options="countryOptions"
id="server-region"
v-model="region"
:options="regionOptions"
searchable
placeholder="Select country"
placeholder="Select region"
:disabled="!hasPermission"
/>
</div>
@@ -48,7 +48,7 @@
</label>
</div>
<div
class="mt-2 flex items-center gap-2"
class="mt-2 flex items-center gap-2 text-sm"
@focusout="
() => {
if (!lastPingAddressChanged && javaPingResult) return
@@ -64,16 +64,6 @@
wrapper-class="flex-grow"
autocomplete="off"
/>
<StyledInput
v-model="javaPort"
type="number"
:min="1"
:max="65535"
:disabled="!hasPermission"
wrapper-class="w-24"
input-class="text-center"
autocomplete="off"
/>
</div>
<div
v-if="javaAddress"
@@ -121,6 +111,17 @@
>.
</div>
</div>
<div v-else class="mt-2 text-sm">
If you have [SRV records]
<InfoIcon
v-tooltip="{
content:
'The address you enter here may have DNS SRV records _minecraft._tcp.{your domain} which point to your Minecraft server address and port.',
popperClass: 'max-w-xs',
}"
/>, you do not need to add a port. Otherwise if you have a port which isn't 25565, you
can include it as :12345
</div>
</div>
<!-- Bedrock Address -->
@@ -140,16 +141,6 @@
wrapper-class="flex-grow"
autocomplete="off"
/>
<StyledInput
v-model="bedrockPort"
type="number"
:min="1"
:max="65535"
:disabled="!hasPermission"
wrapper-class="w-24"
input-class="text-center"
autocomplete="off"
/>
</div>
</div>
@@ -168,7 +159,7 @@
</template>
<script setup>
import { RefreshCwIcon, SpinnerIcon } from '@modrinth/assets'
import { InfoIcon, RefreshCwIcon, SpinnerIcon } from '@modrinth/assets'
import {
ButtonStyled,
Combobox,
@@ -189,27 +180,23 @@ const { addNotification } = injectNotificationManager()
const { projectV3, currentMember, patchProjectV3 } = injectProjectPageContext()
const javaAddress = ref('')
const javaPort = ref(25565)
const bedrockAddress = ref('')
const bedrockPort = ref(19132)
const country = ref('')
const region = ref('')
const languages = ref([])
const javaPingLoading = ref(false)
const javaPingResult = ref(null)
const lastPingedAddress = ref({ address: '', port: null })
const lastPingedAddress = ref('')
const lastPingAddressChanged = computed(() => {
return (
javaAddress.value.trim() !== lastPingedAddress.value.address ||
javaPort.value !== lastPingedAddress.value.port
)
return javaAddress.value.trim() !== lastPingedAddress.value
})
let pingDebounceTimer = null
watch([javaAddress, javaPort], () => {
watch(javaAddress, () => {
clearTimeout(pingDebounceTimer)
pingDebounceTimer = setTimeout(() => {
pingJavaServer()
@@ -231,13 +218,11 @@ async function pingJavaServer() {
javaPingLoading.value = true
javaPingResult.value = null
const port = javaPort.value || 25565
try {
await Promise.race([
client.labrinth.server_ping_internal.pingMinecraftJava({
address,
port,
timeout_ms: PING_TIMEOUT_MS,
}),
new Promise((_, reject) =>
setTimeout(() => reject(new Error('Ping timed out')), PING_TIMEOUT_MS),
@@ -248,17 +233,16 @@ async function pingJavaServer() {
javaPingResult.value = { online: false, latency: null }
} finally {
javaPingLoading.value = false
lastPingedAddress.value = { address, port }
lastPingedAddress.value = address
}
}
function initFromProjectV3(v3) {
if (!v3) return
javaAddress.value = v3.minecraft_java_server?.address ?? ''
javaPort.value = v3.minecraft_java_server?.port ?? 25565
bedrockAddress.value = v3.minecraft_bedrock_server?.address ?? ''
bedrockPort.value = v3.minecraft_bedrock_server?.port ?? 19132
country.value = v3.minecraft_server?.country ?? ''
region.value = v3.minecraft_server?.region ?? ''
languages.value = v3.minecraft_server?.languages ?? []
pingJavaServer()
@@ -278,67 +262,15 @@ if (projectV3.value) {
)
}
const countryOptions = [
{ value: 'US', label: 'United States' },
{ value: 'CA', label: 'Canada' },
{ value: 'GB', label: 'United Kingdom' },
{ value: 'DE', label: 'Germany' },
{ value: 'FR', label: 'France' },
{ value: 'NL', label: 'Netherlands' },
{ value: 'FI', label: 'Finland' },
{ value: 'SE', label: 'Sweden' },
{ value: 'NO', label: 'Norway' },
{ value: 'DK', label: 'Denmark' },
{ value: 'PL', label: 'Poland' },
{ value: 'CZ', label: 'Czech Republic' },
{ value: 'RO', label: 'Romania' },
{ value: 'CH', label: 'Switzerland' },
{ value: 'AT', label: 'Austria' },
{ value: 'BE', label: 'Belgium' },
{ value: 'IE', label: 'Ireland' },
{ value: 'ES', label: 'Spain' },
{ value: 'IT', label: 'Italy' },
{ value: 'PT', label: 'Portugal' },
{ value: 'RU', label: 'Russia' },
{ value: 'UA', label: 'Ukraine' },
{ value: 'LT', label: 'Lithuania' },
{ value: 'LV', label: 'Latvia' },
{ value: 'EE', label: 'Estonia' },
{ value: 'BG', label: 'Bulgaria' },
{ value: 'HR', label: 'Croatia' },
{ value: 'HU', label: 'Hungary' },
{ value: 'SK', label: 'Slovakia' },
{ value: 'RS', label: 'Serbia' },
{ value: 'GR', label: 'Greece' },
{ value: 'TR', label: 'Turkey' },
{ value: 'IL', label: 'Israel' },
{ value: 'AE', label: 'United Arab Emirates' },
{ value: 'SA', label: 'Saudi Arabia' },
{ value: 'IN', label: 'India' },
{ value: 'SG', label: 'Singapore' },
{ value: 'JP', label: 'Japan' },
{ value: 'KR', label: 'South Korea' },
{ value: 'CN', label: 'China' },
{ value: 'HK', label: 'Hong Kong' },
{ value: 'TW', label: 'Taiwan' },
{ value: 'AU', label: 'Australia' },
{ value: 'NZ', label: 'New Zealand' },
{ value: 'BR', label: 'Brazil' },
{ value: 'AR', label: 'Argentina' },
{ value: 'CL', label: 'Chile' },
{ value: 'CO', label: 'Colombia' },
{ value: 'MX', label: 'Mexico' },
{ value: 'ZA', label: 'South Africa' },
{ value: 'NG', label: 'Nigeria' },
{ value: 'KE', label: 'Kenya' },
{ value: 'EG', label: 'Egypt' },
{ value: 'MY', label: 'Malaysia' },
{ value: 'TH', label: 'Thailand' },
{ value: 'VN', label: 'Vietnam' },
{ value: 'PH', label: 'Philippines' },
{ value: 'ID', label: 'Indonesia' },
{ value: 'PK', label: 'Pakistan' },
{ value: 'BD', label: 'Bangladesh' },
const regionOptions = [
{ value: 'us_east', label: 'US East' },
{ value: 'us_west', label: 'US West' },
{ value: 'europe', label: 'Europe' },
{ value: 'asia', label: 'Asia' },
{ value: 'australia', label: 'Australia' },
{ value: 'south_america', label: 'South America' },
{ value: 'middle_east', label: 'Middle East' },
{ value: 'russia', label: 'Russia' },
]
const languageOptions = [
@@ -385,12 +317,10 @@ const languageOptions = [
const javaServerPatchData = computed(() => {
const addressChanged =
javaAddress.value.trim() !== (projectV3.value?.minecraft_java_server?.address ?? '') ||
javaPort.value !== (projectV3.value?.minecraft_java_server?.port ?? 25565)
javaAddress.value.trim() !== (projectV3.value?.minecraft_java_server?.address ?? '')
if (addressChanged) {
return {
address: javaAddress.value.trim(),
port: javaPort.value,
}
}
@@ -399,13 +329,9 @@ const javaServerPatchData = computed(() => {
const bedrockServerPatchData = computed(() => {
const origBedrock = projectV3.value?.minecraft_bedrock_server
if (
bedrockAddress.value !== (origBedrock?.address ?? '') ||
bedrockPort.value !== (origBedrock?.port ?? 19132)
) {
if (bedrockAddress.value !== (origBedrock?.address ?? '')) {
return {
address: bedrockAddress.value.trim(),
port: bedrockPort.value,
}
}
@@ -414,15 +340,15 @@ const bedrockServerPatchData = computed(() => {
const serverPatchData = computed(() => {
const origServer = projectV3.value?.minecraft_server
const countryChanged = country.value && country.value !== origServer?.country
const regionChanged = region.value && region.value !== origServer?.region
const languagesChanged =
JSON.stringify([...languages.value].sort()) !==
JSON.stringify([...(origServer?.languages ?? [])].sort())
if (countryChanged || languagesChanged) {
if (regionChanged || languagesChanged) {
return {
...origServer,
...(countryChanged ? { country: country.value } : {}),
...(regionChanged ? { region: region.value } : {}),
...(languagesChanged ? { languages: languages.value } : {}),
}
}
@@ -448,28 +374,25 @@ const saving = ref(false)
const original = computed(() => ({
javaAddress: projectV3.value?.minecraft_java_server?.address ?? '',
javaPort: projectV3.value?.minecraft_java_server?.port ?? 25565,
bedrockAddress: projectV3.value?.minecraft_bedrock_server?.address ?? '',
bedrockPort: projectV3.value?.minecraft_bedrock_server?.port ?? 19132,
country: projectV3.value?.minecraft_server?.country ?? '',
region: projectV3.value?.minecraft_server?.region ?? '',
languages: projectV3.value?.minecraft_server?.languages ?? [],
}))
const modified = computed(() => ({
javaAddress: javaAddress.value,
javaPort: javaPort.value,
bedrockAddress: bedrockAddress.value,
bedrockPort: bedrockPort.value,
country: country.value,
region: region.value,
languages: languages.value,
}))
function resetChanges() {
javaAddress.value = projectV3.value?.minecraft_java_server?.address ?? ''
javaPort.value = projectV3.value?.minecraft_java_server?.port ?? 25565
bedrockAddress.value = projectV3.value?.minecraft_bedrock_server?.address ?? ''
bedrockPort.value = projectV3.value?.minecraft_bedrock_server?.port ?? 19132
country.value = projectV3.value?.minecraft_server?.country ?? ''
region.value = projectV3.value?.minecraft_server?.region ?? ''
languages.value = projectV3.value?.minecraft_server?.languages ?? []
}
@@ -75,8 +75,8 @@
>
<div class="category-selector__label">
<component
:is="getCategoryIcon(category.name)"
v-if="header !== 'resolutions' && getCategoryIcon(category.name)"
:is="getTagIcon(category.name)"
v-if="header !== 'resolutions' && getTagIcon(category.name)"
aria-hidden="true"
class="icon"
/>
@@ -111,8 +111,8 @@
>
<div class="category-selector__label">
<component
:is="getCategoryIcon(category.name)"
v-if="category.header !== 'resolutions' && getCategoryIcon(category.name)"
:is="getTagIcon(category.name)"
v-if="category.header !== 'resolutions' && getTagIcon(category.name)"
aria-hidden="true"
class="icon"
/>
@@ -135,7 +135,12 @@
</template>
<script setup lang="ts">
import { getCategoryIcon, StarIcon, TriangleAlertIcon } from '@modrinth/assets'
import {
getCategoryIcon,
SERVER_CATEGORY_ICON_MAP,
StarIcon,
TriangleAlertIcon,
} from '@modrinth/assets'
import {
Checkbox,
formatCategory,
@@ -167,9 +172,20 @@ const formatCategoryName = (categoryName: string) => {
const isServerProject = computed(() => projectV3.value?.minecraft_server != null)
const matchesProjectType = (x: Category) =>
x.project_type === project.value.actualProjectType ||
(x.project_type === 'minecraft_java_server' && isServerProject.value)
const getTagIcon = (categoryName: string) => {
const iconName = isServerProject.value
? (SERVER_CATEGORY_ICON_MAP[categoryName] ?? categoryName)
: categoryName
return getCategoryIcon(iconName)
}
const matchesProjectType = (x: Category) => {
if (isServerProject.value) {
return x.project_type === 'minecraft_java_server'
} else {
return x.project_type === project.value.actualProjectType
}
}
const { saved, current, saving, reset, save } = useSavable(
() => ({
@@ -232,6 +248,14 @@ const categoryLists = computed(() => {
lists[header].push(x)
}
})
const featuresKey = 'minecraft_server_features'
if (lists[featuresKey]) {
lists[featuresKey].sort((a, b) => {
if (a.name === 'pokemon') return -1
if (b.name === 'pokemon') return 1
return 0
})
}
return lists
})
@@ -333,6 +357,7 @@ const toggleFeaturedCategory = (category: Category) => {
.category-selector__label {
display: flex;
align-items: center;
text-align: left;
.icon {
height: 1rem;
+49 -12
View File
@@ -207,12 +207,8 @@
<div class="grid-table__row grid-table__header">
<div>
<Checkbox
:model-value="selectedProjects === projects"
@update:model-value="
selectedProjects === projects
? (selectedProjects = [])
: (selectedProjects = projects)
"
:model-value="allBulkEditableProjectsSelected"
@update:model-value="toggleAllBulkEditableProjects()"
/>
</div>
<div>Icon</div>
@@ -225,13 +221,10 @@
<div v-for="project in projects" :key="`project-${project.id}`" class="grid-table__row">
<div>
<Checkbox
:disabled="(project.permissions & EDIT_DETAILS) === EDIT_DETAILS"
v-tooltip="getBulkEditDisabledTooltip(project)"
:disabled="isProjectBulkEditDisabled(project)"
:model-value="selectedProjects.includes(project)"
@update:model-value="
selectedProjects.includes(project)
? (selectedProjects = selectedProjects.filter((it) => it !== project))
: selectedProjects.push(project)
"
@update:model-value="toggleProjectSelection(project)"
/>
</div>
<div>
@@ -375,6 +368,50 @@ const editLinks = reactive({
const editLinksModal = ref(null)
const modal_creation = ref(null)
function isProjectBulkEditDisabled(project) {
return (
(project.permissions & EDIT_DETAILS) === EDIT_DETAILS ||
project.project_type === 'minecraft_java_server'
)
}
const bulkEditableProjects = computed(() =>
projects.value.filter((project) => !isProjectBulkEditDisabled(project)),
)
const allBulkEditableProjectsSelected = computed(
() =>
bulkEditableProjects.value.length > 0 &&
bulkEditableProjects.value.every((project) => selectedProjects.value.includes(project)),
)
function toggleAllBulkEditableProjects() {
selectedProjects.value = allBulkEditableProjectsSelected.value
? []
: bulkEditableProjects.value.slice()
}
function toggleProjectSelection(project) {
if (isProjectBulkEditDisabled(project)) {
return
}
if (selectedProjects.value.includes(project)) {
selectedProjects.value = selectedProjects.value.filter((it) => it !== project)
return
}
selectedProjects.value = [...selectedProjects.value, project]
}
function getBulkEditDisabledTooltip(project) {
if (project.project_type === 'minecraft_java_server') {
return 'Server projects do not support bulk editing'
}
return ''
}
function updateSort(list, sort, desc) {
let sortedArray = list
switch (sort) {
-1
View File
@@ -46,7 +46,6 @@ const selectableProjectTypes = [
label: formatMessage(commonProjectTypeCategoryMessages.server),
href: `/discover/servers`,
type: 'servers',
shown: flags.value.serverDiscovery,
},
]
</script>
@@ -44,6 +44,7 @@ import { computed, type Reactive, watch } from 'vue'
import LogoAnimated from '~/components/brand/LogoAnimated.vue'
import AdPlaceholder from '~/components/ui/AdPlaceholder.vue'
import { projectQueryOptions } from '~/composables/queries/project'
import { versionQueryOptions } from '~/composables/queries/version'
import type { ModrinthServer } from '~/composables/servers/modrinth-servers.ts'
import { useModrinthServers } from '~/composables/servers/modrinth-servers.ts'
import type { DisplayLocation, DisplayMode } from '~/plugins/cosmetics.ts'
@@ -77,6 +78,25 @@ const handleProjectMouseEnter = (result: Labrinth.Search.v2.ResultSearchProject)
prefetchTimeout.start()
}
const handleServerProjectMouseEnter = (result: Labrinth.Search.v3.ResultSearchProject) => {
const slug = result.slug || result.project_id
prefetchTimeout = useTimeoutFn(
async () => {
queryClient.prefetchQuery(projectQueryOptions.v2(slug, modrinthClient))
queryClient.prefetchQuery(projectQueryOptions.v3(slug, modrinthClient))
const content = result.minecraft_java_server?.content
if (content?.kind === 'modpack' && content.version_id) {
queryClient.prefetchQuery(versionQueryOptions.v3(content.version_id, modrinthClient))
}
},
HOVER_DURATION_TO_PREFETCH_MS,
{ immediate: false },
)
prefetchTimeout.start()
}
const handleProjectHoverEnd = () => {
if (prefetchTimeout) prefetchTimeout.stop()
}
@@ -642,6 +662,7 @@ const getServerModpackContent = (hit: Labrinth.Search.v3.ResultSearchProject) =>
'server_category_minecraft_server_meta',
'server_category_minecraft_server_community',
'server_game_version',
'server_status',
].includes(filterType.id)
"
>
@@ -800,7 +821,7 @@ const getServerModpackContent = (hit: Labrinth.Search.v3.ResultSearchProject) =>
project.minecraft_java_server?.ping?.data?.players_online ?? 0
"
:server-recent-plays="project.minecraft_java_server?.verified_plays_2w ?? 0"
:server-region-code="project.minecraft_server?.country"
:server-region="project.minecraft_server?.region"
:server-status-online="!!project.minecraft_java_server?.ping?.data"
:server-modpack-content="getServerModpackContent(project)"
:layout="
@@ -809,6 +830,8 @@ const getServerModpackContent = (hit: Labrinth.Search.v3.ResultSearchProject) =>
:max-tags="2"
is-server-project
exclude-loaders
@mouseenter="handleServerProjectMouseEnter(project)"
@mouseleave="handleProjectHoverEnd"
>
</ProjectCard>
</template>
@@ -9,13 +9,9 @@ const changelogEntry = computed(() =>
route.params.date
? getChangelog().find((x) => {
if (x.product === route.params.product) {
console.log('Found matching product!')
if (x.version && x.version === route.params.date) {
console.log('Found matching version!')
return x
} else if (x.date.unix() === Number(route.params.date as string)) {
console.log('Found matching date!')
return x
}
}
+84 -26
View File
@@ -144,7 +144,7 @@
<div class="card flex-card">
<h2>Members</h2>
<div class="details-list">
<template v-for="member in acceptedMembers" :key="member.user.id">
<template v-for="member in acceptedMembers" :key="member?.user.id">
<nuxt-link
class="details-list__item details-list__item--type-large"
:to="`/user/${member?.user?.username}`"
@@ -187,7 +187,7 @@
<NavTabs :links="navLinks" />
</div>
<ProjectCardList v-if="projects && projects.length > 0">
<ProjectCard
<template
v-for="project in (route.params.projectType !== undefined
? (projects ?? []).filter((x) =>
x.project_types.includes(
@@ -204,28 +204,55 @@
.slice()
.sort((a, b) => b.downloads - a.downloads)"
:key="project.id"
:link="`/${project.project_types[0] ?? 'project'}/${project.slug || project.id}`"
:title="project.name"
:icon-url="project.icon_url"
:banner="project.gallery.find((element) => element.featured)?.url"
:summary="project.summary"
:date-updated="project.updated"
:downloads="project.downloads"
:followers="project.followers"
:tags="project.categories"
:environment="{
clientSide: project.client_side,
serverSide: project.server_side,
}"
:status="
auth.user &&
(auth.user.id! === (user as any).id || tags.staffRoles.includes(auth.user.role))
? (project.status as ProjectStatus)
: undefined
"
:color="project.color"
layout="list"
/>
>
<ProjectCard
v-if="isProjectServer(project)"
:link="`/server/${project.slug || project.id}`"
:title="project.name"
:icon-url="project.icon_url"
:summary="project.summary"
:tags="project.categories"
:server-online-players="
project.minecraft_java_server?.ping?.data?.players_online ?? 0
"
:server-recent-plays="project.minecraft_java_server?.verified_plays_2w ?? 0"
:server-region="project.minecraft_server?.region"
:server-status-online="!!project.minecraft_java_server?.ping?.data"
:server-modpack-content="getServerModpackContent(project)"
:status="
auth.user && (auth.user.id! === user.id || tags.staffRoles.includes(auth.user.role))
? (project.status as ProjectStatus)
: undefined
"
:max-tags="2"
layout="list"
is-server-project
exclude-loaders
/>
<ProjectCard
v-else
:link="`/${project.project_types[0] ?? 'project'}/${project.slug || project.id}`"
:title="project.name"
:icon-url="project.icon_url"
:banner="project.gallery.find((element) => element.featured)?.url"
:summary="project.summary"
:date-updated="project.updated"
:downloads="project.downloads"
:followers="project.followers"
:tags="project.categories"
:environment="{
clientSide: project.client_side,
serverSide: project.server_side,
}"
:status="
auth.user && (auth.user.id! === user.id || tags.staffRoles.includes(auth.user.role))
? (project.status as ProjectStatus)
: undefined
"
:color="project.color"
layout="list"
/>
</template>
</ProjectCardList>
<div v-else-if="true" class="error">
<UpToDate class="icon" />
@@ -234,7 +261,7 @@
This organization doesn't have any projects yet.
<template v-if="isPermission(currentMember?.permissions, 1 << 4)">
Would you like to
<a class="link" @click="($refs as any).modal_creation?.show()">create one</a>?
<a class="link" @click="modal_creation?.show()">create one</a>?
</template>
</span>
</div>
@@ -244,6 +271,7 @@
</template>
<script setup lang="ts">
import type { Labrinth } from '@modrinth/api-client'
import {
BoxIcon,
ChartIcon,
@@ -267,7 +295,7 @@ import {
ProjectCardList,
useVIntl,
} from '@modrinth/ui'
import type { Organization, ProjectStatus, ProjectType, ProjectV3 } from '@modrinth/utils'
import type { Organization, ProjectStatus, ProjectType } from '@modrinth/utils'
import { formatNumber } from '@modrinth/utils'
import UpToDate from '~/assets/images/illustrations/up_to_date.svg?component'
@@ -282,6 +310,11 @@ import {
} from '~/providers/organization-context.ts'
import { isPermission } from '~/utils/permissions.ts'
type ProjectV3 = Labrinth.Projects.v3.Project & {
client_side: 'required' | 'optional' | 'unsupported'
server_side: 'required' | 'optional' | 'unsupported'
}
const vintl = useVIntl()
const { formatMessage } = vintl
@@ -294,6 +327,7 @@ const route = useNativeRoute()
const router = useRouter()
const tags = useGeneratedState()
const config = useRuntimeConfig()
const modal_creation = useTemplateRef('modal_creation')
const orgId = useRouteId()
@@ -397,6 +431,30 @@ const projectTypes = computed(() => {
return Object.keys(obj)
})
function isProjectServer(project: ProjectV3): boolean {
return project.minecraft_server != null
}
function getServerModpackContent(project: ProjectV3) {
const content = project.minecraft_java_server?.content
if (content?.kind === 'modpack') {
const { project_name, project_icon, project_id } = content
if (!project_name) return undefined
return {
name: project_name,
icon: project_icon,
onclick:
project_id !== project.id
? () => {
navigateTo(`/project/${project_id}`)
}
: undefined,
showCustomModpackTooltip: project_id === project.id,
}
}
return undefined
}
const sumDownloads = computed(() => {
let sum = 0
@@ -257,6 +257,10 @@ const projectListLayouts = defineMessages({
id: 'settings.display.project-list-layouts.modpack',
defaultMessage: 'Modpacks page',
},
server: {
id: 'settings.display.project-list-layouts.server',
defaultMessage: 'Servers page',
},
user: {
id: 'settings.display.project-list-layouts.user',
defaultMessage: 'User profile pages',
Binary file not shown.

After

Width:  |  Height:  |  Size: 113 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 255 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 115 KiB

@@ -1,5 +1,12 @@
{
"articles": [
{
"title": "Introducing Server Projects",
"summary": "A new project type made for seamless modded multiplayer on Modrinth.",
"thumbnail": "https://modrinth.com/news/article/introducing-server-projects/thumbnail.webp",
"date": "2026-03-04T01:30:00.000Z",
"link": "https://modrinth.com/news/article/introducing-server-projects"
},
{
"title": "Streamlined Version Creation",
"summary": "Version creation is now dramatically more intelligent and easier for creators.",
File diff suppressed because one or more lines are too long
@@ -49,6 +49,14 @@ const projectTypeMessages = defineMessages({
id: 'project-type.server.plural',
defaultMessage: 'Servers',
},
minecraft_java_server: {
id: 'project-type.minecraft_java_server.singular',
defaultMessage: 'Server',
},
minecraft_java_servers: {
id: 'project-type.minecraft_java_server.plural',
defaultMessage: 'Servers',
},
shader: {
id: 'project-type.shader.singular',
defaultMessage: 'Shader',
+6 -7
View File
@@ -357,11 +357,14 @@ export const MURALPAY_RAILS: Record<string, RailConfig> = {
{
name: 'branchCode',
type: 'text',
label: defineMessage({ id: 'muralpay.field.branch-code', defaultMessage: 'Branch code' }),
label: defineMessage({
id: 'muralpay.field.random-key',
defaultMessage: 'Random key',
}),
required: true,
placeholder: defineMessage({
id: 'muralpay.placeholder.enter-branch-code',
defaultMessage: 'Enter branch code',
id: 'muralpay.placeholder.enter-random-key',
defaultMessage: 'Enter random key',
}),
autocomplete: 'off',
dependsOn: {
@@ -383,10 +386,6 @@ export const MURALPAY_RAILS: Record<string, RailConfig> = {
defaultMessage: 'Brazilian tax identification number',
}),
autocomplete: 'off',
dependsOn: {
field: 'pixAccountType',
value: 'DOCUMENT',
},
},
],
},
-2
View File
@@ -140,8 +140,6 @@ AVALARA_1099_API_KEY=none
AVALARA_1099_API_TEAM_ID=none
AVALARA_1099_COMPANY_ID=207337084
COMPLIANCE_PAYOUT_THRESHOLD=disabled
ANROK_API_KEY=none
ANROK_API_URL=none
-2
View File
@@ -150,8 +150,6 @@ AVALARA_1099_API_KEY=none
AVALARA_1099_API_TEAM_ID=none
AVALARA_1099_COMPANY_ID=207337084
COMPLIANCE_PAYOUT_THRESHOLD=disabled
ANROK_API_KEY=none
ANROK_API_URL=none
@@ -0,0 +1,22 @@
{
"db_name": "PostgreSQL",
"query": "SELECT COUNT(*) FROM payouts_values_notifications WHERE notified = FALSE AND user_id = $1",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "count",
"type_info": "Int8"
}
],
"parameters": {
"Left": [
"Int8"
]
},
"nullable": [
null
]
},
"hash": "0f3d943e4fc48a94363b77c8a7d36eb1dd626e77331d8278c406df952691be4c"
}
@@ -0,0 +1,26 @@
{
"db_name": "PostgreSQL",
"query": "\n SELECT\n id,\n status AS \"status: PayoutStatus\"\n FROM payouts\n ORDER BY id\n ",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "id",
"type_info": "Int8"
},
{
"ordinal": 1,
"name": "status: PayoutStatus",
"type_info": "Varchar"
}
],
"parameters": {
"Left": []
},
"nullable": [
false,
false
]
},
"hash": "1adbd24d815107e13bc1440c7a8f4eeff66ab4165a9f4980032e114db4dc1286"
}
@@ -0,0 +1,20 @@
{
"db_name": "PostgreSQL",
"query": "SELECT COUNT(*) FROM payouts_values_notifications WHERE notified = FALSE",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "count",
"type_info": "Int8"
}
],
"parameters": {
"Left": []
},
"nullable": [
null
]
},
"hash": "20cff8fdf7971e91c9d473b9a4663ce02ca16781e32232ae0fa7a0af1973d3a4"
}
@@ -0,0 +1,23 @@
{
"db_name": "PostgreSQL",
"query": "\n INSERT INTO delphi_report_issues (report_id, issue_type)\n VALUES ($1, $2)\n ON CONFLICT (report_id, issue_type) DO UPDATE SET\n issue_type = EXCLUDED.issue_type\n RETURNING id\n ",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "id",
"type_info": "Int8"
}
],
"parameters": {
"Left": [
"Int8",
"Text"
]
},
"nullable": [
false
]
},
"hash": "3bf93aa9204bc5c52441e0a3789aeaae55206a459299c2e63cc303d2a7615588"
}
@@ -0,0 +1,15 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO payouts_values_notifications (date_available, user_id, notified)\n VALUES ($1, $2, FALSE)\n ON CONFLICT (date_available, user_id) DO NOTHING",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Timestamptz",
"Int8"
]
},
"nullable": []
},
"hash": "6678cd4c51771cfaae2be8021ba66908ea41a06ba858dc5b523aef6aae27b850"
}
@@ -0,0 +1,16 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO payouts_values (user_id, mod_id, amount, created, date_available)\n VALUES ($1, NULL, $2, NOW(), $3)",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Int8",
"Numeric",
"Timestamptz"
]
},
"nullable": []
},
"hash": "69a1cb4b7f1115a990d1fc4805d58541fc78e910111c09ba3d50a12d9ca4a9f8"
}
@@ -0,0 +1,20 @@
{
"db_name": "PostgreSQL",
"query": "\n SELECT DISTINCT m.id\n FROM mods m\n WHERE\n EXISTS(\n SELECT 1\n FROM delphi_issue_details_with_statuses didws\n INNER JOIN delphi_report_issues dri ON dri.id = didws.issue_id\n WHERE\n didws.project_id = m.id\n AND didws.status = 'pending'\n -- see delphi.rs todo comment\n AND dri.issue_type != '__dummy'\n )\n AND NOT EXISTS(\n SELECT 1\n FROM delphi_issue_details_with_statuses didws\n INNER JOIN delphi_report_issues dri ON dri.id = didws.issue_id\n WHERE\n didws.project_id = m.id\n AND didws.status IN ('safe', 'unsafe')\n -- see delphi.rs todo comment\n AND dri.issue_type != '__dummy'\n )\n ",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "id",
"type_info": "Int8"
}
],
"parameters": {
"Left": []
},
"nullable": [
false
]
},
"hash": "9b0edb6399bc5f38ee96677cb2539476ecf08549b914f96f0c23fbfbe3f17f2e"
}
@@ -0,0 +1,20 @@
{
"db_name": "PostgreSQL",
"query": "SELECT status AS \"status: PayoutStatus\" FROM payouts WHERE id = 1",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "status: PayoutStatus",
"type_info": "Varchar"
}
],
"parameters": {
"Left": []
},
"nullable": [
false
]
},
"hash": "b92b5bb7d179c4fcdbc45600ccfd2402f52fea71e27b08e7926fcc2a9e62c0f3"
}
@@ -0,0 +1,18 @@
{
"db_name": "PostgreSQL",
"query": "\n INSERT INTO payouts (id, method, platform_id, status, user_id, amount, created)\n VALUES ($1, $2, $3, $4, $5, 10.0, NOW())\n ",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Int8",
"Text",
"Text",
"Varchar",
"Int8"
]
},
"nullable": []
},
"hash": "cd5ccd618fb3cc41646a6de86f9afedb074492b4ec7f2457c14113f5fd13aa02"
}

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