mirror of
https://github.com/modrinth/code.git
synced 2026-08-25 00:55:25 +00:00
feat: install flow improvements (#6669)
* feat: better error handling + copy details btn for info * refactor: clean up error handling into diagnostics * feat: extra info for failure states + queuing properly * fix: cleanup * fix: lint * fix: fmt * fix: cleanup * fix: cleanup * fix: lint * feat: use bon builder
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { UpdatedIcon } from '@modrinth/assets'
|
||||
import { CheckIcon, CopyIcon, UpdatedIcon } from '@modrinth/assets'
|
||||
import {
|
||||
defineMessages,
|
||||
type PopupNotificationButton,
|
||||
@@ -15,9 +15,11 @@ import {
|
||||
install_job_dismiss,
|
||||
install_job_list,
|
||||
install_job_retry,
|
||||
install_job_support_details,
|
||||
installJobInstanceId,
|
||||
type InstallJobSnapshot,
|
||||
type InstallJobStatus,
|
||||
type InstallPhaseId,
|
||||
type InstallProgress,
|
||||
} from '@/helpers/install'
|
||||
import { get_many as getInstances } from '@/helpers/instance'
|
||||
@@ -31,6 +33,14 @@ const messages = defineMessages({
|
||||
id: 'app.action-bar.install.retry',
|
||||
defaultMessage: 'Retry',
|
||||
},
|
||||
copyDetails: {
|
||||
id: 'app.action-bar.install.copy-details',
|
||||
defaultMessage: 'Copy details',
|
||||
},
|
||||
copied: {
|
||||
id: 'app.action-bar.install.copied-details',
|
||||
defaultMessage: 'Copied',
|
||||
},
|
||||
dismiss: {
|
||||
id: 'app.action-bar.install.dismiss',
|
||||
defaultMessage: 'Dismiss',
|
||||
@@ -39,22 +49,6 @@ const messages = defineMessages({
|
||||
id: 'app.action-bar.install.open-instance',
|
||||
defaultMessage: 'Open instance',
|
||||
},
|
||||
installFailed: {
|
||||
id: 'app.action-bar.install.failed',
|
||||
defaultMessage: 'Install failed',
|
||||
},
|
||||
installFailedAppClosed: {
|
||||
id: 'app.action-bar.install.failed-app-closed',
|
||||
defaultMessage: 'Installation failed due to app closing.',
|
||||
},
|
||||
installFailedNetwork: {
|
||||
id: 'app.action-bar.install.failed-network',
|
||||
defaultMessage: 'Installation failed due to a network error.',
|
||||
},
|
||||
installFailedUnknown: {
|
||||
id: 'app.action-bar.install.failed-unknown',
|
||||
defaultMessage: 'Installation failed due to an unknown error.',
|
||||
},
|
||||
unknownInstance: {
|
||||
id: 'app.action-bar.install.unknown-instance',
|
||||
defaultMessage: 'Unknown instance',
|
||||
@@ -64,7 +58,7 @@ const messages = defineMessages({
|
||||
const phaseMessages = defineMessages({
|
||||
preparing_instance: {
|
||||
id: 'app.install.phase.preparing_instance',
|
||||
defaultMessage: 'Preparing instance',
|
||||
defaultMessage: 'Queued to install',
|
||||
},
|
||||
resolving_pack: {
|
||||
id: 'app.install.phase.resolving_pack',
|
||||
@@ -139,7 +133,97 @@ const javaStepMessages = defineMessages({
|
||||
},
|
||||
})
|
||||
|
||||
const failureSummaryMessages = defineMessages({
|
||||
canceled: {
|
||||
id: 'app.action-bar.install.summary.canceled',
|
||||
defaultMessage: 'Canceled',
|
||||
},
|
||||
appClosed: {
|
||||
id: 'app.action-bar.install.summary.app-closing',
|
||||
defaultMessage: 'Canceled due to app closing',
|
||||
},
|
||||
downloadFailed: {
|
||||
id: 'app.action-bar.install.summary.download-failed',
|
||||
defaultMessage: "Download couldn't finish",
|
||||
},
|
||||
modrinthUnreachable: {
|
||||
id: 'app.action-bar.install.summary.modrinth-unreachable',
|
||||
defaultMessage: "Couldn't reach Modrinth",
|
||||
},
|
||||
packDownloadFailed: {
|
||||
id: 'app.action-bar.install.summary.pack-download-failed',
|
||||
defaultMessage: "Couldn't download pack",
|
||||
},
|
||||
badModpackFile: {
|
||||
id: 'app.action-bar.install.summary.bad-modpack-file',
|
||||
defaultMessage: "Couldn't read modpack",
|
||||
},
|
||||
invalidModpack: {
|
||||
id: 'app.action-bar.install.summary.invalid-modpack',
|
||||
defaultMessage: 'Modpack data invalid',
|
||||
},
|
||||
contentDownloadFailed: {
|
||||
id: 'app.action-bar.install.summary.content-download-failed',
|
||||
defaultMessage: "Couldn't download files",
|
||||
},
|
||||
corruptDownload: {
|
||||
id: 'app.action-bar.install.summary.corrupt-download',
|
||||
defaultMessage: 'Downloaded file is corrupt',
|
||||
},
|
||||
invalidModpackFiles: {
|
||||
id: 'app.action-bar.install.summary.invalid-modpack-files',
|
||||
defaultMessage: 'Modpack files have invalid metadata',
|
||||
},
|
||||
noWritePermission: {
|
||||
id: 'app.action-bar.install.summary.no-write-permission',
|
||||
defaultMessage: 'No permission to write',
|
||||
},
|
||||
couldNotSaveFiles: {
|
||||
id: 'app.action-bar.install.summary.could-not-save-files',
|
||||
defaultMessage: "Couldn't save files",
|
||||
},
|
||||
invalidFilePath: {
|
||||
id: 'app.action-bar.install.summary.invalid-file-path',
|
||||
defaultMessage: 'File path is invalid',
|
||||
},
|
||||
instanceNotFound: {
|
||||
id: 'app.action-bar.install.summary.instance-not-found',
|
||||
defaultMessage: "Instance couldn't be found",
|
||||
},
|
||||
cleanupIncomplete: {
|
||||
id: 'app.action-bar.install.summary.cleanup-incomplete',
|
||||
defaultMessage: "Cleanup didn't finish",
|
||||
},
|
||||
javaSetupFailed: {
|
||||
id: 'app.action-bar.install.summary.java-setup-failed',
|
||||
defaultMessage: "Java setup couldn't finish",
|
||||
},
|
||||
minecraftSetupFailed: {
|
||||
id: 'app.action-bar.install.summary.minecraft-setup-failed',
|
||||
defaultMessage: 'Minecraft setup failed',
|
||||
},
|
||||
loaderSetupFailed: {
|
||||
id: 'app.action-bar.install.summary.loader-setup-failed',
|
||||
defaultMessage: 'Loader setup failed',
|
||||
},
|
||||
localDataError: {
|
||||
id: 'app.action-bar.install.summary.local-data-error',
|
||||
defaultMessage: "Couldn't update local data",
|
||||
},
|
||||
unexpectedError: {
|
||||
id: 'app.action-bar.install.summary.unexpected-error',
|
||||
defaultMessage: 'Something went wrong',
|
||||
},
|
||||
})
|
||||
|
||||
const visibleJobStatuses = new Set<InstallJobStatus>(['queued', 'running', 'failed', 'interrupted'])
|
||||
const copyDetailsStallMs = 30_000
|
||||
|
||||
interface ProgressSnapshot {
|
||||
signature: string
|
||||
changedAt: number
|
||||
timeout: number | null
|
||||
}
|
||||
|
||||
function getDisplayIconUrl(icon: string | null | undefined): string | null {
|
||||
if (!icon) return null
|
||||
@@ -156,10 +240,13 @@ export async function useInstallJobNotifications(opts: {
|
||||
const jobs = ref<InstallJobSnapshot[]>([])
|
||||
const iconUrls = ref<Record<string, string | null>>({})
|
||||
const instanceNames = ref<Record<string, string>>({})
|
||||
const copiedJobIds = ref<Set<string>>(new Set())
|
||||
const jobOrder = new Map<string, number>()
|
||||
let refreshRequest = 0
|
||||
let metadataRequest = 0
|
||||
let nextJobOrder = 0
|
||||
const copiedResetTimeouts = new Map<string, number>()
|
||||
const progressSnapshots = new Map<string, ProgressSnapshot>()
|
||||
|
||||
function getTitle(job: InstallJobSnapshot): string {
|
||||
if (job.display?.title) return job.display.title
|
||||
@@ -174,13 +261,7 @@ export async function useInstallJobNotifications(opts: {
|
||||
|
||||
function getText(job: InstallJobSnapshot): string {
|
||||
if (job.status === 'failed' || job.status === 'interrupted') {
|
||||
if (job.error?.code === 'interrupted') {
|
||||
return formatMessage(messages.installFailedAppClosed)
|
||||
}
|
||||
if (job.error?.code === 'network_error') {
|
||||
return formatMessage(messages.installFailedNetwork)
|
||||
}
|
||||
return formatMessage(messages.installFailedUnknown)
|
||||
return getFailureSummary(job)
|
||||
}
|
||||
if (job.phase === 'preparing_java' && job.details.type === 'java') {
|
||||
return formatMessage(javaStepMessages[job.details.step], {
|
||||
@@ -190,6 +271,104 @@ export async function useInstallJobNotifications(opts: {
|
||||
return formatMessage(phaseMessages[job.phase])
|
||||
}
|
||||
|
||||
function getFailureSummary(job: InstallJobSnapshot): string {
|
||||
const code = job.error?.code
|
||||
const phase = job.error?.phase ?? job.phase
|
||||
|
||||
if (code === 'app_closed' || (job.status === 'interrupted' && code === 'interrupted')) {
|
||||
return formatMessage(failureSummaryMessages.appClosed)
|
||||
}
|
||||
if (code === 'canceled') {
|
||||
return formatMessage(failureSummaryMessages.canceled)
|
||||
}
|
||||
if (job.rollback_error || code === 'rollback_error') {
|
||||
return formatMessage(failureSummaryMessages.cleanupIncomplete)
|
||||
}
|
||||
if (hasPermissionError(job)) {
|
||||
return formatMessage(failureSummaryMessages.noWritePermission)
|
||||
}
|
||||
|
||||
switch (code) {
|
||||
case 'network_error':
|
||||
return formatMessage(
|
||||
phase === 'downloading_pack_file'
|
||||
? failureSummaryMessages.packDownloadFailed
|
||||
: failureSummaryMessages.downloadFailed,
|
||||
)
|
||||
case 'api_error':
|
||||
return formatMessage(failureSummaryMessages.modrinthUnreachable)
|
||||
case 'pack_error':
|
||||
return formatMessage(
|
||||
phase === 'downloading_pack_file'
|
||||
? failureSummaryMessages.packDownloadFailed
|
||||
: failureSummaryMessages.invalidModpack,
|
||||
)
|
||||
case 'archive_error':
|
||||
return formatMessage(failureSummaryMessages.badModpackFile)
|
||||
case 'parse_error':
|
||||
return formatMessage(failureSummaryMessages.invalidModpack)
|
||||
case 'content_error':
|
||||
return formatMessage(failureSummaryMessages.invalidModpackFiles)
|
||||
case 'hash_error':
|
||||
return formatMessage(failureSummaryMessages.corruptDownload)
|
||||
case 'filesystem_error':
|
||||
return formatMessage(failureSummaryMessages.couldNotSaveFiles)
|
||||
case 'path_error':
|
||||
return formatMessage(failureSummaryMessages.invalidFilePath)
|
||||
case 'instance_error':
|
||||
return formatMessage(failureSummaryMessages.instanceNotFound)
|
||||
case 'java_error':
|
||||
return formatMessage(failureSummaryMessages.javaSetupFailed)
|
||||
case 'loader_error':
|
||||
case 'processor_error':
|
||||
return formatMessage(failureSummaryMessages.loaderSetupFailed)
|
||||
case 'database_error':
|
||||
return formatMessage(failureSummaryMessages.localDataError)
|
||||
case 'launcher_error':
|
||||
case 'metadata_error':
|
||||
return getFailureSummaryForPhase(phase)
|
||||
default:
|
||||
return getFailureSummaryForPhase(phase)
|
||||
}
|
||||
}
|
||||
|
||||
function getFailureSummaryForPhase(phase: InstallPhaseId): string {
|
||||
switch (phase) {
|
||||
case 'downloading_pack_file':
|
||||
return formatMessage(failureSummaryMessages.packDownloadFailed)
|
||||
case 'resolving_pack':
|
||||
case 'reading_pack_manifest':
|
||||
return formatMessage(failureSummaryMessages.invalidModpack)
|
||||
case 'downloading_content':
|
||||
return formatMessage(failureSummaryMessages.contentDownloadFailed)
|
||||
case 'extracting_overrides':
|
||||
return formatMessage(failureSummaryMessages.couldNotSaveFiles)
|
||||
case 'resolving_minecraft':
|
||||
case 'downloading_minecraft':
|
||||
return formatMessage(failureSummaryMessages.minecraftSetupFailed)
|
||||
case 'resolving_loader':
|
||||
case 'running_loader_processors':
|
||||
return formatMessage(failureSummaryMessages.loaderSetupFailed)
|
||||
case 'preparing_java':
|
||||
return formatMessage(failureSummaryMessages.javaSetupFailed)
|
||||
case 'preparing_instance':
|
||||
return formatMessage(failureSummaryMessages.instanceNotFound)
|
||||
case 'rolling_back':
|
||||
return formatMessage(failureSummaryMessages.cleanupIncomplete)
|
||||
default:
|
||||
return formatMessage(failureSummaryMessages.unexpectedError)
|
||||
}
|
||||
}
|
||||
|
||||
function hasPermissionError(job: InstallJobSnapshot): boolean {
|
||||
const message = job.error?.message.toLowerCase() ?? ''
|
||||
return (
|
||||
message.includes('permission denied') ||
|
||||
message.includes('access is denied') ||
|
||||
message.includes('operation not permitted')
|
||||
)
|
||||
}
|
||||
|
||||
function getProgressType(job: InstallJobSnapshot): PopupNotificationProgressType | undefined {
|
||||
if (!getEffectiveProgress(job)) return undefined
|
||||
if (
|
||||
@@ -235,11 +414,165 @@ export async function useInstallJobNotifications(opts: {
|
||||
return job.status === 'failed' || job.status === 'interrupted'
|
||||
}
|
||||
|
||||
function getTerminalButtons(job: InstallJobSnapshot): PopupNotificationButton[] | undefined {
|
||||
if (!isTerminalJob(job)) return undefined
|
||||
function canShowStalledProgressDetails(job: InstallJobSnapshot): boolean {
|
||||
return (
|
||||
job.status === 'running' &&
|
||||
job.phase !== 'preparing_instance' &&
|
||||
job.phase !== 'finalizing' &&
|
||||
job.phase !== 'rolling_back'
|
||||
)
|
||||
}
|
||||
|
||||
function getJobSortRank(job: InstallJobSnapshot): number {
|
||||
if (isTerminalJob(job)) return 0
|
||||
if (job.status === 'queued' || job.phase === 'preparing_instance') return 2
|
||||
return 1
|
||||
}
|
||||
|
||||
function progressSignature(job: InstallJobSnapshot): string {
|
||||
const progress = job.progress
|
||||
const secondary = progress?.secondary
|
||||
return [
|
||||
{
|
||||
job.status,
|
||||
job.phase,
|
||||
JSON.stringify(job.details),
|
||||
progress?.current ?? '',
|
||||
progress?.total ?? '',
|
||||
secondary?.current ?? '',
|
||||
secondary?.total ?? '',
|
||||
].join(':')
|
||||
}
|
||||
|
||||
function clearCopied(jobId: string) {
|
||||
if (!copiedJobIds.value.has(jobId)) {
|
||||
return
|
||||
}
|
||||
|
||||
const timeout = copiedResetTimeouts.get(jobId)
|
||||
if (timeout != null) {
|
||||
window.clearTimeout(timeout)
|
||||
copiedResetTimeouts.delete(jobId)
|
||||
}
|
||||
|
||||
const nextCopiedJobIds = new Set(copiedJobIds.value)
|
||||
nextCopiedJobIds.delete(jobId)
|
||||
copiedJobIds.value = nextCopiedJobIds
|
||||
}
|
||||
|
||||
function clearProgressSnapshot(jobId: string) {
|
||||
const snapshot = progressSnapshots.get(jobId)
|
||||
if (snapshot?.timeout != null) {
|
||||
window.clearTimeout(snapshot.timeout)
|
||||
}
|
||||
progressSnapshots.delete(jobId)
|
||||
}
|
||||
|
||||
function scheduleStaleProgressRefresh(jobId: string) {
|
||||
const snapshot = progressSnapshots.get(jobId)
|
||||
if (!snapshot) {
|
||||
return
|
||||
}
|
||||
|
||||
snapshot.timeout = window.setTimeout(() => {
|
||||
const snapshot = progressSnapshots.get(jobId)
|
||||
if (!snapshot) {
|
||||
return
|
||||
}
|
||||
|
||||
snapshot.timeout = null
|
||||
opts.onChange()
|
||||
}, copyDetailsStallMs)
|
||||
}
|
||||
|
||||
function syncProgressSnapshots(nextJobs: InstallJobSnapshot[]) {
|
||||
const trackedJobIds = new Set<string>()
|
||||
const now = Date.now()
|
||||
|
||||
for (const job of nextJobs) {
|
||||
if (!canShowStalledProgressDetails(job)) {
|
||||
continue
|
||||
}
|
||||
|
||||
trackedJobIds.add(job.job_id)
|
||||
const signature = progressSignature(job)
|
||||
const snapshot = progressSnapshots.get(job.job_id)
|
||||
if (snapshot?.signature === signature) {
|
||||
continue
|
||||
}
|
||||
|
||||
clearProgressSnapshot(job.job_id)
|
||||
clearCopied(job.job_id)
|
||||
progressSnapshots.set(job.job_id, {
|
||||
signature,
|
||||
changedAt: now,
|
||||
timeout: null,
|
||||
})
|
||||
scheduleStaleProgressRefresh(job.job_id)
|
||||
}
|
||||
|
||||
for (const jobId of progressSnapshots.keys()) {
|
||||
if (!trackedJobIds.has(jobId)) {
|
||||
clearProgressSnapshot(jobId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function hasStalledProgress(job: InstallJobSnapshot): boolean {
|
||||
const snapshot = progressSnapshots.get(job.job_id)
|
||||
return !!snapshot && Date.now() - snapshot.changedAt >= copyDetailsStallMs
|
||||
}
|
||||
|
||||
function shouldShowCopyDetails(job: InstallJobSnapshot): boolean {
|
||||
return isTerminalJob(job) || (canShowStalledProgressDetails(job) && hasStalledProgress(job))
|
||||
}
|
||||
|
||||
function isCopied(job: InstallJobSnapshot): boolean {
|
||||
return copiedJobIds.value.has(job.job_id)
|
||||
}
|
||||
|
||||
function setCopied(job: InstallJobSnapshot) {
|
||||
copiedJobIds.value = new Set([...copiedJobIds.value, job.job_id])
|
||||
const existingTimeout = copiedResetTimeouts.get(job.job_id)
|
||||
if (existingTimeout != null) {
|
||||
window.clearTimeout(existingTimeout)
|
||||
}
|
||||
copiedResetTimeouts.set(
|
||||
job.job_id,
|
||||
window.setTimeout(() => {
|
||||
copiedResetTimeouts.delete(job.job_id)
|
||||
if (!copiedJobIds.value.has(job.job_id)) {
|
||||
return
|
||||
}
|
||||
const nextCopiedJobIds = new Set(copiedJobIds.value)
|
||||
nextCopiedJobIds.delete(job.job_id)
|
||||
copiedJobIds.value = nextCopiedJobIds
|
||||
opts.onChange()
|
||||
}, 1_000),
|
||||
)
|
||||
opts.onChange()
|
||||
}
|
||||
|
||||
async function copyJobDetails(job: InstallJobSnapshot) {
|
||||
const details = await install_job_support_details(job.job_id).catch((error) => {
|
||||
opts.handleError(error)
|
||||
return null
|
||||
})
|
||||
if (!details) {
|
||||
return
|
||||
}
|
||||
try {
|
||||
await navigator.clipboard.writeText(details)
|
||||
setCopied(job)
|
||||
} catch (error) {
|
||||
opts.handleError(error)
|
||||
}
|
||||
}
|
||||
|
||||
function getButtons(job: InstallJobSnapshot): PopupNotificationButton[] {
|
||||
const buttons: PopupNotificationButton[] = []
|
||||
|
||||
if (isTerminalJob(job)) {
|
||||
buttons.push({
|
||||
label: formatMessage(messages.retry),
|
||||
icon: UpdatedIcon,
|
||||
color: 'brand',
|
||||
@@ -248,8 +581,23 @@ export async function useInstallJobNotifications(opts: {
|
||||
await install_job_retry(job.job_id).catch(opts.handleError)
|
||||
await refresh()
|
||||
},
|
||||
},
|
||||
]
|
||||
})
|
||||
}
|
||||
|
||||
if (shouldShowCopyDetails(job)) {
|
||||
const copied = isCopied(job)
|
||||
buttons.push({
|
||||
label: formatMessage(copied ? messages.copied : messages.copyDetails),
|
||||
icon: copied ? CheckIcon : CopyIcon,
|
||||
color: 'standard',
|
||||
keepOpen: true,
|
||||
action: async () => {
|
||||
await copyJobDetails(job)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
return buttons
|
||||
}
|
||||
|
||||
function setJobs(nextJobs: InstallJobSnapshot[]) {
|
||||
@@ -259,13 +607,15 @@ export async function useInstallJobNotifications(opts: {
|
||||
}
|
||||
}
|
||||
|
||||
jobs.value = nextJobs
|
||||
.filter((job) => visibleJobStatuses.has(job.status))
|
||||
.sort(
|
||||
(a, b) =>
|
||||
a.created.localeCompare(b.created) ||
|
||||
(jobOrder.get(a.job_id) ?? 0) - (jobOrder.get(b.job_id) ?? 0),
|
||||
)
|
||||
const visibleJobs = nextJobs.filter((job) => visibleJobStatuses.has(job.status))
|
||||
syncProgressSnapshots(visibleJobs)
|
||||
|
||||
jobs.value = visibleJobs.sort(
|
||||
(a, b) =>
|
||||
getJobSortRank(a) - getJobSortRank(b) ||
|
||||
a.created.localeCompare(b.created) ||
|
||||
(jobOrder.get(a.job_id) ?? 0) - (jobOrder.get(b.job_id) ?? 0),
|
||||
)
|
||||
}
|
||||
|
||||
const progressItems = computed<PopupNotificationProgressItem[]>(() =>
|
||||
@@ -284,7 +634,7 @@ export async function useInstallJobNotifications(opts: {
|
||||
progressType: isTerminalJob(job) ? undefined : getProgressType(job),
|
||||
progressCurrent: isTerminalJob(job) ? undefined : progress?.current,
|
||||
progressTotal: isTerminalJob(job) ? undefined : progress?.total,
|
||||
buttons: getTerminalButtons(job),
|
||||
buttons: getButtons(job),
|
||||
onDismiss: isTerminalJob(job)
|
||||
? async () => {
|
||||
await install_job_dismiss(job.job_id).catch(opts.handleError)
|
||||
@@ -382,6 +732,14 @@ export async function useInstallJobNotifications(opts: {
|
||||
progressItems,
|
||||
buttons,
|
||||
refresh,
|
||||
dispose: () => unlisten(),
|
||||
dispose: () => {
|
||||
for (const timeout of copiedResetTimeouts.values()) {
|
||||
window.clearTimeout(timeout)
|
||||
}
|
||||
for (const jobId of progressSnapshots.keys()) {
|
||||
clearProgressSnapshot(jobId)
|
||||
}
|
||||
unlisten()
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,6 +85,36 @@ export type InstallJavaStep =
|
||||
| 'extracting'
|
||||
| 'validating'
|
||||
|
||||
export interface InstallErrorView {
|
||||
code: string
|
||||
phase?: InstallPhaseId | null
|
||||
message: string
|
||||
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
|
||||
@@ -114,7 +144,8 @@ export interface InstallJobSnapshot {
|
||||
}
|
||||
| { type: 'import'; launcher_type: string; instance_folder: string }
|
||||
display?: { title: string; icon?: string | null } | null
|
||||
error?: { code: string; message: string } | null
|
||||
error?: InstallErrorView | null
|
||||
rollback_error?: InstallErrorView | null
|
||||
created: string
|
||||
modified: string
|
||||
finished?: string | null
|
||||
@@ -197,6 +228,10 @@ export async function install_job_dismiss(jobId: string) {
|
||||
return await invoke<void>('plugin:install|install_job_dismiss', { jobId })
|
||||
}
|
||||
|
||||
export async function install_job_support_details(jobId: string) {
|
||||
return await invoke<string>('plugin:install|install_job_support_details', { jobId })
|
||||
}
|
||||
|
||||
export function installJobInstanceId(job: InstallJobSnapshot): string | null {
|
||||
return job.instance_id ?? job.target.instance_id ?? null
|
||||
}
|
||||
|
||||
@@ -14,18 +14,6 @@
|
||||
"app.action-bar.install.dismiss": {
|
||||
"message": "Zavřít"
|
||||
},
|
||||
"app.action-bar.install.failed": {
|
||||
"message": "Instalace selhala"
|
||||
},
|
||||
"app.action-bar.install.failed-app-closed": {
|
||||
"message": "Instalace selhala kvůli zavření aplikace."
|
||||
},
|
||||
"app.action-bar.install.failed-network": {
|
||||
"message": "Instalace selhala kvůli chybě sítě."
|
||||
},
|
||||
"app.action-bar.install.failed-unknown": {
|
||||
"message": "Instalace selhala kvůli neznámé chybě."
|
||||
},
|
||||
"app.action-bar.install.open-instance": {
|
||||
"message": "Otevřít instanci"
|
||||
},
|
||||
|
||||
@@ -14,18 +14,6 @@
|
||||
"app.action-bar.install.dismiss": {
|
||||
"message": "Ignorieren"
|
||||
},
|
||||
"app.action-bar.install.failed": {
|
||||
"message": "Installation fehlgeschlagen"
|
||||
},
|
||||
"app.action-bar.install.failed-app-closed": {
|
||||
"message": "Installation aufgrund des Schließens der App fehlgeschlagen."
|
||||
},
|
||||
"app.action-bar.install.failed-network": {
|
||||
"message": "Installation aufgrund eines Netzwerkfehlers fehlgeschlagen."
|
||||
},
|
||||
"app.action-bar.install.failed-unknown": {
|
||||
"message": "Installation aufgrund eines unbekannten Fehlers fehlgeschlagen."
|
||||
},
|
||||
"app.action-bar.install.open-instance": {
|
||||
"message": "Instanz öffnen"
|
||||
},
|
||||
|
||||
@@ -14,18 +14,6 @@
|
||||
"app.action-bar.install.dismiss": {
|
||||
"message": "Ausblenden"
|
||||
},
|
||||
"app.action-bar.install.failed": {
|
||||
"message": "Installation fehlgeschlagen"
|
||||
},
|
||||
"app.action-bar.install.failed-app-closed": {
|
||||
"message": "Installation aufgrund des Schließens der App fehlgeschlagen."
|
||||
},
|
||||
"app.action-bar.install.failed-network": {
|
||||
"message": "Installation aufgrund eines Netzwerkfehlers fehlgeschlagen."
|
||||
},
|
||||
"app.action-bar.install.failed-unknown": {
|
||||
"message": "Installation aufgrund eines unbekannten Fehlers fehlgeschlagen."
|
||||
},
|
||||
"app.action-bar.install.open-instance": {
|
||||
"message": "Instanz öffnen"
|
||||
},
|
||||
|
||||
@@ -11,27 +11,81 @@
|
||||
"app.action-bar.hide-more-running-instances": {
|
||||
"message": "Hide more running instances"
|
||||
},
|
||||
"app.action-bar.install.copied-details": {
|
||||
"message": "Copied"
|
||||
},
|
||||
"app.action-bar.install.copy-details": {
|
||||
"message": "Copy details"
|
||||
},
|
||||
"app.action-bar.install.dismiss": {
|
||||
"message": "Dismiss"
|
||||
},
|
||||
"app.action-bar.install.failed": {
|
||||
"message": "Install failed"
|
||||
},
|
||||
"app.action-bar.install.failed-app-closed": {
|
||||
"message": "Installation failed due to app closing."
|
||||
},
|
||||
"app.action-bar.install.failed-network": {
|
||||
"message": "Installation failed due to a network error."
|
||||
},
|
||||
"app.action-bar.install.failed-unknown": {
|
||||
"message": "Installation failed due to an unknown error."
|
||||
},
|
||||
"app.action-bar.install.open-instance": {
|
||||
"message": "Open instance"
|
||||
},
|
||||
"app.action-bar.install.retry": {
|
||||
"message": "Retry"
|
||||
},
|
||||
"app.action-bar.install.summary.app-closing": {
|
||||
"message": "Canceled due to app closing"
|
||||
},
|
||||
"app.action-bar.install.summary.bad-modpack-file": {
|
||||
"message": "Couldn't read modpack"
|
||||
},
|
||||
"app.action-bar.install.summary.canceled": {
|
||||
"message": "Canceled"
|
||||
},
|
||||
"app.action-bar.install.summary.cleanup-incomplete": {
|
||||
"message": "Cleanup didn't finish"
|
||||
},
|
||||
"app.action-bar.install.summary.content-download-failed": {
|
||||
"message": "Couldn't download files"
|
||||
},
|
||||
"app.action-bar.install.summary.corrupt-download": {
|
||||
"message": "Downloaded file is corrupt"
|
||||
},
|
||||
"app.action-bar.install.summary.could-not-save-files": {
|
||||
"message": "Couldn't save files"
|
||||
},
|
||||
"app.action-bar.install.summary.download-failed": {
|
||||
"message": "Download couldn't finish"
|
||||
},
|
||||
"app.action-bar.install.summary.instance-not-found": {
|
||||
"message": "Instance couldn't be found"
|
||||
},
|
||||
"app.action-bar.install.summary.invalid-file-path": {
|
||||
"message": "File path is invalid"
|
||||
},
|
||||
"app.action-bar.install.summary.invalid-modpack": {
|
||||
"message": "Modpack data invalid"
|
||||
},
|
||||
"app.action-bar.install.summary.invalid-modpack-files": {
|
||||
"message": "Modpack files have invalid metadata"
|
||||
},
|
||||
"app.action-bar.install.summary.java-setup-failed": {
|
||||
"message": "Java setup couldn't finish"
|
||||
},
|
||||
"app.action-bar.install.summary.loader-setup-failed": {
|
||||
"message": "Loader setup failed"
|
||||
},
|
||||
"app.action-bar.install.summary.local-data-error": {
|
||||
"message": "Couldn't update local data"
|
||||
},
|
||||
"app.action-bar.install.summary.minecraft-setup-failed": {
|
||||
"message": "Minecraft setup failed"
|
||||
},
|
||||
"app.action-bar.install.summary.modrinth-unreachable": {
|
||||
"message": "Couldn't reach Modrinth"
|
||||
},
|
||||
"app.action-bar.install.summary.no-write-permission": {
|
||||
"message": "No permission to write"
|
||||
},
|
||||
"app.action-bar.install.summary.pack-download-failed": {
|
||||
"message": "Couldn't download pack"
|
||||
},
|
||||
"app.action-bar.install.summary.unexpected-error": {
|
||||
"message": "Something went wrong"
|
||||
},
|
||||
"app.action-bar.install.unknown-instance": {
|
||||
"message": "Unknown instance"
|
||||
},
|
||||
@@ -231,7 +285,7 @@
|
||||
"message": "Finalizing"
|
||||
},
|
||||
"app.install.phase.preparing_instance": {
|
||||
"message": "Preparing instance"
|
||||
"message": "Queued to install"
|
||||
},
|
||||
"app.install.phase.preparing_java": {
|
||||
"message": "Preparing Java"
|
||||
|
||||
@@ -14,18 +14,6 @@
|
||||
"app.action-bar.install.dismiss": {
|
||||
"message": "Descartar"
|
||||
},
|
||||
"app.action-bar.install.failed": {
|
||||
"message": "La instalación falló"
|
||||
},
|
||||
"app.action-bar.install.failed-app-closed": {
|
||||
"message": "La instalación falló porque la aplicación se cerró."
|
||||
},
|
||||
"app.action-bar.install.failed-network": {
|
||||
"message": "La instalación falló debido a un error de red."
|
||||
},
|
||||
"app.action-bar.install.failed-unknown": {
|
||||
"message": "La instalación falló debido a un error desconocido."
|
||||
},
|
||||
"app.action-bar.install.open-instance": {
|
||||
"message": "Abrir instancia"
|
||||
},
|
||||
|
||||
@@ -14,15 +14,6 @@
|
||||
"app.action-bar.install.dismiss": {
|
||||
"message": "Descartar"
|
||||
},
|
||||
"app.action-bar.install.failed": {
|
||||
"message": "La instalación falló"
|
||||
},
|
||||
"app.action-bar.install.failed-app-closed": {
|
||||
"message": "La instalación falló porque la aplicación se cerró."
|
||||
},
|
||||
"app.action-bar.install.failed-network": {
|
||||
"message": "La instalación falló debido a un error de red."
|
||||
},
|
||||
"app.action-bar.make-primary-instance": {
|
||||
"message": "Establecer como instancia principal"
|
||||
},
|
||||
|
||||
@@ -14,18 +14,6 @@
|
||||
"app.action-bar.install.dismiss": {
|
||||
"message": "Hylkää"
|
||||
},
|
||||
"app.action-bar.install.failed": {
|
||||
"message": "Asennus epäonnistui"
|
||||
},
|
||||
"app.action-bar.install.failed-app-closed": {
|
||||
"message": "Asennus epäonnistui sovelluksen sulkeutumisen vuoksi."
|
||||
},
|
||||
"app.action-bar.install.failed-network": {
|
||||
"message": "Asennus epäonnistui verkkovirheen vuoksi."
|
||||
},
|
||||
"app.action-bar.install.failed-unknown": {
|
||||
"message": "Asennus epäonnistui tuntemattoman virheen vuoksi."
|
||||
},
|
||||
"app.action-bar.install.open-instance": {
|
||||
"message": "Avaa instanssi"
|
||||
},
|
||||
|
||||
@@ -14,18 +14,6 @@
|
||||
"app.action-bar.install.dismiss": {
|
||||
"message": "Rejeter"
|
||||
},
|
||||
"app.action-bar.install.failed": {
|
||||
"message": "Échec de l'installation"
|
||||
},
|
||||
"app.action-bar.install.failed-app-closed": {
|
||||
"message": "Échec de l'installation dû à la fermeture de l'app."
|
||||
},
|
||||
"app.action-bar.install.failed-network": {
|
||||
"message": "Échec de l'installation dû à une erreur réseau."
|
||||
},
|
||||
"app.action-bar.install.failed-unknown": {
|
||||
"message": "Échec de l'installation dû à une erreur inconnue."
|
||||
},
|
||||
"app.action-bar.install.open-instance": {
|
||||
"message": "Ouvrir l'instance"
|
||||
},
|
||||
|
||||
@@ -14,18 +14,6 @@
|
||||
"app.action-bar.install.dismiss": {
|
||||
"message": "Ignora"
|
||||
},
|
||||
"app.action-bar.install.failed": {
|
||||
"message": "Installazione fallita"
|
||||
},
|
||||
"app.action-bar.install.failed-app-closed": {
|
||||
"message": "Installazione fallita: l'app è stata chiusa."
|
||||
},
|
||||
"app.action-bar.install.failed-network": {
|
||||
"message": "Installazione fallita: errore di rete."
|
||||
},
|
||||
"app.action-bar.install.failed-unknown": {
|
||||
"message": "Installazione fallita: errore sconosciuto."
|
||||
},
|
||||
"app.action-bar.install.open-instance": {
|
||||
"message": "Apri istanza"
|
||||
},
|
||||
|
||||
@@ -14,18 +14,6 @@
|
||||
"app.action-bar.install.dismiss": {
|
||||
"message": "Odrzuć"
|
||||
},
|
||||
"app.action-bar.install.failed": {
|
||||
"message": "Instalacja nie powiodła się"
|
||||
},
|
||||
"app.action-bar.install.failed-app-closed": {
|
||||
"message": "Instalacja nie powiodła się z powodu zamknięcia aplikacji."
|
||||
},
|
||||
"app.action-bar.install.failed-network": {
|
||||
"message": "Instalacja nie powiodła się z powodu problemów z połączeniem."
|
||||
},
|
||||
"app.action-bar.install.failed-unknown": {
|
||||
"message": "Instalacja nie powiodła się z nieznanych przyczyn."
|
||||
},
|
||||
"app.action-bar.install.open-instance": {
|
||||
"message": "Otwórz instancję"
|
||||
},
|
||||
|
||||
@@ -14,18 +14,6 @@
|
||||
"app.action-bar.install.dismiss": {
|
||||
"message": "Dispensar"
|
||||
},
|
||||
"app.action-bar.install.failed": {
|
||||
"message": "Instalação falha"
|
||||
},
|
||||
"app.action-bar.install.failed-app-closed": {
|
||||
"message": "A instalação falhou devido ao aplicativo ter fechado."
|
||||
},
|
||||
"app.action-bar.install.failed-network": {
|
||||
"message": "A instalação falhou devido a um erro na rede."
|
||||
},
|
||||
"app.action-bar.install.failed-unknown": {
|
||||
"message": "A instalação falhou devido a um erro desconhecido."
|
||||
},
|
||||
"app.action-bar.install.open-instance": {
|
||||
"message": "Abrir instância"
|
||||
},
|
||||
|
||||
@@ -14,18 +14,6 @@
|
||||
"app.action-bar.install.dismiss": {
|
||||
"message": "Закрыть"
|
||||
},
|
||||
"app.action-bar.install.failed": {
|
||||
"message": "Установка не удалась"
|
||||
},
|
||||
"app.action-bar.install.failed-app-closed": {
|
||||
"message": "Установка не удалась из-за закрытия приложения."
|
||||
},
|
||||
"app.action-bar.install.failed-network": {
|
||||
"message": "Установка не удалась из-за сетевой ошибки."
|
||||
},
|
||||
"app.action-bar.install.failed-unknown": {
|
||||
"message": "Установка не удалась из-за неизвестной ошибки."
|
||||
},
|
||||
"app.action-bar.install.open-instance": {
|
||||
"message": "Открыть сборку"
|
||||
},
|
||||
|
||||
@@ -1,10 +1,4 @@
|
||||
{
|
||||
"app.action-bar.install.failed": {
|
||||
"message": "Neuspešna instalacija"
|
||||
},
|
||||
"app.action-bar.install.failed-app-closed": {
|
||||
"message": "Neuspešna instalacija zbog zatvaranja aplikacije."
|
||||
},
|
||||
"app.auth-servers.unreachable.body": {
|
||||
"message": "Minecraft serveri za autentifikaciju su možda trenutno nedostupni. Molimo vas da proverite vašu internet vezu i pokušajte ponovo kasnije."
|
||||
},
|
||||
|
||||
@@ -14,18 +14,6 @@
|
||||
"app.action-bar.install.dismiss": {
|
||||
"message": "Avvisa"
|
||||
},
|
||||
"app.action-bar.install.failed": {
|
||||
"message": "Installationen misslyckades"
|
||||
},
|
||||
"app.action-bar.install.failed-app-closed": {
|
||||
"message": "Installationen misslyckades för att appen stängdes."
|
||||
},
|
||||
"app.action-bar.install.failed-network": {
|
||||
"message": "Installationen misslyckades av ett nätverksfel."
|
||||
},
|
||||
"app.action-bar.install.failed-unknown": {
|
||||
"message": "Installationen misslyckades med ett okänt fel."
|
||||
},
|
||||
"app.action-bar.install.open-instance": {
|
||||
"message": "Öppna instans"
|
||||
},
|
||||
|
||||
@@ -14,18 +14,6 @@
|
||||
"app.action-bar.install.dismiss": {
|
||||
"message": "Reddet"
|
||||
},
|
||||
"app.action-bar.install.failed": {
|
||||
"message": "Yükleme yapılamadı"
|
||||
},
|
||||
"app.action-bar.install.failed-app-closed": {
|
||||
"message": "Yükleme uygulama kapandığından yapılamadı."
|
||||
},
|
||||
"app.action-bar.install.failed-network": {
|
||||
"message": "Yükleme ağ hatasından dolayı yapılamadı."
|
||||
},
|
||||
"app.action-bar.install.failed-unknown": {
|
||||
"message": "Yükleme bilinmeyen bir hatadan yapılamadı."
|
||||
},
|
||||
"app.action-bar.install.open-instance": {
|
||||
"message": "Kurulumu aç"
|
||||
},
|
||||
|
||||
@@ -14,18 +14,6 @@
|
||||
"app.action-bar.install.dismiss": {
|
||||
"message": "忽略"
|
||||
},
|
||||
"app.action-bar.install.failed": {
|
||||
"message": "安装失败"
|
||||
},
|
||||
"app.action-bar.install.failed-app-closed": {
|
||||
"message": "由于应用关闭,安装失败。"
|
||||
},
|
||||
"app.action-bar.install.failed-network": {
|
||||
"message": "由于网络错误,安装失败。"
|
||||
},
|
||||
"app.action-bar.install.failed-unknown": {
|
||||
"message": "由于未知错误,安装失败。"
|
||||
},
|
||||
"app.action-bar.install.open-instance": {
|
||||
"message": "打开实例"
|
||||
},
|
||||
|
||||
@@ -14,18 +14,6 @@
|
||||
"app.action-bar.install.dismiss": {
|
||||
"message": "忽略"
|
||||
},
|
||||
"app.action-bar.install.failed": {
|
||||
"message": "無法安裝"
|
||||
},
|
||||
"app.action-bar.install.failed-app-closed": {
|
||||
"message": "由於應用程式關閉,無法安裝。"
|
||||
},
|
||||
"app.action-bar.install.failed-network": {
|
||||
"message": "由於網路錯誤,無法安裝。"
|
||||
},
|
||||
"app.action-bar.install.failed-unknown": {
|
||||
"message": "由於未知錯誤,無法安裝。"
|
||||
},
|
||||
"app.action-bar.install.open-instance": {
|
||||
"message": "開啟實例"
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user