feat: app onboarding and library (#6923)
* refactor: remove unused legacy onboarded flag * sqlx prepare * feat: add onboarding checklist app db and add vue provider * feat: add welcome page * feat: add onboarding checklist component * feat: move checklist visibility source of truth in app db * feat: split up import modpack button * feat-small: style tweak * feat: remove discover section from homepage and add library * refactor: move welcome screen to component * refactor: move grid display into /library * refactor: rename existing components * refactor: split up the current library page files * feat: add create group modal * feat: add instance group definitions * feat: update accordian for instance groups * feat: add delete group * feat: implement rename instance groups * refactor: move instance group api out * feat: add context menu for groups * feat: style instance items in library * feat: add selected instance item state * feat: implement action bar for selection * feat: drag and drop to move between groups * feat: show ungrouped with header * feat: improve edit group name * small fix * small style fixes * feat: improve selection behaviour * fix: dont toggle group when context menu open * feat: drag and drop multiple selected * small * feat: add remove from group in action bar * feat: add delay on icon hiding in inline input * feat: improve icon hiding delay * fix: bad routes.js * feat: implement sort and filter options * refactor: dont prefix filenames * feat: change open/collapse group target * feat: improve ungrouped behaviour * feat: add shift to select multiple instances * fix: icon disappear transition * feat: add esc while dragging to cancel * feat: update sorting and moved label * feat: update context menu * fix: multiple context menus can be opened * refactor: instance groups to be identified by ID instead of name, names become display data * feat: creating new group always adds instance * feat: create group button, fix instance selection, and increase group name max length * feat: strokes * fix: show getting started checklist in all sidebar * feat: set size on microsoft login screen * fix: use ButtonStyled in Chips component * feat: creating groups with selected instances and fix some moving selection and duplication bugs * pnpm prepr * feat: add tooltip for instance already in group * feat: create instance with content in creation flow modal - search modpack becomes search projects - creation flow modal context receives prepareProjectInstall and createProjectInstall callbacks, from content-install.ts (shared with ContentInstallModal) * feat: update instance card styles * refactor: separate out instance card content * update styles * refactor: instance card * fix: width of instance * feat: update empty state * feat: update sort and grouping dropdowns * feat: better context menu options * feat: add group action buttons * feat: add/remove favourites group * remove manual sort * feat: improve the "New group" jank * feat: add group by instance type and change group sorting * feat: welcome screen styles match figma * feat: not signed in skins demo * feat: new hosting empty state * feat: new instance card style * feat: add tooltip and spice up animation more * feat: improve the transitions for groups after dropping many instances * feat: set instance group membership for bulk moving * feat: remove fade out * fix: allow dragging from buttons inside instance card * fix: library page load in behaviour * small fix * feat: add group ordering and reordering * feat: add drag to reorder groups * fix: switching page not clearing selected instances * style fixes * style fix * fix spacing * feat: jump in changes * feat: add styled newly created instances * feat: improve jump in cards styles * fix: welcome screen style * fix: empty state styles * feat: max 5 jump in items * feat: max new instances to 3 * fix: search icon * feat: clear selection after move * feat: skins list use check icon styles * feat: list will be deleted instances and default sort last played * fix: stroke * remove: no more group settings in instance general settings * fix: resolving modpack server install * feat: icon editor * feat: add three hue shifted variants * fix: surprise me doesnt pick symbol * feat: add customize icon to create instance modal * feat: skins loading state style * fix: welcome logo * fix: copy * fix: left over usage of ButtonStyled from main's refactor * feat: some leaked merge stuff * feat: server empty state styles * remove: instance helper text * remove: auto link in set up stage * feat: auto focus on search when modal open * fix: delete instance modal style * remove: modpack meta repeating * fix: icon * fix: font weight * fix: dropdown filter preview heights * pnpm prepr * fix: sign into modrinth flashing change in position * feat: add avatar transparent corner gets padding * feat: drag and hold over group opens group * feat: add random icon for custom instance * feat: update loading icon * polish: empty state for search * polish: jump in cards * fix: avatar could skip checking for image if tauri loads it first * feat: add icon customize and randomizer in content install modal for add to new instances * fix: avatar cannot read image pixels due to cors * refactor: popup notifications toast type for instance sharing notifications * feat: apply random icons modal and add popup notification for it * polish: style * fix: set_webview_visible * QA * qa * feat: add inner white 15 opacity stroke around avatar * add to catalog * pnpm prepr * fix: app event import * feat: add new icons and backgrounds for icon editor * feat: recents list generate from randomized icons and save icon in editor + icon editor modal fixes * qa * refactor: recipe -> config * fix: event bus * polish * feat: add safe guard against randomizing empty icons * feat: navigate to instance after creation * fix: icon file names * i18n on welcome screen * i18n pass on PR * fix: inconsistent max group name length * refactor: use sqlx macro * feat: add instance icon background selection drag * feat: update catalog order * fix: remove gradient for welcome screen * remove: frog * fix: min width * feat: update corner size * fix: context menu styles * prepr * fix: instance name trimming * reorder * fix: instance card loading state stayed until hovering * feat: add icon randomizer blacklist * blog * prepr --------- Co-authored-by: Prospector <6166773+Prospector@users.noreply.github.com>
|
After Width: | Height: | Size: 23 KiB |
|
After Width: | Height: | Size: 26 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 27 KiB |
|
After Width: | Height: | Size: 38 KiB |
|
After Width: | Height: | Size: 4.8 KiB |
|
After Width: | Height: | Size: 23 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 28 KiB |
|
After Width: | Height: | Size: 2.8 KiB |
|
After Width: | Height: | Size: 304 B |
|
After Width: | Height: | Size: 937 KiB |
@@ -72,9 +72,6 @@ const props = withDefaults(
|
||||
{
|
||||
type: 'standard',
|
||||
openByDefault: false,
|
||||
buttonClass: null,
|
||||
contentClass: null,
|
||||
titleWrapperClass: null,
|
||||
forceOpen: false,
|
||||
overflowVisible: false,
|
||||
divider: false,
|
||||
|
||||
@@ -6,14 +6,16 @@
|
||||
:style="`--_size: ${cssSize}`"
|
||||
:class="{
|
||||
circle: circle,
|
||||
detecting: !hasDetectedCorners,
|
||||
'no-shadow': noShadow,
|
||||
padded: hasTransparentCorners,
|
||||
raised: raised,
|
||||
pixelated: pixelated,
|
||||
}"
|
||||
:src="src"
|
||||
:alt="alt"
|
||||
:loading="loading"
|
||||
@load="updatePixelated"
|
||||
@load="onLoad"
|
||||
@error="onError"
|
||||
/>
|
||||
<svg
|
||||
@@ -46,11 +48,22 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, ref, useTemplateRef, watch } from 'vue'
|
||||
import { computed, onBeforeUnmount, onMounted, ref, useTemplateRef, watch } from 'vue'
|
||||
|
||||
const pixelated = ref(false)
|
||||
const hasTransparentCorners = ref(false)
|
||||
const hasDetectedCorners = ref(false)
|
||||
const img = useTemplateRef<HTMLImageElement>('img')
|
||||
const failed = ref(false)
|
||||
let detectionTimeout: number | undefined
|
||||
let detectingSource: string | undefined
|
||||
|
||||
const DETECTION_TIMEOUT_MS = 2000
|
||||
const CORNER_SAMPLE_SIZE = 8
|
||||
|
||||
defineExpose({
|
||||
failed,
|
||||
})
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
@@ -88,21 +101,146 @@ const cssSize = computed(() => LEGACY_PRESETS[props.size] ?? props.size)
|
||||
watch(
|
||||
() => props.src,
|
||||
() => {
|
||||
clearDetectionTimeout()
|
||||
detectingSource = undefined
|
||||
failed.value = false
|
||||
hasTransparentCorners.value = false
|
||||
hasDetectedCorners.value = false
|
||||
},
|
||||
)
|
||||
|
||||
onMounted(() => {
|
||||
const image = img.value
|
||||
if (!image?.complete || hasDetectedCorners.value) return
|
||||
|
||||
if (image.naturalWidth > 0) {
|
||||
onLoad()
|
||||
} else {
|
||||
failed.value = true
|
||||
}
|
||||
})
|
||||
|
||||
onBeforeUnmount(clearDetectionTimeout)
|
||||
|
||||
function clearDetectionTimeout() {
|
||||
if (detectionTimeout !== undefined) {
|
||||
window.clearTimeout(detectionTimeout)
|
||||
detectionTimeout = undefined
|
||||
}
|
||||
}
|
||||
|
||||
function onError(e) {
|
||||
clearDetectionTimeout()
|
||||
detectingSource = undefined
|
||||
console.log('Avatar image failed to load:', props.src, e)
|
||||
failed.value = true
|
||||
}
|
||||
|
||||
function updatePixelated() {
|
||||
if (img.value && img.value.naturalWidth && img.value.naturalWidth < 32) {
|
||||
function onLoad() {
|
||||
const image = img.value
|
||||
if (!image) return
|
||||
const source = image.currentSrc
|
||||
if (detectingSource === source) return
|
||||
detectingSource = source
|
||||
clearDetectionTimeout()
|
||||
|
||||
if (image.naturalWidth && image.naturalWidth < 32) {
|
||||
pixelated.value = true
|
||||
} else {
|
||||
pixelated.value = false
|
||||
}
|
||||
|
||||
if (canReadImagePixels(source)) {
|
||||
const transparentCorners = detectTransparentCorners(image)
|
||||
if (transparentCorners !== null) {
|
||||
detectingSource = undefined
|
||||
hasTransparentCorners.value = transparentCorners
|
||||
hasDetectedCorners.value = true
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
const probe = new Image()
|
||||
let detectionFinished = false
|
||||
probe.crossOrigin = 'anonymous'
|
||||
probe.onload = () => {
|
||||
if (detectionFinished || img.value?.currentSrc !== source) return
|
||||
detectionFinished = true
|
||||
detectingSource = undefined
|
||||
clearDetectionTimeout()
|
||||
hasTransparentCorners.value = detectTransparentCorners(probe) ?? false
|
||||
hasDetectedCorners.value = true
|
||||
}
|
||||
probe.onerror = () => {
|
||||
if (detectionFinished || img.value?.currentSrc !== source) return
|
||||
detectionFinished = true
|
||||
detectingSource = undefined
|
||||
clearDetectionTimeout()
|
||||
hasDetectedCorners.value = true
|
||||
}
|
||||
detectionTimeout = window.setTimeout(() => {
|
||||
if (img.value?.currentSrc !== source) return
|
||||
detectionFinished = true
|
||||
detectingSource = undefined
|
||||
detectionTimeout = undefined
|
||||
hasDetectedCorners.value = true
|
||||
}, DETECTION_TIMEOUT_MS)
|
||||
probe.src = source
|
||||
}
|
||||
|
||||
function canReadImagePixels(source: string): boolean {
|
||||
try {
|
||||
const url = new URL(source, window.location.href)
|
||||
return url.protocol === 'data:' || url.origin === window.location.origin
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
function detectTransparentCorners(image: HTMLImageElement): boolean | null {
|
||||
if (image.naturalWidth < CORNER_SAMPLE_SIZE || image.naturalHeight < CORNER_SAMPLE_SIZE) {
|
||||
return false
|
||||
}
|
||||
|
||||
const canvas = document.createElement('canvas')
|
||||
canvas.width = CORNER_SAMPLE_SIZE * 2
|
||||
canvas.height = CORNER_SAMPLE_SIZE * 2
|
||||
const context = canvas.getContext('2d', { willReadFrequently: true })
|
||||
if (!context) return false
|
||||
|
||||
const right = image.naturalWidth - CORNER_SAMPLE_SIZE
|
||||
const bottom = image.naturalHeight - CORNER_SAMPLE_SIZE
|
||||
const drawCorner = (
|
||||
sourceX: number,
|
||||
sourceY: number,
|
||||
destinationX: number,
|
||||
destinationY: number,
|
||||
) =>
|
||||
context.drawImage(
|
||||
image,
|
||||
sourceX,
|
||||
sourceY,
|
||||
CORNER_SAMPLE_SIZE,
|
||||
CORNER_SAMPLE_SIZE,
|
||||
destinationX,
|
||||
destinationY,
|
||||
CORNER_SAMPLE_SIZE,
|
||||
CORNER_SAMPLE_SIZE,
|
||||
)
|
||||
|
||||
try {
|
||||
drawCorner(0, 0, 0, 0)
|
||||
drawCorner(right, 0, CORNER_SAMPLE_SIZE, 0)
|
||||
drawCorner(0, bottom, 0, CORNER_SAMPLE_SIZE)
|
||||
drawCorner(right, bottom, CORNER_SAMPLE_SIZE, CORNER_SAMPLE_SIZE)
|
||||
const pixels = context.getImageData(0, 0, canvas.width, canvas.height).data
|
||||
for (let alpha = 3; alpha < pixels.length; alpha += 4) {
|
||||
if (pixels[alpha] !== 0) return false
|
||||
}
|
||||
return true
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
const tint = computed(() => {
|
||||
@@ -128,7 +266,8 @@ function hash(str: string): number {
|
||||
.avatar {
|
||||
--_size: 2rem;
|
||||
|
||||
border: 1px solid var(--surface-5);
|
||||
outline: 1px solid rgb(255 255 255 / 15%);
|
||||
outline-offset: -1px;
|
||||
background-color: var(--color-button-bg);
|
||||
object-fit: contain;
|
||||
border-radius: calc(16 / 96 * var(--_override-size, var(--_size)));
|
||||
@@ -154,6 +293,14 @@ function hash(str: string): number {
|
||||
image-rendering: pixelated;
|
||||
}
|
||||
|
||||
&.detecting {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
&.padded {
|
||||
padding: calc(6 / 96 * var(--_override-size, var(--_size)));
|
||||
}
|
||||
|
||||
&.raised {
|
||||
background-color: var(--color-raised-bg);
|
||||
}
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
<span
|
||||
v-if="showLabel"
|
||||
class="flex h-10 items-center text-nowrap text-base font-medium text-primary"
|
||||
:aria-label="useFilterIcon ? label : undefined"
|
||||
:aria-label="useFilterIcon ? effectiveLabel : undefined"
|
||||
>
|
||||
<FilterIcon v-if="useFilterIcon" class="size-5 text-primary" aria-hidden="true" />
|
||||
<template v-else>{{ label }}</template>
|
||||
<template v-else>{{ effectiveLabel }}</template>
|
||||
</span>
|
||||
|
||||
<template v-for="preview in appliedFilterPreviews" :key="preview.key">
|
||||
@@ -15,7 +15,7 @@
|
||||
native-type="button"
|
||||
size="lg"
|
||||
:class="effectivePreviewTriggerClass"
|
||||
:aria-label="`Clear ${preview.label} filter`"
|
||||
:aria-label="formatMessage(messages.clearNamedFilter, { name: preview.label })"
|
||||
@click="clearFilterCategory(preview.key)"
|
||||
>
|
||||
<span class="font-semibold">{{ preview.label }}</span>
|
||||
@@ -84,7 +84,7 @@
|
||||
<button
|
||||
type="button"
|
||||
class="flex cursor-pointer items-center justify-center rounded border-none bg-transparent p-0.5 text-secondary transition-colors hover:text-contrast"
|
||||
:aria-label="`Clear ${preview.label} filter`"
|
||||
:aria-label="formatMessage(messages.clearNamedFilter, { name: preview.label })"
|
||||
@click.stop="clearFilterCategory(preview.key)"
|
||||
>
|
||||
<XIcon class="size-4 text-primary" />
|
||||
@@ -142,11 +142,11 @@
|
||||
@keydown="handleAddMenuTriggerKeydown"
|
||||
>
|
||||
<PlusIcon />
|
||||
{{ addLabel }}
|
||||
{{ effectiveAddLabel }}
|
||||
</Button>
|
||||
|
||||
<Button v-if="shouldShowClear" type="quiet" native-type="button" @click="clearAllFilters">{{
|
||||
clearLabel
|
||||
effectiveClearLabel
|
||||
}}</Button>
|
||||
</div>
|
||||
|
||||
@@ -226,7 +226,9 @@
|
||||
v-model="categorySearchQuery"
|
||||
:icon="SearchIcon"
|
||||
type="text"
|
||||
:placeholder="activeCategory.searchPlaceholder ?? 'Search...'"
|
||||
:placeholder="
|
||||
activeCategory.searchPlaceholder ?? formatMessage(messages.searchPlaceholder)
|
||||
"
|
||||
wrapper-class="grow bg-surface-4 mx-1"
|
||||
input-class="ps-9 mx-1.5"
|
||||
/>
|
||||
@@ -327,7 +329,11 @@
|
||||
class="border-0 bg-transparent p-0 text-sm font-semibold text-secondary shadow-none transition-colors hover:text-contrast"
|
||||
@click="toggleSectionHeaderOptions(item)"
|
||||
>
|
||||
{{ areSectionHeaderOptionsSelected(item) ? 'Clear' : 'Select all' }}
|
||||
{{
|
||||
formatMessage(
|
||||
areSectionHeaderOptionsSelected(item) ? messages.clear : messages.selectAll,
|
||||
)
|
||||
}}
|
||||
</button>
|
||||
</div>
|
||||
<button
|
||||
@@ -423,6 +429,7 @@ import { computed, nextTick, onBeforeUnmount, ref, watch } from 'vue'
|
||||
|
||||
import { Button, type ButtonElementHandle, type ButtonSize } from '#ui/components/base/buttons'
|
||||
|
||||
import { defineMessages, useVIntl } from '../../composables/i18n'
|
||||
import { useVirtualScroll } from '../../composables/virtual-scroll'
|
||||
import MultiSelect, { type MultiSelectItem } from './MultiSelect.vue'
|
||||
import StyledInput from './StyledInput.vue'
|
||||
@@ -539,6 +546,21 @@ const TAILWIND_WIDTH_CLASS_SIZE: Record<string, string> = {
|
||||
[DEFAULT_SUBMENU_CLASS]: DEFAULT_SUBMENU_WIDTH,
|
||||
}
|
||||
|
||||
const { formatMessage } = useVIntl()
|
||||
const messages = defineMessages({
|
||||
filteredBy: { id: 'filter-bar.filtered-by', defaultMessage: 'Filtered by' },
|
||||
add: { id: 'filter-bar.add', defaultMessage: 'Add' },
|
||||
clear: { id: 'filter-bar.clear', defaultMessage: 'Clear' },
|
||||
selectAll: { id: 'filter-bar.select-all', defaultMessage: 'Select all' },
|
||||
noOptions: { id: 'filter-bar.no-options', defaultMessage: 'No options available.' },
|
||||
noSearchResults: { id: 'filter-bar.no-search-results', defaultMessage: 'No options found.' },
|
||||
searchPlaceholder: { id: 'filter-bar.search-placeholder', defaultMessage: 'Search...' },
|
||||
clearNamedFilter: {
|
||||
id: 'filter-bar.clear-named-filter',
|
||||
defaultMessage: 'Clear {name} filter',
|
||||
},
|
||||
})
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
modelValue: DropdownFilterBarValue
|
||||
@@ -559,21 +581,26 @@ const props = withDefaults(
|
||||
checkboxPosition?: 'left' | 'right'
|
||||
}>(),
|
||||
{
|
||||
label: 'Filtered by',
|
||||
addLabel: 'Add',
|
||||
clearLabel: 'Clear',
|
||||
showClear: false,
|
||||
showLabel: true,
|
||||
useFilterIcon: false,
|
||||
addButtonSize: 'md',
|
||||
applyImmediately: false,
|
||||
showPreviewFilterIcon: false,
|
||||
emptyOptionsLabel: 'No options available.',
|
||||
emptySearchLabel: 'No options found.',
|
||||
checkboxPosition: 'left',
|
||||
},
|
||||
)
|
||||
|
||||
const effectiveLabel = computed(() => props.label ?? formatMessage(messages.filteredBy))
|
||||
const effectiveAddLabel = computed(() => props.addLabel ?? formatMessage(messages.add))
|
||||
const effectiveClearLabel = computed(() => props.clearLabel ?? formatMessage(messages.clear))
|
||||
const effectiveEmptyOptionsLabel = computed(
|
||||
() => props.emptyOptionsLabel ?? formatMessage(messages.noOptions),
|
||||
)
|
||||
const effectiveEmptySearchLabel = computed(
|
||||
() => props.emptySearchLabel ?? formatMessage(messages.noSearchResults),
|
||||
)
|
||||
|
||||
const emit = defineEmits<{
|
||||
'update:modelValue': [value: DropdownFilterBarValue]
|
||||
clear: []
|
||||
@@ -737,12 +764,12 @@ const activeCategoryOptionsListStyle = computed<CSSProperties | undefined>(() =>
|
||||
const activeCategoryEmptyStateLabel = computed(() => {
|
||||
const category = activeCategory.value
|
||||
if (!category) {
|
||||
return props.emptyOptionsLabel
|
||||
return effectiveEmptyOptionsLabel.value
|
||||
}
|
||||
|
||||
return category.searchable && categorySearchQuery.value.trim().length > 0
|
||||
? (category.emptySearchLabel ?? props.emptySearchLabel)
|
||||
: (category.emptyOptionsLabel ?? props.emptyOptionsLabel)
|
||||
? (category.emptySearchLabel ?? effectiveEmptySearchLabel.value)
|
||||
: (category.emptyOptionsLabel ?? effectiveEmptyOptionsLabel.value)
|
||||
})
|
||||
|
||||
const submenuStyle = computed<CSSProperties>(() => {
|
||||
@@ -788,9 +815,9 @@ const appliedFilterPreviews = computed(() =>
|
||||
|
||||
const hasAppliedFilters = computed(() => appliedFilterPreviews.value.length > 0)
|
||||
const shouldShowClear = computed(() => hasAppliedFilters.value || props.showClear)
|
||||
const DEFAULT_PREVIEW_TRIGGER_CLASS = 'max-w-[16rem]'
|
||||
const DEFAULT_PREVIEW_TRIGGER_CLASS = 'max-w-[16rem] !rounded-xl'
|
||||
const effectivePreviewTriggerClass = computed(
|
||||
() => props.previewTriggerClass ?? DEFAULT_PREVIEW_TRIGGER_CLASS,
|
||||
() => (props.previewTriggerClass ?? DEFAULT_PREVIEW_TRIGGER_CLASS) + ' h-9',
|
||||
)
|
||||
|
||||
function cloneSelectedFilters(filters: DropdownFilterBarValue): DropdownFilterBarValue {
|
||||
|
||||
@@ -0,0 +1,248 @@
|
||||
<script setup lang="ts">
|
||||
import { EditIcon } from '@modrinth/assets'
|
||||
import { computed, nextTick, onUnmounted, ref, watch } from 'vue'
|
||||
|
||||
const model = defineModel<string>({ default: '' })
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
placeholder?: string
|
||||
defaultValue?: string
|
||||
maxWidth?: string
|
||||
maxLength?: number
|
||||
editLabel?: string
|
||||
activationMode?: 'text' | 'icon' | 'manual'
|
||||
inputClass?: string
|
||||
buttonClass?: string
|
||||
iconTextClass?: string
|
||||
validate?: (value: string) => boolean
|
||||
onChange?: (value: string) => boolean | void | Promise<boolean | void>
|
||||
}>(),
|
||||
{
|
||||
placeholder: '',
|
||||
defaultValue: '',
|
||||
maxWidth: '100%',
|
||||
maxLength: undefined,
|
||||
editLabel: 'Edit',
|
||||
activationMode: 'text',
|
||||
validate: undefined,
|
||||
onChange: undefined,
|
||||
},
|
||||
)
|
||||
|
||||
const isEditing = ref(false)
|
||||
const isSaving = ref(false)
|
||||
const isInvalid = ref(false)
|
||||
const input = ref<HTMLInputElement>()
|
||||
const isEditIconVisible = ref(false)
|
||||
const draft = ref(model.value)
|
||||
const originalValue = ref('')
|
||||
let editIconHideTimeout: ReturnType<typeof setTimeout> | undefined
|
||||
|
||||
const displayValue = computed(() => model.value || props.defaultValue || props.placeholder)
|
||||
const sizingValue = computed(() => {
|
||||
if (isEditing.value) {
|
||||
return draft.value || props.placeholder || ' '
|
||||
}
|
||||
|
||||
return displayValue.value || ' '
|
||||
})
|
||||
|
||||
watch(model, (value) => {
|
||||
if (!isEditing.value) {
|
||||
draft.value = value
|
||||
}
|
||||
})
|
||||
|
||||
function clearEditIconHideTimeout() {
|
||||
if (editIconHideTimeout) {
|
||||
clearTimeout(editIconHideTimeout)
|
||||
editIconHideTimeout = undefined
|
||||
}
|
||||
}
|
||||
|
||||
function showEditIcon() {
|
||||
isEditIconVisible.value = true
|
||||
}
|
||||
|
||||
function keepEditIconVisible() {
|
||||
clearEditIconHideTimeout()
|
||||
isEditIconVisible.value = true
|
||||
}
|
||||
|
||||
function hideEditIcon() {
|
||||
if (!editIconHideTimeout) {
|
||||
isEditIconVisible.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function handleEditIconMouseleave() {
|
||||
clearEditIconHideTimeout()
|
||||
editIconHideTimeout = setTimeout(() => {
|
||||
isEditIconVisible.value = false
|
||||
editIconHideTimeout = undefined
|
||||
}, 1000)
|
||||
}
|
||||
|
||||
async function startEditing() {
|
||||
if (isEditing.value) return
|
||||
|
||||
clearEditIconHideTimeout()
|
||||
originalValue.value = model.value
|
||||
draft.value = model.value
|
||||
isInvalid.value = false
|
||||
isEditing.value = true
|
||||
await nextTick()
|
||||
input.value?.focus()
|
||||
input.value?.select()
|
||||
}
|
||||
|
||||
async function applyValue() {
|
||||
if (!isEditing.value || isSaving.value) return
|
||||
|
||||
const nextValue = (draft.value || props.defaultValue).trim()
|
||||
|
||||
if (nextValue === originalValue.value) {
|
||||
model.value = nextValue
|
||||
isEditing.value = false
|
||||
isInvalid.value = false
|
||||
return
|
||||
}
|
||||
|
||||
if (props.validate && !props.validate(nextValue)) {
|
||||
isInvalid.value = true
|
||||
await nextTick()
|
||||
input.value?.focus()
|
||||
return
|
||||
}
|
||||
|
||||
isSaving.value = true
|
||||
isInvalid.value = false
|
||||
|
||||
try {
|
||||
const accepted = await props.onChange?.(nextValue)
|
||||
if (accepted === false) {
|
||||
isInvalid.value = true
|
||||
isSaving.value = false
|
||||
await nextTick()
|
||||
input.value?.focus()
|
||||
return
|
||||
}
|
||||
|
||||
model.value = nextValue
|
||||
isEditing.value = false
|
||||
} catch {
|
||||
isInvalid.value = true
|
||||
isSaving.value = false
|
||||
await nextTick()
|
||||
input.value?.focus()
|
||||
} finally {
|
||||
isSaving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function cancelEditing() {
|
||||
if (isSaving.value) return
|
||||
|
||||
draft.value = originalValue.value
|
||||
isInvalid.value = false
|
||||
isEditing.value = false
|
||||
}
|
||||
|
||||
function handleKeydown(event: KeyboardEvent) {
|
||||
if (event.key === 'Enter') {
|
||||
event.preventDefault()
|
||||
void applyValue()
|
||||
return
|
||||
}
|
||||
|
||||
if (event.key === 'Escape') {
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
cancelEditing()
|
||||
}
|
||||
}
|
||||
|
||||
onUnmounted(clearEditIconHideTimeout)
|
||||
|
||||
defineExpose({
|
||||
isEditing,
|
||||
startEditing,
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
:data-value="sizingValue"
|
||||
class="relative flex h-6 min-w-3 min-h-0 max-w-full flex-col justify-center border-b font-medium"
|
||||
:class="
|
||||
isEditing
|
||||
? [
|
||||
'after:invisible after:block after:w-full after:whitespace-pre after:content-[attr(data-value)]',
|
||||
isInvalid ? 'border-red' : 'border-contrast',
|
||||
]
|
||||
: 'border-transparent'
|
||||
"
|
||||
:style="{ maxWidth }"
|
||||
>
|
||||
<input
|
||||
v-if="isEditing"
|
||||
ref="input"
|
||||
v-model="draft"
|
||||
type="text"
|
||||
:aria-invalid="isInvalid"
|
||||
:aria-label="editLabel"
|
||||
:disabled="isSaving"
|
||||
:maxlength="maxLength"
|
||||
:placeholder="placeholder"
|
||||
class="absolute inset-0 top-px w-full !h-full !min-h-0 min-w-0 truncate bg-transparent !p-0 text-inherit !border-b-2 border-0 !border-brand !border-solid !shadow-none [font:inherit] !outline-none"
|
||||
:class="inputClass"
|
||||
@blur="applyValue"
|
||||
@click.stop
|
||||
@keydown="handleKeydown"
|
||||
/>
|
||||
<div
|
||||
v-else-if="activationMode === 'icon'"
|
||||
class="group/edit-icon flex min-w-0 max-w-full items-center"
|
||||
@mouseenter="showEditIcon"
|
||||
@mouseleave="hideEditIcon"
|
||||
>
|
||||
<span class="min-w-0 truncate" :class="iconTextClass" :title="model || displayValue">
|
||||
{{ displayValue }}
|
||||
</span>
|
||||
<span
|
||||
class="flex shrink-0 overflow-hidden transition-all duration-150"
|
||||
:class="
|
||||
isEditIconVisible
|
||||
? 'max-w-6 translate-x-0 opacity-100'
|
||||
: 'max-w-0 translate-x-1 opacity-0'
|
||||
"
|
||||
@mouseenter="keepEditIconVisible"
|
||||
@mouseleave="handleEditIconMouseleave"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
class="ml-1 flex size-5 cursor-pointer items-center justify-center border-0 bg-transparent p-0 text-secondary transition-colors hover:text-brand focus-visible:text-contrast"
|
||||
:aria-label="`${editLabel}: ${displayValue}`"
|
||||
@click.stop="startEditing"
|
||||
>
|
||||
<EditIcon class="size-5" aria-hidden="true" />
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
<button
|
||||
v-else-if="activationMode === 'text'"
|
||||
type="button"
|
||||
class="flex w-full max-w-full items-center gap-2 truncate border-0 bg-transparent p-0 text-left text-inherit transition-colors hover:text-brand focus-visible:text-contrast [font:inherit]"
|
||||
:class="buttonClass"
|
||||
:aria-label="`${editLabel}: ${displayValue}`"
|
||||
:title="model || displayValue"
|
||||
@click="startEditing"
|
||||
>
|
||||
<span class="min-w-0 truncate">{{ displayValue }}</span>
|
||||
</button>
|
||||
<span v-else class="min-w-0 truncate" :title="model || displayValue">
|
||||
{{ displayValue }}
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
@@ -83,7 +83,7 @@ defineOptions({
|
||||
|
||||
// When clickable is being clicked, give contents an effect
|
||||
:first-child:active + .smart-clickable__contents:not(.smart-clickable__contents--disabled) {
|
||||
scale: 0.98;
|
||||
scale: var(--active-scale, 0.98);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -90,7 +90,7 @@
|
||||
v-if="!multiline && clearable && model && !disabled && !readonly && variant === 'filled'"
|
||||
type="button"
|
||||
class="absolute right-0.5 z-[1] p-2 touch-manipulation bg-transparent border-none text-secondary hover:text-contrast transition-colors cursor-pointer select-none"
|
||||
aria-label="Clear input"
|
||||
:aria-label="formatMessage(messages.clearInput)"
|
||||
@click="clear"
|
||||
>
|
||||
<XIcon class="h-5 w-5" />
|
||||
@@ -101,7 +101,7 @@
|
||||
v-if="!multiline && variant === 'outlined'"
|
||||
type="button"
|
||||
class="flex touch-manipulation items-center justify-center px-2 bg-transparent border border-solid border-button-bg rounded-r-xl text-secondary hover:text-contrast transition-colors shrink-0"
|
||||
:aria-label="clearable && model ? 'Clear input' : 'Search'"
|
||||
:aria-label="formatMessage(clearable && model ? messages.clearInput : messages.search)"
|
||||
:tabindex="clearable && model ? undefined : -1"
|
||||
@click="clearable && model ? clear() : undefined"
|
||||
>
|
||||
@@ -119,6 +119,14 @@
|
||||
import { SearchIcon, XIcon } from '@modrinth/assets'
|
||||
import { type Component, computed, ref } from 'vue'
|
||||
|
||||
import { defineMessages, useVIntl } from '../../composables/i18n'
|
||||
|
||||
const { formatMessage } = useVIntl()
|
||||
const messages = defineMessages({
|
||||
clearInput: { id: 'input.clear', defaultMessage: 'Clear input' },
|
||||
search: { id: 'input.search', defaultMessage: 'Search' },
|
||||
})
|
||||
|
||||
const RING = {
|
||||
error: 'ring-4 ring-red focus:ring-4 focus:ring-red',
|
||||
none: 'outline-none focus:ring-4 focus:ring-brand-shadow',
|
||||
@@ -184,6 +192,10 @@ const resizeClass = computed(
|
||||
|
||||
defineExpose({
|
||||
focus: () => inputRef.value?.focus(),
|
||||
select: () => {
|
||||
inputRef.value?.focus()
|
||||
inputRef.value?.select()
|
||||
},
|
||||
setValue: (v: string) => {
|
||||
if (inputRef.value) inputRef.value.value = v
|
||||
},
|
||||
|
||||
@@ -65,6 +65,7 @@ export { default as HeadingLink } from './HeadingLink.vue'
|
||||
export { default as HorizontalRule } from './HorizontalRule.vue'
|
||||
export { default as I18nDebugPanel } from './I18nDebugPanel.vue'
|
||||
export { default as IconSelect } from './IconSelect.vue'
|
||||
export { default as InlineEditableText } from './InlineEditableText.vue'
|
||||
export { default as IntlFormatted } from './IntlFormatted.vue'
|
||||
export { default as LoadingBar } from './LoadingBar.vue'
|
||||
export { default as LoadingIndicator } from './LoadingIndicator.vue'
|
||||
|
||||
@@ -1,16 +1,81 @@
|
||||
<template>
|
||||
<div class="space-y-6">
|
||||
<div
|
||||
v-if="ctx.projectInstall.value"
|
||||
class="flex items-center gap-2.5 rounded-[20px] bg-surface-2 p-3"
|
||||
>
|
||||
<div class="shrink-0">
|
||||
<div
|
||||
class="size-14 shrink-0 overflow-hidden rounded-2xl border border-solid border-surface-5"
|
||||
>
|
||||
<Avatar
|
||||
v-if="ctx.projectInstall.value.iconUrl"
|
||||
:src="ctx.projectInstall.value.iconUrl"
|
||||
:alt="ctx.projectInstall.value.title"
|
||||
size="100%"
|
||||
class="!rounded-2xl"
|
||||
no-shadow
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-col gap-1">
|
||||
<span class="font-semibold text-contrast">
|
||||
{{ ctx.projectInstall.value.title }}
|
||||
</span>
|
||||
<div
|
||||
v-if="ctx.projectInstall.value.owner"
|
||||
class="flex items-center gap-2 text-sm text-secondary"
|
||||
>
|
||||
<div class="flex items-center gap-1.5 text-inherit">
|
||||
<Avatar
|
||||
:src="ctx.projectInstall.value.owner.iconUrl"
|
||||
:alt="ctx.projectInstall.value.owner.name"
|
||||
size="1.25rem"
|
||||
:circle="ctx.projectInstall.value.owner.circle"
|
||||
no-shadow
|
||||
/>
|
||||
<span class="font-medium">{{ ctx.projectInstall.value.owner.name }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Instance-specific: Icon upload -->
|
||||
<div v-if="ctx.flowType === 'instance'" class="flex items-center gap-4">
|
||||
<Avatar :src="ctx.instanceIconUrl.value ?? undefined" size="5rem" />
|
||||
<div class="flex flex-col gap-2">
|
||||
<div v-if="ctx.flowType === 'instance'" class="flex items-center gap-2.5">
|
||||
<div class="group relative size-[7.75rem] shrink-0">
|
||||
<Avatar :src="ctx.instanceIconUrl.value ?? undefined" size="100%" no-shadow />
|
||||
<div
|
||||
v-if="ctx.instanceIconUrl.value"
|
||||
class="pointer-events-none absolute right-1.5 top-1.5 opacity-0 transition-opacity group-focus-within:pointer-events-auto group-focus-within:opacity-100 group-hover:pointer-events-auto group-hover:opacity-100"
|
||||
>
|
||||
<Button
|
||||
size="sm"
|
||||
class="!p-2"
|
||||
:aria-label="formatMessage(commonMessages.removeImageButton)"
|
||||
@click="removeIcon"
|
||||
>
|
||||
<XIcon />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-col gap-1.5">
|
||||
<Button type="outlined" @click="triggerIconInput">
|
||||
<UploadIcon />
|
||||
{{ formatMessage(messages.selectIcon) }}
|
||||
{{ formatMessage(messages.uploadIcon) }}
|
||||
</Button>
|
||||
<Button type="outlined" :disabled="!ctx.instanceIcon.value" @click="removeIcon">
|
||||
<XIcon />
|
||||
{{ formatMessage(messages.removeIcon) }}
|
||||
<Button
|
||||
type="outlined"
|
||||
:disabled="randomizing"
|
||||
class="disabled:!cursor-defcaault"
|
||||
@click="randomizeIcon"
|
||||
>
|
||||
<SpinnerIcon v-if="randomizing" class="animate-spin" />
|
||||
<RefreshCwIcon v-else />
|
||||
{{ formatMessage(messages.randomizeIcon) }}
|
||||
</Button>
|
||||
<Button type="outlined" @click="ctx.customizeInstanceIcon?.()">
|
||||
<PaletteIcon />
|
||||
{{ formatMessage(messages.customizeIcon) }}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -145,7 +210,15 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { Paper } from '@modrinth/api-client'
|
||||
import { EyeIcon, EyeOffIcon, UploadIcon, XIcon } from '@modrinth/assets'
|
||||
import {
|
||||
EyeIcon,
|
||||
EyeOffIcon,
|
||||
PaletteIcon,
|
||||
RefreshCwIcon,
|
||||
SpinnerIcon,
|
||||
UploadIcon,
|
||||
XIcon,
|
||||
} from '@modrinth/assets'
|
||||
import { commonMessages, defineMessages, useVIntl } from '@modrinth/ui'
|
||||
import { computed, onMounted, ref, watch } from 'vue'
|
||||
|
||||
@@ -177,13 +250,17 @@ const {
|
||||
} = ctx
|
||||
|
||||
const messages = defineMessages({
|
||||
selectIcon: {
|
||||
uploadIcon: {
|
||||
id: 'creation-flow.modal.custom-setup.icon.select',
|
||||
defaultMessage: 'Select icon',
|
||||
defaultMessage: 'Upload',
|
||||
},
|
||||
removeIcon: {
|
||||
id: 'creation-flow.modal.custom-setup.icon.remove',
|
||||
defaultMessage: 'Remove icon',
|
||||
randomizeIcon: {
|
||||
id: 'creation-flow.modal.custom-setup.icon.randomize',
|
||||
defaultMessage: 'Randomize',
|
||||
},
|
||||
customizeIcon: {
|
||||
id: 'creation-flow.modal.custom-setup.icon.customize',
|
||||
defaultMessage: 'Customize',
|
||||
},
|
||||
nameLabel: {
|
||||
id: 'creation-flow.modal.custom-setup.name.label',
|
||||
@@ -265,6 +342,9 @@ function formatLoaderVersionTypeLabel(type: LoaderVersionType): string {
|
||||
// For instance flow, prepend 'vanilla' to available loaders.
|
||||
// For server flows, vanilla is a separate option in the setup type stage, so exclude it here.
|
||||
const effectiveLoaders = computed(() => {
|
||||
if (ctx.projectInstall.value) {
|
||||
return ctx.projectInstall.value.compatibleLoaders
|
||||
}
|
||||
if (ctx.flowType === 'instance') {
|
||||
return ['vanilla', ...ctx.availableLoaders.filter((l) => l !== 'vanilla')]
|
||||
}
|
||||
@@ -277,6 +357,9 @@ const effectiveLoaders = computed(() => {
|
||||
// Pre-select loader and game version from initial values
|
||||
onMounted(() => {
|
||||
debug('mounted, initialLoader:', ctx.initialLoader, 'initialGameVersion:', ctx.initialGameVersion)
|
||||
if (ctx.flowType === 'instance') {
|
||||
void randomizeIcon()
|
||||
}
|
||||
if (!selectedLoader.value) {
|
||||
if (ctx.initialLoader) {
|
||||
selectedLoader.value = ctx.initialLoader
|
||||
@@ -321,6 +404,23 @@ function removeIcon() {
|
||||
ctx.instanceIconPath.value = null
|
||||
}
|
||||
|
||||
const randomizing = ref(false)
|
||||
|
||||
async function randomizeIcon() {
|
||||
if (!ctx.randomizeInstanceIcon || randomizing.value) return
|
||||
|
||||
randomizing.value = true
|
||||
try {
|
||||
const generated = await ctx.randomizeInstanceIcon()
|
||||
if (!generated) return
|
||||
ctx.instanceIcon.value = null
|
||||
ctx.instanceIconUrl.value = generated.previewUrl
|
||||
ctx.instanceIconPath.value = generated.path
|
||||
} finally {
|
||||
randomizing.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const loaderVersionsLoading = ref(false)
|
||||
const loaderVersionsData = ref<LoaderVersionEntry[]>([])
|
||||
|
||||
@@ -333,6 +433,7 @@ function toApiLoaderName(loader: string): string {
|
||||
}
|
||||
|
||||
const gameVersionsLoading = computed(() => {
|
||||
if (ctx.projectInstall.value) return false
|
||||
const loader = selectedLoader.value
|
||||
if (!loader || loader === 'vanilla') return false
|
||||
if (loader === 'paper') return ctx.paperSupportedVersions.value === null
|
||||
@@ -342,6 +443,16 @@ const gameVersionsLoading = computed(() => {
|
||||
|
||||
// Game versions from tags provider, filtered by loader support
|
||||
const gameVersionOptions = computed<ComboboxOption<string>[]>(() => {
|
||||
if (ctx.projectInstall.value) {
|
||||
const versions =
|
||||
ctx.showSnapshots.value || ctx.projectInstall.value.releaseGameVersions.size === 0
|
||||
? ctx.projectInstall.value.gameVersions
|
||||
: ctx.projectInstall.value.gameVersions.filter((version) =>
|
||||
ctx.projectInstall.value!.releaseGameVersions.has(version),
|
||||
)
|
||||
return versions.map((version) => ({ value: version, label: version }))
|
||||
}
|
||||
|
||||
const versions = ctx.showSnapshots.value
|
||||
? tags.gameVersions.value
|
||||
: tags.gameVersions.value.filter((v) => v.version_type === 'release')
|
||||
@@ -506,6 +617,7 @@ function getLoaderVersionsForGameVersion(
|
||||
watch(
|
||||
() => selectedLoader.value,
|
||||
async (loader) => {
|
||||
if (ctx.projectInstall.value) return
|
||||
await fetchLoaderMetadata(loader)
|
||||
},
|
||||
{ immediate: true },
|
||||
@@ -521,6 +633,7 @@ watch(
|
||||
loaderVersionsData.value = []
|
||||
selectedLoaderVersion.value = null
|
||||
|
||||
if (ctx.projectInstall.value) return
|
||||
if (!loader || !gameVersion || loader === 'vanilla') return
|
||||
|
||||
loaderVersionsLoading.value = true
|
||||
|
||||
@@ -1,10 +1,53 @@
|
||||
<template>
|
||||
<div class="flex flex-col gap-4">
|
||||
<span class="font-semibold text-contrast">
|
||||
{{ formatMessage(messages.knownProjectPrompt) }}
|
||||
</span>
|
||||
<Combobox
|
||||
ref="projectSearchCombobox"
|
||||
v-model="ctx.projectSearchProjectId.value"
|
||||
v-tooltip="ctx.finishDisabled.value ? ctx.finishDisabledTooltip.value : undefined"
|
||||
:options="ctx.projectSearchOptions.value"
|
||||
searchable
|
||||
show-search-icon
|
||||
:show-chevron="false"
|
||||
:disabled="ctx.finishDisabled.value"
|
||||
:search-placeholder="formatMessage(messages.searchProjectPlaceholder)"
|
||||
:no-options-message="
|
||||
searchLoading
|
||||
? formatMessage(commonMessages.loadingLabel)
|
||||
: formatMessage(messages.noResultsFound)
|
||||
"
|
||||
:disable-search-filter="true"
|
||||
@search-input="handleSearch"
|
||||
>
|
||||
<template #option-suffix="{ item }">
|
||||
<div
|
||||
class="flex shrink-0 items-center gap-1.5 text-sm font-semibold text-secondary opacity-0 transition-opacity group-hover/option:opacity-100 group-data-[focused=true]/option:opacity-100"
|
||||
>
|
||||
<span>
|
||||
{{
|
||||
formatMessage(
|
||||
isModpackOption(item.value) ? messages.installModpack : messages.createInstance,
|
||||
)
|
||||
}}
|
||||
</span>
|
||||
<DownloadIcon v-if="isModpackOption(item.value)" class="size-5 shrink-0" />
|
||||
<RightArrowIcon v-else class="size-5 shrink-0" />
|
||||
</div>
|
||||
</template>
|
||||
</Combobox>
|
||||
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="h-[1px] w-full flex-1 bg-surface-5" />
|
||||
<span class="text-sm text-secondary">{{ formatMessage(commonMessages.orLabel) }}</span>
|
||||
<div class="h-[1px] w-full flex-1 bg-surface-5" />
|
||||
</div>
|
||||
|
||||
<span class="font-semibold text-contrast">
|
||||
{{ setupTypeTitle }}
|
||||
</span>
|
||||
|
||||
<!-- Instance flow options -->
|
||||
<template v-if="ctx.flowType === 'instance'">
|
||||
<div class="flex flex-col gap-3">
|
||||
<BigOptionButton
|
||||
@@ -14,10 +57,16 @@
|
||||
@click="setSetupType('custom')"
|
||||
/>
|
||||
<BigOptionButton
|
||||
:icon="PackageIcon"
|
||||
:icon="CompassIcon"
|
||||
:title="formatMessage(messages.modpackBaseTitle)"
|
||||
:description="formatMessage(messages.modpackBaseDescription)"
|
||||
@click="setSetupType('modpack')"
|
||||
@click="browseModpacks"
|
||||
/>
|
||||
<BigOptionButton
|
||||
:icon="UploadIcon"
|
||||
:title="formatMessage(messages.uploadModpackTitle)"
|
||||
:description="formatMessage(messages.uploadModpackDescription)"
|
||||
@click="triggerFileInput"
|
||||
/>
|
||||
<BigOptionButton
|
||||
:icon="BoxImportIcon"
|
||||
@@ -26,19 +75,21 @@
|
||||
@click="ctx.setImportMode()"
|
||||
/>
|
||||
</div>
|
||||
<span class="text-sm text-secondary">
|
||||
{{ formatMessage(messages.instanceDescription) }}
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<!-- World / Server onboarding flow options -->
|
||||
<template v-else>
|
||||
<div class="flex flex-col gap-3">
|
||||
<BigOptionButton
|
||||
:icon="PackageIcon"
|
||||
:icon="CompassIcon"
|
||||
:title="formatMessage(messages.modpackBaseTitle)"
|
||||
:description="formatMessage(messages.modpackBaseDescription)"
|
||||
@click="setSetupType('modpack')"
|
||||
@click="browseModpacks"
|
||||
/>
|
||||
<BigOptionButton
|
||||
:icon="UploadIcon"
|
||||
:title="formatMessage(messages.uploadModpackTitle)"
|
||||
:description="formatMessage(messages.uploadModpackDescription)"
|
||||
@click="triggerFileInput"
|
||||
/>
|
||||
<BigOptionButton
|
||||
:icon="BoxesIcon"
|
||||
@@ -58,21 +109,55 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { BoxesIcon, BoxIcon, BoxImportIcon, PackageIcon } from '@modrinth/assets'
|
||||
import { defineMessages, useVIntl } from '@modrinth/ui'
|
||||
import { computed } from 'vue'
|
||||
import {
|
||||
BoxesIcon,
|
||||
BoxIcon,
|
||||
BoxImportIcon,
|
||||
CompassIcon,
|
||||
DownloadIcon,
|
||||
RightArrowIcon,
|
||||
UploadIcon,
|
||||
} from '@modrinth/assets'
|
||||
import { commonMessages, defineMessages, useVIntl } from '@modrinth/ui'
|
||||
import { computed, defineAsyncComponent, h, onMounted, ref, watch } from 'vue'
|
||||
|
||||
import { useDebugLogger } from '#ui/composables/debug-logger'
|
||||
|
||||
import { injectFilePicker } from '../../../../providers'
|
||||
import BigOptionButton from '../../../base/BigOptionButton.vue'
|
||||
import Combobox from '../../../base/Combobox.vue'
|
||||
import { injectCreationFlowContext } from '../creation-flow-context'
|
||||
|
||||
const debug = useDebugLogger('SetupTypeStage')
|
||||
const ctx = injectCreationFlowContext()
|
||||
const filePicker = injectFilePicker()
|
||||
const { setSetupType: _setSetupType } = ctx
|
||||
const { formatMessage } = useVIntl()
|
||||
|
||||
const searchLoading = ref(false)
|
||||
const projectSearchCombobox = ref<{ $el: HTMLElement }>()
|
||||
|
||||
const messages = defineMessages({
|
||||
knownProjectPrompt: {
|
||||
id: 'creation-flow.modal.project.known-project.prompt',
|
||||
defaultMessage: 'Already know what you want to play?',
|
||||
},
|
||||
searchProjectPlaceholder: {
|
||||
id: 'creation-flow.modal.project.search.placeholder',
|
||||
defaultMessage: 'Search mods, modpacks, and more...',
|
||||
},
|
||||
noResultsFound: {
|
||||
id: 'creation-flow.modal.project.search.no-results',
|
||||
defaultMessage: 'No results found',
|
||||
},
|
||||
installModpack: {
|
||||
id: 'creation-flow.modal.project.search.install-modpack',
|
||||
defaultMessage: 'Install modpack',
|
||||
},
|
||||
createInstance: {
|
||||
id: 'creation-flow.modal.project.search.create-instance',
|
||||
defaultMessage: 'Create instance',
|
||||
},
|
||||
instanceTypeTitle: {
|
||||
id: 'creation-flow.modal.setup-type.title.instance',
|
||||
defaultMessage: 'Choose instance type',
|
||||
@@ -95,11 +180,19 @@ const messages = defineMessages({
|
||||
},
|
||||
modpackBaseTitle: {
|
||||
id: 'creation-flow.modal.setup-type.option.modpack-base.title',
|
||||
defaultMessage: 'Install modpack',
|
||||
defaultMessage: 'Start from a mod or modpack',
|
||||
},
|
||||
modpackBaseDescription: {
|
||||
id: 'creation-flow.modal.setup-type.option.modpack-base.description',
|
||||
defaultMessage: 'Browse modpacks on Modrinth or import one from a file.',
|
||||
defaultMessage: 'Choose a project and we’ll use its latest version.',
|
||||
},
|
||||
uploadModpackTitle: {
|
||||
id: 'creation-flow.modal.setup-type.option.upload-modpack.title',
|
||||
defaultMessage: 'Upload a modpack',
|
||||
},
|
||||
uploadModpackDescription: {
|
||||
id: 'creation-flow.modal.setup-type.option.upload-modpack.description',
|
||||
defaultMessage: 'Install a modpack from an .mrpack file on your device.',
|
||||
},
|
||||
importInstanceTitle: {
|
||||
id: 'creation-flow.modal.setup-type.option.import-instance.title',
|
||||
@@ -109,10 +202,6 @@ const messages = defineMessages({
|
||||
id: 'creation-flow.modal.setup-type.option.import-instance.description',
|
||||
defaultMessage: 'Import an instance from Prism, CurseForge, or similar.',
|
||||
},
|
||||
instanceDescription: {
|
||||
id: 'creation-flow.modal.setup-type.instance.description',
|
||||
defaultMessage: 'An instance is a Minecraft setup with a specific loader, version, and mods.',
|
||||
},
|
||||
vanillaMinecraftTitle: {
|
||||
id: 'creation-flow.modal.setup-type.option.vanilla-minecraft.title',
|
||||
defaultMessage: 'Vanilla Minecraft',
|
||||
@@ -133,8 +222,132 @@ const setupTypeTitle = computed(() => {
|
||||
return formatMessage(messages.worldTypeTitle)
|
||||
})
|
||||
|
||||
function setSetupType(type: 'modpack' | 'custom' | 'vanilla') {
|
||||
function isModpackOption(projectId: string) {
|
||||
return ctx.projectSearchHits.value[projectId]?.projectType === 'modpack'
|
||||
}
|
||||
|
||||
function setSetupType(type: 'custom' | 'vanilla') {
|
||||
debug('selected:', type)
|
||||
_setSetupType(type)
|
||||
}
|
||||
|
||||
function selectModpack() {
|
||||
debug('selected: modpack')
|
||||
_setSetupType('modpack')
|
||||
}
|
||||
|
||||
function proceedWithModpack() {
|
||||
if (ctx.finishDisabled.value) return
|
||||
|
||||
if (ctx.flowType === 'instance') {
|
||||
ctx.finish()
|
||||
} else {
|
||||
ctx.modal.value?.setStage('final-config')
|
||||
}
|
||||
}
|
||||
|
||||
function browseModpacks() {
|
||||
if (ctx.finishDisabled.value) return
|
||||
|
||||
selectModpack()
|
||||
ctx.browseModpacks()
|
||||
}
|
||||
|
||||
async function triggerFileInput() {
|
||||
if (ctx.finishDisabled.value) return
|
||||
|
||||
const picked = await filePicker.pickModpackFile({
|
||||
readFile: ctx.flowType !== 'instance',
|
||||
})
|
||||
if (!picked) return
|
||||
|
||||
selectModpack()
|
||||
ctx.modpackFile.value = picked.file ?? null
|
||||
ctx.modpackFilePath.value = picked.path ?? null
|
||||
proceedWithModpack()
|
||||
}
|
||||
|
||||
async function search(query: string) {
|
||||
try {
|
||||
if (!query.trim()) {
|
||||
ctx.projectSearchOptions.value = []
|
||||
return
|
||||
}
|
||||
const results = await ctx.searchProjects(query.trim(), 10)
|
||||
|
||||
ctx.projectSearchHits.value = {}
|
||||
for (const hit of results.hits) {
|
||||
ctx.projectSearchHits.value[hit.project_id] = {
|
||||
title: hit.title,
|
||||
iconUrl: hit.icon_url,
|
||||
latestVersion: hit.latest_version,
|
||||
projectType: hit.project_type ?? 'modpack',
|
||||
}
|
||||
}
|
||||
|
||||
ctx.projectSearchOptions.value = results.hits.map((hit) => ({
|
||||
label: hit.title,
|
||||
value: hit.project_id,
|
||||
icon: defineAsyncComponent(() =>
|
||||
Promise.resolve({
|
||||
setup: () => () =>
|
||||
h('img', {
|
||||
src: hit.icon_url,
|
||||
alt: hit.title,
|
||||
class: 'h-5 w-5 rounded',
|
||||
}),
|
||||
}),
|
||||
),
|
||||
}))
|
||||
} catch (error) {
|
||||
debug('project search failed:', error)
|
||||
ctx.projectSearchOptions.value = []
|
||||
} finally {
|
||||
searchLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function handleSearch(query: string) {
|
||||
searchLoading.value = true
|
||||
await search(query)
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
ctx.projectSearchProjectId.value = undefined
|
||||
search('')
|
||||
setTimeout(() => {
|
||||
projectSearchCombobox.value?.$el.querySelector('input')?.focus()
|
||||
}, 150)
|
||||
})
|
||||
|
||||
watch(
|
||||
() => ctx.projectSearchProjectId.value,
|
||||
async (projectId, oldProjectId) => {
|
||||
if (projectId === oldProjectId) return
|
||||
|
||||
if (!projectId) return
|
||||
const hit = ctx.projectSearchHits.value[projectId]
|
||||
|
||||
if (ctx.flowType === 'instance') {
|
||||
void ctx.selectProject(projectId, hit?.projectType ?? 'mod')
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const versions = await ctx.getProjectVersions(projectId)
|
||||
if (ctx.projectSearchProjectId.value !== projectId || versions.length === 0) return
|
||||
|
||||
selectModpack()
|
||||
ctx.modpackSelection.value = {
|
||||
projectId,
|
||||
versionId: versions[0].id,
|
||||
name: hit?.title ?? '',
|
||||
iconUrl: hit?.iconUrl,
|
||||
}
|
||||
proceedWithModpack()
|
||||
} catch (error) {
|
||||
debug('failed to load project versions:', error)
|
||||
}
|
||||
},
|
||||
)
|
||||
</script>
|
||||
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
} from '#ui/composables/i18n'
|
||||
import { formatLoaderLabel } from '#ui/utils/loaders'
|
||||
|
||||
import { createContext, injectModrinthClient } from '../../../providers'
|
||||
import { createContext, injectModrinthClient, injectNotificationManager } from '../../../providers'
|
||||
import type { ImportableLauncher } from '../../../providers/instance-import'
|
||||
import type { MultiStageModal, StageConfigInput } from '../../base'
|
||||
import type { ComboboxOption } from '../../base/Combobox.vue'
|
||||
@@ -81,10 +81,6 @@ export const creationFlowMessages = defineMessages({
|
||||
id: 'creation-flow.button.import-instances',
|
||||
defaultMessage: 'Import {count, plural, one {# instance} other {# instances}}',
|
||||
},
|
||||
chooseModpackTitle: {
|
||||
id: 'creation-flow.title.choose-modpack',
|
||||
defaultMessage: 'Choose modpack',
|
||||
},
|
||||
})
|
||||
|
||||
export const flowTypeHeadingMessages: Record<FlowType, MessageDescriptor> = {
|
||||
@@ -101,15 +97,17 @@ export interface ModpackSelection {
|
||||
iconUrl?: string
|
||||
}
|
||||
|
||||
export interface ModpackSearchHit {
|
||||
export interface ProjectSearchHit {
|
||||
title: string
|
||||
iconUrl?: string
|
||||
latestVersion?: string
|
||||
projectType: string
|
||||
}
|
||||
|
||||
export interface ModpackSearchResult {
|
||||
export interface ProjectSearchResult {
|
||||
hits: {
|
||||
project_id: string
|
||||
project_type?: string
|
||||
title: string
|
||||
icon_url: string
|
||||
latest_version?: string
|
||||
@@ -119,6 +117,35 @@ export interface ModpackSearchResult {
|
||||
limit: number
|
||||
}
|
||||
|
||||
export interface ProjectInstallSelection {
|
||||
projectId: string
|
||||
title: string
|
||||
iconUrl?: string | null
|
||||
link: string
|
||||
owner?: {
|
||||
name: string
|
||||
iconUrl?: string
|
||||
circle?: boolean
|
||||
link: string | (() => void)
|
||||
} | null
|
||||
compatibleLoaders: string[]
|
||||
gameVersions: string[]
|
||||
releaseGameVersions: Set<string>
|
||||
}
|
||||
|
||||
export interface ProjectInstallCreateData {
|
||||
name: string
|
||||
iconPath: string | null
|
||||
iconPreviewUrl: string | null
|
||||
loader: string
|
||||
gameVersion: string
|
||||
}
|
||||
|
||||
export interface GeneratedInstanceIcon {
|
||||
path: string
|
||||
previewUrl: string
|
||||
}
|
||||
|
||||
export interface CreationFlowContextValue {
|
||||
// Flow
|
||||
flowType: FlowType
|
||||
@@ -152,6 +179,8 @@ export interface CreationFlowContextValue {
|
||||
instanceIcon: Ref<File | null>
|
||||
instanceIconUrl: Ref<string | null>
|
||||
instanceIconPath: Ref<string | null>
|
||||
randomizeInstanceIcon: (() => Promise<GeneratedInstanceIcon | null>) | null
|
||||
customizeInstanceIcon: (() => void) | null
|
||||
|
||||
// Loader/version state (custom setup)
|
||||
selectedLoader: Ref<string | null>
|
||||
@@ -169,13 +198,12 @@ export interface CreationFlowContextValue {
|
||||
modpackSelection: Ref<ModpackSelection | null>
|
||||
modpackFile: Ref<File | null>
|
||||
modpackFilePath: Ref<string | null>
|
||||
projectInstall: Ref<ProjectInstallSelection | null>
|
||||
|
||||
// Modpack search state (persisted across stage navigation)
|
||||
modpackSearchProjectId: Ref<string | undefined>
|
||||
modpackSearchVersionId: Ref<string | undefined>
|
||||
modpackSearchOptions: Ref<ComboboxOption<string>[]>
|
||||
modpackVersionOptions: Ref<ComboboxOption<string>[]>
|
||||
modpackSearchHits: Ref<Record<string, ModpackSearchHit>>
|
||||
// Project search state (persisted across stage navigation)
|
||||
projectSearchProjectId: Ref<string | undefined>
|
||||
projectSearchOptions: Ref<ComboboxOption<string>[]>
|
||||
projectSearchHits: Ref<Record<string, ProjectSearchHit>>
|
||||
|
||||
// Import state (instance flow only)
|
||||
importLaunchers: Ref<ImportableLauncher[]>
|
||||
@@ -206,13 +234,14 @@ export interface CreationFlowContextValue {
|
||||
setSetupType: (type: SetupType) => void
|
||||
setImportMode: () => void
|
||||
browseModpacks: () => void
|
||||
selectProject: (projectId: string, projectType: string) => Promise<void>
|
||||
finish: () => void
|
||||
buildProperties: () => Archon.Content.v1.PropertiesFields
|
||||
fetchLoaderMetadata: (loader?: string | null) => Promise<void>
|
||||
prefetchLoaderMetadata: () => Promise<void>
|
||||
|
||||
// Platform-provided search
|
||||
searchModpacks: (query: string, limit?: number) => Promise<ModpackSearchResult>
|
||||
searchProjects: (query: string, limit?: number) => Promise<ProjectSearchResult>
|
||||
getProjectVersions: (projectId: string) => Promise<{ id: string }[]>
|
||||
getLoaderManifest: LoaderManifestResolver | null
|
||||
}
|
||||
@@ -232,9 +261,16 @@ export interface CreationFlowOptions {
|
||||
initialGameVersion?: string
|
||||
fetchExistingInstanceNames?: () => Promise<string[]>
|
||||
onBack?: () => void
|
||||
searchModpacks?: (query: string, limit?: number) => Promise<ModpackSearchResult>
|
||||
searchProjects?: (query: string, limit?: number) => Promise<ProjectSearchResult>
|
||||
prepareProjectInstall?: (
|
||||
projectId: string,
|
||||
projectType: string,
|
||||
) => Promise<ProjectInstallSelection | null>
|
||||
createProjectInstall?: (data: ProjectInstallCreateData) => Promise<void>
|
||||
getProjectVersions?: (projectId: string) => Promise<{ id: string }[]>
|
||||
getLoaderManifest?: LoaderManifestResolver
|
||||
randomizeInstanceIcon?: () => Promise<GeneratedInstanceIcon | null>
|
||||
customizeInstanceIcon?: () => void
|
||||
finishDisabled?: ComputedRef<boolean>
|
||||
finishDisabledTooltip?: ComputedRef<string | undefined>
|
||||
}
|
||||
@@ -250,6 +286,7 @@ export function createCreationFlowContext(
|
||||
): CreationFlowContextValue {
|
||||
const debug = useDebugLogger('CreationFlow')
|
||||
const client = injectModrinthClient()
|
||||
const { handleError } = injectNotificationManager()
|
||||
const queryClient = useQueryClient()
|
||||
const { formatMessage } = useVIntl()
|
||||
const availableLoaders = options.availableLoaders ?? ['fabric', 'neoforge', 'forge', 'quilt']
|
||||
@@ -259,7 +296,11 @@ export function createCreationFlowContext(
|
||||
const initialLoader = options.initialLoader ?? null
|
||||
const initialGameVersion = options.initialGameVersion ?? null
|
||||
const onBack = options.onBack ?? null
|
||||
const searchModpacks = options.searchModpacks!
|
||||
const randomizeInstanceIcon = options.randomizeInstanceIcon ?? null
|
||||
const customizeInstanceIcon = options.customizeInstanceIcon ?? null
|
||||
const searchProjects = options.searchProjects!
|
||||
const prepareProjectInstall = options.prepareProjectInstall
|
||||
const createProjectInstall = options.createProjectInstall
|
||||
const getProjectVersions = options.getProjectVersions!
|
||||
const getLoaderManifest = options.getLoaderManifest ?? null
|
||||
const finishDisabled = options.finishDisabled ?? computed(() => false)
|
||||
@@ -321,13 +362,12 @@ export function createCreationFlowContext(
|
||||
const modpackSelection = ref<ModpackSelection | null>(null)
|
||||
const modpackFile = ref<File | null>(null)
|
||||
const modpackFilePath = ref<string | null>(null)
|
||||
const projectInstall = ref<ProjectInstallSelection | null>(null)
|
||||
|
||||
// Modpack search state (persisted across stage navigation)
|
||||
const modpackSearchProjectId = ref<string | undefined>()
|
||||
const modpackSearchVersionId = ref<string | undefined>()
|
||||
const modpackSearchOptions = ref<ComboboxOption<string>[]>([])
|
||||
const modpackVersionOptions = ref<ComboboxOption<string>[]>([])
|
||||
const modpackSearchHits = ref<Record<string, ModpackSearchHit>>({})
|
||||
// Project search state (persisted across stage navigation)
|
||||
const projectSearchProjectId = ref<string | undefined>()
|
||||
const projectSearchOptions = ref<ComboboxOption<string>[]>([])
|
||||
const projectSearchHits = ref<Record<string, ProjectSearchHit>>({})
|
||||
|
||||
// Import state (instance flow only)
|
||||
const importLaunchers = ref<ImportableLauncher[]>([])
|
||||
@@ -344,7 +384,10 @@ export function createCreationFlowContext(
|
||||
|
||||
// hideLoaderVersion: hides the loader version section (vanilla world type OR vanilla selected as loader chip)
|
||||
const hideLoaderVersion = computed(
|
||||
() => setupType.value === 'vanilla' || selectedLoader.value === 'vanilla',
|
||||
() =>
|
||||
setupType.value === 'vanilla' ||
|
||||
selectedLoader.value === 'vanilla' ||
|
||||
projectInstall.value !== null,
|
||||
)
|
||||
|
||||
function toApiLoaderName(loader: string): string {
|
||||
@@ -454,11 +497,10 @@ export function createCreationFlowContext(
|
||||
modpackSelection.value = null
|
||||
modpackFile.value = null
|
||||
modpackFilePath.value = null
|
||||
modpackSearchProjectId.value = undefined
|
||||
modpackSearchVersionId.value = undefined
|
||||
modpackSearchOptions.value = []
|
||||
modpackVersionOptions.value = []
|
||||
modpackSearchHits.value = {}
|
||||
projectInstall.value = null
|
||||
projectSearchProjectId.value = undefined
|
||||
projectSearchOptions.value = []
|
||||
projectSearchHits.value = {}
|
||||
|
||||
// Import state
|
||||
importLaunchers.value = []
|
||||
@@ -474,12 +516,12 @@ export function createCreationFlowContext(
|
||||
function setSetupType(type: SetupType) {
|
||||
debug('setSetupType:', type)
|
||||
isImportMode.value = false
|
||||
projectInstall.value = null
|
||||
setupType.value = type
|
||||
if (type === 'modpack') {
|
||||
selectedLoader.value = null
|
||||
selectedLoaderVersion.value = null
|
||||
loaderVersionType.value = 'stable'
|
||||
modal.value?.setStage('modpack')
|
||||
} else {
|
||||
modpackSelection.value = null
|
||||
modpackFile.value = null
|
||||
@@ -506,6 +548,37 @@ export function createCreationFlowContext(
|
||||
emit.browseModpacks()
|
||||
}
|
||||
|
||||
async function selectProject(projectId: string, projectType: string) {
|
||||
if (!prepareProjectInstall) return
|
||||
|
||||
try {
|
||||
const selection = await prepareProjectInstall(projectId, projectType)
|
||||
if (selection) {
|
||||
setProjectInstall(selection)
|
||||
} else {
|
||||
modal.value?.hide()
|
||||
}
|
||||
} catch (error) {
|
||||
projectSearchProjectId.value = undefined
|
||||
handleError(error as Error)
|
||||
}
|
||||
}
|
||||
|
||||
function setProjectInstall(selection: ProjectInstallSelection) {
|
||||
projectInstall.value = selection
|
||||
projectSearchProjectId.value = undefined
|
||||
setupType.value = 'custom'
|
||||
isImportMode.value = false
|
||||
modpackSelection.value = null
|
||||
instanceName.value = selection.title
|
||||
selectedLoader.value = selection.compatibleLoaders[0] ?? null
|
||||
selectedGameVersion.value =
|
||||
selection.gameVersions.find((version) => selection.releaseGameVersions.has(version)) ??
|
||||
selection.gameVersions[0] ??
|
||||
null
|
||||
modal.value?.setStage('custom-setup')
|
||||
}
|
||||
|
||||
function finish() {
|
||||
if (finishDisabled.value) return
|
||||
|
||||
@@ -518,6 +591,22 @@ export function createCreationFlowContext(
|
||||
hasModpackFile: !!modpackFile.value,
|
||||
})
|
||||
loading.value = true
|
||||
if (
|
||||
projectInstall.value &&
|
||||
createProjectInstall &&
|
||||
selectedLoader.value &&
|
||||
selectedGameVersion.value
|
||||
) {
|
||||
modal.value?.hide()
|
||||
void createProjectInstall({
|
||||
name: instanceName.value.trim() || projectInstall.value.title,
|
||||
iconPath: instanceIconPath.value,
|
||||
iconPreviewUrl: instanceIconUrl.value,
|
||||
loader: selectedLoader.value,
|
||||
gameVersion: selectedGameVersion.value,
|
||||
}).catch(handleError)
|
||||
return
|
||||
}
|
||||
emit.create(contextValue)
|
||||
}
|
||||
|
||||
@@ -569,6 +658,8 @@ export function createCreationFlowContext(
|
||||
instanceIcon,
|
||||
instanceIconUrl,
|
||||
instanceIconPath,
|
||||
randomizeInstanceIcon,
|
||||
customizeInstanceIcon,
|
||||
selectedLoader,
|
||||
selectedGameVersion,
|
||||
loaderVersionType,
|
||||
@@ -582,11 +673,10 @@ export function createCreationFlowContext(
|
||||
modpackSelection,
|
||||
modpackFile,
|
||||
modpackFilePath,
|
||||
modpackSearchProjectId,
|
||||
modpackSearchVersionId,
|
||||
modpackSearchOptions,
|
||||
modpackVersionOptions,
|
||||
modpackSearchHits,
|
||||
projectInstall,
|
||||
projectSearchProjectId,
|
||||
projectSearchOptions,
|
||||
projectSearchHits,
|
||||
importLaunchers,
|
||||
importSelectedInstances,
|
||||
importSearchQuery,
|
||||
@@ -603,11 +693,12 @@ export function createCreationFlowContext(
|
||||
setSetupType,
|
||||
setImportMode,
|
||||
browseModpacks,
|
||||
selectProject,
|
||||
finish,
|
||||
buildProperties,
|
||||
fetchLoaderMetadata,
|
||||
prefetchLoaderMetadata,
|
||||
searchModpacks,
|
||||
searchProjects,
|
||||
getProjectVersions,
|
||||
getLoaderManifest,
|
||||
}
|
||||
|
||||
@@ -19,7 +19,9 @@ import {
|
||||
type CreationFlowContextValue,
|
||||
type FlowType,
|
||||
type LoaderManifestResolver,
|
||||
type ModpackSearchResult,
|
||||
type ProjectInstallCreateData,
|
||||
type ProjectInstallSelection,
|
||||
type ProjectSearchResult,
|
||||
provideCreationFlowContext,
|
||||
} from './creation-flow-context'
|
||||
|
||||
@@ -35,9 +37,16 @@ const props = withDefaults(
|
||||
fetchExistingInstanceNames?: () => Promise<string[]>
|
||||
onBack?: (() => void) | null
|
||||
fade?: 'standard' | 'warning' | 'danger'
|
||||
searchModpacks?: (query: string, limit?: number) => Promise<ModpackSearchResult>
|
||||
searchProjects?: (query: string, limit?: number) => Promise<ProjectSearchResult>
|
||||
prepareProjectInstall?: (
|
||||
projectId: string,
|
||||
projectType: string,
|
||||
) => Promise<ProjectInstallSelection | null>
|
||||
createProjectInstall?: (data: ProjectInstallCreateData) => Promise<void>
|
||||
getProjectVersions?: (projectId: string) => Promise<{ id: string }[]>
|
||||
getLoaderManifest?: LoaderManifestResolver
|
||||
randomizeInstanceIcon?: () => Promise<{ path: string; previewUrl: string } | null>
|
||||
customizeInstanceIcon?: () => void
|
||||
finishDisabled?: boolean
|
||||
finishDisabledTooltip?: string
|
||||
}>(),
|
||||
@@ -51,6 +60,8 @@ const props = withDefaults(
|
||||
initialGameVersion: undefined,
|
||||
fetchExistingInstanceNames: undefined,
|
||||
onBack: null,
|
||||
randomizeInstanceIcon: undefined,
|
||||
customizeInstanceIcon: undefined,
|
||||
},
|
||||
)
|
||||
|
||||
@@ -77,9 +88,13 @@ const ctx = createCreationFlowContext(
|
||||
initialGameVersion: props.initialGameVersion,
|
||||
fetchExistingInstanceNames: props.fetchExistingInstanceNames,
|
||||
onBack: props.onBack ?? undefined,
|
||||
searchModpacks: props.searchModpacks,
|
||||
searchProjects: props.searchProjects,
|
||||
prepareProjectInstall: props.prepareProjectInstall,
|
||||
createProjectInstall: props.createProjectInstall,
|
||||
getProjectVersions: props.getProjectVersions,
|
||||
getLoaderManifest: props.getLoaderManifest,
|
||||
randomizeInstanceIcon: props.randomizeInstanceIcon,
|
||||
customizeInstanceIcon: props.customizeInstanceIcon,
|
||||
finishDisabled: computed(() => props.finishDisabled ?? false),
|
||||
finishDisabledTooltip: computed(() => props.finishDisabledTooltip),
|
||||
},
|
||||
|
||||
@@ -72,5 +72,5 @@ export const stageConfig: StageConfigInput<CreationFlowContextValue> = {
|
||||
},
|
||||
}
|
||||
},
|
||||
maxWidth: '520px',
|
||||
maxWidth: '560px',
|
||||
}
|
||||
|
||||
@@ -64,5 +64,5 @@ export const stageConfig: StageConfigInput<CreationFlowContextValue> = {
|
||||
},
|
||||
}
|
||||
},
|
||||
maxWidth: '520px',
|
||||
maxWidth: '560px',
|
||||
}
|
||||
|
||||
@@ -43,5 +43,5 @@ export const stageConfig: StageConfigInput<CreationFlowContextValue> = {
|
||||
onClick: () => ctx.finish(),
|
||||
}
|
||||
},
|
||||
maxWidth: '520px',
|
||||
maxWidth: '560px',
|
||||
}
|
||||
|
||||
@@ -3,12 +3,10 @@ import type { CreationFlowContextValue } from '../creation-flow-context'
|
||||
import { stageConfig as customSetupStageConfig } from './custom-setup-stage'
|
||||
import { stageConfig as finalConfigStageConfig } from './final-config-stage'
|
||||
import { stageConfig as importInstanceStageConfig } from './import-instance-stage'
|
||||
import { stageConfig as modpackStageConfig } from './modpack-stage'
|
||||
import { stageConfig as setupTypeStageConfig } from './setup-type-stage'
|
||||
|
||||
export const stageConfigs: StageConfigInput<CreationFlowContextValue>[] = [
|
||||
setupTypeStageConfig,
|
||||
modpackStageConfig,
|
||||
importInstanceStageConfig,
|
||||
customSetupStageConfig,
|
||||
finalConfigStageConfig,
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
import { LeftArrowIcon } from '@modrinth/assets'
|
||||
import { markRaw } from 'vue'
|
||||
|
||||
import { commonMessages } from '#ui/utils/common-messages'
|
||||
|
||||
import type { StageConfigInput } from '../../../base'
|
||||
import ModpackStage from '../components/ModpackStage.vue'
|
||||
import { type CreationFlowContextValue, creationFlowMessages } from '../creation-flow-context'
|
||||
|
||||
export const stageConfig: StageConfigInput<CreationFlowContextValue> = {
|
||||
id: 'modpack',
|
||||
title: (ctx) => ctx.formatMessage(creationFlowMessages.chooseModpackTitle),
|
||||
stageContent: markRaw(ModpackStage),
|
||||
skip: (ctx) => ctx.setupType.value !== 'modpack' || ctx.isImportMode.value,
|
||||
leftButtonConfig: (ctx) => ({
|
||||
label: ctx.formatMessage(commonMessages.backButton),
|
||||
icon: LeftArrowIcon,
|
||||
onClick: () => ctx.modal.value?.setStage('setup-type'),
|
||||
}),
|
||||
rightButtonConfig: null,
|
||||
maxWidth: '520px',
|
||||
}
|
||||
@@ -10,5 +10,5 @@ export const stageConfig: StageConfigInput<CreationFlowContextValue> = {
|
||||
stageContent: markRaw(SetupTypeStage),
|
||||
leftButtonConfig: null,
|
||||
rightButtonConfig: null,
|
||||
maxWidth: '520px',
|
||||
maxWidth: '560px',
|
||||
}
|
||||
|
||||
@@ -16,27 +16,27 @@
|
||||
@mouseleave="setNotificationTimer(item)"
|
||||
>
|
||||
<NotificationToast
|
||||
v-if="item.toast"
|
||||
v-if="item.contentType === 'toast'"
|
||||
class="min-w-full"
|
||||
:type="item.toast.type"
|
||||
:type="item.type"
|
||||
:action-loading="toastActionLoading(item.id)"
|
||||
:actor-name="item.toast.actorName"
|
||||
:actor-avatar-url="item.toast.actorAvatarUrl"
|
||||
:entity-name="item.toast.entityName"
|
||||
:entity-icon-url="item.toast.entityIconUrl"
|
||||
:status-text="item.toast.statusText"
|
||||
:progress="item.toast.progress"
|
||||
:waiting="item.toast.waiting"
|
||||
:show-progress="item.toast.showProgress"
|
||||
:progress-type="item.toast.progressType"
|
||||
:progress-current="item.toast.progressCurrent"
|
||||
:progress-total="item.toast.progressTotal"
|
||||
@accept="handleToastAccept(item, item.toast.onAccept)"
|
||||
@decline="handleToastAction(item, item.toast.onDecline)"
|
||||
@dismiss="handleToastAction(item, item.toast.onDismiss)"
|
||||
@launch="handleToastAction(item, item.toast.onLaunch)"
|
||||
@open-actor="item.toast.onOpenActor?.()"
|
||||
@open-instance="handleToastAction(item, item.toast.onOpenInstance)"
|
||||
:actor-name="item.actorName"
|
||||
:actor-avatar-url="item.actorAvatarUrl"
|
||||
:entity-name="item.entityName"
|
||||
:entity-icon-url="item.entityIconUrl"
|
||||
:status-text="item.statusText"
|
||||
:progress="item.progress"
|
||||
:waiting="item.waiting"
|
||||
:show-progress="item.showProgress"
|
||||
:progress-type="item.progressType"
|
||||
:progress-current="item.progressCurrent"
|
||||
:progress-total="item.progressTotal"
|
||||
@accept="handleToastAccept(item, item.onAccept)"
|
||||
@decline="handleToastAction(item, item.onDecline)"
|
||||
@dismiss="handleToastAction(item, item.onDismiss)"
|
||||
@launch="handleToastAction(item, item.onLaunch)"
|
||||
@open-actor="item.onOpenActor?.()"
|
||||
@open-instance="handleToastAction(item, item.onOpenInstance)"
|
||||
/>
|
||||
<TransitionGroup
|
||||
v-else-if="isDownloadNotification(item)"
|
||||
@@ -65,6 +65,12 @@
|
||||
/>
|
||||
</div>
|
||||
</TransitionGroup>
|
||||
<component
|
||||
:is="item.component"
|
||||
v-else-if="item.contentType === 'custom'"
|
||||
v-bind="item.componentProps"
|
||||
@dismiss="popupNotificationManager.removeNotification(item.id)"
|
||||
/>
|
||||
<div
|
||||
v-else
|
||||
class="flex w-full flex-col gap-3 overflow-hidden rounded-2xl bg-bg-raised shadow-xl border-surface-5 border-solid border p-4"
|
||||
@@ -105,9 +111,10 @@
|
||||
<IconButton
|
||||
v-if="item.dismissible !== false"
|
||||
type="quiet"
|
||||
label="Close"
|
||||
class="-m-1.5"
|
||||
@click="handleNotificationDismiss(item)"
|
||||
:label="formatMessage(messages.close)"
|
||||
size="sm"
|
||||
class="-m-1.5 -mx-2"
|
||||
@click="handleStandardNotificationDismiss(item)"
|
||||
>
|
||||
<XIcon />
|
||||
</IconButton>
|
||||
@@ -115,11 +122,6 @@
|
||||
<span v-if="item.text" class="text-primary">
|
||||
{{ item.text }}
|
||||
</span>
|
||||
<component
|
||||
:is="item.bodyComponent"
|
||||
v-if="item.bodyComponent"
|
||||
v-bind="item.bodyProps ?? {}"
|
||||
/>
|
||||
</div>
|
||||
<div v-if="item.progressItems?.length" class="flex flex-col gap-3">
|
||||
<div
|
||||
@@ -156,20 +158,8 @@
|
||||
<Button
|
||||
v-for="(btn, idx) in item.buttons"
|
||||
:key="idx"
|
||||
:type="
|
||||
(btn.color || (idx === 0 ? 'brand' : undefined)) &&
|
||||
(btn.color || (idx === 0 ? 'brand' : undefined)) !== 'standard'
|
||||
? 'colored'
|
||||
: 'base'
|
||||
"
|
||||
:color="
|
||||
(btn.color || (idx === 0 ? 'brand' : undefined)) &&
|
||||
(btn.color || (idx === 0 ? 'brand' : undefined)) !== 'standard'
|
||||
? (btn.color || (idx === 0 ? 'brand' : undefined)) === 'medal-promo'
|
||||
? 'medal_promotion'
|
||||
: btn.color || (idx === 0 ? 'brand' : undefined)
|
||||
: undefined
|
||||
"
|
||||
:type="notificationButtonColor(btn, idx) ? 'colored' : 'base'"
|
||||
:color="notificationButtonColor(btn, idx)"
|
||||
@click="handleButtonClick(item.id, btn)"
|
||||
>
|
||||
<component :is="btn.icon" v-if="btn.icon" />
|
||||
@@ -194,18 +184,25 @@ import {
|
||||
} from '@modrinth/assets'
|
||||
import { computed, ref } from 'vue'
|
||||
|
||||
import { Button, IconButton } from '#ui/components/base/buttons'
|
||||
import { Button, type ButtonColor, IconButton } from '#ui/components/base/buttons'
|
||||
|
||||
import { defineMessages, useVIntl } from '../../composables/i18n'
|
||||
import { useModalStack } from '../../composables/modal-stack'
|
||||
import {
|
||||
injectPopupNotificationManager,
|
||||
type PopupNotification,
|
||||
type PopupNotificationButton,
|
||||
type PopupNotificationProgressItem,
|
||||
type PopupNotificationStandard,
|
||||
} from '../../providers'
|
||||
import ProgressBar from '../base/ProgressBar.vue'
|
||||
import NotificationToast from '../notifications/NotificationToast.vue'
|
||||
|
||||
const { formatMessage } = useVIntl()
|
||||
const messages = defineMessages({
|
||||
close: { id: 'notifications.close', defaultMessage: 'Close' },
|
||||
})
|
||||
|
||||
const popupNotificationManager = injectPopupNotificationManager()
|
||||
const notifications = computed<PopupNotification[]>(() =>
|
||||
popupNotificationManager.getVisibleNotifications(),
|
||||
@@ -222,14 +219,25 @@ const setNotificationTimer = (n: PopupNotification) =>
|
||||
popupNotificationManager.setNotificationTimer(n)
|
||||
const toastActionLoading = (id: string | number) => activeToastActions.value[String(id)] ?? null
|
||||
|
||||
function isDownloadNotification(item: PopupNotification) {
|
||||
return (
|
||||
function notificationButtonColor(
|
||||
button: PopupNotificationButton,
|
||||
index: number,
|
||||
): ButtonColor | undefined {
|
||||
const color = button.color ?? (index === 0 ? 'brand' : undefined)
|
||||
return color === 'standard' ? undefined : color
|
||||
}
|
||||
|
||||
function isDownloadNotification(
|
||||
item: PopupNotification,
|
||||
): item is PopupNotificationStandard & { type: 'download' } {
|
||||
return !!(
|
||||
item.contentType === 'standard' &&
|
||||
item.type === 'download' &&
|
||||
(!!item.progressItems?.length || item.progress != null || item.waiting)
|
||||
)
|
||||
}
|
||||
|
||||
function downloadToastItems(item: PopupNotification): PopupNotificationProgressItem[] {
|
||||
function downloadToastItems(item: PopupNotificationStandard): PopupNotificationProgressItem[] {
|
||||
if (item.progressItems?.length) {
|
||||
return popupNotificationManager.getVisibleDownloadProgressItems(item)
|
||||
}
|
||||
@@ -252,7 +260,7 @@ function handleProgressItemDismiss(
|
||||
item: PopupNotification,
|
||||
progressItem: PopupNotificationProgressItem,
|
||||
) {
|
||||
popupNotificationManager.hideDownloadItem(item.id, progressItem.id)
|
||||
popupNotificationManager.hideDownloadItem(item.id, downloadProgressItemId(item, progressItem))
|
||||
}
|
||||
|
||||
async function handleProgressItemAction(
|
||||
@@ -273,10 +281,17 @@ async function handleProgressItemButtonClick(
|
||||
) {
|
||||
await btn.action()
|
||||
if (!btn.keepOpen) {
|
||||
popupNotificationManager.hideDownloadItem(item.id, progressItem.id)
|
||||
popupNotificationManager.hideDownloadItem(item.id, downloadProgressItemId(item, progressItem))
|
||||
}
|
||||
}
|
||||
|
||||
function downloadProgressItemId(
|
||||
item: PopupNotification,
|
||||
progressItem: PopupNotificationProgressItem,
|
||||
): string | undefined {
|
||||
return item.contentType === 'standard' && item.progressItems?.length ? progressItem.id : undefined
|
||||
}
|
||||
|
||||
async function handleButtonClick(id: string | number, btn: PopupNotificationButton) {
|
||||
await btn.action()
|
||||
if (!btn.keepOpen) {
|
||||
@@ -284,7 +299,7 @@ async function handleButtonClick(id: string | number, btn: PopupNotificationButt
|
||||
}
|
||||
}
|
||||
|
||||
async function handleNotificationDismiss(item: PopupNotification) {
|
||||
async function handleStandardNotificationDismiss(item: PopupNotificationStandard) {
|
||||
await item.onDismiss?.()
|
||||
popupNotificationManager.removeNotification(item.id)
|
||||
}
|
||||
@@ -314,7 +329,7 @@ async function handleToastAccept(item: PopupNotification, action?: () => void |
|
||||
}
|
||||
}
|
||||
|
||||
function progressColorForType(type: PopupNotification['type']) {
|
||||
function progressColorForType(type: PopupNotificationStandard['type']) {
|
||||
if (type === 'error') {
|
||||
return 'red'
|
||||
} else if (type === 'warning') {
|
||||
|
||||
@@ -15,47 +15,57 @@
|
||||
<div class="flex min-w-0 flex-1 flex-col gap-2.5">
|
||||
<div class="flex w-full items-start gap-1">
|
||||
<p class="m-0 min-w-0 flex-1 break-words text-lg font-normal leading-6 text-contrast/85">
|
||||
<template v-if="type === 'friend-request'">
|
||||
<span class="font-semibold text-contrast">{{ actorLabel }}</span>
|
||||
<span> sent you a friend request.</span>
|
||||
</template>
|
||||
<template v-else>
|
||||
<button
|
||||
v-if="actorName"
|
||||
type="button"
|
||||
class="m-0 inline border-0 bg-transparent p-0 text-lg font-semibold leading-6 text-contrast hover:underline"
|
||||
@click="$emit('open-actor')"
|
||||
>
|
||||
{{ actorName }}
|
||||
</button>
|
||||
<span v-else class="font-semibold text-contrast">Someone</span>
|
||||
<span class="mx-1">{{ inviteActionText }}</span>
|
||||
<template v-if="type === 'server-invite'">
|
||||
<span class="font-semibold text-contrast">{{ entityLabel }}</span
|
||||
>.
|
||||
<IntlFormatted
|
||||
:message-id="
|
||||
type === 'friend-request'
|
||||
? messages.friendRequest
|
||||
: type === 'server-invite'
|
||||
? messages.serverInvite
|
||||
: messages.instanceInvite
|
||||
"
|
||||
:values="{ actor: actorLabel, entity: entityLabel }"
|
||||
>
|
||||
<template #actor="{ children }">
|
||||
<button
|
||||
v-if="actorName && type !== 'friend-request'"
|
||||
type="button"
|
||||
class="m-0 inline border-0 bg-transparent p-0 text-lg font-semibold leading-6 text-contrast hover:underline"
|
||||
@click="$emit('open-actor')"
|
||||
>
|
||||
<component :is="() => children" />
|
||||
</button>
|
||||
<span v-else class="font-semibold text-contrast">
|
||||
<component :is="() => children" />
|
||||
</span>
|
||||
</template>
|
||||
<template v-else>
|
||||
<Avatar
|
||||
:src="entityIconUrl"
|
||||
:alt="entityLabel"
|
||||
:tint-by="entityLabel"
|
||||
size="28px"
|
||||
no-shadow
|
||||
raised
|
||||
class="inline-block !rounded-lg align-middle"
|
||||
/>
|
||||
<span class="ml-1 font-semibold text-contrast">{{ entityLabel }}</span>
|
||||
<span> instance.</span>
|
||||
<template #entity="{ children }">
|
||||
<template v-if="type === 'instance-invite'">
|
||||
<Avatar
|
||||
:src="entityIconUrl"
|
||||
:alt="entityLabel"
|
||||
:tint-by="entityLabel"
|
||||
size="28px"
|
||||
no-shadow
|
||||
raised
|
||||
class="inline-block !rounded-lg align-middle"
|
||||
/>
|
||||
</template>
|
||||
<span
|
||||
:class="{ 'ml-1': type === 'instance-invite' }"
|
||||
class="font-semibold text-contrast"
|
||||
>
|
||||
<component :is="() => children" />
|
||||
</span>
|
||||
</template>
|
||||
</template>
|
||||
</IntlFormatted>
|
||||
</p>
|
||||
<IconButton
|
||||
v-if="dismissible"
|
||||
type="quiet"
|
||||
size="xs"
|
||||
label="Dismiss notification"
|
||||
size="sm"
|
||||
:label="formatMessage(messages.dismissNotification)"
|
||||
native-type="button"
|
||||
class="notification-toast-dismiss !size-6"
|
||||
class="notification-toast-dismiss -m-1.5"
|
||||
@click="$emit('dismiss')"
|
||||
>
|
||||
<XIcon />
|
||||
@@ -70,11 +80,11 @@
|
||||
>
|
||||
<SpinnerIcon v-if="actionLoading === 'accept'" class="animate-spin" />
|
||||
<CheckIcon v-else />
|
||||
Accept
|
||||
{{ formatMessage(messages.accept) }}
|
||||
</Button>
|
||||
<Button type="outlined" :disabled="actionLoading != null" @click="$emit('decline')">
|
||||
<XIcon />
|
||||
Decline
|
||||
{{ formatMessage(messages.decline) }}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -101,10 +111,10 @@
|
||||
<IconButton
|
||||
v-if="dismissible"
|
||||
type="quiet"
|
||||
size="xs"
|
||||
label="Dismiss notification"
|
||||
size="sm"
|
||||
:label="formatMessage(messages.dismissNotification)"
|
||||
native-type="button"
|
||||
class="notification-toast-dismiss !size-6"
|
||||
class="notification-toast-dismiss -m-1.5"
|
||||
@click="$emit('dismiss')"
|
||||
>
|
||||
<XIcon />
|
||||
@@ -133,8 +143,12 @@
|
||||
class="col-start-1 col-end-3 row-start-3 mt-2 flex min-w-0 items-center justify-between gap-2"
|
||||
>
|
||||
<div class="flex min-w-0 items-center gap-2">
|
||||
<Button type="colored" color="brand" @click="$emit('launch')">Launch game</Button>
|
||||
<Button type="outlined" @click="$emit('open-instance')">Instance</Button>
|
||||
<Button type="colored" color="brand" @click="$emit('launch')">
|
||||
{{ formatMessage(messages.launchGame) }}
|
||||
</Button>
|
||||
<Button type="outlined" @click="$emit('open-instance')">
|
||||
{{ formatMessage(messages.instance) }}
|
||||
</Button>
|
||||
</div>
|
||||
<div v-if="progressLabel" class="notification-inline-progress-label flex-none">
|
||||
{{ progressLabel }}
|
||||
@@ -147,20 +161,8 @@
|
||||
<Button
|
||||
v-for="(action, index) in actions"
|
||||
:key="index"
|
||||
:type="
|
||||
(action.color || (index === 0 ? 'brand' : undefined)) &&
|
||||
(action.color || (index === 0 ? 'brand' : undefined)) !== 'standard'
|
||||
? 'colored'
|
||||
: 'base'
|
||||
"
|
||||
:color="
|
||||
(action.color || (index === 0 ? 'brand' : undefined)) &&
|
||||
(action.color || (index === 0 ? 'brand' : undefined)) !== 'standard'
|
||||
? (action.color || (index === 0 ? 'brand' : undefined)) === 'medal-promo'
|
||||
? 'medal_promotion'
|
||||
: action.color || (index === 0 ? 'brand' : undefined)
|
||||
: undefined
|
||||
"
|
||||
:type="notificationButtonColor(action, index) ? 'colored' : 'base'"
|
||||
:color="notificationButtonColor(action, index)"
|
||||
@click="$emit('action', index)"
|
||||
>
|
||||
<component :is="action.icon" v-if="action.icon" />
|
||||
@@ -193,12 +195,44 @@
|
||||
import { CheckIcon, SpinnerIcon, XIcon } from '@modrinth/assets'
|
||||
import { computed, ref } from 'vue'
|
||||
|
||||
import { Button, IconButton } from '#ui/components/base/buttons'
|
||||
import { Button, type ButtonColor, IconButton } from '#ui/components/base/buttons'
|
||||
|
||||
import { useFormatBytes, useFormatNumber } from '../../composables'
|
||||
import { defineMessages, useVIntl } from '../../composables/i18n'
|
||||
import type { PopupNotificationButton, PopupNotificationProgressType } from '../../providers'
|
||||
import { truncatedTooltip } from '../../utils/truncate'
|
||||
import Avatar from '../base/Avatar.vue'
|
||||
import IntlFormatted from '../base/IntlFormatted.vue'
|
||||
|
||||
const { formatMessage } = useVIntl()
|
||||
const messages = defineMessages({
|
||||
friendRequest: {
|
||||
id: 'notifications.friend-request.body',
|
||||
defaultMessage: '<actor>{actor}</actor> sent you a friend request.',
|
||||
},
|
||||
serverInvite: {
|
||||
id: 'notifications.server-invite.body',
|
||||
defaultMessage:
|
||||
'<actor>{actor}</actor> invited you to manage the server <entity>{entity}</entity>.',
|
||||
},
|
||||
instanceInvite: {
|
||||
id: 'notifications.instance-invite.body',
|
||||
defaultMessage: '<actor>{actor}</actor> invited you to <entity>{entity}</entity> instance.',
|
||||
},
|
||||
dismissNotification: {
|
||||
id: 'notifications.dismiss',
|
||||
defaultMessage: 'Dismiss notification',
|
||||
},
|
||||
accept: { id: 'notifications.invite.accept', defaultMessage: 'Accept' },
|
||||
decline: { id: 'notifications.invite.decline', defaultMessage: 'Decline' },
|
||||
launchGame: { id: 'notifications.instance-ready.launch-game', defaultMessage: 'Launch game' },
|
||||
instance: { id: 'notifications.instance-ready.open-instance', defaultMessage: 'Instance' },
|
||||
someone: { id: 'notifications.actor.unknown', defaultMessage: 'Someone' },
|
||||
installedReady: {
|
||||
id: 'notifications.instance-ready.status',
|
||||
defaultMessage: 'Installed and ready to play.',
|
||||
},
|
||||
})
|
||||
|
||||
type NotificationToastType =
|
||||
| 'friend-request'
|
||||
@@ -258,7 +292,15 @@ const isInviteNotification = computed(
|
||||
props.type === 'instance-invite',
|
||||
)
|
||||
|
||||
const actorLabel = computed(() => props.actorName || 'Someone')
|
||||
function notificationButtonColor(
|
||||
button: PopupNotificationButton,
|
||||
index: number,
|
||||
): ButtonColor | undefined {
|
||||
const color = button.color ?? (index === 0 ? 'brand' : undefined)
|
||||
return color === 'standard' ? undefined : color
|
||||
}
|
||||
|
||||
const actorLabel = computed(() => props.actorName || formatMessage(messages.someone))
|
||||
const entityLabel = computed(() => props.entityName || '')
|
||||
const inviteAvatarUrl = computed(() => props.actorAvatarUrl)
|
||||
const inviteAvatarLabel = computed(() => actorLabel.value)
|
||||
@@ -269,17 +311,9 @@ const isWaitingProgress = computed(() => props.type === 'instance-download' && p
|
||||
const formatBytes = useFormatBytes()
|
||||
const formatNumber = useFormatNumber()
|
||||
|
||||
const inviteActionText = computed(() => {
|
||||
if (props.type === 'server-invite') {
|
||||
return 'invited you to manage the server'
|
||||
}
|
||||
|
||||
return 'invited you to'
|
||||
})
|
||||
|
||||
const resolvedStatusText = computed(() => {
|
||||
if (props.type === 'instance-ready') {
|
||||
return props.statusText ?? 'Installed and ready to play.'
|
||||
return props.statusText ?? formatMessage(messages.installedReady)
|
||||
}
|
||||
|
||||
return props.statusText ?? ''
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
:initial-loader="initialLoader"
|
||||
:initial-game-version="initialGameVersion"
|
||||
:fade="props.initialSetup ? undefined : 'danger'"
|
||||
:search-modpacks="searchModpacks"
|
||||
:search-projects="searchModpacks"
|
||||
:get-project-versions="getProjectVersions"
|
||||
:finish-disabled="!canCompleteSetup"
|
||||
:finish-disabled-tooltip="!canCompleteSetup ? permissionDeniedMessage : undefined"
|
||||
|
||||
@@ -6,7 +6,7 @@ export type {
|
||||
Gamemode,
|
||||
GeneratorSettingsMode,
|
||||
LoaderVersionType,
|
||||
ModpackSearchResult,
|
||||
ProjectSearchResult,
|
||||
SetupType,
|
||||
} from '../../flows/creation-flow-modal/creation-flow-context'
|
||||
export { default as CreationFlowModal } from '../../flows/creation-flow-modal/index.vue'
|
||||
|
||||
|
After Width: | Height: | Size: 551 B |
@@ -1,139 +1,74 @@
|
||||
<template>
|
||||
<div class="grid grid-cols-2 gap-8 items-center justify-center py-10 max-w-[760px]">
|
||||
<!-- Left column -->
|
||||
<div class="flex flex-col gap-8 items-start pr-8 shrink-0">
|
||||
<!-- Heading -->
|
||||
<div class="flex flex-col gap-2 items-start w-[300px]">
|
||||
<p class="text-3xl leading-9 font-semibold text-contrast">
|
||||
{{ formatMessage(messages.modrinthHostingLabel) }}
|
||||
</p>
|
||||
<p class="text-base font-normal text-primary">
|
||||
{{ formatMessage(messages.noServersDescription) }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Feature list -->
|
||||
<div class="flex flex-col gap-4 items-start w-full">
|
||||
<div class="flex gap-3 items-start">
|
||||
<div
|
||||
class="bg-surface-4 border border-solid border-surface-5 rounded-full shrink-0 size-8 flex items-center justify-center"
|
||||
>
|
||||
<PackageOpenIcon class="size-5 text-secondary" aria-hidden="true" />
|
||||
<div class="flex w-full max-w-[56rem] h-full flex-col items-stretch justify-between gap-8 py-6">
|
||||
<div class="flex w-full flex-wrap grow items-center justify-center gap-2">
|
||||
<div class="mx-auto flex w-full max-w-[20rem] flex-col items-start gap-8">
|
||||
<div class="flex flex-col gap-2">
|
||||
<div class="flex items-center gap-3">
|
||||
<h1 class="m-0 text-3xl font-semibold leading-9 text-contrast">
|
||||
{{ formatMessage(messages.modrinthHostingLabel) }}
|
||||
</h1>
|
||||
</div>
|
||||
<div class="flex flex-col gap-0.5">
|
||||
<p class="text-base font-semibold text-contrast">
|
||||
{{ formatMessage(messages.oneClickModInstallsTitle) }}
|
||||
</p>
|
||||
<p class="text-base font-normal text-primary">
|
||||
{{ formatMessage(messages.oneClickModInstallsDescription) }}
|
||||
</p>
|
||||
<p class="m-0 text-base text-primary">
|
||||
{{ formatMessage(messages.noServersDescription) }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="flex w-full flex-col gap-6">
|
||||
<div v-for="feature in features" :key="feature.key" class="flex items-start gap-4">
|
||||
<div
|
||||
class="feature-icon relative flex size-10 shrink-0 items-center justify-center overflow-hidden rounded-[0.875rem] border border-solid bg-surface-1 text-brand"
|
||||
>
|
||||
<div class="feature-icon-gradient absolute left-[-1px] top-[-1px] size-[6.25rem]" />
|
||||
<div class="feature-icon-shade absolute left-[-1px] top-[-1px] size-[6.25rem]" />
|
||||
<img
|
||||
:src="iconTexture"
|
||||
alt=""
|
||||
class="absolute left-1/2 top-1/2 h-[6.25rem] w-[9.8125rem] max-w-none -translate-x-1/2 -translate-y-1/2 object-cover opacity-40 mix-blend-luminosity"
|
||||
/>
|
||||
<component
|
||||
:is="feature.icon"
|
||||
class="feature-icon-glyph relative size-5"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex min-w-0 flex-col gap-0.5">
|
||||
<p class="m-0 text-lg font-semibold leading-6 text-contrast">
|
||||
{{ feature.title }}
|
||||
</p>
|
||||
<p class="m-0 text-sm leading-5 text-primary">
|
||||
{{ feature.description }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-3 items-start">
|
||||
<div
|
||||
class="bg-surface-4 border border-solid border-surface-5 rounded-full shrink-0 size-8 flex items-center justify-center overflow-hidden"
|
||||
>
|
||||
<GlobeIcon class="size-5 text-secondary" aria-hidden="true" />
|
||||
</div>
|
||||
<div class="flex flex-col gap-0.5">
|
||||
<p class="text-base font-semibold text-contrast">
|
||||
{{ formatMessage(messages.simpleSetupTitle) }}
|
||||
</p>
|
||||
<p class="text-base font-normal text-primary">
|
||||
{{ formatMessage(messages.simpleSetupDescription) }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-3 items-start">
|
||||
<div
|
||||
class="bg-surface-4 border border-solid border-surface-5 rounded-full shrink-0 size-8 flex items-center justify-center overflow-hidden"
|
||||
>
|
||||
<UsersIcon class="size-5 text-secondary" aria-hidden="true" />
|
||||
</div>
|
||||
<div class="flex flex-col gap-0.5">
|
||||
<p class="text-base font-semibold text-contrast">
|
||||
{{ formatMessage(messages.playWithFriendsTitle) }}
|
||||
</p>
|
||||
<p class="text-base font-normal text-primary">
|
||||
{{ formatMessage(messages.playWithFriendsDescription) }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- CTA section -->
|
||||
<div class="flex flex-col gap-6 items-start">
|
||||
<div class="flex flex-col gap-3 items-start">
|
||||
<Button type="colored" color="brand" @click="onClickNewServer?.()">
|
||||
<div class="flex flex-wrap items-center gap-4">
|
||||
<Button type="colored" color="brand" size="lg" @click="onClickNewServer?.()">
|
||||
<PlusIcon aria-hidden="true" />
|
||||
{{ formatMessage(messages.newServerButton) }}
|
||||
</Button>
|
||||
|
||||
<AutoLink
|
||||
to="https://modrinth.com/hosting"
|
||||
target="_blank"
|
||||
class="flex items-center gap-1 hover:brightness-125"
|
||||
class="flex items-center gap-1 hover:brightness-125 font-semibold"
|
||||
>
|
||||
{{ formatMessage(messages.learnMoreLink) }}
|
||||
<RightArrowIcon class="size-5 shrink-0" aria-hidden="true" />
|
||||
</AutoLink>
|
||||
</div>
|
||||
|
||||
<template v-if="!loggedIn">
|
||||
<div class="h-px w-full bg-surface-5" />
|
||||
|
||||
<div class="flex gap-3 items-center flex-wrap">
|
||||
<p class="text-base font-normal text-primary">
|
||||
{{ formatMessage(messages.alreadyHaveServerLabel) }}
|
||||
</p>
|
||||
<Button @click="onClickSignIn?.()">
|
||||
<LogInIcon aria-hidden="true" />
|
||||
{{ formatMessage(messages.signInButton) }}
|
||||
</Button>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<ServerListEmptyPreview />
|
||||
</div>
|
||||
|
||||
<!-- Right column - mod icon grid -->
|
||||
<div
|
||||
class="relative flex h-[617px] shrink-0 items-center justify-center overflow-hidden rounded-[40px] pointer-events-none select-none [mask-image:linear-gradient(to_bottom,black_0%,black_35%,transparent_100%)] [-webkit-mask-image:linear-gradient(to_bottom,black_0%,black_35%,transparent_100%)]"
|
||||
>
|
||||
<div class="rotate-[15deg]">
|
||||
<div class="flex flex-col gap-4">
|
||||
<div
|
||||
v-for="row in GRID_ROWS"
|
||||
:key="row"
|
||||
class="flex gap-4 items-center shrink-0"
|
||||
:class="animated ? (row % 2 === 1 ? 'drift-left' : 'drift-right relative left-14') : ''"
|
||||
>
|
||||
<div class="hidden drift-right drift-left"></div>
|
||||
<div
|
||||
v-for="col in GRID_COLS"
|
||||
:key="col"
|
||||
class="border border-surface-5 rounded-[20px] shrink-0 size-[112px] bg-surface-4 overflow-hidden"
|
||||
>
|
||||
<img :src="getGridImage(row - 1, col - 1)" alt="" class="size-full object-cover" />
|
||||
</div>
|
||||
<div
|
||||
v-for="col in GRID_COLS"
|
||||
:key="col"
|
||||
class="border border-surface-5 rounded-[20px] shrink-0 size-[112px] bg-surface-4 overflow-hidden"
|
||||
>
|
||||
<img :src="getGridImage(row - 1, col - 1)" alt="" class="size-full object-cover" />
|
||||
</div>
|
||||
<div
|
||||
v-for="col in GRID_COLS"
|
||||
:key="col"
|
||||
class="border border-surface-5 rounded-[20px] shrink-0 size-[112px] bg-surface-4 overflow-hidden"
|
||||
>
|
||||
<img :src="getGridImage(row - 1, col - 1)" alt="" class="size-full object-cover" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="!loggedIn" class="flex flex-col items-center gap-4 text-center">
|
||||
<p class="m-0 text-sm text-secondary">
|
||||
{{ formatMessage(messages.alreadyHaveServerLabel) }}
|
||||
</p>
|
||||
<Button @click="onClickSignIn?.()">
|
||||
<LogInIcon aria-hidden="true" />
|
||||
{{ formatMessage(messages.signInButton) }}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -147,46 +82,21 @@ import {
|
||||
RightArrowIcon,
|
||||
UsersIcon,
|
||||
} from '@modrinth/assets'
|
||||
import { AutoLink } from '@modrinth/ui'
|
||||
import { computed } from 'vue'
|
||||
|
||||
import iconTexture from '#ui/assets/welcome/icon-texture.png'
|
||||
import AutoLink from '#ui/components/base/AutoLink.vue'
|
||||
import { Button } from '#ui/components/base/buttons'
|
||||
|
||||
import { defineMessages, useVIntl } from '../../../composables/i18n'
|
||||
import imgAircraft from './grid-images/aircraft.png'
|
||||
import imgAlexs from "./grid-images/alex's.png"
|
||||
import imgArtifacts from './grid-images/artifacts.png'
|
||||
import imgBiomes from './grid-images/biomes.png'
|
||||
import imgCatac from './grid-images/catac.png'
|
||||
import imgCobble from './grid-images/cobble.png'
|
||||
import imgComforts from './grid-images/comforts.png'
|
||||
import imgCreate from './grid-images/create.png'
|
||||
import imgCreate1 from './grid-images/create1.png'
|
||||
import imgCreate2 from './grid-images/create2.png'
|
||||
import imgCreate3 from './grid-images/create3.png'
|
||||
import imgCreeper from './grid-images/creeper.png'
|
||||
import imgFriends from './grid-images/friends.png'
|
||||
import imgGeo from './grid-images/geo.png'
|
||||
import imgNaturalist from './grid-images/naturalist.png'
|
||||
import imgSeasons from './grid-images/seasons.png'
|
||||
import imgTravellers from './grid-images/travellers.png'
|
||||
import imgTree from './grid-images/tree.png'
|
||||
import imgYum1 from './grid-images/yum1.png'
|
||||
import imgYum2 from './grid-images/yum2.png'
|
||||
import imgYum3 from './grid-images/yum3.png'
|
||||
import imgYung from './grid-images/yung.png'
|
||||
import ServerListEmptyPreview from './ServerListEmptyPreview.vue'
|
||||
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
animated?: boolean
|
||||
onClickNewServer?: () => void
|
||||
onClickSignIn?: () => void
|
||||
loggedIn?: boolean
|
||||
}>(),
|
||||
{ animated: false },
|
||||
)
|
||||
defineProps<{
|
||||
onClickNewServer?: () => void
|
||||
onClickSignIn?: () => void
|
||||
loggedIn?: boolean
|
||||
}>()
|
||||
|
||||
const GRID_ROWS = 6
|
||||
const GRID_COLS = 5
|
||||
const { formatMessage } = useVIntl()
|
||||
|
||||
const messages = defineMessages({
|
||||
@@ -194,10 +104,6 @@ const messages = defineMessages({
|
||||
id: 'servers.list-empty.modrinth-hosting-label',
|
||||
defaultMessage: 'Modrinth Hosting',
|
||||
},
|
||||
noServersTitle: {
|
||||
id: 'servers.list-empty.no-servers-title',
|
||||
defaultMessage: 'No servers yet',
|
||||
},
|
||||
noServersDescription: {
|
||||
id: 'servers.list-empty.no-servers-description',
|
||||
defaultMessage: 'Install mods, invite friends, and play together all from the Modrinth App.',
|
||||
@@ -224,7 +130,7 @@ const messages = defineMessages({
|
||||
},
|
||||
playWithFriendsDescription: {
|
||||
id: 'servers.list-empty.play-with-friends-description',
|
||||
defaultMessage: 'Invite friends and get them set up right in the Modrinth App.',
|
||||
defaultMessage: 'Invite friends to play your server with one-click play from the Modrinth App.',
|
||||
},
|
||||
newServerButton: {
|
||||
id: 'servers.list-empty.new-server-button',
|
||||
@@ -232,7 +138,7 @@ const messages = defineMessages({
|
||||
},
|
||||
learnMoreLink: {
|
||||
id: 'servers.list-empty.learn-more-link',
|
||||
defaultMessage: 'Learn more about Modrinth Hosting',
|
||||
defaultMessage: 'Learn more',
|
||||
},
|
||||
alreadyHaveServerLabel: {
|
||||
id: 'servers.list-empty.already-have-server-label',
|
||||
@@ -240,70 +146,55 @@ const messages = defineMessages({
|
||||
},
|
||||
signInButton: {
|
||||
id: 'servers.list-empty.sign-in-button',
|
||||
defaultMessage: 'Sign in',
|
||||
defaultMessage: 'Sign in to Modrinth',
|
||||
},
|
||||
})
|
||||
|
||||
const GRID_IMAGES = [
|
||||
imgYum1,
|
||||
imgYum2,
|
||||
imgYum3,
|
||||
imgYung,
|
||||
imgCreeper,
|
||||
imgFriends,
|
||||
imgNaturalist,
|
||||
imgBiomes,
|
||||
imgCatac,
|
||||
imgCobble,
|
||||
imgGeo,
|
||||
imgCreate,
|
||||
imgCreate1,
|
||||
imgCreate2,
|
||||
imgCreate3,
|
||||
imgAircraft,
|
||||
imgArtifacts,
|
||||
imgComforts,
|
||||
imgTravellers,
|
||||
imgAlexs,
|
||||
imgSeasons,
|
||||
imgTree,
|
||||
]
|
||||
|
||||
function getGridImage(row: number, col: number): string {
|
||||
return GRID_IMAGES[(row * GRID_COLS + col) % GRID_IMAGES.length]
|
||||
}
|
||||
const features = computed(() => [
|
||||
{
|
||||
key: 'one-click-mod-installs',
|
||||
icon: PackageOpenIcon,
|
||||
title: formatMessage(messages.oneClickModInstallsTitle),
|
||||
description: formatMessage(messages.oneClickModInstallsDescription),
|
||||
},
|
||||
{
|
||||
key: 'simple-setup',
|
||||
icon: GlobeIcon,
|
||||
title: formatMessage(messages.simpleSetupTitle),
|
||||
description: formatMessage(messages.simpleSetupDescription),
|
||||
},
|
||||
{
|
||||
key: 'play-with-friends',
|
||||
icon: UsersIcon,
|
||||
title: formatMessage(messages.playWithFriendsTitle),
|
||||
description: formatMessage(messages.playWithFriendsDescription),
|
||||
},
|
||||
])
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
p {
|
||||
margin: 0;
|
||||
.feature-icon {
|
||||
border-color: color-mix(in srgb, var(--color-text-primary) 10%, transparent);
|
||||
box-shadow:
|
||||
0 0 0 1px color-mix(in srgb, var(--color-brand) 30%, var(--surface-1)),
|
||||
var(--shadow-card),
|
||||
0 0 3.75rem color-mix(in srgb, var(--color-brand) 10%, transparent);
|
||||
}
|
||||
|
||||
@keyframes drift-right {
|
||||
from {
|
||||
transform: translateX(-33%);
|
||||
}
|
||||
to {
|
||||
transform: translateX(33%);
|
||||
}
|
||||
.feature-icon-gradient {
|
||||
background: linear-gradient(180deg, var(--color-green-800) 0%, var(--color-green-950) 100%);
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
@keyframes drift-left {
|
||||
from {
|
||||
transform: translateX(33%);
|
||||
}
|
||||
to {
|
||||
transform: translateX(-33%);
|
||||
}
|
||||
.feature-icon-shade {
|
||||
background: linear-gradient(
|
||||
-14deg,
|
||||
color-mix(in srgb, var(--color-green-950) 37%, transparent) 8%,
|
||||
transparent 86%
|
||||
);
|
||||
}
|
||||
|
||||
.drift-left {
|
||||
animation: drift-left linear infinite alternate;
|
||||
animation-duration: 400s;
|
||||
}
|
||||
|
||||
.drift-right {
|
||||
animation: drift-right linear infinite alternate;
|
||||
animation-duration: 400s;
|
||||
.feature-icon-glyph {
|
||||
filter: drop-shadow(0 1px 1px color-mix(in srgb, var(--color-green-950) 12%, transparent));
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,245 @@
|
||||
<template>
|
||||
<div
|
||||
inert
|
||||
aria-hidden="true"
|
||||
class="relative mx-auto h-[38rem] w-full max-w-[25rem] select-none -mb-10"
|
||||
>
|
||||
<div
|
||||
class="absolute inset-x-0 top-0 h-full overflow-hidden rounded-2xl border border-solid border-surface-3 bg-surface-2 shadow-card"
|
||||
>
|
||||
<div
|
||||
class="flex items-center justify-between border-0 border-b border-solid border-surface-3 p-4"
|
||||
>
|
||||
<div class="flex items-center gap-2 text-contrast">
|
||||
<UserPlusIcon class="size-5 text-primary" />
|
||||
<span class="text-lg font-semibold">{{ formatMessage(messages.heading) }}</span>
|
||||
</div>
|
||||
<XIcon class="size-5 text-secondary" />
|
||||
</div>
|
||||
|
||||
<div class="flex gap-2 border-0 border-b border-solid border-surface-3 p-4">
|
||||
<div
|
||||
class="flex h-9 min-w-0 flex-1 items-center gap-2 rounded-xl bg-surface-3 px-3 text-sm text-secondary"
|
||||
>
|
||||
<SearchIcon class="size-4 shrink-0" />
|
||||
<span class="truncate text-secondary">{{
|
||||
formatMessage(messages.searchPlaceholder)
|
||||
}}</span>
|
||||
</div>
|
||||
<Button type="colored" color="brand" class="!cursor-default" disabled>
|
||||
<PlusIcon aria-hidden="true" />
|
||||
{{ formatMessage(messages.addButton) }}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div class="bg-surface-1 py-3">
|
||||
<p class="m-0 pb-2 text-sm font-semibold text-primary px-4">
|
||||
{{ formatMessage(messages.friendsHeading, { count: totalFriendCount }) }}
|
||||
</p>
|
||||
<div
|
||||
v-for="(friend, index) in friends"
|
||||
:key="friend.username"
|
||||
class="relative flex h-11 items-center justify-between gap-3 px-4"
|
||||
:class="[index > 4 ? 'opacity-40' : '', friend.showPointer ? 'bg-surface-2' : '']"
|
||||
>
|
||||
<div class="flex min-w-0 items-center gap-2">
|
||||
<div class="relative flex shrink-0">
|
||||
<Avatar :src="friend.avatarUrl" size="1.5rem" class="!border-none" circle no-shadow />
|
||||
<span
|
||||
v-if="friend.presence"
|
||||
class="absolute bottom-0 right-[-1px] size-2 rounded-full border border-solid border-surface-1"
|
||||
:class="friend.presence === 'online' ? 'bg-brand' : 'bg-blue'"
|
||||
/>
|
||||
</div>
|
||||
<span class="truncate text-base font-medium text-primary">{{ friend.username }}</span>
|
||||
</div>
|
||||
<Button
|
||||
:type="friend.status === 'cancel' ? 'outlined' : 'base'"
|
||||
class="!cursor-default !font-medium !h-8"
|
||||
size="md"
|
||||
:disabled="friend.status === 'added'"
|
||||
:class="friend.status === 'added' ? '' : 'w-20'"
|
||||
>
|
||||
<CheckIcon v-if="friend.status === 'added'" aria-hidden="true" />
|
||||
{{ friendStatusLabel(friend.status) }}
|
||||
</Button>
|
||||
<div
|
||||
v-if="friend.showPointer"
|
||||
class="absolute right-[14.25rem] top-9 z-10 flex size-8 -translate-y-1/2 items-center justify-center rounded-full bg-white/10 opacity-75"
|
||||
>
|
||||
<img :src="pointerIcon" alt="" class="size-4" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="absolute inset-x-0 bottom-0 border-0 border-t border-solid border-surface-3 bg-surface-2 p-4"
|
||||
>
|
||||
<p class="m-0 pb-2 text-base font-semibold text-contrast">
|
||||
{{ formatMessage(messages.inviteLinkHeading) }}
|
||||
</p>
|
||||
<div
|
||||
class="flex h-8 items-center justify-between gap-2 rounded-xl bg-surface-1 px-2.5 text-sm font-medium text-primary"
|
||||
>
|
||||
<span class="truncate">https://modrinth.com/server/abc123</span>
|
||||
<ClipboardCopyIcon class="size-4 shrink-0 text-secondary" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="pointer-events-none absolute inset-x-0 bottom-0 h-[28rem] [background:linear-gradient(to_bottom,transparent,var(--surface-1))]"
|
||||
/>
|
||||
|
||||
<div
|
||||
class="absolute left-[32%] top-[23rem] z-10 flex w-[21rem] max-w-[calc(100%-1rem)] gap-4 !overflow-hidden rounded-2xl border border-solid border-surface-4 bg-surface-2 px-4 py-3 shadow-card"
|
||||
>
|
||||
<div
|
||||
class="absolute top-[16px] left-[16px] size-[20px] rounded-full bg-contrast opacity-75 blur-xl"
|
||||
/>
|
||||
<div class="relative h-max">
|
||||
<Avatar :src="geometricallyAvatar" size="2.25rem" circle no-shadow />
|
||||
<span
|
||||
class="absolute bottom-0 right-[-1px] size-3 rounded-full border-2 border-solid border-surface-2 bg-brand"
|
||||
/>
|
||||
</div>
|
||||
<div class="min-w-0 flex-1">
|
||||
<div class="flex items-start gap-1">
|
||||
<div class="min-w-0 flex-1 text-base leading-5 text-primary">
|
||||
<p class="m-0">
|
||||
<span class="font-medium text-contrast">Geometrically</span>
|
||||
{{ formatMessage(messages.invitedYouTo) }}
|
||||
</p>
|
||||
<p class="m-0 flex items-center gap-1">
|
||||
<Avatar :src="modrinthSmpIcon" size="1.25rem" no-shadow />
|
||||
<span class="font-medium text-contrast">Modrinth SMP</span>
|
||||
<span>{{ formatMessage(messages.serverSuffix) }}</span>
|
||||
</p>
|
||||
</div>
|
||||
<XIcon class="size-5 shrink-0 text-secondary" />
|
||||
</div>
|
||||
<div class="mt-2.5 flex gap-2">
|
||||
<Button type="colored" color="brand" class="!cursor-default !font-medium">
|
||||
{{ formatMessage(messages.acceptButton) }}
|
||||
</Button>
|
||||
<Button type="outlined" class="!cursor-default !font-medium">
|
||||
{{ formatMessage(messages.declineButton) }}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
CheckIcon,
|
||||
ClipboardCopyIcon,
|
||||
PlusIcon,
|
||||
SearchIcon,
|
||||
UserPlusIcon,
|
||||
XIcon,
|
||||
} from '@modrinth/assets'
|
||||
|
||||
import borisAvatar from '#ui/assets/servers/server-list-empty/boris.png'
|
||||
import fetchAvatar from '#ui/assets/servers/server-list-empty/fetch.png'
|
||||
import geometricallyAvatar from '#ui/assets/servers/server-list-empty/geometrically.png'
|
||||
import imb11Avatar from '#ui/assets/servers/server-list-empty/imb11.png'
|
||||
import joshAvatar from '#ui/assets/servers/server-list-empty/josh.png'
|
||||
import michaelAvatar from '#ui/assets/servers/server-list-empty/michael.png'
|
||||
import modrinthSmpIcon from '#ui/assets/servers/server-list-empty/modrinth-smp.png'
|
||||
import prospectorAvatar from '#ui/assets/servers/server-list-empty/prospector.png'
|
||||
import sayaAvatar from '#ui/assets/servers/server-list-empty/saya.png'
|
||||
import trumanAvatar from '#ui/assets/servers/server-list-empty/truman.png'
|
||||
import Avatar from '#ui/components/base/Avatar.vue'
|
||||
import Button from '#ui/components/base/buttons/Button.vue'
|
||||
|
||||
import { defineMessages, useVIntl } from '../../../composables/i18n'
|
||||
import pointerIcon from './Pointer.png'
|
||||
|
||||
type FriendStatus = 'added' | 'cancel' | 'invite'
|
||||
type FriendPresence = 'online' | 'playing'
|
||||
|
||||
const { formatMessage } = useVIntl()
|
||||
|
||||
const messages = defineMessages({
|
||||
heading: {
|
||||
id: 'sharing.invite-players-modal.heading',
|
||||
defaultMessage: 'Invite players',
|
||||
},
|
||||
searchPlaceholder: {
|
||||
id: 'sharing.invite-players-modal.search-placeholder',
|
||||
defaultMessage: 'Enter Modrinth username',
|
||||
},
|
||||
addButton: {
|
||||
id: 'sharing.invite-players-modal.add',
|
||||
defaultMessage: 'Add',
|
||||
},
|
||||
friendsHeading: {
|
||||
id: 'sharing.invite-players-modal.friends-heading',
|
||||
defaultMessage: 'Your friends - {count}',
|
||||
},
|
||||
addedButton: {
|
||||
id: 'sharing.invite-players-modal.added',
|
||||
defaultMessage: 'Added',
|
||||
},
|
||||
cancelButton: {
|
||||
id: 'sharing.invite-players-modal.cancel',
|
||||
defaultMessage: 'Cancel',
|
||||
},
|
||||
inviteButton: {
|
||||
id: 'sharing.invite-players-modal.invite',
|
||||
defaultMessage: 'Invite',
|
||||
},
|
||||
inviteLinkHeading: {
|
||||
id: 'sharing.invite-players-modal.invite-link-heading',
|
||||
defaultMessage: 'Or use an invite link',
|
||||
},
|
||||
invitedYouTo: {
|
||||
id: 'sharing.invite-players-toast.invited-you-to',
|
||||
defaultMessage: 'invited you to',
|
||||
},
|
||||
serverSuffix: {
|
||||
id: 'sharing.invite-players-toast.server-suffix',
|
||||
defaultMessage: 'server.',
|
||||
},
|
||||
acceptButton: {
|
||||
id: 'sharing.invite-players-toast.accept',
|
||||
defaultMessage: 'Accept',
|
||||
},
|
||||
declineButton: {
|
||||
id: 'sharing.invite-players-toast.decline',
|
||||
defaultMessage: 'Decline',
|
||||
},
|
||||
})
|
||||
|
||||
const friends: Array<{
|
||||
username: string
|
||||
avatarUrl: string
|
||||
status: FriendStatus
|
||||
presence?: FriendPresence
|
||||
showPointer?: boolean
|
||||
}> = [
|
||||
{ username: 'Josh', avatarUrl: joshAvatar, status: 'added' },
|
||||
{
|
||||
username: 'Prospector',
|
||||
avatarUrl: prospectorAvatar,
|
||||
status: 'invite',
|
||||
presence: 'online',
|
||||
showPointer: true,
|
||||
},
|
||||
{ username: 'Fetch', avatarUrl: fetchAvatar, status: 'cancel', presence: 'playing' },
|
||||
{ username: 'IMB11', avatarUrl: imb11Avatar, status: 'invite' },
|
||||
{ username: 'Truman', avatarUrl: trumanAvatar, status: 'invite' },
|
||||
{ username: 'Boris', avatarUrl: borisAvatar, status: 'invite' },
|
||||
{ username: 'Saya', avatarUrl: sayaAvatar, status: 'invite' },
|
||||
{ username: 'Michael', avatarUrl: michaelAvatar, status: 'invite' },
|
||||
]
|
||||
|
||||
const totalFriendCount = 11
|
||||
|
||||
function friendStatusLabel(status: FriendStatus): string {
|
||||
if (status === 'added') return formatMessage(messages.addedButton)
|
||||
if (status === 'cancel') return formatMessage(messages.cancelButton)
|
||||
return formatMessage(messages.inviteButton)
|
||||
}
|
||||
</script>
|
||||
|
Before Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 704 B |
|
Before Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 990 B |
|
Before Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 35 KiB |
|
Before Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 15 KiB |
@@ -1,6 +1,15 @@
|
||||
<script setup lang="ts">
|
||||
import { CheckIcon } from '@modrinth/assets'
|
||||
import { ref, watch } from 'vue'
|
||||
|
||||
import { defineMessages, useVIntl } from '../../composables/i18n'
|
||||
|
||||
const { formatMessage } = useVIntl()
|
||||
const messages = defineMessages({
|
||||
selectNamedSkin: { id: 'skins.select-named', defaultMessage: 'Select {name}' },
|
||||
selectSkin: { id: 'skins.select', defaultMessage: 'Select skin' },
|
||||
})
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'select'): void
|
||||
(e: 'edit', event: MouseEvent): void
|
||||
@@ -63,7 +72,11 @@ watch(
|
||||
|
||||
<button
|
||||
class="absolute inset-0 z-10 cursor-pointer border-none bg-transparent p-0 focus-visible:outline-none"
|
||||
:aria-label="tooltip ? `Select ${tooltip}` : 'Select skin'"
|
||||
:aria-label="
|
||||
tooltip
|
||||
? formatMessage(messages.selectNamedSkin, { name: tooltip })
|
||||
: formatMessage(messages.selectSkin)
|
||||
"
|
||||
:aria-pressed="selected"
|
||||
:disabled="disabled"
|
||||
@click="emit('select')"
|
||||
@@ -74,6 +87,14 @@ watch(
|
||||
class="pointer-events-none absolute right-3 top-3 z-20 size-3 rounded-full border-2 border-solid border-surface-3 bg-green"
|
||||
></span>
|
||||
|
||||
<span
|
||||
v-if="selected"
|
||||
class="pointer-events-none absolute right-3 top-3 z-20 flex size-6 items-center justify-center rounded-full"
|
||||
>
|
||||
<span class="absolute inset-0 rounded-full bg-contrast"></span>
|
||||
<CheckIcon class="relative size-4 invert [stroke-width:3]" />
|
||||
</span>
|
||||
|
||||
<div v-if="!imagesLoaded.forward" class="skeleton-loader h-full w-full">
|
||||
<div class="skeleton absolute inset-0 aspect-[5/7]"></div>
|
||||
</div>
|
||||
@@ -109,22 +130,23 @@ watch(
|
||||
}
|
||||
|
||||
.skeleton {
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
var(--color-bg) 25%,
|
||||
var(--color-raised-bg) 50%,
|
||||
var(--color-bg) 75%
|
||||
);
|
||||
background-size: 200% 100%;
|
||||
animation: wave 1500ms infinite linear;
|
||||
background: linear-gradient(145deg, var(--surface-2), var(--surface-3));
|
||||
animation: skeleton-pulse 2700ms ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes wave {
|
||||
0% {
|
||||
background-position: -200% 0;
|
||||
}
|
||||
@keyframes skeleton-pulse {
|
||||
0%,
|
||||
100% {
|
||||
background-position: 200% 0;
|
||||
filter: brightness(0.95);
|
||||
}
|
||||
50% {
|
||||
filter: brightness(1.15);
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.skeleton {
|
||||
animation: none;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -168,13 +190,10 @@ watch(
|
||||
.skin-button.skin-button--selected:hover,
|
||||
.skin-button.skin-button--selected:focus-within,
|
||||
.skin-button.skin-button--selected.skin-button--with-actions:hover,
|
||||
.skin-button.skin-button--selected.skin-button--with-actions:focus-within,
|
||||
.skin-button.skin-button--active:hover,
|
||||
.skin-button.skin-button--active:focus-within,
|
||||
.skin-button.skin-button--active.skin-button--with-actions:hover,
|
||||
.skin-button.skin-button--active.skin-button--with-actions:focus-within {
|
||||
border-color: var(--color-brand);
|
||||
background: var(--color-brand-highlight);
|
||||
.skin-button.skin-button--selected.skin-button--with-actions:focus-within {
|
||||
border-color: color-mix(in srgb, var(--color-text-primary) 40%, transparent);
|
||||
background: var(--surface-3);
|
||||
filter: brightness(1.1);
|
||||
}
|
||||
|
||||
.skin-button--disabled {
|
||||
|
||||
@@ -74,9 +74,13 @@
|
||||
class="flex-1"
|
||||
/>
|
||||
<IconButton
|
||||
v-tooltip="`${hideUninstallable ? 'Show' : 'Hide'} unavailable`"
|
||||
v-tooltip="
|
||||
formatMessage(hideUninstallable ? messages.showUnavailable : messages.hideUnavailable)
|
||||
"
|
||||
type="outlined"
|
||||
:label="`${hideUninstallable ? 'Show' : 'Hide'} unavailable`"
|
||||
:label="
|
||||
formatMessage(hideUninstallable ? messages.showUnavailable : messages.hideUnavailable)
|
||||
"
|
||||
@click="hideUninstallable = !hideUninstallable"
|
||||
>
|
||||
<EyeOffIcon v-if="hideUninstallable" />
|
||||
@@ -139,16 +143,42 @@
|
||||
|
||||
<!-- New instance tab -->
|
||||
<div v-else class="flex flex-col gap-6 p-6">
|
||||
<div class="flex items-center gap-4">
|
||||
<Avatar :src="iconPreviewUrl ?? undefined" size="5rem" rounded="2xl" />
|
||||
<div class="flex flex-col gap-2">
|
||||
<div class="flex items-center gap-2.5">
|
||||
<div class="group relative size-[7.75rem] shrink-0">
|
||||
<Avatar :src="iconPreviewUrl ?? undefined" size="100%" no-shadow />
|
||||
<div
|
||||
v-if="iconPreviewUrl"
|
||||
class="pointer-events-none absolute right-1.5 top-1.5 opacity-0 transition-opacity group-focus-within:pointer-events-auto group-focus-within:opacity-100 group-hover:pointer-events-auto group-hover:opacity-100"
|
||||
>
|
||||
<Button
|
||||
size="sm"
|
||||
class="!p-2"
|
||||
:aria-label="formatMessage(commonMessages.removeImageButton)"
|
||||
@click="removeIcon"
|
||||
>
|
||||
<XIcon />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-col gap-1.5">
|
||||
<Button type="outlined" @click="selectIcon">
|
||||
<UploadIcon />
|
||||
{{ formatMessage(messages.selectIcon) }}
|
||||
</Button>
|
||||
<Button type="outlined" :disabled="!iconPreviewUrl" @click="removeIcon">
|
||||
<XIcon />
|
||||
{{ formatMessage(messages.removeIcon) }}
|
||||
<Button
|
||||
v-if="props.randomizeIcon"
|
||||
type="outlined"
|
||||
:disabled="randomizing"
|
||||
class="disabled:!cursor-default"
|
||||
@click="randomizeInstanceIcon"
|
||||
>
|
||||
<SpinnerIcon v-if="randomizing" class="animate-spin" />
|
||||
<RefreshCwIcon v-else />
|
||||
{{ formatMessage(messages.randomizeIcon) }}
|
||||
</Button>
|
||||
<Button v-if="props.customizeIcon" type="outlined" @click="props.customizeIcon?.()">
|
||||
<PaletteIcon />
|
||||
{{ formatMessage(messages.customizeIcon) }}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -245,7 +275,10 @@ import {
|
||||
DownloadIcon,
|
||||
EyeIcon,
|
||||
EyeOffIcon,
|
||||
PaletteIcon,
|
||||
RefreshCwIcon,
|
||||
SearchIcon,
|
||||
SpinnerIcon,
|
||||
TriangleAlertIcon,
|
||||
UploadIcon,
|
||||
XIcon,
|
||||
@@ -288,6 +321,14 @@ const messages = defineMessages({
|
||||
id: 'instances.content-install.search-placeholder',
|
||||
defaultMessage: 'Search instance',
|
||||
},
|
||||
showUnavailable: {
|
||||
id: 'instances.content-install.show-unavailable',
|
||||
defaultMessage: 'Show unavailable',
|
||||
},
|
||||
hideUnavailable: {
|
||||
id: 'instances.content-install.hide-unavailable',
|
||||
defaultMessage: 'Hide unavailable',
|
||||
},
|
||||
installedBadge: {
|
||||
id: 'instances.content-install.installed-badge',
|
||||
defaultMessage: 'Installed',
|
||||
@@ -302,12 +343,16 @@ const messages = defineMessages({
|
||||
'This instance uses a different loader or game version than this project supports.',
|
||||
},
|
||||
selectIcon: {
|
||||
id: 'instances.content-install.select-icon',
|
||||
defaultMessage: 'Select icon',
|
||||
id: 'creation-flow.modal.custom-setup.icon.select',
|
||||
defaultMessage: 'Upload',
|
||||
},
|
||||
removeIcon: {
|
||||
id: 'instances.content-install.remove-icon',
|
||||
defaultMessage: 'Remove icon',
|
||||
randomizeIcon: {
|
||||
id: 'creation-flow.modal.custom-setup.icon.randomize',
|
||||
defaultMessage: 'Randomize',
|
||||
},
|
||||
customizeIcon: {
|
||||
id: 'creation-flow.modal.custom-setup.icon.customize',
|
||||
defaultMessage: 'Customize',
|
||||
},
|
||||
nameLabel: {
|
||||
id: 'instances.content-install.name-label',
|
||||
@@ -368,6 +413,8 @@ const props = defineProps<{
|
||||
preferredLoader?: string | null
|
||||
preferredGameVersion?: string | null
|
||||
projectInfo?: ContentInstallProjectInfo | null
|
||||
randomizeIcon?: () => Promise<{ path: string; previewUrl: string } | null>
|
||||
customizeIcon?: () => void
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
@@ -424,6 +471,7 @@ const selectedLoader = ref<string | null>(null)
|
||||
const selectedGameVersion = ref<string | null>(null)
|
||||
const iconPath = ref<string | null>(null)
|
||||
const iconPreviewUrl = ref<string | null>(null)
|
||||
const randomizing = ref(false)
|
||||
const showSnapshots = ref(false)
|
||||
|
||||
const hasReleaseData = computed(
|
||||
@@ -454,6 +502,20 @@ function removeIcon() {
|
||||
iconPreviewUrl.value = null
|
||||
}
|
||||
|
||||
async function randomizeInstanceIcon() {
|
||||
if (!props.randomizeIcon || randomizing.value) return
|
||||
|
||||
randomizing.value = true
|
||||
try {
|
||||
const generated = await props.randomizeIcon()
|
||||
if (!generated) return
|
||||
iconPath.value = generated.path
|
||||
iconPreviewUrl.value = generated.previewUrl
|
||||
} finally {
|
||||
randomizing.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function resetState() {
|
||||
tab.value = props.defaultTab ?? 'existing'
|
||||
searchFilter.value = ''
|
||||
@@ -461,6 +523,7 @@ function resetState() {
|
||||
instanceName.value = `New instance (${props.instances.length + 1})`
|
||||
iconPath.value = null
|
||||
iconPreviewUrl.value = null
|
||||
void randomizeInstanceIcon()
|
||||
selectedLoader.value = props.preferredLoader ?? props.compatibleLoaders[0] ?? null
|
||||
|
||||
const preferred = props.preferredGameVersion
|
||||
@@ -497,6 +560,11 @@ function hide() {
|
||||
modal.value?.hide()
|
||||
}
|
||||
|
||||
function setIcon(path: string, previewUrl: string) {
|
||||
iconPath.value = path
|
||||
iconPreviewUrl.value = previewUrl
|
||||
}
|
||||
|
||||
function handleCreateAndInstall() {
|
||||
if (!instanceName.value || !selectedLoader.value || !selectedGameVersion.value) return
|
||||
emit('create-and-install', {
|
||||
@@ -509,5 +577,5 @@ function handleCreateAndInstall() {
|
||||
hide()
|
||||
}
|
||||
|
||||
defineExpose({ show, hide })
|
||||
defineExpose({ show, hide, setIcon })
|
||||
</script>
|
||||
|
||||
@@ -65,7 +65,7 @@
|
||||
type="server-onboarding"
|
||||
:available-loaders="['vanilla', 'fabric', 'neoforge', 'forge', 'quilt', 'paper', 'purpur']"
|
||||
:show-snapshot-toggle="true"
|
||||
:search-modpacks="searchModpacks"
|
||||
:search-projects="searchModpacks"
|
||||
:get-project-versions="getProjectVersions"
|
||||
:finish-disabled="!canSetup"
|
||||
:finish-disabled-tooltip="!canSetup ? permissionDeniedMessage : undefined"
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
<template>
|
||||
<div
|
||||
data-pyro-server-list-root
|
||||
class="relative mx-auto mb-6 flex w-full flex-col p-6"
|
||||
:class="serverList.length ? 'min-h-screen' : 'min-h-[calc(100vh-14.5rem)]'"
|
||||
class="relative mx-auto flex w-full flex-col p-6"
|
||||
:class="
|
||||
serverList.length && !showEmptyState
|
||||
? 'min-h-screen mb-6'
|
||||
: 'min-h-[calc(100vh-14.5rem)] h-full py-0'
|
||||
"
|
||||
>
|
||||
<ServersGuestPlanModal
|
||||
ref="guestPlanModal"
|
||||
@@ -134,7 +138,7 @@
|
||||
<div
|
||||
v-else-if="showEmptyState"
|
||||
key="empty"
|
||||
class="flex h-full flex-col items-center justify-center gap-8 grow max-h-[1100px]"
|
||||
class="flex h-full flex-col items-center justify-center gap-8 grow"
|
||||
>
|
||||
<ServerListEmpty
|
||||
:logged-in="loggedIn"
|
||||
@@ -332,6 +336,10 @@ const messages = defineMessages({
|
||||
id: 'servers.manage.handle-error.title',
|
||||
defaultMessage: 'An error occurred',
|
||||
},
|
||||
unknownError: {
|
||||
id: 'servers.manage.error.unknown',
|
||||
defaultMessage: 'Unknown error',
|
||||
},
|
||||
purchaseUnavailableTitle: {
|
||||
id: 'servers.manage.purchase-unavailable.title',
|
||||
defaultMessage: 'Purchase unavailable',
|
||||
@@ -677,7 +685,9 @@ function handleError(err: unknown) {
|
||||
}
|
||||
|
||||
function formatFetchError(error: unknown) {
|
||||
return error instanceof Error && error.message ? error.message : 'Unknown error'
|
||||
return error instanceof Error && error.message
|
||||
? error.message
|
||||
: formatMessage(messages.unknownError)
|
||||
}
|
||||
|
||||
function handleSignIn() {
|
||||
|
||||
@@ -608,9 +608,6 @@
|
||||
"creation-flow.modal.custom-setup.game-version.search-placeholder": {
|
||||
"defaultMessage": "Hledat verzi hry..."
|
||||
},
|
||||
"creation-flow.modal.custom-setup.icon.remove": {
|
||||
"defaultMessage": "Odebrat ikonu"
|
||||
},
|
||||
"creation-flow.modal.custom-setup.icon.select": {
|
||||
"defaultMessage": "Vybrat ikonu"
|
||||
},
|
||||
@@ -767,24 +764,6 @@
|
||||
"creation-flow.modal.import-instance.selection.clear-all": {
|
||||
"defaultMessage": "Zrušit vše"
|
||||
},
|
||||
"creation-flow.modal.modpack.action.browse": {
|
||||
"defaultMessage": "Procházet modpacky"
|
||||
},
|
||||
"creation-flow.modal.modpack.action.import": {
|
||||
"defaultMessage": "Importovat modpack"
|
||||
},
|
||||
"creation-flow.modal.modpack.known-modpack.prompt": {
|
||||
"defaultMessage": "Už víš modpack, který chceš nainstalovat?"
|
||||
},
|
||||
"creation-flow.modal.modpack.search.no-results": {
|
||||
"defaultMessage": "Nenalezeny žádné výsledky"
|
||||
},
|
||||
"creation-flow.modal.modpack.search.placeholder": {
|
||||
"defaultMessage": "Hledat modpack"
|
||||
},
|
||||
"creation-flow.modal.setup-type.instance.description": {
|
||||
"defaultMessage": "Instance je nastavení Minecraftu s konkrétním loaderem, verzí a módy."
|
||||
},
|
||||
"creation-flow.modal.setup-type.option.custom-setup.description": {
|
||||
"defaultMessage": "Začněte od začátku výběrem loaderu a verze hry."
|
||||
},
|
||||
@@ -818,9 +797,6 @@
|
||||
"creation-flow.modal.setup-type.title.world": {
|
||||
"defaultMessage": "Vyber typ světa"
|
||||
},
|
||||
"creation-flow.title.choose-modpack": {
|
||||
"defaultMessage": "Výběr modpacku"
|
||||
},
|
||||
"creation-flow.title.create-instance": {
|
||||
"defaultMessage": "Vytvoření instance"
|
||||
},
|
||||
@@ -1811,15 +1787,9 @@
|
||||
"instances.content-install.no-instances": {
|
||||
"defaultMessage": "Nebyly nalezeny žádné kompatibilní instance"
|
||||
},
|
||||
"instances.content-install.remove-icon": {
|
||||
"defaultMessage": "Odebrat ikonu"
|
||||
},
|
||||
"instances.content-install.search-placeholder": {
|
||||
"defaultMessage": "Hledat instance"
|
||||
},
|
||||
"instances.content-install.select-icon": {
|
||||
"defaultMessage": "Vybrat ikonu"
|
||||
},
|
||||
"instances.updater-modal.badge.current": {
|
||||
"defaultMessage": "Mometálně"
|
||||
},
|
||||
@@ -3665,9 +3635,6 @@
|
||||
"servers.list-empty.already-have-server-label": {
|
||||
"defaultMessage": "Už máte server?"
|
||||
},
|
||||
"servers.list-empty.learn-more-link": {
|
||||
"defaultMessage": "Zjistěte víc o Modrinth hostingu"
|
||||
},
|
||||
"servers.list-empty.modrinth-hosting-label": {
|
||||
"defaultMessage": "Modrinth Hosting"
|
||||
},
|
||||
@@ -3677,9 +3644,6 @@
|
||||
"servers.list-empty.no-servers-description": {
|
||||
"defaultMessage": "Instalujte mody, zvěte přátele a hrajte společně, to vše z Modrinth App."
|
||||
},
|
||||
"servers.list-empty.no-servers-title": {
|
||||
"defaultMessage": "Zatím žádné servery"
|
||||
},
|
||||
"servers.list-empty.one-click-mod-installs-description": {
|
||||
"defaultMessage": "Vyberte si své oblíbené mody a o zbytek se postaráme."
|
||||
},
|
||||
@@ -3695,9 +3659,6 @@
|
||||
"servers.list-empty.sign-in-button": {
|
||||
"defaultMessage": "Přihlásit se"
|
||||
},
|
||||
"servers.list-empty.simple-setup-description": {
|
||||
"defaultMessage": "Nastavte si server stejně jako svět pro jednoho hráče."
|
||||
},
|
||||
"servers.listing.copied-label": {
|
||||
"defaultMessage": "Zkopírováno"
|
||||
},
|
||||
|
||||
@@ -431,9 +431,6 @@
|
||||
"creation-flow.button.setup-server": {
|
||||
"defaultMessage": "Opsæt server"
|
||||
},
|
||||
"creation-flow.modal.custom-setup.icon.remove": {
|
||||
"defaultMessage": "Fjern ikon"
|
||||
},
|
||||
"creation-flow.modal.custom-setup.icon.select": {
|
||||
"defaultMessage": "Vælg ikon"
|
||||
},
|
||||
@@ -500,12 +497,6 @@
|
||||
"creation-flow.modal.import-instance.selection.clear-all": {
|
||||
"defaultMessage": "Ryd alle"
|
||||
},
|
||||
"creation-flow.modal.modpack.search.no-results": {
|
||||
"defaultMessage": "Ingen resultater fundet"
|
||||
},
|
||||
"creation-flow.modal.modpack.search.placeholder": {
|
||||
"defaultMessage": "Søg efter modpack"
|
||||
},
|
||||
"creation-flow.modal.setup-type.option.modpack-base.title": {
|
||||
"defaultMessage": "Installer modpack"
|
||||
},
|
||||
@@ -791,15 +782,9 @@
|
||||
"instances.content-install.new-tab": {
|
||||
"defaultMessage": "Ny instans"
|
||||
},
|
||||
"instances.content-install.remove-icon": {
|
||||
"defaultMessage": "Fjern ikon"
|
||||
},
|
||||
"instances.content-install.search-placeholder": {
|
||||
"defaultMessage": "Søg efter instanser"
|
||||
},
|
||||
"instances.content-install.select-icon": {
|
||||
"defaultMessage": "Vælg ikon"
|
||||
},
|
||||
"instances.updater-modal.badge.current": {
|
||||
"defaultMessage": "Nuværrende"
|
||||
},
|
||||
@@ -1964,9 +1949,6 @@
|
||||
"servers.list-empty.already-have-server-label": {
|
||||
"defaultMessage": "Har du allerede en server?"
|
||||
},
|
||||
"servers.list-empty.learn-more-link": {
|
||||
"defaultMessage": "Lær mere om Modrinth Hosting"
|
||||
},
|
||||
"servers.list-empty.modrinth-hosting-label": {
|
||||
"defaultMessage": "Modrinth Hosting"
|
||||
},
|
||||
@@ -1976,9 +1958,6 @@
|
||||
"servers.list-empty.no-servers-description": {
|
||||
"defaultMessage": "Installer mods, inviter venner og spil alle sammen fra Modrinth Appen."
|
||||
},
|
||||
"servers.list-empty.no-servers-title": {
|
||||
"defaultMessage": "Ingen servere endnu"
|
||||
},
|
||||
"servers.list-empty.one-click-mod-installs-description": {
|
||||
"defaultMessage": "Vælg dine favorit mods og vi håndtere resten."
|
||||
},
|
||||
@@ -1991,9 +1970,6 @@
|
||||
"servers.list-empty.sign-in-button": {
|
||||
"defaultMessage": "Log ind"
|
||||
},
|
||||
"servers.list-empty.simple-setup-title": {
|
||||
"defaultMessage": "Simpel opsætning"
|
||||
},
|
||||
"servers.notice.heading.info": {
|
||||
"defaultMessage": "Information"
|
||||
},
|
||||
|
||||
@@ -707,9 +707,6 @@
|
||||
"creation-flow.modal.custom-setup.game-version.search-placeholder": {
|
||||
"defaultMessage": "Spielversionen durchsuchen..."
|
||||
},
|
||||
"creation-flow.modal.custom-setup.icon.remove": {
|
||||
"defaultMessage": "Icon entfernen"
|
||||
},
|
||||
"creation-flow.modal.custom-setup.icon.select": {
|
||||
"defaultMessage": "Icon auswählen"
|
||||
},
|
||||
@@ -872,24 +869,6 @@
|
||||
"creation-flow.modal.import-instance.selection.clear-all": {
|
||||
"defaultMessage": "Alle entfernen"
|
||||
},
|
||||
"creation-flow.modal.modpack.action.browse": {
|
||||
"defaultMessage": "Modpacks durchsuchen"
|
||||
},
|
||||
"creation-flow.modal.modpack.action.import": {
|
||||
"defaultMessage": "Modpack importieren"
|
||||
},
|
||||
"creation-flow.modal.modpack.known-modpack.prompt": {
|
||||
"defaultMessage": "Weisst du schon, welches Modpack du installieren möchtest?"
|
||||
},
|
||||
"creation-flow.modal.modpack.search.no-results": {
|
||||
"defaultMessage": "Keine Ergebnisse gefunden"
|
||||
},
|
||||
"creation-flow.modal.modpack.search.placeholder": {
|
||||
"defaultMessage": "Nach Modpack suchen"
|
||||
},
|
||||
"creation-flow.modal.setup-type.instance.description": {
|
||||
"defaultMessage": "Eine Instanz ist eine Einrichtung von Minecraft mit einem bestimmten Loader, einer bestimmten Version und Mods."
|
||||
},
|
||||
"creation-flow.modal.setup-type.option.custom-setup.description": {
|
||||
"defaultMessage": "Beginne von vorne, indem du einen Loader und eine Spielversion auswählst."
|
||||
},
|
||||
@@ -923,9 +902,6 @@
|
||||
"creation-flow.modal.setup-type.title.world": {
|
||||
"defaultMessage": "Welttyp auswählen"
|
||||
},
|
||||
"creation-flow.title.choose-modpack": {
|
||||
"defaultMessage": "Modpack auswählen"
|
||||
},
|
||||
"creation-flow.title.create-instance": {
|
||||
"defaultMessage": "Instanz erstellen"
|
||||
},
|
||||
@@ -2057,15 +2033,9 @@
|
||||
"instances.content-install.no-instances": {
|
||||
"defaultMessage": "Keine kompatiblen Instanzen gefunden"
|
||||
},
|
||||
"instances.content-install.remove-icon": {
|
||||
"defaultMessage": "Icon entfernen"
|
||||
},
|
||||
"instances.content-install.search-placeholder": {
|
||||
"defaultMessage": "Instanz suchen"
|
||||
},
|
||||
"instances.content-install.select-icon": {
|
||||
"defaultMessage": "Icon auswählen"
|
||||
},
|
||||
"instances.updater-modal.badge.current": {
|
||||
"defaultMessage": "Aktuell"
|
||||
},
|
||||
@@ -4622,9 +4592,6 @@
|
||||
"servers.list-empty.already-have-server-label": {
|
||||
"defaultMessage": "Du hast bereits einen Server?"
|
||||
},
|
||||
"servers.list-empty.learn-more-link": {
|
||||
"defaultMessage": "Erfahre mehr über Modrinth Hosting"
|
||||
},
|
||||
"servers.list-empty.modrinth-hosting-label": {
|
||||
"defaultMessage": "Modrinth Hosting"
|
||||
},
|
||||
@@ -4634,9 +4601,6 @@
|
||||
"servers.list-empty.no-servers-description": {
|
||||
"defaultMessage": "Installiere Mods, lade Freunde ein und spiel gemeinsam, alles über die Modrinth App."
|
||||
},
|
||||
"servers.list-empty.no-servers-title": {
|
||||
"defaultMessage": "Noch keine Server"
|
||||
},
|
||||
"servers.list-empty.one-click-mod-installs-description": {
|
||||
"defaultMessage": "Wähle deine Lieblingsmods aus, den Rest erledigen wir."
|
||||
},
|
||||
@@ -4652,12 +4616,6 @@
|
||||
"servers.list-empty.sign-in-button": {
|
||||
"defaultMessage": "Anmelden"
|
||||
},
|
||||
"servers.list-empty.simple-setup-description": {
|
||||
"defaultMessage": "Richte deinen Server genauso wie eine Einzelspielerwelt ein."
|
||||
},
|
||||
"servers.list-empty.simple-setup-title": {
|
||||
"defaultMessage": "Einfache Einrichtung"
|
||||
},
|
||||
"servers.listing.copied-label": {
|
||||
"defaultMessage": "Kopiert"
|
||||
},
|
||||
|
||||
@@ -707,9 +707,6 @@
|
||||
"creation-flow.modal.custom-setup.game-version.search-placeholder": {
|
||||
"defaultMessage": "Spielversionen durchsuchen..."
|
||||
},
|
||||
"creation-flow.modal.custom-setup.icon.remove": {
|
||||
"defaultMessage": "Icon entfernen"
|
||||
},
|
||||
"creation-flow.modal.custom-setup.icon.select": {
|
||||
"defaultMessage": "Icon auswählen"
|
||||
},
|
||||
@@ -872,24 +869,6 @@
|
||||
"creation-flow.modal.import-instance.selection.clear-all": {
|
||||
"defaultMessage": "Alle entfernen"
|
||||
},
|
||||
"creation-flow.modal.modpack.action.browse": {
|
||||
"defaultMessage": "Modpacks durchsuchen"
|
||||
},
|
||||
"creation-flow.modal.modpack.action.import": {
|
||||
"defaultMessage": "Modpack importieren"
|
||||
},
|
||||
"creation-flow.modal.modpack.known-modpack.prompt": {
|
||||
"defaultMessage": "Weißt du schon, welches Modpack du installieren möchtest?"
|
||||
},
|
||||
"creation-flow.modal.modpack.search.no-results": {
|
||||
"defaultMessage": "Keine Ergebnisse gefunden"
|
||||
},
|
||||
"creation-flow.modal.modpack.search.placeholder": {
|
||||
"defaultMessage": "Nach Modpack suchen"
|
||||
},
|
||||
"creation-flow.modal.setup-type.instance.description": {
|
||||
"defaultMessage": "Eine Instanz ist eine Einrichtung von Minecraft mit einem bestimmten Loader, einer bestimmten Version und Mods."
|
||||
},
|
||||
"creation-flow.modal.setup-type.option.custom-setup.description": {
|
||||
"defaultMessage": "Beginne von vorne, indem du einen Loader und eine Spielversion auswählst."
|
||||
},
|
||||
@@ -923,9 +902,6 @@
|
||||
"creation-flow.modal.setup-type.title.world": {
|
||||
"defaultMessage": "Welttyp auswählen"
|
||||
},
|
||||
"creation-flow.title.choose-modpack": {
|
||||
"defaultMessage": "Modpack auswählen"
|
||||
},
|
||||
"creation-flow.title.create-instance": {
|
||||
"defaultMessage": "Instanz erstellen"
|
||||
},
|
||||
@@ -2057,15 +2033,9 @@
|
||||
"instances.content-install.no-instances": {
|
||||
"defaultMessage": "Keine kompatiblen Instanzen gefunden"
|
||||
},
|
||||
"instances.content-install.remove-icon": {
|
||||
"defaultMessage": "Icon entfernen"
|
||||
},
|
||||
"instances.content-install.search-placeholder": {
|
||||
"defaultMessage": "Instanz suchen"
|
||||
},
|
||||
"instances.content-install.select-icon": {
|
||||
"defaultMessage": "Icon auswählen"
|
||||
},
|
||||
"instances.updater-modal.badge.current": {
|
||||
"defaultMessage": "Aktuell"
|
||||
},
|
||||
@@ -4622,9 +4592,6 @@
|
||||
"servers.list-empty.already-have-server-label": {
|
||||
"defaultMessage": "Du hast bereits einen Server?"
|
||||
},
|
||||
"servers.list-empty.learn-more-link": {
|
||||
"defaultMessage": "Erfahre mehr über Modrinth Hosting"
|
||||
},
|
||||
"servers.list-empty.modrinth-hosting-label": {
|
||||
"defaultMessage": "Modrinth Hosting"
|
||||
},
|
||||
@@ -4634,9 +4601,6 @@
|
||||
"servers.list-empty.no-servers-description": {
|
||||
"defaultMessage": "Installiere Mods, lade Freunde ein und spiel gemeinsam, alles über die Modrinth App."
|
||||
},
|
||||
"servers.list-empty.no-servers-title": {
|
||||
"defaultMessage": "Noch keine Server"
|
||||
},
|
||||
"servers.list-empty.one-click-mod-installs-description": {
|
||||
"defaultMessage": "Wähle deine Lieblingsmods aus, den Rest erledigen wir."
|
||||
},
|
||||
@@ -4652,12 +4616,6 @@
|
||||
"servers.list-empty.sign-in-button": {
|
||||
"defaultMessage": "Anmelden"
|
||||
},
|
||||
"servers.list-empty.simple-setup-description": {
|
||||
"defaultMessage": "Richte deinen Server genauso wie eine Einzelspielerwelt ein."
|
||||
},
|
||||
"servers.list-empty.simple-setup-title": {
|
||||
"defaultMessage": "Einfache Einrichtung"
|
||||
},
|
||||
"servers.listing.copied-label": {
|
||||
"defaultMessage": "Kopiert"
|
||||
},
|
||||
|
||||
@@ -833,11 +833,14 @@
|
||||
"creation-flow.modal.custom-setup.game-version.search-placeholder": {
|
||||
"defaultMessage": "Search game version..."
|
||||
},
|
||||
"creation-flow.modal.custom-setup.icon.remove": {
|
||||
"defaultMessage": "Remove icon"
|
||||
"creation-flow.modal.custom-setup.icon.customize": {
|
||||
"defaultMessage": "Customize"
|
||||
},
|
||||
"creation-flow.modal.custom-setup.icon.randomize": {
|
||||
"defaultMessage": "Randomize"
|
||||
},
|
||||
"creation-flow.modal.custom-setup.icon.select": {
|
||||
"defaultMessage": "Select icon"
|
||||
"defaultMessage": "Upload"
|
||||
},
|
||||
"creation-flow.modal.custom-setup.loader-version-type.latest": {
|
||||
"defaultMessage": "Latest"
|
||||
@@ -1013,8 +1016,20 @@
|
||||
"creation-flow.modal.modpack.search.placeholder": {
|
||||
"defaultMessage": "Search for modpack"
|
||||
},
|
||||
"creation-flow.modal.setup-type.instance.description": {
|
||||
"defaultMessage": "An instance is a Minecraft setup with a specific loader, version, and mods."
|
||||
"creation-flow.modal.project.known-project.prompt": {
|
||||
"defaultMessage": "Already know what you want to play?"
|
||||
},
|
||||
"creation-flow.modal.project.search.create-instance": {
|
||||
"defaultMessage": "Create instance"
|
||||
},
|
||||
"creation-flow.modal.project.search.install-modpack": {
|
||||
"defaultMessage": "Install modpack"
|
||||
},
|
||||
"creation-flow.modal.project.search.no-results": {
|
||||
"defaultMessage": "No results found"
|
||||
},
|
||||
"creation-flow.modal.project.search.placeholder": {
|
||||
"defaultMessage": "Search mods, modpacks, and more..."
|
||||
},
|
||||
"creation-flow.modal.setup-type.option.custom-setup.description": {
|
||||
"defaultMessage": "Start from scratch by picking a loader and game version."
|
||||
@@ -1029,10 +1044,16 @@
|
||||
"defaultMessage": "Import instance"
|
||||
},
|
||||
"creation-flow.modal.setup-type.option.modpack-base.description": {
|
||||
"defaultMessage": "Browse modpacks on Modrinth or import one from a file."
|
||||
"defaultMessage": "Choose a project and we’ll use its latest version."
|
||||
},
|
||||
"creation-flow.modal.setup-type.option.modpack-base.title": {
|
||||
"defaultMessage": "Install modpack"
|
||||
"defaultMessage": "Start from a mod or modpack"
|
||||
},
|
||||
"creation-flow.modal.setup-type.option.upload-modpack.description": {
|
||||
"defaultMessage": "Install a modpack from an .mrpack file on your device."
|
||||
},
|
||||
"creation-flow.modal.setup-type.option.upload-modpack.title": {
|
||||
"defaultMessage": "Upload a modpack"
|
||||
},
|
||||
"creation-flow.modal.setup-type.option.vanilla-minecraft.description": {
|
||||
"defaultMessage": "Classic Minecraft with no mods or plugins."
|
||||
@@ -1049,9 +1070,6 @@
|
||||
"creation-flow.modal.setup-type.title.world": {
|
||||
"defaultMessage": "Select world type"
|
||||
},
|
||||
"creation-flow.title.choose-modpack": {
|
||||
"defaultMessage": "Choose modpack"
|
||||
},
|
||||
"creation-flow.title.create-instance": {
|
||||
"defaultMessage": "Create instance"
|
||||
},
|
||||
@@ -1733,6 +1751,30 @@
|
||||
"files.zip-url-modal.zip-header": {
|
||||
"defaultMessage": "Uploading .zip contents from URL"
|
||||
},
|
||||
"filter-bar.add": {
|
||||
"defaultMessage": "Add"
|
||||
},
|
||||
"filter-bar.clear": {
|
||||
"defaultMessage": "Clear"
|
||||
},
|
||||
"filter-bar.clear-named-filter": {
|
||||
"defaultMessage": "Clear {name} filter"
|
||||
},
|
||||
"filter-bar.filtered-by": {
|
||||
"defaultMessage": "Filtered by"
|
||||
},
|
||||
"filter-bar.no-options": {
|
||||
"defaultMessage": "No options available."
|
||||
},
|
||||
"filter-bar.no-search-results": {
|
||||
"defaultMessage": "No options found."
|
||||
},
|
||||
"filter-bar.search-placeholder": {
|
||||
"defaultMessage": "Search..."
|
||||
},
|
||||
"filter-bar.select-all": {
|
||||
"defaultMessage": "Select all"
|
||||
},
|
||||
"form.label.address-line": {
|
||||
"defaultMessage": "Address line"
|
||||
},
|
||||
@@ -1970,6 +2012,12 @@
|
||||
"icon-select.select": {
|
||||
"defaultMessage": "Select icon"
|
||||
},
|
||||
"input.clear": {
|
||||
"defaultMessage": "Clear input"
|
||||
},
|
||||
"input.search": {
|
||||
"defaultMessage": "Search"
|
||||
},
|
||||
"input.search-version.placeholder": {
|
||||
"defaultMessage": "Search version..."
|
||||
},
|
||||
@@ -2162,6 +2210,9 @@
|
||||
"instances.content-install.header": {
|
||||
"defaultMessage": "Install project"
|
||||
},
|
||||
"instances.content-install.hide-unavailable": {
|
||||
"defaultMessage": "Hide unavailable"
|
||||
},
|
||||
"instances.content-install.incompatible-tooltip": {
|
||||
"defaultMessage": "This instance uses a different loader or game version than this project supports."
|
||||
},
|
||||
@@ -2189,14 +2240,11 @@
|
||||
"instances.content-install.no-instances": {
|
||||
"defaultMessage": "No compatible instances found"
|
||||
},
|
||||
"instances.content-install.remove-icon": {
|
||||
"defaultMessage": "Remove icon"
|
||||
},
|
||||
"instances.content-install.search-placeholder": {
|
||||
"defaultMessage": "Search instance"
|
||||
},
|
||||
"instances.content-install.select-icon": {
|
||||
"defaultMessage": "Select icon"
|
||||
"instances.content-install.show-unavailable": {
|
||||
"defaultMessage": "Show unavailable"
|
||||
},
|
||||
"instances.managed-content-modal.empty-description": {
|
||||
"defaultMessage": "This source does not include any managed content."
|
||||
@@ -2828,6 +2876,39 @@
|
||||
"notification.error.title": {
|
||||
"defaultMessage": "An error occurred"
|
||||
},
|
||||
"notifications.actor.unknown": {
|
||||
"defaultMessage": "Someone"
|
||||
},
|
||||
"notifications.close": {
|
||||
"defaultMessage": "Close"
|
||||
},
|
||||
"notifications.dismiss": {
|
||||
"defaultMessage": "Dismiss notification"
|
||||
},
|
||||
"notifications.friend-request.body": {
|
||||
"defaultMessage": "<actor>{actor}</actor> sent you a friend request."
|
||||
},
|
||||
"notifications.instance-invite.body": {
|
||||
"defaultMessage": "<actor>{actor}</actor> invited you to <entity>{entity}</entity> instance."
|
||||
},
|
||||
"notifications.instance-ready.launch-game": {
|
||||
"defaultMessage": "Launch game"
|
||||
},
|
||||
"notifications.instance-ready.open-instance": {
|
||||
"defaultMessage": "Instance"
|
||||
},
|
||||
"notifications.instance-ready.status": {
|
||||
"defaultMessage": "Installed and ready to play."
|
||||
},
|
||||
"notifications.invite.accept": {
|
||||
"defaultMessage": "Accept"
|
||||
},
|
||||
"notifications.invite.decline": {
|
||||
"defaultMessage": "Decline"
|
||||
},
|
||||
"notifications.server-invite.body": {
|
||||
"defaultMessage": "<actor>{actor}</actor> invited you to manage the server <entity>{entity}</entity>."
|
||||
},
|
||||
"omorphia.component.badge.label.accepted": {
|
||||
"defaultMessage": "Accepted"
|
||||
},
|
||||
@@ -4971,7 +5052,7 @@
|
||||
"defaultMessage": "Already have a server?"
|
||||
},
|
||||
"servers.list-empty.learn-more-link": {
|
||||
"defaultMessage": "Learn more about Modrinth Hosting"
|
||||
"defaultMessage": "Learn more"
|
||||
},
|
||||
"servers.list-empty.modrinth-hosting-label": {
|
||||
"defaultMessage": "Modrinth Hosting"
|
||||
@@ -4982,9 +5063,6 @@
|
||||
"servers.list-empty.no-servers-description": {
|
||||
"defaultMessage": "Install mods, invite friends, and play together all from the Modrinth App."
|
||||
},
|
||||
"servers.list-empty.no-servers-title": {
|
||||
"defaultMessage": "No servers yet"
|
||||
},
|
||||
"servers.list-empty.one-click-mod-installs-description": {
|
||||
"defaultMessage": "Pick your favorite mods and we handle the rest."
|
||||
},
|
||||
@@ -4992,13 +5070,13 @@
|
||||
"defaultMessage": "One-click mod installs"
|
||||
},
|
||||
"servers.list-empty.play-with-friends-description": {
|
||||
"defaultMessage": "Invite friends and get them set up right in the Modrinth App."
|
||||
"defaultMessage": "Invite friends to play your server with one-click play from the Modrinth App."
|
||||
},
|
||||
"servers.list-empty.play-with-friends-title": {
|
||||
"defaultMessage": "Play with friends"
|
||||
},
|
||||
"servers.list-empty.sign-in-button": {
|
||||
"defaultMessage": "Sign in"
|
||||
"defaultMessage": "Sign in to Modrinth"
|
||||
},
|
||||
"servers.list-empty.simple-setup-description": {
|
||||
"defaultMessage": "Set up your server just like a single player world."
|
||||
@@ -5111,6 +5189,9 @@
|
||||
"servers.manage.error.title": {
|
||||
"defaultMessage": "Servers could not be loaded"
|
||||
},
|
||||
"servers.manage.error.unknown": {
|
||||
"defaultMessage": "Unknown error"
|
||||
},
|
||||
"servers.manage.handle-error.title": {
|
||||
"defaultMessage": "An error occurred"
|
||||
},
|
||||
@@ -5654,6 +5735,9 @@
|
||||
"sharing.invite-players-modal.avatar-alt": {
|
||||
"defaultMessage": "{username}'s avatar"
|
||||
},
|
||||
"sharing.invite-players-modal.cancel": {
|
||||
"defaultMessage": "Cancel"
|
||||
},
|
||||
"sharing.invite-players-modal.custom-expiry": {
|
||||
"defaultMessage": "Custom..."
|
||||
},
|
||||
@@ -5690,6 +5774,9 @@
|
||||
"sharing.invite-players-modal.friends-heading": {
|
||||
"defaultMessage": "Your friends - {count}"
|
||||
},
|
||||
"sharing.invite-players-modal.heading": {
|
||||
"defaultMessage": "Invite players"
|
||||
},
|
||||
"sharing.invite-players-modal.invite": {
|
||||
"defaultMessage": "Invite"
|
||||
},
|
||||
@@ -5735,6 +5822,24 @@
|
||||
"sharing.invite-players-modal.update-invite-link-failed-title": {
|
||||
"defaultMessage": "Failed to update invite link"
|
||||
},
|
||||
"sharing.invite-players-toast.accept": {
|
||||
"defaultMessage": "Accept"
|
||||
},
|
||||
"sharing.invite-players-toast.decline": {
|
||||
"defaultMessage": "Decline"
|
||||
},
|
||||
"sharing.invite-players-toast.invited-you-to": {
|
||||
"defaultMessage": "invited you to"
|
||||
},
|
||||
"sharing.invite-players-toast.server-suffix": {
|
||||
"defaultMessage": "server."
|
||||
},
|
||||
"skins.select": {
|
||||
"defaultMessage": "Select skin"
|
||||
},
|
||||
"skins.select-named": {
|
||||
"defaultMessage": "Select {name}"
|
||||
},
|
||||
"tag.category.128x": {
|
||||
"defaultMessage": "128x"
|
||||
},
|
||||
|
||||
@@ -707,9 +707,6 @@
|
||||
"creation-flow.modal.custom-setup.game-version.search-placeholder": {
|
||||
"defaultMessage": "Buscar versión del juego..."
|
||||
},
|
||||
"creation-flow.modal.custom-setup.icon.remove": {
|
||||
"defaultMessage": "Eliminar ícono"
|
||||
},
|
||||
"creation-flow.modal.custom-setup.icon.select": {
|
||||
"defaultMessage": "Seleccionar ícono"
|
||||
},
|
||||
@@ -872,24 +869,6 @@
|
||||
"creation-flow.modal.import-instance.selection.clear-all": {
|
||||
"defaultMessage": "Limpiar todo"
|
||||
},
|
||||
"creation-flow.modal.modpack.action.browse": {
|
||||
"defaultMessage": "Descubrir modpacks"
|
||||
},
|
||||
"creation-flow.modal.modpack.action.import": {
|
||||
"defaultMessage": "Importar modpack"
|
||||
},
|
||||
"creation-flow.modal.modpack.known-modpack.prompt": {
|
||||
"defaultMessage": "¿Ya sabes qué modpack quieres instalar?"
|
||||
},
|
||||
"creation-flow.modal.modpack.search.no-results": {
|
||||
"defaultMessage": "No se encontraron resultados"
|
||||
},
|
||||
"creation-flow.modal.modpack.search.placeholder": {
|
||||
"defaultMessage": "Buscar un modpack"
|
||||
},
|
||||
"creation-flow.modal.setup-type.instance.description": {
|
||||
"defaultMessage": "Una instancia es una configuración de Minecraft con un loader, una versión y unos mods específicos."
|
||||
},
|
||||
"creation-flow.modal.setup-type.option.custom-setup.description": {
|
||||
"defaultMessage": "Empieza desde cero eligiendo un loader y una versión del juego."
|
||||
},
|
||||
@@ -923,9 +902,6 @@
|
||||
"creation-flow.modal.setup-type.title.world": {
|
||||
"defaultMessage": "Selecciona el tipo de mundo"
|
||||
},
|
||||
"creation-flow.title.choose-modpack": {
|
||||
"defaultMessage": "Elige un modpack"
|
||||
},
|
||||
"creation-flow.title.create-instance": {
|
||||
"defaultMessage": "Crear instancia"
|
||||
},
|
||||
@@ -2057,15 +2033,9 @@
|
||||
"instances.content-install.no-instances": {
|
||||
"defaultMessage": "No se encontraron instancias compatibles"
|
||||
},
|
||||
"instances.content-install.remove-icon": {
|
||||
"defaultMessage": "Eliminar ícono"
|
||||
},
|
||||
"instances.content-install.search-placeholder": {
|
||||
"defaultMessage": "Buscar instancia"
|
||||
},
|
||||
"instances.content-install.select-icon": {
|
||||
"defaultMessage": "Seleccionar ícono"
|
||||
},
|
||||
"instances.updater-modal.badge.current": {
|
||||
"defaultMessage": "Actual"
|
||||
},
|
||||
@@ -4622,9 +4592,6 @@
|
||||
"servers.list-empty.already-have-server-label": {
|
||||
"defaultMessage": "¿Ya tienes un servidor?"
|
||||
},
|
||||
"servers.list-empty.learn-more-link": {
|
||||
"defaultMessage": "Aprende más sobre Modrinth Hosting"
|
||||
},
|
||||
"servers.list-empty.modrinth-hosting-label": {
|
||||
"defaultMessage": "Modrinth Hosting"
|
||||
},
|
||||
@@ -4634,9 +4601,6 @@
|
||||
"servers.list-empty.no-servers-description": {
|
||||
"defaultMessage": "Instala mods, invita amigos, y jueguen juntos todo desde la Modrinth App."
|
||||
},
|
||||
"servers.list-empty.no-servers-title": {
|
||||
"defaultMessage": "Aún no hay servidores"
|
||||
},
|
||||
"servers.list-empty.one-click-mod-installs-description": {
|
||||
"defaultMessage": "Escoge tus mods favoritos y nos encargaremos del resto."
|
||||
},
|
||||
@@ -4652,12 +4616,6 @@
|
||||
"servers.list-empty.sign-in-button": {
|
||||
"defaultMessage": "Iniciar sesión"
|
||||
},
|
||||
"servers.list-empty.simple-setup-description": {
|
||||
"defaultMessage": "Configura tu servidor como si fuera un mundo de un solo jugador."
|
||||
},
|
||||
"servers.list-empty.simple-setup-title": {
|
||||
"defaultMessage": "Configuración simple"
|
||||
},
|
||||
"servers.listing.copied-label": {
|
||||
"defaultMessage": "Copiado"
|
||||
},
|
||||
|
||||
@@ -707,9 +707,6 @@
|
||||
"creation-flow.modal.custom-setup.game-version.search-placeholder": {
|
||||
"defaultMessage": "Buscar versión de juego..."
|
||||
},
|
||||
"creation-flow.modal.custom-setup.icon.remove": {
|
||||
"defaultMessage": "Eliminar icono"
|
||||
},
|
||||
"creation-flow.modal.custom-setup.icon.select": {
|
||||
"defaultMessage": "Seleccionar icono"
|
||||
},
|
||||
@@ -872,24 +869,6 @@
|
||||
"creation-flow.modal.import-instance.selection.clear-all": {
|
||||
"defaultMessage": "Borrar todo"
|
||||
},
|
||||
"creation-flow.modal.modpack.action.browse": {
|
||||
"defaultMessage": "Descubrir modpacks"
|
||||
},
|
||||
"creation-flow.modal.modpack.action.import": {
|
||||
"defaultMessage": "Importar modpack"
|
||||
},
|
||||
"creation-flow.modal.modpack.known-modpack.prompt": {
|
||||
"defaultMessage": "¿Ya sabes cuál es el modpack que quieres instalar?"
|
||||
},
|
||||
"creation-flow.modal.modpack.search.no-results": {
|
||||
"defaultMessage": "Ningún resultado encontrado"
|
||||
},
|
||||
"creation-flow.modal.modpack.search.placeholder": {
|
||||
"defaultMessage": "Buscar por modpack"
|
||||
},
|
||||
"creation-flow.modal.setup-type.instance.description": {
|
||||
"defaultMessage": "Una instancia es una configuración de Minecraft con un específico loader, versión, y mods."
|
||||
},
|
||||
"creation-flow.modal.setup-type.option.custom-setup.description": {
|
||||
"defaultMessage": "Empieza desde cero primero eligiendo un loader y una versión de juego."
|
||||
},
|
||||
@@ -923,9 +902,6 @@
|
||||
"creation-flow.modal.setup-type.title.world": {
|
||||
"defaultMessage": "Seleccione tipo de mundo"
|
||||
},
|
||||
"creation-flow.title.choose-modpack": {
|
||||
"defaultMessage": "Elige modpack"
|
||||
},
|
||||
"creation-flow.title.create-instance": {
|
||||
"defaultMessage": "Crear instancia"
|
||||
},
|
||||
@@ -2057,15 +2033,9 @@
|
||||
"instances.content-install.no-instances": {
|
||||
"defaultMessage": "No se han encontrado instancias compatibles"
|
||||
},
|
||||
"instances.content-install.remove-icon": {
|
||||
"defaultMessage": "Eliminar icono"
|
||||
},
|
||||
"instances.content-install.search-placeholder": {
|
||||
"defaultMessage": "Buscar instancia"
|
||||
},
|
||||
"instances.content-install.select-icon": {
|
||||
"defaultMessage": "Seleccionar icono"
|
||||
},
|
||||
"instances.updater-modal.badge.current": {
|
||||
"defaultMessage": "Actual"
|
||||
},
|
||||
@@ -4619,9 +4589,6 @@
|
||||
"servers.list-empty.already-have-server-label": {
|
||||
"defaultMessage": "¿Ya tienes un servidor?"
|
||||
},
|
||||
"servers.list-empty.learn-more-link": {
|
||||
"defaultMessage": "Conoce más sobre el alojamiento de servidores de Modrinth"
|
||||
},
|
||||
"servers.list-empty.modrinth-hosting-label": {
|
||||
"defaultMessage": "Modrinth Hosting"
|
||||
},
|
||||
@@ -4631,9 +4598,6 @@
|
||||
"servers.list-empty.no-servers-description": {
|
||||
"defaultMessage": "Instala mods, invita amigos, y jueguen todos juntos desde la Modrinth App."
|
||||
},
|
||||
"servers.list-empty.no-servers-title": {
|
||||
"defaultMessage": "No se han encontrado servidores todavía"
|
||||
},
|
||||
"servers.list-empty.one-click-mod-installs-description": {
|
||||
"defaultMessage": "Elige tus mods favoritos y nosotros nos encargamos del resto."
|
||||
},
|
||||
@@ -4649,12 +4613,6 @@
|
||||
"servers.list-empty.sign-in-button": {
|
||||
"defaultMessage": "Inicia sesión"
|
||||
},
|
||||
"servers.list-empty.simple-setup-description": {
|
||||
"defaultMessage": "Prepara tu servidor tal como si fuera un mundo de un solo jugador."
|
||||
},
|
||||
"servers.list-empty.simple-setup-title": {
|
||||
"defaultMessage": "Configuración Simple"
|
||||
},
|
||||
"servers.listing.copied-label": {
|
||||
"defaultMessage": "Copiado"
|
||||
},
|
||||
|
||||
@@ -707,9 +707,6 @@
|
||||
"creation-flow.modal.custom-setup.game-version.search-placeholder": {
|
||||
"defaultMessage": "Rechercher une version de jeu..."
|
||||
},
|
||||
"creation-flow.modal.custom-setup.icon.remove": {
|
||||
"defaultMessage": "Retirer l'icône"
|
||||
},
|
||||
"creation-flow.modal.custom-setup.icon.select": {
|
||||
"defaultMessage": "Sélectionner l'icône"
|
||||
},
|
||||
@@ -872,24 +869,6 @@
|
||||
"creation-flow.modal.import-instance.selection.clear-all": {
|
||||
"defaultMessage": "Effacer tout"
|
||||
},
|
||||
"creation-flow.modal.modpack.action.browse": {
|
||||
"defaultMessage": "Rechercher un modpack"
|
||||
},
|
||||
"creation-flow.modal.modpack.action.import": {
|
||||
"defaultMessage": "Importer un modpack"
|
||||
},
|
||||
"creation-flow.modal.modpack.known-modpack.prompt": {
|
||||
"defaultMessage": "Vous avez déjà le modpack en tête ?"
|
||||
},
|
||||
"creation-flow.modal.modpack.search.no-results": {
|
||||
"defaultMessage": "Aucun résultat"
|
||||
},
|
||||
"creation-flow.modal.modpack.search.placeholder": {
|
||||
"defaultMessage": "Rechercher un modpack"
|
||||
},
|
||||
"creation-flow.modal.setup-type.instance.description": {
|
||||
"defaultMessage": "Une instance est un setup Minecraft avec un mod loader, une version, et des mods spécifiques."
|
||||
},
|
||||
"creation-flow.modal.setup-type.option.custom-setup.description": {
|
||||
"defaultMessage": "Partez de zéro en choisissant un loader et une version de jeu."
|
||||
},
|
||||
@@ -923,9 +902,6 @@
|
||||
"creation-flow.modal.setup-type.title.world": {
|
||||
"defaultMessage": "Sélectionner le type de monde"
|
||||
},
|
||||
"creation-flow.title.choose-modpack": {
|
||||
"defaultMessage": "Choisir un modpack"
|
||||
},
|
||||
"creation-flow.title.create-instance": {
|
||||
"defaultMessage": "Créer une instance"
|
||||
},
|
||||
@@ -2051,15 +2027,9 @@
|
||||
"instances.content-install.no-instances": {
|
||||
"defaultMessage": "Aucune instance compatible n'a été trouvée"
|
||||
},
|
||||
"instances.content-install.remove-icon": {
|
||||
"defaultMessage": "Supprimer l'icône"
|
||||
},
|
||||
"instances.content-install.search-placeholder": {
|
||||
"defaultMessage": "Rechercher une instance"
|
||||
},
|
||||
"instances.content-install.select-icon": {
|
||||
"defaultMessage": "Sélectionnez l'icône"
|
||||
},
|
||||
"instances.updater-modal.badge.current": {
|
||||
"defaultMessage": "Actuel"
|
||||
},
|
||||
@@ -4616,9 +4586,6 @@
|
||||
"servers.list-empty.already-have-server-label": {
|
||||
"defaultMessage": "Vous avez déjà un serveur ?"
|
||||
},
|
||||
"servers.list-empty.learn-more-link": {
|
||||
"defaultMessage": "En savoir plus à propos de Modrinth Hosting"
|
||||
},
|
||||
"servers.list-empty.modrinth-hosting-label": {
|
||||
"defaultMessage": "Modrinth Hosting"
|
||||
},
|
||||
@@ -4628,9 +4595,6 @@
|
||||
"servers.list-empty.no-servers-description": {
|
||||
"defaultMessage": "Des mods, des potes, et beaucoup de fun, tout depuis Modrinth App."
|
||||
},
|
||||
"servers.list-empty.no-servers-title": {
|
||||
"defaultMessage": "Aucun serveur pour le moment"
|
||||
},
|
||||
"servers.list-empty.one-click-mod-installs-description": {
|
||||
"defaultMessage": "Choisissez vos mods préférés et nous nous occupons du reste."
|
||||
},
|
||||
@@ -4646,12 +4610,6 @@
|
||||
"servers.list-empty.sign-in-button": {
|
||||
"defaultMessage": "Se connecter"
|
||||
},
|
||||
"servers.list-empty.simple-setup-description": {
|
||||
"defaultMessage": "Mettez votre serveur en place tout comme un monde solo."
|
||||
},
|
||||
"servers.list-empty.simple-setup-title": {
|
||||
"defaultMessage": "Setup simple"
|
||||
},
|
||||
"servers.listing.copied-label": {
|
||||
"defaultMessage": "Copié"
|
||||
},
|
||||
|
||||
@@ -620,9 +620,6 @@
|
||||
"creation-flow.modal.custom-setup.game-version.search-placeholder": {
|
||||
"defaultMessage": "Játékverzió keresése..."
|
||||
},
|
||||
"creation-flow.modal.custom-setup.icon.remove": {
|
||||
"defaultMessage": "Ikon eltávolítása"
|
||||
},
|
||||
"creation-flow.modal.custom-setup.icon.select": {
|
||||
"defaultMessage": "Ikon feltöltése"
|
||||
},
|
||||
@@ -797,9 +794,6 @@
|
||||
"creation-flow.modal.modpack.search.placeholder": {
|
||||
"defaultMessage": "Modcsomag keresése"
|
||||
},
|
||||
"creation-flow.modal.setup-type.instance.description": {
|
||||
"defaultMessage": "A játékprofil egy külön Minecraft-környezet egy adott modbetöltővel, verzióval és modokkal."
|
||||
},
|
||||
"creation-flow.modal.setup-type.option.custom-setup.description": {
|
||||
"defaultMessage": "Kezdés alapoktól: válassz betöltőt és játékverziót."
|
||||
},
|
||||
@@ -833,9 +827,6 @@
|
||||
"creation-flow.modal.setup-type.title.world": {
|
||||
"defaultMessage": "Világ típusa"
|
||||
},
|
||||
"creation-flow.title.choose-modpack": {
|
||||
"defaultMessage": "Modcsomag kiválasztása"
|
||||
},
|
||||
"creation-flow.title.create-instance": {
|
||||
"defaultMessage": "Játékprofil létrehozása"
|
||||
},
|
||||
@@ -1709,15 +1700,9 @@
|
||||
"instances.content-install.no-instances": {
|
||||
"defaultMessage": "Nem található kompatibilis profil"
|
||||
},
|
||||
"instances.content-install.remove-icon": {
|
||||
"defaultMessage": "Ikon eltávolítása"
|
||||
},
|
||||
"instances.content-install.search-placeholder": {
|
||||
"defaultMessage": "Profilok keresése"
|
||||
},
|
||||
"instances.content-install.select-icon": {
|
||||
"defaultMessage": "Ikon feltöltése"
|
||||
},
|
||||
"instances.updater-modal.badge.current": {
|
||||
"defaultMessage": "Jelenlegi"
|
||||
},
|
||||
@@ -3911,9 +3896,6 @@
|
||||
"servers.list-empty.no-servers-description": {
|
||||
"defaultMessage": "Telepíts modokat, hívd meg a barátaidat, és játsszatok együtt, mindezt a Modrinth Appban."
|
||||
},
|
||||
"servers.list-empty.no-servers-title": {
|
||||
"defaultMessage": "Még nincsenek szerverek"
|
||||
},
|
||||
"servers.list-empty.one-click-mod-installs-description": {
|
||||
"defaultMessage": "Válaszd ki a kedvenc modjaidat, a többit bízd ránk!"
|
||||
},
|
||||
|
||||
@@ -572,9 +572,6 @@
|
||||
"creation-flow.modal.custom-setup.game-version.search-placeholder": {
|
||||
"defaultMessage": "Cari versi permainan..."
|
||||
},
|
||||
"creation-flow.modal.custom-setup.icon.remove": {
|
||||
"defaultMessage": "Hapus ikon"
|
||||
},
|
||||
"creation-flow.modal.custom-setup.icon.select": {
|
||||
"defaultMessage": "Pilih ikon"
|
||||
},
|
||||
@@ -728,9 +725,6 @@
|
||||
"creation-flow.modal.import-instance.selection.clear-all": {
|
||||
"defaultMessage": "Kosongkan semua"
|
||||
},
|
||||
"creation-flow.modal.modpack.search.no-results": {
|
||||
"defaultMessage": "Hasil pencarian tidak ditemukan"
|
||||
},
|
||||
"creation-flow.modal.setup-type.option.vanilla-minecraft.description": {
|
||||
"defaultMessage": "Minecraft klasik tanpa mod atau pasang-masuk."
|
||||
},
|
||||
|
||||
@@ -683,9 +683,6 @@
|
||||
"creation-flow.modal.custom-setup.game-version.search-placeholder": {
|
||||
"defaultMessage": "Cerca tra le versioni del gioco..."
|
||||
},
|
||||
"creation-flow.modal.custom-setup.icon.remove": {
|
||||
"defaultMessage": "Rimuovi icona"
|
||||
},
|
||||
"creation-flow.modal.custom-setup.icon.select": {
|
||||
"defaultMessage": "Scegli un'icona"
|
||||
},
|
||||
@@ -848,24 +845,6 @@
|
||||
"creation-flow.modal.import-instance.selection.clear-all": {
|
||||
"defaultMessage": "Deseleziona tutto"
|
||||
},
|
||||
"creation-flow.modal.modpack.action.browse": {
|
||||
"defaultMessage": "Sfoglia i pacchetti"
|
||||
},
|
||||
"creation-flow.modal.modpack.action.import": {
|
||||
"defaultMessage": "Importa pacchetto"
|
||||
},
|
||||
"creation-flow.modal.modpack.known-modpack.prompt": {
|
||||
"defaultMessage": "Sai già quale pacchetto di mod installare?"
|
||||
},
|
||||
"creation-flow.modal.modpack.search.no-results": {
|
||||
"defaultMessage": "Nessun risultato trovato"
|
||||
},
|
||||
"creation-flow.modal.modpack.search.placeholder": {
|
||||
"defaultMessage": "Cerca tra i pacchetti"
|
||||
},
|
||||
"creation-flow.modal.setup-type.instance.description": {
|
||||
"defaultMessage": "Un'istanza è una combinazione di loader, versione e mod per Minecraft."
|
||||
},
|
||||
"creation-flow.modal.setup-type.option.custom-setup.description": {
|
||||
"defaultMessage": "Parti da zero scegliendo il loader e la versione del gioco."
|
||||
},
|
||||
@@ -899,9 +878,6 @@
|
||||
"creation-flow.modal.setup-type.title.world": {
|
||||
"defaultMessage": "Seleziona il tipo di mondo"
|
||||
},
|
||||
"creation-flow.title.choose-modpack": {
|
||||
"defaultMessage": "Scegli pacchetto"
|
||||
},
|
||||
"creation-flow.title.create-instance": {
|
||||
"defaultMessage": "Crea istanza"
|
||||
},
|
||||
@@ -2015,15 +1991,9 @@
|
||||
"instances.content-install.no-instances": {
|
||||
"defaultMessage": "Nessuna istanza compatibile trovata"
|
||||
},
|
||||
"instances.content-install.remove-icon": {
|
||||
"defaultMessage": "Rimuovi icona"
|
||||
},
|
||||
"instances.content-install.search-placeholder": {
|
||||
"defaultMessage": "Cerca istanza"
|
||||
},
|
||||
"instances.content-install.select-icon": {
|
||||
"defaultMessage": "Scegli un'icona"
|
||||
},
|
||||
"instances.updater-modal.badge.current": {
|
||||
"defaultMessage": "Attuale"
|
||||
},
|
||||
@@ -4580,9 +4550,6 @@
|
||||
"servers.list-empty.already-have-server-label": {
|
||||
"defaultMessage": "Hai già un server?"
|
||||
},
|
||||
"servers.list-empty.learn-more-link": {
|
||||
"defaultMessage": "Scopri di più su Modrinth Hosting"
|
||||
},
|
||||
"servers.list-empty.modrinth-hosting-label": {
|
||||
"defaultMessage": "Modrinth Hosting"
|
||||
},
|
||||
@@ -4592,9 +4559,6 @@
|
||||
"servers.list-empty.no-servers-description": {
|
||||
"defaultMessage": "Crea un server, con o senza mod, e invita chi vuoi tramite la Modrinth App."
|
||||
},
|
||||
"servers.list-empty.no-servers-title": {
|
||||
"defaultMessage": "Ancora nessun server"
|
||||
},
|
||||
"servers.list-empty.one-click-mod-installs-description": {
|
||||
"defaultMessage": "Scegli le tue mod preferite, al resto ci pensiamo noi."
|
||||
},
|
||||
@@ -4610,12 +4574,6 @@
|
||||
"servers.list-empty.sign-in-button": {
|
||||
"defaultMessage": "Accedi"
|
||||
},
|
||||
"servers.list-empty.simple-setup-description": {
|
||||
"defaultMessage": "Configura il tuo server come vuoi tu, senza alcuno sforzo."
|
||||
},
|
||||
"servers.list-empty.simple-setup-title": {
|
||||
"defaultMessage": "Configurazione immediata"
|
||||
},
|
||||
"servers.listing.copied-label": {
|
||||
"defaultMessage": "Copiato"
|
||||
},
|
||||
|
||||
@@ -704,9 +704,6 @@
|
||||
"creation-flow.modal.custom-setup.game-version.search-placeholder": {
|
||||
"defaultMessage": "ゲームバージョンを検索…"
|
||||
},
|
||||
"creation-flow.modal.custom-setup.icon.remove": {
|
||||
"defaultMessage": "アイコンを削除"
|
||||
},
|
||||
"creation-flow.modal.custom-setup.icon.select": {
|
||||
"defaultMessage": "アイコンを選択"
|
||||
},
|
||||
@@ -869,24 +866,6 @@
|
||||
"creation-flow.modal.import-instance.selection.clear-all": {
|
||||
"defaultMessage": "全ての選択を解除"
|
||||
},
|
||||
"creation-flow.modal.modpack.action.browse": {
|
||||
"defaultMessage": "Modパックを探す"
|
||||
},
|
||||
"creation-flow.modal.modpack.action.import": {
|
||||
"defaultMessage": "Modパックをインポート"
|
||||
},
|
||||
"creation-flow.modal.modpack.known-modpack.prompt": {
|
||||
"defaultMessage": "インストールしたいMODパックは既に決まっていますか?"
|
||||
},
|
||||
"creation-flow.modal.modpack.search.no-results": {
|
||||
"defaultMessage": "見つかりません"
|
||||
},
|
||||
"creation-flow.modal.modpack.search.placeholder": {
|
||||
"defaultMessage": "Modパックを検索"
|
||||
},
|
||||
"creation-flow.modal.setup-type.instance.description": {
|
||||
"defaultMessage": "インスタンスとは、特定のローダー、バージョン、およびModを備えたMinecraftの起動構成のことです"
|
||||
},
|
||||
"creation-flow.modal.setup-type.option.custom-setup.description": {
|
||||
"defaultMessage": "ローダーとゲームのバージョンを選択して、最初から始めましょう"
|
||||
},
|
||||
@@ -920,9 +899,6 @@
|
||||
"creation-flow.modal.setup-type.title.world": {
|
||||
"defaultMessage": "ワールドタイプを選択してください"
|
||||
},
|
||||
"creation-flow.title.choose-modpack": {
|
||||
"defaultMessage": "Modパックを選択"
|
||||
},
|
||||
"creation-flow.title.create-instance": {
|
||||
"defaultMessage": "インスタンスを作成する"
|
||||
},
|
||||
@@ -2054,15 +2030,9 @@
|
||||
"instances.content-install.no-instances": {
|
||||
"defaultMessage": "互換性のあるインスタンスが見つかりませんでした"
|
||||
},
|
||||
"instances.content-install.remove-icon": {
|
||||
"defaultMessage": "アイコンを削除"
|
||||
},
|
||||
"instances.content-install.search-placeholder": {
|
||||
"defaultMessage": "インスタンスを検索"
|
||||
},
|
||||
"instances.content-install.select-icon": {
|
||||
"defaultMessage": "アイコンを選択"
|
||||
},
|
||||
"instances.updater-modal.badge.current": {
|
||||
"defaultMessage": "現在"
|
||||
},
|
||||
@@ -4466,9 +4436,6 @@
|
||||
"servers.list-empty.already-have-server-label": {
|
||||
"defaultMessage": "既にサーバーをお持ちですか?"
|
||||
},
|
||||
"servers.list-empty.learn-more-link": {
|
||||
"defaultMessage": "Modrinth Hostingについて詳しくはこちらをご覧ください"
|
||||
},
|
||||
"servers.list-empty.modrinth-hosting-label": {
|
||||
"defaultMessage": "Modrinth Hosting"
|
||||
},
|
||||
@@ -4478,9 +4445,6 @@
|
||||
"servers.list-empty.no-servers-description": {
|
||||
"defaultMessage": "Modrinth Appを使えば、MODのインストール、友達の招待、そして一緒にプレイすることなど、すべてが可能です。"
|
||||
},
|
||||
"servers.list-empty.no-servers-title": {
|
||||
"defaultMessage": "サーバーはまだありません"
|
||||
},
|
||||
"servers.list-empty.one-click-mod-installs-description": {
|
||||
"defaultMessage": "お好みのModを選んだら、あとはすべて対応いたします。"
|
||||
},
|
||||
@@ -4496,12 +4460,6 @@
|
||||
"servers.list-empty.sign-in-button": {
|
||||
"defaultMessage": "サインイン"
|
||||
},
|
||||
"servers.list-empty.simple-setup-description": {
|
||||
"defaultMessage": "サーバーは、シングルプレイヤーワールドと同じように設定してください。"
|
||||
},
|
||||
"servers.list-empty.simple-setup-title": {
|
||||
"defaultMessage": "簡単なセットアップ"
|
||||
},
|
||||
"servers.listing.copied-label": {
|
||||
"defaultMessage": "コピーしました"
|
||||
},
|
||||
|
||||
@@ -707,9 +707,6 @@
|
||||
"creation-flow.modal.custom-setup.game-version.search-placeholder": {
|
||||
"defaultMessage": "게임 버전 검색..."
|
||||
},
|
||||
"creation-flow.modal.custom-setup.icon.remove": {
|
||||
"defaultMessage": "아이콘 제거"
|
||||
},
|
||||
"creation-flow.modal.custom-setup.icon.select": {
|
||||
"defaultMessage": "아이콘 선택"
|
||||
},
|
||||
@@ -872,24 +869,6 @@
|
||||
"creation-flow.modal.import-instance.selection.clear-all": {
|
||||
"defaultMessage": "모두 해제"
|
||||
},
|
||||
"creation-flow.modal.modpack.action.browse": {
|
||||
"defaultMessage": "모드팩 둘러보기"
|
||||
},
|
||||
"creation-flow.modal.modpack.action.import": {
|
||||
"defaultMessage": "모드팩 가져오기"
|
||||
},
|
||||
"creation-flow.modal.modpack.known-modpack.prompt": {
|
||||
"defaultMessage": "설치하고 싶은 모드팩을 이미 알고 계신가요?"
|
||||
},
|
||||
"creation-flow.modal.modpack.search.no-results": {
|
||||
"defaultMessage": "결과 없음"
|
||||
},
|
||||
"creation-flow.modal.modpack.search.placeholder": {
|
||||
"defaultMessage": "모드팩 검색"
|
||||
},
|
||||
"creation-flow.modal.setup-type.instance.description": {
|
||||
"defaultMessage": "인스턴스란 특정 로더, 버전 및 모드가 적용된 Minecraft 환경을 의미합니다."
|
||||
},
|
||||
"creation-flow.modal.setup-type.option.custom-setup.description": {
|
||||
"defaultMessage": "로더와 게임 버전을 선택하여 처음부터 시작합니다."
|
||||
},
|
||||
@@ -923,9 +902,6 @@
|
||||
"creation-flow.modal.setup-type.title.world": {
|
||||
"defaultMessage": "세계 방식 선택"
|
||||
},
|
||||
"creation-flow.title.choose-modpack": {
|
||||
"defaultMessage": "모드팩 선택"
|
||||
},
|
||||
"creation-flow.title.create-instance": {
|
||||
"defaultMessage": "인스턴스 생성"
|
||||
},
|
||||
@@ -2057,15 +2033,9 @@
|
||||
"instances.content-install.no-instances": {
|
||||
"defaultMessage": "호환가능한 인스턴스 없음"
|
||||
},
|
||||
"instances.content-install.remove-icon": {
|
||||
"defaultMessage": "아이콘 제거"
|
||||
},
|
||||
"instances.content-install.search-placeholder": {
|
||||
"defaultMessage": "인스턴스 검색"
|
||||
},
|
||||
"instances.content-install.select-icon": {
|
||||
"defaultMessage": "아이콘 선택"
|
||||
},
|
||||
"instances.updater-modal.badge.current": {
|
||||
"defaultMessage": "현재"
|
||||
},
|
||||
@@ -4607,9 +4577,6 @@
|
||||
"servers.list-empty.already-have-server-label": {
|
||||
"defaultMessage": "서버가 이미 있으신가요?"
|
||||
},
|
||||
"servers.list-empty.learn-more-link": {
|
||||
"defaultMessage": "Modrinth 호스팅에 관하여 알아보기"
|
||||
},
|
||||
"servers.list-empty.modrinth-hosting-label": {
|
||||
"defaultMessage": "Modrinth 호스팅"
|
||||
},
|
||||
@@ -4619,9 +4586,6 @@
|
||||
"servers.list-empty.no-servers-description": {
|
||||
"defaultMessage": "Modrinth 앱에서 모드를 설치하고, 친구를 초대해 함께 플레이하세요."
|
||||
},
|
||||
"servers.list-empty.no-servers-title": {
|
||||
"defaultMessage": "서버 없음"
|
||||
},
|
||||
"servers.list-empty.one-click-mod-installs-description": {
|
||||
"defaultMessage": "즐겨찾는 모드들을 선택하시고 나머지는 저희에게 맡겨주세요."
|
||||
},
|
||||
@@ -4637,12 +4601,6 @@
|
||||
"servers.list-empty.sign-in-button": {
|
||||
"defaultMessage": "로그인"
|
||||
},
|
||||
"servers.list-empty.simple-setup-description": {
|
||||
"defaultMessage": "싱글 플레이어 월드처럼 서버를 설정하세요."
|
||||
},
|
||||
"servers.list-empty.simple-setup-title": {
|
||||
"defaultMessage": "간단한 설정"
|
||||
},
|
||||
"servers.listing.copied-label": {
|
||||
"defaultMessage": "복사됨"
|
||||
},
|
||||
|
||||
@@ -563,9 +563,6 @@
|
||||
"creation-flow.modal.custom-setup.game-version.search-placeholder": {
|
||||
"defaultMessage": "Cari versi permainan..."
|
||||
},
|
||||
"creation-flow.modal.custom-setup.icon.remove": {
|
||||
"defaultMessage": "Alih keluar ikon"
|
||||
},
|
||||
"creation-flow.modal.custom-setup.icon.select": {
|
||||
"defaultMessage": "Pilih ikon"
|
||||
},
|
||||
@@ -722,21 +719,6 @@
|
||||
"creation-flow.modal.import-instance.selection.clear-all": {
|
||||
"defaultMessage": "Kosongkan semua"
|
||||
},
|
||||
"creation-flow.modal.modpack.action.browse": {
|
||||
"defaultMessage": "Layari pek mod"
|
||||
},
|
||||
"creation-flow.modal.modpack.action.import": {
|
||||
"defaultMessage": "Import pek mod"
|
||||
},
|
||||
"creation-flow.modal.modpack.known-modpack.prompt": {
|
||||
"defaultMessage": "Sudah tahu pek mod yang ingin anda pasang?"
|
||||
},
|
||||
"creation-flow.modal.modpack.search.no-results": {
|
||||
"defaultMessage": "Tiada hasil dijumpai"
|
||||
},
|
||||
"creation-flow.modal.modpack.search.placeholder": {
|
||||
"defaultMessage": "Cari pek mod"
|
||||
},
|
||||
"creation-flow.modal.setup-type.option.custom-setup.title": {
|
||||
"defaultMessage": "Persediaan tersuai"
|
||||
},
|
||||
@@ -758,9 +740,6 @@
|
||||
"creation-flow.modal.setup-type.title.world": {
|
||||
"defaultMessage": "Pilih jenis dunia"
|
||||
},
|
||||
"creation-flow.title.choose-modpack": {
|
||||
"defaultMessage": "Pilih pek mod"
|
||||
},
|
||||
"creation-flow.title.create-instance": {
|
||||
"defaultMessage": "Cipta pemasangan"
|
||||
},
|
||||
@@ -1658,15 +1637,9 @@
|
||||
"instances.content-install.no-instances": {
|
||||
"defaultMessage": "Tiada pemasangan yang serasi dijumpai"
|
||||
},
|
||||
"instances.content-install.remove-icon": {
|
||||
"defaultMessage": "Alih keluar ikon"
|
||||
},
|
||||
"instances.content-install.search-placeholder": {
|
||||
"defaultMessage": "Cari pemasangan"
|
||||
},
|
||||
"instances.content-install.select-icon": {
|
||||
"defaultMessage": "Pilih ikon"
|
||||
},
|
||||
"instances.updater-modal.badge.current": {
|
||||
"defaultMessage": "Semasa"
|
||||
},
|
||||
@@ -3884,9 +3857,6 @@
|
||||
"servers.list-empty.already-have-server-label": {
|
||||
"defaultMessage": "Sudah mempunyai pelayan?"
|
||||
},
|
||||
"servers.list-empty.learn-more-link": {
|
||||
"defaultMessage": "Ketahui lebih lanjut tentang Modrinth Hosting"
|
||||
},
|
||||
"servers.list-empty.modrinth-hosting-label": {
|
||||
"defaultMessage": "Modrinth Hosting"
|
||||
},
|
||||
@@ -3896,9 +3866,6 @@
|
||||
"servers.list-empty.no-servers-description": {
|
||||
"defaultMessage": "Pasang mod, jemput rakan dan main bersama-sama, semuanya dari Modrinth App."
|
||||
},
|
||||
"servers.list-empty.no-servers-title": {
|
||||
"defaultMessage": "Belum ada pelayan"
|
||||
},
|
||||
"servers.list-empty.one-click-mod-installs-description": {
|
||||
"defaultMessage": "Pilih mod kegemaran anda dan kami uruskan selebihnya."
|
||||
},
|
||||
@@ -3914,12 +3881,6 @@
|
||||
"servers.list-empty.sign-in-button": {
|
||||
"defaultMessage": "Daftar masuk"
|
||||
},
|
||||
"servers.list-empty.simple-setup-description": {
|
||||
"defaultMessage": "Sediakan pelayan anda seperti dunia ekapemain."
|
||||
},
|
||||
"servers.list-empty.simple-setup-title": {
|
||||
"defaultMessage": "Persediaan yang mudah"
|
||||
},
|
||||
"servers.listing.copied-label": {
|
||||
"defaultMessage": "Disalin"
|
||||
},
|
||||
|
||||
@@ -707,9 +707,6 @@
|
||||
"creation-flow.modal.custom-setup.game-version.search-placeholder": {
|
||||
"defaultMessage": "Spelversie zoeken..."
|
||||
},
|
||||
"creation-flow.modal.custom-setup.icon.remove": {
|
||||
"defaultMessage": "Pictogram verwijderen"
|
||||
},
|
||||
"creation-flow.modal.custom-setup.icon.select": {
|
||||
"defaultMessage": "Pictogram selecteren"
|
||||
},
|
||||
@@ -872,24 +869,6 @@
|
||||
"creation-flow.modal.import-instance.selection.clear-all": {
|
||||
"defaultMessage": "Alles wissen"
|
||||
},
|
||||
"creation-flow.modal.modpack.action.browse": {
|
||||
"defaultMessage": "Modpacks bekijken"
|
||||
},
|
||||
"creation-flow.modal.modpack.action.import": {
|
||||
"defaultMessage": "Modpack importeren"
|
||||
},
|
||||
"creation-flow.modal.modpack.known-modpack.prompt": {
|
||||
"defaultMessage": "Weet je al welk modpack je wilt installeren?"
|
||||
},
|
||||
"creation-flow.modal.modpack.search.no-results": {
|
||||
"defaultMessage": "Geen resultaten gevonden"
|
||||
},
|
||||
"creation-flow.modal.modpack.search.placeholder": {
|
||||
"defaultMessage": "Modpack zoeken"
|
||||
},
|
||||
"creation-flow.modal.setup-type.instance.description": {
|
||||
"defaultMessage": "Een instantie is een Minecraft-installatie met een specifieke loader, versie en mods."
|
||||
},
|
||||
"creation-flow.modal.setup-type.option.custom-setup.description": {
|
||||
"defaultMessage": "Begin helemaal opnieuw door een loader en een spelversie te kiezen."
|
||||
},
|
||||
@@ -923,9 +902,6 @@
|
||||
"creation-flow.modal.setup-type.title.world": {
|
||||
"defaultMessage": "Wereldtype selecteren"
|
||||
},
|
||||
"creation-flow.title.choose-modpack": {
|
||||
"defaultMessage": "Modpack kiezen"
|
||||
},
|
||||
"creation-flow.title.create-instance": {
|
||||
"defaultMessage": "Instantie aanmaken"
|
||||
},
|
||||
@@ -2054,15 +2030,9 @@
|
||||
"instances.content-install.no-instances": {
|
||||
"defaultMessage": "Er zijn geen compatibele instanties gevonden"
|
||||
},
|
||||
"instances.content-install.remove-icon": {
|
||||
"defaultMessage": "Pictogram verwijderen"
|
||||
},
|
||||
"instances.content-install.search-placeholder": {
|
||||
"defaultMessage": "Zoek instantie"
|
||||
},
|
||||
"instances.content-install.select-icon": {
|
||||
"defaultMessage": "Selecteer pictogram"
|
||||
},
|
||||
"instances.updater-modal.badge.current": {
|
||||
"defaultMessage": "Huidige"
|
||||
},
|
||||
@@ -4613,9 +4583,6 @@
|
||||
"servers.list-empty.already-have-server-label": {
|
||||
"defaultMessage": "Heb je al een server?"
|
||||
},
|
||||
"servers.list-empty.learn-more-link": {
|
||||
"defaultMessage": "Meer informatie over Modrinth Hosting"
|
||||
},
|
||||
"servers.list-empty.modrinth-hosting-label": {
|
||||
"defaultMessage": "Modrinth Hosting"
|
||||
},
|
||||
@@ -4625,9 +4592,6 @@
|
||||
"servers.list-empty.no-servers-description": {
|
||||
"defaultMessage": "Installeer mods, nodig vrienden uit en speel samen – allemaal via de Modrinth app."
|
||||
},
|
||||
"servers.list-empty.no-servers-title": {
|
||||
"defaultMessage": "Nog geen servers"
|
||||
},
|
||||
"servers.list-empty.one-click-mod-installs-description": {
|
||||
"defaultMessage": "Kies je favoriete mods en wij regelen de rest."
|
||||
},
|
||||
@@ -4643,12 +4607,6 @@
|
||||
"servers.list-empty.sign-in-button": {
|
||||
"defaultMessage": "Inloggen"
|
||||
},
|
||||
"servers.list-empty.simple-setup-description": {
|
||||
"defaultMessage": "Stel je server in net zoals een eigen wereld."
|
||||
},
|
||||
"servers.list-empty.simple-setup-title": {
|
||||
"defaultMessage": "Eenvoudige set-up"
|
||||
},
|
||||
"servers.listing.copied-label": {
|
||||
"defaultMessage": "Gekopieerd"
|
||||
},
|
||||
|
||||
@@ -437,9 +437,6 @@
|
||||
"creation-flow.modal.custom-setup.game-version.search-placeholder": {
|
||||
"defaultMessage": "Søk etter spillversjon..."
|
||||
},
|
||||
"creation-flow.modal.custom-setup.icon.remove": {
|
||||
"defaultMessage": "Fjern ikon"
|
||||
},
|
||||
"creation-flow.modal.custom-setup.icon.select": {
|
||||
"defaultMessage": "Velg ikon"
|
||||
},
|
||||
@@ -461,12 +458,6 @@
|
||||
"creation-flow.modal.import-instance.search.placeholder": {
|
||||
"defaultMessage": "Søk etter instansnavn"
|
||||
},
|
||||
"creation-flow.modal.modpack.search.no-results": {
|
||||
"defaultMessage": "Ingen resultater funnet"
|
||||
},
|
||||
"creation-flow.modal.modpack.search.placeholder": {
|
||||
"defaultMessage": "Søk etter modpakke"
|
||||
},
|
||||
"external-files.permissions-card.add-files-modal.selected-count": {
|
||||
"defaultMessage": "{count, plural, one {# fil} other {# filer}} valgt"
|
||||
},
|
||||
@@ -851,15 +842,9 @@
|
||||
"instances.content-install.new-tab": {
|
||||
"defaultMessage": "Ny instans"
|
||||
},
|
||||
"instances.content-install.remove-icon": {
|
||||
"defaultMessage": "Fjern ikon"
|
||||
},
|
||||
"instances.content-install.search-placeholder": {
|
||||
"defaultMessage": "Søk etter instans"
|
||||
},
|
||||
"instances.content-install.select-icon": {
|
||||
"defaultMessage": "Velg ikon"
|
||||
},
|
||||
"instances.updater-modal.badge.current": {
|
||||
"defaultMessage": "Nåværende"
|
||||
},
|
||||
|
||||
@@ -707,9 +707,6 @@
|
||||
"creation-flow.modal.custom-setup.game-version.search-placeholder": {
|
||||
"defaultMessage": "Szukaj wersję gry..."
|
||||
},
|
||||
"creation-flow.modal.custom-setup.icon.remove": {
|
||||
"defaultMessage": "Usuń ikonę"
|
||||
},
|
||||
"creation-flow.modal.custom-setup.icon.select": {
|
||||
"defaultMessage": "Wybierz ikonę"
|
||||
},
|
||||
@@ -872,24 +869,6 @@
|
||||
"creation-flow.modal.import-instance.selection.clear-all": {
|
||||
"defaultMessage": "Wyczyść wszystko"
|
||||
},
|
||||
"creation-flow.modal.modpack.action.browse": {
|
||||
"defaultMessage": "Przeglądaj paczki modów"
|
||||
},
|
||||
"creation-flow.modal.modpack.action.import": {
|
||||
"defaultMessage": "Importuj paczkę modów"
|
||||
},
|
||||
"creation-flow.modal.modpack.known-modpack.prompt": {
|
||||
"defaultMessage": "Wiesz już którą paczkę modów chcesz zainstalować?"
|
||||
},
|
||||
"creation-flow.modal.modpack.search.no-results": {
|
||||
"defaultMessage": "Brak wyników wyszukiwania"
|
||||
},
|
||||
"creation-flow.modal.modpack.search.placeholder": {
|
||||
"defaultMessage": "Szukaj paczki modów"
|
||||
},
|
||||
"creation-flow.modal.setup-type.instance.description": {
|
||||
"defaultMessage": "Instancja to instalacja Minecraft w danej wersji, z danym loaderem i modami."
|
||||
},
|
||||
"creation-flow.modal.setup-type.option.custom-setup.description": {
|
||||
"defaultMessage": "Zacznij od podstaw wybierając loader i wersję gry."
|
||||
},
|
||||
@@ -923,9 +902,6 @@
|
||||
"creation-flow.modal.setup-type.title.world": {
|
||||
"defaultMessage": "Wybierz typ świata"
|
||||
},
|
||||
"creation-flow.title.choose-modpack": {
|
||||
"defaultMessage": "Wybierz paczkę modów"
|
||||
},
|
||||
"creation-flow.title.create-instance": {
|
||||
"defaultMessage": "Utwórz instancję"
|
||||
},
|
||||
@@ -2048,15 +2024,9 @@
|
||||
"instances.content-install.no-instances": {
|
||||
"defaultMessage": "Nie znaleziono kompatybilnych instancji"
|
||||
},
|
||||
"instances.content-install.remove-icon": {
|
||||
"defaultMessage": "Usuń ikonę"
|
||||
},
|
||||
"instances.content-install.search-placeholder": {
|
||||
"defaultMessage": "Szukaj instancji"
|
||||
},
|
||||
"instances.content-install.select-icon": {
|
||||
"defaultMessage": "Wybierz ikonę"
|
||||
},
|
||||
"instances.updater-modal.badge.current": {
|
||||
"defaultMessage": "Aktualny"
|
||||
},
|
||||
@@ -4589,9 +4559,6 @@
|
||||
"servers.list-empty.already-have-server-label": {
|
||||
"defaultMessage": "Posiadasz już serwer?"
|
||||
},
|
||||
"servers.list-empty.learn-more-link": {
|
||||
"defaultMessage": "Dowiedz się więcej na temat Modrinth Hosting"
|
||||
},
|
||||
"servers.list-empty.modrinth-hosting-label": {
|
||||
"defaultMessage": "Modrinth Hosting"
|
||||
},
|
||||
@@ -4601,9 +4568,6 @@
|
||||
"servers.list-empty.no-servers-description": {
|
||||
"defaultMessage": "Instaluj modyfikacje, zapraszaj znajomych i graj z nimi przy użyciu Modrinth App."
|
||||
},
|
||||
"servers.list-empty.no-servers-title": {
|
||||
"defaultMessage": "Brak serwerów"
|
||||
},
|
||||
"servers.list-empty.one-click-mod-installs-description": {
|
||||
"defaultMessage": "Wybierz swoje ulubione mody, a my zajmiemy się resztą."
|
||||
},
|
||||
@@ -4619,12 +4583,6 @@
|
||||
"servers.list-empty.sign-in-button": {
|
||||
"defaultMessage": "Zaloguj się"
|
||||
},
|
||||
"servers.list-empty.simple-setup-description": {
|
||||
"defaultMessage": "Ustaw serwer tak, jak zwykły świat w trybie jednoosobowym."
|
||||
},
|
||||
"servers.list-empty.simple-setup-title": {
|
||||
"defaultMessage": "Prosta konfiguracja"
|
||||
},
|
||||
"servers.listing.copied-label": {
|
||||
"defaultMessage": "Skopiowano"
|
||||
},
|
||||
|
||||
@@ -707,9 +707,6 @@
|
||||
"creation-flow.modal.custom-setup.game-version.search-placeholder": {
|
||||
"defaultMessage": "Procurar versão de jogo..."
|
||||
},
|
||||
"creation-flow.modal.custom-setup.icon.remove": {
|
||||
"defaultMessage": "Remover ícone"
|
||||
},
|
||||
"creation-flow.modal.custom-setup.icon.select": {
|
||||
"defaultMessage": "Selecionar ícone"
|
||||
},
|
||||
@@ -872,24 +869,6 @@
|
||||
"creation-flow.modal.import-instance.selection.clear-all": {
|
||||
"defaultMessage": "Limpar tudo"
|
||||
},
|
||||
"creation-flow.modal.modpack.action.browse": {
|
||||
"defaultMessage": "Navegue pelos pacotes de mods"
|
||||
},
|
||||
"creation-flow.modal.modpack.action.import": {
|
||||
"defaultMessage": "Importar pacote de mods"
|
||||
},
|
||||
"creation-flow.modal.modpack.known-modpack.prompt": {
|
||||
"defaultMessage": "Já sabe qual pacote de mods deseja instalar?"
|
||||
},
|
||||
"creation-flow.modal.modpack.search.no-results": {
|
||||
"defaultMessage": "Nenhum resultado encontrado"
|
||||
},
|
||||
"creation-flow.modal.modpack.search.placeholder": {
|
||||
"defaultMessage": "Buscar pacotes de mods"
|
||||
},
|
||||
"creation-flow.modal.setup-type.instance.description": {
|
||||
"defaultMessage": "Uma instância é uma configuração do Minecraft com um loader, versão e mods específicos."
|
||||
},
|
||||
"creation-flow.modal.setup-type.option.custom-setup.description": {
|
||||
"defaultMessage": "Comece do zero, escolhendo um loader e uma versão do jogo."
|
||||
},
|
||||
@@ -923,9 +902,6 @@
|
||||
"creation-flow.modal.setup-type.title.world": {
|
||||
"defaultMessage": "Selecione o tipo de mundo"
|
||||
},
|
||||
"creation-flow.title.choose-modpack": {
|
||||
"defaultMessage": "Escolha o pacote de mods"
|
||||
},
|
||||
"creation-flow.title.create-instance": {
|
||||
"defaultMessage": "Criar instância"
|
||||
},
|
||||
@@ -2057,15 +2033,9 @@
|
||||
"instances.content-install.no-instances": {
|
||||
"defaultMessage": "Nenhuma instância compatível encontrada"
|
||||
},
|
||||
"instances.content-install.remove-icon": {
|
||||
"defaultMessage": "Remover ícone"
|
||||
},
|
||||
"instances.content-install.search-placeholder": {
|
||||
"defaultMessage": "Buscar instância"
|
||||
},
|
||||
"instances.content-install.select-icon": {
|
||||
"defaultMessage": "Selecionar ícone"
|
||||
},
|
||||
"instances.updater-modal.badge.current": {
|
||||
"defaultMessage": "Atual"
|
||||
},
|
||||
@@ -4622,9 +4592,6 @@
|
||||
"servers.list-empty.already-have-server-label": {
|
||||
"defaultMessage": "Já possui um servidor?"
|
||||
},
|
||||
"servers.list-empty.learn-more-link": {
|
||||
"defaultMessage": "Saiba mais sobre o Modrinth Hosting"
|
||||
},
|
||||
"servers.list-empty.modrinth-hosting-label": {
|
||||
"defaultMessage": "Modrinth Hosting"
|
||||
},
|
||||
@@ -4634,9 +4601,6 @@
|
||||
"servers.list-empty.no-servers-description": {
|
||||
"defaultMessage": "Instale mods, convide amigos e joguem juntos, tudo pelo Modrinth App."
|
||||
},
|
||||
"servers.list-empty.no-servers-title": {
|
||||
"defaultMessage": "Ainda não há servidores"
|
||||
},
|
||||
"servers.list-empty.one-click-mod-installs-description": {
|
||||
"defaultMessage": "Escolha seus mods favoritos e nós cuidamos do resto."
|
||||
},
|
||||
@@ -4652,12 +4616,6 @@
|
||||
"servers.list-empty.sign-in-button": {
|
||||
"defaultMessage": "Inicie a sessão"
|
||||
},
|
||||
"servers.list-empty.simple-setup-description": {
|
||||
"defaultMessage": "Configure seu servidor exatamente como um mundo de um jogador."
|
||||
},
|
||||
"servers.list-empty.simple-setup-title": {
|
||||
"defaultMessage": "Configuração simples"
|
||||
},
|
||||
"servers.listing.copied-label": {
|
||||
"defaultMessage": "Copiado"
|
||||
},
|
||||
|
||||
@@ -1001,15 +1001,9 @@
|
||||
"instances.content-install.no-instances": {
|
||||
"defaultMessage": "Nu s-au găsit instanțe compatibile"
|
||||
},
|
||||
"instances.content-install.remove-icon": {
|
||||
"defaultMessage": "Înlăturare iconiță"
|
||||
},
|
||||
"instances.content-install.search-placeholder": {
|
||||
"defaultMessage": "Căutare instanță"
|
||||
},
|
||||
"instances.content-install.select-icon": {
|
||||
"defaultMessage": "Selectează iconița"
|
||||
},
|
||||
"instances.updater-modal.badge.current": {
|
||||
"defaultMessage": "Curent"
|
||||
},
|
||||
|
||||
@@ -647,9 +647,6 @@
|
||||
"creation-flow.modal.custom-setup.game-version.search-placeholder": {
|
||||
"defaultMessage": "Поиск версии игры..."
|
||||
},
|
||||
"creation-flow.modal.custom-setup.icon.remove": {
|
||||
"defaultMessage": "Удалить иконку"
|
||||
},
|
||||
"creation-flow.modal.custom-setup.icon.select": {
|
||||
"defaultMessage": "Выбрать иконку"
|
||||
},
|
||||
@@ -812,24 +809,6 @@
|
||||
"creation-flow.modal.import-instance.selection.clear-all": {
|
||||
"defaultMessage": "Очистить всë"
|
||||
},
|
||||
"creation-flow.modal.modpack.action.browse": {
|
||||
"defaultMessage": "Найти сборки"
|
||||
},
|
||||
"creation-flow.modal.modpack.action.import": {
|
||||
"defaultMessage": "Импортировать сборку"
|
||||
},
|
||||
"creation-flow.modal.modpack.known-modpack.prompt": {
|
||||
"defaultMessage": "Уже знаете, какую сборку хотите скачать?"
|
||||
},
|
||||
"creation-flow.modal.modpack.search.no-results": {
|
||||
"defaultMessage": "Ничего не найдено"
|
||||
},
|
||||
"creation-flow.modal.modpack.search.placeholder": {
|
||||
"defaultMessage": "Поиск сборки"
|
||||
},
|
||||
"creation-flow.modal.setup-type.instance.description": {
|
||||
"defaultMessage": "Сборка — это установка Minecraft с определённым загрузчиком модов, версией и модами."
|
||||
},
|
||||
"creation-flow.modal.setup-type.option.custom-setup.description": {
|
||||
"defaultMessage": "Начать с нуля, выбрав загрузчик и версию игры."
|
||||
},
|
||||
@@ -863,9 +842,6 @@
|
||||
"creation-flow.modal.setup-type.title.world": {
|
||||
"defaultMessage": "Выберите тип мира"
|
||||
},
|
||||
"creation-flow.title.choose-modpack": {
|
||||
"defaultMessage": "Выберите сборку"
|
||||
},
|
||||
"creation-flow.title.create-instance": {
|
||||
"defaultMessage": "Создание сборки"
|
||||
},
|
||||
@@ -1973,15 +1949,9 @@
|
||||
"instances.content-install.no-instances": {
|
||||
"defaultMessage": "Совместимые сборки не найдены"
|
||||
},
|
||||
"instances.content-install.remove-icon": {
|
||||
"defaultMessage": "Удалить иконку"
|
||||
},
|
||||
"instances.content-install.search-placeholder": {
|
||||
"defaultMessage": "Поиск сборки"
|
||||
},
|
||||
"instances.content-install.select-icon": {
|
||||
"defaultMessage": "Выбрать иконку"
|
||||
},
|
||||
"instances.updater-modal.badge.current": {
|
||||
"defaultMessage": "Текущая"
|
||||
},
|
||||
@@ -4535,9 +4505,6 @@
|
||||
"servers.list-empty.already-have-server-label": {
|
||||
"defaultMessage": "Уже есть сервер?"
|
||||
},
|
||||
"servers.list-empty.learn-more-link": {
|
||||
"defaultMessage": "Подробнее о Modrinth Hosting"
|
||||
},
|
||||
"servers.list-empty.modrinth-hosting-label": {
|
||||
"defaultMessage": "Modrinth Hosting"
|
||||
},
|
||||
@@ -4547,9 +4514,6 @@
|
||||
"servers.list-empty.no-servers-description": {
|
||||
"defaultMessage": "Играйте в сборки вместе с друзьями через Modrinth App."
|
||||
},
|
||||
"servers.list-empty.no-servers-title": {
|
||||
"defaultMessage": "Серверов ещё нет"
|
||||
},
|
||||
"servers.list-empty.one-click-mod-installs-description": {
|
||||
"defaultMessage": "Выбирайте любимые моды, а мы сделаем всё остальное."
|
||||
},
|
||||
@@ -4565,12 +4529,6 @@
|
||||
"servers.list-empty.sign-in-button": {
|
||||
"defaultMessage": "Войти"
|
||||
},
|
||||
"servers.list-empty.simple-setup-description": {
|
||||
"defaultMessage": "Настраивайте сервер подобно одиночному миру."
|
||||
},
|
||||
"servers.list-empty.simple-setup-title": {
|
||||
"defaultMessage": "Простая настройка"
|
||||
},
|
||||
"servers.listing.copied-label": {
|
||||
"defaultMessage": "Скопировано"
|
||||
},
|
||||
|
||||
@@ -674,9 +674,6 @@
|
||||
"creation-flow.modal.custom-setup.game-version.search-placeholder": {
|
||||
"defaultMessage": "Traži verziju igre..."
|
||||
},
|
||||
"creation-flow.modal.custom-setup.icon.remove": {
|
||||
"defaultMessage": "Smakni ikonu"
|
||||
},
|
||||
"creation-flow.modal.custom-setup.icon.select": {
|
||||
"defaultMessage": "Izaberi ikonu"
|
||||
},
|
||||
@@ -839,24 +836,6 @@
|
||||
"creation-flow.modal.import-instance.selection.clear-all": {
|
||||
"defaultMessage": "Obriši sve"
|
||||
},
|
||||
"creation-flow.modal.modpack.action.browse": {
|
||||
"defaultMessage": "Pregledaj modpakove"
|
||||
},
|
||||
"creation-flow.modal.modpack.action.import": {
|
||||
"defaultMessage": "Uvezi modpack"
|
||||
},
|
||||
"creation-flow.modal.modpack.known-modpack.prompt": {
|
||||
"defaultMessage": "Već znaš koji modpak želiš da instaliraš?"
|
||||
},
|
||||
"creation-flow.modal.modpack.search.no-results": {
|
||||
"defaultMessage": "Nema pronađenih rezultata"
|
||||
},
|
||||
"creation-flow.modal.modpack.search.placeholder": {
|
||||
"defaultMessage": "Potraži modpack"
|
||||
},
|
||||
"creation-flow.modal.setup-type.instance.description": {
|
||||
"defaultMessage": "Instanca je podešavanje Mínecrafta sa određenim programom za učitavanje, verzijom i modovima."
|
||||
},
|
||||
"creation-flow.modal.setup-type.option.custom-setup.description": {
|
||||
"defaultMessage": "Počni od nule tako što izabereš pokretač i verziju igre."
|
||||
},
|
||||
@@ -890,9 +869,6 @@
|
||||
"creation-flow.modal.setup-type.title.world": {
|
||||
"defaultMessage": "Izaberi tip sveta"
|
||||
},
|
||||
"creation-flow.title.choose-modpack": {
|
||||
"defaultMessage": "Izaberi modpack"
|
||||
},
|
||||
"creation-flow.title.create-instance": {
|
||||
"defaultMessage": "Kreiraj instancu"
|
||||
},
|
||||
@@ -2009,15 +1985,9 @@
|
||||
"instances.content-install.no-instances": {
|
||||
"defaultMessage": "Nema pronađenih kompatibilnih instanca"
|
||||
},
|
||||
"instances.content-install.remove-icon": {
|
||||
"defaultMessage": "Smakni ikonu"
|
||||
},
|
||||
"instances.content-install.search-placeholder": {
|
||||
"defaultMessage": "Pretraži instancu"
|
||||
},
|
||||
"instances.content-install.select-icon": {
|
||||
"defaultMessage": "Izaberi ikonu"
|
||||
},
|
||||
"instances.updater-modal.badge.current": {
|
||||
"defaultMessage": "Upravo"
|
||||
},
|
||||
@@ -4391,9 +4361,6 @@
|
||||
"servers.list-empty.already-have-server-label": {
|
||||
"defaultMessage": "Već imaš server?"
|
||||
},
|
||||
"servers.list-empty.learn-more-link": {
|
||||
"defaultMessage": "Saznaj više o Modrinth hostiranju"
|
||||
},
|
||||
"servers.list-empty.modrinth-hosting-label": {
|
||||
"defaultMessage": "Modrinth hostiranje"
|
||||
},
|
||||
@@ -4403,9 +4370,6 @@
|
||||
"servers.list-empty.no-servers-description": {
|
||||
"defaultMessage": "Instaliraj modove, pozvi prijatelje, i igraj skupa ave u Modrinth aplikaciji."
|
||||
},
|
||||
"servers.list-empty.no-servers-title": {
|
||||
"defaultMessage": "Nema još servera"
|
||||
},
|
||||
"servers.list-empty.one-click-mod-installs-description": {
|
||||
"defaultMessage": "Izaberi svoje omiljene modove, a mi ćemo se pobrinuti za ostalo."
|
||||
},
|
||||
@@ -4421,12 +4385,6 @@
|
||||
"servers.list-empty.sign-in-button": {
|
||||
"defaultMessage": "Prijavi se"
|
||||
},
|
||||
"servers.list-empty.simple-setup-description": {
|
||||
"defaultMessage": "Podesi svoj server baš kao samostalni svet."
|
||||
},
|
||||
"servers.list-empty.simple-setup-title": {
|
||||
"defaultMessage": "Jednostavno podešavanje"
|
||||
},
|
||||
"servers.listing.copied-label": {
|
||||
"defaultMessage": "Kopirano"
|
||||
},
|
||||
|
||||
@@ -701,9 +701,6 @@
|
||||
"creation-flow.modal.custom-setup.game-version.search-placeholder": {
|
||||
"defaultMessage": "Sök spelversion..."
|
||||
},
|
||||
"creation-flow.modal.custom-setup.icon.remove": {
|
||||
"defaultMessage": "Ta bort ikon"
|
||||
},
|
||||
"creation-flow.modal.custom-setup.icon.select": {
|
||||
"defaultMessage": "Välj ikon"
|
||||
},
|
||||
@@ -824,24 +821,6 @@
|
||||
"creation-flow.modal.import-instance.selection.clear-all": {
|
||||
"defaultMessage": "Töm allt"
|
||||
},
|
||||
"creation-flow.modal.modpack.action.browse": {
|
||||
"defaultMessage": "Bläddra bland modpaket"
|
||||
},
|
||||
"creation-flow.modal.modpack.action.import": {
|
||||
"defaultMessage": "Importera modpaket"
|
||||
},
|
||||
"creation-flow.modal.modpack.known-modpack.prompt": {
|
||||
"defaultMessage": "Vet du redan modpaketet du vill installera?"
|
||||
},
|
||||
"creation-flow.modal.modpack.search.no-results": {
|
||||
"defaultMessage": "Inga resultat hittades"
|
||||
},
|
||||
"creation-flow.modal.modpack.search.placeholder": {
|
||||
"defaultMessage": "Sök efter modpaket"
|
||||
},
|
||||
"creation-flow.modal.setup-type.instance.description": {
|
||||
"defaultMessage": "En instans är ett Minecraft-upplägg med en specifik laddare, version och moddar."
|
||||
},
|
||||
"creation-flow.modal.setup-type.option.custom-setup.title": {
|
||||
"defaultMessage": "Anpassat upplägg"
|
||||
},
|
||||
@@ -872,9 +851,6 @@
|
||||
"creation-flow.modal.setup-type.title.world": {
|
||||
"defaultMessage": "Välj världstyp"
|
||||
},
|
||||
"creation-flow.title.choose-modpack": {
|
||||
"defaultMessage": "Välj modpaket"
|
||||
},
|
||||
"creation-flow.title.create-instance": {
|
||||
"defaultMessage": "Skapa instans"
|
||||
},
|
||||
@@ -1778,15 +1754,9 @@
|
||||
"instances.content-install.no-instances": {
|
||||
"defaultMessage": "Inga kompatibla instanser hittades"
|
||||
},
|
||||
"instances.content-install.remove-icon": {
|
||||
"defaultMessage": "Radera ikon"
|
||||
},
|
||||
"instances.content-install.search-placeholder": {
|
||||
"defaultMessage": "Sök för instans"
|
||||
},
|
||||
"instances.content-install.select-icon": {
|
||||
"defaultMessage": "Välj ikon"
|
||||
},
|
||||
"instances.updater-modal.badge.current": {
|
||||
"defaultMessage": "Nuvarande"
|
||||
},
|
||||
@@ -4160,9 +4130,6 @@
|
||||
"servers.list-empty.no-servers-description": {
|
||||
"defaultMessage": "Installera moddar, bjud in vänner och spela tillsammans, allt från Modrinth App."
|
||||
},
|
||||
"servers.list-empty.no-servers-title": {
|
||||
"defaultMessage": "Inga servrar än"
|
||||
},
|
||||
"servers.list-empty.one-click-mod-installs-description": {
|
||||
"defaultMessage": "Välj dina favorit moddar så hanterar vi resten."
|
||||
},
|
||||
|
||||
@@ -308,15 +308,9 @@
|
||||
"instances.content-install.new-tab": {
|
||||
"defaultMessage": "อินสแตนซ์อันใหม่"
|
||||
},
|
||||
"instances.content-install.remove-icon": {
|
||||
"defaultMessage": "ลบไอคอน"
|
||||
},
|
||||
"instances.content-install.search-placeholder": {
|
||||
"defaultMessage": "ค้นหาอินสแตนซ์"
|
||||
},
|
||||
"instances.content-install.select-icon": {
|
||||
"defaultMessage": "เลือกไอคอน"
|
||||
},
|
||||
"label.available": {
|
||||
"defaultMessage": "ว่างอยู่ {amount}"
|
||||
},
|
||||
|
||||
@@ -686,9 +686,6 @@
|
||||
"creation-flow.modal.custom-setup.game-version.search-placeholder": {
|
||||
"defaultMessage": "Oyun sürümü ara..."
|
||||
},
|
||||
"creation-flow.modal.custom-setup.icon.remove": {
|
||||
"defaultMessage": "Simgeyi kaldır"
|
||||
},
|
||||
"creation-flow.modal.custom-setup.icon.select": {
|
||||
"defaultMessage": "Simge seç"
|
||||
},
|
||||
@@ -851,24 +848,6 @@
|
||||
"creation-flow.modal.import-instance.selection.clear-all": {
|
||||
"defaultMessage": "Tümünü temizle"
|
||||
},
|
||||
"creation-flow.modal.modpack.action.browse": {
|
||||
"defaultMessage": "Modpacklere göz atın"
|
||||
},
|
||||
"creation-flow.modal.modpack.action.import": {
|
||||
"defaultMessage": "Mod paketini dışa aktar"
|
||||
},
|
||||
"creation-flow.modal.modpack.known-modpack.prompt": {
|
||||
"defaultMessage": "Yüklemek istediğiniz mod paketini zaten biliyor musunuz?"
|
||||
},
|
||||
"creation-flow.modal.modpack.search.no-results": {
|
||||
"defaultMessage": "Sonuç bulunamadı"
|
||||
},
|
||||
"creation-flow.modal.modpack.search.placeholder": {
|
||||
"defaultMessage": "Mod paketi ara"
|
||||
},
|
||||
"creation-flow.modal.setup-type.instance.description": {
|
||||
"defaultMessage": "Her bir 'kurulum', kendine has yükleyicisi (modloader), sürümü ve modları olan bir Minecraft kurulumudur."
|
||||
},
|
||||
"creation-flow.modal.setup-type.option.custom-setup.description": {
|
||||
"defaultMessage": "Bir yükleyici ve sürüm seçerek başlayın."
|
||||
},
|
||||
@@ -902,9 +881,6 @@
|
||||
"creation-flow.modal.setup-type.title.world": {
|
||||
"defaultMessage": "Dünya Türünü Seçin"
|
||||
},
|
||||
"creation-flow.title.choose-modpack": {
|
||||
"defaultMessage": "Modpack seçin"
|
||||
},
|
||||
"creation-flow.title.create-instance": {
|
||||
"defaultMessage": "Kurulum oluştur"
|
||||
},
|
||||
@@ -2030,15 +2006,9 @@
|
||||
"instances.content-install.no-instances": {
|
||||
"defaultMessage": "Uyumlu kurulum yok"
|
||||
},
|
||||
"instances.content-install.remove-icon": {
|
||||
"defaultMessage": "Simgeyi kaldır"
|
||||
},
|
||||
"instances.content-install.search-placeholder": {
|
||||
"defaultMessage": "Kurulum ara"
|
||||
},
|
||||
"instances.content-install.select-icon": {
|
||||
"defaultMessage": "Simge seç"
|
||||
},
|
||||
"instances.updater-modal.badge.current": {
|
||||
"defaultMessage": "Şu anki"
|
||||
},
|
||||
@@ -4442,9 +4412,6 @@
|
||||
"servers.list-empty.already-have-server-label": {
|
||||
"defaultMessage": "Zaten sunucunuz var mı?"
|
||||
},
|
||||
"servers.list-empty.learn-more-link": {
|
||||
"defaultMessage": "Modrinth Hosting hakkında daha fazla bilgi edinin"
|
||||
},
|
||||
"servers.list-empty.modrinth-hosting-label": {
|
||||
"defaultMessage": "Modrinth Hosting"
|
||||
},
|
||||
@@ -4454,9 +4421,6 @@
|
||||
"servers.list-empty.no-servers-description": {
|
||||
"defaultMessage": "Mod kur, arkadaş davet et, birlikte oyna ve hepsini Modrinth App'ten yap."
|
||||
},
|
||||
"servers.list-empty.no-servers-title": {
|
||||
"defaultMessage": "Daha sunucu yok"
|
||||
},
|
||||
"servers.list-empty.one-click-mod-installs-description": {
|
||||
"defaultMessage": "Beğendiğiniz modları seçin ve biz geri kalan işi hallederiz."
|
||||
},
|
||||
@@ -4472,12 +4436,6 @@
|
||||
"servers.list-empty.sign-in-button": {
|
||||
"defaultMessage": "Giriş yap"
|
||||
},
|
||||
"servers.list-empty.simple-setup-description": {
|
||||
"defaultMessage": "Sunucunuzu tıpkı tek oyunculu bir dünya gibi kurun."
|
||||
},
|
||||
"servers.list-empty.simple-setup-title": {
|
||||
"defaultMessage": "Basit kurulum"
|
||||
},
|
||||
"servers.listing.copied-label": {
|
||||
"defaultMessage": "Kopyalandı"
|
||||
},
|
||||
|
||||
@@ -662,9 +662,6 @@
|
||||
"creation-flow.modal.custom-setup.game-version.search-placeholder": {
|
||||
"defaultMessage": "Знайти версію гри…"
|
||||
},
|
||||
"creation-flow.modal.custom-setup.icon.remove": {
|
||||
"defaultMessage": "Видалити значок"
|
||||
},
|
||||
"creation-flow.modal.custom-setup.icon.select": {
|
||||
"defaultMessage": "Вибрати значок"
|
||||
},
|
||||
@@ -842,9 +839,6 @@
|
||||
"creation-flow.modal.modpack.search.placeholder": {
|
||||
"defaultMessage": "Пошук збірок"
|
||||
},
|
||||
"creation-flow.modal.setup-type.instance.description": {
|
||||
"defaultMessage": "Профіль — це налаштування Minecraft із певним завантажувачем, версією та модами."
|
||||
},
|
||||
"creation-flow.modal.setup-type.option.custom-setup.description": {
|
||||
"defaultMessage": "Почніть з нуля, вибравши завантажувач і версію гри."
|
||||
},
|
||||
@@ -878,9 +872,6 @@
|
||||
"creation-flow.modal.setup-type.title.world": {
|
||||
"defaultMessage": "Виберіть тип світу"
|
||||
},
|
||||
"creation-flow.title.choose-modpack": {
|
||||
"defaultMessage": "Виберіть збірку"
|
||||
},
|
||||
"creation-flow.title.create-instance": {
|
||||
"defaultMessage": "Створити збірку"
|
||||
},
|
||||
@@ -2012,15 +2003,9 @@
|
||||
"instances.content-install.no-instances": {
|
||||
"defaultMessage": "Не знайдено сумісних профілів"
|
||||
},
|
||||
"instances.content-install.remove-icon": {
|
||||
"defaultMessage": "Прибрати значок"
|
||||
},
|
||||
"instances.content-install.search-placeholder": {
|
||||
"defaultMessage": "Пошук профілів"
|
||||
},
|
||||
"instances.content-install.select-icon": {
|
||||
"defaultMessage": "Вибрати значок"
|
||||
},
|
||||
"instances.updater-modal.badge.current": {
|
||||
"defaultMessage": "Поточна"
|
||||
},
|
||||
@@ -4589,9 +4574,6 @@
|
||||
"servers.list-empty.no-servers-description": {
|
||||
"defaultMessage": "Установлюйте моди, запрошуйте друзів і грайте разом із Modrinth App."
|
||||
},
|
||||
"servers.list-empty.no-servers-title": {
|
||||
"defaultMessage": "Серверів ще немає"
|
||||
},
|
||||
"servers.list-empty.one-click-mod-installs-description": {
|
||||
"defaultMessage": "Виберіть свої улюблені моди, а ми подбаємо про решту."
|
||||
},
|
||||
|
||||
@@ -608,9 +608,6 @@
|
||||
"creation-flow.modal.custom-setup.game-version.search-placeholder": {
|
||||
"defaultMessage": "Tìm phiên bản Game..."
|
||||
},
|
||||
"creation-flow.modal.custom-setup.icon.remove": {
|
||||
"defaultMessage": "Xóa biểu tượng"
|
||||
},
|
||||
"creation-flow.modal.custom-setup.icon.select": {
|
||||
"defaultMessage": "Chọn biểu tượng"
|
||||
},
|
||||
@@ -773,24 +770,6 @@
|
||||
"creation-flow.modal.import-instance.selection.clear-all": {
|
||||
"defaultMessage": "Xóa hết"
|
||||
},
|
||||
"creation-flow.modal.modpack.action.browse": {
|
||||
"defaultMessage": "Duyệt modpack"
|
||||
},
|
||||
"creation-flow.modal.modpack.action.import": {
|
||||
"defaultMessage": "Thêm modpack"
|
||||
},
|
||||
"creation-flow.modal.modpack.known-modpack.prompt": {
|
||||
"defaultMessage": "Biết modpack bạn định cài là gì rồi?"
|
||||
},
|
||||
"creation-flow.modal.modpack.search.no-results": {
|
||||
"defaultMessage": "Không tìm thấy"
|
||||
},
|
||||
"creation-flow.modal.modpack.search.placeholder": {
|
||||
"defaultMessage": "Tìm modpack"
|
||||
},
|
||||
"creation-flow.modal.setup-type.instance.description": {
|
||||
"defaultMessage": "Cấu hình là một bản cài Minecraft với mod, phiên bản và loader của riêng nó."
|
||||
},
|
||||
"creation-flow.modal.setup-type.option.custom-setup.description": {
|
||||
"defaultMessage": "Cài từ đầu bằng cách lựa chọn mod loader và phiên bản game."
|
||||
},
|
||||
@@ -824,9 +803,6 @@
|
||||
"creation-flow.modal.setup-type.title.world": {
|
||||
"defaultMessage": "Chọn loại thế giới"
|
||||
},
|
||||
"creation-flow.title.choose-modpack": {
|
||||
"defaultMessage": "Chọn modpack"
|
||||
},
|
||||
"creation-flow.title.create-instance": {
|
||||
"defaultMessage": "Tạo cấu hình"
|
||||
},
|
||||
@@ -1688,15 +1664,9 @@
|
||||
"instances.content-install.no-instances": {
|
||||
"defaultMessage": "Không tìm thấy phiên chơi tương thích"
|
||||
},
|
||||
"instances.content-install.remove-icon": {
|
||||
"defaultMessage": "Xoá biểu tượng"
|
||||
},
|
||||
"instances.content-install.search-placeholder": {
|
||||
"defaultMessage": "Tìm phiên chơi"
|
||||
},
|
||||
"instances.content-install.select-icon": {
|
||||
"defaultMessage": "Chọn biểu tượng"
|
||||
},
|
||||
"instances.updater-modal.badge.current": {
|
||||
"defaultMessage": "Hiện tại"
|
||||
},
|
||||
@@ -3893,9 +3863,6 @@
|
||||
"servers.list-empty.already-have-server-label": {
|
||||
"defaultMessage": "Đã có máy chủ rồi?"
|
||||
},
|
||||
"servers.list-empty.learn-more-link": {
|
||||
"defaultMessage": "Tìm hiểu thêm về Modrinth Hosting"
|
||||
},
|
||||
"servers.list-empty.modrinth-hosting-label": {
|
||||
"defaultMessage": "Modrinth Hosting"
|
||||
},
|
||||
@@ -3905,9 +3872,6 @@
|
||||
"servers.list-empty.no-servers-description": {
|
||||
"defaultMessage": "Cài mod, mời bạn bè, và chơi cùng nhau mà chỉ cần Ứng dụng Modrinth."
|
||||
},
|
||||
"servers.list-empty.no-servers-title": {
|
||||
"defaultMessage": "Không máy chủ khả dụng"
|
||||
},
|
||||
"servers.list-empty.one-click-mod-installs-description": {
|
||||
"defaultMessage": "Chọn bản mod mà bạn yêu thích và chúng tôi sẽ làm tất cả phần còn lại."
|
||||
},
|
||||
@@ -3923,12 +3887,6 @@
|
||||
"servers.list-empty.sign-in-button": {
|
||||
"defaultMessage": "Đăng nhập"
|
||||
},
|
||||
"servers.list-empty.simple-setup-description": {
|
||||
"defaultMessage": "Thiết lập máy chủ giống như thế giới chơi đơn."
|
||||
},
|
||||
"servers.list-empty.simple-setup-title": {
|
||||
"defaultMessage": "Thiết lập đơn giản"
|
||||
},
|
||||
"servers.listing.copied-label": {
|
||||
"defaultMessage": "Đã sao chép"
|
||||
},
|
||||
|
||||
@@ -707,9 +707,6 @@
|
||||
"creation-flow.modal.custom-setup.game-version.search-placeholder": {
|
||||
"defaultMessage": "搜索游戏版本……"
|
||||
},
|
||||
"creation-flow.modal.custom-setup.icon.remove": {
|
||||
"defaultMessage": "移除图标"
|
||||
},
|
||||
"creation-flow.modal.custom-setup.icon.select": {
|
||||
"defaultMessage": "选择图标"
|
||||
},
|
||||
@@ -872,24 +869,6 @@
|
||||
"creation-flow.modal.import-instance.selection.clear-all": {
|
||||
"defaultMessage": "清除所有"
|
||||
},
|
||||
"creation-flow.modal.modpack.action.browse": {
|
||||
"defaultMessage": "浏览整合包"
|
||||
},
|
||||
"creation-flow.modal.modpack.action.import": {
|
||||
"defaultMessage": "导入整合包"
|
||||
},
|
||||
"creation-flow.modal.modpack.known-modpack.prompt": {
|
||||
"defaultMessage": "已经知道你想安装哪个整合包?"
|
||||
},
|
||||
"creation-flow.modal.modpack.search.no-results": {
|
||||
"defaultMessage": "未找到结果"
|
||||
},
|
||||
"creation-flow.modal.modpack.search.placeholder": {
|
||||
"defaultMessage": "搜索整合包"
|
||||
},
|
||||
"creation-flow.modal.setup-type.instance.description": {
|
||||
"defaultMessage": "实例是带有特定加载器、游戏版本和模组的一套 Minecraft 配置。"
|
||||
},
|
||||
"creation-flow.modal.setup-type.option.custom-setup.description": {
|
||||
"defaultMessage": "从头开始,选择一个加载器和游戏版本。"
|
||||
},
|
||||
@@ -923,9 +902,6 @@
|
||||
"creation-flow.modal.setup-type.title.world": {
|
||||
"defaultMessage": "选择世界类型"
|
||||
},
|
||||
"creation-flow.title.choose-modpack": {
|
||||
"defaultMessage": "选择整合包"
|
||||
},
|
||||
"creation-flow.title.create-instance": {
|
||||
"defaultMessage": "创建实例"
|
||||
},
|
||||
@@ -2057,15 +2033,9 @@
|
||||
"instances.content-install.no-instances": {
|
||||
"defaultMessage": "未找到相关的实例"
|
||||
},
|
||||
"instances.content-install.remove-icon": {
|
||||
"defaultMessage": "移除图标"
|
||||
},
|
||||
"instances.content-install.search-placeholder": {
|
||||
"defaultMessage": "搜索实例"
|
||||
},
|
||||
"instances.content-install.select-icon": {
|
||||
"defaultMessage": "选择图标"
|
||||
},
|
||||
"instances.updater-modal.badge.current": {
|
||||
"defaultMessage": "当前"
|
||||
},
|
||||
@@ -4622,9 +4592,6 @@
|
||||
"servers.list-empty.already-have-server-label": {
|
||||
"defaultMessage": "已经有服务器了吗?"
|
||||
},
|
||||
"servers.list-empty.learn-more-link": {
|
||||
"defaultMessage": "深入了解 Modrinth Hosting"
|
||||
},
|
||||
"servers.list-empty.modrinth-hosting-label": {
|
||||
"defaultMessage": "Modrinth Hosting"
|
||||
},
|
||||
@@ -4634,9 +4601,6 @@
|
||||
"servers.list-empty.no-servers-description": {
|
||||
"defaultMessage": "安装模组、邀请好友并一起畅玩,这一切都能在 Modrinth App 中轻松完成。"
|
||||
},
|
||||
"servers.list-empty.no-servers-title": {
|
||||
"defaultMessage": "目前还没有服务器"
|
||||
},
|
||||
"servers.list-empty.one-click-mod-installs-description": {
|
||||
"defaultMessage": "选出你最喜欢的模组,其余的交给我们来处理。"
|
||||
},
|
||||
@@ -4652,12 +4616,6 @@
|
||||
"servers.list-empty.sign-in-button": {
|
||||
"defaultMessage": "登录"
|
||||
},
|
||||
"servers.list-empty.simple-setup-description": {
|
||||
"defaultMessage": "像设置单人游戏世界一样配置你的服务器。"
|
||||
},
|
||||
"servers.list-empty.simple-setup-title": {
|
||||
"defaultMessage": "设置简单"
|
||||
},
|
||||
"servers.listing.copied-label": {
|
||||
"defaultMessage": "已复制"
|
||||
},
|
||||
|
||||
@@ -707,9 +707,6 @@
|
||||
"creation-flow.modal.custom-setup.game-version.search-placeholder": {
|
||||
"defaultMessage": "搜尋遊戲版本..."
|
||||
},
|
||||
"creation-flow.modal.custom-setup.icon.remove": {
|
||||
"defaultMessage": "移除圖示"
|
||||
},
|
||||
"creation-flow.modal.custom-setup.icon.select": {
|
||||
"defaultMessage": "選擇圖示"
|
||||
},
|
||||
@@ -872,24 +869,6 @@
|
||||
"creation-flow.modal.import-instance.selection.clear-all": {
|
||||
"defaultMessage": "清除全部"
|
||||
},
|
||||
"creation-flow.modal.modpack.action.browse": {
|
||||
"defaultMessage": "瀏覽模組包"
|
||||
},
|
||||
"creation-flow.modal.modpack.action.import": {
|
||||
"defaultMessage": "匯入模組包"
|
||||
},
|
||||
"creation-flow.modal.modpack.known-modpack.prompt": {
|
||||
"defaultMessage": "已經選好想要安裝的模組包了嗎?"
|
||||
},
|
||||
"creation-flow.modal.modpack.search.no-results": {
|
||||
"defaultMessage": "找不到結果"
|
||||
},
|
||||
"creation-flow.modal.modpack.search.placeholder": {
|
||||
"defaultMessage": "搜尋模組包"
|
||||
},
|
||||
"creation-flow.modal.setup-type.instance.description": {
|
||||
"defaultMessage": "實例是指具有特定載入器、版本及模組的 Minecraft 設定。"
|
||||
},
|
||||
"creation-flow.modal.setup-type.option.custom-setup.description": {
|
||||
"defaultMessage": "從頭開始,先選擇載入器與遊戲版本。"
|
||||
},
|
||||
@@ -923,9 +902,6 @@
|
||||
"creation-flow.modal.setup-type.title.world": {
|
||||
"defaultMessage": "選擇世界類型"
|
||||
},
|
||||
"creation-flow.title.choose-modpack": {
|
||||
"defaultMessage": "選擇模組包"
|
||||
},
|
||||
"creation-flow.title.create-instance": {
|
||||
"defaultMessage": "建立實例"
|
||||
},
|
||||
@@ -2057,15 +2033,9 @@
|
||||
"instances.content-install.no-instances": {
|
||||
"defaultMessage": "找不到相容的實例"
|
||||
},
|
||||
"instances.content-install.remove-icon": {
|
||||
"defaultMessage": "移除圖示"
|
||||
},
|
||||
"instances.content-install.search-placeholder": {
|
||||
"defaultMessage": "搜尋實例"
|
||||
},
|
||||
"instances.content-install.select-icon": {
|
||||
"defaultMessage": "選擇圖示"
|
||||
},
|
||||
"instances.updater-modal.badge.current": {
|
||||
"defaultMessage": "目前"
|
||||
},
|
||||
@@ -4622,9 +4592,6 @@
|
||||
"servers.list-empty.already-have-server-label": {
|
||||
"defaultMessage": "已經有伺服器了嗎?"
|
||||
},
|
||||
"servers.list-empty.learn-more-link": {
|
||||
"defaultMessage": "深入了解 Modrinth Hosting"
|
||||
},
|
||||
"servers.list-empty.modrinth-hosting-label": {
|
||||
"defaultMessage": "Modrinth Hosting"
|
||||
},
|
||||
@@ -4634,9 +4601,6 @@
|
||||
"servers.list-empty.no-servers-description": {
|
||||
"defaultMessage": "安裝模組、邀請好友並一起暢玩,這一切都能在 Modrinth App 中輕鬆完成。"
|
||||
},
|
||||
"servers.list-empty.no-servers-title": {
|
||||
"defaultMessage": "目前還沒有伺服器"
|
||||
},
|
||||
"servers.list-empty.one-click-mod-installs-description": {
|
||||
"defaultMessage": "挑選你喜愛的模組,剩下的就交給我們處理。"
|
||||
},
|
||||
@@ -4652,12 +4616,6 @@
|
||||
"servers.list-empty.sign-in-button": {
|
||||
"defaultMessage": "登入"
|
||||
},
|
||||
"servers.list-empty.simple-setup-description": {
|
||||
"defaultMessage": "設定伺服器就像建立單人遊戲世界一樣簡單。"
|
||||
},
|
||||
"servers.list-empty.simple-setup-title": {
|
||||
"defaultMessage": "簡單設定"
|
||||
},
|
||||
"servers.listing.copied-label": {
|
||||
"defaultMessage": "已複製"
|
||||
},
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
import { type Component, type Ref, ref } from 'vue'
|
||||
import { type Component, markRaw, type Ref, ref } from 'vue'
|
||||
import type { ComponentProps } from 'vue-component-type-helpers'
|
||||
|
||||
import type { ButtonColor } from '#ui/components/base/buttons/types.ts'
|
||||
|
||||
import { createContext } from '.'
|
||||
|
||||
@@ -6,7 +9,7 @@ export interface PopupNotificationButton {
|
||||
label: string
|
||||
action: () => void | Promise<void>
|
||||
icon?: Component
|
||||
color?: 'brand' | 'red' | 'orange' | 'green' | 'blue' | 'standard'
|
||||
color?: ButtonColor | 'standard'
|
||||
keepOpen?: boolean
|
||||
}
|
||||
|
||||
@@ -35,7 +38,39 @@ export type PopupNotificationToastType =
|
||||
| 'instance-download'
|
||||
| 'instance-ready'
|
||||
|
||||
export interface PopupNotificationToast {
|
||||
interface PopupNotificationBase {
|
||||
id: string | number
|
||||
autoCloseMs?: number | null
|
||||
timer?: NodeJS.Timeout
|
||||
}
|
||||
|
||||
export interface PopupNotificationCustom<
|
||||
TComponent extends Component = Component,
|
||||
> extends PopupNotificationBase {
|
||||
contentType: 'custom'
|
||||
component: TComponent
|
||||
componentProps?: ComponentProps<TComponent>
|
||||
}
|
||||
|
||||
export interface PopupNotificationStandard extends PopupNotificationBase {
|
||||
contentType: 'standard'
|
||||
title: string
|
||||
type: 'error' | 'warning' | 'success' | 'info' | 'download'
|
||||
titleLogo?: Component
|
||||
text?: string
|
||||
iconUrl?: string | null
|
||||
hideIcon?: boolean
|
||||
progress?: number
|
||||
waiting?: boolean
|
||||
progressItems?: PopupNotificationProgressItem[]
|
||||
buttons?: PopupNotificationButton[]
|
||||
dismissible?: boolean
|
||||
onDismiss?: () => void | Promise<void>
|
||||
}
|
||||
|
||||
export interface PopupNotificationToast extends PopupNotificationBase {
|
||||
contentType: 'toast'
|
||||
title: string
|
||||
type: PopupNotificationToastType
|
||||
actorName?: string | null
|
||||
actorAvatarUrl?: string | null
|
||||
@@ -56,25 +91,40 @@ export interface PopupNotificationToast {
|
||||
onOpenInstance?: () => void | Promise<void>
|
||||
}
|
||||
|
||||
export interface PopupNotification {
|
||||
id: string | number
|
||||
title: string
|
||||
titleLogo?: Component
|
||||
bodyComponent?: Component
|
||||
bodyProps?: Record<string, unknown>
|
||||
text?: string
|
||||
iconUrl?: string | null
|
||||
hideIcon?: boolean
|
||||
type?: 'error' | 'warning' | 'success' | 'info' | 'download'
|
||||
progress?: number
|
||||
waiting?: boolean
|
||||
progressItems?: PopupNotificationProgressItem[]
|
||||
buttons?: PopupNotificationButton[]
|
||||
toast?: PopupNotificationToast
|
||||
dismissible?: boolean
|
||||
onDismiss?: () => void | Promise<void>
|
||||
autoCloseMs?: number | null
|
||||
timer?: NodeJS.Timeout
|
||||
export type PopupNotification =
|
||||
| PopupNotificationCustom
|
||||
| PopupNotificationStandard
|
||||
| PopupNotificationToast
|
||||
|
||||
type PopupNotificationDownload =
|
||||
| (PopupNotificationStandard & { type: 'download' })
|
||||
| (PopupNotificationToast & { type: 'instance-download' })
|
||||
|
||||
type PopupNotificationInput = PopupNotification extends infer Notification
|
||||
? Notification extends PopupNotification
|
||||
? Omit<Notification, 'id' | 'timer'>
|
||||
: never
|
||||
: never
|
||||
|
||||
type PopupNotificationCustomInput<TComponent extends Component> = Omit<
|
||||
PopupNotificationCustom<TComponent>,
|
||||
'id' | 'timer'
|
||||
>
|
||||
|
||||
type PopupNotificationNonCustomInput =
|
||||
| Omit<PopupNotificationStandard, 'id' | 'timer'>
|
||||
| Omit<PopupNotificationToast, 'id' | 'timer'>
|
||||
|
||||
type StoredPopupNotification<Input extends PopupNotificationInput> = Input &
|
||||
Pick<PopupNotificationBase, 'id' | 'timer'>
|
||||
|
||||
interface AddPopupNotification {
|
||||
<TComponent extends Component>(
|
||||
notification: PopupNotificationCustomInput<TComponent>,
|
||||
): StoredPopupNotification<PopupNotificationCustomInput<TComponent>>
|
||||
<Input extends PopupNotificationNonCustomInput>(
|
||||
notification: Input,
|
||||
): StoredPopupNotification<Input>
|
||||
}
|
||||
|
||||
export interface PopupNotificationDownloadState {
|
||||
@@ -108,7 +158,7 @@ export abstract class AbstractPopupNotificationManager {
|
||||
)
|
||||
|
||||
getVisibleDownloadProgressItems = (
|
||||
notification: PopupNotification,
|
||||
notification: PopupNotificationStandard,
|
||||
): PopupNotificationProgressItem[] =>
|
||||
(notification.progressItems ?? []).filter(
|
||||
(progressItem) =>
|
||||
@@ -121,17 +171,18 @@ export abstract class AbstractPopupNotificationManager {
|
||||
protected abstract removeNotificationFromStorage(id: string | number): void
|
||||
protected abstract clearAllNotificationsFromStorage(): void
|
||||
|
||||
addPopupNotification = (
|
||||
notification: Omit<PopupNotification, 'id' | 'timer'>,
|
||||
): PopupNotification => {
|
||||
const newNotification: PopupNotification = {
|
||||
addPopupNotification = ((notification: PopupNotificationInput) => {
|
||||
const newNotification = {
|
||||
...notification,
|
||||
...(notification.contentType === 'custom'
|
||||
? { component: markRaw(notification.component) }
|
||||
: {}),
|
||||
id: Date.now() + Math.random(),
|
||||
}
|
||||
} as StoredPopupNotification<PopupNotificationInput>
|
||||
this.setNotificationTimer(newNotification)
|
||||
this.addNotificationToStorage(newNotification)
|
||||
return newNotification
|
||||
}
|
||||
}) as AddPopupNotification
|
||||
|
||||
removeNotification = (id: string | number): void => {
|
||||
const notifications = this.getNotifications()
|
||||
@@ -151,18 +202,18 @@ export abstract class AbstractPopupNotificationManager {
|
||||
this.hiddenDownloadItemKeys.value.clear()
|
||||
}
|
||||
|
||||
hideDownloadItem = (notificationId: string | number, progressItemId: string): void => {
|
||||
hideDownloadItem = (notificationId: string | number, progressItemId?: string): void => {
|
||||
const notification = this.getDownloadNotifications().find(
|
||||
(candidate) => candidate.id === notificationId,
|
||||
)
|
||||
if (!notification?.progressItems?.some((item) => item.id === progressItemId)) return
|
||||
if (!notification) return
|
||||
|
||||
this.hiddenDownloadItemKeys.value.add(this.getDownloadItemKey(notificationId, progressItemId))
|
||||
if (
|
||||
this.getDownloadItemKeys(notification).every((key) =>
|
||||
this.hiddenDownloadItemKeys.value.has(key),
|
||||
)
|
||||
) {
|
||||
const itemKey = this.getDownloadItemKey(notificationId, progressItemId)
|
||||
const itemKeys = this.getDownloadItemKeys(notification)
|
||||
if (!itemKeys.includes(itemKey)) return
|
||||
|
||||
this.hiddenDownloadItemKeys.value.add(itemKey)
|
||||
if (itemKeys.every((key) => this.hiddenDownloadItemKeys.value.has(key))) {
|
||||
this.clearNotificationTimer(notification)
|
||||
}
|
||||
}
|
||||
@@ -212,11 +263,16 @@ export abstract class AbstractPopupNotificationManager {
|
||||
}
|
||||
}
|
||||
|
||||
private isDownloadNotification(notification: PopupNotification): boolean {
|
||||
return notification.type === 'download' || notification.toast?.type === 'instance-download'
|
||||
private isDownloadNotification(
|
||||
notification: PopupNotification,
|
||||
): notification is PopupNotificationDownload {
|
||||
return (
|
||||
(notification.contentType === 'standard' && notification.type === 'download') ||
|
||||
(notification.contentType === 'toast' && notification.type === 'instance-download')
|
||||
)
|
||||
}
|
||||
|
||||
private getDownloadNotifications(): PopupNotification[] {
|
||||
private getDownloadNotifications(): PopupNotificationDownload[] {
|
||||
return this.getNotifications().filter((notification) =>
|
||||
this.isDownloadNotification(notification),
|
||||
)
|
||||
@@ -224,7 +280,7 @@ export abstract class AbstractPopupNotificationManager {
|
||||
|
||||
private getDownloadItemKeys(notification: PopupNotification): string[] {
|
||||
if (!this.isDownloadNotification(notification)) return []
|
||||
if (notification.progressItems?.length) {
|
||||
if (notification.contentType === 'standard' && notification.progressItems?.length) {
|
||||
return notification.progressItems.map((progressItem) =>
|
||||
this.getDownloadItemKey(notification.id, progressItem.id),
|
||||
)
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
import type { Meta, StoryObj } from '@storybook/vue3-vite'
|
||||
import { ref } from 'vue'
|
||||
|
||||
import InlineEditableText from '../../components/base/InlineEditableText.vue'
|
||||
|
||||
const meta = {
|
||||
title: 'Base/InlineEditableText',
|
||||
component: InlineEditableText,
|
||||
render: (args) => ({
|
||||
components: { InlineEditableText },
|
||||
setup() {
|
||||
const value = ref('Editable text')
|
||||
return { args, value }
|
||||
},
|
||||
template: '<InlineEditableText v-model="value" v-bind="args" />',
|
||||
}),
|
||||
args: {
|
||||
editLabel: 'Edit text',
|
||||
},
|
||||
} satisfies Meta<typeof InlineEditableText>
|
||||
|
||||
export default meta
|
||||
type Story = StoryObj<typeof meta>
|
||||
|
||||
export const Default: Story = {}
|
||||
|
||||
export const IconActivated: Story = {
|
||||
args: {
|
||||
activationMode: 'icon',
|
||||
iconTextClass: '!text-brand',
|
||||
},
|
||||
}
|
||||
|
||||
export const CustomClasses: Story = {
|
||||
args: {
|
||||
buttonClass: '!rounded-lg !bg-surface-3 !px-2',
|
||||
inputClass: '!text-brand',
|
||||
},
|
||||
}
|
||||
|
||||
export const ConstrainedWidth: Story = {
|
||||
args: {
|
||||
maxWidth: '12rem',
|
||||
},
|
||||
render: (args) => ({
|
||||
components: { InlineEditableText },
|
||||
setup() {
|
||||
const value = ref('A long value that truncates within its available width')
|
||||
return { args, value }
|
||||
},
|
||||
template: '<InlineEditableText v-model="value" v-bind="args" />',
|
||||
}),
|
||||
}
|
||||
|
||||
export const Placeholder: Story = {
|
||||
args: {
|
||||
placeholder: 'Click to add a value',
|
||||
},
|
||||
render: (args) => ({
|
||||
components: { InlineEditableText },
|
||||
setup() {
|
||||
const value = ref('')
|
||||
return { args, value }
|
||||
},
|
||||
template: '<InlineEditableText v-model="value" v-bind="args" />',
|
||||
}),
|
||||
}
|
||||
|
||||
export const DefaultValue: Story = {
|
||||
args: {
|
||||
defaultValue: 'Default value',
|
||||
},
|
||||
render: (args) => ({
|
||||
components: { InlineEditableText },
|
||||
setup() {
|
||||
const value = ref('')
|
||||
return { args, value }
|
||||
},
|
||||
template: '<InlineEditableText v-model="value" v-bind="args" />',
|
||||
}),
|
||||
}
|
||||
|
||||
export const RejectedValue: Story = {
|
||||
args: {
|
||||
validate: (value: string) => value.toLowerCase() !== 'reserved',
|
||||
},
|
||||
render: (args) => ({
|
||||
components: { InlineEditableText },
|
||||
setup() {
|
||||
const value = ref('Try changing this to "reserved"')
|
||||
return { args, value }
|
||||
},
|
||||
template: '<InlineEditableText v-model="value" v-bind="args" />',
|
||||
}),
|
||||
}
|
||||
@@ -19,6 +19,7 @@ export const Default: StoryObj = {
|
||||
|
||||
const showSuccess = () => {
|
||||
popupManager.addPopupNotification({
|
||||
contentType: 'standard',
|
||||
title: 'Install complete',
|
||||
text: 'Complex Gaming [Cobblemon] is installed and ready to play.',
|
||||
type: 'success',
|
||||
@@ -38,6 +39,7 @@ export const Default: StoryObj = {
|
||||
|
||||
const showError = () => {
|
||||
popupManager.addPopupNotification({
|
||||
contentType: 'standard',
|
||||
title: 'Download failed',
|
||||
text: 'Failed to download the modpack. Please try again.',
|
||||
type: 'error',
|
||||
@@ -53,6 +55,7 @@ export const Default: StoryObj = {
|
||||
|
||||
const showWarning = () => {
|
||||
popupManager.addPopupNotification({
|
||||
contentType: 'standard',
|
||||
title: 'Update available',
|
||||
text: "Modrinth App v2.1.0 is available now! Since you're on a metered network, we didn't automatically download it.",
|
||||
type: 'warning',
|
||||
@@ -73,6 +76,7 @@ export const Default: StoryObj = {
|
||||
|
||||
const showInfo = () => {
|
||||
popupManager.addPopupNotification({
|
||||
contentType: 'standard',
|
||||
title: 'Download complete',
|
||||
text: 'Modrinth App v2.1.0 has finished downloading. Reload to update now.',
|
||||
type: 'info',
|
||||
@@ -92,6 +96,7 @@ export const Default: StoryObj = {
|
||||
|
||||
const showNoButtons = () => {
|
||||
popupManager.addPopupNotification({
|
||||
contentType: 'standard',
|
||||
title: 'Heads up',
|
||||
text: 'This notification has no action buttons and will auto-close in 30 seconds.',
|
||||
type: 'info',
|
||||
@@ -100,6 +105,7 @@ export const Default: StoryObj = {
|
||||
|
||||
const showPermanent = () => {
|
||||
popupManager.addPopupNotification({
|
||||
contentType: 'standard',
|
||||
title: 'Permanent notification',
|
||||
text: 'This notification will stay open until manually dismissed.',
|
||||
type: 'warning',
|
||||
@@ -109,6 +115,7 @@ export const Default: StoryObj = {
|
||||
|
||||
const showBlocking = () => {
|
||||
popupManager.addPopupNotification({
|
||||
contentType: 'standard',
|
||||
title: 'Your privacy and how ads support Modrinth',
|
||||
text: 'Ads make Modrinth possible and fund creator rewards. Our partners may store unique identifiers to personalize ads and measure performance.',
|
||||
type: 'info',
|
||||
@@ -139,6 +146,7 @@ export const Default: StoryObj = {
|
||||
|
||||
const showWaitingProgress = () => {
|
||||
popupManager.addPopupNotification({
|
||||
contentType: 'standard',
|
||||
title: 'Installing modpack...',
|
||||
text: 'example-pack-1.0.0.mrpack',
|
||||
type: 'info',
|
||||
@@ -149,6 +157,7 @@ export const Default: StoryObj = {
|
||||
|
||||
const showDeterminateProgress = () => {
|
||||
popupManager.addPopupNotification({
|
||||
contentType: 'standard',
|
||||
title: 'Downloading update',
|
||||
text: 'Downloading files...',
|
||||
type: 'success',
|
||||
@@ -159,6 +168,7 @@ export const Default: StoryObj = {
|
||||
|
||||
const showGroupedDownloads = () => {
|
||||
popupManager.addPopupNotification({
|
||||
contentType: 'standard',
|
||||
title: 'Downloads',
|
||||
type: 'download',
|
||||
autoCloseMs: null,
|
||||
|
||||
@@ -2,7 +2,10 @@ import type { Meta, StoryObj } from '@storybook/vue3-vite'
|
||||
import { ref } from 'vue'
|
||||
|
||||
import { Button } from '../../components/base/buttons'
|
||||
import type { CreationFlowContextValue } from '../../components/flows/creation-flow-modal/creation-flow-context'
|
||||
import type {
|
||||
CreationFlowContextValue,
|
||||
ProjectInstallCreateData,
|
||||
} from '../../components/flows/creation-flow-modal/creation-flow-context'
|
||||
import CreationFlowModal from '../../components/flows/creation-flow-modal/index.vue'
|
||||
|
||||
const meta = {
|
||||
@@ -102,7 +105,50 @@ export const Instance: Story = {
|
||||
const onCreate = (config: CreationFlowContextValue) => {
|
||||
lastEvent.value = `create emitted — loader: ${config.selectedLoader.value}, version: ${config.selectedGameVersion.value}`
|
||||
}
|
||||
return { modalRef, openModal, lastEvent, onCreate }
|
||||
const prepareProjectInstall = async (projectId: string, projectType: string) => {
|
||||
lastEvent.value = `project selected — ${projectId}`
|
||||
if (projectType === 'modpack') return null
|
||||
return {
|
||||
projectId,
|
||||
title: 'Fabric API',
|
||||
iconUrl: 'https://cdn.modrinth.com/data/P7dR8mSH/icon.png',
|
||||
link: '/project/fabric-api',
|
||||
compatibleLoaders: ['fabric'],
|
||||
gameVersions: ['1.21.5', '1.21.4'],
|
||||
releaseGameVersions: new Set(['1.21.5', '1.21.4']),
|
||||
}
|
||||
}
|
||||
const createProjectInstall = async (data: ProjectInstallCreateData) => {
|
||||
lastEvent.value = `project instance created — ${data.name}`
|
||||
}
|
||||
const searchProjects = async () => ({
|
||||
hits: [
|
||||
{
|
||||
project_id: 'fabric-api',
|
||||
project_type: 'mod',
|
||||
title: 'Fabric API',
|
||||
icon_url: 'https://cdn.modrinth.com/data/P7dR8mSH/icon.png',
|
||||
},
|
||||
{
|
||||
project_id: 'adrenaline',
|
||||
project_type: 'modpack',
|
||||
title: 'Adrenaline',
|
||||
icon_url: 'https://cdn.modrinth.com/data/RPYx1T0D/icon.png',
|
||||
},
|
||||
],
|
||||
offset: 0,
|
||||
limit: 10,
|
||||
total_hits: 2,
|
||||
})
|
||||
return {
|
||||
modalRef,
|
||||
openModal,
|
||||
lastEvent,
|
||||
onCreate,
|
||||
prepareProjectInstall,
|
||||
createProjectInstall,
|
||||
searchProjects,
|
||||
}
|
||||
},
|
||||
template: /*html*/ `
|
||||
<div class="flex flex-col gap-4 items-center">
|
||||
@@ -112,6 +158,9 @@ export const Instance: Story = {
|
||||
ref="modalRef"
|
||||
type="instance"
|
||||
:show-snapshot-toggle="true"
|
||||
:search-projects="searchProjects"
|
||||
:prepare-project-install="prepareProjectInstall"
|
||||
:create-project-install="createProjectInstall"
|
||||
@hide="() => {}"
|
||||
@browse-modpacks="() => console.log('browse-modpacks emitted')"
|
||||
@create="onCreate"
|
||||
|
||||
@@ -8,9 +8,34 @@ const meta = {
|
||||
parameters: {
|
||||
layout: 'centered',
|
||||
},
|
||||
args: {
|
||||
onClickNewServer: () => undefined,
|
||||
onClickSignIn: () => undefined,
|
||||
},
|
||||
} satisfies Meta<typeof ServerListEmpty>
|
||||
|
||||
export default meta
|
||||
type Story = StoryObj<typeof meta>
|
||||
|
||||
export const Default: Story = {}
|
||||
export const SignedOut: Story = {
|
||||
args: {
|
||||
loggedIn: false,
|
||||
},
|
||||
}
|
||||
|
||||
export const LoggedIn: Story = {
|
||||
args: {
|
||||
loggedIn: true,
|
||||
},
|
||||
}
|
||||
|
||||
export const Narrow: Story = {
|
||||
args: {
|
||||
loggedIn: false,
|
||||
},
|
||||
parameters: {
|
||||
viewport: {
|
||||
defaultViewport: 'mobile2',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||