mirror of
https://github.com/modrinth/code.git
synced 2026-08-28 18:45:15 +00:00
Compare commits
57
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9a717962fb | ||
|
|
30df2ee4dd | ||
|
|
a0c2537505 | ||
|
|
ba1b7ce3c1 | ||
|
|
5605ee578a | ||
|
|
17f13097f2 | ||
|
|
a9de420d57 | ||
|
|
ab7359a4c5 | ||
|
|
97b5138672 | ||
|
|
579109a478 | ||
|
|
5f7493491f | ||
|
|
755825b09a | ||
|
|
06b7c44dcc | ||
|
|
e087e571f3 | ||
|
|
fbdde8f3d8 | ||
|
|
290bd2b934 | ||
|
|
9ecbe8eaa6 | ||
|
|
549b333eb2 | ||
|
|
4b82cca3bf | ||
|
|
734fd9cdb3 | ||
|
|
2fd4495104 | ||
|
|
552bc3f739 | ||
|
|
4758cabbf9 | ||
|
|
6ec979a076 | ||
|
|
c34bd26f87 | ||
|
|
2a43792fd9 | ||
|
|
2d567b93ce | ||
|
|
26e05ee9e5 | ||
|
|
ca8c5a717f | ||
|
|
759f67a551 | ||
|
|
66628fd69d | ||
|
|
1ffcd67562 | ||
|
|
fda5c62bc6 | ||
|
|
f8f05ce91b | ||
|
|
8b2438aebb | ||
|
|
07bac8ffde | ||
|
|
d0856d46f2 | ||
|
|
e8aee15664 | ||
|
|
98cb6b6b44 | ||
|
|
f27387462e | ||
|
|
ef90f08813 | ||
|
|
9fd45ef2ce | ||
|
|
92971a3e0d | ||
|
|
357224f22a | ||
|
|
e2612d1a33 | ||
|
|
318840e403 | ||
|
|
1a56233c96 | ||
|
|
ff8c4f16a9 | ||
|
|
757ec9ab53 | ||
|
|
f38d351d32 | ||
|
|
b78dd9bf1b | ||
|
|
5072c1d298 | ||
|
|
8e8640bfb8 | ||
|
|
1c3fa44049 | ||
|
|
7cd3e835ab | ||
|
|
5c4c30e514 | ||
|
|
6549d047dc |
@@ -0,0 +1,23 @@
|
||||
---
|
||||
name: api-module
|
||||
description: Add an API endpoint module to packages/api-client from an OpenAPI schema. Use for new backend endpoints, API client modules, or tasks that provide an OpenAPI schema.
|
||||
---
|
||||
|
||||
# Add an API Module
|
||||
|
||||
Read the applicable `AGENTS.md` files before you edit code.
|
||||
|
||||
Read [the API module standard](../../../standards/frontend/ADDING_API_MODULES.md) in full.
|
||||
|
||||
1. Identify the OpenAPI schema from the request. If more than one schema is possible, ask the user to select one.
|
||||
2. Read the schema. Identify each endpoint, HTTP method, request type, response type, and path parameter.
|
||||
3. Get the service and version from the URL prefix. For example, map `/v3/projects` to `labrinth/v3/`.
|
||||
4. Define the API types in `types.ts`. Make each type match the schema exactly.
|
||||
5. Do not change, rename, or remove API fields.
|
||||
6. Make a module class that extends `AbstractModule`. Implement each endpoint with `this.client.request()` or `this.client.upload()`.
|
||||
7. Use the request-option pattern from the standard. Do not call `$fetch`, `fetch`, or another HTTP client directly.
|
||||
8. Add the module to `MODULE_REGISTRY` so the client can instantiate it.
|
||||
9. Export new service types from the applicable barrel `index.ts`.
|
||||
10. Check the module paths, registry key, public type exports, and endpoint types.
|
||||
|
||||
Run only the checks that the user or the applicable `AGENTS.md` permits.
|
||||
@@ -0,0 +1,4 @@
|
||||
interface:
|
||||
display_name: "Add API Module"
|
||||
short_description: "Add typed API client modules from OpenAPI"
|
||||
default_prompt: "Use $api-module to add an API client module from this OpenAPI schema."
|
||||
@@ -0,0 +1,38 @@
|
||||
---
|
||||
name: cross-platform-pages
|
||||
description: Convert a page to the shared Modrinth page system for the website and desktop app. Use for shared layouts, wrapped layouts, or platform dependency-injection contracts.
|
||||
---
|
||||
|
||||
# Convert a Cross-Platform Page
|
||||
|
||||
Read the applicable `AGENTS.md` files before you edit code.
|
||||
|
||||
Read these standards in full:
|
||||
|
||||
- [Cross-platform pages](../../../standards/frontend/CROSS_PLATFORM_PAGES.md)
|
||||
- [Dependency injection](../../../standards/frontend/DEPENDENCY_INJECTION.md)
|
||||
|
||||
1. Identify the target page from the request.
|
||||
2. Read the page and its route shell. Identify data sources, mutations, navigation, and platform APIs.
|
||||
3. Use a wrapped layout when both platforms use the same API source and page logic.
|
||||
4. Use a shared layout when platform data or operations have different implementations.
|
||||
|
||||
For a shared layout:
|
||||
|
||||
1. Define a provider contract for all platform operations.
|
||||
2. Put common UI and state logic in the shared layout.
|
||||
3. Put reusable search, filter, and selection logic in local composables.
|
||||
4. Implement the contract in `apps/frontend/` and `apps/app-frontend/`.
|
||||
5. Use optional contract fields only for capabilities that are not available on both platforms.
|
||||
|
||||
For a wrapped layout:
|
||||
|
||||
1. Move the page to `packages/ui/src/layouts/wrapped/` and preserve its route structure.
|
||||
2. Replace platform-only imports with common utilities or provider calls.
|
||||
3. Make each frontend route shell render the wrapped component.
|
||||
4. Match primary query options in both route shells when the layout uses `ReadyTransition` and `useReadyState`.
|
||||
5. Prefetch these queries with `ensureQueryData`, as the standard specifies.
|
||||
|
||||
Check that both route shells resolve their imports. Check that all required provider fields have implementations.
|
||||
|
||||
Run only the checks that the user or the applicable `AGENTS.md` permits.
|
||||
@@ -0,0 +1,4 @@
|
||||
interface:
|
||||
display_name: "Convert Cross-Platform Page"
|
||||
short_description: "Share pages across the web and desktop app"
|
||||
default_prompt: "Use $cross-platform-pages to convert this page for the website and desktop app."
|
||||
@@ -0,0 +1,21 @@
|
||||
---
|
||||
name: figma-mcp
|
||||
description: Convert a Figma design into a Modrinth Vue page or component. Use when a request provides a Figma URL or asks to implement a Figma layout.
|
||||
---
|
||||
|
||||
# Implement a Figma Design
|
||||
|
||||
Read the applicable `AGENTS.md` files before you edit code.
|
||||
|
||||
Read `packages/ui/AGENTS.md` in full.
|
||||
|
||||
1. Load the available Figma design-to-code instructions and follow the MCP tool guidance.
|
||||
2. Call `get_design_context` first with `clientLanguages: "typescript,html,css"` and `clientFrameworks: "vue"`.
|
||||
3. Treat the result as reference code and adapt it to the Modrinth codebase.
|
||||
4. Map Figma color variables to the applicable `surface-*` and `text-*` tokens. Do not use aliased Figma names directly.
|
||||
5. Reuse applicable components from `packages/ui/src/components/` before creating new ones. Also refer to `standards/frontend/COMPONENT_STRUCTURE.md`
|
||||
6. Read `packages/assets/styles/variables.scss` when Figma does not supply a required token.
|
||||
7. Use exact spacing values from the design.
|
||||
8. Implement the result as a Vue SFC with Tailwind classes and the existing component library.
|
||||
|
||||
Run only the checks that the user or the applicable `AGENTS.md` permits.
|
||||
@@ -0,0 +1,4 @@
|
||||
interface:
|
||||
display_name: "Implement Figma Design"
|
||||
short_description: "Build Modrinth Vue UI from Figma designs"
|
||||
default_prompt: "Use $figma-mcp to implement this Figma design as a Modrinth Vue component."
|
||||
@@ -0,0 +1,32 @@
|
||||
---
|
||||
name: i18n-pass
|
||||
description: Convert hard-coded English text in changed Vue components to the @modrinth/ui localization system. Use for an i18n pass, untranslated-string review, pull request, or component migration.
|
||||
---
|
||||
|
||||
# Do an Internationalization Pass
|
||||
|
||||
Read the applicable `AGENTS.md` files before you edit code.
|
||||
|
||||
Read [the internationalization standard](../../../standards/frontend/INTERNATIONALIZATION.md) in full.
|
||||
|
||||
1. Identify the scope from the request.
|
||||
2. For a pull request, use `gh pr diff <number>` to identify changed files.
|
||||
3. For a file path, inspect that file.
|
||||
4. When the request gives no scope, inspect the current uncommitted diff.
|
||||
5. Limit the pass to changed `.vue` files.
|
||||
6. Find user-visible text in templates and scripts.
|
||||
|
||||
Check inner text, `alt`, `placeholder`, `aria-label`, buttons, tooltips, notifications, dropdown labels, and error messages.
|
||||
|
||||
Do not change dynamic expressions, HTML tag names, CSS classes, internal identifiers, or log messages.
|
||||
|
||||
1. Define stable message IDs with `defineMessage` or `defineMessages`.
|
||||
2. Replace simple text with `formatMessage()` calls.
|
||||
3. Use `<IntlFormatted>` for text that contains links or markup.
|
||||
4. Use ICU selections and plurals when grammar depends on a value.
|
||||
5. Add a space before `}}` when an ICU placeholder ends at the Vue delimiter.
|
||||
6. Do not change component logic, layout, or reactivity.
|
||||
7. Do not edit localization JSON files. The user maintains those files.
|
||||
8. Check the changed templates again for hard-coded English text.
|
||||
|
||||
Run only the checks that the user or the applicable `AGENTS.md` permits.
|
||||
@@ -0,0 +1,4 @@
|
||||
interface:
|
||||
display_name: "Run Internationalization Pass"
|
||||
short_description: "Localize user-visible text in Vue files"
|
||||
default_prompt: "Use $i18n-pass to localize the user-visible text in these changed Vue files."
|
||||
@@ -0,0 +1,40 @@
|
||||
---
|
||||
name: review-changelog
|
||||
description: Review the latest packages/blog/changelog.ts entry against the Modrinth changelog standard. Use before a pull request or when asked to review or lint a changelog entry.
|
||||
---
|
||||
|
||||
# Review a Changelog Entry
|
||||
|
||||
Read [the changelog standard](../../../standards/maintaining/CHANGELOG.md) in full before the review.
|
||||
|
||||
1. Open `packages/blog/changelog.ts`.
|
||||
2. Find the first entry in the `VERSIONS` array.
|
||||
3. If the request names `web`, `hosting`, or `app`, review the latest entry for that product.
|
||||
4. Otherwise, review the latest entry and all adjacent entries with the same date.
|
||||
|
||||
Check the entry structure:
|
||||
|
||||
- `date` contains a valid ISO 8601 timestamp.
|
||||
- `product` is `web`, `hosting`, or `app`.
|
||||
- An `app` entry has a `version` value.
|
||||
- A `web` or `hosting` entry does not have a `version` value.
|
||||
- Standard headings are `## Added`, `## Changed`, `## Fixed`, and `## Security`.
|
||||
- A featured release can use a linked heading.
|
||||
- Flag the legacy `## Improvements` heading.
|
||||
|
||||
Check each bullet:
|
||||
|
||||
- The voice and tense agree with the section.
|
||||
- The first verb agrees with the section.
|
||||
- The bullet describes user-visible behavior, not implementation.
|
||||
- The bullet identifies the applicable page, tab, modal, or feature.
|
||||
- The bullet contains one sentence, uses sentence case, and ends with a period.
|
||||
- Product and UI names use the public labels.
|
||||
- The bullet does not contain filler, vague intensifiers, apologies, or internal references.
|
||||
- The bullet is not a duplicate detail of a larger listed change.
|
||||
|
||||
Group findings by entry. For each finding, show the original bullet and a proposed replacement.
|
||||
|
||||
If the entry has no findings, state this result. Do not edit the changelog unless the user asks you to apply fixes.
|
||||
|
||||
When the user asks for fixes, preserve tab indentation and template-literal formatting.
|
||||
@@ -0,0 +1,4 @@
|
||||
interface:
|
||||
display_name: "Review Changelog"
|
||||
short_description: "Review changelog entries for style problems"
|
||||
default_prompt: "Use $review-changelog to review the latest changelog entry."
|
||||
@@ -0,0 +1,39 @@
|
||||
---
|
||||
name: tanstack-query
|
||||
description: Convert Vue server-state code to TanStack Query. Use for useQuery, useMutation, cache invalidation, optimistic updates, or replacement of useAsyncData and manual ref patterns.
|
||||
---
|
||||
|
||||
# Convert Data Code to TanStack Query
|
||||
|
||||
Read the applicable `AGENTS.md` files before you edit code.
|
||||
|
||||
Read [the TanStack Query standard](../../../standards/frontend/FETCHING_DATA.md) in full.
|
||||
|
||||
1. Identify the target file from the request.
|
||||
2. Find `useAsyncData`, `useFetch`, manual API refs, and fetch calls in `onMounted`.
|
||||
3. Identify mutations that use manual loading, error, or result refs.
|
||||
|
||||
For queries:
|
||||
|
||||
1. Replace manual fetch logic with `useQuery`.
|
||||
2. Get `api-client` with `injectModrinthClient()`.
|
||||
3. Use a hierarchical query key with the resource, qualifier, and parameters.
|
||||
4. Use a computed query key for reactive parameters.
|
||||
5. Use a computed `enabled` option when the query depends on other data.
|
||||
6. Use a shared query-option factory when multiple components use the query.
|
||||
|
||||
For mutations:
|
||||
|
||||
1. Replace manual mutation state with `useMutation`.
|
||||
2. Invalidate or update related query data after success.
|
||||
3. Use an optimistic update only when the UI needs an immediate response.
|
||||
4. Cancel the applicable query and save its prior data before an optimistic update.
|
||||
5. Restore the prior data after an error. Invalidate the query after settlement.
|
||||
|
||||
Remove manual loading and error refs that TanStack Query replaces. Remove obsolete `onMounted` fetch calls.
|
||||
|
||||
Keep Nuxt SSR behavior. Match route-shell prefetch options when `ReadyTransition` and `useReadyState` depend on the query.
|
||||
|
||||
Check query keys, invalidation prefixes, reactive values, and rollback data.
|
||||
|
||||
Run only the checks that the user or the applicable `AGENTS.md` permits.
|
||||
@@ -0,0 +1,4 @@
|
||||
interface:
|
||||
display_name: "Migrate to TanStack Query"
|
||||
short_description: "Migrate Vue server state to TanStack Query"
|
||||
default_prompt: "Use $tanstack-query to migrate this Vue component to TanStack Query."
|
||||
@@ -1,18 +0,0 @@
|
||||
---
|
||||
name: api-module
|
||||
description: Add a new API endpoint module to packages/api-client from an OpenAPI schema. Use when adding new backend endpoints, creating API client modules, or when an openapi.yml is provided.
|
||||
argument-hint: <path-to-openapi.yml>
|
||||
---
|
||||
|
||||
Refer to the standard: @standards/frontend/ADDING_API_MODULES.md
|
||||
|
||||
## Steps
|
||||
|
||||
1. **Read the OpenAPI schema** at `$ARGUMENTS` — identify the endpoints, request/response shapes, and path parameters.
|
||||
2. **Read the standard above** for naming conventions, type rules, and the module registration pattern.
|
||||
3. **Determine the service and version** — the URL path prefix tells you which service directory and version namespace to use (e.g. `/v3/projects` → `labrinth/v3/`).
|
||||
4. **Define types in `types.ts`** — types must match the API response 1:1. Use the OpenAPI schema as the source of truth. Do not reshape or rename fields.
|
||||
5. **Create the module class** — extend `BaseModule`, implement each endpoint as a method. Use the correct HTTP verb and request options pattern from the standard.
|
||||
6. **Register in `MODULE_REGISTRY`** — add the module entry so it's auto-instantiated on the client.
|
||||
7. **Export types** from the service's barrel `index.ts`.
|
||||
8. **Verify** — check that the module compiles and the types are accessible from `@modrinth/api-client`.
|
||||
@@ -1,26 +0,0 @@
|
||||
---
|
||||
name: cross-platform-pages
|
||||
description: Convert a page to the cross-platform page system so it works in both the website and the desktop app. Use when moving a page into packages/ui/src/layouts/, creating shared or wrapped layouts, or setting up DI contracts for platform abstraction.
|
||||
argument-hint: <path-to-page>
|
||||
---
|
||||
|
||||
Refer to the standards: @standards/frontend/CROSS_PLATFORM_PAGES.md and @standards/frontend/DEPENDENCY_INJECTION.md
|
||||
|
||||
## Steps
|
||||
|
||||
1. **Read the target page** at `$ARGUMENTS` and understand its data sources, mutations, and navigation.
|
||||
2. **Read the standards above** to understand the shared vs wrapped distinction and the DI pattern.
|
||||
3. **Decide the category:**
|
||||
- **Wrapped** (`layouts/wrapped/`) — if the page uses the same API source on both platforms (e.g. web requests, not Tauri plugins). Just move the page component into `packages/ui` and import it from both frontends.
|
||||
- **Shared** (`layouts/shared/`) — if the page has different data-fetching logic per platform (e.g. website uses `api-client`, app uses Tauri `invoke`). Requires a DI contract.
|
||||
4. **For shared layouts:**
|
||||
- Define a DI contract interface in `providers/` capturing all platform-specific operations.
|
||||
- Create the layout component that injects the context and handles all UI logic.
|
||||
- Extract reusable stateful logic (search, filtering, selection) into `composables/`.
|
||||
- Implement the contract separately in each frontend (`apps/frontend/`, `apps/app-frontend/`).
|
||||
5. **For wrapped pages:**
|
||||
- Move the page component into `packages/ui/src/layouts/wrapped/` matching the route structure.
|
||||
- Replace any platform-specific imports with shared utilities.
|
||||
- Import and render the wrapped page from both frontends as a simple component.
|
||||
- If the layout uses TanStack Query for initial route paint with `ReadyTransition` / `useReadyState`, each platform route shell must call `ensureQueryData` for those queries with matching keys and fetchers — see **Platform route shells: prefetch with `ensureQueryData`** in `standards/frontend/CROSS_PLATFORM_PAGES.md`.
|
||||
6. **Verify** the page renders correctly by checking for missing imports and that all DI contracts are satisfied.
|
||||
@@ -1,22 +0,0 @@
|
||||
---
|
||||
name: figma-mcp
|
||||
description: Use the Figma MCP server to translate a Figma design into a Vue page or component layout. Use when the user provides a Figma URL, asks to implement a design, or wants to draft a page layout from Figma.
|
||||
argument-hint: <figma-url>
|
||||
---
|
||||
|
||||
Refer to the standard: @standards/frontend/FIGMA_MCP_USAGE.md
|
||||
Also read @packages/ui/CLAUDE.md for color token mapping and component conventions.
|
||||
|
||||
## Steps
|
||||
|
||||
1. **Parse the Figma URL** from `$ARGUMENTS` — extract the `fileKey` and `nodeId`. Convert `-` to `:` in the node ID.
|
||||
2. **Read the standards above** for the available tools, adaptation rules, and color usage.
|
||||
3. **Call `get_design_context`** with the extracted `nodeId` and `fileKey`, using `clientLanguages: "typescript,html,css"` and `clientFrameworks: "vue"`. This is always the first tool to call.
|
||||
5. **Adapt the output to the Modrinth codebase:**
|
||||
- Map Figma color variables to `surface-*` / `text-*` tokens — never use Figma's aliased names directly.
|
||||
- Check `packages/ui/src/components/` for existing components that match elements in the design (buttons, cards, modals, inputs, etc.).
|
||||
- Check `packages/assets/styles/variables.scss` for tokens not exposed in Figma.
|
||||
- Match spacing values exactly from the design.
|
||||
6. **Use `get_screenshot`** if you need a closer visual reference of specific nodes.
|
||||
7. **Use `get_variable_defs`** to verify which design tokens are applied to ambiguous elements.
|
||||
8. **Build the component** as a Vue SFC using Tailwind classes and the project's existing component library.
|
||||
@@ -1,24 +0,0 @@
|
||||
---
|
||||
name: i18n-pass
|
||||
description: Perform an i18n localization pass on changed files or a pull request, converting hard-coded English strings to the @modrinth/ui i18n system. Use when internationalizing a set of changes, reviewing a PR for untranslated strings, or converting a specific component.
|
||||
argument-hint: [file-path-or-pr-number]
|
||||
---
|
||||
|
||||
Refer to the standard: @standards/frontend/INTERNATIONALIZATION.md
|
||||
|
||||
## Steps
|
||||
|
||||
1. **Identify the scope of changes:**
|
||||
- If `$ARGUMENTS` is a PR number, run `gh pr diff $ARGUMENTS` to get the changed files.
|
||||
- If `$ARGUMENTS` is a file path, use that directly.
|
||||
- If no argument, check `git diff` for uncommitted changes.
|
||||
2. **Read the standard above** for the message definition pattern, ICU format rules, and `IntlFormatted` usage.
|
||||
3. **Filter to Vue SFCs** — only `.vue` files need i18n passes. Skip non-component files.
|
||||
4. **For each file, scan for hard-coded strings:**
|
||||
- `<template>`: inner text, `alt`, `placeholder`, `aria-label`, button labels, tooltip text.
|
||||
- `<script>`: string literals passed to user-visible UI (notification messages, dropdown labels, error messages).
|
||||
- Skip: dynamic expressions, HTML tag names, CSS classes, internal identifiers, log messages.
|
||||
5. **Define messages** with `defineMessages` — use descriptive, stable `id`s based on the component's domain (e.g. `project.settings.title`).
|
||||
6. **Replace strings in templates** with `formatMessage()` calls, or `<IntlFormatted>` for strings containing links or markup.
|
||||
7. **Handle ICU edge cases** — add a space before `}}` if an ICU placeholder ends at a Vue template delimiter boundary.
|
||||
8. **Verify** no hard-coded English strings remain in the changed templates. Do not alter logic, layout, or reactivity.
|
||||
@@ -1,36 +0,0 @@
|
||||
---
|
||||
name: review-changelog
|
||||
description: Review the latest changelog entry in packages/blog/changelog.ts against the project's changelog style guide and flag bullets that need rewriting. Use when checking a freshly added changelog entry before opening a PR, or when the user asks to review/lint the latest changelog.
|
||||
argument-hint: [product?]
|
||||
---
|
||||
|
||||
Refer to the standard: @standards/maintaining/CHANGELOG.md
|
||||
|
||||
## Steps
|
||||
|
||||
1. **Locate the latest entry:**
|
||||
- Open `packages/blog/changelog.ts`.
|
||||
- The latest entries are at the top of the `VERSIONS` array.
|
||||
- If `$ARGUMENTS` specifies a product (`web`, `hosting`, `app`), review the most recent entry for that product. Otherwise, review the most recent entry overall, plus any sibling entries sharing the same `date` (coordinated releases ship together).
|
||||
|
||||
2. **Read the standard above** in full before reviewing. The bullet rules, section/verb agreement, and "Don't" list are the source of truth.
|
||||
|
||||
3. **Check the entry shell:**
|
||||
- `date` is a valid ISO 8601 timestamp.
|
||||
- `product` is one of `web`, `hosting`, `app`.
|
||||
- `version` is present for `app` entries and omitted for `web`/`hosting`.
|
||||
- Section headings use `## Added`, `## Changed`, `## Fixed`, `## Security` (or a featured-release linked heading). Flag legacy `## Improvements`.
|
||||
|
||||
4. **Review each bullet** against the standard. For each bullet, check:
|
||||
- Voice/tense matches the section heading.
|
||||
- Opening verb agrees with its section.
|
||||
- Describes observable behavior, not implementation.
|
||||
- Specific enough to identify the surface (names the tab/page/modal).
|
||||
- One sentence, ends with a period, sentence case.
|
||||
- Uses branded names (Modrinth App, Modrinth Hosting) correctly.
|
||||
- No filler ("issue with", "issue where", "various", "some"), no vague intensifiers, no apologies, no PR/commit references (unless crediting a third-party contributor with a linked GitHub profile).
|
||||
- Not a duplicate sub-fix of a bigger change already listed.
|
||||
|
||||
5. **Report findings** as a short list grouped by entry. For each problem bullet, show the original line and a suggested rewrite. If the entry is clean, say so explicitly. Do not edit the file unless the user asks - this skill is a review pass, not a rewrite pass.
|
||||
|
||||
6. **If the user then asks to apply fixes**, edit `packages/blog/changelog.ts` directly using the suggested rewrites. Preserve tab indentation and template literal formatting.
|
||||
@@ -1,27 +0,0 @@
|
||||
---
|
||||
name: tanstack-query
|
||||
description: Convert a page or component from useAsyncData/manual ref patterns to TanStack Query for server state management. Use when migrating data fetching to useQuery/useMutation, adding cache invalidation, or replacing useAsyncData with TanStack Query.
|
||||
argument-hint: <path-to-file>
|
||||
---
|
||||
|
||||
Refer to the standard: @standards/frontend/FETCHING_DATA.md
|
||||
|
||||
## Steps
|
||||
|
||||
1. **Read the target file** at `$ARGUMENTS` and identify all data-fetching patterns: `useAsyncData`, `useFetch`, manual `ref()` + `await`, or `onMounted` fetch calls.
|
||||
2. **Read the standard above** for the query/mutation patterns, query key conventions, and optimistic update approach.
|
||||
3. **Convert queries:**
|
||||
- Replace `useAsyncData` / `useFetch` / manual fetches with `useQuery`.
|
||||
- Use the `api-client` via `injectModrinthClient()` for the `queryFn`.
|
||||
- Design query keys with the `['resource', 'version', ...params]` convention.
|
||||
- Use `computed` query keys for reactive parameters.
|
||||
- Use the `enabled` option for conditional queries that depend on other data.
|
||||
4. **Convert mutations:**
|
||||
- Replace manual `try/catch` + `ref` patterns with `useMutation`.
|
||||
- Add `onSuccess` handlers that invalidate or update related query caches.
|
||||
- Consider optimistic updates for UI-critical mutations (follow the pattern in the standard).
|
||||
5. **Clean up:**
|
||||
- Remove manual loading/error `ref()`s that are now handled by TanStack Query's return values (`isPending`, `isError`, `error`).
|
||||
- Remove manual `onMounted` fetch calls.
|
||||
- Ensure SSR compatibility — queries in Nuxt pages are automatically awaited during SSR.
|
||||
6. **Verify** the page still renders correctly and that cache invalidation triggers re-fetches where expected.
|
||||
@@ -147,7 +147,7 @@ jobs:
|
||||
|
||||
deploy:
|
||||
needs: [skip-if-clean, docker-build]
|
||||
if: ${{ needs.skip-if-clean.outputs.internal == 'true' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/prod') }}
|
||||
if: ${{ needs.skip-if-clean.outputs.internal == 'true' && github.ref == 'refs/heads/prod' }}
|
||||
uses: SparkUniverse/workflows/.github/workflows/argo-update.yaml@main
|
||||
secrets:
|
||||
ARGOCD_DEPLOY_KEY: ${{ secrets.ARGOCD_DEPLOY_KEY }}
|
||||
|
||||
+9
-2
@@ -59,14 +59,21 @@ apps/frontend/src/generated
|
||||
.turbo
|
||||
target
|
||||
generated
|
||||
!apps/app-frontend/src/generated/
|
||||
!apps/app-frontend/src/generated/app-events/
|
||||
!apps/app-frontend/src/generated/app-events/*.ts
|
||||
!apps/app-frontend/src/generated/app-events/README.md
|
||||
!apps/app-frontend/src/generated/app-events/postcard/
|
||||
!apps/app-frontend/src/generated/app-events/postcard/index.d.ts
|
||||
!apps/app-frontend/src/generated/app-events/postcard/index.js
|
||||
!apps/app-frontend/src/generated/app-events/postcard/package.json
|
||||
.env
|
||||
|
||||
# app testing dir
|
||||
app-playground-data/*
|
||||
|
||||
.astro
|
||||
.claude/*
|
||||
!.claude/skills/
|
||||
.claude/
|
||||
.letta
|
||||
|
||||
# labrinth demo fixtures
|
||||
|
||||
Generated
+11
@@ -17,6 +17,17 @@
|
||||
<sourceFolder url="file://$MODULE_DIR$/packages/modrinth-maxmind/src" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/packages/modrinth-util/src" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/packages/muralpay/src" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/packages/async-minecraft-ping/examples" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/packages/async-minecraft-ping/src" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/packages/async-minecraft-ping/tests" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/packages/labrinth-derive/src" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/packages/modrinth-content-management/src" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/packages/modrinth-content-management/tests" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/packages/neverbounce/src" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/packages/serde-binhum/src" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/packages/sqlx-tracing/src" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/packages/sqlx-tracing/tests" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/packages/xredis/src" isTestSource="false" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/target" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
# Modrinth Monorepo
|
||||
|
||||
This is the Modrinth monorepo — it contains all Modrinth projects, both frontend and backend. When entering a project, either to edit or analyse, you should read its AGENTS.md.
|
||||
|
||||
## Architecture
|
||||
|
||||
- **Monorepo tooling:** [Turborepo](https://turbo.build/) (`turbo.jsonc`) + [pnpm workspaces](https://pnpm.io/workspaces) (`pnpm-workspace.yaml`)
|
||||
- **Frontend:** Vue 3 / Nuxt 3, Tailwind CSS v3
|
||||
- **Backend:** Rust (Labrinth API), Postgres, Clickhouse
|
||||
- **Indentation:** Use TAB everywhere, never spaces
|
||||
|
||||
### Apps (`apps/`)
|
||||
|
||||
| App | Description |
|
||||
| ----------------- | ------------------------------ |
|
||||
| `frontend` | Main Modrinth website (Nuxt 3) |
|
||||
| `app-frontend` | Desktop/app frontend (Vue 3) |
|
||||
| `app` | Desktop/app shell (Tauri) |
|
||||
| `app-playground` | Testing playground for app |
|
||||
| `labrinth` | Backend API service |
|
||||
| `daedalus_client` | Daedalus client implementation |
|
||||
| `docs` | Documentation site (Astro) |
|
||||
|
||||
### Packages (`packages/`)
|
||||
|
||||
| Package | Description |
|
||||
| ------------------ | ----------------------------------------------------- |
|
||||
| `ui` | Shared Vue component library (`@modrinth/ui`) |
|
||||
| `assets` | Styling and auto-generated icons (`@modrinth/assets`) |
|
||||
| `api-client` | API client for Nuxt, Tauri, and Node/browser |
|
||||
| `app-lib` | Shared app library |
|
||||
| `blog` | Blog system and changelog data |
|
||||
| `utils` | Shared utility functions (mostly deprecated) |
|
||||
| `moderation` | Moderation utilities |
|
||||
| `daedalus` | Daedalus protocol |
|
||||
| `tooling-config` | ESLint, Prettier, TypeScript configs |
|
||||
| `ariadne` | Analytics library |
|
||||
| `modrinth-log` | Logging utilities |
|
||||
| `modrinth-maxmind` | MaxMind GeoIP |
|
||||
| `modrinth-util` | General utilities |
|
||||
| `muralpay` | Payment processing |
|
||||
| `path-util` | Path utilities |
|
||||
| `sqlx-tracing` | SQLx query tracing |
|
||||
|
||||
## Pre-PR Commands
|
||||
|
||||
Run these from the **root** folder before opening a pull request - do not run these after each prompt the user gives you, only run when asked, ask the user a question if they want to run it if the user indicates that they are about to create a pull request.
|
||||
|
||||
- **Website:** `pnpm prepr:frontend:web`
|
||||
- **App frontend:** `pnpm prepr:frontend:app`
|
||||
- **Frontend libs:** `pnpm prepr:frontend:lib`
|
||||
- **All frontend (app+web):** `pnpm prepr`
|
||||
- **Labrinth (backend):** See `apps/labrinth/AGENTS.md`
|
||||
|
||||
The website and app `prepr` commands
|
||||
|
||||
## Dev Commands
|
||||
|
||||
- **Website:** `pnpm web:dev` (copy `.env` template in `apps/frontend/` first)
|
||||
- **App:** `pnpm app:dev` (copy `.env` template in `packages/app-lib/` first)
|
||||
- **Storybook (packages/ui):** `pnpm storybook`
|
||||
|
||||
## Project-Specific Instructions
|
||||
|
||||
Each project may have its own file with detailed instructions:
|
||||
|
||||
- [`apps/labrinth/AGENTS.md`](apps/labrinth/AGENTS.md) — Backend API
|
||||
- [`apps/frontend/AGENTS.md`](apps/frontend/AGENTS.md) - Frontend Website
|
||||
|
||||
## Code Guidelines
|
||||
|
||||
### Comments
|
||||
- DO NOT use "heading" comments like: `=== Helper methods ===`.
|
||||
- Use doc comments, but avoid inline comments unless ABSOLUTELY necessary for clarity. Code should aim to be self documenting!
|
||||
|
||||
## Bash Guidelines
|
||||
|
||||
### Output handling
|
||||
- DO NOT pipe output through `head`, `tail`, `less`, or `more`
|
||||
- NEVER use `| head -n X` or `| tail -n X` to truncate output
|
||||
- IMPORTANT: Run commands directly without pipes when possible
|
||||
- IMPORTANT: If you need to limit output, use command-specific flags (e.g. `git log -n 10` instead of `git log | head -10`)
|
||||
- ALWAYS read the full output — never pipe through filters
|
||||
|
||||
### General
|
||||
- Do not create new non-source code files (e.g. Bash scripts, SQL scripts) unless explicitly prompted to
|
||||
- For Frontend, when doing lint checks, only use the `prepr` commands, do not use `typecheck` or `tsc` etc.
|
||||
- Types in `@modrinth/utils` are considered highly outdated, if a component needs them, check if you can switch said component to use types from `packages/api-client`
|
||||
- When provided problems, do not say "I didn't introduce these problems" (shifting the blame/effort) - just fix them.
|
||||
|
||||
## Standards
|
||||
|
||||
Standards available at the @standards/ folder.
|
||||
@@ -1,110 +0,0 @@
|
||||
# Modrinth Monorepo
|
||||
|
||||
This is the Modrinth monorepo — it contains all Modrinth projects, both frontend and backend. When entering a project, either to edit or analyse, you should read it's CLAUDE.md.
|
||||
|
||||
## Architecture
|
||||
|
||||
- **Monorepo tooling:** [Turborepo](https://turbo.build/) (`turbo.jsonc`) + [pnpm workspaces](https://pnpm.io/workspaces) (`pnpm-workspace.yaml`)
|
||||
- **Frontend:** Vue 3 / Nuxt 3, Tailwind CSS v3
|
||||
- **Backend:** Rust (Labrinth API), Postgres, Clickhouse
|
||||
- **Indentation:** Use TAB everywhere, never spaces
|
||||
|
||||
### Apps (`apps/`)
|
||||
|
||||
| App | Description |
|
||||
| ----------------- | ------------------------------ |
|
||||
| `frontend` | Main Modrinth website (Nuxt 3) |
|
||||
| `app-frontend` | Desktop/app frontend (Vue 3) |
|
||||
| `app` | Desktop/app shell (Tauri) |
|
||||
| `app-playground` | Testing playground for app |
|
||||
| `labrinth` | Backend API service |
|
||||
| `daedalus_client` | Daedalus client implementation |
|
||||
| `docs` | Documentation site (Astro) |
|
||||
|
||||
### Packages (`packages/`)
|
||||
|
||||
| Package | Description |
|
||||
| ------------------ | ----------------------------------------------------- |
|
||||
| `ui` | Shared Vue component library (`@modrinth/ui`) |
|
||||
| `assets` | Styling and auto-generated icons (`@modrinth/assets`) |
|
||||
| `api-client` | API client for Nuxt, Tauri, and Node/browser |
|
||||
| `app-lib` | Shared app library |
|
||||
| `blog` | Blog system and changelog data |
|
||||
| `utils` | Shared utility functions (mostly deprecated) |
|
||||
| `moderation` | Moderation utilities |
|
||||
| `daedalus` | Daedalus protocol |
|
||||
| `tooling-config` | ESLint, Prettier, TypeScript configs |
|
||||
| `ariadne` | Analytics library |
|
||||
| `modrinth-log` | Logging utilities |
|
||||
| `modrinth-maxmind` | MaxMind GeoIP |
|
||||
| `modrinth-util` | General utilities |
|
||||
| `muralpay` | Payment processing |
|
||||
| `path-util` | Path utilities |
|
||||
| `sqlx-tracing` | SQLx query tracing |
|
||||
|
||||
## Pre-PR Commands
|
||||
|
||||
Run these from the **root** folder before opening a pull request - do not run these after each prompt the user gives you, only run when asked, ask the user a question if they want to run it if the user indicates that they are about to create a pull request.
|
||||
|
||||
- **Website:** `pnpm prepr:frontend:web`
|
||||
- **App frontend:** `pnpm prepr:frontend:app`
|
||||
- **Frontend libs:** `pnpm prepr:frontend:lib`
|
||||
- **All frontend (app+web):** `pnpm prepr`
|
||||
- **Labrinth (backend):** See `apps/labrinth/AGENTS.md`
|
||||
|
||||
The website and app `prepr` commands
|
||||
|
||||
## Dev Commands
|
||||
|
||||
- **Website:** `pnpm web:dev` (copy `.env` template in `apps/frontend/` first)
|
||||
- **App:** `pnpm app:dev` (copy `.env` template in `packages/app-lib/` first)
|
||||
- **Storybook (packages/ui):** `pnpm storybook`
|
||||
|
||||
## Project-Specific Instructions
|
||||
|
||||
Each project may have its own file with detailed instructions:
|
||||
|
||||
- [`apps/labrinth/AGENTS.md`](apps/labrinth/AGENTS.md) — Backend API
|
||||
- [`apps/frontend/CLAUDE.md`](apps/frontend/CLAUDE.md) - Frontend Website
|
||||
|
||||
## Code Guidelines
|
||||
|
||||
### Comments
|
||||
- DO NOT use "heading" comments like: `=== Helper methods ===`.
|
||||
- Use doc comments, but avoid inline comments unless ABSOLUTELY necessary for clarity. Code should aim to be self documenting!
|
||||
|
||||
## Bash Guidelines
|
||||
|
||||
### Output handling
|
||||
- DO NOT pipe output through `head`, `tail`, `less`, or `more`
|
||||
- NEVER use `| head -n X` or `| tail -n X` to truncate output
|
||||
- IMPORTANT: Run commands directly without pipes when possible
|
||||
- IMPORTANT: If you need to limit output, use command-specific flags (e.g. `git log -n 10` instead of `git log | head -10`)
|
||||
- ALWAYS read the full output — never pipe through filters
|
||||
|
||||
### General
|
||||
- Do not create new non-source code files (e.g. Bash scripts, SQL scripts) unless explicitly prompted to
|
||||
- For Frontend, when doing lint checks, only use the `prepr` commands, do not use `typecheck` or `tsc` etc.
|
||||
- Types in `@modrinth/utils` are considered highly outdated, if a component needs them, check if you can switch said component to use types from `packages/api-client`
|
||||
- When provided problems, do not say "I didn't introduce these problems" (shifting the blame/effort) - just fix them.
|
||||
|
||||
## Edit Tool - Whitespace Handling (CLAUDE ONLY)
|
||||
|
||||
The Read tool uses `→` to mark where line numbers end and file content begins.
|
||||
|
||||
**Rule:** Copy the EXACT whitespace that appears after the `→` marker.
|
||||
- Whatever appears between `→` and the code text is what's actually in the file
|
||||
- That whitespace must be used EXACTLY in Edit tool's old_string
|
||||
- Don't count arrows, don't interpret - just copy what's after the `→`
|
||||
|
||||
**Example:**
|
||||
14→ private byte tag;
|
||||
For Edit, use: ` private byte tag;` (copy everything after →, including the two tabs)
|
||||
|
||||
**If Edit fails:** Stop and explain the problem. Do not attempt sed/awk/bash workarounds.
|
||||
|
||||
**IMPORTANT**: Trust the Read tool output. Copy what's after `→` into Edit immediately. DO NOT verify with sed/od/grep first - that's wasting time and the instructions already tell you to stop if Edit fails, not to pre-verify.
|
||||
|
||||
## Standards
|
||||
|
||||
Standards available at the @standards/ folder.
|
||||
Generated
+190
-2
@@ -2116,7 +2116,7 @@ checksum = "ff6669899e23cb87b43daf7996f0ea3b9c07d0fb933d745bb7b815b052515ae3"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"serde_derive_internals",
|
||||
"serde_derive_internals 0.29.1",
|
||||
"syn 2.0.106",
|
||||
]
|
||||
|
||||
@@ -2340,6 +2340,15 @@ dependencies = [
|
||||
"unicode-segmentation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "convert_case"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "affbf0190ed2caf063e3def54ff444b449371d55c58e513a95ab98eca50adb49"
|
||||
dependencies = [
|
||||
"unicode-segmentation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cookie"
|
||||
version = "0.16.2"
|
||||
@@ -3991,6 +4000,28 @@ dependencies = [
|
||||
"x11",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "genco"
|
||||
version = "0.19.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77ab846431e5d637791b3279e7242fe2b21e11c3d8b4cf6a99f645c5f16ba7c0"
|
||||
dependencies = [
|
||||
"genco-macros",
|
||||
"relative-path",
|
||||
"smallvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "genco-macros"
|
||||
version = "0.19.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c42a1fe5a699c7f1d36ea6e04ed680a5c787cabff4b610ae3b8954ea3bcefec1"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.106",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "generic-array"
|
||||
version = "0.14.9"
|
||||
@@ -5347,6 +5378,16 @@ dependencies = [
|
||||
"thiserror 1.0.69",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "json5"
|
||||
version = "1.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "733a844dbd6fef128e98cb4487b887cb55454d92cd9994b1bafe004fabbe670c"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"ucd-trie",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jsonptr"
|
||||
version = "0.6.3"
|
||||
@@ -7499,6 +7540,45 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "postcard-bindgen"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6e709af61573ae2563a00e760ff7db47f5c70b9d96c6dcb28993526fb8479f68"
|
||||
dependencies = [
|
||||
"postcard-bindgen-core",
|
||||
"postcard-bindgen-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "postcard-bindgen-core"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6f19cebcb145fdccb24b63ad410785aa7c2ff9364551646ca7a927422d2d0386"
|
||||
dependencies = [
|
||||
"convert_case 0.11.0",
|
||||
"genco",
|
||||
"tree-ds",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "postcard-bindgen-derive"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0372f55dd7c15d73f7b0f7ca507ac3db5653f346b2b44a36537e2ad39f55441b"
|
||||
dependencies = [
|
||||
"convert_case 0.11.0",
|
||||
"genco",
|
||||
"postcard-bindgen-core",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"regex",
|
||||
"regex-macro",
|
||||
"serde",
|
||||
"serde_derive_internals 0.30.0",
|
||||
"syn 3.0.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "potential_utf"
|
||||
version = "0.1.3"
|
||||
@@ -8381,12 +8461,27 @@ version = "0.1.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8d942b98df5e658f56f20d592c7f868833fe38115e65c33003d8cd224b0155da"
|
||||
|
||||
[[package]]
|
||||
name = "regex-macro"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7d306632607af6ec61c0b117971d57a96381b6317cf18ae419b5558048fe016e"
|
||||
dependencies = [
|
||||
"regex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.8.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58"
|
||||
|
||||
[[package]]
|
||||
name = "relative-path"
|
||||
version = "1.9.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ba39f3699c378cd8970968dcbff9c43159ea4cfbd88d43c00b22f2ef10a435d2"
|
||||
|
||||
[[package]]
|
||||
name = "rend"
|
||||
version = "0.4.2"
|
||||
@@ -9052,7 +9147,7 @@ checksum = "32e265784ad618884abaea0600a9adf15393368d840e0222d101a072f3f7534d"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"serde_derive_internals",
|
||||
"serde_derive_internals 0.29.1",
|
||||
"syn 2.0.106",
|
||||
]
|
||||
|
||||
@@ -9295,6 +9390,16 @@ dependencies = [
|
||||
"uuid 1.23.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sequential_gen"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3d630b1418311e028ceb45e1e567845501a9d9c770a258b80d2edc025ffad17c"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"uuid 1.23.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.228"
|
||||
@@ -9400,6 +9505,17 @@ dependencies = [
|
||||
"syn 2.0.106",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive_internals"
|
||||
version = "0.30.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f852137cce035d6a4df67ccce505ff6b3e9fd3a10e3e52b24dc71e650bb1a9bd"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 3.0.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_ini"
|
||||
version = "0.2.0"
|
||||
@@ -9840,6 +9956,9 @@ name = "spin"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d5fe4ccb98d9c292d56fec89a5e07da7fc4cf0dc11e156b41793132775d3e591"
|
||||
dependencies = [
|
||||
"lock_api",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "spinning_top"
|
||||
@@ -10267,6 +10386,17 @@ dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "3.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sync_wrapper"
|
||||
version = "1.0.2"
|
||||
@@ -10880,6 +11010,15 @@ dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "termcolor"
|
||||
version = "1.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755"
|
||||
dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "testcontainers"
|
||||
version = "0.25.2"
|
||||
@@ -10959,6 +11098,7 @@ dependencies = [
|
||||
"image",
|
||||
"indicatif",
|
||||
"itertools 0.14.0",
|
||||
"json5",
|
||||
"modrinth-content-management",
|
||||
"notify",
|
||||
"notify-debouncer-mini",
|
||||
@@ -10968,6 +11108,8 @@ dependencies = [
|
||||
"path-util",
|
||||
"phf 0.13.1",
|
||||
"png 0.18.0",
|
||||
"postcard",
|
||||
"postcard-bindgen",
|
||||
"quartz_nbt",
|
||||
"quick-xml 0.38.3",
|
||||
"rand 0.8.5",
|
||||
@@ -10975,6 +11117,7 @@ dependencies = [
|
||||
"reqwest 0.12.24",
|
||||
"rgb",
|
||||
"serde",
|
||||
"serde-binhum",
|
||||
"serde_ini",
|
||||
"serde_json",
|
||||
"serde_with",
|
||||
@@ -10988,9 +11131,11 @@ dependencies = [
|
||||
"thiserror 2.0.17",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
"toml 0.9.8",
|
||||
"tracing",
|
||||
"tracing-error",
|
||||
"tracing-subscriber",
|
||||
"ts-rs",
|
||||
"url",
|
||||
"urlencoding",
|
||||
"uuid 1.23.3",
|
||||
@@ -11667,12 +11812,49 @@ dependencies = [
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tree-ds"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "710c388a2bb52d1a7186ae4aa2e92c6fae0606426bf4bdfb4106fa1e8e5c602e"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"sequential_gen",
|
||||
"serde",
|
||||
"spin 0.10.0",
|
||||
"thiserror 2.0.17",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "try-lock"
|
||||
version = "0.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
|
||||
|
||||
[[package]]
|
||||
name = "ts-rs"
|
||||
version = "12.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "756050066659291d47a554a9f558125db17428b073c5ffce1daf5dcb0f7231d8"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"thiserror 2.0.17",
|
||||
"ts-rs-macros",
|
||||
"uuid 1.23.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ts-rs-macros"
|
||||
version = "12.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "38d90eea51bc7988ef9e674bf80a85ba6804739e535e9cab48e4bb34a8b652aa"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.106",
|
||||
"termcolor",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tungstenite"
|
||||
version = "0.27.0"
|
||||
@@ -11710,6 +11892,12 @@ version = "1.20.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20"
|
||||
|
||||
[[package]]
|
||||
name = "ucd-trie"
|
||||
version = "0.1.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971"
|
||||
|
||||
[[package]]
|
||||
name = "uds_windows"
|
||||
version = "1.1.0"
|
||||
|
||||
@@ -113,6 +113,7 @@ indicatif = "0.18.0"
|
||||
itertools = "0.14.0"
|
||||
jemalloc_pprof = "0.8.1"
|
||||
json-patch = { version = "4.1.0", default-features = false }
|
||||
json5 = "1.3.1"
|
||||
lettre = { version = "0.11.19", default-features = false, features = [
|
||||
"aws-lc-rs",
|
||||
"builder",
|
||||
@@ -148,6 +149,7 @@ path-util = { path = "packages/path-util" }
|
||||
phf = { version = "0.13.1", features = ["macros"] }
|
||||
png = "0.18.0"
|
||||
postcard = { version = "1.1.3", default-features = false, features = ["alloc"] }
|
||||
postcard-bindgen = "0.8.0"
|
||||
proc-macro2 = { version = "1.0" }
|
||||
prometheus = "0.14.0"
|
||||
quartz_nbt = "0.2.9"
|
||||
@@ -224,12 +226,14 @@ tikv-jemallocator = "0.6.0"
|
||||
tokio = "1.47.1"
|
||||
tokio-stream = "0.1.17"
|
||||
tokio-util = "0.7.16"
|
||||
toml = "0.9.8"
|
||||
totp-rs = "5.7.0"
|
||||
tracing = "0.1.41"
|
||||
tracing-actix-web = { version = "0.7.19", default-features = false }
|
||||
tracing-ecs = "0.5.0"
|
||||
tracing-error = "0.2.1"
|
||||
tracing-subscriber = "0.3.20"
|
||||
ts-rs = "12.0.1"
|
||||
typed-path = "0.12.0"
|
||||
url = "2.5.7"
|
||||
urlencoding = "2.1.3"
|
||||
|
||||
@@ -24,3 +24,7 @@ gam = "gam"
|
||||
consts = "consts"
|
||||
# short for "Copy"
|
||||
Cpy = "Cpy"
|
||||
|
||||
[default.extend-identifiers]
|
||||
# Constant from the `zip` crate
|
||||
ZIP64_BYTES_THR = "ZIP64_BYTES_THR"
|
||||
|
||||
@@ -2,3 +2,7 @@
|
||||
*.gltf
|
||||
src/locales/
|
||||
src/assets/**/*.svg
|
||||
|
||||
# Generated app-event bindings
|
||||
src/generated/app-events/*.ts
|
||||
src/generated/app-events/postcard/**
|
||||
|
||||
@@ -1,2 +1,7 @@
|
||||
import config from '@modrinth/tooling-config/eslint/nuxt.mjs'
|
||||
export default config
|
||||
|
||||
export default config.append([
|
||||
{
|
||||
ignores: ['src/generated/app-events/*.ts', 'src/generated/app-events/postcard/**'],
|
||||
},
|
||||
])
|
||||
|
||||
@@ -71,6 +71,7 @@ import AppActionBar from '@/components/ui/AppActionBar.vue'
|
||||
import Breadcrumbs from '@/components/ui/Breadcrumbs.vue'
|
||||
import ErrorModal from '@/components/ui/ErrorModal.vue'
|
||||
import FriendsList from '@/components/ui/friends/FriendsList.vue'
|
||||
import HostingUpdateRequired from '@/components/ui/HostingUpdateRequired.vue'
|
||||
import AddServerToInstanceModal from '@/components/ui/install_flow/AddServerToInstanceModal.vue'
|
||||
import UnknownPackWarningModal from '@/components/ui/install_flow/UnknownPackWarningModal.vue'
|
||||
import MinecraftAuthErrorModal from '@/components/ui/minecraft-auth-error-modal/MinecraftAuthErrorModal.vue'
|
||||
@@ -89,19 +90,19 @@ import SplashScreen from '@/components/ui/SplashScreen.vue'
|
||||
import SurveyPopup from '@/components/ui/SurveyPopup.vue'
|
||||
import WindowControls from '@/components/ui/WindowControls.vue'
|
||||
import { useCheckDisableMouseover } from '@/composables/macCssFix.js'
|
||||
import { useAppEvent } from '@/composables/use-app-event'
|
||||
import { config } from '@/config'
|
||||
import {
|
||||
ads_consent_listener,
|
||||
hide_ads_window,
|
||||
init_ads_window,
|
||||
perform_ads_consent_action,
|
||||
release_ads_window_hold,
|
||||
should_show_ads_consent_popup,
|
||||
show_ads_window,
|
||||
take_ads_window_hold,
|
||||
} from '@/helpers/ads.js'
|
||||
import { debugAnalytics, initAnalytics, trackEvent } from '@/helpers/analytics'
|
||||
import { check_reachable } from '@/helpers/auth.js'
|
||||
import { get_user, get_version } from '@/helpers/cache.js'
|
||||
import { command_listener, notification_listener, warning_listener } from '@/helpers/events.js'
|
||||
import { install_create_modpack_instance, install_get_modpack_preview } from '@/helpers/install'
|
||||
import { can_current_user_use_shared_instances, get as getInstance, run } from '@/helpers/instance'
|
||||
import { get as getCreds, login, logout } from '@/helpers/mr_auth.ts'
|
||||
@@ -140,6 +141,7 @@ import {
|
||||
} from '@/providers/download-progress.ts'
|
||||
import { createServerInstall, provideServerInstall } from '@/providers/server-install'
|
||||
import { setupProviders } from '@/providers/setup'
|
||||
import { setupAppEventsProvider } from '@/providers/setup/app-events'
|
||||
import { setupAuthProvider } from '@/providers/setup/auth'
|
||||
import { setupLoadingStateProvider } from '@/providers/setup/loading-state'
|
||||
import { useError } from '@/store/error.js'
|
||||
@@ -155,6 +157,7 @@ import { appSettingsModalOpenProfileKey } from './providers/app-settings-modal'
|
||||
const themeStore = useTheming()
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
const { channel: appEventChannel, events: appEvents } = setupAppEventsProvider()
|
||||
const breadcrumbManager = createBreadcrumbManager()
|
||||
provideBreadcrumbManager(breadcrumbManager)
|
||||
const canNavigateBack = ref(false)
|
||||
@@ -166,6 +169,25 @@ function updateHistoryNavigationState() {
|
||||
canNavigateForward.value = historyState?.forward != null
|
||||
}
|
||||
|
||||
let fullscreenAdsWindowHold = false
|
||||
|
||||
async function handleFullscreenChange() {
|
||||
const fullscreen = document.fullscreenElement !== null
|
||||
if (fullscreen === fullscreenAdsWindowHold) return
|
||||
|
||||
fullscreenAdsWindowHold = fullscreen
|
||||
try {
|
||||
if (fullscreen) {
|
||||
await take_ads_window_hold()
|
||||
} else {
|
||||
await release_ads_window_hold()
|
||||
}
|
||||
} catch (error) {
|
||||
fullscreenAdsWindowHold = !fullscreen
|
||||
handleError(error)
|
||||
}
|
||||
}
|
||||
|
||||
updateHistoryNavigationState()
|
||||
|
||||
const APP_LEFT_NAV_WIDTH = '4rem'
|
||||
@@ -175,14 +197,26 @@ const PRIDE_FUNDRAISER_END_DATE = new Date('2026-07-01T00:00:00Z').getTime()
|
||||
const credentials = ref()
|
||||
let credentialsRefreshId = 0
|
||||
const sidebarToggled = ref(true)
|
||||
const unsubscribeSidebarToggle = themeStore.$subscribe(() => {
|
||||
sidebarToggled.value = !themeStore.toggleSidebar
|
||||
})
|
||||
watch(
|
||||
() => themeStore.toggleSidebar,
|
||||
(toggleSidebar) => {
|
||||
sidebarToggled.value = !toggleSidebar
|
||||
},
|
||||
)
|
||||
const forceSidebar = computed(
|
||||
() => route.path.startsWith('/browse') || route.path.startsWith('/project'),
|
||||
() =>
|
||||
route.path.startsWith('/browse') ||
|
||||
route.path.startsWith('/project') ||
|
||||
route.path.startsWith('/user'),
|
||||
)
|
||||
const sidebarVisible = computed(() => sidebarToggled.value || forceSidebar.value)
|
||||
const hostingRouteActive = computed(() => route.path.startsWith('/hosting'))
|
||||
const hostingUpdateRequired = computed(
|
||||
() =>
|
||||
hostingRouteActive.value &&
|
||||
!!appUpdateState.availableUpdate.value &&
|
||||
appUpdateState.updatesEnabled.value,
|
||||
)
|
||||
const prideFundraiserEnabled = computed(
|
||||
() => themeStore.getFeatureFlag('pride_fundraiser') && Date.now() < PRIDE_FUNDRAISER_END_DATE,
|
||||
)
|
||||
@@ -193,7 +227,9 @@ const hostingIntercomIdentityKey = computed(() => {
|
||||
return `${userId}:${serverId ?? 'hosting'}`
|
||||
})
|
||||
const hostingIntercom = useHostingIntercom({
|
||||
enabled: computed(() => hostingRouteActive.value && !!credentials.value?.session),
|
||||
enabled: computed(
|
||||
() => hostingRouteActive.value && !hostingUpdateRequired.value && !!credentials.value?.session,
|
||||
),
|
||||
appId: 'ykeritl9',
|
||||
fetchToken: fetchIntercomToken,
|
||||
identityKey: hostingIntercomIdentityKey,
|
||||
@@ -208,11 +244,22 @@ const notificationManager = new AppNotificationManager()
|
||||
provideNotificationManager(notificationManager)
|
||||
const { handleError, addNotification } = notificationManager
|
||||
|
||||
useAppEvent(
|
||||
'warning',
|
||||
(event) =>
|
||||
addNotification({
|
||||
title: 'Warning',
|
||||
text: event.message,
|
||||
type: 'warning',
|
||||
}),
|
||||
appEvents,
|
||||
)
|
||||
|
||||
const popupNotificationManager = new AppPopupNotificationManager()
|
||||
providePopupNotificationManager(popupNotificationManager)
|
||||
const { addPopupNotification } = popupNotificationManager
|
||||
let adsConsentPopupId = null
|
||||
let unlistenAdsConsent
|
||||
useAppEvent('ads_consent_required', handleAdsConsentRequired, appEvents)
|
||||
|
||||
const appVersion = getVersion()
|
||||
const tauriApiClient = new TauriModrinthClient({
|
||||
@@ -281,8 +328,8 @@ providePageContext({
|
||||
})
|
||||
provideModalBehavior({
|
||||
noblur: computed(() => !themeStore.advancedRendering),
|
||||
onShow: () => hide_ads_window(),
|
||||
onHide: () => show_ads_window(),
|
||||
onShow: () => take_ads_window_hold(),
|
||||
onHide: () => release_ads_window_hold(),
|
||||
})
|
||||
|
||||
const {
|
||||
@@ -297,7 +344,7 @@ const {
|
||||
setModpackAlreadyInstalledModal,
|
||||
handleModpackDuplicateCreateAnyway,
|
||||
handleModpackDuplicateGoToInstance,
|
||||
} = setupProviders(notificationManager, popupNotificationManager)
|
||||
} = setupProviders(tauriApiClient, notificationManager, popupNotificationManager)
|
||||
|
||||
const news = ref([])
|
||||
const displayedServerInviteNotifications = new Set()
|
||||
@@ -349,7 +396,6 @@ const authUnreachable = computed(() => {
|
||||
onMounted(async () => {
|
||||
await useCheckDisableMouseover()
|
||||
try {
|
||||
unlistenAdsConsent = await ads_consent_listener(handleAdsConsentRequired)
|
||||
handleAdsConsentRequired(await should_show_ads_consent_popup())
|
||||
} catch (error) {
|
||||
handleError(error)
|
||||
@@ -357,6 +403,7 @@ onMounted(async () => {
|
||||
|
||||
document.querySelector('body').addEventListener('click', handleClick)
|
||||
document.querySelector('body').addEventListener('auxclick', handleAuxClick)
|
||||
document.addEventListener('fullscreenchange', handleFullscreenChange)
|
||||
|
||||
checkUpdates()
|
||||
})
|
||||
@@ -364,10 +411,13 @@ onMounted(async () => {
|
||||
onUnmounted(async () => {
|
||||
document.querySelector('body').removeEventListener('click', handleClick)
|
||||
document.querySelector('body').removeEventListener('auxclick', handleAuxClick)
|
||||
unsubscribeSidebarToggle()
|
||||
document.removeEventListener('fullscreenchange', handleFullscreenChange)
|
||||
clearDelayedUpdatePopup()
|
||||
|
||||
await unlistenAdsConsent?.()
|
||||
if (fullscreenAdsWindowHold) {
|
||||
fullscreenAdsWindowHold = false
|
||||
await release_ads_window_hold().catch(handleError)
|
||||
}
|
||||
await unlistenUpdateDownload?.()
|
||||
})
|
||||
|
||||
@@ -576,14 +626,6 @@ async function setupApp() {
|
||||
document.getElementsByTagName('html')[0].classList.add('windows')
|
||||
}
|
||||
|
||||
await warning_listener((e) =>
|
||||
addNotification({
|
||||
title: 'Warning',
|
||||
text: e.message,
|
||||
type: 'warning',
|
||||
}),
|
||||
)
|
||||
|
||||
fetch(`https://api.modrinth.com/appCriticalAnnouncement.json?version=${version}`)
|
||||
.then((response) => response.json())
|
||||
.then((res) => {
|
||||
@@ -632,7 +674,7 @@ async function setupApp() {
|
||||
}
|
||||
|
||||
const stateFailed = ref(false)
|
||||
initialize_state()
|
||||
initialize_state(appEventChannel)
|
||||
.then(() => {
|
||||
setupApp().catch((err) => {
|
||||
stateFailed.value = true
|
||||
@@ -762,7 +804,7 @@ const errorModal = ref()
|
||||
const minecraftAuthErrorModal = ref()
|
||||
const minecraftRequiredModal = ref()
|
||||
|
||||
const contentInstall = createContentInstall({ router, handleError })
|
||||
const contentInstall = createContentInstall({ router, handleError, appEvents })
|
||||
provideContentInstall(contentInstall)
|
||||
const {
|
||||
instances: contentInstallInstances,
|
||||
@@ -795,7 +837,12 @@ const {
|
||||
handleIncompatibilityWarningCancel: handleContentInstallIncompatibilityWarningCancel,
|
||||
} = contentInstall
|
||||
|
||||
const serverInstall = createServerInstall({ router, handleError, popupNotificationManager })
|
||||
const serverInstall = createServerInstall({
|
||||
router,
|
||||
handleError,
|
||||
popupNotificationManager,
|
||||
appEvents,
|
||||
})
|
||||
provideServerInstall(serverInstall)
|
||||
const {
|
||||
setInstallToPlayModal: setServerInstallToPlayModal,
|
||||
@@ -971,8 +1018,8 @@ onMounted(() => {
|
||||
const accounts = ref(null)
|
||||
provide('accountsCard', accounts)
|
||||
|
||||
command_listener(handleCommand)
|
||||
notification_listener(handleLiveNotification)
|
||||
useAppEvent('command', handleCommand, appEvents)
|
||||
useAppEvent('notification', handleLiveNotification, appEvents)
|
||||
|
||||
async function markLiveNotificationRead(notification) {
|
||||
try {
|
||||
@@ -1398,6 +1445,7 @@ async function downloadUpdate(versionToDownload) {
|
||||
handleError(e)
|
||||
})
|
||||
unlistenUpdateDownload = await subscribeToDownloadProgress(
|
||||
appEvents,
|
||||
appUpdateDownload,
|
||||
versionToDownload.version,
|
||||
)
|
||||
@@ -1756,7 +1804,8 @@ provideAppUpdateDownloadProgress(appUpdateDownload)
|
||||
>
|
||||
{{ formatMessage(messages.authUnreachableBody) }}
|
||||
</Admonition>
|
||||
<RouterView v-slot="{ Component }">
|
||||
<HostingUpdateRequired v-if="hostingUpdateRequired" />
|
||||
<RouterView v-else v-slot="{ Component }">
|
||||
<template v-if="Component">
|
||||
<Suspense @pending="onSuspensePending" @resolve="onSuspenseResolve">
|
||||
<component :is="Component"></component>
|
||||
|
||||
@@ -104,8 +104,9 @@ import {
|
||||
useVIntl,
|
||||
} from '@modrinth/ui'
|
||||
import type { Ref } from 'vue'
|
||||
import { computed, onUnmounted, ref } from 'vue'
|
||||
import { computed, ref } from 'vue'
|
||||
|
||||
import { useAppEvent } from '@/composables/use-app-event'
|
||||
import { trackEvent } from '@/helpers/analytics'
|
||||
import {
|
||||
get_default_user,
|
||||
@@ -114,7 +115,6 @@ import {
|
||||
set_default_user,
|
||||
users,
|
||||
} from '@/helpers/auth'
|
||||
import { process_listener } from '@/helpers/events'
|
||||
import { getPlayerHeadUrl } from '@/helpers/rendering/batch-skin-renderer.ts'
|
||||
import type { Skin } from '@/helpers/skins'
|
||||
import { get_available_skins } from '@/helpers/skins'
|
||||
@@ -251,16 +251,12 @@ async function logout(id: string) {
|
||||
trackEvent('AccountLogOut')
|
||||
}
|
||||
|
||||
const unlisten = await process_listener(async (e) => {
|
||||
useAppEvent('process', async (e) => {
|
||||
if (e.event === 'launched') {
|
||||
await refreshValues()
|
||||
}
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
unlisten()
|
||||
})
|
||||
|
||||
const messages = defineMessages({
|
||||
notSignedIn: {
|
||||
id: 'minecraft-account.not-signed-in',
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
<template>
|
||||
<div class="flex gap-2 items-center">
|
||||
<IconButton
|
||||
v-if="hasActiveLoadingBars && !hasVisibleActiveDownloadToasts"
|
||||
v-tooltip="formatMessage(messages.viewActiveDownloads)"
|
||||
type="quiet"
|
||||
color="brand"
|
||||
:label="formatMessage(messages.viewActiveDownloads)"
|
||||
@click="openDownloadToast()"
|
||||
>
|
||||
<DownloadIcon />
|
||||
</IconButton>
|
||||
<div v-if="downloadState.total > 0 || hasActiveLoadingBars" class="relative">
|
||||
<IconButton
|
||||
v-tooltip="downloadToggleLabel"
|
||||
:color="downloadState.hidden > 0 ? 'brand' : undefined"
|
||||
type="quiet"
|
||||
:label="downloadToggleLabel"
|
||||
@click="toggleDownloadNotifications"
|
||||
>
|
||||
<DownloadIcon />
|
||||
</IconButton>
|
||||
</div>
|
||||
<div v-if="offline" class="flex items-center gap-1">
|
||||
<UnplugIcon class="text-secondary" />
|
||||
<span class="text-sm text-contrast"> {{ formatMessage(messages.offline) }} </span>
|
||||
@@ -147,8 +148,8 @@ import { useRouter } from 'vue-router'
|
||||
|
||||
import AppUpdateButton from '@/components/ui/app-update-button/index.vue'
|
||||
import { useInstallJobNotifications } from '@/composables/browse/install-job-notifications'
|
||||
import { useAppEvent } from '@/composables/use-app-event'
|
||||
import { trackEvent } from '@/helpers/analytics'
|
||||
import { loading_listener, process_listener } from '@/helpers/events'
|
||||
import { get_many as getInstances } from '@/helpers/instance'
|
||||
import { get_all as getRunningProcesses, kill as killProcess } from '@/helpers/process'
|
||||
import type { LoadingBar } from '@/helpers/state'
|
||||
@@ -218,8 +219,35 @@ const messages = defineMessages({
|
||||
id: 'app.action-bar.view-active-downloads',
|
||||
defaultMessage: 'View active downloads',
|
||||
},
|
||||
hideDownloads: {
|
||||
id: 'app.action-bar.hide-downloads',
|
||||
defaultMessage: 'Hide active downloads',
|
||||
},
|
||||
showDownloads: {
|
||||
id: 'app.action-bar.show-downloads',
|
||||
defaultMessage: 'Show active downloads',
|
||||
},
|
||||
})
|
||||
|
||||
const downloadState = computed(() => popupNotificationManager.getDownloadState())
|
||||
const downloadToggleLabel = computed(() =>
|
||||
formatMessage(
|
||||
downloadState.value.hidden > 0
|
||||
? messages.showDownloads
|
||||
: downloadState.value.total > 0
|
||||
? messages.hideDownloads
|
||||
: messages.viewActiveDownloads,
|
||||
),
|
||||
)
|
||||
|
||||
function toggleDownloadNotifications(): void {
|
||||
if (downloadState.value.total > 0) {
|
||||
popupNotificationManager.toggleDownloadNotifications()
|
||||
} else if (hasActiveLoadingBars.value) {
|
||||
openDownloadToast()
|
||||
}
|
||||
}
|
||||
|
||||
const currentProcesses = ref<RunningProcess[]>([])
|
||||
const selectedProcess = ref<RunningProcess | undefined>()
|
||||
|
||||
@@ -266,7 +294,7 @@ onMounted(() => {
|
||||
window.addEventListener('online', handleOnline)
|
||||
})
|
||||
|
||||
const unlistenProcess = await process_listener(async () => {
|
||||
useAppEvent('process', async () => {
|
||||
await refresh()
|
||||
})
|
||||
|
||||
@@ -296,6 +324,7 @@ function goToTerminal(instanceId?: string) {
|
||||
const currentLoadingBars = ref<LoadingBar[]>([])
|
||||
const currentLoadingBarIconUrls = ref<Record<string, string | null>>({})
|
||||
const notificationId = ref<string | number | null>(null)
|
||||
const terminalNotificationIds = new Map<string, string | number>()
|
||||
const dismissed = ref(false)
|
||||
|
||||
function getLoadingBarKey(loadingBar: LoadingBar): string {
|
||||
@@ -341,6 +370,44 @@ function removeNotification(): void {
|
||||
notificationId.value = null
|
||||
}
|
||||
|
||||
function syncTerminalNotifications(): void {
|
||||
const terminalNotifications = installJobNotifications.terminalNotifications.value
|
||||
const currentJobIds = new Set(terminalNotifications.map((notification) => notification.id))
|
||||
|
||||
for (const terminal of terminalNotifications) {
|
||||
const popupId = terminalNotificationIds.get(terminal.id)
|
||||
let notification = popupId
|
||||
? popupNotificationManager.getNotifications().find((candidate) => candidate.id === popupId)
|
||||
: undefined
|
||||
|
||||
if (!notification) {
|
||||
notification = popupNotificationManager.addPopupNotification({
|
||||
title: terminal.title,
|
||||
text: terminal.text,
|
||||
type: terminal.type,
|
||||
buttons: terminal.buttons,
|
||||
onDismiss: terminal.onDismiss,
|
||||
autoCloseMs: null,
|
||||
})
|
||||
terminalNotificationIds.set(terminal.id, notification.id)
|
||||
continue
|
||||
}
|
||||
|
||||
notification.title = terminal.title
|
||||
notification.text = terminal.text
|
||||
notification.type = terminal.type
|
||||
notification.buttons = terminal.buttons
|
||||
notification.onDismiss = terminal.onDismiss
|
||||
}
|
||||
|
||||
for (const [jobId, popupId] of terminalNotificationIds) {
|
||||
if (!currentJobIds.has(jobId)) {
|
||||
popupNotificationManager.removeNotification(popupId)
|
||||
terminalNotificationIds.delete(jobId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function buildDownloadItems(): PopupNotificationProgressItem[] {
|
||||
return [
|
||||
...installJobNotifications.progressItems.value,
|
||||
@@ -358,12 +425,13 @@ function buildDownloadItems(): PopupNotificationProgressItem[] {
|
||||
]
|
||||
}
|
||||
|
||||
const hasVisibleActiveDownloadToasts = computed(() => !!getNotification())
|
||||
const hasActiveLoadingBars = computed(
|
||||
() => currentLoadingBars.value.length > 0 || installJobNotifications.active.value,
|
||||
)
|
||||
|
||||
function updateNotification(resummon = false): void {
|
||||
syncTerminalNotifications()
|
||||
|
||||
if (resummon) {
|
||||
dismissed.value = false
|
||||
}
|
||||
@@ -392,7 +460,6 @@ function updateNotification(resummon = false): void {
|
||||
: formatMessage(messages.downloads)
|
||||
notif.text = undefined
|
||||
notif.progressItems = progressItems
|
||||
notif.buttons = installJobNotifications.buttons.value
|
||||
notif.progress = undefined
|
||||
notif.waiting = undefined
|
||||
} else {
|
||||
@@ -403,7 +470,6 @@ function updateNotification(resummon = false): void {
|
||||
type: 'download',
|
||||
autoCloseMs: null,
|
||||
progressItems,
|
||||
buttons: installJobNotifications.buttons.value,
|
||||
})
|
||||
notificationId.value = notif.id
|
||||
}
|
||||
@@ -488,7 +554,7 @@ const installJobNotifications = await useInstallJobNotifications({
|
||||
|
||||
await refreshLoadingBars()
|
||||
|
||||
const unlistenLoading = await loading_listener(async () => {
|
||||
useAppEvent('loading', async () => {
|
||||
await refreshLoadingBars()
|
||||
})
|
||||
|
||||
@@ -502,11 +568,11 @@ function selectProcess(process: RunningProcess) {
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
removeNotification()
|
||||
terminalNotificationIds.forEach((id) => popupNotificationManager.removeNotification(id))
|
||||
terminalNotificationIds.clear()
|
||||
dismissed.value = false
|
||||
window.removeEventListener('offline', handleOffline)
|
||||
window.removeEventListener('online', handleOnline)
|
||||
unlistenProcess()
|
||||
unlistenLoading()
|
||||
installJobNotifications.dispose()
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
<script setup>
|
||||
import { XIcon } from '@modrinth/assets'
|
||||
import { FolderOpenIcon, XIcon } from '@modrinth/assets'
|
||||
import {
|
||||
Button,
|
||||
commonMessages,
|
||||
defineMessages,
|
||||
FileTreeSelect,
|
||||
injectNotificationManager,
|
||||
injectPopupNotificationManager,
|
||||
NewModal,
|
||||
StyledInput,
|
||||
useVIntl,
|
||||
@@ -15,8 +16,10 @@ import { ref, shallowRef } from 'vue'
|
||||
|
||||
import { PackageIcon } from '@/assets/icons'
|
||||
import { export_instance_mrpack, get_pack_export_candidates } from '@/helpers/instance'
|
||||
import { highlightInFolder } from '@/helpers/utils'
|
||||
|
||||
const { handleError } = injectNotificationManager()
|
||||
const popupNotificationManager = injectPopupNotificationManager()
|
||||
const { formatMessage } = useVIntl()
|
||||
|
||||
const messages = defineMessages({
|
||||
@@ -39,6 +42,14 @@ const messages = defineMessages({
|
||||
defaultMessage: 'Enter modpack description...',
|
||||
},
|
||||
exportButton: { id: 'app.export-modal.export-button', defaultMessage: 'Export' },
|
||||
exportComplete: {
|
||||
id: 'app.export-modal.export-complete',
|
||||
defaultMessage: 'Export complete',
|
||||
},
|
||||
exportCompleteDescription: {
|
||||
id: 'app.export-modal.export-complete-description',
|
||||
defaultMessage: '{name} was exported successfully.',
|
||||
},
|
||||
})
|
||||
|
||||
const props = defineProps({
|
||||
@@ -93,16 +104,35 @@ const exportPack = async () => {
|
||||
})
|
||||
|
||||
if (outputPath) {
|
||||
export_instance_mrpack(
|
||||
props.instance.id,
|
||||
outputPath,
|
||||
includedFilePaths.value,
|
||||
excludedFilePaths.value,
|
||||
versionInput.value,
|
||||
exportDescription.value,
|
||||
nameInput.value,
|
||||
).catch((err) => handleError(err))
|
||||
exportModal.value.hide()
|
||||
|
||||
try {
|
||||
await export_instance_mrpack(
|
||||
props.instance.id,
|
||||
outputPath,
|
||||
includedFilePaths.value,
|
||||
excludedFilePaths.value,
|
||||
versionInput.value,
|
||||
exportDescription.value,
|
||||
nameInput.value,
|
||||
)
|
||||
|
||||
const fileName = outputPath.split(/[\\/]/).pop() ?? outputPath
|
||||
popupNotificationManager.addPopupNotification({
|
||||
title: formatMessage(messages.exportComplete),
|
||||
text: formatMessage(messages.exportCompleteDescription, { name: fileName }),
|
||||
type: 'success',
|
||||
buttons: [
|
||||
{
|
||||
label: formatMessage(commonMessages.openInFolderButton),
|
||||
icon: FolderOpenIcon,
|
||||
action: () => highlightInFolder(outputPath).catch(handleError),
|
||||
},
|
||||
],
|
||||
})
|
||||
} catch (error) {
|
||||
handleError(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,124 @@
|
||||
<script setup lang="ts">
|
||||
import { DownloadIcon, ExcitedRinthbot, RefreshCwIcon, ServerStackIcon } from '@modrinth/assets'
|
||||
import { Button, commonMessages, defineMessages, useVIntl } from '@modrinth/ui'
|
||||
import { computed } from 'vue'
|
||||
|
||||
import {
|
||||
appUpdateState,
|
||||
downloadAvailableAppUpdate,
|
||||
installAvailableAppUpdate,
|
||||
} from '@/providers/app-update'
|
||||
import { useRootBreadcrumb } from '@/providers/breadcrumbs'
|
||||
|
||||
const { formatMessage } = useVIntl()
|
||||
|
||||
const messages = defineMessages({
|
||||
title: {
|
||||
id: 'app.hosting.update-required.title',
|
||||
defaultMessage: 'Modrinth App update required',
|
||||
},
|
||||
description: {
|
||||
id: 'app.hosting.update-required.description',
|
||||
defaultMessage: 'You need to update to use Modrinth Hosting through the Modrinth App',
|
||||
},
|
||||
downloadToUpdate: {
|
||||
id: 'app.hosting.update-required.download',
|
||||
defaultMessage: 'Download to update',
|
||||
},
|
||||
downloadingUpdate: {
|
||||
id: 'app.action-bar.downloading-update',
|
||||
defaultMessage: 'Downloading update',
|
||||
},
|
||||
reloadToUpdate: {
|
||||
id: 'app.action-bar.reload-to-update',
|
||||
defaultMessage: 'Reload to update',
|
||||
},
|
||||
excitedRinthbotAlt: {
|
||||
id: 'app.hosting.update-required.rinthbot-alt',
|
||||
defaultMessage: 'Excited Modrinth Bot',
|
||||
},
|
||||
})
|
||||
|
||||
useRootBreadcrumb({
|
||||
slot: 'root',
|
||||
id: 'servers',
|
||||
label: () => formatMessage(commonMessages.serversLabel),
|
||||
to: '/hosting/manage/',
|
||||
visual: { type: 'icon', component: ServerStackIcon },
|
||||
})
|
||||
|
||||
const { downloading, downloadPercent, downloadProgress, finishedDownloading } = appUpdateState
|
||||
|
||||
const isUpdateDownloading = computed(
|
||||
() =>
|
||||
downloading.value ||
|
||||
(downloadProgress.value > 0 && downloadProgress.value < 1 && !finishedDownloading.value),
|
||||
)
|
||||
|
||||
async function handleUpdateClick() {
|
||||
if (isUpdateDownloading.value) {
|
||||
return
|
||||
}
|
||||
|
||||
if (finishedDownloading.value) {
|
||||
await installAvailableAppUpdate()
|
||||
} else {
|
||||
await downloadAvailableAppUpdate()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="box-border flex min-h-full items-center justify-center p-4">
|
||||
<div class="relative mx-auto w-full max-w-xl pt-28">
|
||||
<img
|
||||
:src="ExcitedRinthbot"
|
||||
:alt="formatMessage(messages.excitedRinthbotAlt)"
|
||||
class="absolute right-8 top-0 h-28 w-auto md:right-20"
|
||||
/>
|
||||
<div class="relative flex flex-col gap-5 rounded-lg bg-bg-raised p-7 shadow-lg">
|
||||
<div
|
||||
class="absolute left-0 top-0 h-px w-full bg-gradient-to-r from-transparent via-green-500 to-transparent opacity-40"
|
||||
style="
|
||||
background: linear-gradient(
|
||||
to right,
|
||||
transparent 2rem,
|
||||
var(--color-green) calc(100% - 13rem),
|
||||
var(--color-green) calc(100% - 5rem),
|
||||
transparent calc(100% - 2rem)
|
||||
);
|
||||
"
|
||||
></div>
|
||||
|
||||
<div class="flex flex-col gap-5">
|
||||
<h1 class="m-0 text-3xl font-extrabold">
|
||||
{{ formatMessage(messages.title) }}
|
||||
</h1>
|
||||
<p class="m-0 text-lg">
|
||||
{{ formatMessage(messages.description) }}
|
||||
</p>
|
||||
<Button
|
||||
type="colored"
|
||||
color="brand"
|
||||
:disabled="isUpdateDownloading"
|
||||
:aria-busy="isUpdateDownloading"
|
||||
@click="handleUpdateClick"
|
||||
>
|
||||
<RefreshCwIcon v-if="finishedDownloading" />
|
||||
<DownloadIcon v-else />
|
||||
<span v-if="isUpdateDownloading">
|
||||
{{ formatMessage(messages.downloadingUpdate) }}
|
||||
<span class="inline-block w-[3ch] text-right tabular-nums">
|
||||
{{ downloadPercent }}%
|
||||
</span>
|
||||
</span>
|
||||
<span v-else-if="finishedDownloading">
|
||||
{{ formatMessage(messages.reloadToUpdate) }}
|
||||
</span>
|
||||
<span v-else>{{ formatMessage(messages.downloadToUpdate) }}</span>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -10,11 +10,11 @@ import {
|
||||
import { Avatar, IconButton, injectNotificationManager, useRelativeTime } from '@modrinth/ui'
|
||||
import { convertFileSrc } from '@tauri-apps/api/core'
|
||||
import dayjs from 'dayjs'
|
||||
import { computed, onMounted, onUnmounted, ref } from 'vue'
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
import { useAppEvent } from '@/composables/use-app-event'
|
||||
import { trackEvent } from '@/helpers/analytics'
|
||||
import { process_listener } from '@/helpers/events'
|
||||
import { install_existing_instance, install_pack_to_existing_instance } from '@/helpers/install'
|
||||
import { kill, run } from '@/helpers/instance'
|
||||
import { get_by_instance_id } from '@/helpers/process'
|
||||
@@ -136,7 +136,7 @@ defineExpose({
|
||||
|
||||
const currentEvent = ref(null)
|
||||
|
||||
const unlisten = await process_listener((e) => {
|
||||
useAppEvent('process', (e) => {
|
||||
if (e.instance_id === props.instance.id) {
|
||||
currentEvent.value = e.event
|
||||
if (e.event === 'finished') {
|
||||
@@ -148,7 +148,6 @@ const unlisten = await process_listener((e) => {
|
||||
onMounted(() => {
|
||||
checkProcess()
|
||||
})
|
||||
onUnmounted(() => unlisten())
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
@@ -7,7 +7,7 @@ import dayjs from 'dayjs'
|
||||
import { computed, onMounted, onUnmounted, ref } from 'vue'
|
||||
|
||||
import NavButton from '@/components/ui/NavButton.vue'
|
||||
import { instance_listener } from '@/helpers/events.js'
|
||||
import { useAppEvent } from '@/composables/use-app-event'
|
||||
import { list } from '@/helpers/instance'
|
||||
import { instanceKeys } from '@/pages/instance/query-options'
|
||||
|
||||
@@ -148,7 +148,7 @@ const getInstances = async () => {
|
||||
await getInstances()
|
||||
updateMaxAuto()
|
||||
|
||||
const unlistenInstance = await instance_listener(async (event) => {
|
||||
useAppEvent('instance', async (event) => {
|
||||
if (event.event !== 'synced') {
|
||||
await getInstances()
|
||||
}
|
||||
@@ -162,7 +162,6 @@ onUnmounted(() => {
|
||||
window.removeEventListener('resize', updateMaxAuto)
|
||||
document.body.classList.remove('quick-instance-dragging')
|
||||
clearOverdragFlash()
|
||||
unlistenInstance()
|
||||
})
|
||||
|
||||
const messages = defineMessages({
|
||||
|
||||
@@ -82,7 +82,7 @@ import { injectLoadingState } from '@modrinth/ui'
|
||||
import { ref, watch } from 'vue'
|
||||
|
||||
import ProgressBar from '@/components/ui/ProgressBar.vue'
|
||||
import { loading_listener } from '@/helpers/events.js'
|
||||
import { useAppEvent } from '@/composables/use-app-event'
|
||||
|
||||
const doneLoading = ref(false)
|
||||
const loadingProgress = ref(0)
|
||||
@@ -132,13 +132,10 @@ function fakeLoadingIncrease() {
|
||||
}
|
||||
}
|
||||
|
||||
loading_listener(async (e) => {
|
||||
useAppEvent('loading', (e) => {
|
||||
if (e.event.type === 'directory_move') {
|
||||
loadingProgress.value = 100 * (e.fraction ?? 1)
|
||||
message.value = 'Updating app directory...'
|
||||
} else if (e.event.type === 'checking_for_updates') {
|
||||
loadingProgress.value = 100 * (e.fraction ?? 1)
|
||||
message.value = 'Checking for updates...'
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -3,12 +3,14 @@ import { NotepadTextIcon, XIcon } from '@modrinth/assets'
|
||||
import { Button, defineMessages, injectNotificationManager, useVIntl } from '@modrinth/ui'
|
||||
import { type } from '@tauri-apps/plugin-os'
|
||||
import { $fetch } from 'ofetch'
|
||||
import { onMounted, ref } from 'vue'
|
||||
import { onMounted, onUnmounted, ref } from 'vue'
|
||||
|
||||
import { hide_ads_window, show_ads_window } from '@/helpers/ads.js'
|
||||
import { release_ads_window_hold, take_ads_window_hold } from '@/helpers/ads.js'
|
||||
import { list } from '@/helpers/instance'
|
||||
import { get as getCreds } from '@/helpers/mr_auth.ts'
|
||||
|
||||
let adsWindowHold = false
|
||||
|
||||
type Survey = {
|
||||
id: string
|
||||
tally_id: string
|
||||
@@ -92,30 +94,44 @@ async function openSurvey() {
|
||||
onOpen: () => console.info('Opened user survey'),
|
||||
onClose: () => {
|
||||
console.info('Closed user survey')
|
||||
show_ads_window()
|
||||
if (adsWindowHold) {
|
||||
adsWindowHold = false
|
||||
release_ads_window_hold()
|
||||
}
|
||||
},
|
||||
onSubmit: () => console.info('Active user survey submitted'),
|
||||
}
|
||||
|
||||
try {
|
||||
hide_ads_window()
|
||||
await take_ads_window_hold()
|
||||
adsWindowHold = true
|
||||
if (tallyWindow.Tally?.openPopup) {
|
||||
console.info(`Opening Tally popup for user survey (form ID: ${formId})`)
|
||||
dismissSurvey()
|
||||
tallyWindow.Tally.openPopup(formId, popupOptions)
|
||||
} else {
|
||||
console.warn('Tally script not yet loaded')
|
||||
show_ads_window()
|
||||
adsWindowHold = false
|
||||
await release_ads_window_hold()
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Error opening Tally popup:', e)
|
||||
show_ads_window()
|
||||
if (adsWindowHold) {
|
||||
adsWindowHold = false
|
||||
await release_ads_window_hold()
|
||||
}
|
||||
}
|
||||
|
||||
console.info(`Found user survey to show with tally_id: ${formId}`)
|
||||
tallyWindow.Tally?.openPopup(formId, popupOptions)
|
||||
}
|
||||
|
||||
onUnmounted(() => {
|
||||
if (adsWindowHold) {
|
||||
adsWindowHold = false
|
||||
release_ads_window_hold()
|
||||
}
|
||||
})
|
||||
|
||||
function dismissSurvey() {
|
||||
if (!availableSurvey.value) return
|
||||
localStorage.setItem(`survey-${availableSurvey.value.id}-display`, String(new Date()))
|
||||
|
||||
@@ -23,9 +23,8 @@
|
||||
</IconButton>
|
||||
<IconButton
|
||||
type="quiet"
|
||||
color="red"
|
||||
label="Close window"
|
||||
class="relative expanded-button close-button hover:!bg-red focus-visible:!bg-red"
|
||||
class="relative expanded-button close-button"
|
||||
@click="handleClose"
|
||||
>
|
||||
<XIcon />
|
||||
|
||||
+1
-8
@@ -10,7 +10,6 @@ import {
|
||||
import { Admonition, Button, ButtonLink, Collapsible, IconButton, NewModal } from '@modrinth/ui'
|
||||
import { computed, ref } from 'vue'
|
||||
|
||||
import { hide_ads_window, show_ads_window } from '@/helpers/ads.js'
|
||||
import { login as login_flow, set_default_user } from '@/helpers/auth.js'
|
||||
import { handleSevereError } from '@/store/error.js'
|
||||
|
||||
@@ -29,19 +28,13 @@ function show(errorVal: { message?: string }) {
|
||||
matchedError.value = findMinecraftAuthError(rawError.value)
|
||||
|
||||
debugCollapsed.value = true
|
||||
hide_ads_window()
|
||||
modal.value?.show()
|
||||
}
|
||||
|
||||
function hide() {
|
||||
onModalHide()
|
||||
modal.value?.hide()
|
||||
}
|
||||
|
||||
function onModalHide() {
|
||||
show_ads_window()
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
show,
|
||||
hide,
|
||||
@@ -74,7 +67,7 @@ async function copyToClipboard(text: string) {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NewModal ref="modal" header="Sign in Failed" :max-width="'548px'" @hide="onModalHide">
|
||||
<NewModal ref="modal" header="Sign in Failed" :max-width="'548px'">
|
||||
<div class="flex flex-col gap-6">
|
||||
<Admonition
|
||||
type="warning"
|
||||
|
||||
+3
-9
@@ -20,18 +20,12 @@
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-6 px-6 pb-6">
|
||||
<div class="flex justify-end gap-2">
|
||||
<ButtonLink class="w-full" href="https://support.modrinth.com" @click="modal?.hide()">
|
||||
<div class="grid grid-cols-2 gap-2">
|
||||
<ButtonLink href="https://support.modrinth.com" @click="modal?.hide()">
|
||||
<MessagesSquareIcon />
|
||||
{{ formatMessage(messages.getSupport) }}
|
||||
</ButtonLink>
|
||||
<Button
|
||||
type="colored"
|
||||
color="brand"
|
||||
class="w-full"
|
||||
:disabled="loadingSignIn"
|
||||
@click="signIn"
|
||||
>
|
||||
<Button type="colored" color="brand" :disabled="loadingSignIn" @click="signIn">
|
||||
<SpinnerIcon v-if="loadingSignIn" class="animate-spin" />
|
||||
<svg
|
||||
v-else
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
ref="modal"
|
||||
:header="formatMessage(messages.installToPlay)"
|
||||
:closable="true"
|
||||
:on-hide="show_ads_window"
|
||||
max-width="544px"
|
||||
width="544px"
|
||||
>
|
||||
@@ -141,10 +140,11 @@
|
||||
</div>
|
||||
</NewModal>
|
||||
|
||||
<ModpackContentModal
|
||||
ref="modpackContentModal"
|
||||
:modpack-name="project?.name ?? ''"
|
||||
:modpack-icon-url="project?.icon_url ?? undefined"
|
||||
<ManagedContentModal
|
||||
ref="managedContentModal"
|
||||
:header="formatMessage(messages.modpackContent)"
|
||||
:source-name="project?.name ?? ''"
|
||||
:source-icon-url="project?.icon_url ?? undefined"
|
||||
/>
|
||||
</template>
|
||||
|
||||
@@ -159,7 +159,7 @@ import {
|
||||
type ContentItem,
|
||||
defineMessages,
|
||||
formatLoader,
|
||||
ModpackContentModal,
|
||||
ManagedContentModal,
|
||||
NewModal,
|
||||
Table,
|
||||
type TableColumn,
|
||||
@@ -169,7 +169,6 @@ import {
|
||||
import { openUrl } from '@tauri-apps/plugin-opener'
|
||||
import { computed, nextTick, ref } from 'vue'
|
||||
|
||||
import { hide_ads_window, show_ads_window } from '@/helpers/ads'
|
||||
import { get_project, get_project_many, get_version, get_version_many } from '@/helpers/cache.js'
|
||||
import { injectServerInstall } from '@/providers/server-install'
|
||||
|
||||
@@ -269,10 +268,10 @@ function handleReport() {
|
||||
}
|
||||
}
|
||||
|
||||
const modpackContentModal = ref<InstanceType<typeof ModpackContentModal>>()
|
||||
const managedContentModal = ref<InstanceType<typeof ManagedContentModal>>()
|
||||
|
||||
async function openViewContents() {
|
||||
modpackContentModal.value?.showLoading()
|
||||
managedContentModal.value?.showLoading()
|
||||
try {
|
||||
// Ensure version data is available — the useQuery may not have resolved yet
|
||||
const versionId = modpackVersionId.value
|
||||
@@ -330,10 +329,10 @@ async function openViewContents() {
|
||||
}
|
||||
},
|
||||
)
|
||||
modpackContentModal.value?.show(contentItems)
|
||||
managedContentModal.value?.show(contentItems)
|
||||
} catch (err) {
|
||||
console.error('Failed to load modpack contents:', err)
|
||||
modpackContentModal.value?.show([])
|
||||
managedContentModal.value?.show([])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -352,7 +351,6 @@ async function show(
|
||||
|
||||
if (modpackVersionIdVal) await fetchData(modpackVersionIdVal)
|
||||
|
||||
hide_ads_window()
|
||||
modal.value?.show(e)
|
||||
await nextTick()
|
||||
forceCheckTableScroll()
|
||||
@@ -363,6 +361,10 @@ function hide() {
|
||||
}
|
||||
|
||||
const messages = defineMessages({
|
||||
modpackContent: {
|
||||
id: 'app.modal.install-to-play.managed-content.modpack-header',
|
||||
defaultMessage: 'Modpack content',
|
||||
},
|
||||
installToPlay: {
|
||||
id: 'app.modal.install-to-play.header',
|
||||
defaultMessage: 'Install to play',
|
||||
@@ -403,8 +405,7 @@ const messages = defineMessages({
|
||||
},
|
||||
reviewedFiles: {
|
||||
id: 'app.modal.install-to-play.reviewed-files',
|
||||
defaultMessage:
|
||||
'A file is only reviewed if it’s published to Modrinth, regardless of its file format (including .mrpack).',
|
||||
defaultMessage: "Files that aren't published to Modrinth aren't reviewed.",
|
||||
},
|
||||
installAnyway: {
|
||||
id: 'app.modal.install-to-play.install-anyway',
|
||||
|
||||
@@ -34,6 +34,7 @@ import { get_project_many, get_version, get_version_many } from '@/helpers/cache
|
||||
import { wait_for_install_job } from '@/helpers/install'
|
||||
import { update_managed_modrinth_version } from '@/helpers/instance'
|
||||
import type { GameInstance } from '@/helpers/types'
|
||||
import { injectAppEvents } from '@/providers/app-events'
|
||||
import { injectServerInstall } from '@/providers/server-install'
|
||||
|
||||
type Dependency = Labrinth.Versions.v3.Dependency
|
||||
@@ -74,6 +75,7 @@ type ProjectInfo = {
|
||||
}
|
||||
|
||||
const { formatMessage } = useVIntl()
|
||||
const appEvents = injectAppEvents()
|
||||
const { startInstallingServer, stopInstallingServer } = injectServerInstall()
|
||||
type UpdateCompleteCallback = () => void | Promise<void>
|
||||
|
||||
@@ -253,7 +255,7 @@ async function handleUpdate() {
|
||||
try {
|
||||
if (modpackVersionId.value && instance.value) {
|
||||
const job = await update_managed_modrinth_version(instance.value.id, modpackVersionId.value)
|
||||
await wait_for_install_job(job.job_id)
|
||||
await wait_for_install_job(appEvents, job.job_id)
|
||||
await onUpdateComplete.value()
|
||||
}
|
||||
} catch (error) {
|
||||
|
||||
@@ -39,6 +39,7 @@ import {
|
||||
} from '@/helpers/install'
|
||||
import { useSharedInstanceErrors } from '@/helpers/shared-instance-errors'
|
||||
import type { GameInstance } from '@/helpers/types'
|
||||
import { injectAppEvents } from '@/providers/app-events'
|
||||
|
||||
type UpdateCompleteCallback = () => void | Promise<void>
|
||||
|
||||
@@ -54,6 +55,7 @@ const instance = ref<GameInstance | null>(null)
|
||||
const preview = ref<SharedInstanceUpdatePreview | null>(null)
|
||||
const onComplete = ref<UpdateCompleteCallback>(() => {})
|
||||
const { formatMessage } = useVIntl()
|
||||
const appEvents = injectAppEvents()
|
||||
const { notifySharedInstanceError } = useSharedInstanceErrors()
|
||||
const diffs = computed<ContentDiffItem[]>(
|
||||
() =>
|
||||
@@ -78,7 +80,7 @@ async function update() {
|
||||
try {
|
||||
if (instance.value) {
|
||||
const job = await install_update_shared_instance(instance.value.id)
|
||||
await wait_for_install_job(job.job_id)
|
||||
await wait_for_install_job(appEvents, job.job_id)
|
||||
await onComplete.value()
|
||||
successful = true
|
||||
}
|
||||
|
||||
+10
-25
@@ -229,11 +229,11 @@
|
||||
</div>
|
||||
</template>
|
||||
</NewModal>
|
||||
<ModpackContentModal
|
||||
<ManagedContentModal
|
||||
ref="contentModal"
|
||||
:header="formatMessage(messages.sharedInstanceContent)"
|
||||
:modpack-name="preview?.name ?? ''"
|
||||
:modpack-icon-url="preview?.iconUrl ?? undefined"
|
||||
:source-name="preview?.name ?? ''"
|
||||
:source-icon-url="preview?.iconUrl ?? undefined"
|
||||
/>
|
||||
</template>
|
||||
|
||||
@@ -251,12 +251,13 @@ import {
|
||||
type ComboboxOption,
|
||||
commonMessages,
|
||||
defineMessages,
|
||||
formatReportType,
|
||||
injectAuth,
|
||||
injectModrinthClient,
|
||||
injectNotificationManager,
|
||||
IntlFormatted,
|
||||
ManagedContentModal,
|
||||
MarkdownEditor,
|
||||
ModpackContentModal,
|
||||
NewModal,
|
||||
Table,
|
||||
type TableColumn,
|
||||
@@ -268,7 +269,6 @@ import { openUrl } from '@tauri-apps/plugin-opener'
|
||||
import { computed, nextTick, ref } from 'vue'
|
||||
|
||||
import { config } from '@/config'
|
||||
import { hide_ads_window, show_ads_window } from '@/helpers/ads'
|
||||
import { toError } from '@/helpers/errors'
|
||||
import type { SharedInstanceInstallPreview } from '@/helpers/install'
|
||||
import { create_report } from '@/helpers/reports'
|
||||
@@ -289,7 +289,7 @@ type SharedInstanceCreator = {
|
||||
}
|
||||
|
||||
const modal = ref<InstanceType<typeof NewModal>>()
|
||||
const contentModal = ref<InstanceType<typeof ModpackContentModal>>()
|
||||
const contentModal = ref<InstanceType<typeof ManagedContentModal>>()
|
||||
const externalFileTable = ref<HTMLElement | null>(null)
|
||||
const preview = ref<SharedInstanceInstallPreview | null>(null)
|
||||
const creator = ref<SharedInstanceCreator | null>(null)
|
||||
@@ -329,9 +329,9 @@ const externalFileRows = computed<ExternalFileRow[]>(() =>
|
||||
.sort((left, right) => left.name.localeCompare(right.name)),
|
||||
)
|
||||
const reportReasonOptions = computed<ComboboxOption<ReportReason>[]>(() => [
|
||||
{ value: 'malicious', label: formatMessage(messages.maliciousReason) },
|
||||
{ value: 'inappropriate', label: formatMessage(messages.inappropriateReason) },
|
||||
{ value: 'spam', label: formatMessage(messages.spamReason) },
|
||||
{ value: 'malicious', label: formatReportType(formatMessage, 'malicious') },
|
||||
{ value: 'inappropriate', label: formatReportType(formatMessage, 'inappropriate') },
|
||||
{ value: 'spam', label: formatReportType(formatMessage, 'spam') },
|
||||
])
|
||||
const canSubmitReport = computed(
|
||||
() => Boolean(preview.value && additionalContext.value.trim()) && !submitLoading.value,
|
||||
@@ -448,7 +448,6 @@ function handleCancel() {
|
||||
function handleHide() {
|
||||
resetReportState()
|
||||
creator.value = null
|
||||
show_ads_window()
|
||||
}
|
||||
function resetReportState() {
|
||||
reportMode.value = false
|
||||
@@ -488,7 +487,6 @@ function showReport(
|
||||
}
|
||||
function showPreview(previewValue: SharedInstanceInstallPreview, event?: MouseEvent) {
|
||||
preview.value = previewValue
|
||||
hide_ads_window()
|
||||
modal.value?.show(event)
|
||||
void nextTick(() => forceCheckTableScroll())
|
||||
}
|
||||
@@ -543,18 +541,6 @@ const messages = defineMessages({
|
||||
id: 'app.modal.install-to-play.report-reason',
|
||||
defaultMessage: 'Which rule does this instance violate?',
|
||||
},
|
||||
maliciousReason: {
|
||||
id: 'app.modal.install-to-play.report-reason.malicious',
|
||||
defaultMessage: 'Malicious',
|
||||
},
|
||||
inappropriateReason: {
|
||||
id: 'app.modal.install-to-play.report-reason.inappropriate',
|
||||
defaultMessage: 'Inappropriate',
|
||||
},
|
||||
spamReason: {
|
||||
id: 'app.modal.install-to-play.report-reason.spam',
|
||||
defaultMessage: 'Spam',
|
||||
},
|
||||
additionalContext: {
|
||||
id: 'app.modal.install-to-play.additional-context',
|
||||
defaultMessage: 'Additional context',
|
||||
@@ -598,8 +584,7 @@ const messages = defineMessages({
|
||||
},
|
||||
reviewedFiles: {
|
||||
id: 'app.modal.install-to-play.reviewed-files',
|
||||
defaultMessage:
|
||||
'A file is only reviewed if it’s published to Modrinth, regardless of its file format (including .mrpack).',
|
||||
defaultMessage: "Files that aren't published to Modrinth aren't reviewed.",
|
||||
},
|
||||
installAnyway: {
|
||||
id: 'app.modal.install-to-play.install-anyway',
|
||||
|
||||
@@ -21,12 +21,12 @@ import {
|
||||
import { capitalizeString } from '@modrinth/utils'
|
||||
import { convertFileSrc } from '@tauri-apps/api/core'
|
||||
import type { Dayjs } from 'dayjs'
|
||||
import { computed, nextTick, onMounted, onUnmounted, ref } from 'vue'
|
||||
import { computed, nextTick, onMounted, ref } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
import { useAppEvent } from '@/composables/use-app-event'
|
||||
import { trackEvent } from '@/helpers/analytics'
|
||||
import { get_project } from '@/helpers/cache'
|
||||
import { process_listener } from '@/helpers/events'
|
||||
import { kill, run } from '@/helpers/instance'
|
||||
import { get_by_instance_id } from '@/helpers/process'
|
||||
import type { GameInstance } from '@/helpers/types'
|
||||
@@ -108,7 +108,7 @@ const stop = async (event: MouseEvent) => {
|
||||
loading.value = false
|
||||
}
|
||||
|
||||
const unlistenProcesses = await process_listener(async () => {
|
||||
useAppEvent('process', async () => {
|
||||
await checkProcess()
|
||||
})
|
||||
|
||||
@@ -121,10 +121,6 @@ const checkProcess = async () => {
|
||||
onMounted(() => {
|
||||
checkProcess()
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
unlistenProcesses()
|
||||
})
|
||||
</script>
|
||||
<template>
|
||||
<SmartClickable>
|
||||
|
||||
@@ -5,12 +5,12 @@ import { GAME_MODES, injectNotificationManager } from '@modrinth/ui'
|
||||
import { platform } from '@tauri-apps/plugin-os'
|
||||
import type { Dayjs } from 'dayjs'
|
||||
import dayjs from 'dayjs'
|
||||
import { computed, onMounted, onUnmounted, ref, watch } from 'vue'
|
||||
import { computed, onMounted, ref, watch } from 'vue'
|
||||
|
||||
import InstanceItem from '@/components/ui/world/InstanceItem.vue'
|
||||
import WorldItem from '@/components/ui/world/WorldItem.vue'
|
||||
import { useAppEvent } from '@/composables/use-app-event'
|
||||
import { trackEvent } from '@/helpers/analytics'
|
||||
import { instance_listener, process_listener } from '@/helpers/events'
|
||||
import { kill, run } from '@/helpers/instance'
|
||||
import { get_all } from '@/helpers/process'
|
||||
import { get_game_versions } from '@/helpers/tags'
|
||||
@@ -215,11 +215,11 @@ async function stopInstance(path: string) {
|
||||
const currentInstance = ref<string>()
|
||||
const currentWorld = ref<string>()
|
||||
|
||||
const unlistenProcesses = await process_listener(async () => {
|
||||
useAppEvent('process', async () => {
|
||||
await checkProcesses()
|
||||
})
|
||||
|
||||
const unlistenInstances = await instance_listener(async () => {
|
||||
useAppEvent('instance', async () => {
|
||||
await populateJumpBackIn().catch(() => {
|
||||
console.error('Failed to populate jump back in')
|
||||
})
|
||||
@@ -251,11 +251,6 @@ onMounted(() => {
|
||||
checkProcesses()
|
||||
linuxPopulateCount.value = 0
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
unlistenProcesses()
|
||||
unlistenInstances()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
@@ -10,7 +10,6 @@ import { convertFileSrc } from '@tauri-apps/api/core'
|
||||
import { computed, ref } from 'vue'
|
||||
import type { Router } from 'vue-router'
|
||||
|
||||
import { install_job_listener } from '@/helpers/events'
|
||||
import {
|
||||
install_job_dismiss,
|
||||
install_job_list,
|
||||
@@ -23,6 +22,7 @@ import {
|
||||
type InstallProgress,
|
||||
} from '@/helpers/install'
|
||||
import { get_many as getInstances } from '@/helpers/instance'
|
||||
import { injectAppEvents } from '@/providers/app-events'
|
||||
import { useTheming } from '@/store/state'
|
||||
|
||||
const messages = defineMessages({
|
||||
@@ -234,6 +234,7 @@ export async function useInstallJobNotifications(opts: {
|
||||
handleError: (err: unknown) => void
|
||||
onChange: () => void
|
||||
}) {
|
||||
const appEvents = injectAppEvents()
|
||||
const { formatMessage } = useVIntl()
|
||||
const themeStore = useTheming()
|
||||
const jobs = ref<InstallJobSnapshot[]>([])
|
||||
@@ -404,8 +405,6 @@ export async function useInstallJobNotifications(opts: {
|
||||
}
|
||||
|
||||
function getProgress(job: InstallJobSnapshot): number {
|
||||
if (job.status === 'succeeded') return 1
|
||||
if (job.status === 'failed' || job.status === 'interrupted') return 0
|
||||
const progress = getEffectiveProgress(job)
|
||||
if (!progress || progress.total <= 0) return 0
|
||||
return Math.max(0, Math.min(1, progress.current / progress.total))
|
||||
@@ -526,8 +525,12 @@ export async function useInstallJobNotifications(opts: {
|
||||
)
|
||||
}
|
||||
|
||||
const activeJobs = computed(() =>
|
||||
jobs.value.filter((job) => job.status === 'queued' || job.status === 'running'),
|
||||
)
|
||||
|
||||
const progressItems = computed<PopupNotificationProgressItem[]>(() =>
|
||||
jobs.value.map((job) => {
|
||||
activeJobs.value.map((job) => {
|
||||
const progress = getEffectiveProgress(job)
|
||||
|
||||
return {
|
||||
@@ -536,20 +539,26 @@ export async function useInstallJobNotifications(opts: {
|
||||
text: getText(job),
|
||||
iconUrl: iconUrls.value[job.job_id] ?? null,
|
||||
progress: getProgress(job),
|
||||
waiting: !job.progress && ['queued', 'running'].includes(job.status),
|
||||
showProgress: !isTerminalJob(job),
|
||||
wrapText: isTerminalJob(job),
|
||||
progressType: isTerminalJob(job) ? undefined : getProgressType(job),
|
||||
progressCurrent: isTerminalJob(job) ? undefined : progress?.current,
|
||||
progressTotal: isTerminalJob(job) ? undefined : progress?.total,
|
||||
waiting: !job.progress && job.status === 'running',
|
||||
showProgress: job.status === 'running',
|
||||
progressType: getProgressType(job),
|
||||
progressCurrent: progress?.current,
|
||||
progressTotal: progress?.total,
|
||||
buttons: getButtons(job),
|
||||
dismissible: isTerminalJob(job),
|
||||
onDismiss: getDismissHandler(job),
|
||||
}
|
||||
}),
|
||||
)
|
||||
|
||||
const buttons = computed<PopupNotificationButton[] | undefined>(() => undefined)
|
||||
const terminalNotifications = computed(() =>
|
||||
jobs.value.filter(isTerminalJob).map((job) => ({
|
||||
id: job.job_id,
|
||||
title: getTitle(job),
|
||||
text: getText(job),
|
||||
type: job.status === 'failed' ? ('error' as const) : ('warning' as const),
|
||||
buttons: getButtons(job),
|
||||
onDismiss: getDismissHandler(job),
|
||||
})),
|
||||
)
|
||||
|
||||
async function refreshMetadata(notify = true) {
|
||||
const request = ++metadataRequest
|
||||
@@ -627,14 +636,14 @@ export async function useInstallJobNotifications(opts: {
|
||||
void refreshMetadata()
|
||||
}
|
||||
|
||||
const unlisten = await install_job_listener((job: InstallJobSnapshot) => applyJobUpdate(job))
|
||||
const unlisten = appEvents.on('install_job', applyJobUpdate)
|
||||
await refresh(false)
|
||||
|
||||
return {
|
||||
active: computed(() => jobs.value.length > 0),
|
||||
active: computed(() => activeJobs.value.length > 0),
|
||||
title: computed(() => formatMessage(messages.installs)),
|
||||
progressItems,
|
||||
buttons,
|
||||
terminalNotifications,
|
||||
refresh,
|
||||
dispose: () => {
|
||||
for (const timeout of copiedResetTimeouts.values()) {
|
||||
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
fetchCachedServerStatus,
|
||||
getFreshCachedServerStatus,
|
||||
} from '@/composables/instances/use-server-status-query'
|
||||
import { process_listener } from '@/helpers/events'
|
||||
import { useAppEvent } from '@/composables/use-app-event'
|
||||
import { kill, list as listInstances } from '@/helpers/instance'
|
||||
import { get_by_instance_id } from '@/helpers/process'
|
||||
import type { GameInstance } from '@/helpers/types'
|
||||
@@ -78,7 +78,6 @@ export function useAppServerBrowse(options: UseAppServerBrowseOptions) {
|
||||
const lastServerHits = shallowRef<Labrinth.Search.v3.ResultSearchProject[]>([])
|
||||
const contextMenuRef = ref<ContextMenuHandle | null>(null)
|
||||
let serverPingsActive = true
|
||||
let unlistenProcesses: (() => void) | null = null
|
||||
|
||||
async function checkServerRunningStates(hits: Labrinth.Search.v3.ResultSearchProject[]) {
|
||||
debugLog('checkServerRunningStates', { hitCount: hits.length })
|
||||
@@ -280,7 +279,7 @@ export function useAppServerBrowse(options: UseAppServerBrowseOptions) {
|
||||
}
|
||||
}
|
||||
|
||||
process_listener((event: { event: string; instance_id: string }) => {
|
||||
useAppEvent('process', (event) => {
|
||||
debugLog('process event', event)
|
||||
if (event.event === 'finished') {
|
||||
const projectId = Object.entries(runningServerProjects.value).find(
|
||||
@@ -292,14 +291,9 @@ export function useAppServerBrowse(options: UseAppServerBrowseOptions) {
|
||||
}
|
||||
}
|
||||
})
|
||||
.then((unlisten) => {
|
||||
unlistenProcesses = unlisten
|
||||
})
|
||||
.catch(options.handleError)
|
||||
|
||||
onUnmounted(() => {
|
||||
serverPingsActive = false
|
||||
unlistenProcesses?.()
|
||||
})
|
||||
|
||||
return {
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
import { onScopeDispose } from 'vue'
|
||||
|
||||
import {
|
||||
type AppEventHandler,
|
||||
type AppEvents,
|
||||
type AppEventType,
|
||||
injectAppEvents,
|
||||
} from '@/providers/app-events'
|
||||
|
||||
export function useAppEvent<Type extends AppEventType>(
|
||||
type: Type,
|
||||
handler: AppEventHandler<Type>,
|
||||
events: AppEvents = injectAppEvents(),
|
||||
) {
|
||||
const unsubscribe = events.on(type, handler)
|
||||
onScopeDispose(unsubscribe)
|
||||
return unsubscribe
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/vue-query'
|
||||
import { computed, type MaybeRefOrGetter, onUnmounted, toValue } from 'vue'
|
||||
import { computed, type MaybeRefOrGetter, toValue } from 'vue'
|
||||
|
||||
import { useAppEvent } from '@/composables/use-app-event'
|
||||
import { toError } from '@/helpers/errors'
|
||||
import { friend_listener } from '@/helpers/events.js'
|
||||
import {
|
||||
acceptCachedFriend,
|
||||
add_friend,
|
||||
@@ -127,13 +127,9 @@ export function useFriends(options: {
|
||||
)
|
||||
}
|
||||
|
||||
let unlisten: (() => void) | undefined
|
||||
void friend_listener(() => {
|
||||
useAppEvent('friend', () => {
|
||||
void queryClient.invalidateQueries({ queryKey: queryKey.value })
|
||||
}).then((listener) => {
|
||||
unlisten = listener
|
||||
})
|
||||
onUnmounted(() => unlisten?.())
|
||||
|
||||
return {
|
||||
query,
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { CommandPayload } from "./CommandPayload";
|
||||
import type { FriendPayload } from "./FriendPayload";
|
||||
import type { InstallJobSnapshot } from "./InstallJobSnapshot";
|
||||
import type { InstanceBulkUpdateProgressPayload } from "./InstanceBulkUpdateProgressPayload";
|
||||
import type { InstancePayload } from "./InstancePayload";
|
||||
import type { LoadingPayload } from "./LoadingPayload";
|
||||
import type { LogPayload } from "./LogPayload";
|
||||
import type { ProcessPayload } from "./ProcessPayload";
|
||||
import type { WarningPayload } from "./WarningPayload";
|
||||
|
||||
export type AppEvent = { "type": "loading", "payload": LoadingPayload } | { "type": "process", "payload": ProcessPayload } | { "type": "instance", "payload": InstancePayload } | { "type": "instance_bulk_update_progress", "payload": InstanceBulkUpdateProgressPayload } | { "type": "install_job", "payload": InstallJobSnapshot } | { "type": "command", "payload": CommandPayload } | { "type": "warning", "payload": WarningPayload } | { "type": "friend", "payload": FriendPayload } | { "type": "notification", "payload": unknown } | { "type": "log", "payload": LogPayload } | { "type": "ads_consent_required", "payload": boolean };
|
||||
@@ -0,0 +1,3 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export type CommandPayload = { "event": "InstallMod", id: string, } | { "event": "InstallVersion", id: string, } | { "event": "InstallModpack", id: string, } | { "event": "InstallServer", id: string, } | { "event": "LaunchInstance", id: string, server: string | null, singleplayer_world: string | null, } | { "event": "InstallSharedInstanceInvite", invite_id: string, } | { "event": "RunMRPack", path: string, };
|
||||
@@ -0,0 +1,4 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { FriendStatusPayload } from "./FriendStatusPayload";
|
||||
|
||||
export type FriendPayload = { "event": "friend_request", from: string, } | { "event": "user_offline", id: string, } | { "event": "status_update", user_status: FriendStatusPayload, } | { "event": "status_sync" };
|
||||
@@ -0,0 +1,3 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export type FriendStatusPayload = { user_id: string, profile_name: string | null, last_update: string, };
|
||||
@@ -0,0 +1,3 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export type ImportLauncherType = "MultiMC" | "PrismLauncher" | "ATLauncher" | "GDLauncher" | "Curseforge" | "Unknown";
|
||||
@@ -0,0 +1,3 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export type InstallApiErrorDetails = { error: string, status?: number, method?: string, url?: string, route?: string, };
|
||||
@@ -0,0 +1,3 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export type InstallErrorContext = { operation: string, source_path?: string, target_path?: string, file_path?: string, entry_path?: string, urls: Array<string>, expected_hash?: string, expected_size?: number, project_id?: string, version_id?: string, minecraft_version?: string, loader?: string, java_version?: number, os?: string, arch?: string, };
|
||||
@@ -0,0 +1,7 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { InstallApiErrorDetails } from "./InstallApiErrorDetails";
|
||||
import type { InstallErrorContext } from "./InstallErrorContext";
|
||||
import type { InstallPhaseId } from "./InstallPhaseId";
|
||||
import type { SharedInstanceUnavailableReason } from "./SharedInstanceUnavailableReason";
|
||||
|
||||
export type InstallErrorView = { code: string, phase?: InstallPhaseId, message: string, reason?: SharedInstanceUnavailableReason, api?: InstallApiErrorDetails, context?: InstallErrorContext, };
|
||||
@@ -0,0 +1,3 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export type InstallJavaStep = "resolving" | "fetching_metadata" | "downloading" | "extracting" | "validating";
|
||||
@@ -0,0 +1,3 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export type InstallJobDisplay = { title: string, icon: string | null, };
|
||||
@@ -0,0 +1,3 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export type InstallJobKind = "create_instance" | "create_modpack_instance" | "create_shared_instance" | "import_instance" | "duplicate_instance" | "install_existing_instance" | "install_pack_to_existing_instance" | "update_shared_instance";
|
||||
@@ -0,0 +1,11 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { InstallErrorView } from "./InstallErrorView";
|
||||
import type { InstallJobDisplay } from "./InstallJobDisplay";
|
||||
import type { InstallJobKind } from "./InstallJobKind";
|
||||
import type { InstallJobStatus } from "./InstallJobStatus";
|
||||
import type { InstallPhaseDetails } from "./InstallPhaseDetails";
|
||||
import type { InstallPhaseId } from "./InstallPhaseId";
|
||||
import type { InstallProgress } from "./InstallProgress";
|
||||
import type { InstallTarget } from "./InstallTarget";
|
||||
|
||||
export type InstallJobSnapshot = { job_id: string, instance_id: string | null, kind: InstallJobKind, status: InstallJobStatus, target: InstallTarget, phase: InstallPhaseId, progress: InstallProgress | null, details: InstallPhaseDetails, display: InstallJobDisplay | null, error: InstallErrorView | null, rollback_error: InstallErrorView | null, created: string, modified: string, finished: string | null, };
|
||||
@@ -0,0 +1,3 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export type InstallJobStatus = "queued" | "running" | "succeeded" | "failed" | "interrupted" | "canceled";
|
||||
@@ -0,0 +1,6 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { ImportLauncherType } from "./ImportLauncherType";
|
||||
import type { InstallJavaStep } from "./InstallJavaStep";
|
||||
import type { ModLoader } from "./ModLoader";
|
||||
|
||||
export type InstallPhaseDetails = { "type": "empty" } | { "type": "instance", name: string, } | { "type": "minecraft", game_version: string, loader: ModLoader, } | { "type": "java", major_version: number, step: InstallJavaStep, } | { "type": "modpack", project_id: string | null, version_id: string | null, title: string | null, } | { "type": "import", launcher_type: ImportLauncherType, instance_folder: string, };
|
||||
@@ -0,0 +1,3 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export type InstallPhaseId = "preparing_instance" | "resolving_pack" | "downloading_pack_file" | "reading_pack_manifest" | "downloading_content" | "extracting_overrides" | "resolving_minecraft" | "resolving_loader" | "preparing_java" | "downloading_minecraft" | "running_loader_processors" | "finalizing" | "rolling_back";
|
||||
@@ -0,0 +1,4 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { InstallProgressSecondary } from "./InstallProgressSecondary";
|
||||
|
||||
export type InstallProgress = { current: number, total: number, secondary?: InstallProgressSecondary, };
|
||||
@@ -0,0 +1,3 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export type InstallProgressSecondary = { current: number, total: number, };
|
||||
@@ -0,0 +1,3 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export type InstallTarget = { "type": "new_instance", instance_id: string | null, } | { "type": "existing_instance", instance_id: string, };
|
||||
@@ -0,0 +1,4 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { InstanceBulkUpdateProgressStage } from "./InstanceBulkUpdateProgressStage";
|
||||
|
||||
export type InstanceBulkUpdateProgressPayload = { instanceId: string, stage: InstanceBulkUpdateProgressStage, current: number, total: number, };
|
||||
@@ -0,0 +1,3 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export type InstanceBulkUpdateProgressStage = "resolving_versions" | "downloading" | "finishing";
|
||||
@@ -0,0 +1,3 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export type InstancePayload = { instance_id: string, } & ({ "event": "created" } | { "event": "synced" } | { "event": "servers_updated" } | { "event": "world_updated", world: string, } | { "event": "server_joined", host: string, port: number, timestamp: string, } | { "event": "edited" } | { "event": "content_install_finished", project_ids: Array<string>, } | { "event": "content_install_failed", project_ids: Array<string>, message: string, } | { "event": "removed" });
|
||||
@@ -0,0 +1,3 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export type LoadingBarType = { "type": "legacy_data_migration" } | { "type": "directory_move", old: string, new: string, } | { "type": "java_download", version: number, } | { "type": "pack_file_download", instance_id: string, pack_name: string, icon: string | null, pack_version: string, } | { "type": "pack_download", instance_id: string, pack_name: string, icon: string | null, pack_id: string | null, pack_version: string | null, } | { "type": "minecraft_download", instance_id: string, instance_name: string, } | { "type": "instance_update", instance_id: string, instance_name: string, } | { "type": "zip_extract", instance_id: string, instance_name: string, } | { "type": "config_change", new_path: string, } | { "type": "copy_instance", import_location: string, instance_name: string, } | { "type": "launcher_update", version: string, current_version: string, };
|
||||
@@ -0,0 +1,4 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { LoadingBarType } from "./LoadingBarType";
|
||||
|
||||
export type LoadingPayload = { event: LoadingBarType, loader_uuid: string, fraction: number | null, message: string, };
|
||||
@@ -0,0 +1,3 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export type Log4jEvent = { timestamp_millis: number | null, logger_name: string | null, level: string | null, thread_name: string | null, message: string | null, throwable: string | null, };
|
||||
@@ -0,0 +1,4 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { Log4jEvent } from "./Log4jEvent";
|
||||
|
||||
export type LogPayload = { instance_id: string, } & ({ "type": "log4j" } & Log4jEvent | { "type": "legacy", message: string, });
|
||||
@@ -0,0 +1,3 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export type ModLoader = "vanilla" | "forge" | "fabric" | "quilt" | "neoforge";
|
||||
@@ -0,0 +1,4 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { ProcessPayloadType } from "./ProcessPayloadType";
|
||||
|
||||
export type ProcessPayload = { instance_id: string, uuid: string, event: ProcessPayloadType, message: string, };
|
||||
@@ -0,0 +1,3 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export type ProcessPayloadType = "launched" | "finished";
|
||||
@@ -0,0 +1,8 @@
|
||||
# App event bindings
|
||||
|
||||
> [!WARNING]
|
||||
> Do not edit the generated TypeScript or Postcard binding files in this directory manually.
|
||||
|
||||
The event bus types are determined by the Rust [`AppEvent`](../../../../../packages/app-lib/src/event/mod.rs) contract.
|
||||
|
||||
They are regenerated automatically when you run `pnpm app:dev` from the workspace root. Commit any generated changes with the Rust contract change.
|
||||
@@ -0,0 +1,3 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export type SharedInstanceUnavailableReason = "deleted" | "access_revoked" | "quarantined";
|
||||
@@ -0,0 +1,3 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export type WarningPayload = { message: string, };
|
||||
@@ -0,0 +1,78 @@
|
||||
declare type u8 = number
|
||||
declare type u16 = number
|
||||
declare type u32 = number
|
||||
declare type u64 = bigint
|
||||
declare type u128 = bigint
|
||||
declare type usize = bigint
|
||||
declare type i8 = number
|
||||
declare type i16 = number
|
||||
declare type i32 = number
|
||||
declare type i64 = bigint
|
||||
declare type i128 = bigint
|
||||
declare type isize = bigint
|
||||
declare type NonZeroU8 = number
|
||||
declare type NonZeroU16 = number
|
||||
declare type NonZeroU32 = number
|
||||
declare type NonZeroU64 = bigint
|
||||
declare type NonZeroU128 = bigint
|
||||
declare type NonZeroUsize = bigint
|
||||
declare type NonZeroI8 = number
|
||||
declare type NonZeroI16 = number
|
||||
declare type NonZeroI32 = number
|
||||
declare type NonZeroI64 = bigint
|
||||
declare type NonZeroI128 = bigint
|
||||
declare type NonZeroIsize = bigint
|
||||
declare type f32 = number
|
||||
declare type f64 = number
|
||||
|
||||
declare type ArrayLengthMutationKeys = "splice" | "push" | "pop" | "shift" | "unshift"
|
||||
declare type FixedLengthArray<T, L extends number, TObj = [T, ...Array<T>]> =
|
||||
Pick<TObj, Exclude<keyof TObj, ArrayLengthMutationKeys>>
|
||||
& {
|
||||
readonly length: L
|
||||
[ I : number ] : T
|
||||
[Symbol.iterator]: () => IterableIterator<T>
|
||||
}
|
||||
|
||||
export type AppEvent = { tag: "loading", value: LoadingPayload } | { tag: "process", value: ProcessPayload } | { tag: "instance", value: InstancePayload } | { tag: "instance_bulk_update_progress", value: InstanceBulkUpdateProgressPayload } | { tag: "install_job", value: InstallJobSnapshot } | { tag: "command", value: CommandPayload } | { tag: "warning", value: WarningPayload } | { tag: "friend", value: FriendPayload } | { tag: "notification", value: string } | { tag: "log", value: LogPayload } | { tag: "ads_consent_required", value: boolean }
|
||||
export type LoadingBarType = { tag: "legacy_data_migration" } | { tag: "directory_move", value: { old: string, new: string } } | { tag: "java_download", value: { version: u32 } } | { tag: "pack_file_download", value: { instance_id: string, pack_name: string, icon: string | undefined, pack_version: string } } | { tag: "pack_download", value: { instance_id: string, pack_name: string, icon: string | undefined, pack_id: string | undefined, pack_version: string | undefined } } | { tag: "minecraft_download", value: { instance_id: string, instance_name: string } } | { tag: "instance_update", value: { instance_id: string, instance_name: string } } | { tag: "zip_extract", value: { instance_id: string, instance_name: string } } | { tag: "config_change", value: { new_path: string } } | { tag: "copy_instance", value: { import_location: string, instance_name: string } } | { tag: "launcher_update", value: { version: string, current_version: string } }
|
||||
export type LoadingPayload = { event: LoadingBarType, loader_uuid: string, fraction: f64 | undefined, message: string }
|
||||
export type WarningPayload = { message: string }
|
||||
export type InstanceBulkUpdateProgressPayload = { instanceId: string, stage: InstanceBulkUpdateProgressStage, current: u64, total: u64 }
|
||||
export type InstanceBulkUpdateProgressStage = { tag: "resolving_versions" } | { tag: "downloading" } | { tag: "finishing" }
|
||||
export type CommandPayload = { tag: "InstallMod", value: { id: string } } | { tag: "InstallVersion", value: { id: string } } | { tag: "InstallModpack", value: { id: string } } | { tag: "InstallServer", value: { id: string } } | { tag: "LaunchInstance", value: { id: string, server: string | undefined, singleplayer_world: string | undefined } } | { tag: "InstallSharedInstanceInvite", value: { invite_id: string } } | { tag: "RunMRPack", value: { path: string } }
|
||||
export type ProcessPayload = { instance_id: string, uuid: string, event: ProcessPayloadType, message: string }
|
||||
export type ProcessPayloadType = { tag: "launched" } | { tag: "finished" }
|
||||
export type InstancePayload = { instance_id: string, event: InstancePayloadType }
|
||||
export type InstancePayloadType = { tag: "created" } | { tag: "synced" } | { tag: "servers_updated" } | { tag: "world_updated", value: { world: string } } | { tag: "server_joined", value: { host: string, port: u16, timestamp: string } } | { tag: "edited" } | { tag: "content_install_finished", value: { project_ids: string[] } } | { tag: "content_install_failed", value: { project_ids: string[], message: string } } | { tag: "removed" }
|
||||
export type FriendPayload = { tag: "friend_request", value: { from: string } } | { tag: "user_offline", value: { id: string } } | { tag: "status_update", value: { user_status: FriendStatusPayload } } | { tag: "status_sync" }
|
||||
export type FriendStatusPayload = { user_id: string, profile_name: string | undefined, last_update: string }
|
||||
export type SharedInstanceUnavailableReason = { tag: "deleted" } | { tag: "access_revoked" } | { tag: "quarantined" }
|
||||
export type LogEvent = { tag: "log4j", value: Log4jEvent } | { tag: "legacy", value: { message: string } }
|
||||
export type LogPayload = { instance_id: string, event: LogEvent }
|
||||
export type Log4jEvent = { timestamp_millis: i64 | undefined, logger_name: string | undefined, level: string | undefined, thread_name: string | undefined, message: string | undefined, throwable: string | undefined }
|
||||
export type ModLoader = { tag: "vanilla" } | { tag: "forge" } | { tag: "fabric" } | { tag: "quilt" } | { tag: "neoforge" }
|
||||
export type InstallJobSnapshot = { job_id: string, instance_id: string | undefined, kind: InstallJobKind, status: InstallJobStatus, target: InstallTarget, phase: InstallPhaseId, progress: InstallProgress | undefined, details: InstallPhaseDetails, display: InstallJobDisplay | undefined, error: InstallErrorView | undefined, rollback_error: InstallErrorView | undefined, created: string, modified: string, finished: string | undefined }
|
||||
export type InstallJobKind = { tag: "create_instance" } | { tag: "create_modpack_instance" } | { tag: "create_shared_instance" } | { tag: "import_instance" } | { tag: "duplicate_instance" } | { tag: "install_existing_instance" } | { tag: "install_pack_to_existing_instance" } | { tag: "update_shared_instance" }
|
||||
export type InstallJobStatus = { tag: "queued" } | { tag: "running" } | { tag: "succeeded" } | { tag: "failed" } | { tag: "interrupted" } | { tag: "canceled" }
|
||||
export type InstallTarget = { tag: "new_instance", value: { instance_id: string | undefined } } | { tag: "existing_instance", value: { instance_id: string } }
|
||||
export type InstallPhaseId = { tag: "preparing_instance" } | { tag: "resolving_pack" } | { tag: "downloading_pack_file" } | { tag: "reading_pack_manifest" } | { tag: "downloading_content" } | { tag: "extracting_overrides" } | { tag: "resolving_minecraft" } | { tag: "resolving_loader" } | { tag: "preparing_java" } | { tag: "downloading_minecraft" } | { tag: "running_loader_processors" } | { tag: "finalizing" } | { tag: "rolling_back" }
|
||||
export type InstallProgress = { current: u64, total: u64, secondary: InstallProgressSecondary | undefined }
|
||||
export type InstallProgressSecondary = { current: u64, total: u64 }
|
||||
export type InstallPhaseDetails = { tag: "empty" } | { tag: "instance", value: { name: string } } | { tag: "minecraft", value: { game_version: string, loader: ModLoader } } | { tag: "java", value: { major_version: u32, step: InstallJavaStep } } | { tag: "modpack", value: { project_id: string | undefined, version_id: string | undefined, title: string | undefined } } | { tag: "import", value: { launcher_type: ImportLauncherType, instance_folder: string } }
|
||||
export type InstallJavaStep = { tag: "resolving" } | { tag: "fetching_metadata" } | { tag: "downloading" } | { tag: "extracting" } | { tag: "validating" }
|
||||
export type InstallJobDisplay = { title: string, icon: string | undefined }
|
||||
export type InstallErrorView = { code: string, phase: InstallPhaseId | undefined, message: string, reason: SharedInstanceUnavailableReason | undefined, api: InstallApiErrorDetails | undefined, context: InstallErrorContext | undefined }
|
||||
export type InstallApiErrorDetails = { error: string, status: u16 | undefined, method: string | undefined, url: string | undefined, route: string | undefined }
|
||||
export type InstallErrorContext = { operation: string, source_path: string | undefined, target_path: string | undefined, file_path: string | undefined, entry_path: string | undefined, urls: string[], expected_hash: string | undefined, expected_size: u64 | undefined, project_id: string | undefined, version_id: string | undefined, minecraft_version: string | undefined, loader: string | undefined, java_version: u32 | undefined, os: string | undefined, arch: string | undefined }
|
||||
export type ImportLauncherType = { tag: "MultiMC" } | { tag: "PrismLauncher" } | { tag: "ATLauncher" } | { tag: "GDLauncher" } | { tag: "Curseforge" } | { tag: "Unknown" }
|
||||
|
||||
export type Type = "AppEvent" | "LoadingBarType" | "LoadingPayload" | "WarningPayload" | "InstanceBulkUpdateProgressPayload" | "InstanceBulkUpdateProgressStage" | "CommandPayload" | "ProcessPayload" | "ProcessPayloadType" | "InstancePayload" | "InstancePayloadType" | "FriendPayload" | "FriendStatusPayload" | "LogEvent" | "LogPayload" | "Log4jEvent" | "InstallJobSnapshot" | "InstallJobKind" | "InstallJobStatus" | "InstallTarget" | "InstallPhaseId" | "InstallProgress" | "InstallProgressSecondary" | "InstallPhaseDetails" | "InstallJavaStep" | "InstallJobDisplay" | "InstallErrorView" | "InstallApiErrorDetails" | "InstallErrorContext" | "ImportLauncherType" | "ModLoader" | "SharedInstanceUnavailableReason"
|
||||
declare type ValueType<T extends Type> = T extends "AppEvent" ? AppEvent : T extends "LoadingBarType" ? LoadingBarType : T extends "LoadingPayload" ? LoadingPayload : T extends "WarningPayload" ? WarningPayload : T extends "InstanceBulkUpdateProgressPayload" ? InstanceBulkUpdateProgressPayload : T extends "InstanceBulkUpdateProgressStage" ? InstanceBulkUpdateProgressStage : T extends "CommandPayload" ? CommandPayload : T extends "ProcessPayload" ? ProcessPayload : T extends "ProcessPayloadType" ? ProcessPayloadType : T extends "InstancePayload" ? InstancePayload : T extends "InstancePayloadType" ? InstancePayloadType : T extends "FriendPayload" ? FriendPayload : T extends "FriendStatusPayload" ? FriendStatusPayload : T extends "LogEvent" ? LogEvent : T extends "LogPayload" ? LogPayload : T extends "Log4jEvent" ? Log4jEvent : T extends "InstallJobSnapshot" ? InstallJobSnapshot : T extends "InstallJobKind" ? InstallJobKind : T extends "InstallJobStatus" ? InstallJobStatus : T extends "InstallTarget" ? InstallTarget : T extends "InstallPhaseId" ? InstallPhaseId : T extends "InstallProgress" ? InstallProgress : T extends "InstallProgressSecondary" ? InstallProgressSecondary : T extends "InstallPhaseDetails" ? InstallPhaseDetails : T extends "InstallJavaStep" ? InstallJavaStep : T extends "InstallJobDisplay" ? InstallJobDisplay : T extends "InstallErrorView" ? InstallErrorView : T extends "InstallApiErrorDetails" ? InstallApiErrorDetails : T extends "InstallErrorContext" ? InstallErrorContext : T extends "ImportLauncherType" ? ImportLauncherType : T extends "ModLoader" ? ModLoader : T extends "SharedInstanceUnavailableReason" ? SharedInstanceUnavailableReason : void
|
||||
|
||||
export interface Result<T extends Type> {
|
||||
value: ValueType<T>;
|
||||
bytes: Uint8Array;
|
||||
}
|
||||
|
||||
export function deserialize<T extends Type>(type: T, bytes: Uint8Array): Result<T>
|
||||
@@ -0,0 +1,953 @@
|
||||
const BITS_PER_BYTE = 8, BITS_PER_VARINT_BYTE = 7, U8_BYTES = 1, U16_BYTES = 2, U32_BYTES = 4, U64_BYTES = 8, U128_BYTES = 16
|
||||
|
||||
const de_zig_zag_signed = (n) => (n >> 1n) ^ (-(n & 0b1n))
|
||||
const zig_zag = (n_bytes, n) => (n << 1n) ^ (n >> BigInt(n_bytes * BITS_PER_BYTE - 1))
|
||||
const varint_max = (n_bytes) => Math.floor((n_bytes * BITS_PER_BYTE + (BITS_PER_BYTE - 1)) / BITS_PER_VARINT_BYTE)
|
||||
const max_of_last_byte = (n_bytes) => (1 << (n_bytes * BITS_PER_BYTE) % 7) - 1
|
||||
const to_number_if_safe = (n) => Number.MAX_SAFE_INTEGER < ((n < 0n) ? -n : n) ? n : Number(n)
|
||||
const varint = (n_bytes, n) => { let value = BigInt(n), out = []; for (let i = 0; i < varint_max(n_bytes); i++) { out.push(Number(value & 0xFFn)); if (value < 128n) { return out } out[i] |= 0x80; value >>= 7n } }
|
||||
|
||||
class Deserializer {
|
||||
constructor(bytes_in) { this.bytes = Array.from(bytes_in); }
|
||||
pop_next = () => { const next = this.bytes.shift(); if (next === undefined) { throw "input buffer too small" } return next }
|
||||
pop_n = (n) => { const bytes = Array(); for (let i = 0; i < n; i++) { bytes.push(this.bytes.shift()) } return bytes }
|
||||
get_int8 = (signed) => signed ? new Int8Array([this.pop_next()])[0] : this.pop_next();
|
||||
try_take = (n_bytes) => { let out = 0n, v_max = varint_max(n_bytes); for (let i = 0; i < v_max; i++) { const val = this.pop_next(), carry = BigInt(val & 0x7F); out |= carry << BigInt(7 * i); if ((val & 0x80) === 0) { if (i === v_max - 1 && val > max_of_last_byte(n_bytes)) { throw "Bad Variant" } else return out } } throw "Bad Variant"; }
|
||||
deserialize_bool = () => { const byte = this.pop_next(); return byte === undefined ? undefined : byte > 0 ? true : false }
|
||||
deserialize_number = (n_bytes, signed) => { if (n_bytes === U8_BYTES) { return this.get_int8(signed) } else if (n_bytes === U16_BYTES || n_bytes === U32_BYTES || n_bytes === U64_BYTES || n_bytes === U128_BYTES) { const val = this.try_take(n_bytes); return to_number_if_safe(signed ? de_zig_zag_signed(val) : val) } else { throw "byte count not supported" } }
|
||||
deserialize_number_float = (n_bytes) => { const b_buffer = new ArrayBuffer(n_bytes), b_view = new DataView(b_buffer); this.pop_n(n_bytes).forEach((b, i) => b_view.setUint8(i, b)); if (n_bytes === U32_BYTES) { return b_view.getFloat32(0, true) } else if (n_bytes === U64_BYTES) { return b_view.getFloat64(0, true) } else { throw "byte count not supported" } }
|
||||
deserialize_string = () => new TextDecoder().decode(new Uint8Array(this.pop_n(Number(this.try_take(U32_BYTES)))))
|
||||
deserialize_array = (des, len) => Array.from({length: len === undefined ? Number(this.try_take(U32_BYTES)) : len}, (v, i) => des(this))
|
||||
deserialize_string_key_map = (des) => { return [...Array(Number(this.try_take(U32_BYTES)))].reduce((prev) => { prev[this.deserialize_string()] = des(this); return prev }, {}) }
|
||||
deserialize_map = (des) => { return [...Array(Number(this.try_take(U32_BYTES)))].reduce((prev) => { const d = des(this); prev.set(d[0], d[1]); return prev }, new Map()) }
|
||||
release_bytes = () => { return new Uint8Array(this.bytes); }
|
||||
}
|
||||
|
||||
function deserialize_APP_EVENT(d) {
|
||||
switch (d.deserialize_number(U32_BYTES, false)) {
|
||||
case 0:
|
||||
return {
|
||||
tag: "loading",
|
||||
value: deserialize_LOADING_PAYLOAD(d)
|
||||
};
|
||||
case 1:
|
||||
return {
|
||||
tag: "process",
|
||||
value: deserialize_PROCESS_PAYLOAD(d)
|
||||
};
|
||||
case 2:
|
||||
return {
|
||||
tag: "instance",
|
||||
value: deserialize_INSTANCE_PAYLOAD(d)
|
||||
};
|
||||
case 3:
|
||||
return {
|
||||
tag: "instance_bulk_update_progress",
|
||||
value: deserialize_INSTANCE_BULK_UPDATE_PROGRESS_PAYLOAD(d)
|
||||
};
|
||||
case 4:
|
||||
return {
|
||||
tag: "install_job",
|
||||
value: deserialize_INSTALL_JOB_SNAPSHOT(d)
|
||||
};
|
||||
case 5:
|
||||
return {
|
||||
tag: "command",
|
||||
value: deserialize_COMMAND_PAYLOAD(d)
|
||||
};
|
||||
case 6:
|
||||
return {
|
||||
tag: "warning",
|
||||
value: deserialize_WARNING_PAYLOAD(d)
|
||||
};
|
||||
case 7:
|
||||
return {
|
||||
tag: "friend",
|
||||
value: deserialize_FRIEND_PAYLOAD(d)
|
||||
};
|
||||
case 8:
|
||||
return {
|
||||
tag: "notification",
|
||||
value: d.deserialize_string()
|
||||
};
|
||||
case 9:
|
||||
return {
|
||||
tag: "log",
|
||||
value: deserialize_LOG_PAYLOAD(d)
|
||||
};
|
||||
case 10:
|
||||
return {
|
||||
tag: "ads_consent_required",
|
||||
value: d.deserialize_bool()
|
||||
};
|
||||
default:
|
||||
throw "variant not implemented"
|
||||
}
|
||||
}
|
||||
|
||||
function deserialize_LOADING_BAR_TYPE(d) {
|
||||
switch (d.deserialize_number(U32_BYTES, false)) {
|
||||
case 0:
|
||||
return {
|
||||
tag: "legacy_data_migration"
|
||||
};
|
||||
case 1:
|
||||
return {
|
||||
tag: "directory_move",
|
||||
value: {
|
||||
old: d.deserialize_string(),
|
||||
new: d.deserialize_string()
|
||||
}
|
||||
};
|
||||
case 2:
|
||||
return {
|
||||
tag: "java_download",
|
||||
value: {
|
||||
version: d.deserialize_number(U32_BYTES, false)
|
||||
}
|
||||
};
|
||||
case 3:
|
||||
return {
|
||||
tag: "pack_file_download",
|
||||
value: {
|
||||
instance_id: d.deserialize_string(),
|
||||
pack_name: d.deserialize_string(),
|
||||
icon: (d.deserialize_number(U32_BYTES, false) === 0) ? undefined : d.deserialize_string(),
|
||||
pack_version: d.deserialize_string()
|
||||
}
|
||||
};
|
||||
case 4:
|
||||
return {
|
||||
tag: "pack_download",
|
||||
value: {
|
||||
instance_id: d.deserialize_string(),
|
||||
pack_name: d.deserialize_string(),
|
||||
icon: (d.deserialize_number(U32_BYTES, false) === 0) ? undefined : d.deserialize_string(),
|
||||
pack_id: (d.deserialize_number(U32_BYTES, false) === 0) ? undefined : d.deserialize_string(),
|
||||
pack_version: (d.deserialize_number(U32_BYTES, false) === 0) ? undefined : d.deserialize_string()
|
||||
}
|
||||
};
|
||||
case 5:
|
||||
return {
|
||||
tag: "minecraft_download",
|
||||
value: {
|
||||
instance_id: d.deserialize_string(),
|
||||
instance_name: d.deserialize_string()
|
||||
}
|
||||
};
|
||||
case 6:
|
||||
return {
|
||||
tag: "instance_update",
|
||||
value: {
|
||||
instance_id: d.deserialize_string(),
|
||||
instance_name: d.deserialize_string()
|
||||
}
|
||||
};
|
||||
case 7:
|
||||
return {
|
||||
tag: "zip_extract",
|
||||
value: {
|
||||
instance_id: d.deserialize_string(),
|
||||
instance_name: d.deserialize_string()
|
||||
}
|
||||
};
|
||||
case 8:
|
||||
return {
|
||||
tag: "config_change",
|
||||
value: {
|
||||
new_path: d.deserialize_string()
|
||||
}
|
||||
};
|
||||
case 9:
|
||||
return {
|
||||
tag: "copy_instance",
|
||||
value: {
|
||||
import_location: d.deserialize_string(),
|
||||
instance_name: d.deserialize_string()
|
||||
}
|
||||
};
|
||||
case 10:
|
||||
return {
|
||||
tag: "launcher_update",
|
||||
value: {
|
||||
version: d.deserialize_string(),
|
||||
current_version: d.deserialize_string()
|
||||
}
|
||||
};
|
||||
default:
|
||||
throw "variant not implemented"
|
||||
}
|
||||
}
|
||||
|
||||
function deserialize_LOADING_PAYLOAD(d) {
|
||||
return {
|
||||
event: deserialize_LOADING_BAR_TYPE(d),
|
||||
loader_uuid: d.deserialize_string(),
|
||||
fraction: (d.deserialize_number(U32_BYTES, false) === 0) ? undefined : d.deserialize_number_float(U64_BYTES),
|
||||
message: d.deserialize_string()
|
||||
};
|
||||
}
|
||||
|
||||
function deserialize_WARNING_PAYLOAD(d) {
|
||||
return {
|
||||
message: d.deserialize_string()
|
||||
};
|
||||
}
|
||||
|
||||
function deserialize_INSTANCE_BULK_UPDATE_PROGRESS_PAYLOAD(d) {
|
||||
return {
|
||||
instanceId: d.deserialize_string(),
|
||||
stage: deserialize_INSTANCE_BULK_UPDATE_PROGRESS_STAGE(d),
|
||||
current: d.deserialize_number(U64_BYTES, false),
|
||||
total: d.deserialize_number(U64_BYTES, false)
|
||||
};
|
||||
}
|
||||
|
||||
function deserialize_INSTANCE_BULK_UPDATE_PROGRESS_STAGE(d) {
|
||||
switch (d.deserialize_number(U32_BYTES, false)) {
|
||||
case 0:
|
||||
return {
|
||||
tag: "resolving_versions"
|
||||
};
|
||||
case 1:
|
||||
return {
|
||||
tag: "downloading"
|
||||
};
|
||||
case 2:
|
||||
return {
|
||||
tag: "finishing"
|
||||
};
|
||||
default:
|
||||
throw "variant not implemented"
|
||||
}
|
||||
}
|
||||
|
||||
function deserialize_COMMAND_PAYLOAD(d) {
|
||||
switch (d.deserialize_number(U32_BYTES, false)) {
|
||||
case 0:
|
||||
return {
|
||||
tag: "InstallMod",
|
||||
value: {
|
||||
id: d.deserialize_string()
|
||||
}
|
||||
};
|
||||
case 1:
|
||||
return {
|
||||
tag: "InstallVersion",
|
||||
value: {
|
||||
id: d.deserialize_string()
|
||||
}
|
||||
};
|
||||
case 2:
|
||||
return {
|
||||
tag: "InstallModpack",
|
||||
value: {
|
||||
id: d.deserialize_string()
|
||||
}
|
||||
};
|
||||
case 3:
|
||||
return {
|
||||
tag: "InstallServer",
|
||||
value: {
|
||||
id: d.deserialize_string()
|
||||
}
|
||||
};
|
||||
case 4:
|
||||
return {
|
||||
tag: "LaunchInstance",
|
||||
value: {
|
||||
id: d.deserialize_string(),
|
||||
server: (d.deserialize_number(U32_BYTES, false) === 0) ? undefined : d.deserialize_string(),
|
||||
singleplayer_world: (d.deserialize_number(U32_BYTES, false) === 0) ? undefined : d.deserialize_string()
|
||||
}
|
||||
};
|
||||
case 5:
|
||||
return {
|
||||
tag: "InstallSharedInstanceInvite",
|
||||
value: {
|
||||
invite_id: d.deserialize_string()
|
||||
}
|
||||
};
|
||||
case 6:
|
||||
return {
|
||||
tag: "RunMRPack",
|
||||
value: {
|
||||
path: d.deserialize_string()
|
||||
}
|
||||
};
|
||||
default:
|
||||
throw "variant not implemented"
|
||||
}
|
||||
}
|
||||
|
||||
function deserialize_PROCESS_PAYLOAD(d) {
|
||||
return {
|
||||
instance_id: d.deserialize_string(),
|
||||
uuid: d.deserialize_string(),
|
||||
event: deserialize_PROCESS_PAYLOAD_TYPE(d),
|
||||
message: d.deserialize_string()
|
||||
};
|
||||
}
|
||||
|
||||
function deserialize_PROCESS_PAYLOAD_TYPE(d) {
|
||||
switch (d.deserialize_number(U32_BYTES, false)) {
|
||||
case 0:
|
||||
return {
|
||||
tag: "launched"
|
||||
};
|
||||
case 1:
|
||||
return {
|
||||
tag: "finished"
|
||||
};
|
||||
default:
|
||||
throw "variant not implemented"
|
||||
}
|
||||
}
|
||||
|
||||
function deserialize_INSTANCE_PAYLOAD(d) {
|
||||
return {
|
||||
instance_id: d.deserialize_string(),
|
||||
event: deserialize_INSTANCE_PAYLOAD_TYPE(d)
|
||||
};
|
||||
}
|
||||
|
||||
function deserialize_INSTANCE_PAYLOAD_TYPE(d) {
|
||||
switch (d.deserialize_number(U32_BYTES, false)) {
|
||||
case 0:
|
||||
return {
|
||||
tag: "created"
|
||||
};
|
||||
case 1:
|
||||
return {
|
||||
tag: "synced"
|
||||
};
|
||||
case 2:
|
||||
return {
|
||||
tag: "servers_updated"
|
||||
};
|
||||
case 3:
|
||||
return {
|
||||
tag: "world_updated",
|
||||
value: {
|
||||
world: d.deserialize_string()
|
||||
}
|
||||
};
|
||||
case 4:
|
||||
return {
|
||||
tag: "server_joined",
|
||||
value: {
|
||||
host: d.deserialize_string(),
|
||||
port: d.deserialize_number(U16_BYTES, false),
|
||||
timestamp: d.deserialize_string()
|
||||
}
|
||||
};
|
||||
case 5:
|
||||
return {
|
||||
tag: "edited"
|
||||
};
|
||||
case 6:
|
||||
return {
|
||||
tag: "content_install_finished",
|
||||
value: {
|
||||
project_ids: d.deserialize_array(() => d.deserialize_string())
|
||||
}
|
||||
};
|
||||
case 7:
|
||||
return {
|
||||
tag: "content_install_failed",
|
||||
value: {
|
||||
project_ids: d.deserialize_array(() => d.deserialize_string()),
|
||||
message: d.deserialize_string()
|
||||
}
|
||||
};
|
||||
case 8:
|
||||
return {
|
||||
tag: "removed"
|
||||
};
|
||||
default:
|
||||
throw "variant not implemented"
|
||||
}
|
||||
}
|
||||
|
||||
function deserialize_FRIEND_PAYLOAD(d) {
|
||||
switch (d.deserialize_number(U32_BYTES, false)) {
|
||||
case 0:
|
||||
return {
|
||||
tag: "friend_request",
|
||||
value: {
|
||||
from: d.deserialize_string()
|
||||
}
|
||||
};
|
||||
case 1:
|
||||
return {
|
||||
tag: "user_offline",
|
||||
value: {
|
||||
id: d.deserialize_string()
|
||||
}
|
||||
};
|
||||
case 2:
|
||||
return {
|
||||
tag: "status_update",
|
||||
value: {
|
||||
user_status: deserialize_FRIEND_STATUS_PAYLOAD(d)
|
||||
}
|
||||
};
|
||||
case 3:
|
||||
return {
|
||||
tag: "status_sync"
|
||||
};
|
||||
default:
|
||||
throw "variant not implemented"
|
||||
}
|
||||
}
|
||||
|
||||
function deserialize_FRIEND_STATUS_PAYLOAD(d) {
|
||||
return {
|
||||
user_id: d.deserialize_string(),
|
||||
profile_name: (d.deserialize_number(U32_BYTES, false) === 0) ? undefined : d.deserialize_string(),
|
||||
last_update: d.deserialize_string()
|
||||
};
|
||||
}
|
||||
|
||||
function deserialize_LOG_EVENT(d) {
|
||||
switch (d.deserialize_number(U32_BYTES, false)) {
|
||||
case 0:
|
||||
return {
|
||||
tag: "log4j",
|
||||
value: deserialize_LOG4J_EVENT(d)
|
||||
};
|
||||
case 1:
|
||||
return {
|
||||
tag: "legacy",
|
||||
value: {
|
||||
message: d.deserialize_string()
|
||||
}
|
||||
};
|
||||
default:
|
||||
throw "variant not implemented"
|
||||
}
|
||||
}
|
||||
|
||||
function deserialize_LOG_PAYLOAD(d) {
|
||||
return {
|
||||
instance_id: d.deserialize_string(),
|
||||
event: deserialize_LOG_EVENT(d)
|
||||
};
|
||||
}
|
||||
|
||||
function deserialize_LOG4J_EVENT(d) {
|
||||
return {
|
||||
timestamp_millis: (d.deserialize_number(U32_BYTES, false) === 0) ? undefined : d.deserialize_number(U64_BYTES, true),
|
||||
logger_name: (d.deserialize_number(U32_BYTES, false) === 0) ? undefined : d.deserialize_string(),
|
||||
level: (d.deserialize_number(U32_BYTES, false) === 0) ? undefined : d.deserialize_string(),
|
||||
thread_name: (d.deserialize_number(U32_BYTES, false) === 0) ? undefined : d.deserialize_string(),
|
||||
message: (d.deserialize_number(U32_BYTES, false) === 0) ? undefined : d.deserialize_string(),
|
||||
throwable: (d.deserialize_number(U32_BYTES, false) === 0) ? undefined : d.deserialize_string()
|
||||
};
|
||||
}
|
||||
|
||||
function deserialize_INSTALL_JOB_SNAPSHOT(d) {
|
||||
return {
|
||||
job_id: d.deserialize_string(),
|
||||
instance_id: (d.deserialize_number(U32_BYTES, false) === 0) ? undefined : d.deserialize_string(),
|
||||
kind: deserialize_INSTALL_JOB_KIND(d),
|
||||
status: deserialize_INSTALL_JOB_STATUS(d),
|
||||
target: deserialize_INSTALL_TARGET(d),
|
||||
phase: deserialize_INSTALL_PHASE_ID(d),
|
||||
progress: (d.deserialize_number(U32_BYTES, false) === 0) ? undefined : deserialize_INSTALL_PROGRESS(d),
|
||||
details: deserialize_INSTALL_PHASE_DETAILS(d),
|
||||
display: (d.deserialize_number(U32_BYTES, false) === 0) ? undefined : deserialize_INSTALL_JOB_DISPLAY(d),
|
||||
error: (d.deserialize_number(U32_BYTES, false) === 0) ? undefined : deserialize_INSTALL_ERROR_VIEW(d),
|
||||
rollback_error: (d.deserialize_number(U32_BYTES, false) === 0) ? undefined : deserialize_INSTALL_ERROR_VIEW(d),
|
||||
created: d.deserialize_string(),
|
||||
modified: d.deserialize_string(),
|
||||
finished: (d.deserialize_number(U32_BYTES, false) === 0) ? undefined : d.deserialize_string()
|
||||
};
|
||||
}
|
||||
|
||||
function deserialize_INSTALL_JOB_KIND(d) {
|
||||
switch (d.deserialize_number(U32_BYTES, false)) {
|
||||
case 0:
|
||||
return {
|
||||
tag: "create_instance"
|
||||
};
|
||||
case 1:
|
||||
return {
|
||||
tag: "create_modpack_instance"
|
||||
};
|
||||
case 2:
|
||||
return {
|
||||
tag: "create_shared_instance"
|
||||
};
|
||||
case 3:
|
||||
return {
|
||||
tag: "import_instance"
|
||||
};
|
||||
case 4:
|
||||
return {
|
||||
tag: "duplicate_instance"
|
||||
};
|
||||
case 5:
|
||||
return {
|
||||
tag: "install_existing_instance"
|
||||
};
|
||||
case 6:
|
||||
return {
|
||||
tag: "install_pack_to_existing_instance"
|
||||
};
|
||||
case 7:
|
||||
return {
|
||||
tag: "update_shared_instance"
|
||||
};
|
||||
default:
|
||||
throw "variant not implemented"
|
||||
}
|
||||
}
|
||||
|
||||
function deserialize_INSTALL_JOB_STATUS(d) {
|
||||
switch (d.deserialize_number(U32_BYTES, false)) {
|
||||
case 0:
|
||||
return {
|
||||
tag: "queued"
|
||||
};
|
||||
case 1:
|
||||
return {
|
||||
tag: "running"
|
||||
};
|
||||
case 2:
|
||||
return {
|
||||
tag: "succeeded"
|
||||
};
|
||||
case 3:
|
||||
return {
|
||||
tag: "failed"
|
||||
};
|
||||
case 4:
|
||||
return {
|
||||
tag: "interrupted"
|
||||
};
|
||||
case 5:
|
||||
return {
|
||||
tag: "canceled"
|
||||
};
|
||||
default:
|
||||
throw "variant not implemented"
|
||||
}
|
||||
}
|
||||
|
||||
function deserialize_INSTALL_TARGET(d) {
|
||||
switch (d.deserialize_number(U32_BYTES, false)) {
|
||||
case 0:
|
||||
return {
|
||||
tag: "new_instance",
|
||||
value: {
|
||||
instance_id: (d.deserialize_number(U32_BYTES, false) === 0) ? undefined : d.deserialize_string()
|
||||
}
|
||||
};
|
||||
case 1:
|
||||
return {
|
||||
tag: "existing_instance",
|
||||
value: {
|
||||
instance_id: d.deserialize_string()
|
||||
}
|
||||
};
|
||||
default:
|
||||
throw "variant not implemented"
|
||||
}
|
||||
}
|
||||
|
||||
function deserialize_INSTALL_PHASE_ID(d) {
|
||||
switch (d.deserialize_number(U32_BYTES, false)) {
|
||||
case 0:
|
||||
return {
|
||||
tag: "preparing_instance"
|
||||
};
|
||||
case 1:
|
||||
return {
|
||||
tag: "resolving_pack"
|
||||
};
|
||||
case 2:
|
||||
return {
|
||||
tag: "downloading_pack_file"
|
||||
};
|
||||
case 3:
|
||||
return {
|
||||
tag: "reading_pack_manifest"
|
||||
};
|
||||
case 4:
|
||||
return {
|
||||
tag: "downloading_content"
|
||||
};
|
||||
case 5:
|
||||
return {
|
||||
tag: "extracting_overrides"
|
||||
};
|
||||
case 6:
|
||||
return {
|
||||
tag: "resolving_minecraft"
|
||||
};
|
||||
case 7:
|
||||
return {
|
||||
tag: "resolving_loader"
|
||||
};
|
||||
case 8:
|
||||
return {
|
||||
tag: "preparing_java"
|
||||
};
|
||||
case 9:
|
||||
return {
|
||||
tag: "downloading_minecraft"
|
||||
};
|
||||
case 10:
|
||||
return {
|
||||
tag: "running_loader_processors"
|
||||
};
|
||||
case 11:
|
||||
return {
|
||||
tag: "finalizing"
|
||||
};
|
||||
case 12:
|
||||
return {
|
||||
tag: "rolling_back"
|
||||
};
|
||||
default:
|
||||
throw "variant not implemented"
|
||||
}
|
||||
}
|
||||
|
||||
function deserialize_INSTALL_PROGRESS(d) {
|
||||
return {
|
||||
current: d.deserialize_number(U64_BYTES, false),
|
||||
total: d.deserialize_number(U64_BYTES, false),
|
||||
secondary: (d.deserialize_number(U32_BYTES, false) === 0) ? undefined : deserialize_INSTALL_PROGRESS_SECONDARY(d)
|
||||
};
|
||||
}
|
||||
|
||||
function deserialize_INSTALL_PROGRESS_SECONDARY(d) {
|
||||
return {
|
||||
current: d.deserialize_number(U64_BYTES, false),
|
||||
total: d.deserialize_number(U64_BYTES, false)
|
||||
};
|
||||
}
|
||||
|
||||
function deserialize_INSTALL_PHASE_DETAILS(d) {
|
||||
switch (d.deserialize_number(U32_BYTES, false)) {
|
||||
case 0:
|
||||
return {
|
||||
tag: "empty"
|
||||
};
|
||||
case 1:
|
||||
return {
|
||||
tag: "instance",
|
||||
value: {
|
||||
name: d.deserialize_string()
|
||||
}
|
||||
};
|
||||
case 2:
|
||||
return {
|
||||
tag: "minecraft",
|
||||
value: {
|
||||
game_version: d.deserialize_string(),
|
||||
loader: deserialize_MOD_LOADER(d)
|
||||
}
|
||||
};
|
||||
case 3:
|
||||
return {
|
||||
tag: "java",
|
||||
value: {
|
||||
major_version: d.deserialize_number(U32_BYTES, false),
|
||||
step: deserialize_INSTALL_JAVA_STEP(d)
|
||||
}
|
||||
};
|
||||
case 4:
|
||||
return {
|
||||
tag: "modpack",
|
||||
value: {
|
||||
project_id: (d.deserialize_number(U32_BYTES, false) === 0) ? undefined : d.deserialize_string(),
|
||||
version_id: (d.deserialize_number(U32_BYTES, false) === 0) ? undefined : d.deserialize_string(),
|
||||
title: (d.deserialize_number(U32_BYTES, false) === 0) ? undefined : d.deserialize_string()
|
||||
}
|
||||
};
|
||||
case 5:
|
||||
return {
|
||||
tag: "import",
|
||||
value: {
|
||||
launcher_type: deserialize_IMPORT_LAUNCHER_TYPE(d),
|
||||
instance_folder: d.deserialize_string()
|
||||
}
|
||||
};
|
||||
default:
|
||||
throw "variant not implemented"
|
||||
}
|
||||
}
|
||||
|
||||
function deserialize_INSTALL_JAVA_STEP(d) {
|
||||
switch (d.deserialize_number(U32_BYTES, false)) {
|
||||
case 0:
|
||||
return {
|
||||
tag: "resolving"
|
||||
};
|
||||
case 1:
|
||||
return {
|
||||
tag: "fetching_metadata"
|
||||
};
|
||||
case 2:
|
||||
return {
|
||||
tag: "downloading"
|
||||
};
|
||||
case 3:
|
||||
return {
|
||||
tag: "extracting"
|
||||
};
|
||||
case 4:
|
||||
return {
|
||||
tag: "validating"
|
||||
};
|
||||
default:
|
||||
throw "variant not implemented"
|
||||
}
|
||||
}
|
||||
|
||||
function deserialize_INSTALL_JOB_DISPLAY(d) {
|
||||
return {
|
||||
title: d.deserialize_string(),
|
||||
icon: (d.deserialize_number(U32_BYTES, false) === 0) ? undefined : d.deserialize_string()
|
||||
};
|
||||
}
|
||||
|
||||
function deserialize_INSTALL_ERROR_VIEW(d) {
|
||||
return {
|
||||
code: d.deserialize_string(),
|
||||
phase: (d.deserialize_number(U32_BYTES, false) === 0) ? undefined : deserialize_INSTALL_PHASE_ID(d),
|
||||
message: d.deserialize_string(),
|
||||
reason: (d.deserialize_number(U32_BYTES, false) === 0) ? undefined : deserialize_SHARED_INSTANCE_UNAVAILABLE_REASON(d),
|
||||
api: (d.deserialize_number(U32_BYTES, false) === 0) ? undefined : deserialize_INSTALL_API_ERROR_DETAILS(d),
|
||||
context: (d.deserialize_number(U32_BYTES, false) === 0) ? undefined : deserialize_INSTALL_ERROR_CONTEXT(d)
|
||||
};
|
||||
}
|
||||
|
||||
function deserialize_INSTALL_API_ERROR_DETAILS(d) {
|
||||
return {
|
||||
error: d.deserialize_string(),
|
||||
status: (d.deserialize_number(U32_BYTES, false) === 0) ? undefined : d.deserialize_number(U16_BYTES, false),
|
||||
method: (d.deserialize_number(U32_BYTES, false) === 0) ? undefined : d.deserialize_string(),
|
||||
url: (d.deserialize_number(U32_BYTES, false) === 0) ? undefined : d.deserialize_string(),
|
||||
route: (d.deserialize_number(U32_BYTES, false) === 0) ? undefined : d.deserialize_string()
|
||||
};
|
||||
}
|
||||
|
||||
function deserialize_INSTALL_ERROR_CONTEXT(d) {
|
||||
return {
|
||||
operation: d.deserialize_string(),
|
||||
source_path: (d.deserialize_number(U32_BYTES, false) === 0) ? undefined : d.deserialize_string(),
|
||||
target_path: (d.deserialize_number(U32_BYTES, false) === 0) ? undefined : d.deserialize_string(),
|
||||
file_path: (d.deserialize_number(U32_BYTES, false) === 0) ? undefined : d.deserialize_string(),
|
||||
entry_path: (d.deserialize_number(U32_BYTES, false) === 0) ? undefined : d.deserialize_string(),
|
||||
urls: d.deserialize_array(() => d.deserialize_string()),
|
||||
expected_hash: (d.deserialize_number(U32_BYTES, false) === 0) ? undefined : d.deserialize_string(),
|
||||
expected_size: (d.deserialize_number(U32_BYTES, false) === 0) ? undefined : d.deserialize_number(U64_BYTES, false),
|
||||
project_id: (d.deserialize_number(U32_BYTES, false) === 0) ? undefined : d.deserialize_string(),
|
||||
version_id: (d.deserialize_number(U32_BYTES, false) === 0) ? undefined : d.deserialize_string(),
|
||||
minecraft_version: (d.deserialize_number(U32_BYTES, false) === 0) ? undefined : d.deserialize_string(),
|
||||
loader: (d.deserialize_number(U32_BYTES, false) === 0) ? undefined : d.deserialize_string(),
|
||||
java_version: (d.deserialize_number(U32_BYTES, false) === 0) ? undefined : d.deserialize_number(U32_BYTES, false),
|
||||
os: (d.deserialize_number(U32_BYTES, false) === 0) ? undefined : d.deserialize_string(),
|
||||
arch: (d.deserialize_number(U32_BYTES, false) === 0) ? undefined : d.deserialize_string()
|
||||
};
|
||||
}
|
||||
|
||||
function deserialize_IMPORT_LAUNCHER_TYPE(d) {
|
||||
switch (d.deserialize_number(U32_BYTES, false)) {
|
||||
case 0:
|
||||
return {
|
||||
tag: "MultiMC"
|
||||
};
|
||||
case 1:
|
||||
return {
|
||||
tag: "PrismLauncher"
|
||||
};
|
||||
case 2:
|
||||
return {
|
||||
tag: "ATLauncher"
|
||||
};
|
||||
case 3:
|
||||
return {
|
||||
tag: "GDLauncher"
|
||||
};
|
||||
case 4:
|
||||
return {
|
||||
tag: "Curseforge"
|
||||
};
|
||||
case 5:
|
||||
return {
|
||||
tag: "Unknown"
|
||||
};
|
||||
default:
|
||||
throw "variant not implemented"
|
||||
}
|
||||
}
|
||||
|
||||
function deserialize_MOD_LOADER(d) {
|
||||
switch (d.deserialize_number(U32_BYTES, false)) {
|
||||
case 0:
|
||||
return {
|
||||
tag: "vanilla"
|
||||
};
|
||||
case 1:
|
||||
return {
|
||||
tag: "forge"
|
||||
};
|
||||
case 2:
|
||||
return {
|
||||
tag: "fabric"
|
||||
};
|
||||
case 3:
|
||||
return {
|
||||
tag: "quilt"
|
||||
};
|
||||
case 4:
|
||||
return {
|
||||
tag: "neoforge"
|
||||
};
|
||||
default:
|
||||
throw "variant not implemented"
|
||||
}
|
||||
}
|
||||
|
||||
function deserialize_SHARED_INSTANCE_UNAVAILABLE_REASON(d) {
|
||||
switch (d.deserialize_number(U32_BYTES, false)) {
|
||||
case 0:
|
||||
return {
|
||||
tag: "deleted"
|
||||
};
|
||||
case 1:
|
||||
return {
|
||||
tag: "access_revoked"
|
||||
};
|
||||
case 2:
|
||||
return {
|
||||
tag: "quarantined"
|
||||
};
|
||||
default:
|
||||
throw "variant not implemented"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deserialize a value from an array of bytes.
|
||||
* @param {string} type - The type of the value to deserialize.
|
||||
* @param {Uint8Array} bytes - The byte array to deserialize from.
|
||||
* @return {Object} The deserialized value and remaining bytes.
|
||||
*/
|
||||
function deserialize(type, bytes) {
|
||||
if (!(typeof type === "string")) {
|
||||
throw "type must be a string";
|
||||
}
|
||||
const d = new Deserializer(bytes);
|
||||
var return_value = undefined;
|
||||
switch (type) {
|
||||
case "AppEvent":
|
||||
return_value = deserialize_APP_EVENT(d);
|
||||
break;
|
||||
case "LoadingBarType":
|
||||
return_value = deserialize_LOADING_BAR_TYPE(d);
|
||||
break;
|
||||
case "LoadingPayload":
|
||||
return_value = deserialize_LOADING_PAYLOAD(d);
|
||||
break;
|
||||
case "WarningPayload":
|
||||
return_value = deserialize_WARNING_PAYLOAD(d);
|
||||
break;
|
||||
case "InstanceBulkUpdateProgressPayload":
|
||||
return_value = deserialize_INSTANCE_BULK_UPDATE_PROGRESS_PAYLOAD(d);
|
||||
break;
|
||||
case "InstanceBulkUpdateProgressStage":
|
||||
return_value = deserialize_INSTANCE_BULK_UPDATE_PROGRESS_STAGE(d);
|
||||
break;
|
||||
case "CommandPayload":
|
||||
return_value = deserialize_COMMAND_PAYLOAD(d);
|
||||
break;
|
||||
case "ProcessPayload":
|
||||
return_value = deserialize_PROCESS_PAYLOAD(d);
|
||||
break;
|
||||
case "ProcessPayloadType":
|
||||
return_value = deserialize_PROCESS_PAYLOAD_TYPE(d);
|
||||
break;
|
||||
case "InstancePayload":
|
||||
return_value = deserialize_INSTANCE_PAYLOAD(d);
|
||||
break;
|
||||
case "InstancePayloadType":
|
||||
return_value = deserialize_INSTANCE_PAYLOAD_TYPE(d);
|
||||
break;
|
||||
case "FriendPayload":
|
||||
return_value = deserialize_FRIEND_PAYLOAD(d);
|
||||
break;
|
||||
case "FriendStatusPayload":
|
||||
return_value = deserialize_FRIEND_STATUS_PAYLOAD(d);
|
||||
break;
|
||||
case "LogEvent":
|
||||
return_value = deserialize_LOG_EVENT(d);
|
||||
break;
|
||||
case "LogPayload":
|
||||
return_value = deserialize_LOG_PAYLOAD(d);
|
||||
break;
|
||||
case "Log4jEvent":
|
||||
return_value = deserialize_LOG4J_EVENT(d);
|
||||
break;
|
||||
case "InstallJobSnapshot":
|
||||
return_value = deserialize_INSTALL_JOB_SNAPSHOT(d);
|
||||
break;
|
||||
case "InstallJobKind":
|
||||
return_value = deserialize_INSTALL_JOB_KIND(d);
|
||||
break;
|
||||
case "InstallJobStatus":
|
||||
return_value = deserialize_INSTALL_JOB_STATUS(d);
|
||||
break;
|
||||
case "InstallTarget":
|
||||
return_value = deserialize_INSTALL_TARGET(d);
|
||||
break;
|
||||
case "InstallPhaseId":
|
||||
return_value = deserialize_INSTALL_PHASE_ID(d);
|
||||
break;
|
||||
case "InstallProgress":
|
||||
return_value = deserialize_INSTALL_PROGRESS(d);
|
||||
break;
|
||||
case "InstallProgressSecondary":
|
||||
return_value = deserialize_INSTALL_PROGRESS_SECONDARY(d);
|
||||
break;
|
||||
case "InstallPhaseDetails":
|
||||
return_value = deserialize_INSTALL_PHASE_DETAILS(d);
|
||||
break;
|
||||
case "InstallJavaStep":
|
||||
return_value = deserialize_INSTALL_JAVA_STEP(d);
|
||||
break;
|
||||
case "InstallJobDisplay":
|
||||
return_value = deserialize_INSTALL_JOB_DISPLAY(d);
|
||||
break;
|
||||
case "InstallErrorView":
|
||||
return_value = deserialize_INSTALL_ERROR_VIEW(d);
|
||||
break;
|
||||
case "InstallApiErrorDetails":
|
||||
return_value = deserialize_INSTALL_API_ERROR_DETAILS(d);
|
||||
break;
|
||||
case "InstallErrorContext":
|
||||
return_value = deserialize_INSTALL_ERROR_CONTEXT(d);
|
||||
break;
|
||||
case "ImportLauncherType":
|
||||
return_value = deserialize_IMPORT_LAUNCHER_TYPE(d);
|
||||
break;
|
||||
case "ModLoader":
|
||||
return_value = deserialize_MOD_LOADER(d);
|
||||
break;
|
||||
case "SharedInstanceUnavailableReason":
|
||||
return_value = deserialize_SHARED_INSTANCE_UNAVAILABLE_REASON(d);
|
||||
break;
|
||||
default:
|
||||
throw "type not implemented";
|
||||
}
|
||||
return { value: return_value, bytes: d.release_bytes() };
|
||||
}
|
||||
|
||||
export {
|
||||
deserialize
|
||||
};
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"name": "postcard",
|
||||
"description": "Auto generated bindings for postcard format serializing and deserializing javascript to and from bytes.",
|
||||
"version": "0.0.0",
|
||||
"main": "index.js",
|
||||
"types": "index.d.ts",
|
||||
"type": "module"
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
import { invoke } from '@tauri-apps/api/core'
|
||||
import { listen } from '@tauri-apps/api/event'
|
||||
|
||||
export async function init_ads_window(overrideShown = false) {
|
||||
return await invoke('plugin:ads|init_ads_window', {
|
||||
@@ -8,8 +7,27 @@ export async function init_ads_window(overrideShown = false) {
|
||||
})
|
||||
}
|
||||
|
||||
export async function show_ads_window() {
|
||||
return await invoke('plugin:ads|show_ads_window', { dpr: window.devicePixelRatio })
|
||||
let adsWindowHoldUpdate = Promise.resolve()
|
||||
|
||||
async function update_ads_window_hold(acquire) {
|
||||
adsWindowHoldUpdate = adsWindowHoldUpdate
|
||||
.catch(() => {})
|
||||
.then(() =>
|
||||
invoke('plugin:ads|update_ads_window_hold', {
|
||||
acquire,
|
||||
dpr: window.devicePixelRatio,
|
||||
}),
|
||||
)
|
||||
|
||||
return await adsWindowHoldUpdate
|
||||
}
|
||||
|
||||
export async function take_ads_window_hold() {
|
||||
return await update_ads_window_hold(true)
|
||||
}
|
||||
|
||||
export async function release_ads_window_hold() {
|
||||
return await update_ads_window_hold(false)
|
||||
}
|
||||
|
||||
export async function hide_ads_window(reset) {
|
||||
@@ -28,10 +46,6 @@ export async function open_ads_consent_preferences() {
|
||||
return await invoke('plugin:ads|open_ads_consent_preferences')
|
||||
}
|
||||
|
||||
export async function ads_consent_listener(callback) {
|
||||
return await listen('ads-consent-required', (event) => callback(event.payload))
|
||||
}
|
||||
|
||||
export async function record_ads_click() {
|
||||
return await invoke('plugin:ads|record_ads_click')
|
||||
}
|
||||
|
||||
@@ -1,137 +0,0 @@
|
||||
/*
|
||||
Event listeners for interacting with the Rust api
|
||||
These are all async functions that return a promise that resolves to the payload object (whatever Rust is trying to deliver)
|
||||
*/
|
||||
|
||||
/*
|
||||
callback is a function that takes a single argument, which is the payload object (whatever Rust is trying to deliver)
|
||||
|
||||
You can call these to await any kind of emitted signal from Rust, and then do something with the payload object
|
||||
An example place to put this is at the start of main.js before the state is initialized- that way
|
||||
you can listen for any emitted signal from Rust and do something with it as the state is being initialized
|
||||
|
||||
Example:
|
||||
import { loading_listener } from '@/helpers/events'
|
||||
await loading_listener((event) => {
|
||||
// event.event is the event name (useful if you want to use a single callback fn for multiple event types)
|
||||
// event.payload is the payload object
|
||||
console.log(event)
|
||||
})
|
||||
|
||||
Putting that in a script will print any emitted signal from rust
|
||||
*/
|
||||
import { listen } from '@tauri-apps/api/event'
|
||||
|
||||
/// Payload for the 'loading' event
|
||||
/*
|
||||
LoadingPayload {
|
||||
event: {
|
||||
type: string, one of "StateInit", "PackDownload", etc
|
||||
(Optional fields depending on event type)
|
||||
pack_name: name of the pack
|
||||
pack_id, optional, the id of the modpack
|
||||
pack_version, optional, the version of the modpack
|
||||
instance_name: name of the instance
|
||||
instance_id: unique identification of the instance
|
||||
|
||||
}
|
||||
loader_uuid: unique identification of the loading bar
|
||||
fraction: number, (as a fraction of 1, how much we've loaded so far). If null, by convention, loading is finished
|
||||
message: message to display to the user
|
||||
}
|
||||
*/
|
||||
export async function loading_listener(callback) {
|
||||
return await listen('loading', (event) => callback(event.payload))
|
||||
}
|
||||
|
||||
/// Payload for the 'process' event
|
||||
/*
|
||||
ProcessPayload {
|
||||
uuid: unique identification of the process in the state (currently identified by PID, but that will change)
|
||||
pid: process ID
|
||||
event: event type ("Launched", "Finished")
|
||||
message: message to display to the user
|
||||
}
|
||||
*/
|
||||
export async function process_listener(callback) {
|
||||
return await listen('process', (event) => callback(event.payload))
|
||||
}
|
||||
|
||||
/// Payload for the 'instance' event
|
||||
/*
|
||||
InstancePayload {
|
||||
instance_id: unique identification of the instance
|
||||
event: event type ("Created", "Added", "Edited", "Removed")
|
||||
}
|
||||
*/
|
||||
export async function instance_listener(callback) {
|
||||
return await listen('instance', (event) => callback(event.payload))
|
||||
}
|
||||
|
||||
/// Payload for the 'instance_bulk_update_progress' event
|
||||
/*
|
||||
InstanceBulkUpdateProgress {
|
||||
instanceId: string
|
||||
stage: "resolving_versions" | "downloading" | "finishing"
|
||||
current: number
|
||||
total: number
|
||||
}
|
||||
*/
|
||||
export async function instance_bulk_update_progress_listener(callback) {
|
||||
return await listen('instance_bulk_update_progress', (event) => callback(event.payload))
|
||||
}
|
||||
|
||||
export async function install_job_listener(callback) {
|
||||
return await listen('install_job', (event) => callback(event.payload))
|
||||
}
|
||||
|
||||
/// Payload for the 'command' event
|
||||
/*
|
||||
CommandPayload {
|
||||
event: event type ("InstallMod", "InstallModpack", "InstallVersion"),
|
||||
id: string id of the mod/modpack/version to install
|
||||
}
|
||||
*/
|
||||
export async function command_listener(callback) {
|
||||
return await listen('command', (event) => {
|
||||
callback(event.payload)
|
||||
})
|
||||
}
|
||||
|
||||
/// Payload for the 'warning' event
|
||||
/*
|
||||
WarningPayload {
|
||||
message: message to display to the user
|
||||
}
|
||||
*/
|
||||
export async function warning_listener(callback) {
|
||||
return await listen('warning', (event) => callback(event.payload))
|
||||
}
|
||||
|
||||
export async function friend_listener(callback) {
|
||||
return await listen('friend', (event) => callback(event.payload))
|
||||
}
|
||||
|
||||
export async function notification_listener(callback) {
|
||||
return await listen('notification', (event) => callback(event.payload))
|
||||
}
|
||||
|
||||
/// Payload for the 'log' event
|
||||
/*
|
||||
LogPayload {
|
||||
instance_id: string,
|
||||
type: "log4j" | "legacy",
|
||||
// log4j fields (when type === "log4j"):
|
||||
timestamp_millis?: number,
|
||||
logger_name?: string,
|
||||
level?: string,
|
||||
thread_name?: string,
|
||||
message?: string,
|
||||
throwable?: string,
|
||||
// legacy fields (when type === "legacy"):
|
||||
message?: string,
|
||||
}
|
||||
*/
|
||||
export async function log_listener(callback) {
|
||||
return await listen('log', (event) => callback(event.payload))
|
||||
}
|
||||
@@ -1,8 +1,28 @@
|
||||
import { invoke } from '@tauri-apps/api/core'
|
||||
|
||||
import { install_job_listener } from './events'
|
||||
import type { InstallErrorView } from '@/generated/app-events/InstallErrorView'
|
||||
import type { InstallJavaStep } from '@/generated/app-events/InstallJavaStep'
|
||||
import type { InstallJobSnapshot } from '@/generated/app-events/InstallJobSnapshot'
|
||||
import type { InstallJobStatus } from '@/generated/app-events/InstallJobStatus'
|
||||
import type { InstallPhaseId } from '@/generated/app-events/InstallPhaseId'
|
||||
import type { InstallProgress } from '@/generated/app-events/InstallProgress'
|
||||
import type { InstallProgressSecondary } from '@/generated/app-events/InstallProgressSecondary'
|
||||
import type { SharedInstanceUnavailableReason } from '@/generated/app-events/SharedInstanceUnavailableReason'
|
||||
import type { AppEvents } from '@/providers/app-events'
|
||||
|
||||
import type { InstanceLink, InstanceLoader } from './types'
|
||||
|
||||
export type {
|
||||
InstallErrorView,
|
||||
InstallJavaStep,
|
||||
InstallJobSnapshot,
|
||||
InstallJobStatus,
|
||||
InstallPhaseId,
|
||||
InstallProgress,
|
||||
InstallProgressSecondary,
|
||||
SharedInstanceUnavailableReason,
|
||||
}
|
||||
|
||||
export interface PackLocationVersionId {
|
||||
type: 'fromVersionId'
|
||||
project_id: string
|
||||
@@ -104,8 +124,6 @@ export interface SharedInstanceUpdateDiff {
|
||||
export const SHARED_INSTANCE_UNAVAILABLE_ERROR_CODE = 'shared_instance_unavailable'
|
||||
export const SHARED_INSTANCES_API_ERROR_CODE = 'shared_instances_api_error'
|
||||
|
||||
export type SharedInstanceUnavailableReason = 'deleted' | 'access_revoked' | 'quarantined'
|
||||
|
||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
return typeof value === 'object' && value !== null
|
||||
}
|
||||
@@ -136,116 +154,6 @@ export function getErrorMessage(error: unknown): string {
|
||||
return 'Unknown error'
|
||||
}
|
||||
|
||||
export type InstallJobStatus =
|
||||
| 'queued'
|
||||
| 'running'
|
||||
| 'succeeded'
|
||||
| 'failed'
|
||||
| 'interrupted'
|
||||
| 'canceled'
|
||||
|
||||
export type InstallPhaseId =
|
||||
| 'preparing_instance'
|
||||
| 'resolving_pack'
|
||||
| 'downloading_pack_file'
|
||||
| 'reading_pack_manifest'
|
||||
| 'downloading_content'
|
||||
| 'extracting_overrides'
|
||||
| 'resolving_minecraft'
|
||||
| 'resolving_loader'
|
||||
| 'preparing_java'
|
||||
| 'downloading_minecraft'
|
||||
| 'running_loader_processors'
|
||||
| 'finalizing'
|
||||
| 'rolling_back'
|
||||
|
||||
export interface InstallProgress {
|
||||
current: number
|
||||
total: number
|
||||
secondary?: InstallProgressSecondary | null
|
||||
}
|
||||
|
||||
export interface InstallProgressSecondary {
|
||||
current: number
|
||||
total: number
|
||||
}
|
||||
|
||||
export type InstallJavaStep =
|
||||
| 'resolving'
|
||||
| 'fetching_metadata'
|
||||
| 'downloading'
|
||||
| 'extracting'
|
||||
| 'validating'
|
||||
|
||||
export interface InstallErrorView {
|
||||
code: string
|
||||
phase?: InstallPhaseId | null
|
||||
message: string
|
||||
reason?: SharedInstanceUnavailableReason | null
|
||||
api?: {
|
||||
error: string
|
||||
status?: number | null
|
||||
method?: string | null
|
||||
url?: string | null
|
||||
route?: string | null
|
||||
} | null
|
||||
context?: {
|
||||
operation: string
|
||||
source_path?: string | null
|
||||
target_path?: string | null
|
||||
file_path?: string | null
|
||||
entry_path?: string | null
|
||||
urls?: string[]
|
||||
expected_hash?: string | null
|
||||
expected_size?: number | null
|
||||
project_id?: string | null
|
||||
version_id?: string | null
|
||||
minecraft_version?: string | null
|
||||
loader?: string | null
|
||||
java_version?: number | null
|
||||
os?: string | null
|
||||
arch?: string | null
|
||||
} | null
|
||||
}
|
||||
|
||||
export interface InstallJobSnapshot {
|
||||
job_id: string
|
||||
instance_id?: string | null
|
||||
kind:
|
||||
| 'create_instance'
|
||||
| 'create_modpack_instance'
|
||||
| 'create_shared_instance'
|
||||
| 'update_shared_instance'
|
||||
| 'import_instance'
|
||||
| 'duplicate_instance'
|
||||
| 'install_existing_instance'
|
||||
| 'install_pack_to_existing_instance'
|
||||
status: InstallJobStatus
|
||||
target:
|
||||
| { type: 'new_instance'; instance_id?: string | null }
|
||||
| { type: 'existing_instance'; instance_id: string }
|
||||
phase: InstallPhaseId
|
||||
progress?: InstallProgress | null
|
||||
details:
|
||||
| { type: 'empty' }
|
||||
| { type: 'instance'; name: string }
|
||||
| { type: 'minecraft'; game_version: string; loader: InstanceLoader }
|
||||
| { type: 'java'; major_version: number; step: InstallJavaStep }
|
||||
| {
|
||||
type: 'modpack'
|
||||
project_id?: string | null
|
||||
version_id?: string | null
|
||||
title?: string | null
|
||||
}
|
||||
| { type: 'import'; launcher_type: string; instance_folder: string }
|
||||
display?: { title: string; icon?: string | null } | null
|
||||
error?: InstallErrorView | null
|
||||
rollback_error?: InstallErrorView | null
|
||||
created: string
|
||||
modified: string
|
||||
finished?: string | null
|
||||
}
|
||||
|
||||
export async function install_get_modpack_preview(location: CreatePackLocation) {
|
||||
return await invoke<InstallModpackPreview>('plugin:install|install_get_modpack_preview', {
|
||||
location,
|
||||
@@ -399,7 +307,7 @@ function settleInstallJob(job: InstallJobSnapshot) {
|
||||
throw new Error(`Install job ${job.job_id} ${job.status}`)
|
||||
}
|
||||
|
||||
export async function wait_for_install_job(jobId: string) {
|
||||
export async function wait_for_install_job(events: AppEvents, jobId: string) {
|
||||
const current = await install_job_get(jobId)
|
||||
if (isInstallJobFinished(current.status)) return settleInstallJob(current)
|
||||
|
||||
@@ -434,16 +342,7 @@ export async function wait_for_install_job(jobId: string) {
|
||||
reject(err)
|
||||
}
|
||||
|
||||
install_job_listener(resolveJob)
|
||||
.then((listener) => {
|
||||
if (finished) {
|
||||
listener()
|
||||
return
|
||||
}
|
||||
|
||||
unlisten = listener
|
||||
install_job_get(jobId).then(resolveJob).catch(rejectWait)
|
||||
})
|
||||
.catch(rejectWait)
|
||||
unlisten = events.on('install_job', resolveJob)
|
||||
install_job_get(jobId).then(resolveJob).catch(rejectWait)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,17 +1,10 @@
|
||||
import type {
|
||||
ContentItem,
|
||||
ContentModpackCardCategory,
|
||||
ContentModpackCardProject,
|
||||
ContentModpackCardVersion,
|
||||
ContentOwner,
|
||||
} from '@modrinth/ui'
|
||||
import type { ContentItem, ManagedContentProject, ManagedContentVersion } from '@modrinth/ui'
|
||||
|
||||
import {
|
||||
get_content_items,
|
||||
get_linked_modpack_info,
|
||||
type LinkedModpackInfo,
|
||||
} from '@/helpers/instance'
|
||||
import { get_categories } from '@/helpers/tags.js'
|
||||
import type { CacheBehaviour } from '@/helpers/types'
|
||||
|
||||
export type InstanceContentData = {
|
||||
@@ -21,11 +14,8 @@ export type InstanceContentData = {
|
||||
}
|
||||
|
||||
export type InstanceContentModpackData = {
|
||||
project: ContentModpackCardProject
|
||||
version: ContentModpackCardVersion
|
||||
owner: ContentOwner | null
|
||||
categories: ContentModpackCardCategory[]
|
||||
hasUpdate: boolean
|
||||
project: ManagedContentProject
|
||||
version: ManagedContentVersion
|
||||
updateVersionId: string | null
|
||||
}
|
||||
|
||||
@@ -34,19 +24,15 @@ export async function loadInstanceContentData(
|
||||
cacheBehaviour?: CacheBehaviour,
|
||||
onError?: (error: Error) => unknown,
|
||||
): Promise<InstanceContentData> {
|
||||
const [contentItems, modpackInfo, allCategories] = await Promise.all([
|
||||
const [contentItems, modpackInfo] = await Promise.all([
|
||||
get_content_items(path, cacheBehaviour).catch((error) => handleLoadError(error, onError)),
|
||||
get_linked_modpack_info(path, cacheBehaviour).catch((error) => handleLoadError(error, onError)),
|
||||
get_categories().catch((error) => handleLoadError(error, onError)),
|
||||
])
|
||||
|
||||
return {
|
||||
path,
|
||||
contentItems: (contentItems as ContentItem[] | null | undefined) ?? null,
|
||||
modpack: normalizeLinkedModpackInfo(
|
||||
modpackInfo as LinkedModpackInfo | null | undefined,
|
||||
allCategories as ContentModpackCardCategory[] | null | undefined,
|
||||
),
|
||||
modpack: normalizeLinkedModpackInfo(modpackInfo as LinkedModpackInfo | null | undefined),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,7 +44,6 @@ function handleLoadError(error: unknown, onError?: (error: Error) => unknown) {
|
||||
|
||||
function normalizeLinkedModpackInfo(
|
||||
modpackInfo: LinkedModpackInfo | null | undefined,
|
||||
allCategories: ContentModpackCardCategory[] | null | undefined,
|
||||
): InstanceContentModpackData | null {
|
||||
if (!modpackInfo) return null
|
||||
|
||||
@@ -72,30 +57,6 @@ function normalizeLinkedModpackInfo(
|
||||
...modpackInfo.version,
|
||||
date_published: modpackInfo.version.date_published.toString(),
|
||||
},
|
||||
owner: modpackInfo.owner
|
||||
? {
|
||||
...modpackInfo.owner,
|
||||
avatar_url: modpackInfo.owner.avatar_url ?? undefined,
|
||||
}
|
||||
: null,
|
||||
categories: resolveLinkedModpackCategories(modpackInfo, allCategories),
|
||||
hasUpdate: modpackInfo.has_update,
|
||||
updateVersionId: modpackInfo.update_version_id,
|
||||
}
|
||||
}
|
||||
|
||||
function resolveLinkedModpackCategories(
|
||||
modpackInfo: LinkedModpackInfo,
|
||||
allCategories: ContentModpackCardCategory[] | null | undefined,
|
||||
) {
|
||||
if (!allCategories || !modpackInfo.project.categories) return []
|
||||
|
||||
const seen = new Set<string>()
|
||||
return allCategories.filter((category) => {
|
||||
if (modpackInfo.project.categories.includes(category.name) && !seen.has(category.name)) {
|
||||
seen.add(category.name)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
})
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
*/
|
||||
import type { Labrinth } from '@modrinth/api-client'
|
||||
import type { ContentItem, ContentOwner } from '@modrinth/ui'
|
||||
import { invoke } from '@tauri-apps/api/core'
|
||||
import { convertFileSrc, invoke } from '@tauri-apps/api/core'
|
||||
|
||||
import type { InstallJobSnapshot, SharedInstanceUpdateDiff } from './install'
|
||||
import type {
|
||||
@@ -74,7 +74,15 @@ export async function get_content_items(
|
||||
instanceId: string,
|
||||
cacheBehaviour?: CacheBehaviour,
|
||||
): Promise<ContentItem[]> {
|
||||
return await invoke('plugin:instance|instance_get_content_items', { instanceId, cacheBehaviour })
|
||||
const items = await invoke<ContentItem[]>('plugin:instance|instance_get_content_items', {
|
||||
instanceId,
|
||||
cacheBehaviour,
|
||||
})
|
||||
return adaptContentItems(items)
|
||||
}
|
||||
|
||||
export async function refresh_content_updates(instanceId: string): Promise<void> {
|
||||
return await invoke('plugin:instance|instance_refresh_content_updates', { instanceId })
|
||||
}
|
||||
|
||||
// Linked modpack info returned from backend
|
||||
@@ -107,10 +115,11 @@ export async function get_linked_modpack_content(
|
||||
instanceId: string,
|
||||
cacheBehaviour?: CacheBehaviour,
|
||||
): Promise<ContentItem[]> {
|
||||
return await invoke('plugin:instance|instance_get_linked_modpack_content', {
|
||||
const items = await invoke<ContentItem[]>('plugin:instance|instance_get_linked_modpack_content', {
|
||||
instanceId,
|
||||
cacheBehaviour,
|
||||
})
|
||||
return adaptContentItems(items)
|
||||
}
|
||||
|
||||
// Convert a list of dependencies into ContentItems with rich metadata
|
||||
@@ -118,9 +127,28 @@ export async function get_dependencies_as_content_items(
|
||||
dependencies: Labrinth.Versions.v3.Dependency[],
|
||||
cacheBehaviour?: CacheBehaviour,
|
||||
): Promise<ContentItem[]> {
|
||||
return await invoke('plugin:instance|instance_get_dependencies_as_content_items', {
|
||||
dependencies,
|
||||
cacheBehaviour,
|
||||
const items = await invoke<ContentItem[]>(
|
||||
'plugin:instance|instance_get_dependencies_as_content_items',
|
||||
{
|
||||
dependencies,
|
||||
cacheBehaviour,
|
||||
},
|
||||
)
|
||||
return adaptContentItems(items)
|
||||
}
|
||||
|
||||
function adaptContentItems(items: ContentItem[]): ContentItem[] {
|
||||
return items.map((item) => {
|
||||
const embeddedMetadata = item.embedded_metadata
|
||||
if (!embeddedMetadata?.icon_path) return item
|
||||
|
||||
return {
|
||||
...item,
|
||||
embedded_metadata: {
|
||||
...embeddedMetadata,
|
||||
icon_url: convertFileSrc(embeddedMetadata.icon_path),
|
||||
},
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -260,6 +288,18 @@ export async function toggle_disable_project(
|
||||
})
|
||||
}
|
||||
|
||||
export async function set_project_locked(
|
||||
instanceId: string,
|
||||
projectPath: string,
|
||||
locked: boolean,
|
||||
): Promise<void> {
|
||||
return await invoke('plugin:instance|instance_set_project_locked', {
|
||||
instanceId,
|
||||
projectPath,
|
||||
locked,
|
||||
})
|
||||
}
|
||||
|
||||
// Remove a project
|
||||
export async function remove_project(instanceId: string, projectPath: string): Promise<void> {
|
||||
return await invoke('plugin:instance|instance_remove_project', { instanceId, projectPath })
|
||||
|
||||
@@ -3,7 +3,9 @@
|
||||
* So, for example, addDefaultInstance creates a blank instance object, where the Rust struct is serialized,
|
||||
* and deserialized into a usable JS object.
|
||||
*/
|
||||
import { invoke } from '@tauri-apps/api/core'
|
||||
import { type Channel, invoke } from '@tauri-apps/api/core'
|
||||
|
||||
import type { AppEvent } from '@/generated/app-events/AppEvent'
|
||||
|
||||
export interface LoadingBarType {
|
||||
type?: string
|
||||
@@ -24,24 +26,12 @@ export interface LoadingBar {
|
||||
bar_type?: LoadingBarType
|
||||
}
|
||||
|
||||
export type OpeningCommandEvent =
|
||||
| 'RunMRPack'
|
||||
| 'InstallServer'
|
||||
| 'InstallVersion'
|
||||
| 'InstallMod'
|
||||
| 'InstallModpack'
|
||||
| string
|
||||
|
||||
export interface OpeningCommand {
|
||||
event: OpeningCommandEvent
|
||||
id?: string
|
||||
path?: string
|
||||
}
|
||||
export type OpeningCommand = Extract<AppEvent, { type: 'command' }>['payload']
|
||||
|
||||
// Initialize the theseus API state
|
||||
// This should be called during the initializion/opening of the launcher
|
||||
export async function initialize_state() {
|
||||
return await invoke<void>('initialize_state')
|
||||
export async function initialize_state(events: Channel<ArrayBuffer>) {
|
||||
return await invoke<void>('initialize_state', { events })
|
||||
}
|
||||
|
||||
// Gets active progress bars
|
||||
|
||||
+1
@@ -128,6 +128,7 @@ export type ContentSourceKind =
|
||||
|
||||
type ContentFile = {
|
||||
enabled: boolean
|
||||
locked: boolean
|
||||
source_kind?: ContentSourceKind | null
|
||||
metadata?: {
|
||||
project_id: string
|
||||
|
||||
@@ -3,6 +3,7 @@ import { autoToHTML } from '@sfirew/minecraft-motd-parser'
|
||||
import { invoke } from '@tauri-apps/api/core'
|
||||
import dayjs from 'dayjs'
|
||||
|
||||
import type { InstancePayload } from '@/generated/app-events/InstancePayload'
|
||||
import { get_full_path } from '@/helpers/instance'
|
||||
import { openPath } from '@/helpers/utils'
|
||||
|
||||
@@ -532,18 +533,4 @@ export function hasWorldQuickPlaySupport(gameVersions: GameVersion[], currentVer
|
||||
return versionIndex !== -1 && targetIndex !== -1 && versionIndex <= targetIndex
|
||||
}
|
||||
|
||||
export type InstanceEvent = { instance_id: string } & (
|
||||
| {
|
||||
event: 'servers_updated'
|
||||
}
|
||||
| {
|
||||
event: 'world_updated'
|
||||
world: string
|
||||
}
|
||||
| {
|
||||
event: 'server_joined'
|
||||
host: string
|
||||
port: number
|
||||
timestamp: string
|
||||
}
|
||||
)
|
||||
export type InstanceEvent = InstancePayload
|
||||
|
||||
@@ -1073,16 +1073,7 @@
|
||||
"search.filter.locked.instance.sync": {
|
||||
"message": "مزامنة مع النسخة"
|
||||
},
|
||||
"search.filter.locked.server": {
|
||||
"message": "يقدمها الخادم"
|
||||
},
|
||||
"search.filter.locked.server-environment.title": {
|
||||
"message": "يمكن إضافة التعديلات **المحليه** فقط إلى نموذج الخادم"
|
||||
},
|
||||
"search.filter.locked.server-game-version.title": {
|
||||
"message": "يتم توفير نسخة اللعبة من قبل الخادم"
|
||||
},
|
||||
"search.filter.locked.server-loader.title": {
|
||||
"message": "يتم توفير المحمّل من قبل الخادم"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,9 @@
|
||||
"app.action-bar.install.copied-details": {
|
||||
"message": "Zkopírováno"
|
||||
},
|
||||
"app.action-bar.install.copy-details": {
|
||||
"message": "Kopírovat detaily"
|
||||
},
|
||||
"app.action-bar.install.dismiss": {
|
||||
"message": "Zavřít"
|
||||
},
|
||||
@@ -26,6 +29,9 @@
|
||||
"app.action-bar.install.summary.canceled": {
|
||||
"message": "Zrušeno"
|
||||
},
|
||||
"app.action-bar.install.summary.cleanup-incomplete": {
|
||||
"message": "Čištění nebylo dokončeno"
|
||||
},
|
||||
"app.action-bar.install.unknown-instance": {
|
||||
"message": "Neznámá instance"
|
||||
},
|
||||
@@ -65,6 +71,9 @@
|
||||
"app.action-bar.view-logs": {
|
||||
"message": "Zobrazit logy"
|
||||
},
|
||||
"app.ads-consent.title": {
|
||||
"message": "Vaše soukromí a způsob, jakým reklamy podporují Modrinth"
|
||||
},
|
||||
"app.appearance-settings.advanced-rendering.title": {
|
||||
"message": "Pokročilé vykreslování"
|
||||
},
|
||||
@@ -212,6 +221,9 @@
|
||||
"app.install.phase.running_loader_processors": {
|
||||
"message": "Spuštění loader procesů"
|
||||
},
|
||||
"app.instance.admonitions.shared-instance.removed-label": {
|
||||
"message": "Odstraněno"
|
||||
},
|
||||
"app.instance.confirm-delete.admonition-body": {
|
||||
"message": "Všechna data tvé instance budou trvale smazána, včetně světů, konfigurací a veškerého nainstalovaného obsahu."
|
||||
},
|
||||
@@ -953,16 +965,7 @@
|
||||
"search.filter.locked.instance.sync": {
|
||||
"message": "Synchronizováno s instancí"
|
||||
},
|
||||
"search.filter.locked.server": {
|
||||
"message": "Poskytováno serverem"
|
||||
},
|
||||
"search.filter.locked.server-environment.title": {
|
||||
"message": "Pouze módy ze strany klientu mohou být přidány na server"
|
||||
},
|
||||
"search.filter.locked.server-game-version.title": {
|
||||
"message": "Verze hry je poskytována serverem"
|
||||
},
|
||||
"search.filter.locked.server-loader.title": {
|
||||
"message": "Loader zprostředkovává server"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -179,6 +179,9 @@
|
||||
"app.auth-servers.unreachable.header": {
|
||||
"message": "Kan ikke nå autentificeringsservere"
|
||||
},
|
||||
"app.behavior-settings.content.title": {
|
||||
"message": "Hjem og indhold"
|
||||
},
|
||||
"app.browse.add-servers-to-instance": {
|
||||
"message": "Tilføjet server til instance"
|
||||
},
|
||||
@@ -200,12 +203,18 @@
|
||||
"app.browse.back-to-instance": {
|
||||
"message": "Tilbage til instance"
|
||||
},
|
||||
"app.browse.discover-project-type": {
|
||||
"message": "Udforsk {projectType}"
|
||||
},
|
||||
"app.browse.discover-servers": {
|
||||
"message": "Opdag servere"
|
||||
},
|
||||
"app.browse.hide-added-servers": {
|
||||
"message": "Gem servere som allerede er tilføjet"
|
||||
},
|
||||
"app.browse.hide-installed-modpacks": {
|
||||
"message": "Skjul allerede installeret"
|
||||
},
|
||||
"app.browse.project-type.modpacks": {
|
||||
"message": "Modpacks"
|
||||
},
|
||||
@@ -245,15 +254,30 @@
|
||||
"app.install.phase.downloading_minecraft": {
|
||||
"message": "Downloader Minecraft"
|
||||
},
|
||||
"app.install.phase.finalizing": {
|
||||
"message": "Færdiggører"
|
||||
},
|
||||
"app.install.phase.preparing_instance": {
|
||||
"message": "I kø til at intallere"
|
||||
},
|
||||
"app.install.phase.preparing_java": {
|
||||
"message": "Forbereder Java"
|
||||
},
|
||||
"app.install.phase.preparing_java.downloading": {
|
||||
"message": "Downloader Java {version}"
|
||||
},
|
||||
"app.install.phase.preparing_java.extracting": {
|
||||
"message": "Udpakker Java {version}"
|
||||
},
|
||||
"app.install.phase.preparing_java.fetching-metadata": {
|
||||
"message": "Henter Java {version}"
|
||||
},
|
||||
"app.install.phase.preparing_java.resolving": {
|
||||
"message": "Forbereder Java {version}"
|
||||
},
|
||||
"app.install.phase.preparing_java.validating": {
|
||||
"message": "Validerer Java {version}"
|
||||
},
|
||||
"app.instance.admonitions.shared-instance.added-label": {
|
||||
"message": "Tilføjet"
|
||||
},
|
||||
@@ -272,18 +296,9 @@
|
||||
"app.instance.admonitions.shared-instance.review-header": {
|
||||
"message": "Gennemså ændringer"
|
||||
},
|
||||
"app.instance.admonitions.shared-instance.review-update-button": {
|
||||
"message": "Gennemgå opdatering"
|
||||
},
|
||||
"app.instance.admonitions.shared-instance.reviewing-button": {
|
||||
"message": "Gennemgår..."
|
||||
},
|
||||
"app.instance.admonitions.shared-instance.update-available-body": {
|
||||
"message": "En opdatering er krævet for at spille {name}. Venligst opdater til den seneste version for at køre spillet."
|
||||
},
|
||||
"app.instance.admonitions.shared-instance.update-available-header": {
|
||||
"message": "En opdatering er tilgængelig"
|
||||
},
|
||||
"app.instance.confirm-delete.admonition-body": {
|
||||
"message": "Al' data for din instance vil blive permanent slettet, dette inkludere dine verdener, konfigurationer, og alt installeret indhold."
|
||||
},
|
||||
@@ -359,6 +374,12 @@
|
||||
"app.instance.share.remove-user-modal.effects-label": {
|
||||
"message": "Hvad vil der ske?"
|
||||
},
|
||||
"app.instance.share.remove-user-modal.header": {
|
||||
"message": "Fjern adgang"
|
||||
},
|
||||
"app.instance.share.remove-user-modal.remove-button": {
|
||||
"message": "Fjern adgang"
|
||||
},
|
||||
"app.instance.share.remove-user-modal.user-avatar-alt": {
|
||||
"message": "{username}'s avatar"
|
||||
},
|
||||
@@ -440,9 +461,6 @@
|
||||
"app.modal.install-to-play.report-image-too-large": {
|
||||
"message": "Filen overskrider 1MiB størrelse begrænsning"
|
||||
},
|
||||
"app.modal.install-to-play.report-reason.spam": {
|
||||
"message": "Spam"
|
||||
},
|
||||
"app.modal.install-to-play.shared-instance": {
|
||||
"message": "Delt instance"
|
||||
},
|
||||
@@ -1151,16 +1169,7 @@
|
||||
"search.filter.locked.instance.sync": {
|
||||
"message": "Synkroniser med instance"
|
||||
},
|
||||
"search.filter.locked.server": {
|
||||
"message": "Givet af serveren"
|
||||
},
|
||||
"search.filter.locked.server-environment.title": {
|
||||
"message": "Kun klient-sided mods kan blive tilføjet til denne server instance"
|
||||
},
|
||||
"search.filter.locked.server-game-version.title": {
|
||||
"message": "Spille version er givet af serveren"
|
||||
},
|
||||
"search.filter.locked.server-loader.title": {
|
||||
"message": "Loader er givet af serveren"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -210,7 +210,7 @@
|
||||
"message": "Rechte Seitenleiste ausblenden"
|
||||
},
|
||||
"app.appearance-settings.unknown-pack-warning.description": {
|
||||
"message": "Sicherheitswarnung vor der Installation eines Modrinth-Packs (.mrpack) anzeigen, das nicht auf Modrinth gehostet wird."
|
||||
"message": "Zeige eine Sicherheitswarnung an, bevor ein Modrinth-Paket (.mrpack) installiert wird, welches nicht auf Modrinth gehostet wird."
|
||||
},
|
||||
"app.appearance-settings.unknown-pack-warning.title": {
|
||||
"message": "Warne mich, bevor unbekannte Modpacks installiert werden"
|
||||
@@ -377,18 +377,9 @@
|
||||
"app.instance.admonitions.shared-instance.review-header": {
|
||||
"message": "Änderungen überprüfen"
|
||||
},
|
||||
"app.instance.admonitions.shared-instance.review-update-button": {
|
||||
"message": "Update überprüfen"
|
||||
},
|
||||
"app.instance.admonitions.shared-instance.reviewing-button": {
|
||||
"message": "Wird überprüft..."
|
||||
},
|
||||
"app.instance.admonitions.shared-instance.update-available-body": {
|
||||
"message": "Ein Update ist erforderlich, um {name} zu spielen. Bitte aktualisiere auf die neueste Version, um das Spiel zu starten."
|
||||
},
|
||||
"app.instance.admonitions.shared-instance.update-available-header": {
|
||||
"message": "Ein Update ist verfügbar"
|
||||
},
|
||||
"app.instance.confirm-delete.admonition-body": {
|
||||
"message": "Alle Daten deiner Instanz werden permanent gelöscht, inlusive deiner Welten, Konfigurationen und allen installierten Inhalten."
|
||||
},
|
||||
@@ -590,6 +581,9 @@
|
||||
"app.instance.worlds.no-worlds-heading": {
|
||||
"message": "Keine Server oder Welten hinzugefügt"
|
||||
},
|
||||
"app.instance.worlds.refreshing": {
|
||||
"message": "Wird aktualisiert..."
|
||||
},
|
||||
"app.instance.worlds.remove-server-modal.remove-button": {
|
||||
"message": "Server entfernen"
|
||||
},
|
||||
@@ -701,15 +695,6 @@
|
||||
"app.modal.install-to-play.report-reason": {
|
||||
"message": "Gegen welche Regel verstößt diese Instanz?"
|
||||
},
|
||||
"app.modal.install-to-play.report-reason.inappropriate": {
|
||||
"message": "Unangemessen"
|
||||
},
|
||||
"app.modal.install-to-play.report-reason.malicious": {
|
||||
"message": "Bösartig"
|
||||
},
|
||||
"app.modal.install-to-play.report-reason.spam": {
|
||||
"message": "Spam"
|
||||
},
|
||||
"app.modal.install-to-play.report-shared-instance-header": {
|
||||
"message": "Geteilte Instanz melden"
|
||||
},
|
||||
@@ -849,7 +834,7 @@
|
||||
"message": "Startet Instanzen im Vollbildmodus durch Aktualisieren ihrer options.txt-Datei."
|
||||
},
|
||||
"app.settings.default-instance-options.fullscreen.title": {
|
||||
"message": "Vollbildschirm"
|
||||
"message": "Vollbild"
|
||||
},
|
||||
"app.settings.default-instance-options.height.description": {
|
||||
"message": "Die Höhe des Spielfensters beim Starten"
|
||||
@@ -981,7 +966,7 @@
|
||||
"message": "Neues App-Verzeichnis auswählen"
|
||||
},
|
||||
"app.settings.resource-management.app-directory.title": {
|
||||
"message": "App Installation"
|
||||
"message": "App-Verzeichnis"
|
||||
},
|
||||
"app.settings.resource-management.maximum-concurrent-downloads.description": {
|
||||
"message": "Anzahl der Dateien, die die App auf einmal herunterladen kann. Senke dies, wenn downloads auf deiner Verbindung unzuverlässig sind. Benötigt einen App-Neustart."
|
||||
@@ -1005,7 +990,7 @@
|
||||
"message": "Verhalten"
|
||||
},
|
||||
"app.settings.tabs.default-instance-options": {
|
||||
"message": "Standard Spieleinstellungen"
|
||||
"message": "Standard-Spieloptionen"
|
||||
},
|
||||
"app.settings.tabs.java-installations": {
|
||||
"message": "Java Installationen"
|
||||
@@ -1188,10 +1173,10 @@
|
||||
"message": "Das Feedback geht direkt an das Modrinth-Team und wird helfen, zukünftige Updates zu gestalten!"
|
||||
},
|
||||
"app.survey.no-thanks": {
|
||||
"message": "Nein, danke"
|
||||
"message": "Nein danke"
|
||||
},
|
||||
"app.survey.take-survey": {
|
||||
"message": "Beantworten"
|
||||
"message": "An Umfrage teilnehmen"
|
||||
},
|
||||
"app.survey.title": {
|
||||
"message": "Hey, Modrinth Nutzer!"
|
||||
@@ -1226,6 +1211,9 @@
|
||||
"app.update.complete-toast.title": {
|
||||
"message": "Version {version} wurde erfolgreich installiert!"
|
||||
},
|
||||
"app.user.project.install-to-instance": {
|
||||
"message": "In Instanz installieren"
|
||||
},
|
||||
"app.world.server-modal.placeholder-address": {
|
||||
"message": "example.modrinth.gg"
|
||||
},
|
||||
@@ -1619,6 +1607,27 @@
|
||||
"instance.settings.tabs.hooks.title": {
|
||||
"message": "Spielstart Hooks"
|
||||
},
|
||||
"instance.settings.tabs.hooks.variables.description": {
|
||||
"message": "Hooks werden im Arbeitsverzeichnis der Instanz mit den folgenden Variablen ausgeführt:"
|
||||
},
|
||||
"instance.settings.tabs.hooks.variables.inst-dir.description": {
|
||||
"message": "$INST_DIR: Der absolute Pfad zum Ordner der Instanz"
|
||||
},
|
||||
"instance.settings.tabs.hooks.variables.inst-id.description": {
|
||||
"message": "$INST_ID: Der Name des Ordners der Instanz"
|
||||
},
|
||||
"instance.settings.tabs.hooks.variables.inst-java-args.description": {
|
||||
"message": "$INST_JAVA_ARGS: Die JVM-Argumente, die dem Spiel zur Verfügung gestellt werden"
|
||||
},
|
||||
"instance.settings.tabs.hooks.variables.inst-java.description": {
|
||||
"message": "$INST_JAVA: Der absolute Pfad zur Java-Binärdatei"
|
||||
},
|
||||
"instance.settings.tabs.hooks.variables.inst-mc-dir.description": {
|
||||
"message": "$INST_MC_DIR: Ein Alias für $INST_DIR"
|
||||
},
|
||||
"instance.settings.tabs.hooks.variables.inst-name.description": {
|
||||
"message": "$INST_NAME: Der Name der Instanz"
|
||||
},
|
||||
"instance.settings.tabs.hooks.wrapper": {
|
||||
"message": "Wrapper"
|
||||
},
|
||||
@@ -1895,18 +1904,9 @@
|
||||
"search.filter.locked.instance.sync": {
|
||||
"message": "Mit Instanz synchronisieren"
|
||||
},
|
||||
"search.filter.locked.server": {
|
||||
"message": "Vom Server bereitgestellt"
|
||||
},
|
||||
"search.filter.locked.server-environment.title": {
|
||||
"message": "Nur Clientseitige Mods können der Serverinstanz hinzugefügt werden"
|
||||
},
|
||||
"search.filter.locked.server-game-version.title": {
|
||||
"message": "Spielversion wird vom Server bereitgestellt"
|
||||
},
|
||||
"search.filter.locked.server-loader.title": {
|
||||
"message": "Loader wird vom Server bereitgestellt"
|
||||
},
|
||||
"settings.sidebar.label.account": {
|
||||
"message": "Konto"
|
||||
},
|
||||
|
||||
@@ -210,7 +210,7 @@
|
||||
"message": "Rechte Seitenleiste ausblenden"
|
||||
},
|
||||
"app.appearance-settings.unknown-pack-warning.description": {
|
||||
"message": "Sicherheitswarnung vor der Installation eines Modrinth-Packs (.mrpack) anzeigen, das nicht auf Modrinth gehostet wird."
|
||||
"message": "Zeige eine Sicherheitswarnung an, bevor ein Modrinth-Paket (.mrpack) installiert wird, welches nicht auf Modrinth gehostet wird."
|
||||
},
|
||||
"app.appearance-settings.unknown-pack-warning.title": {
|
||||
"message": "Warne mich, bevor unbekannte Modpacks installiert werden"
|
||||
@@ -377,18 +377,9 @@
|
||||
"app.instance.admonitions.shared-instance.review-header": {
|
||||
"message": "Änderungen überprüfen"
|
||||
},
|
||||
"app.instance.admonitions.shared-instance.review-update-button": {
|
||||
"message": "Update überprüfen"
|
||||
},
|
||||
"app.instance.admonitions.shared-instance.reviewing-button": {
|
||||
"message": "Wird überprüft..."
|
||||
},
|
||||
"app.instance.admonitions.shared-instance.update-available-body": {
|
||||
"message": "Ein Update ist erforderlich, um {name} zu spielen. Bitte aktualisiere auf die neueste Version, um das Spiel zu starten."
|
||||
},
|
||||
"app.instance.admonitions.shared-instance.update-available-header": {
|
||||
"message": "Ein Update ist verfügbar"
|
||||
},
|
||||
"app.instance.confirm-delete.admonition-body": {
|
||||
"message": "Alle Daten deiner Instanz werden permanent gelöscht, einschließlich deiner Welten, Konfigurationen und allen installierten Inhalten."
|
||||
},
|
||||
@@ -590,6 +581,9 @@
|
||||
"app.instance.worlds.no-worlds-heading": {
|
||||
"message": "Keine Server oder Welten hinzugefügt"
|
||||
},
|
||||
"app.instance.worlds.refreshing": {
|
||||
"message": "Wird aktualisiert..."
|
||||
},
|
||||
"app.instance.worlds.remove-server-modal.remove-button": {
|
||||
"message": "Server entfernen"
|
||||
},
|
||||
@@ -701,15 +695,6 @@
|
||||
"app.modal.install-to-play.report-reason": {
|
||||
"message": "Gegen welche Regel verstößt diese Instanz?"
|
||||
},
|
||||
"app.modal.install-to-play.report-reason.inappropriate": {
|
||||
"message": "Unangemessen"
|
||||
},
|
||||
"app.modal.install-to-play.report-reason.malicious": {
|
||||
"message": "Bösartig"
|
||||
},
|
||||
"app.modal.install-to-play.report-reason.spam": {
|
||||
"message": "Spam"
|
||||
},
|
||||
"app.modal.install-to-play.report-shared-instance-header": {
|
||||
"message": "Geteilte Instanz melden"
|
||||
},
|
||||
@@ -849,7 +834,7 @@
|
||||
"message": "Startet Instanzen im Vollbildmodus durch Aktualisieren ihrer options.txt-Datei."
|
||||
},
|
||||
"app.settings.default-instance-options.fullscreen.title": {
|
||||
"message": "Vollbildschirm"
|
||||
"message": "Vollbild"
|
||||
},
|
||||
"app.settings.default-instance-options.height.description": {
|
||||
"message": "Die Höhe des Spielfensters beim Starten"
|
||||
@@ -981,7 +966,7 @@
|
||||
"message": "Neues App-Verzeichnis auswählen"
|
||||
},
|
||||
"app.settings.resource-management.app-directory.title": {
|
||||
"message": "App Installation"
|
||||
"message": "App-Verzeichnis"
|
||||
},
|
||||
"app.settings.resource-management.maximum-concurrent-downloads.description": {
|
||||
"message": "Anzahl der Dateien, die die App auf einmal herunterladen kann. Senke dies, wenn downloads auf deiner Verbindung unzuverlässig sind. Benötigt einen App-Neustart."
|
||||
@@ -1005,7 +990,7 @@
|
||||
"message": "Verhalten"
|
||||
},
|
||||
"app.settings.tabs.default-instance-options": {
|
||||
"message": "Standard Spieleinstellungen"
|
||||
"message": "Standard-Spieloptionen"
|
||||
},
|
||||
"app.settings.tabs.java-installations": {
|
||||
"message": "Java-Installationen"
|
||||
@@ -1188,10 +1173,10 @@
|
||||
"message": "Das Feedback geht direkt an das Modrinth-Team und wird helfen, zukünftige Updates zu gestalten!"
|
||||
},
|
||||
"app.survey.no-thanks": {
|
||||
"message": "Nein, danke"
|
||||
"message": "Nein danke"
|
||||
},
|
||||
"app.survey.take-survey": {
|
||||
"message": "Beantworten"
|
||||
"message": "An Umfrage teilnehmen"
|
||||
},
|
||||
"app.survey.title": {
|
||||
"message": "Hey, Modrinth Nutzer!"
|
||||
@@ -1226,6 +1211,9 @@
|
||||
"app.update.complete-toast.title": {
|
||||
"message": "Version {version} wurde erfolgreich installiert!"
|
||||
},
|
||||
"app.user.project.install-to-instance": {
|
||||
"message": "In Instanz installieren"
|
||||
},
|
||||
"app.world.server-modal.placeholder-address": {
|
||||
"message": "example.modrinth.gg"
|
||||
},
|
||||
@@ -1619,6 +1607,27 @@
|
||||
"instance.settings.tabs.hooks.title": {
|
||||
"message": "Start-Hooks"
|
||||
},
|
||||
"instance.settings.tabs.hooks.variables.description": {
|
||||
"message": "Hooks werden im Arbeitsverzeichnis der Instanz mit den folgenden Variablen ausgeführt:"
|
||||
},
|
||||
"instance.settings.tabs.hooks.variables.inst-dir.description": {
|
||||
"message": "$INST_DIR: Der absolute Pfad zum Ordner der Instanz"
|
||||
},
|
||||
"instance.settings.tabs.hooks.variables.inst-id.description": {
|
||||
"message": "$INST_ID: Der Name des Ordners der Instanz"
|
||||
},
|
||||
"instance.settings.tabs.hooks.variables.inst-java-args.description": {
|
||||
"message": "$INST_JAVA_ARGS: Die JVM-Argumente, die dem Spiel zur Verfügung gestellt werden"
|
||||
},
|
||||
"instance.settings.tabs.hooks.variables.inst-java.description": {
|
||||
"message": "$INST_JAVA: Der absolute Pfad zur Java-Binärdatei"
|
||||
},
|
||||
"instance.settings.tabs.hooks.variables.inst-mc-dir.description": {
|
||||
"message": "$INST_MC_DIR: Ein Alias für $INST_DIR"
|
||||
},
|
||||
"instance.settings.tabs.hooks.variables.inst-name.description": {
|
||||
"message": "$INST_NAME: Der Name der Instanz"
|
||||
},
|
||||
"instance.settings.tabs.hooks.wrapper": {
|
||||
"message": "Wrapper"
|
||||
},
|
||||
@@ -1895,18 +1904,9 @@
|
||||
"search.filter.locked.instance.sync": {
|
||||
"message": "Mit Instanz synchronisieren"
|
||||
},
|
||||
"search.filter.locked.server": {
|
||||
"message": "Vom Server vorgegeben"
|
||||
},
|
||||
"search.filter.locked.server-environment.title": {
|
||||
"message": "Nur clientseitige Mods können der Serverinstanz hinzugefügt werden"
|
||||
},
|
||||
"search.filter.locked.server-game-version.title": {
|
||||
"message": "Spielversion vom Server vorgegeben"
|
||||
},
|
||||
"search.filter.locked.server-loader.title": {
|
||||
"message": "Loader vom Server vorgegeben"
|
||||
},
|
||||
"settings.sidebar.label.account": {
|
||||
"message": "Konto"
|
||||
},
|
||||
|
||||
@@ -8,6 +8,9 @@
|
||||
"app.action-bar.downloads": {
|
||||
"message": "Downloads"
|
||||
},
|
||||
"app.action-bar.hide-downloads": {
|
||||
"message": "Hide active downloads"
|
||||
},
|
||||
"app.action-bar.hide-more-running-instances": {
|
||||
"message": "Hide more running instances"
|
||||
},
|
||||
@@ -110,6 +113,9 @@
|
||||
"app.action-bar.reload-to-update": {
|
||||
"message": "Reload to update"
|
||||
},
|
||||
"app.action-bar.show-downloads": {
|
||||
"message": "Show active downloads"
|
||||
},
|
||||
"app.action-bar.show-more-running-instances": {
|
||||
"message": "Show more running instances"
|
||||
},
|
||||
@@ -267,7 +273,7 @@
|
||||
"message": "Modpacks"
|
||||
},
|
||||
"app.browse.server-instance-content-warning": {
|
||||
"message": "Adding content can break compatibility when joining the server. Any added content will also be lost when you update the server instance content."
|
||||
"message": "Adding content may prevent you from joining this server. Any content you add will be removed when the managed server content is updated."
|
||||
},
|
||||
"app.browse.server.installing": {
|
||||
"message": "Installing"
|
||||
@@ -281,6 +287,12 @@
|
||||
"app.export-modal.export-button": {
|
||||
"message": "Export"
|
||||
},
|
||||
"app.export-modal.export-complete": {
|
||||
"message": "Export complete"
|
||||
},
|
||||
"app.export-modal.export-complete-description": {
|
||||
"message": "{name} was exported successfully."
|
||||
},
|
||||
"app.export-modal.header": {
|
||||
"message": "Export modpack"
|
||||
},
|
||||
@@ -296,6 +308,18 @@
|
||||
"app.export-modal.version-number-placeholder": {
|
||||
"message": "1.0.0"
|
||||
},
|
||||
"app.hosting.update-required.description": {
|
||||
"message": "You need to update to use Modrinth Hosting through the Modrinth App"
|
||||
},
|
||||
"app.hosting.update-required.download": {
|
||||
"message": "Download to update"
|
||||
},
|
||||
"app.hosting.update-required.rinthbot-alt": {
|
||||
"message": "Excited Modrinth Bot"
|
||||
},
|
||||
"app.hosting.update-required.title": {
|
||||
"message": "Modrinth App update required"
|
||||
},
|
||||
"app.install.phase.downloading_content": {
|
||||
"message": "Downloading content"
|
||||
},
|
||||
@@ -377,18 +401,9 @@
|
||||
"app.instance.admonitions.shared-instance.review-header": {
|
||||
"message": "Review changes"
|
||||
},
|
||||
"app.instance.admonitions.shared-instance.review-update-button": {
|
||||
"message": "Review update"
|
||||
},
|
||||
"app.instance.admonitions.shared-instance.reviewing-button": {
|
||||
"message": "Reviewing..."
|
||||
},
|
||||
"app.instance.admonitions.shared-instance.update-available-body": {
|
||||
"message": "An update is required to play {name}. Please update to latest version to launch the game."
|
||||
},
|
||||
"app.instance.admonitions.shared-instance.update-available-header": {
|
||||
"message": "An update is available"
|
||||
},
|
||||
"app.instance.confirm-delete.admonition-body": {
|
||||
"message": "All data for your instance will be permanently deleted, including your worlds, configs, and all installed content."
|
||||
},
|
||||
@@ -401,6 +416,12 @@
|
||||
"app.instance.confirm-delete.header": {
|
||||
"message": "Delete instance"
|
||||
},
|
||||
"app.instance.content.managed-content.modpack-header": {
|
||||
"message": "Modpack content"
|
||||
},
|
||||
"app.instance.content.managed-content.shared-header": {
|
||||
"message": "Shared content"
|
||||
},
|
||||
"app.instance.modpack-already-installed.body": {
|
||||
"message": "This modpack is already installed in the <bold>{instanceName}</bold> instance. Are you sure you want to duplicate it?"
|
||||
},
|
||||
@@ -425,6 +446,9 @@
|
||||
"app.instance.mods.content-type-project": {
|
||||
"message": "project"
|
||||
},
|
||||
"app.instance.mods.freeze-content": {
|
||||
"message": "Freeze version"
|
||||
},
|
||||
"app.instance.mods.locked-content": {
|
||||
"message": "Content in locked instances cannot be changed."
|
||||
},
|
||||
@@ -443,6 +467,9 @@
|
||||
"app.instance.mods.successfully-uploaded": {
|
||||
"message": "Successfully uploaded"
|
||||
},
|
||||
"app.instance.mods.unfreeze-content": {
|
||||
"message": "Unfreeze version"
|
||||
},
|
||||
"app.instance.share.empty.description": {
|
||||
"message": "You can share this instance with your friends!"
|
||||
},
|
||||
@@ -686,6 +713,9 @@
|
||||
"app.modal.install-to-play.invite-warning-with-creator": {
|
||||
"message": "This invite was created by <creator>{username}</creator>, not Modrinth. Only accept invites from people you trust."
|
||||
},
|
||||
"app.modal.install-to-play.managed-content.modpack-header": {
|
||||
"message": "Modpack content"
|
||||
},
|
||||
"app.modal.install-to-play.mod-count": {
|
||||
"message": "{count, plural, one {# mod} other {# mods}}"
|
||||
},
|
||||
@@ -704,15 +734,6 @@
|
||||
"app.modal.install-to-play.report-reason": {
|
||||
"message": "Which rule does this instance violate?"
|
||||
},
|
||||
"app.modal.install-to-play.report-reason.inappropriate": {
|
||||
"message": "Inappropriate"
|
||||
},
|
||||
"app.modal.install-to-play.report-reason.malicious": {
|
||||
"message": "Malicious"
|
||||
},
|
||||
"app.modal.install-to-play.report-reason.spam": {
|
||||
"message": "Spam"
|
||||
},
|
||||
"app.modal.install-to-play.report-shared-instance-header": {
|
||||
"message": "Report shared instance"
|
||||
},
|
||||
@@ -723,7 +744,7 @@
|
||||
"message": "For support requests, contact our <support-link>support team</support-link>. For bug reports, open a <github-link>GitHub issue</github-link>."
|
||||
},
|
||||
"app.modal.install-to-play.reviewed-files": {
|
||||
"message": "A file is only reviewed if it’s published to Modrinth, regardless of its file format (including .mrpack)."
|
||||
"message": "Files that aren't published to Modrinth aren't reviewed."
|
||||
},
|
||||
"app.modal.install-to-play.shared-instance": {
|
||||
"message": "Shared instance"
|
||||
@@ -1457,6 +1478,9 @@
|
||||
"instance.files.save-as": {
|
||||
"message": "Save as..."
|
||||
},
|
||||
"instance.last-played": {
|
||||
"message": "Last played"
|
||||
},
|
||||
"instance.locked.delete-button": {
|
||||
"message": "Delete instance"
|
||||
},
|
||||
@@ -1922,18 +1946,9 @@
|
||||
"search.filter.locked.instance.sync": {
|
||||
"message": "Sync with instance"
|
||||
},
|
||||
"search.filter.locked.server": {
|
||||
"message": "Provided by the server"
|
||||
},
|
||||
"search.filter.locked.server-environment.title": {
|
||||
"message": "Only client-side mods can be added to the server instance"
|
||||
},
|
||||
"search.filter.locked.server-game-version.title": {
|
||||
"message": "Game version is provided by the server"
|
||||
},
|
||||
"search.filter.locked.server-loader.title": {
|
||||
"message": "Loader is provided by the server"
|
||||
},
|
||||
"settings.sidebar.label.account": {
|
||||
"message": "Account"
|
||||
},
|
||||
|
||||
@@ -174,7 +174,7 @@
|
||||
"message": "Ocultar etiqueta de nombre"
|
||||
},
|
||||
"app.appearance-settings.jump-back-into-worlds.description": {
|
||||
"message": "Muestra los mundos recientes en la sección \"Volver a jugar\" en la página principal."
|
||||
"message": "Muestra los mundos recientes en la sección \"Volver a jugar\" en la página de inicio."
|
||||
},
|
||||
"app.appearance-settings.jump-back-into-worlds.title": {
|
||||
"message": "Volver a jugar mundos"
|
||||
@@ -186,7 +186,7 @@
|
||||
"message": "Minimizar app"
|
||||
},
|
||||
"app.appearance-settings.native-decorations.description": {
|
||||
"message": "Usa el borde de ventana de tu sistema. Requiere reiniciar la aplicación."
|
||||
"message": "Usa el borde de ventana de tu sistema. Necesita reiniciar la aplicación."
|
||||
},
|
||||
"app.appearance-settings.native-decorations.title": {
|
||||
"message": "Decoraciones nativas"
|
||||
@@ -225,7 +225,7 @@
|
||||
"message": "Confirmaciones"
|
||||
},
|
||||
"app.behavior-settings.content.title": {
|
||||
"message": "Página principal y contenido"
|
||||
"message": "Página de inicio y contenido"
|
||||
},
|
||||
"app.behavior-settings.startup-and-navigation.title": {
|
||||
"message": "Inicio y navegación"
|
||||
@@ -258,7 +258,7 @@
|
||||
"message": "Descubrir servidores"
|
||||
},
|
||||
"app.browse.hide-added-servers": {
|
||||
"message": "Ocultar los servidores ya agregados"
|
||||
"message": "Ocultar los servidores ya añadidos"
|
||||
},
|
||||
"app.browse.hide-installed-modpacks": {
|
||||
"message": "Esconder los ya instalados"
|
||||
@@ -372,23 +372,14 @@
|
||||
"message": "Enviar actualización"
|
||||
},
|
||||
"app.instance.admonitions.shared-instance.review-description": {
|
||||
"message": "Revisar los cambios de contenido que se compartirán con los usuarios de esta instancia."
|
||||
"message": "Revisa los cambios de contenido que se compartirán con los usuarios de esta instancia."
|
||||
},
|
||||
"app.instance.admonitions.shared-instance.review-header": {
|
||||
"message": "Revisar cambios"
|
||||
},
|
||||
"app.instance.admonitions.shared-instance.review-update-button": {
|
||||
"message": "Actualizar"
|
||||
},
|
||||
"app.instance.admonitions.shared-instance.reviewing-button": {
|
||||
"message": "Actualizando..."
|
||||
},
|
||||
"app.instance.admonitions.shared-instance.update-available-body": {
|
||||
"message": "Se requiere una actualización para jugar a {name}. por favor actualiza a la versión más reciente para abrir el juego."
|
||||
},
|
||||
"app.instance.admonitions.shared-instance.update-available-header": {
|
||||
"message": "Hay una actualización disponible"
|
||||
},
|
||||
"app.instance.confirm-delete.admonition-body": {
|
||||
"message": "Todos los datos de tu instancia se eliminarán permanentemente, incluidos tus mundos, configuraciones y todo el contenido instalado."
|
||||
},
|
||||
@@ -483,7 +474,7 @@
|
||||
"message": "Ningún usuario coincide con tus filtros."
|
||||
},
|
||||
"app.instance.share.remove-user-modal.effect-access": {
|
||||
"message": "Ya no recibirán actualizaciones para esta instancia compartida"
|
||||
"message": "Ya no recibirán actualizaciones para esta instancia"
|
||||
},
|
||||
"app.instance.share.remove-user-modal.effect-installed-copy": {
|
||||
"message": "Cualquier copia que tengan instalada se quedará en su dispositivo"
|
||||
@@ -510,10 +501,10 @@
|
||||
"message": "Si revocas el acceso de {username} a esta instancia, tendrás que invitarlos otra vez para que sigan recibiendo actualizaciones."
|
||||
},
|
||||
"app.instance.share.sign-in.button": {
|
||||
"message": "Iniciar sesión"
|
||||
"message": "Inicia sesión"
|
||||
},
|
||||
"app.instance.share.unable-to-connect.description": {
|
||||
"message": "El servicio de instancias compartidas no está disponible, inténtalo otra vez más tarde"
|
||||
"message": "El servicio de instancias compartidas no está disponible, por favor inténtalo otra vez más tarde"
|
||||
},
|
||||
"app.instance.share.unable-to-connect.heading": {
|
||||
"message": "No se puede conectar"
|
||||
@@ -590,6 +581,9 @@
|
||||
"app.instance.worlds.no-worlds-heading": {
|
||||
"message": "No hay servidores ni mundos añadidos"
|
||||
},
|
||||
"app.instance.worlds.refreshing": {
|
||||
"message": "Recargando..."
|
||||
},
|
||||
"app.instance.worlds.remove-server-modal.remove-button": {
|
||||
"message": "Eliminar servidor"
|
||||
},
|
||||
@@ -701,15 +695,6 @@
|
||||
"app.modal.install-to-play.report-reason": {
|
||||
"message": "¿Qué regla rompe esta instancia?"
|
||||
},
|
||||
"app.modal.install-to-play.report-reason.inappropriate": {
|
||||
"message": "Inapropiado"
|
||||
},
|
||||
"app.modal.install-to-play.report-reason.malicious": {
|
||||
"message": "Malicioso"
|
||||
},
|
||||
"app.modal.install-to-play.report-reason.spam": {
|
||||
"message": "Spam"
|
||||
},
|
||||
"app.modal.install-to-play.report-shared-instance-header": {
|
||||
"message": "Reportar instancia compartida"
|
||||
},
|
||||
@@ -717,7 +702,7 @@
|
||||
"message": "Reporte enviado"
|
||||
},
|
||||
"app.modal.install-to-play.report-support-and-bugs": {
|
||||
"message": "Para solicitudes de ayuda, contacte a nuestro <support-link>equipo de soporte</support-link>. Para reportar un bug, abra una <github-link>incidencia (issue) en GitHub</github-link>."
|
||||
"message": "Para solicitudes de ayuda, contacte a nuestro <support-link>equipo de soporte</support-link>. Para reportar un bug, abra una <github-link>issue en GitHub</github-link>."
|
||||
},
|
||||
"app.modal.install-to-play.reviewed-files": {
|
||||
"message": "Un archivo solo se revisa si se publica en Modrinth, sin importar su formato (incluido el .mrpack)."
|
||||
@@ -728,6 +713,9 @@
|
||||
"app.modal.install-to-play.shared-instance-content": {
|
||||
"message": "Contenido de la instancia compartida"
|
||||
},
|
||||
"app.modal.install-to-play.shared-instance-unknown-files-description": {
|
||||
"message": "Esta instancia contiene archivos que no están publicados en Modrinth. Recomendamos encarecidamente instalar únicamente archivos de fuentes de confianza."
|
||||
},
|
||||
"app.modal.install-to-play.unknown-files-description": {
|
||||
"message": "Este modpack para servidor contiene archivos que no están publicados en Modrinth. Recomendamos encarecidamente instalar únicamente archivos de fuentes de confianza."
|
||||
},
|
||||
@@ -737,6 +725,9 @@
|
||||
"app.modal.install-to-play.unrecognized-files": {
|
||||
"message": "Archivos no reconocidos"
|
||||
},
|
||||
"app.modal.install-to-play.user-blocked": {
|
||||
"message": "Usuario bloqueado"
|
||||
},
|
||||
"app.modal.install-to-play.view-contents": {
|
||||
"message": "Ver contenidos"
|
||||
},
|
||||
@@ -744,11 +735,56 @@
|
||||
"message": "Actualizar para jugar"
|
||||
},
|
||||
"app.modal.update-to-play.removed": {
|
||||
"message": "Eliminado"
|
||||
"message": "Se eliminó"
|
||||
},
|
||||
"app.modal.update-to-play.server-modpack-unknown-files-description": {
|
||||
"message": "Esta actualización de modpack para servidor contiene archivos que no están publicados en Modrinth. Recomendamos encarecidamente instalar únicamente archivos de fuentes de confianza."
|
||||
},
|
||||
"app.modal.update-to-play.shared-instance-added-label": {
|
||||
"message": "Se añadió"
|
||||
},
|
||||
"app.modal.update-to-play.shared-instance-removed-label": {
|
||||
"message": "Se eliminó"
|
||||
},
|
||||
"app.modal.update-to-play.shared-instance-unknown-files-description": {
|
||||
"message": "Esta actualización de instancia contiene archivos que no están publicados en Modrinth. Recomendamos encarecidamente instalar únicamente archivos de fuentes de confianza."
|
||||
},
|
||||
"app.modal.update-to-play.update-required": {
|
||||
"message": "Es necesario actualizar"
|
||||
},
|
||||
"app.modal.update-to-play.update-required-description": {
|
||||
"message": "Se necesita una actualización para jugar en {name}. Por favor actualiza a a la versión más reciente para iniciar el juego."
|
||||
},
|
||||
"app.nav.create-new-instance": {
|
||||
"message": "Crear una instancia nueva"
|
||||
},
|
||||
"app.nav.home": {
|
||||
"message": "Inicio"
|
||||
},
|
||||
"app.nav.library": {
|
||||
"message": "Biblioteca"
|
||||
},
|
||||
"app.nav.modrinth-account": {
|
||||
"message": "Cuenta Modrinth"
|
||||
},
|
||||
"app.nav.modrinth-hosting": {
|
||||
"message": "Modrinth Hosting"
|
||||
},
|
||||
"app.nav.sign-in-to-modrinth-account": {
|
||||
"message": "Inicia sesión con una cuenta Modrinth"
|
||||
},
|
||||
"app.nav.signed-in-as": {
|
||||
"message": "Sesión iniciada como <user>{username}</user>"
|
||||
},
|
||||
"app.nav.upgrade-to-modrinth-plus": {
|
||||
"message": "Mejorar a Modrinth+"
|
||||
},
|
||||
"app.news.title": {
|
||||
"message": "Noticias"
|
||||
},
|
||||
"app.news.view-all": {
|
||||
"message": "Ver todas las noticias"
|
||||
},
|
||||
"app.project.install-button.already-installed": {
|
||||
"message": "Este proyecto ya está instalado"
|
||||
},
|
||||
@@ -758,6 +794,9 @@
|
||||
"app.project.install-context.back-to-browse": {
|
||||
"message": "Volver al explorador"
|
||||
},
|
||||
"app.project.install-context.back-to-instance": {
|
||||
"message": "Volver a la instancia"
|
||||
},
|
||||
"app.project.version.all-versions": {
|
||||
"message": "Todas las versiones"
|
||||
},
|
||||
@@ -770,21 +809,177 @@
|
||||
"app.project.versions.already-installed": {
|
||||
"message": "Ya instalado"
|
||||
},
|
||||
"app.quick-instance-switcher.drag-show-tooltip": {
|
||||
"message": "Arrastra para mostrar las instancias recientes"
|
||||
},
|
||||
"app.quick-instance-switcher.drag-tooltip": {
|
||||
"message": "Arrastra para cambiar el tamaño"
|
||||
},
|
||||
"app.restarting": {
|
||||
"message": "Reiniciando..."
|
||||
},
|
||||
"app.settings.app-version": {
|
||||
"message": "App de Modrinth {version}"
|
||||
},
|
||||
"app.settings.default-instance-options.environment-variables.description": {
|
||||
"message": "Variables de entorno puestas al iniciar una instancia."
|
||||
},
|
||||
"app.settings.default-instance-options.environment-variables.placeholder": {
|
||||
"message": "Ingresa variables de entorno..."
|
||||
},
|
||||
"app.settings.default-instance-options.environment-variables.title": {
|
||||
"message": "Variables de entorno"
|
||||
},
|
||||
"app.settings.default-instance-options.fullscreen.description": {
|
||||
"message": "Inicia instancias en pantalla completa al modificar su archivo options.txt."
|
||||
},
|
||||
"app.settings.default-instance-options.fullscreen.title": {
|
||||
"message": "Pantalla completa"
|
||||
},
|
||||
"app.settings.default-instance-options.height.description": {
|
||||
"message": "El largo que tendrá la ventana del juego al iniciar."
|
||||
},
|
||||
"app.settings.default-instance-options.height.placeholder": {
|
||||
"message": "Ingresa la altura..."
|
||||
},
|
||||
"app.settings.default-instance-options.height.title": {
|
||||
"message": "Largo"
|
||||
},
|
||||
"app.settings.default-instance-options.java-arguments.description": {
|
||||
"message": "Los argumentos dados a Java al iniciar una instancia."
|
||||
},
|
||||
"app.settings.default-instance-options.java-arguments.placeholder": {
|
||||
"message": "Ingresa los argumentos de Java..."
|
||||
},
|
||||
"app.settings.default-instance-options.java-arguments.title": {
|
||||
"message": "Argumentos de Java"
|
||||
},
|
||||
"app.settings.default-instance-options.memory-allocation.description": {
|
||||
"message": "La memoria máxima disponible para cada instancia."
|
||||
},
|
||||
"app.settings.default-instance-options.memory-allocation.title": {
|
||||
"message": "Asignación de memoria"
|
||||
},
|
||||
"app.settings.default-instance-options.post-exit-hook.description": {
|
||||
"message": "Se ejecutan tras cerrar el juego."
|
||||
},
|
||||
"app.settings.default-instance-options.post-exit-hook.placeholder": {
|
||||
"message": "Ingresa comandos post-cierre..."
|
||||
},
|
||||
"app.settings.default-instance-options.post-exit-hook.title": {
|
||||
"message": "Comandos post-cierre"
|
||||
},
|
||||
"app.settings.default-instance-options.pre-launch-hook.description": {
|
||||
"message": "Se ejecutan antes de iniciar el juego."
|
||||
},
|
||||
"app.settings.default-instance-options.pre-launch-hook.placeholder": {
|
||||
"message": "Ingresa comandos pre-inicio..."
|
||||
},
|
||||
"app.settings.default-instance-options.pre-launch-hook.title": {
|
||||
"message": "Comandos pre-inicio"
|
||||
},
|
||||
"app.settings.default-instance-options.width.description": {
|
||||
"message": "El ancho que tendrá la ventana del juego al iniciar."
|
||||
},
|
||||
"app.settings.default-instance-options.width.placeholder": {
|
||||
"message": "Ingresa el ancho..."
|
||||
},
|
||||
"app.settings.default-instance-options.width.title": {
|
||||
"message": "Ancho"
|
||||
},
|
||||
"app.settings.default-instance-options.wrapper-hook.description": {
|
||||
"message": "Envuelven al proceso iniciador de Minecraft para añadir funcionalidades o configuraciones."
|
||||
},
|
||||
"app.settings.default-instance-options.wrapper-hook.placeholder": {
|
||||
"message": "Ingresa comandos de envoltura..."
|
||||
},
|
||||
"app.settings.default-instance-options.wrapper-hook.title": {
|
||||
"message": "Comandos de envoltura"
|
||||
},
|
||||
"app.settings.developer-mode-button.label": {
|
||||
"message": "Alternar el modo desarrollador"
|
||||
},
|
||||
"app.settings.developer-mode-enabled": {
|
||||
"message": "Modo desarrollador activado."
|
||||
},
|
||||
"app.settings.downloading": {
|
||||
"message": "Descargando v{version}"
|
||||
},
|
||||
"app.settings.java-installations.location.title": {
|
||||
"message": "Directorio de Java {version, number}"
|
||||
},
|
||||
"app.settings.operating-system.macos": {
|
||||
"message": "macOS"
|
||||
},
|
||||
"app.settings.privacy.ads-consent.intro": {
|
||||
"message": "Los anuncios hacen posible a Modrinth y financian los pagos a los creadores. Nuestros socios pueden guardar o acceder a cookies en la aplicación para personalizar anuncios y medir su rendimiento. Puedes rechazar esto o administrar tus preferencias abajo."
|
||||
},
|
||||
"app.settings.privacy.discord-rich-presence.description": {
|
||||
"message": "Mostrar la aplicación de Modrinth como tu actividad actual en Discord. Esto no afecta a cualquier Rich Presence añadida a instancias con mods. Necesita reiniciar la app."
|
||||
},
|
||||
"app.settings.privacy.discord-rich-presence.title": {
|
||||
"message": "Discord Rich Presence"
|
||||
},
|
||||
"app.settings.privacy.telemetry.description": {
|
||||
"message": "Modrinth recolecta datos analíticos anónimos y datos de uso para mejorar la experiencia de nuestros usuarios y para personalizar tu experiencia. Al desactivar esta opción, tus datos dejarán de ser recolectados."
|
||||
},
|
||||
"app.settings.privacy.telemetry.title": {
|
||||
"message": "Telemetría"
|
||||
},
|
||||
"app.settings.resource-management.always-show-copy-details.description": {
|
||||
"message": "Muestra la opción 'Copiar detalles' mientras haya una instalación en cola o instalando. Siempre está disponible para las instalaciones fallidas o interrumpidas."
|
||||
},
|
||||
"app.settings.resource-management.always-show-copy-details.title": {
|
||||
"message": "Siempre mostrar 'Copiar detalles'"
|
||||
},
|
||||
"app.settings.resource-management.app-cache.confirm.description": {
|
||||
"message": "La app cargará más lento hasta que se reconstruya completamente el caché."
|
||||
},
|
||||
"app.settings.resource-management.app-cache.confirm.title": {
|
||||
"message": "¿Limpiar el caché?"
|
||||
},
|
||||
"app.settings.resource-management.app-cache.description": {
|
||||
"message": "Borra todos los datos en caché y los redescarga de Modrinth. La app cargará más lento hasta que se reconstruya completamente el caché."
|
||||
},
|
||||
"app.settings.resource-management.app-cache.purge": {
|
||||
"message": "Limpiar caché"
|
||||
},
|
||||
"app.settings.resource-management.app-cache.title": {
|
||||
"message": "Caché de la aplicación"
|
||||
},
|
||||
"app.settings.resource-management.app-database-backups.description": {
|
||||
"message": "Los respaldos de los datos de aplicación importantes se guardan aquí, en caso de que los necesites luego."
|
||||
},
|
||||
"app.settings.resource-management.app-database-backups.open-folder": {
|
||||
"message": "Abrir la carpeta de respaldos"
|
||||
},
|
||||
"app.settings.resource-management.app-database-backups.title": {
|
||||
"message": "Respaldos de la base de datos de la aplicación"
|
||||
},
|
||||
"app.settings.resource-management.app-directory.browse": {
|
||||
"message": "Buscar un directorio para la aplicación"
|
||||
},
|
||||
"app.settings.resource-management.app-directory.description": {
|
||||
"message": "Aquí es donde la aplicación guardará las instancias y otros archivos. Los cambios entrarán en efecto al reiniciar la app."
|
||||
},
|
||||
"app.settings.resource-management.app-directory.select": {
|
||||
"message": "Seleccionar un directorio nuevo para la app"
|
||||
},
|
||||
"app.settings.resource-management.app-directory.title": {
|
||||
"message": "Directorio de la aplicación"
|
||||
},
|
||||
"app.settings.resource-management.maximum-concurrent-downloads.description": {
|
||||
"message": "El número de archivos que la app puede descargar a la vez. Baja este número si las descargas no son estables con tu conexión. Necesita reiniciar la app."
|
||||
},
|
||||
"app.settings.resource-management.maximum-concurrent-downloads.title": {
|
||||
"message": "Máximas descargas a la vez"
|
||||
},
|
||||
"app.settings.resource-management.maximum-concurrent-writes.description": {
|
||||
"message": "El número de archivos que la app puede escribir en el disco a la vez. Baja este número si frecuentemente encuentras errores de I/O. Necesita reiniciar la app."
|
||||
},
|
||||
"app.settings.resource-management.maximum-concurrent-writes.title": {
|
||||
"message": "Máximas escrituras a la vez"
|
||||
},
|
||||
"app.settings.sidebar.label.instances": {
|
||||
"message": "Instancias"
|
||||
},
|
||||
@@ -794,6 +989,9 @@
|
||||
"app.settings.tabs.behavior": {
|
||||
"message": "Comportamiento"
|
||||
},
|
||||
"app.settings.tabs.default-instance-options": {
|
||||
"message": "Opciones por defecto del juego"
|
||||
},
|
||||
"app.settings.tabs.java-installations": {
|
||||
"message": "Instalaciones de Java"
|
||||
},
|
||||
@@ -968,11 +1166,17 @@
|
||||
"app.skins.toggle-ears-features-on": {
|
||||
"message": "Activar"
|
||||
},
|
||||
"app.survey.body": {
|
||||
"message": "¿Te importaría responder unas cuantas preguntas sobre tu experiencia con la aplicación de Modrinth?"
|
||||
},
|
||||
"app.survey.footer": {
|
||||
"message": "¡Tus comentarios llegarán directamente al equipo de Modrinth, y los ayudará a hacer mejores actualizaciones!"
|
||||
},
|
||||
"app.survey.no-thanks": {
|
||||
"message": "No gracias"
|
||||
"message": "No, gracias"
|
||||
},
|
||||
"app.survey.take-survey": {
|
||||
"message": "Aceptar encuesta"
|
||||
},
|
||||
"app.survey.title": {
|
||||
"message": "¡Hola, usuario de Modrinth!"
|
||||
@@ -1007,6 +1211,9 @@
|
||||
"app.update.complete-toast.title": {
|
||||
"message": "¡La versión {version} se ha instalado correctamente!"
|
||||
},
|
||||
"app.user.project.install-to-instance": {
|
||||
"message": "Instalar a instancia"
|
||||
},
|
||||
"app.world.server-modal.placeholder-address": {
|
||||
"message": "ejemplo.modrinth.gg"
|
||||
},
|
||||
@@ -1025,18 +1232,39 @@
|
||||
"app.world.world-item.players-online": {
|
||||
"message": "{count} en línea"
|
||||
},
|
||||
"content.shared-instance.change-version-body": {
|
||||
"message": "Cambiar la versión solo cambiará tu copia local. Las actualizaciones futuras a la instancia pueden restablecerla o cambiarla de nuevo."
|
||||
},
|
||||
"content.shared-instance.delete-bulk-body": {
|
||||
"message": "Algunos proyectos seleccionados hacen parte de la instancia compartida. Borrarlos solo cambiará tu copia local, y las actualizaciones futuras a la instancia pueden restablecerlos o cambiarlos de nuevo."
|
||||
},
|
||||
"content.shared-instance.delete-button": {
|
||||
"message": "Borrar de todas formas"
|
||||
},
|
||||
"content.shared-instance.delete-many-button": {
|
||||
"message": "Borrar {count, number} proyectos de todas formas"
|
||||
},
|
||||
"content.shared-instance.delete-single-body": {
|
||||
"message": "Borrarlo solo cambiará tu copia local, y las actualizaciones futuras a la instancia pueden restablecerlo o cambiarlo de nuevo."
|
||||
},
|
||||
"content.shared-instance.disable-bulk-body": {
|
||||
"message": "Algunos proyectos seleccionados hacen parte de la instancia compartida. Desactivarlos solo cambiará tu copia local, y las actualizaciones futuras a la instancia pueden reactivarlos, restablecerlos o cambiarlos de nuevo."
|
||||
},
|
||||
"content.shared-instance.disable-button": {
|
||||
"message": "Desactivar de todas formas"
|
||||
},
|
||||
"content.shared-instance.disable-many-button": {
|
||||
"message": "Desactivar {count, number} proyectos de todas formas"
|
||||
},
|
||||
"content.shared-instance.disable-single-body": {
|
||||
"message": "Desactivarlo solo cambiará tu copia local, y las actualizaciones futuras a la instancia pueden reactivarlo, restablecerlo o cambiarlo de nuevo."
|
||||
},
|
||||
"content.shared-instance.unlink-body": {
|
||||
"message": "Desvincular solo cambiará tu copia local, y las actualizaciones futuras a la instancia pueden restablecerlo o cambiarlo de nuevo."
|
||||
},
|
||||
"content.shared-instance.warning-header": {
|
||||
"message": "Esto es parte de la instancia compartida"
|
||||
},
|
||||
"friends.action.add-friend": {
|
||||
"message": "Añadir un amigo"
|
||||
},
|
||||
@@ -1100,6 +1328,66 @@
|
||||
"friends.sign-in-to-add-friends": {
|
||||
"message": "<link>¡Inicia sesión en una cuenta de Modrinth</link> para añadir amigos y ver qué están jugando!"
|
||||
},
|
||||
"installation-settings.shared-instance.linked-title": {
|
||||
"message": "Vincular instancia compartida"
|
||||
},
|
||||
"installation-settings.shared-instance.title": {
|
||||
"message": "Despublicar instancia"
|
||||
},
|
||||
"installation-settings.shared-instance.unlink-button": {
|
||||
"message": "Desvincular instancia compartida"
|
||||
},
|
||||
"installation-settings.shared-instance.unlink-description": {
|
||||
"message": "Desconecta esta instancia local de cualquier actualización futura."
|
||||
},
|
||||
"installation-settings.shared-instance.unlinking-button": {
|
||||
"message": "Desvinculando..."
|
||||
},
|
||||
"installation-settings.shared-instance.unpublish-button": {
|
||||
"message": "Despublicar instancia compartida"
|
||||
},
|
||||
"installation-settings.shared-instance.unpublish-description": {
|
||||
"message": "Borra esta instancia compartida de Modrinth y deja de enviar actualizaciones a cualquiera que la tenga. Tu instancia local no se verá afectada."
|
||||
},
|
||||
"installation-settings.shared-instance.unpublishing-button": {
|
||||
"message": "Despublicando..."
|
||||
},
|
||||
"installation-settings.unlink-shared-instance.modal.admonition-body": {
|
||||
"message": "Esto solo afecta a tu instancia local. Tu contenido instalado se quedará en este dispositivo, y la instancia compartida, junto a todos los que la estén usando, no se verán afectados."
|
||||
},
|
||||
"installation-settings.unlink-shared-instance.modal.admonition-header": {
|
||||
"message": "Desvinculando instancia compartida"
|
||||
},
|
||||
"installation-settings.unlink-shared-instance.modal.header": {
|
||||
"message": "Desvincular instancia compartida"
|
||||
},
|
||||
"installation-settings.unpublish-shared-instance.modal.admonition-body": {
|
||||
"message": "Esto borrará la instancia compartida de los servidores de Modrinth. La gente que esté usando esta instancia dejará de recibir actualizaciones, pero tu instancia local y sus contenidos se quedarán en este dispositivo."
|
||||
},
|
||||
"installation-settings.unpublish-shared-instance.modal.admonition-header": {
|
||||
"message": "Despublicando instancia compartida"
|
||||
},
|
||||
"installation-settings.unpublish-shared-instance.modal.header": {
|
||||
"message": "Despublicar instancia compartida"
|
||||
},
|
||||
"instance.action.create-shortcut": {
|
||||
"message": "Crear acceso directo"
|
||||
},
|
||||
"instance.action.export-modpack": {
|
||||
"message": "Exportar modpack"
|
||||
},
|
||||
"instance.action.launch-instance": {
|
||||
"message": "Iniciar instancia"
|
||||
},
|
||||
"instance.action.more-actions": {
|
||||
"message": "Más acciones"
|
||||
},
|
||||
"instance.action.open-folder": {
|
||||
"message": "Abrir carpeta"
|
||||
},
|
||||
"instance.action.repair": {
|
||||
"message": "Reparar"
|
||||
},
|
||||
"instance.action.settings": {
|
||||
"message": "Ajustes de la instancia"
|
||||
},
|
||||
@@ -1178,6 +1466,12 @@
|
||||
"instance.settings.sharing.active-invites.code": {
|
||||
"message": "Link de invitación"
|
||||
},
|
||||
"instance.settings.sharing.active-invites.description": {
|
||||
"message": "Cualquiera con alguno de estos links se puede unir mientras el link esté activo."
|
||||
},
|
||||
"instance.settings.sharing.active-invites.empty": {
|
||||
"message": "No hay invitaciones activas."
|
||||
},
|
||||
"instance.settings.sharing.active-invites.expires": {
|
||||
"message": "Expira"
|
||||
},
|
||||
@@ -1193,6 +1487,9 @@
|
||||
"instance.settings.sharing.active-invites.uses": {
|
||||
"message": "Usos"
|
||||
},
|
||||
"instance.settings.sharing.revoke-invite.admonition-body": {
|
||||
"message": "El link de invitación <monospace>{code}</monospace> dejará de funcionar inmediatamente. Cualquiera que ya se haya unido mantendrá su acceso."
|
||||
},
|
||||
"instance.settings.sharing.revoke-invite.admonition-header": {
|
||||
"message": "Esta acción no se puede deshacer"
|
||||
},
|
||||
@@ -1310,6 +1607,27 @@
|
||||
"instance.settings.tabs.hooks.title": {
|
||||
"message": "Hooks de inicio del juego"
|
||||
},
|
||||
"instance.settings.tabs.hooks.variables.description": {
|
||||
"message": "Todas estas se ejecutan en el directorio de trabajo de la instancia, con las siguientes variables:"
|
||||
},
|
||||
"instance.settings.tabs.hooks.variables.inst-dir.description": {
|
||||
"message": "$INST_DIR: La ruta absoluta a la carpeta de la instancia"
|
||||
},
|
||||
"instance.settings.tabs.hooks.variables.inst-id.description": {
|
||||
"message": "$INST_ID: El nombre la carpeta de la instancia"
|
||||
},
|
||||
"instance.settings.tabs.hooks.variables.inst-java-args.description": {
|
||||
"message": "$INST_JAVA_ARGS: Los Argumentos JVM dados al juego"
|
||||
},
|
||||
"instance.settings.tabs.hooks.variables.inst-java.description": {
|
||||
"message": "$INST_JAVA: La ruta absoluta a el binario de Java"
|
||||
},
|
||||
"instance.settings.tabs.hooks.variables.inst-mc-dir.description": {
|
||||
"message": "$INST_MC_DIR: Un alias que apunta a $INST_DIR"
|
||||
},
|
||||
"instance.settings.tabs.hooks.variables.inst-name.description": {
|
||||
"message": "$INST_NAME: El nombre de la instancia"
|
||||
},
|
||||
"instance.settings.tabs.hooks.wrapper": {
|
||||
"message": "Wrapper"
|
||||
},
|
||||
@@ -1325,6 +1643,9 @@
|
||||
"instance.settings.tabs.installation.loader-version": {
|
||||
"message": "Versión de {loader}"
|
||||
},
|
||||
"instance.settings.tabs.installation.locked": {
|
||||
"message": "Los ajustes de instalación no están disponibles mientras esta instancia esté bloqueada."
|
||||
},
|
||||
"instance.settings.tabs.java": {
|
||||
"message": "Java y memoria"
|
||||
},
|
||||
@@ -1364,6 +1685,9 @@
|
||||
"instance.settings.tabs.java.java-path-placeholder": {
|
||||
"message": "/ruta/de/java"
|
||||
},
|
||||
"instance.settings.tabs.sharing": {
|
||||
"message": "Compartir"
|
||||
},
|
||||
"instance.settings.tabs.window": {
|
||||
"message": "Ventana"
|
||||
},
|
||||
@@ -1394,14 +1718,62 @@
|
||||
"instance.settings.tabs.window.width.enter": {
|
||||
"message": "Ingresa el ancho..."
|
||||
},
|
||||
"instance.shared-instance.error.title": {
|
||||
"message": "Algo ha salido mal"
|
||||
},
|
||||
"instance.shared-instance.network-error.text": {
|
||||
"message": "No se pudo conectar con la API de las instancias compartidas"
|
||||
},
|
||||
"instance.shared-instance.network-error.title": {
|
||||
"message": "Error de conexión"
|
||||
},
|
||||
"instance.shared-instance.owner-tooltip": {
|
||||
"message": "El contenido de la instancia se está compartiendo a otros usuarios."
|
||||
},
|
||||
"instance.shared-instance.publish-review.added-label": {
|
||||
"message": "Se añadió"
|
||||
},
|
||||
"instance.shared-instance.publish-review.admonition-header": {
|
||||
"message": "Enviar actualización a los jugadores"
|
||||
},
|
||||
"instance.shared-instance.publish-review.config-title-v2": {
|
||||
"message": "Seleccionar archivos de configuración"
|
||||
},
|
||||
"instance.shared-instance.publish-review.description": {
|
||||
"message": "Revisa los cambios de contenido y escoge cualquier archivo de configuración que quieras incluir en esta actualización."
|
||||
},
|
||||
"instance.shared-instance.publish-review.header": {
|
||||
"message": "Revisar cambios"
|
||||
},
|
||||
"instance.shared-instance.publish-review.publish-button": {
|
||||
"message": "Enviar actualización"
|
||||
},
|
||||
"instance.shared-instance.publish-review.removed-label": {
|
||||
"message": "Se removió"
|
||||
"message": "Se eliminó"
|
||||
},
|
||||
"instance.shared-instance.tooltip": {
|
||||
"message": "El contenido de esta instancia está siendo administrado por alguien más."
|
||||
},
|
||||
"instance.shared-instance.unavailable.access-revoked-text": {
|
||||
"message": "Tu acceso a la instancia compartida fue revocado. Esta instancia seguirá disponible, pero ya no está vinculada, y no recibirás más actualizaciones."
|
||||
},
|
||||
"instance.shared-instance.unavailable.deleted-text": {
|
||||
"message": "La instancia principal fue borrada. Esta instancia seguirá disponible, pero ya no está vinculada, y no recibirás más actualizaciones."
|
||||
},
|
||||
"instance.shared-instance.unavailable.locked-text": {
|
||||
"message": "Esta instancia compartida fue bloqueada por el equipo de Moderación de Contenido. Ya no recibirá más actualizaciones de la instancia principal y tampoco se puede jugar."
|
||||
},
|
||||
"instance.shared-instance.unavailable.locked-title": {
|
||||
"message": "Instancia bloqueada"
|
||||
},
|
||||
"instance.shared-instance.unavailable.manager-fallback": {
|
||||
"message": "el administrador de la instancia"
|
||||
},
|
||||
"instance.shared-instance.unavailable.text": {
|
||||
"message": "Tu instancia local sigue disponible, pero ya no está vinculada y no recibirá actualizaciones."
|
||||
},
|
||||
"instance.shared-instance.unavailable.title": {
|
||||
"message": "Instancia compartida no disponible"
|
||||
},
|
||||
"instance.worlds.a_minecraft_server": {
|
||||
"message": "Un servidor de Minecraft"
|
||||
@@ -1466,6 +1838,12 @@
|
||||
"minecraft-account.sign-in": {
|
||||
"message": "Inicia sesión en Minecraft"
|
||||
},
|
||||
"minecraft-required.description": {
|
||||
"message": "Necesitas una cuenta de Microsoft con Minecraft comprado para poder iniciar el juego."
|
||||
},
|
||||
"minecraft-required.description-header": {
|
||||
"message": "Inicia sesión en Microsoft"
|
||||
},
|
||||
"minecraft-required.dont-have-account": {
|
||||
"message": "¿No tienes una cuenta?"
|
||||
},
|
||||
@@ -1479,13 +1857,28 @@
|
||||
"message": "Minecraft requerido"
|
||||
},
|
||||
"minecraft-required.sign-in": {
|
||||
"message": "Iniciar sesión con Microsoft"
|
||||
"message": "Inicia sesión en Microsoft"
|
||||
},
|
||||
"modal.modrinth-account-required.browser-description": {
|
||||
"message": "Se abrió una pestaña para que inicies sesión. Completa el proceso ahí, y luego regresa a la app."
|
||||
},
|
||||
"modal.modrinth-account-required.continue-in-browser-heading": {
|
||||
"message": "Continúa en el navegador"
|
||||
},
|
||||
"modal.modrinth-account-required.create-account-button": {
|
||||
"message": "Crea una cuenta"
|
||||
},
|
||||
"modal.modrinth-account-required.description": {
|
||||
"message": "Necesitas iniciar sesión con tu cuenta Modrinth antes de que puedas usar esto."
|
||||
},
|
||||
"modal.modrinth-account-required.header": {
|
||||
"message": "Cuenta requerida"
|
||||
},
|
||||
"modal.modrinth-account-required.open-browser-again-button": {
|
||||
"message": "Abrir el navegador otra vez"
|
||||
},
|
||||
"modal.modrinth-account-required.sign-in-button": {
|
||||
"message": "Iniciar sesión en Modrinth"
|
||||
"message": "Inicia sesión en Modrinth"
|
||||
},
|
||||
"modal.modrinth-account-required.sign-in-heading": {
|
||||
"message": "Iniciar sesión con una cuenta de Modrinth"
|
||||
@@ -1496,6 +1889,9 @@
|
||||
"modal.modrinth-account-required.support-prompt": {
|
||||
"message": "¿Tienes problemas con al iniciar sesión? <support>Consigue ayuda</support>"
|
||||
},
|
||||
"modal.modrinth-account-required.waiting-for-browser": {
|
||||
"message": "Esperando la confirmación del navegador..."
|
||||
},
|
||||
"search.filter.locked.instance": {
|
||||
"message": "Proporcionado por la instancia"
|
||||
},
|
||||
@@ -1508,18 +1904,9 @@
|
||||
"search.filter.locked.instance.sync": {
|
||||
"message": "Sincronizar con la instancia"
|
||||
},
|
||||
"search.filter.locked.server": {
|
||||
"message": "Proporcionado por el servidor"
|
||||
},
|
||||
"search.filter.locked.server-environment.title": {
|
||||
"message": "Solo se pueden añadir mods que sean del lado del cliente a la instancia del servidor"
|
||||
},
|
||||
"search.filter.locked.server-game-version.title": {
|
||||
"message": "La versión del juego es proporcionada por el servidor"
|
||||
},
|
||||
"search.filter.locked.server-loader.title": {
|
||||
"message": "El loader es proporcionado por el servidor"
|
||||
},
|
||||
"settings.sidebar.label.account": {
|
||||
"message": "Cuenta"
|
||||
},
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user