chore: clean up a bunch of legacy styles (#5973)
* remove unused experimental-styles-within * remove unused styles * more cleanup + prepr * Refactor nearly all legacy buttons to use ButtonStyled * prepr * Update MC account selector to modern version * prepr --------- Co-authored-by: Calum H. <calum@modrinth.com>
This commit is contained in:
@@ -1177,7 +1177,7 @@ provideAppUpdateDownloadProgress(appUpdateDownload)
|
||||
<div id="teleports"></div>
|
||||
<div
|
||||
v-if="stateInitialized"
|
||||
class="app-grid-layout experimental-styles-within relative"
|
||||
class="app-grid-layout relative"
|
||||
:class="{ 'disable-advanced-rendering': !themeStore.advancedRendering }"
|
||||
>
|
||||
<Transition name="fade">
|
||||
@@ -1378,7 +1378,7 @@ provideAppUpdateDownloadProgress(appUpdateDownload)
|
||||
</div>
|
||||
<div
|
||||
v-if="stateInitialized"
|
||||
class="app-contents experimental-styles-within"
|
||||
class="app-contents"
|
||||
:class="{
|
||||
'sidebar-enabled': sidebarVisible,
|
||||
'disable-advanced-rendering': !themeStore.advancedRendering,
|
||||
@@ -1472,7 +1472,7 @@ provideAppUpdateDownloadProgress(appUpdateDownload)
|
||||
<div class="p-4 border-0 border-b-[1px] border-[--brand-gradient-border] border-solid">
|
||||
<h3 class="text-base text-primary font-medium m-0">Playing as</h3>
|
||||
<suspense>
|
||||
<AccountsCard ref="accounts" mode="small" />
|
||||
<AccountsCard ref="accounts" />
|
||||
</suspense>
|
||||
</div>
|
||||
<div class="p-4 border-0 border-b-[1px] border-[--brand-gradient-border] border-solid">
|
||||
|
||||
@@ -1,81 +1,107 @@
|
||||
<template>
|
||||
<div
|
||||
v-if="mode !== 'isolated'"
|
||||
ref="button"
|
||||
class="button-base mt-2 px-3 py-2 bg-button-bg rounded-xl flex items-center gap-2"
|
||||
:class="{ expanded: mode === 'expanded' }"
|
||||
@click="toggleMenu"
|
||||
v-if="accounts.length === 0"
|
||||
class="flex flex-col gap-3 bg-button-bg border border-solid border-surface-5 rounded-xl p-3 mt-2"
|
||||
>
|
||||
<Avatar
|
||||
size="36px"
|
||||
:src="
|
||||
selectedAccount ? avatarUrl : 'https://launcher-files.modrinth.com/assets/steve_head.png'
|
||||
"
|
||||
/>
|
||||
<div class="flex flex-col w-full">
|
||||
<span>{{ selectedAccount ? selectedAccount.profile.name : 'Select account' }}</span>
|
||||
<span class="text-secondary text-xs">Minecraft account</span>
|
||||
</div>
|
||||
<DropdownIcon class="w-5 h-5 shrink-0" />
|
||||
<span>{{ formatMessage(messages.notSignedIn) }}</span>
|
||||
<ButtonStyled color="brand">
|
||||
<button color="primary" :disabled="loginDisabled" @click="login()">
|
||||
<LogInIcon v-if="!loginDisabled" />
|
||||
<SpinnerIcon v-else class="animate-spin" />
|
||||
{{ formatMessage(messages.signInToMinecraft) }}
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
<transition name="fade">
|
||||
<Card
|
||||
v-if="showCard || mode === 'isolated'"
|
||||
ref="card"
|
||||
class="account-card"
|
||||
:class="{ expanded: mode === 'expanded', isolated: mode === 'isolated' }"
|
||||
>
|
||||
<div v-if="selectedAccount" class="selected account">
|
||||
<Avatar size="xs" :src="avatarUrl" />
|
||||
<div>
|
||||
<h4>{{ selectedAccount.profile.name }}</h4>
|
||||
<p>Selected</p>
|
||||
</div>
|
||||
<Button
|
||||
v-tooltip="'Log out'"
|
||||
icon-only
|
||||
color="raised"
|
||||
@click="logout(selectedAccount.profile.id)"
|
||||
>
|
||||
<TrashIcon />
|
||||
</Button>
|
||||
</div>
|
||||
<div v-else class="logged-out account">
|
||||
<h4>Not signed in</h4>
|
||||
<Button
|
||||
v-tooltip="'Log in'"
|
||||
:disabled="loginDisabled"
|
||||
icon-only
|
||||
color="primary"
|
||||
@click="login()"
|
||||
>
|
||||
<LogInIcon v-if="!loginDisabled" />
|
||||
<SpinnerIcon v-else class="animate-spin" />
|
||||
</Button>
|
||||
</div>
|
||||
<div v-if="displayAccounts.length > 0" class="account-group">
|
||||
<div v-for="account in displayAccounts" :key="account.profile.id" class="account-row">
|
||||
<Button class="option account" @click="setAccount(account)">
|
||||
<Avatar :src="getAccountAvatarUrl(account)" class="icon" />
|
||||
<p>{{ account.profile.name }}</p>
|
||||
</Button>
|
||||
<Button v-tooltip="'Log out'" icon-only @click="logout(account.profile.id)">
|
||||
<TrashIcon />
|
||||
</Button>
|
||||
<Accordion
|
||||
v-else
|
||||
class="w-full mt-2 bg-button-bg border border-solid border-surface-5 rounded-xl overflow-clip"
|
||||
button-class="button-base w-full bg-transparent px-3 py-2 border-0 cursor-pointer"
|
||||
:open-by-default="false"
|
||||
>
|
||||
<template #title>
|
||||
<div class="flex gap-2 w-full min-w-0">
|
||||
<Avatar
|
||||
size="36px"
|
||||
:src="
|
||||
selectedAccount
|
||||
? avatarUrl
|
||||
: 'https://launcher-files.modrinth.com/assets/steve_head.png'
|
||||
"
|
||||
/>
|
||||
<div class="flex flex-col items-start w-full min-w-0">
|
||||
<span class="truncate w-full text-left">{{
|
||||
selectedAccount ? selectedAccount.profile.name : formatMessage(messages.selectAccount)
|
||||
}}</span>
|
||||
<span class="text-secondary text-xs">{{ formatMessage(messages.minecraftAccount) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<Button v-if="accounts.length > 0" @click="login()">
|
||||
<PlusIcon />
|
||||
Add account
|
||||
</Button>
|
||||
</Card>
|
||||
</transition>
|
||||
</template>
|
||||
<div class="bg-button-bg pt-1 pb-2 border border-solid border-surface-5">
|
||||
<template v-if="accounts.length > 0">
|
||||
<div v-for="account in accounts" :key="account.profile.id" class="flex gap-1 items-center">
|
||||
<button
|
||||
class="flex items-center flex-shrink flex-grow overflow-clip gap-2 p-2 border-0 bg-transparent cursor-pointer button-base min-w-0"
|
||||
@click="setAccount(account)"
|
||||
>
|
||||
<RadioButtonCheckedIcon
|
||||
v-if="selectedAccount && selectedAccount.profile.id === account.profile.id"
|
||||
class="w-5 h-5 text-brand shrink-0"
|
||||
/>
|
||||
<RadioButtonIcon v-else class="w-5 h-5 text-secondary shrink-0" />
|
||||
<Avatar :src="getAccountAvatarUrl(account)" size="24px" />
|
||||
<p
|
||||
class="m-0 truncate min-w-0"
|
||||
:class="
|
||||
selectedAccount && selectedAccount.profile.id === account.profile.id
|
||||
? 'text-contrast font-semibold'
|
||||
: 'text-primary'
|
||||
"
|
||||
>
|
||||
{{ account.profile.name }}
|
||||
</p>
|
||||
</button>
|
||||
<ButtonStyled circular color="red" color-fill="none" hover-color-fill="background">
|
||||
<button
|
||||
v-tooltip="formatMessage(messages.removeAccount)"
|
||||
class="mr-2"
|
||||
@click="logout(account.profile.id)"
|
||||
>
|
||||
<TrashIcon />
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
</template>
|
||||
<div class="flex flex-col gap-2 px-2 pt-2">
|
||||
<ButtonStyled v-if="accounts.length > 0" class="w-full">
|
||||
<button :disabled="loginDisabled" @click="login()">
|
||||
<PlusIcon />
|
||||
{{ formatMessage(messages.addAccount) }}
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
</div>
|
||||
</Accordion>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { DropdownIcon, LogInIcon, PlusIcon, SpinnerIcon, TrashIcon } from '@modrinth/assets'
|
||||
import { Avatar, Button, Card, injectNotificationManager } from '@modrinth/ui'
|
||||
import { computed, onBeforeUnmount, onMounted, onUnmounted, ref } from 'vue'
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
LogInIcon,
|
||||
PlusIcon,
|
||||
RadioButtonCheckedIcon,
|
||||
RadioButtonIcon,
|
||||
SpinnerIcon,
|
||||
TrashIcon,
|
||||
} from '@modrinth/assets'
|
||||
import {
|
||||
Accordion,
|
||||
Avatar,
|
||||
ButtonStyled,
|
||||
defineMessages,
|
||||
injectNotificationManager,
|
||||
useVIntl,
|
||||
} from '@modrinth/ui'
|
||||
import type { Ref } from 'vue'
|
||||
import { computed, onUnmounted, ref } from 'vue'
|
||||
|
||||
import { trackEvent } from '@/helpers/analytics'
|
||||
import {
|
||||
@@ -87,34 +113,39 @@ import {
|
||||
} from '@/helpers/auth'
|
||||
import { process_listener } from '@/helpers/events'
|
||||
import { getPlayerHeadUrl } from '@/helpers/rendering/batch-skin-renderer.ts'
|
||||
import type { Skin } from '@/helpers/skins'
|
||||
import { get_available_skins } from '@/helpers/skins'
|
||||
import { handleSevereError } from '@/store/error.js'
|
||||
|
||||
const { formatMessage } = useVIntl()
|
||||
const { handleError } = injectNotificationManager()
|
||||
|
||||
defineProps({
|
||||
mode: {
|
||||
type: String,
|
||||
required: true,
|
||||
default: 'normal',
|
||||
},
|
||||
})
|
||||
const emit = defineEmits<{
|
||||
change: []
|
||||
}>()
|
||||
|
||||
const emit = defineEmits(['change'])
|
||||
type MinecraftCredential = {
|
||||
profile: {
|
||||
id: string
|
||||
name: string
|
||||
}
|
||||
}
|
||||
|
||||
const accounts = ref({})
|
||||
const accounts: Ref<MinecraftCredential[]> = ref([])
|
||||
const loginDisabled = ref(false)
|
||||
const defaultUser = ref()
|
||||
const equippedSkin = ref(null)
|
||||
const headUrlCache = ref(new Map())
|
||||
const defaultUser = ref<string | undefined>()
|
||||
const equippedSkin = ref<Skin | null>(null)
|
||||
const headUrlCache = ref(new Map<string, string>())
|
||||
|
||||
async function refreshValues() {
|
||||
defaultUser.value = await get_default_user().catch(handleError)
|
||||
accounts.value = await users().catch(handleError)
|
||||
const userList = await users().catch(handleError)
|
||||
accounts.value = Array.isArray(userList) ? [...userList] : []
|
||||
accounts.value.sort((a, b) => (a.profile?.name ?? '').localeCompare(b.profile?.name ?? ''))
|
||||
|
||||
try {
|
||||
const skins = await get_available_skins()
|
||||
equippedSkin.value = skins.find((skin) => skin.is_equipped)
|
||||
equippedSkin.value = skins.find((skin) => skin.is_equipped) ?? null
|
||||
|
||||
if (equippedSkin.value) {
|
||||
try {
|
||||
@@ -129,7 +160,7 @@ async function refreshValues() {
|
||||
}
|
||||
}
|
||||
|
||||
function setLoginDisabled(value) {
|
||||
function setLoginDisabled(value: boolean) {
|
||||
loginDisabled.value = value
|
||||
}
|
||||
|
||||
@@ -138,10 +169,11 @@ defineExpose({
|
||||
setLoginDisabled,
|
||||
loginDisabled,
|
||||
})
|
||||
|
||||
await refreshValues()
|
||||
|
||||
const displayAccounts = computed(() =>
|
||||
accounts.value.filter((account) => defaultUser.value !== account.profile.id),
|
||||
const selectedAccount = computed(() =>
|
||||
accounts.value.find((account) => account.profile.id === defaultUser.value),
|
||||
)
|
||||
|
||||
const avatarUrl = computed(() => {
|
||||
@@ -158,7 +190,7 @@ const avatarUrl = computed(() => {
|
||||
return 'https://launcher-files.modrinth.com/assets/steve_head.png'
|
||||
})
|
||||
|
||||
function getAccountAvatarUrl(account) {
|
||||
function getAccountAvatarUrl(account: MinecraftCredential) {
|
||||
if (
|
||||
account.profile.id === selectedAccount.value?.profile?.id &&
|
||||
equippedSkin.value?.texture_key
|
||||
@@ -171,13 +203,10 @@ function getAccountAvatarUrl(account) {
|
||||
return `https://mc-heads.net/avatar/${account.profile.id}/128`
|
||||
}
|
||||
|
||||
const selectedAccount = computed(() =>
|
||||
accounts.value.find((account) => account.profile.id === defaultUser.value),
|
||||
)
|
||||
|
||||
async function setAccount(account) {
|
||||
async function setAccount(account: MinecraftCredential) {
|
||||
defaultUser.value = account.profile.id
|
||||
await set_default_user(account.profile.id).catch(handleError)
|
||||
await refreshValues()
|
||||
emit('change')
|
||||
}
|
||||
|
||||
@@ -187,292 +216,57 @@ async function login() {
|
||||
|
||||
if (loggedIn) {
|
||||
await setAccount(loggedIn)
|
||||
await refreshValues()
|
||||
}
|
||||
|
||||
trackEvent('AccountLogIn')
|
||||
loginDisabled.value = false
|
||||
}
|
||||
|
||||
const logout = async (id) => {
|
||||
async function logout(id: string) {
|
||||
await remove_user(id).catch(handleError)
|
||||
await refreshValues()
|
||||
if (!selectedAccount.value && accounts.value.length > 0) {
|
||||
await setAccount(accounts.value[0])
|
||||
await refreshValues()
|
||||
} else {
|
||||
emit('change')
|
||||
}
|
||||
trackEvent('AccountLogOut')
|
||||
}
|
||||
|
||||
const showCard = ref(false)
|
||||
const card = ref(null)
|
||||
const button = ref(null)
|
||||
const handleClickOutside = (event) => {
|
||||
const elements = document.elementsFromPoint(event.clientX, event.clientY)
|
||||
if (
|
||||
card.value &&
|
||||
card.value.$el !== event.target &&
|
||||
!elements.includes(card.value.$el) &&
|
||||
!button.value.contains(event.target)
|
||||
) {
|
||||
toggleMenu(false)
|
||||
}
|
||||
}
|
||||
|
||||
function toggleMenu(override = true) {
|
||||
if (showCard.value || !override) {
|
||||
showCard.value = false
|
||||
} else {
|
||||
showCard.value = true
|
||||
}
|
||||
}
|
||||
|
||||
const unlisten = await process_listener(async (e) => {
|
||||
if (e.event === 'launched') {
|
||||
await refreshValues()
|
||||
}
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
window.addEventListener('click', handleClickOutside)
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
window.removeEventListener('click', handleClickOutside)
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
unlisten()
|
||||
})
|
||||
|
||||
const messages = defineMessages({
|
||||
notSignedIn: {
|
||||
id: 'minecraft-account.not-signed-in',
|
||||
defaultMessage: 'Not signed in',
|
||||
},
|
||||
addAccount: {
|
||||
id: 'minecraft-account.add-account',
|
||||
defaultMessage: 'Add account',
|
||||
},
|
||||
removeAccount: {
|
||||
id: 'minecraft-account.remove-account',
|
||||
defaultMessage: 'Remove account',
|
||||
},
|
||||
selectAccount: {
|
||||
id: 'minecraft-account.select-account',
|
||||
defaultMessage: 'Select account',
|
||||
},
|
||||
minecraftAccount: {
|
||||
id: 'minecraft-account.label',
|
||||
defaultMessage: 'Minecraft account',
|
||||
},
|
||||
signInToMinecraft: {
|
||||
id: 'minecraft-account.sign-in',
|
||||
defaultMessage: 'Sign in to Minecraft',
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.selected {
|
||||
background: var(--color-brand-highlight);
|
||||
border-radius: var(--radius-lg);
|
||||
color: var(--color-contrast);
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.logged-out {
|
||||
background: var(--color-bg);
|
||||
border-radius: var(--radius-lg);
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.account {
|
||||
width: max-content;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
text-align: left;
|
||||
padding: 0.5rem 1rem;
|
||||
|
||||
h4,
|
||||
p {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.account-card {
|
||||
position: fixed;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-top: 0.5rem;
|
||||
right: 2rem;
|
||||
z-index: 11;
|
||||
gap: 0.5rem;
|
||||
padding: 1rem;
|
||||
border: 1px solid var(--color-divider);
|
||||
width: max-content;
|
||||
user-select: none;
|
||||
-ms-user-select: none;
|
||||
-webkit-user-select: none;
|
||||
max-height: calc(100vh - 300px);
|
||||
overflow-y: auto;
|
||||
|
||||
&::-webkit-scrollbar-track {
|
||||
border-top-right-radius: 1rem;
|
||||
border-bottom-right-radius: 1rem;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
border-top-right-radius: 1rem;
|
||||
border-bottom-right-radius: 1rem;
|
||||
}
|
||||
|
||||
&.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&.expanded {
|
||||
left: 13.5rem;
|
||||
}
|
||||
|
||||
&.isolated {
|
||||
position: relative;
|
||||
left: 0;
|
||||
top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.accounts-title {
|
||||
font-size: 1.2rem;
|
||||
font-weight: bolder;
|
||||
}
|
||||
|
||||
.account-group {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.option {
|
||||
width: calc(100% - 2.25rem);
|
||||
background: var(--color-raised-bg);
|
||||
color: var(--color-base);
|
||||
box-shadow: none;
|
||||
|
||||
img {
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
.icon {
|
||||
--size: 1.5rem !important;
|
||||
}
|
||||
|
||||
.account-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 0.5rem;
|
||||
vertical-align: center;
|
||||
justify-content: space-between;
|
||||
padding-right: 1rem;
|
||||
}
|
||||
|
||||
.fade-enter-active,
|
||||
.fade-leave-active {
|
||||
transition:
|
||||
opacity 0.25s ease,
|
||||
translate 0.25s ease,
|
||||
scale 0.25s ease;
|
||||
}
|
||||
|
||||
.fade-enter-from,
|
||||
.fade-leave-to {
|
||||
opacity: 0;
|
||||
translate: 0 -2rem;
|
||||
scale: 0.9;
|
||||
}
|
||||
|
||||
.avatar-button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
color: var(--color-base);
|
||||
background-color: var(--color-button-bg);
|
||||
border-radius: var(--radius-md);
|
||||
width: 100%;
|
||||
padding: 0.5rem 0.75rem;
|
||||
text-align: left;
|
||||
|
||||
&.expanded {
|
||||
border: 1px solid var(--color-divider);
|
||||
padding: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
.avatar-text {
|
||||
margin: auto 0 auto 0.25rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.text {
|
||||
width: 6rem;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.accounts-text {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.25rem;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.qr-code {
|
||||
background-color: white !important;
|
||||
border-radius: var(--radius-md);
|
||||
}
|
||||
|
||||
.modal-body {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: var(--gap-lg);
|
||||
align-items: center;
|
||||
padding: var(--gap-xl);
|
||||
|
||||
.modal-text {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--gap-sm);
|
||||
width: 100%;
|
||||
|
||||
h2,
|
||||
p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.code-text {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: var(--gap-xs);
|
||||
align-items: center;
|
||||
|
||||
.code {
|
||||
background-color: var(--color-bg);
|
||||
border-radius: var(--radius-md);
|
||||
border: solid 1px var(--color-button-bg);
|
||||
font-family: var(--mono-font);
|
||||
letter-spacing: var(--gap-md);
|
||||
color: var(--color-contrast);
|
||||
font-size: 2rem;
|
||||
font-weight: bold;
|
||||
padding: var(--gap-sm) 0 var(--gap-sm) var(--gap-md);
|
||||
}
|
||||
|
||||
.btn {
|
||||
width: 2.5rem;
|
||||
height: 2.5rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.button-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.modal {
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.code {
|
||||
color: var(--color-brand);
|
||||
padding: 0.05rem 0.1rem;
|
||||
// row not column
|
||||
display: flex;
|
||||
|
||||
.card {
|
||||
background: var(--color-base);
|
||||
color: var(--color-contrast);
|
||||
padding: 0.5rem 1rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script setup>
|
||||
import { PlusIcon, XIcon } from '@modrinth/assets'
|
||||
import {
|
||||
Button,
|
||||
ButtonStyled,
|
||||
Checkbox,
|
||||
commonMessages,
|
||||
defineMessages,
|
||||
@@ -179,14 +179,12 @@ const exportPack = async () => {
|
||||
<div class="table-head">
|
||||
<div class="table-cell row-wise">
|
||||
{{ formatMessage(messages.selectFilesLabel) }}
|
||||
<Button
|
||||
class="sleek-primary collapsed-button"
|
||||
icon-only
|
||||
@click="() => (showingFiles = !showingFiles)"
|
||||
>
|
||||
<PlusIcon v-if="!showingFiles" />
|
||||
<XIcon v-else />
|
||||
</Button>
|
||||
<ButtonStyled circular>
|
||||
<button @click="() => (showingFiles = !showingFiles)">
|
||||
<PlusIcon v-if="!showingFiles" />
|
||||
<XIcon v-else />
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="showingFiles" class="table-content">
|
||||
@@ -235,14 +233,18 @@ const exportPack = async () => {
|
||||
</div>
|
||||
</div>
|
||||
<div class="button-row push-right">
|
||||
<Button @click="exportModal.hide">
|
||||
<XIcon />
|
||||
{{ formatMessage(commonMessages.cancelButton) }}
|
||||
</Button>
|
||||
<Button color="primary" @click="exportPack">
|
||||
<PackageIcon />
|
||||
{{ formatMessage(messages.exportButton) }}
|
||||
</Button>
|
||||
<ButtonStyled type="outlined">
|
||||
<button @click="exportModal.hide">
|
||||
<XIcon />
|
||||
{{ formatMessage(commonMessages.cancelButton) }}
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled color="brand">
|
||||
<button @click="exportPack">
|
||||
<PackageIcon />
|
||||
{{ formatMessage(messages.exportButton) }}
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
</div>
|
||||
</ModalWrapper>
|
||||
|
||||
@@ -15,10 +15,12 @@
|
||||
<span>{{ javaInstall.path }}</span>
|
||||
</div>
|
||||
<div class="table-cell table-text manage">
|
||||
<Button v-if="currentSelected.path === javaInstall.path" disabled
|
||||
><CheckIcon /> Selected</Button
|
||||
>
|
||||
<Button v-else @click="setJavaInstall(javaInstall)"><PlusIcon /> Select</Button>
|
||||
<ButtonStyled v-if="currentSelected.path === javaInstall.path">
|
||||
<button disabled><CheckIcon /> Selected</button>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled v-else>
|
||||
<button @click="setJavaInstall(javaInstall)"><PlusIcon /> Select</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="chosenInstallOptions.length === 0" class="table-row entire-row">
|
||||
@@ -26,17 +28,19 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="input-group push-right">
|
||||
<Button @click="$refs.detectJavaModal.hide()">
|
||||
<XIcon />
|
||||
Cancel
|
||||
</Button>
|
||||
<ButtonStyled type="outlined">
|
||||
<button @click="$refs.detectJavaModal.hide()">
|
||||
<XIcon />
|
||||
Cancel
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
</div>
|
||||
</ModalWrapper>
|
||||
</template>
|
||||
<script setup>
|
||||
import { CheckIcon, PlusIcon, XIcon } from '@modrinth/assets'
|
||||
import { Button, injectNotificationManager } from '@modrinth/ui'
|
||||
import { ButtonStyled, injectNotificationManager } from '@modrinth/ui'
|
||||
import { ref } from 'vue'
|
||||
|
||||
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
|
||||
|
||||
@@ -17,35 +17,45 @@
|
||||
"
|
||||
/>
|
||||
<span class="installation-buttons">
|
||||
<Button
|
||||
v-if="props.version"
|
||||
:disabled="props.disabled || installingJava"
|
||||
@click="reinstallJava"
|
||||
>
|
||||
<DownloadIcon />
|
||||
{{ installingJava ? 'Installing...' : 'Install recommended' }}
|
||||
</Button>
|
||||
<Button :disabled="props.disabled" @click="autoDetect">
|
||||
<SearchIcon />
|
||||
Detect
|
||||
</Button>
|
||||
<Button :disabled="props.disabled" @click="handleJavaFileInput()">
|
||||
<FolderSearchIcon />
|
||||
Browse
|
||||
</Button>
|
||||
<Button v-if="testingJava" disabled> Testing... </Button>
|
||||
<Button v-else-if="testingJavaSuccess === true">
|
||||
<CheckIcon class="test-success" />
|
||||
Success
|
||||
</Button>
|
||||
<Button v-else-if="testingJavaSuccess === false">
|
||||
<XIcon class="test-fail" />
|
||||
Failed
|
||||
</Button>
|
||||
<Button v-else :disabled="props.disabled" @click="testJava">
|
||||
<PlayIcon />
|
||||
Test
|
||||
</Button>
|
||||
<ButtonStyled v-if="props.version">
|
||||
<button :disabled="props.disabled || installingJava" @click="reinstallJava">
|
||||
<DownloadIcon />
|
||||
{{ installingJava ? 'Installing...' : 'Install recommended' }}
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled>
|
||||
<button :disabled="props.disabled" @click="autoDetect">
|
||||
<SearchIcon />
|
||||
Detect
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled>
|
||||
<button :disabled="props.disabled" @click="handleJavaFileInput()">
|
||||
<FolderSearchIcon />
|
||||
Browse
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled v-if="testingJava">
|
||||
<button disabled>Testing...</button>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled v-else-if="testingJavaSuccess === true">
|
||||
<button disabled>
|
||||
<CheckIcon />
|
||||
Success
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled v-else-if="testingJavaSuccess === false">
|
||||
<button disabled>
|
||||
<XIcon />
|
||||
Failed
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled v-else>
|
||||
<button :disabled="props.disabled" @click="testJava">
|
||||
<PlayIcon />
|
||||
Test
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
@@ -59,7 +69,7 @@ import {
|
||||
SearchIcon,
|
||||
XIcon,
|
||||
} from '@modrinth/assets'
|
||||
import { Button, injectNotificationManager, StyledInput } from '@modrinth/ui'
|
||||
import { ButtonStyled, injectNotificationManager, StyledInput } from '@modrinth/ui'
|
||||
import { open } from '@tauri-apps/plugin-dialog'
|
||||
import { ref } from 'vue'
|
||||
|
||||
@@ -204,10 +214,6 @@ async function reinstallJava() {
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
margin: 0;
|
||||
|
||||
.btn {
|
||||
width: max-content;
|
||||
}
|
||||
}
|
||||
|
||||
.test-success {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script setup>
|
||||
import { CheckIcon } from '@modrinth/assets'
|
||||
import { Badge, Button } from '@modrinth/ui'
|
||||
import { Badge, ButtonStyled } from '@modrinth/ui'
|
||||
import { computed, ref } from 'vue'
|
||||
|
||||
import { SwapIcon } from '@/assets/icons/index.js'
|
||||
@@ -74,15 +74,18 @@ const onHide = () => {
|
||||
@click="$router.push(`/project/${version.project_id}/version/${version.id}`)"
|
||||
>
|
||||
<div class="table-cell table-text">
|
||||
<Button
|
||||
:color="version.id === installedVersion ? '' : 'primary'"
|
||||
icon-only
|
||||
:disabled="inProgress || installing || version.id === installedVersion"
|
||||
@click.stop="() => switchVersion(version.id)"
|
||||
<ButtonStyled
|
||||
circular
|
||||
:color="version.id === installedVersion ? 'standard' : 'brand'"
|
||||
>
|
||||
<SwapIcon v-if="version.id !== installedVersion" />
|
||||
<CheckIcon v-else />
|
||||
</Button>
|
||||
<button
|
||||
:disabled="inProgress || installing || version.id === installedVersion"
|
||||
@click.stop="() => switchVersion(version.id)"
|
||||
>
|
||||
<SwapIcon v-if="version.id !== installedVersion" />
|
||||
<CheckIcon v-else />
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
<div class="name-cell table-cell table-text">
|
||||
<div class="version-link">
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script setup>
|
||||
import { Button, injectNotificationManager, ProjectCard } from '@modrinth/ui'
|
||||
import { ButtonStyled, injectNotificationManager, ProjectCard } from '@modrinth/ui'
|
||||
import { ref } from 'vue'
|
||||
|
||||
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
|
||||
@@ -12,7 +12,6 @@ const { install: installVersion } = injectContentInstall()
|
||||
const confirmModal = ref(null)
|
||||
const project = ref(null)
|
||||
const version = ref(null)
|
||||
const installing = ref(false)
|
||||
|
||||
defineExpose({
|
||||
async show(event) {
|
||||
@@ -70,7 +69,9 @@ async function install() {
|
||||
</p>
|
||||
</div>
|
||||
<div class="button-group">
|
||||
<Button :loading="installing" color="primary" @click="install">Install</Button>
|
||||
<ButtonStyled color="brand">
|
||||
<button @click="install">Install</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -34,10 +34,14 @@
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="button-group">
|
||||
<Button @click="() => incompatibleModal.hide()"><XIcon />Cancel</Button>
|
||||
<Button color="primary" :disabled="installing" @click="install()">
|
||||
<DownloadIcon /> {{ installing ? 'Installing' : 'Install' }}
|
||||
</Button>
|
||||
<ButtonStyled type="outlined">
|
||||
<button @click="() => incompatibleModal.hide()"><XIcon />Cancel</button>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled color="brand">
|
||||
<button :disabled="installing" @click="install()">
|
||||
<DownloadIcon /> {{ installing ? 'Installing' : 'Install' }}
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
</div>
|
||||
</ModalWrapper>
|
||||
@@ -45,7 +49,13 @@
|
||||
|
||||
<script setup>
|
||||
import { DownloadIcon, XIcon } from '@modrinth/assets'
|
||||
import { Button, Combobox, formatLoader, injectNotificationManager, useVIntl } from '@modrinth/ui'
|
||||
import {
|
||||
ButtonStyled,
|
||||
Combobox,
|
||||
formatLoader,
|
||||
injectNotificationManager,
|
||||
useVIntl,
|
||||
} from '@modrinth/ui'
|
||||
import { computed, ref } from 'vue'
|
||||
|
||||
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
|
||||
|
||||
@@ -1,418 +0,0 @@
|
||||
<script setup>
|
||||
import {
|
||||
CheckIcon,
|
||||
DownloadIcon,
|
||||
PlusIcon,
|
||||
RightArrowIcon,
|
||||
SearchIcon,
|
||||
UploadIcon,
|
||||
XIcon,
|
||||
} from '@modrinth/assets'
|
||||
import { Avatar, Button, Card, injectNotificationManager, StyledInput } from '@modrinth/ui'
|
||||
import { convertFileSrc } from '@tauri-apps/api/core'
|
||||
import { open } from '@tauri-apps/plugin-dialog'
|
||||
import { computed, ref } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
|
||||
import { trackEvent } from '@/helpers/analytics'
|
||||
import { get_project_v3_many } from '@/helpers/cache.js'
|
||||
import {
|
||||
add_project_from_version as installMod,
|
||||
check_installed,
|
||||
create,
|
||||
get,
|
||||
list,
|
||||
} from '@/helpers/profile'
|
||||
import {
|
||||
findPreferredVersion,
|
||||
installVersionDependencies,
|
||||
isVersionCompatible,
|
||||
} from '@/store/install.js'
|
||||
|
||||
const { handleError } = injectNotificationManager()
|
||||
const router = useRouter()
|
||||
|
||||
const versions = ref()
|
||||
const project = ref()
|
||||
|
||||
const installModal = ref()
|
||||
const searchFilter = ref('')
|
||||
|
||||
const showCreation = ref(false)
|
||||
const icon = ref(null)
|
||||
const name = ref(null)
|
||||
const display_icon = ref(null)
|
||||
const loader = ref(null)
|
||||
const gameVersion = ref(null)
|
||||
const creatingInstance = ref(false)
|
||||
|
||||
const profiles = ref([])
|
||||
|
||||
const shownProfiles = computed(() =>
|
||||
profiles.value.filter((profile) => {
|
||||
return profile.name.toLowerCase().includes(searchFilter.value.toLowerCase())
|
||||
}),
|
||||
)
|
||||
|
||||
const isProfileCompatible = (profile) =>
|
||||
versions.value?.some((version) => isVersionCompatible(version, project.value, profile))
|
||||
|
||||
const onInstall = ref(() => {})
|
||||
|
||||
defineExpose({
|
||||
show: async (projectVal, versionsVal, callback) => {
|
||||
project.value = projectVal
|
||||
versions.value = versionsVal
|
||||
searchFilter.value = ''
|
||||
|
||||
showCreation.value = false
|
||||
name.value = null
|
||||
icon.value = null
|
||||
display_icon.value = null
|
||||
gameVersion.value = null
|
||||
loader.value = null
|
||||
|
||||
onInstall.value = callback
|
||||
|
||||
const profilesVal = await list().catch(handleError)
|
||||
for (const profile of profilesVal) {
|
||||
profile.installing = false
|
||||
profile.installedMod = await check_installed(profile.path, project.value.id).catch(
|
||||
handleError,
|
||||
)
|
||||
}
|
||||
|
||||
const linkedProjectIds = profilesVal
|
||||
.filter((p) => p.linked_data?.project_id)
|
||||
.map((p) => p.linked_data.project_id)
|
||||
if (linkedProjectIds.length > 0) {
|
||||
const linkedProjects = await get_project_v3_many(linkedProjectIds, 'must_revalidate').catch(
|
||||
() => [],
|
||||
)
|
||||
const serverProjectIds = new Set(
|
||||
linkedProjects.filter((p) => p?.minecraft_server != null).map((p) => p.id),
|
||||
)
|
||||
for (const profile of profilesVal) {
|
||||
profile.isServerInstance = serverProjectIds.has(profile.linked_data?.project_id)
|
||||
}
|
||||
}
|
||||
|
||||
profiles.value = profilesVal
|
||||
|
||||
installModal.value.show()
|
||||
|
||||
trackEvent('ProjectInstallStart', { source: 'ProjectInstallModal' })
|
||||
},
|
||||
})
|
||||
|
||||
async function install(instance) {
|
||||
instance.installing = true
|
||||
const version = findPreferredVersion(versions.value, project.value, instance)
|
||||
|
||||
if (!version) {
|
||||
instance.installing = false
|
||||
handleError('No compatible version found')
|
||||
return
|
||||
}
|
||||
|
||||
await installMod(instance.path, version.id, 'standalone').catch(handleError)
|
||||
await installVersionDependencies(instance, version).catch(handleError)
|
||||
|
||||
instance.installedMod = true
|
||||
instance.installing = false
|
||||
|
||||
trackEvent('ProjectInstall', {
|
||||
loader: instance.loader,
|
||||
game_version: instance.game_version,
|
||||
id: project.value.id,
|
||||
version_id: version.id,
|
||||
project_type: project.value.project_type,
|
||||
title: project.value.title,
|
||||
source: 'ProjectInstallModal',
|
||||
})
|
||||
|
||||
onInstall.value(version.id)
|
||||
}
|
||||
|
||||
const toggleCreation = () => {
|
||||
showCreation.value = !showCreation.value
|
||||
name.value = null
|
||||
icon.value = null
|
||||
display_icon.value = null
|
||||
gameVersion.value = null
|
||||
loader.value = null
|
||||
|
||||
if (showCreation.value) {
|
||||
trackEvent('InstanceCreateStart', { source: 'ProjectInstallModal' })
|
||||
}
|
||||
}
|
||||
|
||||
const upload_icon = async () => {
|
||||
const res = await open({
|
||||
multiple: false,
|
||||
filters: [
|
||||
{
|
||||
name: 'Image',
|
||||
extensions: ['png', 'jpeg'],
|
||||
},
|
||||
],
|
||||
})
|
||||
icon.value = res.path ?? res
|
||||
|
||||
if (!icon.value) return
|
||||
display_icon.value = convertFileSrc(icon.value)
|
||||
}
|
||||
|
||||
const reset_icon = () => {
|
||||
icon.value = null
|
||||
display_icon.value = null
|
||||
}
|
||||
|
||||
const createInstance = async () => {
|
||||
creatingInstance.value = true
|
||||
|
||||
const gameVersions = versions.value[0].game_versions
|
||||
const gameVersion = gameVersions[0]
|
||||
|
||||
const loaders = versions.value[0].loaders
|
||||
const loader = loaders.includes('fabric')
|
||||
? 'fabric'
|
||||
: loaders.includes('neoforge')
|
||||
? 'neoforge'
|
||||
: loaders.includes('forge')
|
||||
? 'forge'
|
||||
: loaders.includes('quilt')
|
||||
? 'quilt'
|
||||
: 'vanilla'
|
||||
|
||||
const id = await create(name.value, gameVersion, loader, 'latest', icon.value).catch(handleError)
|
||||
|
||||
await installMod(id, versions.value[0].id, 'standalone').catch(handleError)
|
||||
|
||||
await router.push(`/instance/${encodeURIComponent(id)}/`)
|
||||
|
||||
const instance = await get(id, true)
|
||||
await installVersionDependencies(instance, versions.value[0]).catch(handleError)
|
||||
|
||||
trackEvent('InstanceCreate', {
|
||||
profile_name: name.value,
|
||||
game_version: versions.value[0].game_versions[0],
|
||||
loader: loader,
|
||||
loader_version: 'latest',
|
||||
has_icon: !!icon.value,
|
||||
source: 'ProjectInstallModal',
|
||||
})
|
||||
|
||||
trackEvent('ProjectInstall', {
|
||||
loader: loader,
|
||||
game_version: versions.value[0].game_versions[0],
|
||||
id: project.value,
|
||||
version_id: versions.value[0].id,
|
||||
project_type: project.value.project_type,
|
||||
title: project.value.title,
|
||||
source: 'ProjectInstallModal',
|
||||
})
|
||||
|
||||
onInstall.value(versions.value[0].id)
|
||||
|
||||
if (installModal.value) installModal.value.hide()
|
||||
creatingInstance.value = false
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ModalWrapper ref="installModal" header="Install project to instance" :on-hide="onInstall">
|
||||
<div class="modal-body">
|
||||
<StyledInput
|
||||
v-model="searchFilter"
|
||||
:icon="SearchIcon"
|
||||
type="search"
|
||||
placeholder="Search for an instance"
|
||||
autocomplete="off"
|
||||
/>
|
||||
<div class="profiles" :class="{ 'hide-creation': !showCreation }">
|
||||
<div v-for="profile in shownProfiles" :key="profile.name" class="option">
|
||||
<router-link
|
||||
class="btn btn-transparent profile-button"
|
||||
:to="`/instance/${encodeURIComponent(profile.path)}`"
|
||||
@click="installModal.hide()"
|
||||
>
|
||||
<Avatar
|
||||
:src="profile.icon_path ? convertFileSrc(profile.icon_path) : null"
|
||||
class="profile-image"
|
||||
/>
|
||||
{{ profile.name }}
|
||||
</router-link>
|
||||
<div
|
||||
v-tooltip="
|
||||
profile.linked_data?.locked && !profile.installedMod
|
||||
? 'Unpair or unlock an instance to add mods.'
|
||||
: ''
|
||||
"
|
||||
>
|
||||
<Button
|
||||
:disabled="
|
||||
!isProfileCompatible(profile) || profile.installedMod || profile.installing
|
||||
"
|
||||
@click="install(profile)"
|
||||
>
|
||||
<DownloadIcon
|
||||
v-if="isProfileCompatible(profile) && !profile.installedMod && !profile.installing"
|
||||
/>
|
||||
<CheckIcon v-else-if="profile.installedMod" />
|
||||
{{
|
||||
profile.installing
|
||||
? 'Installing...'
|
||||
: profile.installedMod
|
||||
? 'Installed'
|
||||
: !isProfileCompatible(profile)
|
||||
? 'Incompatible'
|
||||
: 'Install'
|
||||
}}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Card v-if="showCreation" class="creation-card">
|
||||
<div class="creation-container">
|
||||
<div class="creation-icon">
|
||||
<Avatar size="md" class="icon" :src="display_icon" />
|
||||
<div class="creation-icon__description">
|
||||
<Button @click="upload_icon()">
|
||||
<UploadIcon />
|
||||
<span class="no-wrap"> Select icon </span>
|
||||
</Button>
|
||||
<Button :disabled="!display_icon" @click="reset_icon()">
|
||||
<XIcon />
|
||||
<span class="no-wrap"> Remove icon </span>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="creation-settings">
|
||||
<StyledInput
|
||||
v-model="name"
|
||||
autocomplete="off"
|
||||
type="text"
|
||||
placeholder="Name"
|
||||
class="creation-input"
|
||||
/>
|
||||
<Button :disabled="creatingInstance === true || !name" @click="createInstance()">
|
||||
<RightArrowIcon />
|
||||
{{ creatingInstance ? 'Creating...' : 'Create' }}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
<div class="input-group push-right">
|
||||
<Button :color="showCreation ? '' : 'primary'" @click="toggleCreation()">
|
||||
<PlusIcon />
|
||||
{{ showCreation ? 'Hide New Instance' : 'Create new instance' }}
|
||||
</Button>
|
||||
<Button @click="installModal.hide()">Cancel</Button>
|
||||
</div>
|
||||
</div>
|
||||
</ModalWrapper>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.creation-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
margin: 0;
|
||||
background-color: var(--color-bg);
|
||||
}
|
||||
|
||||
.creation-container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.creation-icon {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 1rem;
|
||||
align-items: center;
|
||||
flex-grow: 1;
|
||||
|
||||
.creation-icon__description {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
.creation-input {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.no-wrap {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.creation-dropdown {
|
||||
width: min-content !important;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.creation-settings {
|
||||
width: 100%;
|
||||
margin-left: 0.5rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.modal-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
min-width: 350px;
|
||||
}
|
||||
|
||||
.profiles {
|
||||
max-height: 12rem;
|
||||
overflow-y: auto;
|
||||
|
||||
&.hide-creation {
|
||||
max-height: 21rem;
|
||||
}
|
||||
}
|
||||
|
||||
.option {
|
||||
width: calc(100%);
|
||||
background: var(--color-raised-bg);
|
||||
color: var(--color-base);
|
||||
box-shadow: none;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
|
||||
img {
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
|
||||
.name {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.profile-button {
|
||||
align-content: start;
|
||||
padding: 0.5rem;
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
|
||||
.profile-image {
|
||||
--size: 2rem !important;
|
||||
}
|
||||
</style>
|
||||
@@ -7,7 +7,7 @@
|
||||
<template #actions>
|
||||
<div class="flex gap-2 justify-end">
|
||||
<ButtonStyled type="outlined">
|
||||
<button class="!border !border-surface-4" @click="modal?.hide()">
|
||||
<button @click="modal?.hide()">
|
||||
<XIcon />
|
||||
{{ formatMessage(commonMessages.cancelButton) }}
|
||||
</button>
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<template #actions>
|
||||
<div class="flex gap-2 justify-end">
|
||||
<ButtonStyled type="outlined">
|
||||
<button class="!border !border-surface-4" @click="handleCancel">
|
||||
<button @click="handleCancel">
|
||||
<XIcon />
|
||||
{{ formatMessage(commonMessages.cancelButton) }}
|
||||
</button>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script setup>
|
||||
import { BoxIcon, FolderSearchIcon, TrashIcon } from '@modrinth/assets'
|
||||
import { Button, injectNotificationManager, Slider, StyledInput } from '@modrinth/ui'
|
||||
import { ButtonStyled, injectNotificationManager, Slider, StyledInput } from '@modrinth/ui'
|
||||
import { open } from '@tauri-apps/plugin-dialog'
|
||||
import { ref, watch } from 'vue'
|
||||
|
||||
@@ -73,9 +73,11 @@ async function findLauncherDir() {
|
||||
wrapper-class="w-full"
|
||||
>
|
||||
<template #right>
|
||||
<Button class="ml-1.5" @click="findLauncherDir">
|
||||
<FolderSearchIcon />
|
||||
</Button>
|
||||
<ButtonStyled circular>
|
||||
<button class="ml-1.5" @click="findLauncherDir">
|
||||
<FolderSearchIcon />
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</template>
|
||||
</StyledInput>
|
||||
<p class="m-0 leading-tight text-secondary">
|
||||
|
||||
@@ -25,7 +25,9 @@
|
||||
<div class="flex flex-col gap-4 w-full min-h-[20rem]">
|
||||
<section>
|
||||
<h2 class="text-base font-semibold mb-2">Texture</h2>
|
||||
<Button @click="openUploadSkinModal"> <UploadIcon /> Replace texture </Button>
|
||||
<ButtonStyled>
|
||||
<button @click="openUploadSkinModal"><UploadIcon /> Replace texture</button>
|
||||
</ButtonStyled>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
@@ -79,7 +81,7 @@
|
||||
</div>
|
||||
|
||||
<div class="flex gap-2 mt-12">
|
||||
<ButtonStyled color="brand" :disabled="disableSave || isSaving">
|
||||
<ButtonStyled color="brand">
|
||||
<button v-tooltip="saveTooltip" :disabled="disableSave || isSaving" @click="save">
|
||||
<SpinnerIcon v-if="isSaving" class="animate-spin" />
|
||||
<CheckIcon v-else-if="mode === 'new'" />
|
||||
@@ -87,7 +89,9 @@
|
||||
{{ mode === 'new' ? 'Add skin' : 'Save skin' }}
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<Button :disabled="isSaving" @click="hide"><XIcon />Cancel</Button>
|
||||
<ButtonStyled type="outlined">
|
||||
<button :disabled="isSaving" @click="hide"><XIcon />Cancel</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
</ModalWrapper>
|
||||
|
||||
@@ -109,7 +113,6 @@ import {
|
||||
XIcon,
|
||||
} from '@modrinth/assets'
|
||||
import {
|
||||
Button,
|
||||
ButtonStyled,
|
||||
CapeButton,
|
||||
CapeLikeTextButton,
|
||||
|
||||
@@ -93,7 +93,7 @@ defineExpose({ show, hide })
|
||||
<template #actions>
|
||||
<div class="flex gap-2 justify-end">
|
||||
<ButtonStyled type="outlined">
|
||||
<button class="!border !border-surface-4" @click="hide()">
|
||||
<button @click="hide()">
|
||||
<XIcon />
|
||||
{{ formatMessage(commonMessages.cancelButton) }}
|
||||
</button>
|
||||
|
||||
@@ -106,7 +106,7 @@ const titleMessage = defineMessage({
|
||||
<template #actions>
|
||||
<div class="flex gap-2 justify-end">
|
||||
<ButtonStyled type="outlined">
|
||||
<button class="!border !border-surface-4" @click="hide()">
|
||||
<button @click="hide()">
|
||||
<XIcon />
|
||||
{{ formatMessage(commonMessages.cancelButton) }}
|
||||
</button>
|
||||
|
||||
@@ -692,6 +692,24 @@
|
||||
"instance.worlds.world_in_use": {
|
||||
"message": "World is in use"
|
||||
},
|
||||
"minecraft-account.add-account": {
|
||||
"message": "Add account"
|
||||
},
|
||||
"minecraft-account.label": {
|
||||
"message": "Minecraft account"
|
||||
},
|
||||
"minecraft-account.not-signed-in": {
|
||||
"message": "Not signed in"
|
||||
},
|
||||
"minecraft-account.remove-account": {
|
||||
"message": "Remove account"
|
||||
},
|
||||
"minecraft-account.select-account": {
|
||||
"message": "Select account"
|
||||
},
|
||||
"minecraft-account.sign-in": {
|
||||
"message": "Sign in to Minecraft"
|
||||
},
|
||||
"search.filter.locked.instance": {
|
||||
"message": "Provided by the instance"
|
||||
},
|
||||
|
||||
@@ -9,7 +9,6 @@ import {
|
||||
UpdatedIcon,
|
||||
} from '@modrinth/assets'
|
||||
import {
|
||||
Button,
|
||||
ButtonStyled,
|
||||
ConfirmModal,
|
||||
injectNotificationManager,
|
||||
@@ -383,25 +382,25 @@ await Promise.all([loadCapes(), loadSkins(), loadCurrentUser()])
|
||||
@select="changeSkin(skin)"
|
||||
>
|
||||
<template #overlay-buttons>
|
||||
<Button
|
||||
color="green"
|
||||
aria-label="Edit skin"
|
||||
class="pointer-events-auto"
|
||||
@click.stop="(e: MouseEvent) => editSkinModal?.show(e, skin)"
|
||||
>
|
||||
<EditIcon /> Edit
|
||||
</Button>
|
||||
<Button
|
||||
v-show="!skin.is_equipped"
|
||||
v-tooltip="'Delete skin'"
|
||||
aria-label="Delete skin"
|
||||
color="red"
|
||||
class="!rounded-[100%] pointer-events-auto"
|
||||
icon-only
|
||||
@click.stop="() => confirmDeleteSkin(skin)"
|
||||
>
|
||||
<TrashIcon />
|
||||
</Button>
|
||||
<ButtonStyled color="brand">
|
||||
<button
|
||||
aria-label="Edit skin"
|
||||
class="pointer-events-auto"
|
||||
@click.stop="(e: MouseEvent) => editSkinModal?.show(e, skin)"
|
||||
>
|
||||
<EditIcon /> Edit
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled v-show="!skin.is_equipped" circular color="red">
|
||||
<button
|
||||
v-tooltip="'Delete skin'"
|
||||
aria-label="Delete skin"
|
||||
class="!rounded-[100%] pointer-events-auto"
|
||||
@click.stop="() => confirmDeleteSkin(skin)"
|
||||
>
|
||||
<TrashIcon />
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</template>
|
||||
</SkinButton>
|
||||
</div>
|
||||
|
||||
@@ -55,7 +55,7 @@
|
||||
/>
|
||||
<div class="flex gap-2">
|
||||
<ButtonStyled type="outlined">
|
||||
<button class="!h-10 !border-button-bg !border-[1px]" @click="addServerModal?.show()">
|
||||
<button class="!h-10" @click="addServerModal?.show()">
|
||||
<PlusIcon class="size-5" />
|
||||
{{ formatMessage(messages.addServer) }}
|
||||
</button>
|
||||
@@ -141,7 +141,7 @@
|
||||
>
|
||||
<template #actions>
|
||||
<ButtonStyled type="outlined">
|
||||
<button class="!h-10 !border-button-bg !border-[1px]" @click="addServerModal?.show()">
|
||||
<button class="!h-10" @click="addServerModal?.show()">
|
||||
<PlusIcon class="size-5" />
|
||||
{{ formatMessage(messages.addServer) }}
|
||||
</button>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { PlusIcon } from '@modrinth/assets'
|
||||
import { Button, injectNotificationManager, NavTabs } from '@modrinth/ui'
|
||||
import { ButtonStyled, injectNotificationManager, NavTabs } from '@modrinth/ui'
|
||||
import { inject, onUnmounted, ref, shallowRef } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
|
||||
@@ -55,10 +55,12 @@ onUnmounted(() => {
|
||||
<NewInstanceImage />
|
||||
</div>
|
||||
<h3>No instances found</h3>
|
||||
<Button color="primary" :disabled="offline" @click="showCreationModal?.()">
|
||||
<PlusIcon />
|
||||
Create new instance
|
||||
</Button>
|
||||
<ButtonStyled color="brand">
|
||||
<button :disabled="offline" @click="showCreationModal?.()">
|
||||
<PlusIcon />
|
||||
Create new instance
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -39,35 +39,40 @@
|
||||
</div>
|
||||
<div class="controls">
|
||||
<div class="buttons">
|
||||
<Button class="close" icon-only @click="hideImage">
|
||||
<XIcon aria-hidden="true" />
|
||||
</Button>
|
||||
<a
|
||||
class="open btn icon-only"
|
||||
target="_blank"
|
||||
:href="
|
||||
expandedGalleryItem.raw_url
|
||||
? expandedGalleryItem.raw_url
|
||||
: 'https://cdn.modrinth.com/placeholder-banner.svg'
|
||||
"
|
||||
>
|
||||
<ExternalIcon aria-hidden="true" />
|
||||
</a>
|
||||
<Button icon-only @click="zoomedIn = !zoomedIn">
|
||||
<ExpandIcon v-if="!zoomedIn" aria-hidden="true" />
|
||||
<ContractIcon v-else aria-hidden="true" />
|
||||
</Button>
|
||||
<Button
|
||||
v-if="filteredGallery.length > 1"
|
||||
class="previous"
|
||||
icon-only
|
||||
@click="previousImage()"
|
||||
>
|
||||
<LeftArrowIcon aria-hidden="true" />
|
||||
</Button>
|
||||
<Button v-if="filteredGallery.length > 1" class="next" icon-only @click="nextImage()">
|
||||
<RightArrowIcon aria-hidden="true" />
|
||||
</Button>
|
||||
<ButtonStyled circular>
|
||||
<button class="close" @click="hideImage">
|
||||
<XIcon aria-hidden="true" />
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled circular>
|
||||
<a
|
||||
class="open btn icon-only"
|
||||
target="_blank"
|
||||
:href="
|
||||
expandedGalleryItem.raw_url
|
||||
? expandedGalleryItem.raw_url
|
||||
: 'https://cdn.modrinth.com/placeholder-banner.svg'
|
||||
"
|
||||
>
|
||||
<ExternalIcon aria-hidden="true" />
|
||||
</a>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled circular>
|
||||
<button @click="zoomedIn = !zoomedIn">
|
||||
<ExpandIcon v-if="!zoomedIn" aria-hidden="true" />
|
||||
<ContractIcon v-else aria-hidden="true" />
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled v-if="filteredGallery.length > 1" circular>
|
||||
<button class="previous" @click="previousImage()">
|
||||
<LeftArrowIcon aria-hidden="true" />
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled v-if="filteredGallery.length > 1" circular>
|
||||
<button class="next" @click="nextImage()">
|
||||
<RightArrowIcon aria-hidden="true" />
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -85,7 +90,7 @@ import {
|
||||
RightArrowIcon,
|
||||
XIcon,
|
||||
} from '@modrinth/assets'
|
||||
import { Button, Card, useFormatDateTime } from '@modrinth/ui'
|
||||
import { ButtonStyled, Card, useFormatDateTime } from '@modrinth/ui'
|
||||
import { computed, onMounted, onUnmounted, ref } from 'vue'
|
||||
|
||||
import { hide_ads_window, show_ads_window } from '@/helpers/ads.js'
|
||||
|
||||
@@ -14,34 +14,38 @@
|
||||
<h2>{{ version.name }}</h2>
|
||||
</div>
|
||||
<div class="button-group">
|
||||
<Button
|
||||
color="primary"
|
||||
:action="() => install(version.id)"
|
||||
:disabled="installing || (installed && installedVersion === version.id)"
|
||||
>
|
||||
<DownloadIcon v-if="!installed" />
|
||||
<SwapIcon v-else-if="installedVersion !== version.id" />
|
||||
<CheckIcon v-else />
|
||||
{{
|
||||
installing
|
||||
? 'Installing...'
|
||||
: installed && installedVersion === version.id
|
||||
? 'Installed'
|
||||
: 'Install'
|
||||
}}
|
||||
</Button>
|
||||
<Button>
|
||||
<ReportIcon />
|
||||
Report
|
||||
</Button>
|
||||
<a
|
||||
:href="`https://modrinth.com/mod/${route.params.id}/version/${route.params.version}`"
|
||||
rel="external"
|
||||
class="btn"
|
||||
>
|
||||
<ExternalIcon />
|
||||
Modrinth website
|
||||
</a>
|
||||
<ButtonStyled color="brand">
|
||||
<button
|
||||
:disabled="installing || (installed && installedVersion === version.id)"
|
||||
@click="() => install(version.id)"
|
||||
>
|
||||
<DownloadIcon v-if="!installed" />
|
||||
<SwapIcon v-else-if="installedVersion !== version.id" />
|
||||
<CheckIcon v-else />
|
||||
{{
|
||||
installing
|
||||
? 'Installing...'
|
||||
: installed && installedVersion === version.id
|
||||
? 'Installed'
|
||||
: 'Install'
|
||||
}}
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled>
|
||||
<button>
|
||||
<ReportIcon />
|
||||
Report
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled>
|
||||
<a
|
||||
:href="`https://modrinth.com/mod/${route.params.id}/version/${route.params.version}`"
|
||||
rel="external"
|
||||
>
|
||||
Modrinth website
|
||||
<ExternalIcon />
|
||||
</a>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
</Card>
|
||||
<div class="version-container">
|
||||
@@ -68,16 +72,13 @@
|
||||
<span v-if="file.primary" class="primary-label"> Primary </span>
|
||||
</span>
|
||||
</span>
|
||||
<Button
|
||||
v-if="project.project_type !== 'modpack' || file.primary"
|
||||
class="download"
|
||||
:action="() => install(version.id)"
|
||||
:disabled="installed"
|
||||
>
|
||||
<DownloadIcon v-if="!installed" />
|
||||
<CheckIcon v-else />
|
||||
{{ installed ? 'Installed' : 'Install' }}
|
||||
</Button>
|
||||
<ButtonStyled v-if="project.project_type !== 'modpack' || file.primary" color="brand">
|
||||
<button class="download" :disabled="installed" @click="() => install(version.id)">
|
||||
<DownloadIcon v-if="!installed" />
|
||||
<CheckIcon v-else />
|
||||
{{ installed ? 'Installed' : 'Install' }}
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</Card>
|
||||
</Card>
|
||||
<Card v-if="displayDependencies.length > 0">
|
||||
@@ -168,7 +169,15 @@
|
||||
|
||||
<script setup>
|
||||
import { CheckIcon, DownloadIcon, ExternalIcon, FileIcon, ReportIcon } from '@modrinth/assets'
|
||||
import { Avatar, Badge, Breadcrumbs, Button, Card, CopyCode, useFormatDateTime } from '@modrinth/ui'
|
||||
import {
|
||||
Avatar,
|
||||
Badge,
|
||||
Breadcrumbs,
|
||||
ButtonStyled,
|
||||
Card,
|
||||
CopyCode,
|
||||
useFormatDateTime,
|
||||
} from '@modrinth/ui'
|
||||
import { formatBytes, renderString } from '@modrinth/utils'
|
||||
import { computed, ref, watch } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
|
||||
@@ -134,10 +134,6 @@ const [loaders, gameVersions] = await Promise.all([
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
flex-grow: 1;
|
||||
|
||||
.multiselect {
|
||||
flex-grow: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.card-row {
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1,64 +1,8 @@
|
||||
/*
|
||||
Base components
|
||||
*/
|
||||
|
||||
.known-error .multiselect__tags {
|
||||
border-color: var(--color-red) !important;
|
||||
background-color: var(--color-warning-bg) !important;
|
||||
|
||||
&::placeholder {
|
||||
color: var(--color-warning-text);
|
||||
}
|
||||
}
|
||||
|
||||
.grid-display {
|
||||
display: grid;
|
||||
grid-gap: var(--spacing-card-md);
|
||||
grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr));
|
||||
|
||||
.grid-display__item {
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
background-color: var(--color-bg);
|
||||
border-radius: var(--size-rounded-card);
|
||||
padding: var(--spacing-card-lg);
|
||||
gap: var(--spacing-card-md);
|
||||
outline: 1px solid transparent;
|
||||
|
||||
.label {
|
||||
color: var(--color-heading);
|
||||
font-weight: bold;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.value {
|
||||
color: var(--color-text-primary);
|
||||
font-weight: bold;
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
.goto-link {
|
||||
margin-top: auto;
|
||||
}
|
||||
}
|
||||
|
||||
&.width-12 {
|
||||
grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr));
|
||||
}
|
||||
|
||||
&.width-16 {
|
||||
grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr));
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Cards and body styling
|
||||
*/
|
||||
.base-card {
|
||||
@extend .padding-lg;
|
||||
|
||||
padding: var(--spacing-card-lg);
|
||||
position: relative;
|
||||
|
||||
background-color: var(--color-raised-bg);
|
||||
@@ -129,73 +73,9 @@
|
||||
}
|
||||
}
|
||||
|
||||
.padding-lg {
|
||||
padding: var(--spacing-card-lg);
|
||||
}
|
||||
|
||||
.padding-bg {
|
||||
padding: var(--spacing-card-bg);
|
||||
}
|
||||
|
||||
.padding-md {
|
||||
padding: var(--spacing-card-md);
|
||||
}
|
||||
|
||||
.padding-sm {
|
||||
padding: var(--spacing-card-sm);
|
||||
}
|
||||
|
||||
.padding-0 {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.padding-block-lg {
|
||||
padding-block: var(--spacing-card-lg);
|
||||
}
|
||||
|
||||
.padding-block-bg {
|
||||
padding-block: var(--spacing-card-bg);
|
||||
}
|
||||
|
||||
.padding-block-md {
|
||||
padding-block: var(--spacing-card-md);
|
||||
}
|
||||
|
||||
.padding-block-sm {
|
||||
padding-block: var(--spacing-card-sm);
|
||||
}
|
||||
|
||||
.padding-block-0 {
|
||||
padding-block: 0;
|
||||
}
|
||||
|
||||
.padding-inline-lg {
|
||||
padding-inline: var(--spacing-card-lg);
|
||||
}
|
||||
|
||||
.padding-inline-bg {
|
||||
padding-inline: var(--spacing-card-bg);
|
||||
}
|
||||
|
||||
.padding-inline-md {
|
||||
padding-inline: var(--spacing-card-md);
|
||||
}
|
||||
|
||||
.padding-inline-sm {
|
||||
padding-inline: var(--spacing-card-sm);
|
||||
}
|
||||
|
||||
.padding-inline-0 {
|
||||
padding-inline: 0;
|
||||
}
|
||||
|
||||
.universal-body {
|
||||
@extend .universal-labels;
|
||||
|
||||
.multiselect {
|
||||
width: 15rem;
|
||||
}
|
||||
|
||||
> :where(input + *, .input-group + *, .chips + *, .input-div + *) {
|
||||
&:not(:empty) {
|
||||
margin-block-start: var(--spacing-card-md);
|
||||
@@ -340,19 +220,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
.navigation-card {
|
||||
@extend .base-card;
|
||||
@extend .padding-inline-lg;
|
||||
@extend .padding-block-md;
|
||||
|
||||
align-items: center;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
flex-wrap: wrap;
|
||||
row-gap: 0.5rem;
|
||||
min-height: 3.75rem;
|
||||
}
|
||||
|
||||
/*
|
||||
Other
|
||||
*/
|
||||
@@ -372,19 +239,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
.title-link {
|
||||
text-decoration: underline;
|
||||
|
||||
&:focus-visible,
|
||||
&:hover {
|
||||
color: var(--color-heading);
|
||||
}
|
||||
|
||||
&:active {
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
}
|
||||
|
||||
.button-base {
|
||||
@extend .button-animation;
|
||||
font-weight: 500;
|
||||
@@ -455,36 +309,6 @@ tr.button-transparent {
|
||||
}
|
||||
}
|
||||
|
||||
.button-within {
|
||||
transition:
|
||||
opacity 0.5s ease-in-out,
|
||||
filter 0.2s ease-in-out,
|
||||
transform 0.05s ease-in-out,
|
||||
outline 0.2s ease-in-out;
|
||||
|
||||
&:focus-visible:not(&.disabled),
|
||||
&:hover:not(&.disabled) {
|
||||
filter: brightness(0.85);
|
||||
}
|
||||
|
||||
&:active:not(&.disabled) {
|
||||
filter: brightness(0.8);
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
cursor: not-allowed;
|
||||
filter: grayscale(50%);
|
||||
opacity: 0.5;
|
||||
box-shadow: none;
|
||||
|
||||
&disabled,
|
||||
&[disabled='true'] {
|
||||
cursor: not-allowed;
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.button-color-base {
|
||||
box-sizing: border-box;
|
||||
--text-color: var(--color-button-text);
|
||||
@@ -536,10 +360,6 @@ tr.button-transparent {
|
||||
background: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
&.bold-button {
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
.square-button {
|
||||
@@ -570,24 +390,6 @@ tr.button-transparent {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.header-button {
|
||||
@extend .iconified-button;
|
||||
|
||||
box-sizing: content-box;
|
||||
min-height: unset;
|
||||
border-radius: var(--size-rounded-max);
|
||||
white-space: nowrap;
|
||||
padding: 0.5rem 0.75rem;
|
||||
max-height: 1.75rem;
|
||||
|
||||
svg {
|
||||
vertical-align: middle;
|
||||
margin-right: 0.5rem;
|
||||
height: 1.25rem;
|
||||
width: 1.25rem;
|
||||
}
|
||||
}
|
||||
|
||||
.raised-button {
|
||||
--background-color: var(--color-raised-bg);
|
||||
box-shadow: var(--shadow-inset-sm), var(--shadow-raised);
|
||||
@@ -608,11 +410,6 @@ tr.button-transparent {
|
||||
--text-color: var(--color-brand-inverted);
|
||||
}
|
||||
|
||||
.alt-brand-button {
|
||||
--background-color: var(--color-brand-highlight);
|
||||
--text-color: var(--color-text);
|
||||
}
|
||||
|
||||
.button-group {
|
||||
display: flex;
|
||||
grid-gap: var(--spacing-card-sm);
|
||||
@@ -621,21 +418,6 @@ tr.button-transparent {
|
||||
justify-content: right;
|
||||
}
|
||||
|
||||
.multiselect--above .multiselect__content-wrapper {
|
||||
border-top: none !important;
|
||||
border-top-left-radius: var(--size-rounded-card) !important;
|
||||
border-top-right-radius: var(--size-rounded-card) !important;
|
||||
}
|
||||
|
||||
.known-error .multiselect__tags {
|
||||
border-color: var(--color-red) !important;
|
||||
background-color: var(--color-warning-bg) !important;
|
||||
|
||||
&::placeholder {
|
||||
color: var(--color-warning-text);
|
||||
}
|
||||
}
|
||||
|
||||
.error {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -707,36 +489,6 @@ textarea.known-error {
|
||||
color: var(--color-link-active);
|
||||
}
|
||||
|
||||
h1 {
|
||||
.beta-badge {
|
||||
font-size: 0.4em;
|
||||
}
|
||||
}
|
||||
|
||||
.beta-badge {
|
||||
font-size: 0.7em;
|
||||
padding: 0.2rem 0.4rem;
|
||||
background-color: transparent;
|
||||
box-sizing: border-box;
|
||||
border: 2px solid var(--color-text);
|
||||
color: var(--color-text);
|
||||
border-radius: var(--size-rounded-max);
|
||||
margin-left: 0.5rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.router-link-exact-active,
|
||||
h1,
|
||||
h2,
|
||||
h3 {
|
||||
.beta-badge {
|
||||
background-color: var(--color-button-text-active);
|
||||
box-sizing: border-box;
|
||||
border-color: transparent;
|
||||
color: var(--color-raised-bg);
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion) {
|
||||
.button-animation,
|
||||
button {
|
||||
@@ -751,7 +503,6 @@ h3 {
|
||||
}
|
||||
|
||||
.full-width-inputs {
|
||||
.multiselect,
|
||||
input,
|
||||
.iconified-input {
|
||||
width: 100%;
|
||||
@@ -774,10 +525,6 @@ button {
|
||||
max-width: 100%;
|
||||
align-items: center;
|
||||
|
||||
.multiselect {
|
||||
width: 15rem;
|
||||
}
|
||||
|
||||
input {
|
||||
flex-shrink: 2;
|
||||
}
|
||||
@@ -799,20 +546,6 @@ button {
|
||||
}
|
||||
}
|
||||
|
||||
.input-stack {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
> *:not(:last-child) {
|
||||
margin-bottom: var(--spacing-card-sm);
|
||||
}
|
||||
|
||||
> .multiselect {
|
||||
width: unset;
|
||||
height: inherit;
|
||||
}
|
||||
}
|
||||
|
||||
.text-input-wrapper {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
@@ -844,22 +577,6 @@ button {
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.text-input-wrapper__after {
|
||||
display: flex;
|
||||
color: var(--color-text);
|
||||
padding: 0.5rem 1rem 0.5rem 0;
|
||||
font-weight: var(--font-weight-medium);
|
||||
min-height: 36px;
|
||||
box-sizing: border-box;
|
||||
width: fit-content;
|
||||
align-items: center;
|
||||
filter: grayscale(50%);
|
||||
opacity: 0.5;
|
||||
box-shadow: none;
|
||||
flex-shrink: 0;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
input,
|
||||
textarea {
|
||||
border-radius: 0;
|
||||
@@ -880,30 +597,6 @@ button {
|
||||
}
|
||||
}
|
||||
|
||||
.primary-stat {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
margin-bottom: 0.6rem;
|
||||
|
||||
.primary-stat__icon {
|
||||
height: 1rem;
|
||||
width: 1rem;
|
||||
}
|
||||
|
||||
.primary-stat__text {
|
||||
margin-left: 0.4rem;
|
||||
}
|
||||
|
||||
.primary-stat__counter {
|
||||
font-size: var(--font-size-lg);
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
&.no-margin {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.project-list {
|
||||
width: 100%;
|
||||
gap: var(--spacing-card-md);
|
||||
@@ -1033,181 +726,87 @@ a.iconified-link {
|
||||
}
|
||||
}
|
||||
|
||||
a.subtle-link {
|
||||
&:focus-visible,
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
filter: var(--hover-filter);
|
||||
}
|
||||
|
||||
&:active {
|
||||
filter: var(--active-filter);
|
||||
}
|
||||
}
|
||||
|
||||
.inline-svg svg,
|
||||
svg.inline-svg {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
// START STUFF FOR OMORPHIA
|
||||
.experimental-styles-within {
|
||||
.tag-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: var(--gap-4);
|
||||
.tag-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: var(--gap-4);
|
||||
}
|
||||
|
||||
.flex-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--gap-12);
|
||||
padding: var(--gap-16);
|
||||
|
||||
h2 {
|
||||
font-size: var(--text-18);
|
||||
font-weight: var(--weight-extrabold);
|
||||
color: var(--color-contrast);
|
||||
line-height: initial;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.tag-list__item {
|
||||
background-color: var(--_bg-color, var(--color-button-bg));
|
||||
padding: var(--gap-4) var(--gap-8);
|
||||
border-radius: var(--radius-max);
|
||||
h3 {
|
||||
font-size: var(--text-16);
|
||||
font-weight: var(--weight-bold);
|
||||
font-size: var(--text-14);
|
||||
display: flex;
|
||||
gap: var(--gap-4);
|
||||
align-items: center;
|
||||
vertical-align: middle;
|
||||
color: var(--_color, var(--color-secondary));
|
||||
|
||||
svg {
|
||||
width: var(--icon-14);
|
||||
height: var(--icon-14);
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
.status-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--gap-8);
|
||||
padding-left: var(--gap-6);
|
||||
|
||||
color: var(--color-base);
|
||||
font-weight: var(--weight-bold);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.status-list__item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--gap-4);
|
||||
|
||||
svg {
|
||||
width: var(--icon-16);
|
||||
height: var(--icon-16);
|
||||
margin-right: var(--gap-4);
|
||||
}
|
||||
|
||||
span {
|
||||
color: var(--color-secondary);
|
||||
font-style: italic;
|
||||
font-weight: var(--weight-normal);
|
||||
}
|
||||
}
|
||||
|
||||
.status-list__item--color-green svg {
|
||||
color: var(--color-green);
|
||||
}
|
||||
|
||||
.status-list__item--color-orange svg {
|
||||
color: var(--color-orange);
|
||||
}
|
||||
|
||||
.status-list__item--color-red svg {
|
||||
color: var(--color-red);
|
||||
}
|
||||
|
||||
.status-list__item--color-blue svg {
|
||||
color: var(--color-blue);
|
||||
}
|
||||
|
||||
.status-list__item--color-purple svg {
|
||||
color: var(--color-purple);
|
||||
}
|
||||
|
||||
&.flex-card,
|
||||
.flex-card {
|
||||
> section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--gap-12);
|
||||
padding: var(--gap-16);
|
||||
|
||||
h2 {
|
||||
font-size: var(--text-18);
|
||||
font-weight: var(--weight-extrabold);
|
||||
color: var(--color-contrast);
|
||||
line-height: initial;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: var(--text-16);
|
||||
font-weight: var(--weight-bold);
|
||||
color: var(--color-base);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
> section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--gap-8);
|
||||
}
|
||||
}
|
||||
|
||||
.list-style {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--gap-12);
|
||||
font-weight: var(--weight-bold);
|
||||
|
||||
hr {
|
||||
width: 100%;
|
||||
border-color: var(--color-button-border);
|
||||
margin-block: var(--gap-2);
|
||||
}
|
||||
}
|
||||
|
||||
.iconified-list-item {
|
||||
display: flex;
|
||||
gap: var(--gap-8);
|
||||
vertical-align: middle;
|
||||
align-items: center;
|
||||
width: fit-content;
|
||||
}
|
||||
}
|
||||
|
||||
svg {
|
||||
width: var(--icon-16);
|
||||
height: var(--icon-16);
|
||||
}
|
||||
.list-style {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--gap-12);
|
||||
font-weight: var(--weight-bold);
|
||||
|
||||
> svg:first-child {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
hr {
|
||||
width: 100%;
|
||||
border-color: var(--color-button-border);
|
||||
margin-block: var(--gap-2);
|
||||
}
|
||||
}
|
||||
|
||||
.iconified-list-item {
|
||||
display: flex;
|
||||
gap: var(--gap-8);
|
||||
vertical-align: middle;
|
||||
align-items: center;
|
||||
width: fit-content;
|
||||
|
||||
svg {
|
||||
width: var(--icon-16);
|
||||
height: var(--icon-16);
|
||||
}
|
||||
|
||||
.links-list {
|
||||
@extend .list-style;
|
||||
|
||||
> a {
|
||||
@extend .iconified-list-item;
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
> svg:first-child {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.details-list {
|
||||
@extend .list-style;
|
||||
}
|
||||
.details-list {
|
||||
@extend .list-style;
|
||||
}
|
||||
|
||||
.details-list__item {
|
||||
@extend .iconified-list-item;
|
||||
.details-list__item {
|
||||
@extend .iconified-list-item;
|
||||
|
||||
.details-list__item__text--style-secondary {
|
||||
color: var(--color-secondary);
|
||||
font-weight: var(--weight-normal);
|
||||
font-size: var(--text-14);
|
||||
}
|
||||
.details-list__item__text--style-secondary {
|
||||
color: var(--color-secondary);
|
||||
font-weight: var(--weight-normal);
|
||||
font-size: var(--text-14);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,9 +12,11 @@
|
||||
<div class="modal-body">
|
||||
<div v-if="header" class="header">
|
||||
<strong>{{ header }}</strong>
|
||||
<button class="iconified-button icon-only transparent" @click="hide">
|
||||
<XIcon />
|
||||
</button>
|
||||
<ButtonStyled circular type="transparent">
|
||||
<button @click="hide">
|
||||
<XIcon />
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
<div class="content">
|
||||
<slot />
|
||||
@@ -27,9 +29,11 @@
|
||||
|
||||
<script>
|
||||
import { XIcon } from '@modrinth/assets'
|
||||
import { ButtonStyled } from '@modrinth/ui'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
ButtonStyled,
|
||||
XIcon,
|
||||
},
|
||||
props: {
|
||||
|
||||
@@ -223,9 +223,7 @@ function developerModeIncrement() {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<footer
|
||||
class="footer-brand-background experimental-styles-within border-0 border-t-[1px] border-solid"
|
||||
>
|
||||
<footer class="footer-brand-background border-0 border-t-[1px] border-solid">
|
||||
<div class="mx-auto flex max-w-screen-xl flex-col gap-6 p-6 pb-20 sm:px-12 md:py-12">
|
||||
<div
|
||||
class="grid grid-cols-1 gap-4 text-primary md:grid-cols-[1fr_2fr] lg:grid-cols-[auto_auto_auto_auto_auto]"
|
||||
|
||||
@@ -199,47 +199,9 @@
|
||||
</span>
|
||||
<div v-if="compact" class="notification__actions">
|
||||
<template v-if="type === 'team_invite' || type === 'organization_invite'">
|
||||
<button
|
||||
v-tooltip="`Accept`"
|
||||
class="iconified-button square-button brand-button button-transparent"
|
||||
@click="
|
||||
() => {
|
||||
acceptTeamInvite(notification.body.team_id)
|
||||
read()
|
||||
}
|
||||
"
|
||||
>
|
||||
<CheckIcon />
|
||||
</button>
|
||||
<button
|
||||
v-tooltip="`Decline`"
|
||||
class="iconified-button square-button danger-button button-transparent"
|
||||
@click="
|
||||
() => {
|
||||
removeSelfFromTeam(notification.body.team_id)
|
||||
read()
|
||||
}
|
||||
"
|
||||
>
|
||||
<XIcon />
|
||||
</button>
|
||||
</template>
|
||||
<button
|
||||
v-else-if="!notification.read"
|
||||
v-tooltip="`Mark as read`"
|
||||
class="iconified-button square-button button-transparent"
|
||||
@click="read()"
|
||||
>
|
||||
<XIcon />
|
||||
</button>
|
||||
</div>
|
||||
<div v-else class="notification__actions">
|
||||
<div v-if="type !== null" class="input-group">
|
||||
<template
|
||||
v-if="(type === 'team_invite' || type === 'organization_invite') && !notification.read"
|
||||
>
|
||||
<ButtonStyled circular color="brand" type="transparent">
|
||||
<button
|
||||
class="iconified-button brand-button"
|
||||
v-tooltip="`Accept`"
|
||||
@click="
|
||||
() => {
|
||||
acceptTeamInvite(notification.body.team_id)
|
||||
@@ -248,10 +210,11 @@
|
||||
"
|
||||
>
|
||||
<CheckIcon />
|
||||
Accept
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled circular color="red" type="transparent">
|
||||
<button
|
||||
class="iconified-button danger-button"
|
||||
v-tooltip="`Decline`"
|
||||
@click="
|
||||
() => {
|
||||
removeSelfFromTeam(notification.body.team_id)
|
||||
@@ -260,51 +223,75 @@
|
||||
"
|
||||
>
|
||||
<XIcon />
|
||||
Decline
|
||||
</button>
|
||||
</template>
|
||||
<button
|
||||
v-else-if="!notification.read"
|
||||
class="iconified-button"
|
||||
:class="{ 'raised-button': raised }"
|
||||
@click="read()"
|
||||
>
|
||||
<CheckIcon />
|
||||
Mark as read
|
||||
</ButtonStyled>
|
||||
</template>
|
||||
<ButtonStyled v-else-if="!notification.read" circular type="transparent">
|
||||
<button v-tooltip="`Mark as read`" @click="read()">
|
||||
<XIcon />
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
<div v-else class="notification__actions">
|
||||
<div v-if="type !== null" class="input-group">
|
||||
<template
|
||||
v-if="(type === 'team_invite' || type === 'organization_invite') && !notification.read"
|
||||
>
|
||||
<ButtonStyled color="brand">
|
||||
<button
|
||||
@click="
|
||||
() => {
|
||||
acceptTeamInvite(notification.body.team_id)
|
||||
read()
|
||||
}
|
||||
"
|
||||
>
|
||||
<CheckIcon />
|
||||
Accept
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled color="red">
|
||||
<button
|
||||
@click="
|
||||
() => {
|
||||
removeSelfFromTeam(notification.body.team_id)
|
||||
read()
|
||||
}
|
||||
"
|
||||
>
|
||||
<XIcon />
|
||||
Decline
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</template>
|
||||
<ButtonStyled v-else-if="!notification.read">
|
||||
<button @click="read()">
|
||||
<CheckIcon />
|
||||
Mark as read
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<CopyCode v-if="flags.developerMode" :text="notification.id" />
|
||||
</div>
|
||||
<div v-else class="input-group">
|
||||
<nuxt-link
|
||||
v-if="notification.link && notification.link !== '#'"
|
||||
class="iconified-button"
|
||||
:class="{ 'raised-button': raised }"
|
||||
:to="notification.link"
|
||||
target="_blank"
|
||||
>
|
||||
<ExternalIcon />
|
||||
Open link
|
||||
</nuxt-link>
|
||||
<button
|
||||
v-for="(action, actionIndex) in notification.actions"
|
||||
:key="actionIndex"
|
||||
class="iconified-button"
|
||||
:class="{ 'raised-button': raised }"
|
||||
@click="performAction(notification, actionIndex)"
|
||||
>
|
||||
<CheckIcon v-if="action.title === 'Accept'" />
|
||||
<XIcon v-else-if="action.title === 'Deny'" />
|
||||
{{ action.title }}
|
||||
</button>
|
||||
<button
|
||||
v-if="notification.actions.length === 0 && !notification.read"
|
||||
class="iconified-button"
|
||||
:class="{ 'raised-button': raised }"
|
||||
@click="performAction(notification, null)"
|
||||
>
|
||||
<CheckIcon />
|
||||
Mark as read
|
||||
</button>
|
||||
<ButtonStyled v-if="notification.link && notification.link !== '#'">
|
||||
<nuxt-link :to="notification.link" target="_blank">
|
||||
<ExternalIcon />
|
||||
Open link
|
||||
</nuxt-link>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled v-for="(action, actionIndex) in notification.actions" :key="actionIndex">
|
||||
<button @click="performAction(notification, actionIndex)">
|
||||
<CheckIcon v-if="action.title === 'Accept'" />
|
||||
<XIcon v-else-if="action.title === 'Deny'" />
|
||||
{{ action.title }}
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled v-if="notification.actions.length === 0 && !notification.read">
|
||||
<button @click="performAction(notification, null)">
|
||||
<CheckIcon />
|
||||
Mark as read
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<CopyCode v-if="flags.developerMode" :text="notification.id" />
|
||||
</div>
|
||||
</div>
|
||||
@@ -325,6 +312,7 @@ import {
|
||||
} from '@modrinth/assets'
|
||||
import {
|
||||
Avatar,
|
||||
ButtonStyled,
|
||||
Categories,
|
||||
CopyCode,
|
||||
DoubleIcon,
|
||||
@@ -594,10 +582,6 @@ function getLoaderCategories(ver) {
|
||||
gap: var(--spacing-card-sm);
|
||||
}
|
||||
|
||||
.notification__actions .iconified-button.square-button svg {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.unknown-type {
|
||||
color: var(--color-red);
|
||||
}
|
||||
@@ -618,4 +602,8 @@ function getLoaderCategories(ver) {
|
||||
color: var(--color-blue);
|
||||
}
|
||||
}
|
||||
|
||||
.title-link {
|
||||
@apply underline hover:brightness-[--hover-brightness];
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<nav
|
||||
ref="scrollContainer"
|
||||
class="card-shadow experimental-styles-within relative flex w-fit overflow-x-auto rounded-full bg-bg-raised p-1 text-sm font-bold"
|
||||
class="card-shadow relative flex w-fit overflow-x-auto rounded-full bg-bg-raised p-1 text-sm font-bold"
|
||||
>
|
||||
<button
|
||||
v-for="(option, index) in options"
|
||||
|
||||
@@ -67,50 +67,55 @@
|
||||
</div>
|
||||
|
||||
<div class="table-cell">
|
||||
<nuxt-link
|
||||
class="btn icon-only"
|
||||
:to="`/project/${project.slug ? project.slug : project.id}/settings`"
|
||||
>
|
||||
<SettingsIcon />
|
||||
</nuxt-link>
|
||||
<ButtonStyled circular>
|
||||
<nuxt-link :to="`/project/${project.slug ? project.slug : project.id}/settings`">
|
||||
<SettingsIcon />
|
||||
</nuxt-link>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="push-right input-group">
|
||||
<Button @click="$refs.modalOpen?.hide()">
|
||||
<XIcon />
|
||||
Cancel
|
||||
</Button>
|
||||
<Button :disabled="!selectedProjects?.length" color="primary" @click="onSubmitHandler()">
|
||||
<TransferIcon />
|
||||
<span>
|
||||
Transfer
|
||||
<ButtonStyled type="outlined">
|
||||
<button @click="$refs.modalOpen?.hide()">
|
||||
<XIcon />
|
||||
Cancel
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled color="brand">
|
||||
<button :disabled="!selectedProjects?.length" @click="onSubmitHandler()">
|
||||
<TransferIcon />
|
||||
<span>
|
||||
{{
|
||||
selectedProjects.length === props.projects.length
|
||||
? 'All'
|
||||
: selectedProjects.length
|
||||
}}
|
||||
Transfer
|
||||
<span>
|
||||
{{
|
||||
selectedProjects.length === props.projects.length
|
||||
? 'All'
|
||||
: selectedProjects.length
|
||||
}}
|
||||
</span>
|
||||
<span>
|
||||
{{ ' ' }}
|
||||
{{ selectedProjects.length === 1 ? 'project' : 'projects' }}
|
||||
</span>
|
||||
</span>
|
||||
<span>
|
||||
{{ ' ' }}
|
||||
{{ selectedProjects.length === 1 ? 'project' : 'projects' }}
|
||||
</span>
|
||||
</span>
|
||||
</Button>
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
<Button @click="$refs.modalOpen?.show()">
|
||||
<TransferIcon />
|
||||
<span>Transfer projects</span>
|
||||
</Button>
|
||||
<ButtonStyled>
|
||||
<button @click="$refs.modalOpen?.show()">
|
||||
<TransferIcon />
|
||||
<span>Transfer projects</span>
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { BoxIcon, SettingsIcon, TransferIcon, XIcon } from '@modrinth/assets'
|
||||
import { Avatar, Button, Checkbox, CopyCode, Modal } from '@modrinth/ui'
|
||||
import { Avatar, ButtonStyled, Checkbox, CopyCode, Modal } from '@modrinth/ui'
|
||||
import { formatProjectType } from '@modrinth/utils'
|
||||
|
||||
const EDIT_DETAILS = 1 << 2
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { SettingsIcon } from '@modrinth/assets'
|
||||
import { defineMessages, PagewideBanner, useVIntl } from '@modrinth/ui'
|
||||
import { ButtonStyled, defineMessages, PagewideBanner, useVIntl } from '@modrinth/ui'
|
||||
|
||||
const { formatMessage } = useVIntl()
|
||||
|
||||
@@ -30,10 +30,12 @@ const messages = defineMessages({
|
||||
<span>{{ formatMessage(messages.description) }}</span>
|
||||
</template>
|
||||
<template #actions>
|
||||
<nuxt-link class="btn" to="/settings/billing">
|
||||
<SettingsIcon aria-hidden="true" />
|
||||
{{ formatMessage(messages.action) }}
|
||||
</nuxt-link>
|
||||
<ButtonStyled>
|
||||
<nuxt-link to="/settings/billing">
|
||||
<SettingsIcon aria-hidden="true" />
|
||||
{{ formatMessage(messages.action) }}
|
||||
</nuxt-link>
|
||||
</ButtonStyled>
|
||||
</template>
|
||||
</PagewideBanner>
|
||||
</template>
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
<script setup lang="ts">
|
||||
import { SettingsIcon } from '@modrinth/assets'
|
||||
import { defineMessages, injectNotificationManager, PagewideBanner, useVIntl } from '@modrinth/ui'
|
||||
import {
|
||||
ButtonStyled,
|
||||
defineMessages,
|
||||
injectNotificationManager,
|
||||
PagewideBanner,
|
||||
useVIntl,
|
||||
} from '@modrinth/ui'
|
||||
import { FetchError } from 'ofetch'
|
||||
|
||||
const { addNotification } = injectNotificationManager()
|
||||
@@ -91,13 +97,17 @@ async function handleResendEmailVerification() {
|
||||
</span>
|
||||
</template>
|
||||
<template #actions>
|
||||
<button v-if="hasEmail" class="btn" @click="handleResendEmailVerification">
|
||||
{{ formatMessage(verifyEmailBannerMessages.action) }}
|
||||
</button>
|
||||
<nuxt-link v-else class="btn" to="/settings/account">
|
||||
<SettingsIcon aria-hidden="true" />
|
||||
{{ formatMessage(addEmailBannerMessages.action) }}
|
||||
</nuxt-link>
|
||||
<ButtonStyled v-if="hasEmail">
|
||||
<button @click="handleResendEmailVerification">
|
||||
{{ formatMessage(verifyEmailBannerMessages.action) }}
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled v-else>
|
||||
<nuxt-link to="/settings/account">
|
||||
<SettingsIcon aria-hidden="true" />
|
||||
{{ formatMessage(addEmailBannerMessages.action) }}
|
||||
</nuxt-link>
|
||||
</ButtonStyled>
|
||||
</template>
|
||||
</PagewideBanner>
|
||||
</template>
|
||||
|
||||
@@ -81,15 +81,21 @@
|
||||
</span>
|
||||
</h2>
|
||||
<div class="chart-controls__buttons">
|
||||
<Button v-tooltip="'Toggle project colors'" icon-only @click="onToggleColors">
|
||||
<PaletteIcon />
|
||||
</Button>
|
||||
<Button v-tooltip="'Download this data as CSV'" icon-only @click="onDownloadSetAsCSV">
|
||||
<DownloadIcon />
|
||||
</Button>
|
||||
<Button v-tooltip="'Refresh the chart'" icon-only @click="resetCharts">
|
||||
<UpdatedIcon />
|
||||
</Button>
|
||||
<ButtonStyled circular type="outlined">
|
||||
<button v-tooltip="'Toggle project colors'" @click="onToggleColors">
|
||||
<PaletteIcon />
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled circular type="outlined">
|
||||
<button v-tooltip="'Download this data as CSV'" @click="onDownloadSetAsCSV">
|
||||
<DownloadIcon />
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled circular type="outlined">
|
||||
<button v-tooltip="'Refresh the chart'" @click="resetCharts">
|
||||
<UpdatedIcon />
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<DropdownSelect
|
||||
v-model="selectedRange"
|
||||
class="range-dropdown"
|
||||
@@ -156,8 +162,8 @@
|
||||
<template v-for="project in selectedDataSetProjects" :key="project">
|
||||
<button
|
||||
v-tooltip="project.title"
|
||||
:class="`legend__item button-base btn-transparent ${
|
||||
!projectIsOnDisplay(project.id) ? 'btn-dimmed' : ''
|
||||
:class="`legend__item button-base legend__item-transparent ${
|
||||
!projectIsOnDisplay(project.id) ? 'legend__item-dimmed' : ''
|
||||
}`"
|
||||
@click="
|
||||
() =>
|
||||
@@ -311,7 +317,7 @@
|
||||
<script setup lang="ts">
|
||||
import { DownloadIcon, PaletteIcon, UpdatedIcon } from '@modrinth/assets'
|
||||
import {
|
||||
Button,
|
||||
ButtonStyled,
|
||||
Card,
|
||||
DropdownSelect,
|
||||
useCompactNumber,
|
||||
@@ -842,7 +848,7 @@ const defaultRanges: RangeObject[] = [
|
||||
}
|
||||
}
|
||||
|
||||
.btn-transparent {
|
||||
.legend__item-transparent {
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
@@ -851,7 +857,7 @@ const defaultRanges: RangeObject[] = [
|
||||
font-weight: var(--font-weight-regular);
|
||||
}
|
||||
|
||||
.btn-dimmed {
|
||||
.legend__item-dimmed {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
|
||||
@@ -38,13 +38,13 @@
|
||||
{{ formatMessage(messages.collectionInfo, { count: projectIds.length }) }}
|
||||
</p>
|
||||
<div class="flex justify-end gap-2">
|
||||
<ButtonStyled class="w-24">
|
||||
<ButtonStyled type="outlined">
|
||||
<button @click="modal.hide()">
|
||||
<XIcon aria-hidden="true" />
|
||||
{{ formatMessage(commonMessages.cancelButton) }}
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled color="brand" class="w-36">
|
||||
<ButtonStyled color="brand">
|
||||
<button :disabled="hasHitLimit" @click="create">
|
||||
<PlusIcon aria-hidden="true" />
|
||||
{{ formatMessage(messages.createCollection) }}
|
||||
|
||||
@@ -59,13 +59,13 @@
|
||||
{{ formatMessage(messages.ownershipInfo) }}
|
||||
</p>
|
||||
<div class="flex justify-end gap-2">
|
||||
<ButtonStyled class="w-24">
|
||||
<ButtonStyled type="outlined">
|
||||
<button @click="hide">
|
||||
<XIcon aria-hidden="true" />
|
||||
{{ formatMessage(commonMessages.cancelButton) }}
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled color="brand" class="w-40">
|
||||
<ButtonStyled color="brand">
|
||||
<button :disabled="hasHitLimit" @click="createOrganization">
|
||||
<PlusIcon aria-hidden="true" />
|
||||
{{ formatMessage(messages.createOrganization) }}
|
||||
|
||||
@@ -110,13 +110,13 @@
|
||||
<span>{{ formatMessage(messages.summaryDescription) }}</span>
|
||||
</div>
|
||||
<div class="flex justify-end gap-2.5">
|
||||
<ButtonStyled class="w-24">
|
||||
<ButtonStyled type="outlined">
|
||||
<button @click="cancel">
|
||||
<XIcon aria-hidden="true" />
|
||||
{{ formatMessage(commonMessages.cancelButton) }}
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled color="brand" class="w-32">
|
||||
<ButtonStyled color="brand">
|
||||
<button v-tooltip="missingFieldsTooltip" :disabled="disableCreate" @click="createProject">
|
||||
<PlusIcon aria-hidden="true" />
|
||||
{{ formatMessage(messages.createProject) }}
|
||||
|
||||
@@ -58,24 +58,20 @@
|
||||
</div>
|
||||
<template #actions>
|
||||
<div v-if="currentStage === 'completion'" class="mt-4 flex w-full gap-3">
|
||||
<ButtonStyled class="flex-1">
|
||||
<button class="w-full text-contrast" @click="handleClose">
|
||||
<ButtonStyled>
|
||||
<button class="w-full flex-1 text-contrast" @click="handleClose">
|
||||
{{ formatMessage(commonMessages.closeButton) }}
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled class="flex-1">
|
||||
<button class="w-full text-contrast" @click="handleViewTransactions">
|
||||
<ButtonStyled>
|
||||
<button class="w-full flex-1 text-contrast" @click="handleViewTransactions">
|
||||
{{ formatMessage(messages.transactionsButton) }}
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
<div v-else class="mt-4 flex flex-col justify-end gap-2 sm:flex-row">
|
||||
<ButtonStyled type="outlined">
|
||||
<button
|
||||
class="!border-surface-5"
|
||||
:disabled="leftButtonConfig.disabled"
|
||||
@click="leftButtonConfig.handler"
|
||||
>
|
||||
<button :disabled="leftButtonConfig.disabled" @click="leftButtonConfig.handler">
|
||||
<component :is="leftButtonConfig.icon" />
|
||||
{{ leftButtonConfig.label }}
|
||||
</button>
|
||||
|
||||
@@ -30,9 +30,13 @@
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="!collapsed" class="grid-display width-16 mt-4">
|
||||
<div v-for="nag in visibleNags" :key="nag.id" class="grid-display__item">
|
||||
<span class="flex items-center gap-2 font-semibold">
|
||||
<div v-if="!collapsed" class="mt-4 grid grid-cols-[repeat(auto-fit,minmax(15rem,1fr))] gap-2">
|
||||
<div
|
||||
v-for="nag in visibleNags"
|
||||
:key="nag.id"
|
||||
class="flex flex-col gap-3 rounded-2xl border border-solid border-surface-5 bg-surface-2 p-4"
|
||||
>
|
||||
<span class="flex items-center gap-2 font-medium text-contrast">
|
||||
<component
|
||||
:is="nag.icon || getDefaultIcon(nag.status)"
|
||||
v-tooltip="getStatusTooltip(nag.status)"
|
||||
@@ -52,7 +56,7 @@
|
||||
:to="`/${project.project_type}/${project.slug ? project.slug : project.id}/${
|
||||
nag.link.path
|
||||
}`"
|
||||
class="goto-link"
|
||||
class="goto-link mt-auto"
|
||||
>
|
||||
{{ getFormattedMessage(nag.link.title) }}
|
||||
<ChevronRightIcon aria-hidden="true" class="featured-header-chevron" />
|
||||
|
||||
@@ -154,8 +154,8 @@
|
||||
@update-thread="updateThread"
|
||||
>
|
||||
<template #closedActions>
|
||||
<ButtonStyled v-if="isStaff(auth.user)" color="green" class="mt-2">
|
||||
<button class="w-full gap-2 sm:w-auto" @click="reopenReport()">
|
||||
<ButtonStyled v-if="isStaff(auth.user)" color="green">
|
||||
<button class="mt-2 w-full gap-2 sm:w-auto" @click="reopenReport()">
|
||||
<CheckCircleIcon class="size-4" />
|
||||
Reopen Thread
|
||||
</button>
|
||||
|
||||
@@ -1201,7 +1201,6 @@ async function handleSubmitReview(verdict: 'safe' | 'unsafe') {
|
||||
:href="file.download_url"
|
||||
:title="`Download ${file.file_name}`"
|
||||
:download="file.file_name"
|
||||
class="!border-px !border-surface-4"
|
||||
tabindex="0"
|
||||
>
|
||||
<DownloadIcon /> Download
|
||||
@@ -1362,7 +1361,6 @@ async function handleSubmitReview(verdict: 'safe' | 'unsafe') {
|
||||
"
|
||||
>
|
||||
<button
|
||||
class="!border-[1px]"
|
||||
:disabled="updatingDetails.has(flag.detail.id)"
|
||||
@click="updateDetailStatus(flag.detail.id, 'safe')"
|
||||
>
|
||||
@@ -1379,7 +1377,6 @@ async function handleSubmitReview(verdict: 'safe' | 'unsafe') {
|
||||
"
|
||||
>
|
||||
<button
|
||||
class="!border-[1px]"
|
||||
:disabled="updatingDetails.has(flag.detail.id)"
|
||||
@click="updateDetailStatus(flag.detail.id, 'unsafe')"
|
||||
>
|
||||
|
||||
@@ -40,11 +40,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<ButtonStyled v-if="content" type="outlined">
|
||||
<button
|
||||
class="!border-[1px]"
|
||||
:disabled="!hasPermission"
|
||||
@click="handleSwitchCompatibility"
|
||||
>
|
||||
<button :disabled="!hasPermission" @click="handleSwitchCompatibility">
|
||||
<ArrowLeftRightIcon />
|
||||
Switch type
|
||||
</button>
|
||||
|
||||
@@ -21,10 +21,10 @@
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<ButtonStyled color="medal-promo" type="outlined" size="large" class="z-10 my-auto mt-2">
|
||||
<ButtonStyled color="medal-promo" type="outlined" size="large">
|
||||
<nuxt-link
|
||||
to="https://medal.tv/modrinth"
|
||||
class="z-10 flex w-full items-center justify-center gap-1 md:mt-0 md:w-auto"
|
||||
class="z-10 my-auto mt-2 flex w-full items-center justify-center gap-1 md:mt-0 md:w-auto"
|
||||
>{{ formatMessage(messages.learnMoreButton) }} <ExternalIcon
|
||||
/></nuxt-link>
|
||||
</ButtonStyled>
|
||||
|
||||
@@ -23,14 +23,12 @@
|
||||
I confirm that I have properly addressed the moderators' comments.
|
||||
</Checkbox>
|
||||
<div class="input-group push-right">
|
||||
<button
|
||||
class="iconified-button moderation-button"
|
||||
:disabled="!submissionConfirmation"
|
||||
@click="resubmit()"
|
||||
>
|
||||
<ScaleIcon aria-hidden="true" />
|
||||
Resubmit for review
|
||||
</button>
|
||||
<ButtonStyled color="orange">
|
||||
<button :disabled="!submissionConfirmation" @click="resubmit()">
|
||||
<ScaleIcon aria-hidden="true" />
|
||||
Resubmit for review
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
@@ -55,14 +53,12 @@
|
||||
I acknowledge that the moderators do not actively monitor the thread.
|
||||
</Checkbox>
|
||||
<div class="input-group push-right">
|
||||
<button
|
||||
class="btn btn-primary"
|
||||
:disabled="!replyConfirmation"
|
||||
@click="sendReplyFromModal()"
|
||||
>
|
||||
<ReplyIcon aria-hidden="true" />
|
||||
Reply to thread
|
||||
</button>
|
||||
<ButtonStyled color="brand">
|
||||
<button :disabled="!replyConfirmation" @click="sendReplyFromModal()">
|
||||
<ReplyIcon aria-hidden="true" />
|
||||
Reply to thread
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
@@ -85,10 +81,12 @@
|
||||
</div>
|
||||
<template v-if="report && report.closed">
|
||||
<p>This thread is closed and new messages cannot be sent to it.</p>
|
||||
<button v-if="isStaff(auth.user)" class="iconified-button" @click="reopenReport()">
|
||||
<CheckCircleIcon aria-hidden="true" />
|
||||
Reopen thread
|
||||
</button>
|
||||
<ButtonStyled v-if="isStaff(auth.user)">
|
||||
<button @click="reopenReport()">
|
||||
<CheckCircleIcon aria-hidden="true" />
|
||||
Reopen thread
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</template>
|
||||
<template v-else-if="!report || !report.closed">
|
||||
<div class="markdown-editor-spacing">
|
||||
@@ -99,196 +97,175 @@
|
||||
/>
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<button
|
||||
v-if="sortedMessages.length > 0"
|
||||
class="btn btn-primary"
|
||||
:disabled="!replyBody"
|
||||
@click="isApproved(project) && !isStaff(auth.user) ? openReplyModal() : sendReply()"
|
||||
>
|
||||
<ReplyIcon aria-hidden="true" />
|
||||
Reply
|
||||
</button>
|
||||
<button
|
||||
v-else
|
||||
class="btn btn-primary"
|
||||
:disabled="!replyBody"
|
||||
@click="isApproved(project) && !isStaff(auth.user) ? openReplyModal() : sendReply()"
|
||||
>
|
||||
<SendIcon aria-hidden="true" />
|
||||
Send
|
||||
</button>
|
||||
<button
|
||||
v-if="isStaff(auth.user)"
|
||||
class="btn"
|
||||
:disabled="!replyBody"
|
||||
@click="sendReply(null, true)"
|
||||
>
|
||||
<ScaleIcon aria-hidden="true" />
|
||||
Add private note
|
||||
</button>
|
||||
<ButtonStyled color="brand">
|
||||
<button
|
||||
v-if="sortedMessages.length > 0"
|
||||
:disabled="!replyBody"
|
||||
@click="isApproved(project) && !isStaff(auth.user) ? openReplyModal() : sendReply()"
|
||||
>
|
||||
<ReplyIcon aria-hidden="true" />
|
||||
Reply
|
||||
</button>
|
||||
<button
|
||||
v-else
|
||||
:disabled="!replyBody"
|
||||
@click="isApproved(project) && !isStaff(auth.user) ? openReplyModal() : sendReply()"
|
||||
>
|
||||
<SendIcon aria-hidden="true" />
|
||||
Send
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled v-if="isStaff(auth.user)">
|
||||
<button :disabled="!replyBody" @click="sendReply(null, true)">
|
||||
<ScaleIcon aria-hidden="true" />
|
||||
Add private note
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<template v-if="currentMember && !isStaff(auth.user)">
|
||||
<template v-if="isRejected(project)">
|
||||
<button
|
||||
v-if="replyBody"
|
||||
class="iconified-button moderation-button"
|
||||
@click="openResubmitModal(true)"
|
||||
>
|
||||
<ScaleIcon aria-hidden="true" />
|
||||
Resubmit for review with reply
|
||||
</button>
|
||||
<button
|
||||
v-else
|
||||
class="iconified-button moderation-button"
|
||||
@click="openResubmitModal(false)"
|
||||
>
|
||||
<ScaleIcon aria-hidden="true" />
|
||||
Resubmit for review
|
||||
</button>
|
||||
<ButtonStyled color="orange">
|
||||
<button v-if="replyBody" @click="openResubmitModal(true)">
|
||||
<ScaleIcon aria-hidden="true" />
|
||||
Resubmit for review with reply
|
||||
</button>
|
||||
<button v-else @click="openResubmitModal(false)">
|
||||
<ScaleIcon aria-hidden="true" />
|
||||
Resubmit for review
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</template>
|
||||
</template>
|
||||
<div class="spacer"></div>
|
||||
<div class="input-group extra-options">
|
||||
<template v-if="report">
|
||||
<template v-if="isStaff(auth.user)">
|
||||
<button
|
||||
v-if="replyBody"
|
||||
class="iconified-button danger-button"
|
||||
@click="closeReport(true)"
|
||||
>
|
||||
<CheckCircleIcon aria-hidden="true" />
|
||||
Close with reply
|
||||
</button>
|
||||
<button v-else class="iconified-button danger-button" @click="closeReport()">
|
||||
<CheckCircleIcon aria-hidden="true" />
|
||||
Close thread
|
||||
</button>
|
||||
<ButtonStyled color="red">
|
||||
<button v-if="replyBody" @click="closeReport(true)">
|
||||
<CheckCircleIcon aria-hidden="true" />
|
||||
Close with reply
|
||||
</button>
|
||||
<button v-else @click="closeReport()">
|
||||
<CheckCircleIcon aria-hidden="true" />
|
||||
Close thread
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</template>
|
||||
</template>
|
||||
<template v-if="project">
|
||||
<template v-if="isStaff(auth.user)">
|
||||
<button
|
||||
v-if="replyBody"
|
||||
class="btn btn-green"
|
||||
:disabled="isApproved(project)"
|
||||
@click="sendReply(requestedStatus)"
|
||||
>
|
||||
<CheckIcon aria-hidden="true" />
|
||||
Approve with reply
|
||||
</button>
|
||||
<button
|
||||
v-else
|
||||
class="btn btn-green"
|
||||
:disabled="isApproved(project)"
|
||||
@click="setStatus(requestedStatus)"
|
||||
>
|
||||
<CheckIcon aria-hidden="true" />
|
||||
Approve
|
||||
</button>
|
||||
<ButtonStyled v-if="replyBody" color="green">
|
||||
<button :disabled="isApproved(project)" @click="sendReply(requestedStatus)">
|
||||
<CheckIcon aria-hidden="true" />
|
||||
Approve with reply
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled v-else color="green">
|
||||
<button :disabled="isApproved(project)" @click="setStatus(requestedStatus)">
|
||||
<CheckIcon aria-hidden="true" />
|
||||
Approve
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<div class="joined-buttons">
|
||||
<button
|
||||
v-if="replyBody"
|
||||
class="btn btn-danger"
|
||||
:disabled="project.status === 'rejected'"
|
||||
@click="sendReply('rejected')"
|
||||
>
|
||||
<XIcon aria-hidden="true" />
|
||||
Reject with reply
|
||||
</button>
|
||||
<button
|
||||
v-else
|
||||
class="btn btn-danger"
|
||||
:disabled="project.status === 'rejected'"
|
||||
@click="setStatus('rejected')"
|
||||
>
|
||||
<XIcon aria-hidden="true" />
|
||||
Reject
|
||||
</button>
|
||||
<OverflowMenu
|
||||
class="btn btn-danger btn-dropdown-animation icon-only"
|
||||
:options="
|
||||
replyBody
|
||||
? [
|
||||
{
|
||||
id: 'withhold-reply',
|
||||
color: 'danger',
|
||||
action: () => {
|
||||
sendReply('withheld')
|
||||
<ButtonStyled v-if="replyBody" color="red">
|
||||
<button :disabled="project.status === 'rejected'" @click="sendReply('rejected')">
|
||||
<XIcon aria-hidden="true" />
|
||||
Reject with reply
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled v-else color="red">
|
||||
<button :disabled="project.status === 'rejected'" @click="setStatus('rejected')">
|
||||
<XIcon aria-hidden="true" />
|
||||
Reject
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled color="red">
|
||||
<OverflowMenu
|
||||
class="btn-dropdown-animation"
|
||||
:options="
|
||||
replyBody
|
||||
? [
|
||||
{
|
||||
id: 'withhold-reply',
|
||||
color: 'danger',
|
||||
action: () => {
|
||||
sendReply('withheld')
|
||||
},
|
||||
hoverFilled: true,
|
||||
disabled: project.status === 'withheld',
|
||||
},
|
||||
hoverFilled: true,
|
||||
disabled: project.status === 'withheld',
|
||||
},
|
||||
{
|
||||
id: 'set-to-draft-reply',
|
||||
action: () => {
|
||||
sendReply('draft')
|
||||
{
|
||||
id: 'set-to-draft-reply',
|
||||
action: () => {
|
||||
sendReply('draft')
|
||||
},
|
||||
hoverFilled: true,
|
||||
disabled: project.status === 'draft',
|
||||
},
|
||||
hoverFilled: true,
|
||||
disabled: project.status === 'draft',
|
||||
},
|
||||
{
|
||||
id: 'send-to-review-reply',
|
||||
action: () => {
|
||||
sendReply('processing', true)
|
||||
{
|
||||
id: 'send-to-review-reply',
|
||||
action: () => {
|
||||
sendReply('processing', true)
|
||||
},
|
||||
hoverFilled: true,
|
||||
disabled: project.status === 'processing',
|
||||
},
|
||||
hoverFilled: true,
|
||||
disabled: project.status === 'processing',
|
||||
},
|
||||
]
|
||||
: [
|
||||
{
|
||||
id: 'withhold',
|
||||
color: 'danger',
|
||||
action: () => {
|
||||
setStatus('withheld')
|
||||
]
|
||||
: [
|
||||
{
|
||||
id: 'withhold',
|
||||
color: 'danger',
|
||||
action: () => {
|
||||
setStatus('withheld')
|
||||
},
|
||||
hoverFilled: true,
|
||||
disabled: project.status === 'withheld',
|
||||
},
|
||||
hoverFilled: true,
|
||||
disabled: project.status === 'withheld',
|
||||
},
|
||||
{
|
||||
id: 'set-to-draft',
|
||||
action: () => {
|
||||
setStatus('draft')
|
||||
{
|
||||
id: 'set-to-draft',
|
||||
action: () => {
|
||||
setStatus('draft')
|
||||
},
|
||||
hoverFilled: true,
|
||||
disabled: project.status === 'draft',
|
||||
},
|
||||
hoverFilled: true,
|
||||
disabled: project.status === 'draft',
|
||||
},
|
||||
{
|
||||
id: 'send-to-review',
|
||||
action: () => {
|
||||
setStatus('processing')
|
||||
{
|
||||
id: 'send-to-review',
|
||||
action: () => {
|
||||
setStatus('processing')
|
||||
},
|
||||
hoverFilled: true,
|
||||
disabled: project.status === 'processing',
|
||||
},
|
||||
hoverFilled: true,
|
||||
disabled: project.status === 'processing',
|
||||
},
|
||||
]
|
||||
"
|
||||
>
|
||||
<DropdownIcon style="rotate: 180deg" aria-hidden="true" />
|
||||
<template #withhold-reply>
|
||||
<EyeOffIcon aria-hidden="true" />
|
||||
Withhold with reply
|
||||
</template>
|
||||
<template #withhold>
|
||||
<EyeOffIcon aria-hidden="true" />
|
||||
Withhold
|
||||
</template>
|
||||
<template #set-to-draft-reply>
|
||||
<FileTextIcon aria-hidden="true" />
|
||||
Set to draft with reply
|
||||
</template>
|
||||
<template #set-to-draft>
|
||||
<FileTextIcon aria-hidden="true" />
|
||||
Set to draft
|
||||
</template>
|
||||
<template #send-to-review-reply>
|
||||
<ScaleIcon aria-hidden="true" />
|
||||
Send to review with reply
|
||||
</template>
|
||||
<template #send-to-review>
|
||||
<ScaleIcon aria-hidden="true" />
|
||||
Send to review
|
||||
</template>
|
||||
</OverflowMenu>
|
||||
]
|
||||
"
|
||||
>
|
||||
<DropdownIcon aria-hidden="true" />
|
||||
<template #withhold-reply>
|
||||
<EyeOffIcon aria-hidden="true" />
|
||||
Withhold with reply
|
||||
</template>
|
||||
<template #withhold>
|
||||
<EyeOffIcon aria-hidden="true" />
|
||||
Withhold
|
||||
</template>
|
||||
<template #set-to-draft-reply>
|
||||
<FileTextIcon aria-hidden="true" />
|
||||
Set to draft with reply
|
||||
</template>
|
||||
<template #set-to-draft>
|
||||
<FileTextIcon aria-hidden="true" />
|
||||
Set to draft
|
||||
</template>
|
||||
<template #send-to-review-reply>
|
||||
<ScaleIcon aria-hidden="true" />
|
||||
Send to review with reply
|
||||
</template>
|
||||
<template #send-to-review>
|
||||
<ScaleIcon aria-hidden="true" />
|
||||
Send to review
|
||||
</template>
|
||||
</OverflowMenu>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
@@ -311,6 +288,7 @@ import {
|
||||
XIcon,
|
||||
} from '@modrinth/assets'
|
||||
import {
|
||||
ButtonStyled,
|
||||
Checkbox,
|
||||
CopyCode,
|
||||
injectNotificationManager,
|
||||
|
||||
@@ -104,20 +104,22 @@
|
||||
</span>
|
||||
</span>
|
||||
<div v-if="isStaff(auth.user) && message.author_id === auth.user.id" class="message__actions">
|
||||
<OverflowMenu
|
||||
class="btn btn-transparent icon-only"
|
||||
:options="[
|
||||
{
|
||||
id: 'delete',
|
||||
action: () => deleteMessage(),
|
||||
color: 'red',
|
||||
hoverFilled: true,
|
||||
},
|
||||
]"
|
||||
>
|
||||
<MoreHorizontalIcon />
|
||||
<template #delete> <TrashIcon /> Delete </template>
|
||||
</OverflowMenu>
|
||||
<ButtonStyled circular type="transparent">
|
||||
<OverflowMenu
|
||||
class="btn-dropdown-animation"
|
||||
:options="[
|
||||
{
|
||||
id: 'delete',
|
||||
action: () => deleteMessage(),
|
||||
color: 'red',
|
||||
hoverFilled: true,
|
||||
},
|
||||
]"
|
||||
>
|
||||
<MoreHorizontalIcon />
|
||||
<template #delete> <TrashIcon /> Delete </template>
|
||||
</OverflowMenu>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -135,6 +137,7 @@ import {
|
||||
AutoLink,
|
||||
Avatar,
|
||||
Badge,
|
||||
ButtonStyled,
|
||||
OverflowMenu,
|
||||
useFormatDateTime,
|
||||
useRelativeTime,
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<NuxtLayout>
|
||||
<LoadingBar />
|
||||
<NotificationPanel />
|
||||
<div class="main experimental-styles-within">
|
||||
<div class="main">
|
||||
<div v-if="is404" class="error-graphic">
|
||||
<Logo404 />
|
||||
</div>
|
||||
|
||||
@@ -54,7 +54,7 @@
|
||||
:api-url="config.public.apiBaseUrl"
|
||||
/>
|
||||
<header
|
||||
class="experimental-styles-within desktop-only relative z-[5] mx-auto grid max-w-[1280px] grid-cols-[1fr_auto] items-center gap-2 px-6 py-4 lg:grid-cols-[auto_1fr_auto]"
|
||||
class="desktop-only relative z-[5] mx-auto grid max-w-[1280px] grid-cols-[1fr_auto] items-center gap-2 px-6 py-4 lg:grid-cols-[auto_1fr_auto]"
|
||||
>
|
||||
<div>
|
||||
<NuxtLink
|
||||
@@ -1298,7 +1298,8 @@ const { cycle: changeTheme } = useTheme()
|
||||
justify-content: center;
|
||||
padding: 1rem;
|
||||
|
||||
.iconified-button {
|
||||
> button,
|
||||
> a {
|
||||
width: 100%;
|
||||
max-width: 500px;
|
||||
padding: 0.75rem;
|
||||
|
||||
@@ -45,15 +45,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-else class="experimental-styles-within">
|
||||
<NewModal ref="settingsModal">
|
||||
<template #title>
|
||||
<Avatar :src="project.icon_url" :alt="project.title" class="icon" size="32px" />
|
||||
<span class="text-lg font-extrabold text-contrast">
|
||||
{{ formatMessage(messages.settingsTitle) }}
|
||||
</span>
|
||||
</template>
|
||||
</NewModal>
|
||||
<div v-else>
|
||||
<NewModal
|
||||
ref="modalLicense"
|
||||
:header="project.license.name ? project.license.name : formatMessage(messages.licenseTitle)"
|
||||
@@ -464,30 +456,19 @@
|
||||
:member="!!currentMember"
|
||||
>
|
||||
<template #actions>
|
||||
<ButtonStyled
|
||||
v-if="auth.user && currentMember"
|
||||
size="large"
|
||||
color="brand"
|
||||
class="lg:!hidden"
|
||||
circular
|
||||
>
|
||||
<ButtonStyled v-if="auth.user && currentMember" size="large" color="brand" circular>
|
||||
<nuxt-link
|
||||
v-tooltip="'Edit project'"
|
||||
:to="`/${project.project_type}/${project.slug ? project.slug : project.id}/settings`"
|
||||
class="!font-bold"
|
||||
class="!font-bold lg:!hidden"
|
||||
>
|
||||
<SettingsIcon aria-hidden="true" />
|
||||
</nuxt-link>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled
|
||||
v-if="auth.user && currentMember"
|
||||
size="large"
|
||||
color="brand"
|
||||
class="max-lg:!hidden"
|
||||
>
|
||||
<ButtonStyled v-if="auth.user && currentMember" size="large" color="brand">
|
||||
<nuxt-link
|
||||
:to="`/${project.project_type}/${project.slug ? project.slug : project.id}/settings`"
|
||||
class="!font-bold"
|
||||
class="!font-bold max-lg:!hidden"
|
||||
>
|
||||
<SettingsIcon aria-hidden="true" />
|
||||
Edit project
|
||||
@@ -594,7 +575,7 @@
|
||||
</nuxt-link>
|
||||
</ButtonStyled>
|
||||
<template #popper>
|
||||
<div class="experimental-styles-within grid grid-cols-[min-content] gap-1">
|
||||
<div class="grid grid-cols-[min-content] gap-1">
|
||||
<div class="flex min-w-60 items-center justify-between gap-4">
|
||||
<h3
|
||||
class="m-0 flex items-center gap-2 whitespace-nowrap text-base font-bold text-contrast"
|
||||
@@ -724,13 +705,15 @@
|
||||
<div v-else class="menu-text">
|
||||
<p class="popout-text">{{ formatMessage(messages.noCollectionsFound) }}</p>
|
||||
</div>
|
||||
<button
|
||||
class="btn collection-button"
|
||||
@click="(event) => $refs.modal_collection.show(event)"
|
||||
>
|
||||
<PlusIcon aria-hidden="true" />
|
||||
{{ formatMessage(messages.createNewCollection) }}
|
||||
</button>
|
||||
<ButtonStyled>
|
||||
<button
|
||||
class="mx-3 mb-3"
|
||||
@click="(event) => $refs.modal_collection.show(event)"
|
||||
>
|
||||
<PlusIcon aria-hidden="true" />
|
||||
{{ formatMessage(messages.createNewCollection) }}
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</template>
|
||||
</PopoutMenu>
|
||||
<nuxt-link v-else v-tooltip="'Save'" :to="signInRouteObj" aria-label="Save">
|
||||
@@ -890,32 +873,29 @@
|
||||
:supported-versions="serverSupportedVersions"
|
||||
:loaders="serverModpackLoaders"
|
||||
:status-online="projectV3?.minecraft_java_server?.ping?.data != null"
|
||||
class="card flex-card experimental-styles-within"
|
||||
class="card flex-card"
|
||||
/>
|
||||
<ProjectSidebarCompatibility
|
||||
v-if="projectV3Loaded && !isServerProject"
|
||||
:project="project"
|
||||
:tags="tags"
|
||||
:project-v3="projectV3"
|
||||
class="card flex-card experimental-styles-within"
|
||||
class="card flex-card"
|
||||
/>
|
||||
<AdPlaceholder v-if="!auth.user && tags.approvedStatuses.includes(project.status)" />
|
||||
<ProjectSidebarLinks
|
||||
:project="project"
|
||||
:project-v3="projectV3"
|
||||
:link-target="$external()"
|
||||
class="card flex-card experimental-styles-within"
|
||||
/>
|
||||
<ProjectSidebarTags
|
||||
:project="project"
|
||||
class="card flex-card experimental-styles-within"
|
||||
class="card flex-card"
|
||||
/>
|
||||
<ProjectSidebarTags :project="project" class="card flex-card" />
|
||||
<ProjectSidebarCreators
|
||||
:organization="organization"
|
||||
:members="members"
|
||||
:org-link="(slug) => `/organization/${slug}`"
|
||||
:user-link="(username) => `/user/${username}`"
|
||||
class="card flex-card experimental-styles-within"
|
||||
class="card flex-card"
|
||||
/>
|
||||
<!-- TODO: Finish license modal and enable -->
|
||||
<ProjectSidebarDetails
|
||||
@@ -924,9 +904,9 @@
|
||||
:has-versions="versions.length > 0"
|
||||
:link-target="$external()"
|
||||
:show-followers="isServerProject"
|
||||
class="card flex-card experimental-styles-within"
|
||||
class="card flex-card"
|
||||
/>
|
||||
<div class="card flex-card experimental-styles-within">
|
||||
<div class="card flex-card">
|
||||
<h2>{{ formatMessage(detailsMessages.title) }}</h2>
|
||||
|
||||
<div class="details-list">
|
||||
@@ -1177,7 +1157,6 @@ const formatDateTime = useFormatDateTime({
|
||||
|
||||
const debug = useDebugLogger('DownloadModal')
|
||||
|
||||
const settingsModal = ref()
|
||||
const downloadModal = ref()
|
||||
const openInAppModal = ref()
|
||||
const overTheTopDownloadAnimation = ref()
|
||||
@@ -2748,11 +2727,6 @@ provideProjectPageContext({
|
||||
color: var(--color-secondary);
|
||||
}
|
||||
|
||||
.collection-button {
|
||||
margin: var(--gap-sm) var(--gap-md);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.menu-text {
|
||||
padding: 0 var(--gap-md);
|
||||
font-size: var(--font-size-nm);
|
||||
|
||||
@@ -53,14 +53,16 @@
|
||||
{{ formatDate(version.date_published) }}</span
|
||||
>
|
||||
</div>
|
||||
<a
|
||||
:href="version.primaryFile?.url"
|
||||
class="iconified-button download"
|
||||
:title="`Download ${version.name}`"
|
||||
>
|
||||
<DownloadIcon aria-hidden="true" />
|
||||
Download
|
||||
</a>
|
||||
<ButtonStyled color="brand" type="transparent">
|
||||
<a
|
||||
class="ml-auto"
|
||||
:href="version.primaryFile?.url"
|
||||
:title="`Download ${version.name}`"
|
||||
>
|
||||
<DownloadIcon aria-hidden="true" />
|
||||
Download
|
||||
</a>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
<div
|
||||
v-if="version.changelog && !version.duplicate"
|
||||
@@ -87,6 +89,7 @@
|
||||
<script setup>
|
||||
import { DownloadIcon, SpinnerIcon } from '@modrinth/assets'
|
||||
import {
|
||||
ButtonStyled,
|
||||
injectModrinthClient,
|
||||
injectProjectPageContext,
|
||||
Pagination,
|
||||
|
||||
@@ -10,23 +10,24 @@
|
||||
<div class="file-header">
|
||||
<ImageIcon aria-hidden="true" />
|
||||
<strong>{{ editFile ? editFile.name : 'Current image' }}</strong>
|
||||
<FileInput
|
||||
v-if="editIndex === -1"
|
||||
class="iconified-button raised-button"
|
||||
prompt="Replace"
|
||||
:accept="acceptFileTypes"
|
||||
:max-size="5242880"
|
||||
should-always-reset
|
||||
aria-label="Replace image"
|
||||
@change="
|
||||
(x) => {
|
||||
editFile = x[0]
|
||||
showPreviewImage()
|
||||
}
|
||||
"
|
||||
>
|
||||
<TransferIcon aria-hidden="true" />
|
||||
</FileInput>
|
||||
<ButtonStyled v-if="editIndex === -1" type="outlined">
|
||||
<FileInput
|
||||
class="button-like"
|
||||
prompt="Replace"
|
||||
:accept="acceptFileTypes"
|
||||
:max-size="5242880"
|
||||
should-always-reset
|
||||
aria-label="Replace image"
|
||||
@change="
|
||||
(x) => {
|
||||
editFile = x[0]
|
||||
showPreviewImage()
|
||||
}
|
||||
"
|
||||
>
|
||||
<TransferIcon aria-hidden="true" />
|
||||
</FileInput>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
<img
|
||||
:src="
|
||||
@@ -68,53 +69,42 @@
|
||||
placeholder="Enter order index..."
|
||||
/>
|
||||
<label for="gallery-image-featured">
|
||||
<span class="label__title">Featured</span>
|
||||
<span class="label__title">Banner image</span>
|
||||
<span class="label__description">
|
||||
A featured gallery image shows up in search and your project card. Only one gallery
|
||||
image can be featured.
|
||||
You can feature one image on your project to be used as a banner image.
|
||||
</span>
|
||||
</label>
|
||||
<button
|
||||
v-if="!editFeatured"
|
||||
id="gallery-image-featured"
|
||||
class="iconified-button"
|
||||
@click="editFeatured = true"
|
||||
>
|
||||
<StarIcon aria-hidden="true" />
|
||||
Feature image
|
||||
</button>
|
||||
<button
|
||||
v-else
|
||||
id="gallery-image-featured"
|
||||
class="iconified-button"
|
||||
@click="editFeatured = false"
|
||||
>
|
||||
<StarIcon fill="currentColor" aria-hidden="true" />
|
||||
Unfeature image
|
||||
</button>
|
||||
<ButtonStyled v-if="!editFeatured">
|
||||
<button id="gallery-image-featured" class="w-fit" @click="editFeatured = true">
|
||||
<StarIcon aria-hidden="true" />
|
||||
Set as banner
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled v-else>
|
||||
<button id="gallery-image-featured" class="w-fit" @click="editFeatured = false">
|
||||
<StarIcon fill="currentColor" aria-hidden="true" />
|
||||
Unset as banner
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<div class="button-group">
|
||||
<button class="iconified-button" @click="modalEditItem?.hide()">
|
||||
<XIcon aria-hidden="true" />
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
v-if="editIndex === -1"
|
||||
class="iconified-button brand-button"
|
||||
:disabled="shouldPreventActions"
|
||||
@click="createGalleryItem"
|
||||
>
|
||||
<PlusIcon aria-hidden="true" />
|
||||
Add gallery image
|
||||
</button>
|
||||
<button
|
||||
v-else
|
||||
class="iconified-button brand-button"
|
||||
:disabled="shouldPreventActions"
|
||||
@click="editGalleryItem"
|
||||
>
|
||||
<SaveIcon aria-hidden="true" />
|
||||
Save changes
|
||||
</button>
|
||||
<ButtonStyled type="outlined">
|
||||
<button @click="modalEditItem?.hide()">
|
||||
<XIcon aria-hidden="true" />
|
||||
Cancel
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled v-if="editIndex === -1" color="brand">
|
||||
<button :disabled="shouldPreventActions" @click="createGalleryItem">
|
||||
<PlusIcon aria-hidden="true" />
|
||||
Add gallery image
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled v-else color="brand">
|
||||
<button :disabled="shouldPreventActions" @click="editGalleryItem">
|
||||
<SaveIcon aria-hidden="true" />
|
||||
Save changes
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
@@ -155,39 +145,41 @@
|
||||
</p>
|
||||
</div>
|
||||
<div class="controls">
|
||||
<div class="buttons">
|
||||
<button class="close circle-button" @click="expandedGalleryItem = null">
|
||||
<XIcon aria-hidden="true" />
|
||||
</button>
|
||||
<a
|
||||
class="open circle-button"
|
||||
target="_blank"
|
||||
:href="
|
||||
expandedGalleryItem?.raw_url
|
||||
? expandedGalleryItem?.raw_url
|
||||
: 'https://cdn.modrinth.com/placeholder-banner.svg'
|
||||
"
|
||||
>
|
||||
<ExternalIcon aria-hidden="true" />
|
||||
</a>
|
||||
<button class="circle-button" @click="zoomedIn = !zoomedIn">
|
||||
<ExpandIcon v-if="!zoomedIn" aria-hidden="true" />
|
||||
<ContractIcon v-else aria-hidden="true" />
|
||||
</button>
|
||||
<button
|
||||
v-if="filteredGallery.length > 1"
|
||||
class="previous circle-button"
|
||||
@click="previousImage()"
|
||||
>
|
||||
<LeftArrowIcon aria-hidden="true" />
|
||||
</button>
|
||||
<button
|
||||
v-if="filteredGallery.length > 1"
|
||||
class="next circle-button"
|
||||
@click="nextImage()"
|
||||
>
|
||||
<RightArrowIcon aria-hidden="true" />
|
||||
</button>
|
||||
<div class="flex gap-2">
|
||||
<ButtonStyled circular>
|
||||
<button class="close" @click="expandedGalleryItem = null">
|
||||
<XIcon aria-hidden="true" />
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled circular>
|
||||
<a
|
||||
class="open"
|
||||
target="_blank"
|
||||
:href="
|
||||
expandedGalleryItem?.raw_url
|
||||
? expandedGalleryItem?.raw_url
|
||||
: 'https://cdn.modrinth.com/placeholder-banner.svg'
|
||||
"
|
||||
>
|
||||
<ExternalIcon aria-hidden="true" />
|
||||
</a>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled circular>
|
||||
<button @click="zoomedIn = !zoomedIn">
|
||||
<ExpandIcon v-if="!zoomedIn" aria-hidden="true" />
|
||||
<ContractIcon v-else aria-hidden="true" />
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled v-if="filteredGallery.length > 1" circular>
|
||||
<button class="previous" @click="previousImage()">
|
||||
<LeftArrowIcon aria-hidden="true" />
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled v-if="filteredGallery.length > 1" circular>
|
||||
<button class="next" @click="nextImage()">
|
||||
<RightArrowIcon aria-hidden="true" />
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -195,17 +187,19 @@
|
||||
</div>
|
||||
|
||||
<div v-if="currentMember && filteredGallery.length" class="card header-buttons">
|
||||
<FileInput
|
||||
:max-size="5242880"
|
||||
:accept="acceptFileTypes"
|
||||
prompt="Upload an image"
|
||||
aria-label="Upload an image"
|
||||
class="iconified-button brand-button"
|
||||
:disabled="!isPermission(currentMember?.permissions, 1 << 2)"
|
||||
@change="handleFiles"
|
||||
>
|
||||
<UploadIcon aria-hidden="true" />
|
||||
</FileInput>
|
||||
<ButtonStyled color="brand">
|
||||
<FileInput
|
||||
:max-size="5242880"
|
||||
:accept="acceptFileTypes"
|
||||
prompt="Upload an image"
|
||||
aria-label="Upload an image"
|
||||
class="button-like"
|
||||
:disabled="!isPermission(currentMember?.permissions, 1 << 2)"
|
||||
@change="handleFiles"
|
||||
>
|
||||
<UploadIcon aria-hidden="true" />
|
||||
</FileInput>
|
||||
</ButtonStyled>
|
||||
<span class="indicator">
|
||||
<InfoIcon aria-hidden="true" /> Click to choose an image or drag one onto this page
|
||||
</span>
|
||||
@@ -239,35 +233,37 @@
|
||||
{{ formatDate(item.created) }}
|
||||
</div>
|
||||
<div v-if="currentMember" class="gallery-buttons input-group">
|
||||
<button
|
||||
class="iconified-button"
|
||||
@click="
|
||||
() => {
|
||||
resetEdit()
|
||||
editIndex = index
|
||||
editTitle = item.title ?? ''
|
||||
editDescription = item.description ?? ''
|
||||
editFeatured = item.featured
|
||||
editOrder = item.ordering
|
||||
modalEditItem?.show()
|
||||
}
|
||||
"
|
||||
>
|
||||
<EditIcon aria-hidden="true" />
|
||||
Edit
|
||||
</button>
|
||||
<button
|
||||
class="iconified-button"
|
||||
@click="
|
||||
() => {
|
||||
deleteIndex = index
|
||||
modalConfirm?.show()
|
||||
}
|
||||
"
|
||||
>
|
||||
<TrashIcon aria-hidden="true" />
|
||||
Remove
|
||||
</button>
|
||||
<ButtonStyled>
|
||||
<button
|
||||
@click="
|
||||
() => {
|
||||
resetEdit()
|
||||
editIndex = index
|
||||
editTitle = item.title ?? ''
|
||||
editDescription = item.description ?? ''
|
||||
editFeatured = item.featured
|
||||
editOrder = item.ordering
|
||||
modalEditItem?.show()
|
||||
}
|
||||
"
|
||||
>
|
||||
<EditIcon aria-hidden="true" />
|
||||
Edit
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled>
|
||||
<button
|
||||
@click="
|
||||
() => {
|
||||
deleteIndex = index
|
||||
modalConfirm?.show()
|
||||
}
|
||||
"
|
||||
>
|
||||
<TrashIcon aria-hidden="true" />
|
||||
Remove
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -304,6 +300,7 @@ import {
|
||||
XIcon,
|
||||
} from '@modrinth/assets'
|
||||
import {
|
||||
ButtonStyled,
|
||||
ConfirmModal,
|
||||
DropArea,
|
||||
FileInput,
|
||||
@@ -541,43 +538,6 @@ async function deleteGalleryImage() {
|
||||
width: calc(100vw - 2 * var(--spacing-card-lg));
|
||||
height: calc(100vh - 2 * var(--spacing-card-lg));
|
||||
|
||||
.circle-button {
|
||||
padding: 0.5rem;
|
||||
line-height: 1;
|
||||
display: flex;
|
||||
max-width: 2rem;
|
||||
color: var(--color-button-text);
|
||||
background-color: var(--color-button-bg);
|
||||
border-radius: var(--size-rounded-max);
|
||||
margin: 0;
|
||||
box-shadow: inset 0px -1px 1px rgb(17 24 39 / 10%);
|
||||
|
||||
&:not(:last-child) {
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: var(--color-button-bg-hover) !important;
|
||||
|
||||
svg {
|
||||
color: var(--color-button-text-hover) !important;
|
||||
}
|
||||
}
|
||||
|
||||
&:active {
|
||||
background-color: var(--color-button-bg-active) !important;
|
||||
|
||||
svg {
|
||||
color: var(--color-button-text-active) !important;
|
||||
}
|
||||
}
|
||||
|
||||
svg {
|
||||
height: 1rem;
|
||||
width: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
.image {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
@@ -653,14 +613,6 @@ async function deleteGalleryImage() {
|
||||
}
|
||||
}
|
||||
|
||||
.buttons {
|
||||
display: flex;
|
||||
|
||||
button {
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
.items {
|
||||
display: grid;
|
||||
grid-template-rows: 1fr;
|
||||
@@ -762,10 +714,6 @@ async function deleteGalleryImage() {
|
||||
strong {
|
||||
word-wrap: anywhere;
|
||||
}
|
||||
|
||||
.iconified-button {
|
||||
margin-left: auto;
|
||||
}
|
||||
}
|
||||
|
||||
img {
|
||||
@@ -778,8 +726,4 @@ async function deleteGalleryImage() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.brand-button {
|
||||
color: var(--color-accent-contrast);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -152,7 +152,7 @@ watch(route, () => {
|
||||
@toggle-collapsed="() => (collapsedChecklist = !collapsedChecklist)"
|
||||
@set-processing="setProcessing"
|
||||
/>
|
||||
<div class="experimental-styles-within grid gap-4 lg:grid-cols-[1fr_3fr]">
|
||||
<div class="grid gap-4 lg:grid-cols-[1fr_3fr]">
|
||||
<div>
|
||||
<NavStack :items="navItems" />
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<LoadingIndicator v-if="!projectV3" class="py-6" />
|
||||
<div v-else-if="showEnvironmentMigration" class="card experimental-styles-within">
|
||||
<div v-else-if="showEnvironmentMigration" class="card">
|
||||
<h2 class="m-0 mb-2 block text-lg font-extrabold text-contrast">Project environment</h2>
|
||||
<EnvironmentMigration />
|
||||
</div>
|
||||
|
||||
@@ -10,23 +10,24 @@
|
||||
<div class="file-header">
|
||||
<ImageIcon aria-hidden="true" />
|
||||
<strong>{{ editFile ? editFile.name : 'Current image' }}</strong>
|
||||
<FileInput
|
||||
v-if="editIndex === -1"
|
||||
class="iconified-button raised-button"
|
||||
prompt="Replace"
|
||||
:accept="acceptFileTypes"
|
||||
:max-size="5242880"
|
||||
should-always-reset
|
||||
aria-label="Replace image"
|
||||
@change="
|
||||
(x) => {
|
||||
editFile = x[0]
|
||||
showPreviewImage()
|
||||
}
|
||||
"
|
||||
>
|
||||
<TransferIcon aria-hidden="true" />
|
||||
</FileInput>
|
||||
<ButtonStyled v-if="editIndex === -1" type="outlined">
|
||||
<FileInput
|
||||
class="button-like"
|
||||
prompt="Replace"
|
||||
:accept="acceptFileTypes"
|
||||
:max-size="5242880"
|
||||
should-always-reset
|
||||
aria-label="Replace image"
|
||||
@change="
|
||||
(x) => {
|
||||
editFile = x[0]
|
||||
showPreviewImage()
|
||||
}
|
||||
"
|
||||
>
|
||||
<TransferIcon aria-hidden="true" />
|
||||
</FileInput>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
<img
|
||||
:src="
|
||||
@@ -68,53 +69,42 @@
|
||||
placeholder="Enter order index..."
|
||||
/>
|
||||
<label for="gallery-image-featured">
|
||||
<span class="label__title">Featured</span>
|
||||
<span class="label__title">Banner image</span>
|
||||
<span class="label__description">
|
||||
A featured gallery image shows up in search and your project card. Only one gallery
|
||||
image can be featured.
|
||||
You can feature one image on your project to be used as a banner image.
|
||||
</span>
|
||||
</label>
|
||||
<button
|
||||
v-if="!editFeatured"
|
||||
id="gallery-image-featured"
|
||||
class="iconified-button"
|
||||
@click="editFeatured = true"
|
||||
>
|
||||
<StarIcon aria-hidden="true" />
|
||||
Feature image
|
||||
</button>
|
||||
<button
|
||||
v-else
|
||||
id="gallery-image-featured"
|
||||
class="iconified-button"
|
||||
@click="editFeatured = false"
|
||||
>
|
||||
<StarIcon fill="currentColor" aria-hidden="true" />
|
||||
Unfeature image
|
||||
</button>
|
||||
<ButtonStyled v-if="!editFeatured">
|
||||
<button id="gallery-image-featured" class="w-fit" @click="editFeatured = true">
|
||||
<StarIcon aria-hidden="true" />
|
||||
Set as banner
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled v-else>
|
||||
<button id="gallery-image-featured" class="w-fit" @click="editFeatured = false">
|
||||
<StarIcon fill="currentColor" aria-hidden="true" />
|
||||
Unset as banner
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<div class="button-group">
|
||||
<button class="iconified-button" @click="modal_edit_item.hide()">
|
||||
<XIcon aria-hidden="true" />
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
v-if="editIndex === -1"
|
||||
class="iconified-button brand-button"
|
||||
:disabled="shouldPreventActions"
|
||||
@click="createGalleryItem"
|
||||
>
|
||||
<PlusIcon aria-hidden="true" />
|
||||
Add gallery image
|
||||
</button>
|
||||
<button
|
||||
v-else
|
||||
class="iconified-button brand-button"
|
||||
:disabled="shouldPreventActions"
|
||||
@click="editGalleryItem"
|
||||
>
|
||||
<SaveIcon aria-hidden="true" />
|
||||
Save changes
|
||||
</button>
|
||||
<ButtonStyled type="outlined">
|
||||
<button @click="modal_edit_item.hide()">
|
||||
<XIcon aria-hidden="true" />
|
||||
Cancel
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled v-if="editIndex === -1" color="brand">
|
||||
<button :disabled="shouldPreventActions" @click="createGalleryItem">
|
||||
<PlusIcon aria-hidden="true" />
|
||||
Add gallery image
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled v-else color="brand">
|
||||
<button :disabled="shouldPreventActions" @click="editGalleryItem">
|
||||
<SaveIcon aria-hidden="true" />
|
||||
Save changes
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
@@ -155,56 +145,60 @@
|
||||
</p>
|
||||
</div>
|
||||
<div class="controls">
|
||||
<div class="buttons">
|
||||
<button class="close circle-button" @click="expandedGalleryItem = null">
|
||||
<XIcon aria-hidden="true" />
|
||||
</button>
|
||||
<a
|
||||
class="open circle-button"
|
||||
target="_blank"
|
||||
:href="
|
||||
expandedGalleryItem.raw_url
|
||||
? expandedGalleryItem.raw_url
|
||||
: 'https://cdn.modrinth.com/placeholder-banner.svg'
|
||||
"
|
||||
>
|
||||
<ExternalIcon aria-hidden="true" />
|
||||
</a>
|
||||
<button class="circle-button" @click="zoomedIn = !zoomedIn">
|
||||
<ExpandIcon v-if="!zoomedIn" aria-hidden="true" />
|
||||
<ContractIcon v-else aria-hidden="true" />
|
||||
</button>
|
||||
<button
|
||||
v-if="filteredGallery.length > 1"
|
||||
class="previous circle-button"
|
||||
@click="previousImage()"
|
||||
>
|
||||
<LeftArrowIcon aria-hidden="true" />
|
||||
</button>
|
||||
<button
|
||||
v-if="filteredGallery.length > 1"
|
||||
class="next circle-button"
|
||||
@click="nextImage()"
|
||||
>
|
||||
<RightArrowIcon aria-hidden="true" />
|
||||
</button>
|
||||
<div class="flex gap-2">
|
||||
<ButtonStyled circular>
|
||||
<button class="close" @click="expandedGalleryItem = null">
|
||||
<XIcon aria-hidden="true" />
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled circular>
|
||||
<a
|
||||
class="open"
|
||||
target="_blank"
|
||||
:href="
|
||||
expandedGalleryItem.raw_url
|
||||
? expandedGalleryItem.raw_url
|
||||
: 'https://cdn.modrinth.com/placeholder-banner.svg'
|
||||
"
|
||||
>
|
||||
<ExternalIcon aria-hidden="true" />
|
||||
</a>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled circular>
|
||||
<button @click="zoomedIn = !zoomedIn">
|
||||
<ExpandIcon v-if="!zoomedIn" aria-hidden="true" />
|
||||
<ContractIcon v-else aria-hidden="true" />
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled v-if="filteredGallery.length > 1" circular>
|
||||
<button class="previous" @click="previousImage()">
|
||||
<LeftArrowIcon aria-hidden="true" />
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled v-if="filteredGallery.length > 1" circular>
|
||||
<button class="next" @click="nextImage()">
|
||||
<RightArrowIcon aria-hidden="true" />
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="currentMember" class="card header-buttons">
|
||||
<FileInput
|
||||
:max-size="5242880"
|
||||
:accept="acceptFileTypes"
|
||||
prompt="Upload an image"
|
||||
aria-label="Upload an image"
|
||||
class="iconified-button brand-button"
|
||||
:disabled="!isPermission(currentMember?.permissions, 1 << 2)"
|
||||
@change="handleFiles"
|
||||
>
|
||||
<UploadIcon aria-hidden="true" />
|
||||
</FileInput>
|
||||
<ButtonStyled color="brand">
|
||||
<FileInput
|
||||
:max-size="5242880"
|
||||
:accept="acceptFileTypes"
|
||||
prompt="Upload an image"
|
||||
aria-label="Upload an image"
|
||||
class="button-like"
|
||||
:disabled="!isPermission(currentMember?.permissions, 1 << 2)"
|
||||
@change="handleFiles"
|
||||
>
|
||||
<UploadIcon aria-hidden="true" />
|
||||
</FileInput>
|
||||
</ButtonStyled>
|
||||
<span class="indicator">
|
||||
<InfoIcon aria-hidden="true" /> Click to choose an image or drag one onto this page
|
||||
</span>
|
||||
@@ -238,35 +232,37 @@
|
||||
{{ formatDate(item.created) }}
|
||||
</div>
|
||||
<div v-if="currentMember" class="gallery-buttons input-group">
|
||||
<button
|
||||
class="iconified-button"
|
||||
@click="
|
||||
() => {
|
||||
resetEdit()
|
||||
editIndex = index
|
||||
editTitle = item.title
|
||||
editDescription = item.description
|
||||
editFeatured = item.featured
|
||||
editOrder = item.ordering
|
||||
modal_edit_item.show()
|
||||
}
|
||||
"
|
||||
>
|
||||
<EditIcon aria-hidden="true" />
|
||||
Edit
|
||||
</button>
|
||||
<button
|
||||
class="iconified-button"
|
||||
@click="
|
||||
() => {
|
||||
deleteIndex = index
|
||||
modal_confirm.show()
|
||||
}
|
||||
"
|
||||
>
|
||||
<TrashIcon aria-hidden="true" />
|
||||
Remove
|
||||
</button>
|
||||
<ButtonStyled>
|
||||
<button
|
||||
@click="
|
||||
() => {
|
||||
resetEdit()
|
||||
editIndex = index
|
||||
editTitle = item.title
|
||||
editDescription = item.description
|
||||
editFeatured = item.featured
|
||||
editOrder = item.ordering
|
||||
modal_edit_item.show()
|
||||
}
|
||||
"
|
||||
>
|
||||
<EditIcon aria-hidden="true" />
|
||||
Edit
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled>
|
||||
<button
|
||||
@click="
|
||||
() => {
|
||||
deleteIndex = index
|
||||
modal_confirm.show()
|
||||
}
|
||||
"
|
||||
>
|
||||
<TrashIcon aria-hidden="true" />
|
||||
Remove
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -294,6 +290,7 @@ import {
|
||||
XIcon,
|
||||
} from '@modrinth/assets'
|
||||
import {
|
||||
ButtonStyled,
|
||||
ConfirmModal,
|
||||
DropArea,
|
||||
FileInput,
|
||||
@@ -501,43 +498,6 @@ onUnmounted(() => {
|
||||
width: calc(100vw - 2 * var(--spacing-card-lg));
|
||||
height: calc(100vh - 2 * var(--spacing-card-lg));
|
||||
|
||||
.circle-button {
|
||||
padding: 0.5rem;
|
||||
line-height: 1;
|
||||
display: flex;
|
||||
max-width: 2rem;
|
||||
color: var(--color-button-text);
|
||||
background-color: var(--color-button-bg);
|
||||
border-radius: var(--size-rounded-max);
|
||||
margin: 0;
|
||||
box-shadow: inset 0px -1px 1px rgb(17 24 39 / 10%);
|
||||
|
||||
&:not(:last-child) {
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: var(--color-button-bg-hover) !important;
|
||||
|
||||
svg {
|
||||
color: var(--color-button-text-hover) !important;
|
||||
}
|
||||
}
|
||||
|
||||
&:active {
|
||||
background-color: var(--color-button-bg-active) !important;
|
||||
|
||||
svg {
|
||||
color: var(--color-button-text-active) !important;
|
||||
}
|
||||
}
|
||||
|
||||
svg {
|
||||
height: 1rem;
|
||||
width: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
.image {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
@@ -613,14 +573,6 @@ onUnmounted(() => {
|
||||
}
|
||||
}
|
||||
|
||||
.buttons {
|
||||
display: flex;
|
||||
|
||||
button {
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
.items {
|
||||
display: grid;
|
||||
grid-template-rows: 1fr;
|
||||
@@ -723,7 +675,7 @@ onUnmounted(() => {
|
||||
word-wrap: anywhere;
|
||||
}
|
||||
|
||||
.iconified-button {
|
||||
label.button-like {
|
||||
margin-left: auto;
|
||||
}
|
||||
}
|
||||
@@ -738,8 +690,4 @@ onUnmounted(() => {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.brand-button {
|
||||
color: var(--color-accent-contrast);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -81,29 +81,28 @@
|
||||
size="md"
|
||||
class="project__icon"
|
||||
/>
|
||||
<div class="input-stack">
|
||||
<FileInput
|
||||
id="project-icon"
|
||||
:max-size="262144000"
|
||||
:show-icon="true"
|
||||
accept="image/png,image/jpeg,image/gif,image/webp"
|
||||
class="choose-image iconified-button"
|
||||
prompt="Upload icon"
|
||||
aria-label="Upload icon"
|
||||
:disabled="!hasPermission"
|
||||
@change="showPreviewImage"
|
||||
>
|
||||
<UploadIcon aria-hidden="true" />
|
||||
</FileInput>
|
||||
<button
|
||||
v-if="!deletedIcon && (previewImage || project.icon_url)"
|
||||
class="iconified-button"
|
||||
:disabled="!hasPermission"
|
||||
@click="markIconForDeletion"
|
||||
>
|
||||
<TrashIcon aria-hidden="true" />
|
||||
Remove icon
|
||||
</button>
|
||||
<div class="flex flex-col gap-2">
|
||||
<ButtonStyled>
|
||||
<FileInput
|
||||
id="project-icon"
|
||||
:max-size="262144000"
|
||||
:show-icon="true"
|
||||
accept="image/png,image/jpeg,image/gif,image/webp"
|
||||
class="button-like choose-image"
|
||||
prompt="Upload icon"
|
||||
aria-label="Upload icon"
|
||||
:disabled="!hasPermission"
|
||||
@change="showPreviewImage"
|
||||
>
|
||||
<UploadIcon aria-hidden="true" />
|
||||
</FileInput>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled v-if="!deletedIcon && (previewImage || project.icon_url)">
|
||||
<button :disabled="!hasPermission" @click="markIconForDeletion">
|
||||
<TrashIcon aria-hidden="true" />
|
||||
Remove icon
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -157,26 +156,25 @@
|
||||
</label>
|
||||
</div>
|
||||
<div class="mt-2 flex items-center gap-2">
|
||||
<FileInput
|
||||
:max-size="524288"
|
||||
:show-icon="true"
|
||||
accept="image/png,image/jpeg,image/gif,image/webp"
|
||||
class="iconified-button"
|
||||
prompt="Upload banner"
|
||||
:disabled="!hasPermission"
|
||||
@change="showBannerPreview"
|
||||
>
|
||||
<UploadIcon aria-hidden="true" />
|
||||
</FileInput>
|
||||
<button
|
||||
v-if="!deletedBanner && (bannerPreview || bannerGalleryImage?.url)"
|
||||
class="iconified-button"
|
||||
:disabled="!hasPermission"
|
||||
@click="markBannerForDeletion"
|
||||
>
|
||||
<TrashIcon aria-hidden="true" />
|
||||
Remove banner
|
||||
</button>
|
||||
<ButtonStyled>
|
||||
<FileInput
|
||||
:max-size="524288"
|
||||
:show-icon="true"
|
||||
accept="image/png,image/jpeg,image/gif,image/webp"
|
||||
class="button-like"
|
||||
prompt="Upload banner"
|
||||
:disabled="!hasPermission"
|
||||
@change="showBannerPreview"
|
||||
>
|
||||
<UploadIcon aria-hidden="true" />
|
||||
</FileInput>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled v-if="!deletedBanner && (bannerPreview || bannerGalleryImage?.url)">
|
||||
<button :disabled="!hasPermission" @click="markBannerForDeletion">
|
||||
<TrashIcon aria-hidden="true" />
|
||||
Remove banner
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
<div class="mt-2 text-secondary">Gif, 468×60px recommended.</div>
|
||||
</div>
|
||||
@@ -285,15 +283,12 @@
|
||||
Removes your project from Modrinth's servers and search. Clicking on this will delete your
|
||||
project, so be extra careful!
|
||||
</p>
|
||||
<button
|
||||
type="button"
|
||||
class="iconified-button danger-button"
|
||||
:disabled="!hasDeletePermission"
|
||||
@click="$refs.modal_confirm.show()"
|
||||
>
|
||||
<TrashIcon aria-hidden="true" />
|
||||
Delete project
|
||||
</button>
|
||||
<ButtonStyled color="red">
|
||||
<button :disabled="!hasDeletePermission" @click="$refs.modal_confirm.show()">
|
||||
<TrashIcon aria-hidden="true" />
|
||||
Delete project
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</section>
|
||||
<UnsavedChangesPopup
|
||||
:original="original"
|
||||
@@ -319,6 +314,7 @@ import {
|
||||
import { MIN_SUMMARY_CHARS } from '@modrinth/moderation'
|
||||
import {
|
||||
Avatar,
|
||||
ButtonStyled,
|
||||
Combobox,
|
||||
ConfirmLeaveModal,
|
||||
ConfirmModal,
|
||||
|
||||
@@ -104,7 +104,7 @@
|
||||
>
|
||||
</label>
|
||||
|
||||
<div class="input-stack w-1/2">
|
||||
<div class="flex w-1/2 flex-col gap-2">
|
||||
<StyledInput
|
||||
v-if="!current.nonSpdxLicense"
|
||||
id="license-spdx"
|
||||
|
||||
@@ -105,15 +105,12 @@
|
||||
/>
|
||||
</div>
|
||||
<div class="button-group">
|
||||
<button
|
||||
type="button"
|
||||
class="iconified-button brand-button"
|
||||
:disabled="!hasServerChanges"
|
||||
@click="saveServerChanges()"
|
||||
>
|
||||
<SaveIcon />
|
||||
Save changes
|
||||
</button>
|
||||
<ButtonStyled color="brand">
|
||||
<button :disabled="!hasServerChanges" @click="saveServerChanges()">
|
||||
<SaveIcon />
|
||||
Save changes
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -275,15 +272,12 @@
|
||||
/>
|
||||
</div>
|
||||
<div class="button-group">
|
||||
<button
|
||||
type="button"
|
||||
class="iconified-button brand-button"
|
||||
:disabled="!hasChanges"
|
||||
@click="saveChanges()"
|
||||
>
|
||||
<SaveIcon />
|
||||
Save changes
|
||||
</button>
|
||||
<ButtonStyled color="brand">
|
||||
<button :disabled="!hasChanges" @click="saveChanges()">
|
||||
<SaveIcon />
|
||||
Save changes
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
@@ -293,6 +287,7 @@
|
||||
import { SaveIcon, TriangleAlertIcon } from '@modrinth/assets'
|
||||
import { commonLinkDomains, isCommonUrl, isDiscordUrl, isLinkShortener } from '@modrinth/moderation'
|
||||
import {
|
||||
ButtonStyled,
|
||||
DropdownSelect,
|
||||
injectModrinthClient,
|
||||
injectNotificationManager,
|
||||
|
||||
@@ -41,31 +41,33 @@
|
||||
@keypress.enter="inviteTeamMember()"
|
||||
/>
|
||||
<label for="username" class="hidden">Username</label>
|
||||
<button
|
||||
class="iconified-button brand-button"
|
||||
:disabled="(currentMember?.permissions & MANAGE_INVITES) !== MANAGE_INVITES"
|
||||
@click="inviteTeamMember()"
|
||||
>
|
||||
<UserPlusIcon />
|
||||
Invite
|
||||
</button>
|
||||
<ButtonStyled color="brand">
|
||||
<button
|
||||
:disabled="(currentMember?.permissions & MANAGE_INVITES) !== MANAGE_INVITES"
|
||||
@click="inviteTeamMember()"
|
||||
>
|
||||
<UserPlusIcon />
|
||||
Invite
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
<div class="adjacent-input">
|
||||
<span class="label">
|
||||
<span class="label__title">Leave project</span>
|
||||
<span class="label__description"> Remove yourself as a member of this project. </span>
|
||||
</span>
|
||||
<button
|
||||
class="iconified-button danger-button"
|
||||
:disabled="currentMember?.is_owner"
|
||||
:title="
|
||||
currentMember?.is_owner ? 'You cannot leave the project if you are the owner!' : ''
|
||||
"
|
||||
@click="leaveProject()"
|
||||
>
|
||||
<UserXIcon />
|
||||
Leave project
|
||||
</button>
|
||||
<ButtonStyled color="red">
|
||||
<button
|
||||
:disabled="currentMember?.is_owner"
|
||||
:title="
|
||||
currentMember?.is_owner ? 'You cannot leave the project if you are the owner!' : ''
|
||||
"
|
||||
@click="leaveProject()"
|
||||
>
|
||||
<UserXIcon />
|
||||
Leave project
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
</Card>
|
||||
<div
|
||||
@@ -88,16 +90,18 @@
|
||||
<div class="side-buttons">
|
||||
<Badge v-if="member.accepted" type="accepted" />
|
||||
<Badge v-else type="pending" />
|
||||
<button
|
||||
class="square-button dropdown-icon"
|
||||
@click="
|
||||
openTeamMembers.indexOf(member.user.id) === -1
|
||||
? openTeamMembers.push(member.user.id)
|
||||
: (openTeamMembers = openTeamMembers.filter((it) => it !== member.user.id))
|
||||
"
|
||||
>
|
||||
<DropdownIcon />
|
||||
</button>
|
||||
<ButtonStyled circular>
|
||||
<button
|
||||
class="dropdown-icon"
|
||||
@click="
|
||||
openTeamMembers.indexOf(member.user.id) === -1
|
||||
? openTeamMembers.push(member.user.id)
|
||||
: (openTeamMembers = openTeamMembers.filter((it) => it !== member.user.id))
|
||||
"
|
||||
>
|
||||
<DropdownIcon />
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
</div>
|
||||
<div class="content">
|
||||
@@ -225,31 +229,30 @@
|
||||
</div>
|
||||
</template>
|
||||
<div class="input-group">
|
||||
<button
|
||||
class="iconified-button brand-button"
|
||||
:disabled="(currentMember?.permissions & EDIT_MEMBER) !== EDIT_MEMBER"
|
||||
@click="updateTeamMember(index)"
|
||||
>
|
||||
<SaveIcon />
|
||||
Save changes
|
||||
</button>
|
||||
<button
|
||||
v-if="!member.is_owner"
|
||||
class="iconified-button danger-button"
|
||||
:disabled="(currentMember?.permissions & EDIT_MEMBER) !== EDIT_MEMBER"
|
||||
@click="removeTeamMember(index)"
|
||||
>
|
||||
<UserXIcon />
|
||||
Remove member
|
||||
</button>
|
||||
<button
|
||||
v-if="!member.is_owner && currentMember?.is_owner && member.accepted"
|
||||
class="iconified-button"
|
||||
@click="openTransferModal(index, $event)"
|
||||
>
|
||||
<TransferIcon />
|
||||
Transfer ownership
|
||||
</button>
|
||||
<ButtonStyled color="brand">
|
||||
<button
|
||||
:disabled="(currentMember?.permissions & EDIT_MEMBER) !== EDIT_MEMBER"
|
||||
@click="updateTeamMember(index)"
|
||||
>
|
||||
<SaveIcon />
|
||||
Save changes
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled v-if="!member.is_owner" color="red">
|
||||
<button
|
||||
:disabled="(currentMember?.permissions & EDIT_MEMBER) !== EDIT_MEMBER"
|
||||
@click="removeTeamMember(index)"
|
||||
>
|
||||
<UserXIcon />
|
||||
Remove member
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled v-if="!member.is_owner && currentMember?.is_owner && member.accepted">
|
||||
<button @click="openTransferModal(index, $event)">
|
||||
<TransferIcon />
|
||||
Transfer ownership
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -305,19 +308,19 @@
|
||||
force-direction="up"
|
||||
:disabled="!currentMember?.is_owner || organizationOptions.length === 0"
|
||||
/>
|
||||
<button
|
||||
class="btn btn-primary"
|
||||
:disabled="!selectedOrganization"
|
||||
@click="openTransferToOrgModal($event)"
|
||||
>
|
||||
<CheckIcon />
|
||||
<span class="w-max"> Transfer management </span>
|
||||
</button>
|
||||
<ButtonStyled color="brand">
|
||||
<button :disabled="!selectedOrganization" @click="openTransferToOrgModal($event)">
|
||||
<CheckIcon />
|
||||
<span class="w-max"> Transfer management </span>
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
<button v-if="organization" class="btn" @click="$refs.modal_remove.show()">
|
||||
<OrganizationIcon />
|
||||
Remove from organization
|
||||
</button>
|
||||
<ButtonStyled v-if="organization">
|
||||
<button @click="$refs.modal_remove.show()">
|
||||
<OrganizationIcon />
|
||||
Remove from organization
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</section>
|
||||
<div
|
||||
v-for="(member, index) in allOrgMembers"
|
||||
@@ -339,16 +342,18 @@
|
||||
<div class="side-buttons">
|
||||
<Badge v-if="member.accepted" type="accepted" />
|
||||
<Badge v-else type="pending" />
|
||||
<button
|
||||
class="square-button dropdown-icon"
|
||||
@click="
|
||||
openTeamMembers.indexOf(member.user.id) === -1
|
||||
? openTeamMembers.push(member.user.id)
|
||||
: (openTeamMembers = openTeamMembers.filter((it) => it !== member.user.id))
|
||||
"
|
||||
>
|
||||
<DropdownIcon />
|
||||
</button>
|
||||
<ButtonStyled circular>
|
||||
<button
|
||||
class="dropdown-icon"
|
||||
@click="
|
||||
openTeamMembers.indexOf(member.user.id) === -1
|
||||
? openTeamMembers.push(member.user.id)
|
||||
: (openTeamMembers = openTeamMembers.filter((it) => it !== member.user.id))
|
||||
"
|
||||
>
|
||||
<DropdownIcon />
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
</div>
|
||||
<div class="content">
|
||||
@@ -522,17 +527,18 @@
|
||||
|
||||
we don't allow clicking the button in that last case.
|
||||
-->
|
||||
<button
|
||||
class="iconified-button brand-button"
|
||||
:disabled="
|
||||
(currentMember?.permissions & EDIT_MEMBER) !== EDIT_MEMBER ||
|
||||
(!allOrgMembers[index].oldOverride && !allOrgMembers[index].override)
|
||||
"
|
||||
@click="updateOrgMember(index)"
|
||||
>
|
||||
<SaveIcon />
|
||||
Save changes
|
||||
</button>
|
||||
<ButtonStyled color="brand">
|
||||
<button
|
||||
:disabled="
|
||||
(currentMember?.permissions & EDIT_MEMBER) !== EDIT_MEMBER ||
|
||||
(!allOrgMembers[index].oldOverride && !allOrgMembers[index].override)
|
||||
"
|
||||
@click="updateOrgMember(index)"
|
||||
>
|
||||
<SaveIcon />
|
||||
Save changes
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -554,6 +560,7 @@ import {
|
||||
import {
|
||||
Avatar,
|
||||
Badge,
|
||||
ButtonStyled,
|
||||
Card,
|
||||
Checkbox,
|
||||
Combobox,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<section class="experimental-styles-within overflow-visible">
|
||||
<section class="overflow-visible">
|
||||
<!-- Loading state -->
|
||||
<div
|
||||
v-if="versionsLoading && !versions?.length"
|
||||
|
||||
@@ -132,7 +132,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</NewModal>
|
||||
<div class="page experimental-styles-within">
|
||||
<div class="page">
|
||||
<div
|
||||
class="mb-4 flex items-center justify-between border-0 border-b border-solid border-divider pb-4"
|
||||
>
|
||||
|
||||
@@ -96,8 +96,8 @@ onMounted(() => {
|
||||
</div>
|
||||
|
||||
<div class="mt-auto flex gap-2">
|
||||
<ButtonStyled color="brand" class="flex-1">
|
||||
<button class="w-full justify-center" @click="openPreview(id)">
|
||||
<ButtonStyled color="brand">
|
||||
<button class="w-full flex-1 justify-center" @click="openPreview(id)">
|
||||
<PlayIcon class="h-4 w-4" aria-hidden="true" />
|
||||
Preview
|
||||
</button>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { CopyIcon, LibraryIcon, PlayIcon, SearchIcon } from '@modrinth/assets'
|
||||
import { ButtonStyled, Card, NewModal, StyledInput } from '@modrinth/ui'
|
||||
import { ButtonStyled, NewModal, StyledInput } from '@modrinth/ui'
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
|
||||
import emails from '~/templates/emails'
|
||||
@@ -178,84 +178,82 @@ onMounted(() => {
|
||||
</div>
|
||||
|
||||
<div class="input-group mt-4">
|
||||
<button class="iconified-button transparent" type="button" @click="closePreview">
|
||||
Close
|
||||
</button>
|
||||
<ButtonStyled type="transparent">
|
||||
<button @click="closePreview">Close</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</NewModal>
|
||||
<div class="normal-page__content">
|
||||
<Card class="mb-6 flex flex-col gap-4">
|
||||
<div class="flex flex-wrap items-center gap-3">
|
||||
<StyledInput
|
||||
id="email-search"
|
||||
v-model="query"
|
||||
type="search"
|
||||
:icon="SearchIcon"
|
||||
placeholder="Search templates..."
|
||||
wrapper-class="w-72"
|
||||
/>
|
||||
<div class="flex flex-wrap items-center gap-3">
|
||||
<StyledInput
|
||||
id="email-search"
|
||||
v-model="query"
|
||||
type="search"
|
||||
:icon="SearchIcon"
|
||||
placeholder="Search templates..."
|
||||
wrapper-class="w-72"
|
||||
/>
|
||||
|
||||
<ButtonStyled color="brand">
|
||||
<button :disabled="filtered.length === 0" @click="openAll">
|
||||
<LibraryIcon class="h-4 w-4" aria-hidden="true" />
|
||||
Open all ({{ counts.shown }})
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled color="brand">
|
||||
<button :disabled="filtered.length === 0" @click="openAll">
|
||||
<LibraryIcon class="h-4 w-4" aria-hidden="true" />
|
||||
Open all ({{ counts.shown }})
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
|
||||
<span class="text-sm text-secondary">
|
||||
Showing <span class="font-medium text-contrast">{{ counts.shown }}</span> of
|
||||
<span class="font-medium text-contrast">{{ counts.total }}</span>
|
||||
</span>
|
||||
</div>
|
||||
<span class="text-sm text-secondary">
|
||||
Showing <span class="font-medium text-contrast">{{ counts.shown }}</span> of
|
||||
<span class="font-medium text-contrast">{{ counts.total }}</span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="filtered.length === 0"
|
||||
class="rounded-lg border border-dashed border-divider px-6 py-10 text-center text-sm text-secondary"
|
||||
<div
|
||||
v-if="filtered.length === 0"
|
||||
class="mt-4 border-0 border-b border-solid border-surface-4 pb-4"
|
||||
>
|
||||
No templates match your search.
|
||||
</div>
|
||||
|
||||
<ul v-else class="m-0 mt-4 grid gap-3 p-0 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
|
||||
<li
|
||||
v-for="id in filtered"
|
||||
:key="id"
|
||||
class="group flex flex-col justify-between rounded-2xl border border-solid border-surface-4 bg-surface-3 p-4 shadow-sm transition hover:shadow"
|
||||
>
|
||||
No templates match your search.
|
||||
</div>
|
||||
|
||||
<ul v-else class="grid gap-4 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
|
||||
<li
|
||||
v-for="id in filtered"
|
||||
:key="id"
|
||||
class="hover:border-green/70 group flex flex-col justify-between rounded-lg border border-divider bg-button-bg p-4 shadow-sm transition hover:shadow"
|
||||
>
|
||||
<div class="mb-3">
|
||||
<div class="font-mono text-sm font-semibold tracking-tight text-contrast">
|
||||
{{ id }}
|
||||
</div>
|
||||
<div class="mt-1 truncate text-xs text-secondary">
|
||||
/_internal/templates/email/{{ id }}
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<div class="font-mono text-sm font-semibold tracking-tight text-contrast">
|
||||
{{ id }}
|
||||
</div>
|
||||
|
||||
<div class="mt-auto flex gap-2">
|
||||
<ButtonStyled color="brand" class="flex-1">
|
||||
<button class="w-full justify-center" @click="openPreview(id, $event)">
|
||||
<PlayIcon class="h-4 w-4" aria-hidden="true" />
|
||||
Preview
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
|
||||
<ButtonStyled>
|
||||
<button class="justify-center" title="Copy preview URL" @click="copy(id)">
|
||||
<CopyIcon class="h-4 w-4" aria-hidden="true" />
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<div class="mt-1 truncate text-xs text-secondary">
|
||||
/_internal/templates/email/{{ id }}
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<p class="mt-2 text-xs text-secondary">
|
||||
<div class="mt-auto flex gap-2">
|
||||
<ButtonStyled color="brand">
|
||||
<button @click="openPreview(id, $event)">
|
||||
<PlayIcon aria-hidden="true" />
|
||||
Preview
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
|
||||
<ButtonStyled circular type="outlined">
|
||||
<button title="Copy preview URL" @click="copy(id)">
|
||||
<CopyIcon aria-hidden="true" />
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<p class="mt-4">
|
||||
All templates come from
|
||||
<code class="rounded bg-code-bg px-1 py-0.5 text-[11px] text-code-text"
|
||||
<code class="rounded bg-code-bg px-1 py-0.5 text-sm text-code-text"
|
||||
>src/emails/index.ts</code
|
||||
>. Popouts render via
|
||||
<code class="rounded bg-code-bg px-1 py-0.5 text-[11px] text-code-text"
|
||||
<code class="rounded bg-code-bg px-1 py-0.5 text-sm text-code-text"
|
||||
>/_internal/templates/email/[template]</code
|
||||
>.
|
||||
</p>
|
||||
|
||||
@@ -120,7 +120,7 @@
|
||||
</div>
|
||||
</NewModal>
|
||||
<AssignNoticeModal ref="assignNoticeModal" @close="refreshNotices" />
|
||||
<div class="page experimental-styles-within">
|
||||
<div class="page">
|
||||
<div
|
||||
class="mb-6 flex items-end justify-between border-0 border-b border-solid border-divider pb-4"
|
||||
>
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
proceed-label="Cancel transfer"
|
||||
@proceed="confirmCancel"
|
||||
/>
|
||||
<div class="experimental-styles-within mx-auto max-w-[78.5rem] p-4">
|
||||
<div class="mx-auto max-w-[78.5rem] p-4">
|
||||
<div
|
||||
class="mb-6 flex items-end justify-between border-0 border-b border-solid border-divider pb-4"
|
||||
>
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -31,17 +31,19 @@ useSeoMeta({
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.auth-container .btn {
|
||||
.auth-container .btn-wrapper :is(a, button) {
|
||||
font-weight: 700;
|
||||
min-height: 2.5rem;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.centered-btn {
|
||||
.centered-btn :is(a, button),
|
||||
.centered-btn:is(a, button) {
|
||||
margin-inline: auto;
|
||||
}
|
||||
|
||||
.btn.continue-btn svg {
|
||||
.continue-btn :is(a, button) svg,
|
||||
.continue-btn:is(a, button) svg {
|
||||
margin: 0 0 0 0.5rem;
|
||||
}
|
||||
|
||||
@@ -52,19 +54,19 @@ useSeoMeta({
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.third-party .btn {
|
||||
.third-party a {
|
||||
width: 100%;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.third-party .btn svg {
|
||||
.third-party a svg {
|
||||
margin-right: var(--gap-sm);
|
||||
width: 1.25rem;
|
||||
height: 1.25rem;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 25.5rem) {
|
||||
.third-party .btn {
|
||||
.third-party a {
|
||||
grid-column: 1 / 3;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,14 +55,18 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="button-row">
|
||||
<Button class="wide-button" large :action="onReject" :disabled="pending">
|
||||
<XIcon />
|
||||
{{ formatMessage(messages.decline) }}
|
||||
</Button>
|
||||
<Button class="wide-button" color="primary" large :action="onAuthorize" :disabled="pending">
|
||||
<CheckIcon />
|
||||
{{ formatMessage(messages.authorize) }}
|
||||
</Button>
|
||||
<ButtonStyled size="large">
|
||||
<button class="wide-button" :disabled="pending" @click="onReject">
|
||||
<XIcon />
|
||||
{{ formatMessage(messages.decline) }}
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled color="brand" size="large">
|
||||
<button class="wide-button" :disabled="pending" @click="onAuthorize">
|
||||
<CheckIcon />
|
||||
{{ formatMessage(messages.authorize) }}
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
<div class="redirection-notice">
|
||||
<p class="redirect-instructions">
|
||||
@@ -83,7 +87,7 @@
|
||||
import { CheckIcon, XIcon } from '@modrinth/assets'
|
||||
import {
|
||||
Avatar,
|
||||
Button,
|
||||
ButtonStyled,
|
||||
commonMessages,
|
||||
defineMessages,
|
||||
injectModrinthClient,
|
||||
|
||||
@@ -22,13 +22,15 @@
|
||||
|
||||
<HCaptcha v-if="globals?.captcha_enabled" ref="captcha" v-model="token" />
|
||||
|
||||
<button
|
||||
class="btn btn-primary centered-btn"
|
||||
:disabled="globals?.captcha_enabled ? !token : false"
|
||||
@click="recovery"
|
||||
>
|
||||
<SendIcon /> {{ formatMessage(methodChoiceMessages.action) }}
|
||||
</button>
|
||||
<ButtonStyled color="brand">
|
||||
<button
|
||||
class="mx-auto"
|
||||
:disabled="globals?.captcha_enabled ? !token : false"
|
||||
@click="recovery"
|
||||
>
|
||||
<SendIcon /> {{ formatMessage(methodChoiceMessages.action) }}
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</template>
|
||||
<template v-else-if="step === 'passed_challenge'">
|
||||
<p>{{ formatMessage(postChallengeMessages.description) }}</p>
|
||||
@@ -57,9 +59,11 @@
|
||||
wrapper-class="w-full"
|
||||
/>
|
||||
|
||||
<button class="auth-form__input btn btn-primary continue-btn" @click="changePassword">
|
||||
{{ formatMessage(postChallengeMessages.action) }}
|
||||
</button>
|
||||
<ButtonStyled color="brand">
|
||||
<button class="auth-form__input continue-btn" @click="changePassword">
|
||||
{{ formatMessage(postChallengeMessages.action) }}
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</template>
|
||||
</section>
|
||||
</div>
|
||||
@@ -67,6 +71,7 @@
|
||||
<script setup>
|
||||
import { KeyIcon, MailIcon, SendIcon } from '@modrinth/assets'
|
||||
import {
|
||||
ButtonStyled,
|
||||
commonMessages,
|
||||
defineMessages,
|
||||
injectModrinthClient,
|
||||
|
||||
@@ -23,38 +23,52 @@
|
||||
@keyup.enter="begin2FASignIn"
|
||||
/>
|
||||
|
||||
<button class="btn btn-primary continue-btn" @click="begin2FASignIn">
|
||||
{{ formatMessage(commonMessages.signInButton) }} <RightArrowIcon />
|
||||
</button>
|
||||
<ButtonStyled color="brand">
|
||||
<button class="continue-btn" @click="begin2FASignIn">
|
||||
{{ formatMessage(commonMessages.signInButton) }} <RightArrowIcon />
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</template>
|
||||
<template v-else>
|
||||
<h1>{{ formatMessage(messages.signInWithLabel) }}</h1>
|
||||
|
||||
<section class="third-party">
|
||||
<a class="btn" :href="getAuthUrl('discord', redirectTarget)">
|
||||
<DiscordColorIcon />
|
||||
<span>Discord</span>
|
||||
</a>
|
||||
<a class="btn" :href="getAuthUrl('github', redirectTarget)">
|
||||
<GitHubColorIcon />
|
||||
<span>GitHub</span>
|
||||
</a>
|
||||
<a class="btn" :href="getAuthUrl('microsoft', redirectTarget)">
|
||||
<MicrosoftColorIcon />
|
||||
<span>Microsoft</span>
|
||||
</a>
|
||||
<a class="btn" :href="getAuthUrl('google', redirectTarget)">
|
||||
<GoogleColorIcon />
|
||||
<span>Google</span>
|
||||
</a>
|
||||
<a class="btn" :href="getAuthUrl('steam', redirectTarget)">
|
||||
<SteamColorIcon />
|
||||
<span>Steam</span>
|
||||
</a>
|
||||
<a class="btn" :href="getAuthUrl('gitlab', redirectTarget)">
|
||||
<GitLabColorIcon />
|
||||
<span>GitLab</span>
|
||||
</a>
|
||||
<ButtonStyled>
|
||||
<a :href="getAuthUrl('discord', redirectTarget)">
|
||||
<DiscordColorIcon />
|
||||
<span>Discord</span>
|
||||
</a>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled>
|
||||
<a :href="getAuthUrl('github', redirectTarget)">
|
||||
<GitHubColorIcon />
|
||||
<span>GitHub</span>
|
||||
</a>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled>
|
||||
<a :href="getAuthUrl('microsoft', redirectTarget)">
|
||||
<MicrosoftColorIcon />
|
||||
<span>Microsoft</span>
|
||||
</a>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled>
|
||||
<a :href="getAuthUrl('google', redirectTarget)">
|
||||
<GoogleColorIcon />
|
||||
<span>Google</span>
|
||||
</a>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled>
|
||||
<a :href="getAuthUrl('steam', redirectTarget)">
|
||||
<SteamColorIcon />
|
||||
<span>Steam</span>
|
||||
</a>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled>
|
||||
<a :href="getAuthUrl('gitlab', redirectTarget)">
|
||||
<GitLabColorIcon />
|
||||
<span>GitLab</span>
|
||||
</a>
|
||||
</ButtonStyled>
|
||||
</section>
|
||||
|
||||
<h1>{{ formatMessage(messages.usePasswordLabel) }}</h1>
|
||||
@@ -85,13 +99,15 @@
|
||||
|
||||
<HCaptcha v-if="globals?.captcha_enabled" ref="captcha" v-model="token" />
|
||||
|
||||
<button
|
||||
class="btn btn-primary continue-btn centered-btn"
|
||||
:disabled="globals?.captcha_enabled ? !token : false"
|
||||
@click="beginPasswordSignIn()"
|
||||
>
|
||||
{{ formatMessage(commonMessages.signInButton) }} <RightArrowIcon />
|
||||
</button>
|
||||
<ButtonStyled color="brand">
|
||||
<button
|
||||
class="continue-btn centered-btn"
|
||||
:disabled="globals?.captcha_enabled ? !token : false"
|
||||
@click="beginPasswordSignIn()"
|
||||
>
|
||||
{{ formatMessage(commonMessages.signInButton) }} <RightArrowIcon />
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
|
||||
<div class="auth-form__additional-options">
|
||||
<IntlFormatted :message-id="messages.additionalOptionsLabel">
|
||||
@@ -137,6 +153,7 @@ import {
|
||||
SteamColorIcon,
|
||||
} from '@modrinth/assets'
|
||||
import {
|
||||
ButtonStyled,
|
||||
commonMessages,
|
||||
defineMessages,
|
||||
injectModrinthClient,
|
||||
|
||||
@@ -3,30 +3,42 @@
|
||||
<h1>{{ formatMessage(messages.signUpWithTitle) }}</h1>
|
||||
|
||||
<section class="third-party">
|
||||
<a class="btn discord-btn" :href="getAuthUrl('discord', redirectTarget)">
|
||||
<DiscordColorIcon />
|
||||
<span>Discord</span>
|
||||
</a>
|
||||
<a class="btn" :href="getAuthUrl('github', redirectTarget)">
|
||||
<GitHubColorIcon />
|
||||
<span>GitHub</span>
|
||||
</a>
|
||||
<a class="btn" :href="getAuthUrl('microsoft', redirectTarget)">
|
||||
<MicrosoftColorIcon />
|
||||
<span>Microsoft</span>
|
||||
</a>
|
||||
<a class="btn" :href="getAuthUrl('google', redirectTarget)">
|
||||
<GoogleColorIcon />
|
||||
<span>Google</span>
|
||||
</a>
|
||||
<a class="btn" :href="getAuthUrl('steam', redirectTarget)">
|
||||
<SteamColorIcon />
|
||||
<span>Steam</span>
|
||||
</a>
|
||||
<a class="btn" :href="getAuthUrl('gitlab', redirectTarget)">
|
||||
<GitLabColorIcon />
|
||||
<span>GitLab</span>
|
||||
</a>
|
||||
<ButtonStyled>
|
||||
<a class="discord-btn" :href="getAuthUrl('discord', redirectTarget)">
|
||||
<DiscordColorIcon />
|
||||
<span>Discord</span>
|
||||
</a>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled>
|
||||
<a :href="getAuthUrl('github', redirectTarget)">
|
||||
<GitHubColorIcon />
|
||||
<span>GitHub</span>
|
||||
</a>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled>
|
||||
<a :href="getAuthUrl('microsoft', redirectTarget)">
|
||||
<MicrosoftColorIcon />
|
||||
<span>Microsoft</span>
|
||||
</a>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled>
|
||||
<a :href="getAuthUrl('google', redirectTarget)">
|
||||
<GoogleColorIcon />
|
||||
<span>Google</span>
|
||||
</a>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled>
|
||||
<a :href="getAuthUrl('steam', redirectTarget)">
|
||||
<SteamColorIcon />
|
||||
<span>Steam</span>
|
||||
</a>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled>
|
||||
<a :href="getAuthUrl('gitlab', redirectTarget)">
|
||||
<GitLabColorIcon />
|
||||
<span>GitLab</span>
|
||||
</a>
|
||||
</ButtonStyled>
|
||||
</section>
|
||||
|
||||
<h1>{{ formatMessage(messages.createAccountTitle) }}</h1>
|
||||
@@ -100,13 +112,15 @@
|
||||
|
||||
<HCaptcha v-if="globals?.captcha_enabled" ref="captcha" v-model="token" />
|
||||
|
||||
<button
|
||||
class="btn btn-primary continue-btn centered-btn"
|
||||
:disabled="globals?.captcha_enabled ? !token : false"
|
||||
@click="createAccount"
|
||||
>
|
||||
{{ formatMessage(messages.createAccountButton) }} <RightArrowIcon />
|
||||
</button>
|
||||
<ButtonStyled color="brand">
|
||||
<button
|
||||
class="continue-btn centered-btn"
|
||||
:disabled="globals?.captcha_enabled ? !token : false"
|
||||
@click="createAccount"
|
||||
>
|
||||
{{ formatMessage(messages.createAccountButton) }} <RightArrowIcon />
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
|
||||
<div class="auth-form__additional-options">
|
||||
{{ formatMessage(messages.alreadyHaveAccountLabel) }}
|
||||
@@ -138,6 +152,7 @@ import {
|
||||
UserIcon,
|
||||
} from '@modrinth/assets'
|
||||
import {
|
||||
ButtonStyled,
|
||||
Checkbox,
|
||||
commonMessages,
|
||||
defineMessages,
|
||||
|
||||
@@ -6,9 +6,11 @@
|
||||
<section class="auth-form">
|
||||
<p>{{ formatMessage(alreadyVerifiedMessages.description) }}</p>
|
||||
|
||||
<NuxtLink class="btn" to="/settings/account">
|
||||
<SettingsIcon /> {{ formatMessage(messages.accountSettings) }}
|
||||
</NuxtLink>
|
||||
<ButtonStyled>
|
||||
<NuxtLink to="/settings/account">
|
||||
<SettingsIcon /> {{ formatMessage(messages.accountSettings) }}
|
||||
</NuxtLink>
|
||||
</ButtonStyled>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -24,10 +24,12 @@
|
||||
:description="formatMessage(messages.subscribeCheckbox)"
|
||||
/>
|
||||
|
||||
<button class="btn btn-primary centered-btn" @click="continueSignUp">
|
||||
{{ formatMessage(commonMessages.continueButton) }}
|
||||
<RightArrowIcon />
|
||||
</button>
|
||||
<ButtonStyled color="brand">
|
||||
<button class="centered-btn" @click="continueSignUp">
|
||||
{{ formatMessage(commonMessages.continueButton) }}
|
||||
<RightArrowIcon />
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
|
||||
<p class="tos-text">
|
||||
<IntlFormatted :message-id="messages.tosLabel">
|
||||
@@ -50,6 +52,7 @@
|
||||
<script setup>
|
||||
import { RightArrowIcon, WavingRinthbot } from '@modrinth/assets'
|
||||
import {
|
||||
ButtonStyled,
|
||||
Checkbox,
|
||||
commonMessages,
|
||||
defineMessages,
|
||||
|
||||
@@ -116,14 +116,14 @@
|
||||
</RadioButtons>
|
||||
</div>
|
||||
<div class="flex justify-end gap-2">
|
||||
<ButtonStyled class="w-24">
|
||||
<button @click="() => editModal?.hide()">
|
||||
<ButtonStyled>
|
||||
<button class="w-24" @click="() => editModal?.hide()">
|
||||
<XIcon aria-hidden="true" />
|
||||
{{ formatMessage(commonMessages.cancelButton) }}
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled color="brand" class="w-36">
|
||||
<button :disabled="saving" @click="save()">
|
||||
<ButtonStyled color="brand">
|
||||
<button class="w-36" :disabled="saving" @click="save()">
|
||||
<SpinnerIcon v-if="saving" class="animate-spin" aria-hidden="true" />
|
||||
<SaveIcon v-else aria-hidden="true" />
|
||||
{{
|
||||
@@ -193,7 +193,7 @@
|
||||
}"
|
||||
>
|
||||
<template #stat="{ children }">
|
||||
<span class="primary-stat__counter">
|
||||
<span>
|
||||
<component :is="() => normalizeChildren(children)" />
|
||||
</span>
|
||||
</template>
|
||||
@@ -333,24 +333,19 @@
|
||||
"
|
||||
>
|
||||
<template v-if="canEdit || collection.id === 'following'" #actions>
|
||||
<button
|
||||
v-if="canEdit"
|
||||
class="iconified-button remove-btn"
|
||||
:disabled="removing"
|
||||
@click="() => removeProject(project)"
|
||||
>
|
||||
<SpinnerIcon v-if="removing" class="animate-spin" aria-hidden="true" />
|
||||
<XIcon v-else aria-hidden="true" />
|
||||
{{ formatMessage(messages.removeProjectButton) }}
|
||||
</button>
|
||||
<button
|
||||
v-if="collection.id === 'following'"
|
||||
class="iconified-button"
|
||||
@click="unfollowProject(project)"
|
||||
>
|
||||
<HeartMinusIcon aria-hidden="true" />
|
||||
{{ formatMessage(messages.unfollowProjectButton) }}
|
||||
</button>
|
||||
<ButtonStyled v-if="canEdit">
|
||||
<button class="remove-btn" :disabled="removing" @click="() => removeProject(project)">
|
||||
<SpinnerIcon v-if="removing" class="animate-spin" aria-hidden="true" />
|
||||
<XIcon v-else aria-hidden="true" />
|
||||
{{ formatMessage(messages.removeProjectButton) }}
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled v-if="collection.id === 'following'">
|
||||
<button @click="unfollowProject(project)">
|
||||
<HeartMinusIcon aria-hidden="true" />
|
||||
{{ formatMessage(messages.unfollowProjectButton) }}
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</template>
|
||||
</ProjectCard>
|
||||
</ProjectCardList>
|
||||
|
||||
@@ -30,14 +30,12 @@
|
||||
<span class="font-semibold text-secondary">{{ selected }}</span>
|
||||
</DropdownSelect>
|
||||
|
||||
<Button
|
||||
color="primary"
|
||||
class="ml-auto"
|
||||
@click="(event) => $refs.modal_creation.show(event)"
|
||||
>
|
||||
<PlusIcon aria-hidden="true" />
|
||||
{{ formatMessage(messages.createNewButton) }}
|
||||
</Button>
|
||||
<ButtonStyled color="brand">
|
||||
<button class="ml-auto" @click="(event) => $refs.modal_creation.show(event)">
|
||||
<PlusIcon aria-hidden="true" />
|
||||
{{ formatMessage(messages.createNewButton) }}
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
</div>
|
||||
<div class="collections-grid">
|
||||
@@ -146,7 +144,7 @@ import {
|
||||
} from '@modrinth/assets'
|
||||
import {
|
||||
Avatar,
|
||||
Button,
|
||||
ButtonStyled,
|
||||
commonMessages,
|
||||
defineMessages,
|
||||
DropdownSelect,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="dashboard-overview">
|
||||
<div>
|
||||
<section class="universal-card dashboard-header">
|
||||
<Avatar :src="auth.user.avatar_url" size="md" circle :alt="auth.user.username" />
|
||||
<div class="username">
|
||||
@@ -12,7 +12,7 @@
|
||||
</NuxtLink>
|
||||
</div>
|
||||
</section>
|
||||
<div class="dashboard-notifications">
|
||||
<div>
|
||||
<section class="universal-card">
|
||||
<div class="header__row">
|
||||
<h2 class="header__title text-2xl">
|
||||
@@ -50,44 +50,12 @@
|
||||
</template>
|
||||
<div v-else class="universal-body">
|
||||
<p>{{ formatMessage(messages.noUnreadNotifications) }}</p>
|
||||
<nuxt-link class="iconified-button !mt-4" to="/dashboard/notifications/history">
|
||||
<HistoryIcon />
|
||||
{{ formatMessage(messages.viewNotificationHistory) }}
|
||||
</nuxt-link>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<div class="dashboard-analytics">
|
||||
<section class="universal-card">
|
||||
<h2>{{ formatMessage(commonMessages.analyticsButton) }}</h2>
|
||||
<div class="grid-display">
|
||||
<div class="grid-display__item">
|
||||
<div class="label">{{ formatMessage(messages.totalDownloads) }}</div>
|
||||
<div class="value">
|
||||
{{ $formatNumber(projects.reduce((agg, x) => agg + x.downloads, 0)) }}
|
||||
</div>
|
||||
<span>{{
|
||||
formatMessage(messages.fromProjects, { count: downloadsProjectCount })
|
||||
}}</span>
|
||||
<!-- <NuxtLink class="goto-link" to="/dashboard/analytics"-->
|
||||
<!-- >View breakdown-->
|
||||
<!-- <ChevronRightIcon-->
|
||||
<!-- class="featured-header-chevron"-->
|
||||
<!-- aria-hidden="true"-->
|
||||
<!-- /></NuxtLink>-->
|
||||
</div>
|
||||
<div class="grid-display__item">
|
||||
<div class="label">{{ formatMessage(messages.totalFollowers) }}</div>
|
||||
<div class="value">
|
||||
{{ $formatNumber(projects.reduce((agg, x) => agg + x.followers, 0)) }}
|
||||
</div>
|
||||
<span>
|
||||
<span>{{
|
||||
formatMessage(messages.fromProjects, { count: followersProjectCount })
|
||||
}}</span>
|
||||
</span>
|
||||
</div>
|
||||
<ButtonStyled>
|
||||
<nuxt-link to="/dashboard/notifications/history" class="!mt-4 w-fit">
|
||||
<HistoryIcon />
|
||||
{{ formatMessage(messages.viewNotificationHistory) }}
|
||||
</nuxt-link>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
@@ -97,6 +65,7 @@
|
||||
import { ChevronRightIcon, HistoryIcon } from '@modrinth/assets'
|
||||
import {
|
||||
Avatar,
|
||||
ButtonStyled,
|
||||
commonMessages,
|
||||
defineMessages,
|
||||
injectModrinthClient,
|
||||
@@ -152,19 +121,6 @@ useHead({
|
||||
const auth = await useAuth()
|
||||
const client = injectModrinthClient()
|
||||
|
||||
const { data: projects } = useQuery({
|
||||
queryKey: computed(() => ['user', auth.value?.user?.id, 'projects']),
|
||||
queryFn: () => client.labrinth.users_v2.getProjects(auth.value?.user?.id),
|
||||
placeholderData: [],
|
||||
})
|
||||
|
||||
const downloadsProjectCount = computed(
|
||||
() => projects.value.filter((project) => project.downloads > 0).length,
|
||||
)
|
||||
const followersProjectCount = computed(
|
||||
() => projects.value.filter((project) => project.followers > 0).length,
|
||||
)
|
||||
|
||||
const { data, refetch } = useQuery({
|
||||
queryKey: computed(() => ['user', auth.value?.user?.id, 'notifications']),
|
||||
queryFn: async () => {
|
||||
@@ -190,40 +146,6 @@ const notifications = computed(() => {
|
||||
const extraNotifs = computed(() => (data.value ? data.value.extraNotifs : 0))
|
||||
</script>
|
||||
<style lang="scss">
|
||||
.dashboard-overview {
|
||||
display: grid;
|
||||
grid-template:
|
||||
'header header'
|
||||
'notifications analytics' / 1fr auto;
|
||||
gap: var(--spacing-card-md);
|
||||
|
||||
> .universal-card {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 750px) {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
|
||||
.dashboard-notifications {
|
||||
grid-area: notifications;
|
||||
//display: flex;
|
||||
//flex-direction: column;
|
||||
//gap: var(--spacing-card-md);
|
||||
|
||||
a.view-more-notifs {
|
||||
display: flex;
|
||||
width: fit-content;
|
||||
margin-left: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.dashboard-analytics {
|
||||
grid-area: analytics;
|
||||
}
|
||||
|
||||
.dashboard-header {
|
||||
display: flex;
|
||||
gap: var(--spacing-card-bg);
|
||||
|
||||
@@ -21,14 +21,18 @@
|
||||
</h2>
|
||||
</div>
|
||||
<template v-if="!history">
|
||||
<Button v-if="data.hasRead" @click="updateRoute()">
|
||||
<HistoryIcon />
|
||||
{{ formatMessage(messages.viewHistory) }}
|
||||
</Button>
|
||||
<Button v-if="notifications.length > 0" color="danger" @click="readAll()">
|
||||
<CheckCheckIcon />
|
||||
{{ formatMessage(messages.markAllAsRead) }}
|
||||
</Button>
|
||||
<ButtonStyled v-if="data.hasRead">
|
||||
<button @click="updateRoute()">
|
||||
<HistoryIcon />
|
||||
{{ formatMessage(messages.viewHistory) }}
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled v-if="notifications.length > 0" color="red">
|
||||
<button @click="readAll()">
|
||||
<CheckCheckIcon />
|
||||
{{ formatMessage(messages.markAllAsRead) }}
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</template>
|
||||
</div>
|
||||
<Chips
|
||||
@@ -67,7 +71,7 @@
|
||||
<script setup>
|
||||
import { CheckCheckIcon, HistoryIcon } from '@modrinth/assets'
|
||||
import {
|
||||
Button,
|
||||
ButtonStyled,
|
||||
Chips,
|
||||
commonMessages,
|
||||
defineMessages,
|
||||
|
||||
@@ -5,10 +5,12 @@
|
||||
<div class="header__row">
|
||||
<h2 class="header__title text-2xl">{{ formatMessage(messages.organizationsTitle) }}</h2>
|
||||
<div class="input-group">
|
||||
<button class="iconified-button brand-button" @click="openCreateOrgModal">
|
||||
<PlusIcon aria-hidden="true" />
|
||||
{{ formatMessage(messages.createOrganization) }}
|
||||
</button>
|
||||
<ButtonStyled color="brand">
|
||||
<button @click="openCreateOrgModal">
|
||||
<PlusIcon aria-hidden="true" />
|
||||
{{ formatMessage(messages.createOrganization) }}
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
</div>
|
||||
<template v-if="orgs?.length > 0">
|
||||
@@ -51,7 +53,7 @@
|
||||
|
||||
<script setup>
|
||||
import { PlusIcon, UsersIcon } from '@modrinth/assets'
|
||||
import { Avatar, defineMessages, injectModrinthClient, useVIntl } from '@modrinth/ui'
|
||||
import { Avatar, ButtonStyled, defineMessages, injectModrinthClient, useVIntl } from '@modrinth/ui'
|
||||
import { useQuery } from '@tanstack/vue-query'
|
||||
|
||||
import OrganizationCreateModal from '~/components/ui/create/OrganizationCreateModal.vue'
|
||||
|
||||
@@ -7,68 +7,77 @@
|
||||
<label for="issue-tracker-input" :title="formatMessage(messages.issueTrackerDescription)">
|
||||
<span class="label__title">{{ formatMessage(messages.issueTrackerLabel) }}</span>
|
||||
</label>
|
||||
<div class="input-group shrink-first">
|
||||
<div class="flex gap-2">
|
||||
<StyledInput
|
||||
id="issue-tracker-input"
|
||||
v-model="editLinks.issues.val"
|
||||
:disabled="editLinks.issues.clear"
|
||||
type="url"
|
||||
class="w-full"
|
||||
:placeholder="getLinkInputPlaceholder(editLinks.issues.clear)"
|
||||
:maxlength="2048"
|
||||
/>
|
||||
<button
|
||||
v-tooltip="formatMessage(messages.clearLinkLabel)"
|
||||
:aria-label="formatMessage(messages.clearLinkLabel)"
|
||||
class="square-button label-button"
|
||||
:data-active="editLinks.issues.clear"
|
||||
@click="editLinks.issues.clear = !editLinks.issues.clear"
|
||||
>
|
||||
<TrashIcon />
|
||||
</button>
|
||||
<ButtonStyled circular>
|
||||
<button
|
||||
v-tooltip="formatMessage(messages.clearLinkLabel)"
|
||||
class="label-button"
|
||||
:aria-label="formatMessage(messages.clearLinkLabel)"
|
||||
:data-active="editLinks.issues.clear"
|
||||
@click="editLinks.issues.clear = !editLinks.issues.clear"
|
||||
>
|
||||
<TrashIcon />
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
<label for="source-code-input" :title="formatMessage(messages.sourceCodeDescription)">
|
||||
<span class="label__title">{{ formatMessage(messages.sourceCodeLabel) }}</span>
|
||||
</label>
|
||||
<div class="input-group shrink-first">
|
||||
<div class="flex gap-2">
|
||||
<StyledInput
|
||||
id="source-code-input"
|
||||
v-model="editLinks.source.val"
|
||||
:disabled="editLinks.source.clear"
|
||||
type="url"
|
||||
class="w-full"
|
||||
:maxlength="2048"
|
||||
:placeholder="getLinkInputPlaceholder(editLinks.source.clear)"
|
||||
/>
|
||||
<button
|
||||
v-tooltip="formatMessage(messages.clearLinkLabel)"
|
||||
:aria-label="formatMessage(messages.clearLinkLabel)"
|
||||
class="square-button label-button"
|
||||
:data-active="editLinks.source.clear"
|
||||
@click="editLinks.source.clear = !editLinks.source.clear"
|
||||
>
|
||||
<TrashIcon />
|
||||
</button>
|
||||
<ButtonStyled circular>
|
||||
<button
|
||||
v-tooltip="formatMessage(messages.clearLinkLabel)"
|
||||
class="label-button"
|
||||
:aria-label="formatMessage(messages.clearLinkLabel)"
|
||||
:data-active="editLinks.source.clear"
|
||||
@click="editLinks.source.clear = !editLinks.source.clear"
|
||||
>
|
||||
<TrashIcon />
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
<label for="wiki-page-input" :title="formatMessage(messages.wikiPageDescription)">
|
||||
<span class="label__title">{{ formatMessage(messages.wikiPageLabel) }}</span>
|
||||
</label>
|
||||
<div class="input-group shrink-first">
|
||||
<div class="flex gap-2">
|
||||
<StyledInput
|
||||
id="wiki-page-input"
|
||||
v-model="editLinks.wiki.val"
|
||||
:disabled="editLinks.wiki.clear"
|
||||
type="url"
|
||||
class="w-full"
|
||||
:maxlength="2048"
|
||||
:placeholder="getLinkInputPlaceholder(editLinks.wiki.clear)"
|
||||
/>
|
||||
<button
|
||||
v-tooltip="formatMessage(messages.clearLinkLabel)"
|
||||
:aria-label="formatMessage(messages.clearLinkLabel)"
|
||||
class="square-button label-button"
|
||||
:data-active="editLinks.wiki.clear"
|
||||
@click="editLinks.wiki.clear = !editLinks.wiki.clear"
|
||||
>
|
||||
<TrashIcon />
|
||||
</button>
|
||||
<ButtonStyled circular>
|
||||
<button
|
||||
v-tooltip="formatMessage(messages.clearLinkLabel)"
|
||||
class="label-button"
|
||||
:aria-label="formatMessage(messages.clearLinkLabel)"
|
||||
:data-active="editLinks.wiki.clear"
|
||||
@click="editLinks.wiki.clear = !editLinks.wiki.clear"
|
||||
>
|
||||
<TrashIcon />
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
<label
|
||||
for="discord-invite-input"
|
||||
@@ -76,24 +85,27 @@
|
||||
>
|
||||
<span class="label__title">{{ formatMessage(messages.discordInviteLabel) }}</span>
|
||||
</label>
|
||||
<div class="input-group shrink-first">
|
||||
<div class="flex gap-2">
|
||||
<StyledInput
|
||||
id="discord-invite-input"
|
||||
v-model="editLinks.discord.val"
|
||||
:disabled="editLinks.discord.clear"
|
||||
class="w-full"
|
||||
type="url"
|
||||
:maxlength="2048"
|
||||
:placeholder="getLinkInputPlaceholder(editLinks.discord.clear, true)"
|
||||
/>
|
||||
<button
|
||||
v-tooltip="formatMessage(messages.clearLinkLabel)"
|
||||
:aria-label="formatMessage(messages.clearLinkLabel)"
|
||||
class="square-button label-button"
|
||||
:data-active="editLinks.discord.clear"
|
||||
@click="editLinks.discord.clear = !editLinks.discord.clear"
|
||||
>
|
||||
<TrashIcon />
|
||||
</button>
|
||||
<ButtonStyled circular>
|
||||
<button
|
||||
v-tooltip="formatMessage(messages.clearLinkLabel)"
|
||||
class="label-button"
|
||||
:aria-label="formatMessage(messages.clearLinkLabel)"
|
||||
:data-active="editLinks.discord.clear"
|
||||
@click="editLinks.discord.clear = !editLinks.discord.clear"
|
||||
>
|
||||
<TrashIcon />
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
</section>
|
||||
<p>
|
||||
@@ -128,15 +140,19 @@
|
||||
:label="formatMessage(messages.showAllProjects)"
|
||||
:description="formatMessage(messages.showAllProjects)"
|
||||
/>
|
||||
<div class="push-right input-group">
|
||||
<button class="iconified-button" @click="$refs.editLinksModal.hide()">
|
||||
<XIcon />
|
||||
{{ formatMessage(commonMessages.cancelButton) }}
|
||||
</button>
|
||||
<button class="iconified-button brand-button" @click="bulkEditLinks()">
|
||||
<SaveIcon />
|
||||
{{ formatMessage(commonMessages.saveChangesButton) }}
|
||||
</button>
|
||||
<div class="input-group ml-auto mt-4">
|
||||
<ButtonStyled type="outlined">
|
||||
<button @click="$refs.editLinksModal.hide()">
|
||||
<XIcon />
|
||||
{{ formatMessage(commonMessages.cancelButton) }}
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled color="brand">
|
||||
<button @click="bulkEditLinks()">
|
||||
<SaveIcon />
|
||||
{{ formatMessage(commonMessages.saveChangesButton) }}
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
</div>
|
||||
</NewModal>
|
||||
@@ -145,10 +161,12 @@
|
||||
<div class="header__row">
|
||||
<h2 class="header__title text-2xl">{{ formatMessage(messages.headTitle) }}</h2>
|
||||
<div class="input-group">
|
||||
<button class="iconified-button brand-button" @click="$refs.modal_creation.show($event)">
|
||||
<PlusIcon />
|
||||
{{ formatMessage(commonMessages.createAProjectButton) }}
|
||||
</button>
|
||||
<ButtonStyled color="brand">
|
||||
<button @click="$refs.modal_creation.show($event)">
|
||||
<PlusIcon />
|
||||
{{ formatMessage(commonMessages.createAProjectButton) }}
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
</div>
|
||||
<p v-if="projects.length < 1">
|
||||
@@ -157,14 +175,12 @@
|
||||
<template v-else>
|
||||
<p>{{ formatMessage(messages.bulkEditHint) }}</p>
|
||||
<div class="input-group">
|
||||
<button
|
||||
class="iconified-button"
|
||||
:disabled="selectedProjects.length === 0"
|
||||
@click="$refs.editLinksModal.show()"
|
||||
>
|
||||
<EditIcon />
|
||||
{{ formatMessage(messages.editLinksButton) }}
|
||||
</button>
|
||||
<ButtonStyled>
|
||||
<button :disabled="selectedProjects.length === 0" @click="$refs.editLinksModal.show()">
|
||||
<EditIcon />
|
||||
{{ formatMessage(messages.editLinksButton) }}
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<div class="push-right">
|
||||
<div class="labeled-control-row">
|
||||
{{ formatMessage(commonMessages.sortByLabel) }}
|
||||
@@ -175,14 +191,15 @@
|
||||
:options="sortOptions"
|
||||
@update:model-value="projects = updateSort(projects, sortBy, descending)"
|
||||
/>
|
||||
<button
|
||||
v-tooltip="formatMessage(descending ? messages.descending : messages.ascending)"
|
||||
class="square-button"
|
||||
@click="updateDescending()"
|
||||
>
|
||||
<SortDescIcon v-if="descending" />
|
||||
<SortAscIcon v-else />
|
||||
</button>
|
||||
<ButtonStyled circular>
|
||||
<button
|
||||
v-tooltip="formatMessage(descending ? messages.descending : messages.ascending)"
|
||||
@click="updateDescending()"
|
||||
>
|
||||
<SortDescIcon v-if="descending" />
|
||||
<SortAscIcon v-else />
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -603,9 +603,7 @@
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="experimental-styles-within flex w-full flex-col-reverse gap-2 md:w-auto md:flex-col md:items-center"
|
||||
>
|
||||
<div class="flex w-full flex-col-reverse gap-2 md:w-auto md:flex-col md:items-center">
|
||||
<ButtonStyled color="standard" size="large">
|
||||
<button class="w-full md:w-fit" @click="selectProduct('custom')">
|
||||
{{ formatMessage(messages.getStartedButton) }}
|
||||
|
||||
@@ -208,7 +208,7 @@
|
||||
<p>
|
||||
<IntlFormatted :message-id="messages.playWithLauncherDescription">
|
||||
<template #link="{ children }">
|
||||
<nuxt-link class="title-link" to="/app">
|
||||
<nuxt-link class="underline hover:brightness-[--hover-brightness]" to="/app">
|
||||
<component :is="() => children" />
|
||||
</nuxt-link>
|
||||
</template>
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
<template>
|
||||
<div
|
||||
class="experimental-styles-within relative mx-auto mb-6 flex min-h-screen w-full max-w-[1280px] flex-col px-6"
|
||||
>
|
||||
<div class="relative mx-auto mb-6 flex min-h-screen w-full max-w-[1280px] flex-col px-6">
|
||||
<h1>Moderation</h1>
|
||||
<NavTabs :links="moderationLinks" class="mb-4 hidden sm:flex" />
|
||||
<div class="mb-4 sm:hidden">
|
||||
|
||||
@@ -57,7 +57,7 @@
|
||||
</Combobox>
|
||||
</div>
|
||||
|
||||
<ButtonStyled color="orange" class="w-full sm:w-auto">
|
||||
<ButtonStyled color="orange">
|
||||
<button
|
||||
class="flex !h-[40px] w-full items-center justify-center gap-2 sm:w-auto"
|
||||
:disabled="paginatedProjects?.length === 0"
|
||||
|
||||
@@ -108,7 +108,7 @@ onMounted(() => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="page experimental-styles-within py-6">
|
||||
<div class="page py-6">
|
||||
<div
|
||||
class="flex flex-wrap items-center justify-between gap-4 border-0 border-b-[1px] border-solid border-divider px-6 pb-6"
|
||||
>
|
||||
@@ -122,7 +122,7 @@ onMounted(() => {
|
||||
<RssIcon />
|
||||
</a>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled circular icon-only>
|
||||
<ButtonStyled circular>
|
||||
<a v-tooltip="`Changelog`" href="/news/changelog" aria-label="Changelog">
|
||||
<GitGraphIcon />
|
||||
</a>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="page experimental-styles-within">
|
||||
<div class="page">
|
||||
<h1 class="m-0 text-3xl font-extrabold">Changelog</h1>
|
||||
<p class="my-3">Keep up-to-date on what's new with Modrinth.</p>
|
||||
<NuxtPage />
|
||||
|
||||
@@ -41,7 +41,7 @@ useSeoMeta({
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="page experimental-styles-within py-6">
|
||||
<div class="page py-6">
|
||||
<div class="flex flex-wrap items-center justify-between gap-4 px-6">
|
||||
<div>
|
||||
<h1 class="m-0 text-3xl font-extrabold">News</h1>
|
||||
@@ -53,7 +53,7 @@ useSeoMeta({
|
||||
<RssIcon />
|
||||
</a>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled circular icon-only>
|
||||
<ButtonStyled circular>
|
||||
<a v-tooltip="`Changelog`" href="/news/changelog" aria-label="Changelog">
|
||||
<GitGraphIcon />
|
||||
</a>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
</div>
|
||||
<div
|
||||
v-else-if="organization"
|
||||
class="experimental-styles-within new-page sidebar"
|
||||
class="new-page sidebar"
|
||||
:class="{ 'alt-layout': cosmetics.leftContentLayout || routeHasSettings }"
|
||||
>
|
||||
<ModalCreation ref="modal_creation" :organization-id="organization.id" />
|
||||
@@ -176,14 +176,18 @@
|
||||
<h2>Invitation to join {{ organization.name }}</h2>
|
||||
<p>You have been invited to join {{ organization.name }}.</p>
|
||||
<div class="input-group">
|
||||
<button class="iconified-button brand-button" @click="onAcceptInvite">
|
||||
<CheckIcon />
|
||||
Accept
|
||||
</button>
|
||||
<button class="iconified-button danger-button" @click="onDeclineInvite">
|
||||
<XIcon />
|
||||
Decline
|
||||
</button>
|
||||
<ButtonStyled color="brand">
|
||||
<button @click="onAcceptInvite">
|
||||
<CheckIcon />
|
||||
Accept
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled color="red">
|
||||
<button @click="onDeclineInvite">
|
||||
<XIcon />
|
||||
Decline
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="navLinks.length > 2" class="mb-4 max-w-full overflow-x-auto">
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import { TrashIcon, UploadIcon } from '@modrinth/assets'
|
||||
import {
|
||||
Avatar,
|
||||
Button,
|
||||
ButtonStyled,
|
||||
ConfirmModal,
|
||||
FileInput,
|
||||
injectNotificationManager,
|
||||
@@ -161,27 +161,27 @@ const onDeleteOrganization = useClientTry(async () => {
|
||||
size="md"
|
||||
class="project__icon"
|
||||
/>
|
||||
<div class="input-stack">
|
||||
<FileInput
|
||||
id="project-icon"
|
||||
:max-size="262144"
|
||||
:show-icon="true"
|
||||
accept="image/png,image/jpeg,image/gif,image/webp"
|
||||
class="btn"
|
||||
prompt="Upload icon"
|
||||
:disabled="!hasPermission"
|
||||
@change="showPreviewImage"
|
||||
>
|
||||
<UploadIcon />
|
||||
</FileInput>
|
||||
<Button
|
||||
v-if="!deletedIcon && (previewImage || organization.icon_url)"
|
||||
:disabled="!hasPermission"
|
||||
@click="markIconForDeletion"
|
||||
>
|
||||
<TrashIcon />
|
||||
Remove icon
|
||||
</Button>
|
||||
<div class="flex flex-col gap-2">
|
||||
<ButtonStyled>
|
||||
<FileInput
|
||||
id="project-icon"
|
||||
:max-size="262144"
|
||||
:show-icon="true"
|
||||
accept="image/png,image/jpeg,image/gif,image/webp"
|
||||
class="button-like"
|
||||
prompt="Upload icon"
|
||||
:disabled="!hasPermission"
|
||||
@change="showPreviewImage"
|
||||
>
|
||||
<UploadIcon />
|
||||
</FileInput>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled v-if="!deletedIcon && (previewImage || organization.icon_url)">
|
||||
<button :disabled="!hasPermission" @click="markIconForDeletion">
|
||||
<TrashIcon />
|
||||
Remove icon
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -231,10 +231,12 @@ const onDeleteOrganization = useClientTry(async () => {
|
||||
Deleting your organization will transfer all of its projects to the organization owner. This
|
||||
action cannot be undone.
|
||||
</p>
|
||||
<Button color="danger" @click="() => $refs.modal_deletion.show()">
|
||||
<TrashIcon />
|
||||
Delete organization
|
||||
</Button>
|
||||
<ButtonStyled color="red">
|
||||
<button @click="() => $refs.modal_deletion.show()">
|
||||
<TrashIcon />
|
||||
Delete organization
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
<UnsavedChangesPopup
|
||||
:original="originalState"
|
||||
|
||||
@@ -43,19 +43,20 @@
|
||||
@keypress.enter="() => onInviteTeamMember(organization.team, currentUsername)"
|
||||
/>
|
||||
<label for="username" class="hidden">Username</label>
|
||||
<Button
|
||||
color="primary"
|
||||
:disabled="
|
||||
!isPermission(
|
||||
currentMember.organization_permissions,
|
||||
organizationPermissions.MANAGE_INVITES,
|
||||
)
|
||||
"
|
||||
@click="() => onInviteTeamMember(organization.team_id, currentUsername)"
|
||||
>
|
||||
<UserPlusIcon />
|
||||
Invite
|
||||
</Button>
|
||||
<ButtonStyled color="brand">
|
||||
<button
|
||||
:disabled="
|
||||
!isPermission(
|
||||
currentMember.organization_permissions,
|
||||
organizationPermissions.MANAGE_INVITES,
|
||||
)
|
||||
"
|
||||
@click="() => onInviteTeamMember(organization.team_id, currentUsername)"
|
||||
>
|
||||
<UserPlusIcon />
|
||||
Invite
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
<div class="adjacent-input">
|
||||
<span class="label">
|
||||
@@ -64,14 +65,15 @@
|
||||
Remove yourself as a member of this organization.
|
||||
</span>
|
||||
</span>
|
||||
<Button
|
||||
color="danger"
|
||||
:disabled="currentMember.is_owner"
|
||||
@click="() => onLeaveProject(organization.team_id, auth.user.id)"
|
||||
>
|
||||
<UserRemoveIcon />
|
||||
Leave organization
|
||||
</Button>
|
||||
<ButtonStyled color="red">
|
||||
<button
|
||||
:disabled="currentMember.is_owner"
|
||||
@click="() => onLeaveProject(organization.team_id, auth.user.id)"
|
||||
>
|
||||
<UserRemoveIcon />
|
||||
Leave organization
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
@@ -94,18 +96,18 @@
|
||||
<div class="side-buttons">
|
||||
<Badge v-if="member.accepted" type="accepted" />
|
||||
<Badge v-else type="pending" />
|
||||
<Button
|
||||
icon-only
|
||||
transparent
|
||||
class="dropdown-icon"
|
||||
@click="
|
||||
openTeamMembers.indexOf(member.user.id) === -1
|
||||
? openTeamMembers.push(member.user.id)
|
||||
: (openTeamMembers = openTeamMembers.filter((it) => it !== member.user.id))
|
||||
"
|
||||
>
|
||||
<DropdownIcon />
|
||||
</Button>
|
||||
<ButtonStyled circular type="transparent">
|
||||
<button
|
||||
class="dropdown-icon"
|
||||
@click="
|
||||
openTeamMembers.indexOf(member.user.id) === -1
|
||||
? openTeamMembers.push(member.user.id)
|
||||
: (openTeamMembers = openTeamMembers.filter((it) => it !== member.user.id))
|
||||
"
|
||||
>
|
||||
<DropdownIcon />
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
</div>
|
||||
<div class="content">
|
||||
@@ -188,44 +190,44 @@
|
||||
</div>
|
||||
</template>
|
||||
<div class="input-group">
|
||||
<Button
|
||||
color="primary"
|
||||
:disabled="
|
||||
!isPermission(
|
||||
currentMember.organization_permissions,
|
||||
organizationPermissions.EDIT_MEMBER,
|
||||
)
|
||||
"
|
||||
@click="onUpdateTeamMember(organization.team_id, member)"
|
||||
>
|
||||
<SaveIcon />
|
||||
Save changes
|
||||
</Button>
|
||||
<Button
|
||||
v-if="!member.is_owner"
|
||||
color="danger"
|
||||
:disabled="
|
||||
!isPermission(
|
||||
currentMember.organization_permissions,
|
||||
organizationPermissions.EDIT_MEMBER,
|
||||
) &&
|
||||
!isPermission(
|
||||
currentMember.organization_permissions,
|
||||
organizationPermissions.REMOVE_MEMBER,
|
||||
)
|
||||
"
|
||||
@click="onRemoveMember(organization.team_id, member)"
|
||||
>
|
||||
<UserRemoveIcon />
|
||||
Remove member
|
||||
</Button>
|
||||
<Button
|
||||
v-if="!member.is_owner && currentMember.is_owner && member.accepted"
|
||||
@click="(e) => openTransferModal(member, e)"
|
||||
>
|
||||
<TransferIcon />
|
||||
Transfer ownership
|
||||
</Button>
|
||||
<ButtonStyled color="brand">
|
||||
<button
|
||||
:disabled="
|
||||
!isPermission(
|
||||
currentMember.organization_permissions,
|
||||
organizationPermissions.EDIT_MEMBER,
|
||||
)
|
||||
"
|
||||
@click="onUpdateTeamMember(organization.team_id, member)"
|
||||
>
|
||||
<SaveIcon />
|
||||
Save changes
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled v-if="!member.is_owner" color="red">
|
||||
<button
|
||||
:disabled="
|
||||
!isPermission(
|
||||
currentMember.organization_permissions,
|
||||
organizationPermissions.EDIT_MEMBER,
|
||||
) &&
|
||||
!isPermission(
|
||||
currentMember.organization_permissions,
|
||||
organizationPermissions.REMOVE_MEMBER,
|
||||
)
|
||||
"
|
||||
@click="onRemoveMember(organization.team_id, member)"
|
||||
>
|
||||
<UserRemoveIcon />
|
||||
Remove member
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled v-if="!member.is_owner && currentMember.is_owner && member.accepted">
|
||||
<button @click="(e) => openTransferModal(member, e)">
|
||||
<TransferIcon />
|
||||
Transfer ownership
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -244,7 +246,7 @@ import {
|
||||
import {
|
||||
Avatar,
|
||||
Badge,
|
||||
Button,
|
||||
ButtonStyled,
|
||||
Checkbox,
|
||||
injectNotificationManager,
|
||||
StyledInput,
|
||||
|
||||
@@ -25,15 +25,17 @@
|
||||
"
|
||||
:maxlength="2048"
|
||||
/>
|
||||
<button
|
||||
v-tooltip="'Clear link'"
|
||||
aria-label="Clear link"
|
||||
class="square-button label-button"
|
||||
:data-active="editLinks.issues.clear"
|
||||
@click="editLinks.issues.clear = !editLinks.issues.clear"
|
||||
>
|
||||
<TrashIcon />
|
||||
</button>
|
||||
<ButtonStyled circular>
|
||||
<button
|
||||
v-tooltip="'Clear link'"
|
||||
class="label-button"
|
||||
aria-label="Clear link"
|
||||
:data-active="editLinks.issues.clear"
|
||||
@click="editLinks.issues.clear = !editLinks.issues.clear"
|
||||
>
|
||||
<TrashIcon />
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
<label
|
||||
for="source-code-input"
|
||||
@@ -52,15 +54,17 @@
|
||||
editLinks.source.clear ? 'Existing link will be cleared' : 'Enter a valid URL'
|
||||
"
|
||||
/>
|
||||
<button
|
||||
v-tooltip="'Clear link'"
|
||||
aria-label="Clear link"
|
||||
class="square-button label-button"
|
||||
:data-active="editLinks.source.clear"
|
||||
@click="editLinks.source.clear = !editLinks.source.clear"
|
||||
>
|
||||
<TrashIcon />
|
||||
</button>
|
||||
<ButtonStyled circular>
|
||||
<button
|
||||
v-tooltip="'Clear link'"
|
||||
class="label-button"
|
||||
aria-label="Clear link"
|
||||
:data-active="editLinks.source.clear"
|
||||
@click="editLinks.source.clear = !editLinks.source.clear"
|
||||
>
|
||||
<TrashIcon />
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
<label
|
||||
for="wiki-page-input"
|
||||
@@ -79,15 +83,17 @@
|
||||
editLinks.wiki.clear ? 'Existing link will be cleared' : 'Enter a valid URL'
|
||||
"
|
||||
/>
|
||||
<button
|
||||
v-tooltip="'Clear link'"
|
||||
aria-label="Clear link"
|
||||
class="square-button label-button"
|
||||
:data-active="editLinks.wiki.clear"
|
||||
@click="editLinks.wiki.clear = !editLinks.wiki.clear"
|
||||
>
|
||||
<TrashIcon />
|
||||
</button>
|
||||
<ButtonStyled circular>
|
||||
<button
|
||||
v-tooltip="'Clear link'"
|
||||
class="label-button"
|
||||
aria-label="Clear link"
|
||||
:data-active="editLinks.wiki.clear"
|
||||
@click="editLinks.wiki.clear = !editLinks.wiki.clear"
|
||||
>
|
||||
<TrashIcon />
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
<label for="discord-invite-input" title="An invitation link to your Discord server.">
|
||||
<span class="label__title">Discord invite</span>
|
||||
@@ -105,15 +111,17 @@
|
||||
: 'Enter a valid Discord invite URL'
|
||||
"
|
||||
/>
|
||||
<button
|
||||
v-tooltip="'Clear link'"
|
||||
aria-label="Clear link"
|
||||
class="square-button label-button"
|
||||
:data-active="editLinks.discord.clear"
|
||||
@click="editLinks.discord.clear = !editLinks.discord.clear"
|
||||
>
|
||||
<TrashIcon />
|
||||
</button>
|
||||
<ButtonStyled circular>
|
||||
<button
|
||||
v-tooltip="'Clear link'"
|
||||
class="label-button"
|
||||
aria-label="Clear link"
|
||||
:data-active="editLinks.discord.clear"
|
||||
@click="editLinks.discord.clear = !editLinks.discord.clear"
|
||||
>
|
||||
<TrashIcon />
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
</section>
|
||||
<p>
|
||||
@@ -143,14 +151,18 @@
|
||||
description="Show all projects"
|
||||
/>
|
||||
<div class="push-right input-group">
|
||||
<button class="iconified-button" @click="$refs.editLinksModal.hide()">
|
||||
<XIcon />
|
||||
Cancel
|
||||
</button>
|
||||
<button class="iconified-button brand-button" @click="onBulkEditLinks">
|
||||
<SaveIcon />
|
||||
Save changes
|
||||
</button>
|
||||
<ButtonStyled>
|
||||
<button @click="$refs.editLinksModal.hide()">
|
||||
<XIcon />
|
||||
Cancel
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled color="brand">
|
||||
<button @click="onBulkEditLinks">
|
||||
<SaveIcon />
|
||||
Save changes
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
</div>
|
||||
</NewModal>
|
||||
@@ -159,10 +171,12 @@
|
||||
<div class="header__row">
|
||||
<h2 class="header__title text-2xl">Projects</h2>
|
||||
<div class="input-group">
|
||||
<button class="iconified-button brand-button" @click="$refs.modal_creation.show($event)">
|
||||
<PlusIcon />
|
||||
{{ formatMessage(commonMessages.createAProjectButton) }}
|
||||
</button>
|
||||
<ButtonStyled color="brand">
|
||||
<button @click="$refs.modal_creation.show($event)">
|
||||
<PlusIcon />
|
||||
{{ formatMessage(commonMessages.createAProjectButton) }}
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<OrganizationProjectTransferModal
|
||||
:projects="usersOwnedProjects || []"
|
||||
@submit="onProjectTransferSubmit"
|
||||
@@ -175,14 +189,12 @@
|
||||
<template v-else>
|
||||
<p>You can edit multiple projects at once by selecting them below.</p>
|
||||
<div class="input-group">
|
||||
<button
|
||||
class="iconified-button"
|
||||
:disabled="selectedProjects.length === 0"
|
||||
@click="$refs.editLinksModal.show()"
|
||||
>
|
||||
<EditIcon />
|
||||
Edit links
|
||||
</button>
|
||||
<ButtonStyled>
|
||||
<button :disabled="selectedProjects.length === 0" @click="$refs.editLinksModal.show()">
|
||||
<EditIcon />
|
||||
Edit links
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<div class="push-right">
|
||||
<div class="labeled-control-row">
|
||||
Sort by
|
||||
@@ -193,14 +205,15 @@
|
||||
:options="sortOptions"
|
||||
@change="sortedProjects = updateSort(sortedProjects, sortBy, descending)"
|
||||
/>
|
||||
<button
|
||||
v-tooltip="descending ? 'Descending' : 'Ascending'"
|
||||
class="square-button"
|
||||
@click="updateDescending()"
|
||||
>
|
||||
<SortDescIcon v-if="descending" />
|
||||
<SortAscIcon v-else />
|
||||
</button>
|
||||
<ButtonStyled circular>
|
||||
<button
|
||||
v-tooltip="descending ? 'Descending' : 'Ascending'"
|
||||
@click="updateDescending()"
|
||||
>
|
||||
<SortDescIcon v-if="descending" />
|
||||
<SortAscIcon v-else />
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -38,24 +38,24 @@
|
||||
{{ calculateSavings(price.prices.intervals.monthly, price.prices.intervals.yearly) }}% with
|
||||
annual billing!
|
||||
</p>
|
||||
<nuxt-link
|
||||
<ButtonStyled
|
||||
v-if="auth.user && isPermission(auth.user.badges, 1 << 0)"
|
||||
to="/settings/billing"
|
||||
class="btn btn-purple btn-large"
|
||||
color="purple"
|
||||
size="large"
|
||||
>
|
||||
<SettingsIcon aria-hidden="true" />
|
||||
Manage subscription
|
||||
</nuxt-link>
|
||||
<button v-else-if="auth.user" class="btn btn-purple btn-large" @click="purchaseModal.show()">
|
||||
Subscribe
|
||||
</button>
|
||||
<nuxt-link
|
||||
v-else
|
||||
:to="`/auth/sign-in?redirect=${encodeURIComponent('/plus?showModal=true')}`"
|
||||
class="btn btn-purple btn-large"
|
||||
>
|
||||
Subscribe
|
||||
</nuxt-link>
|
||||
<nuxt-link to="/settings/billing">
|
||||
<SettingsIcon aria-hidden="true" />
|
||||
Manage subscription
|
||||
</nuxt-link>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled v-else-if="auth.user" color="purple" size="large">
|
||||
<button @click="purchaseModal.show()">Subscribe</button>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled v-else color="purple" size="large">
|
||||
<nuxt-link :to="`/auth/sign-in?redirect=${encodeURIComponent('/plus?showModal=true')}`">
|
||||
Subscribe
|
||||
</nuxt-link>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
</div>
|
||||
<div class="perks-hero">
|
||||
@@ -86,7 +86,12 @@
|
||||
</template>
|
||||
<script setup>
|
||||
import { HeartIcon, ModrinthPlusIcon, SettingsIcon, SparklesIcon, StarIcon } from '@modrinth/assets'
|
||||
import { injectNotificationManager, PurchaseModal, useFormatPrice } from '@modrinth/ui'
|
||||
import {
|
||||
ButtonStyled,
|
||||
injectNotificationManager,
|
||||
PurchaseModal,
|
||||
useFormatPrice,
|
||||
} from '@modrinth/ui'
|
||||
import { calculateSavings, getCurrency } from '@modrinth/utils'
|
||||
|
||||
import { useBaseFetch } from '@/composables/fetch.js'
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="page">
|
||||
<div class="experimental-styles-within flex flex-col gap-2">
|
||||
<div class="flex flex-col gap-2">
|
||||
<RadialHeader class="top-box mb-2 flex flex-col items-center justify-center" color="orange">
|
||||
<ScaleIcon class="h-12 w-12 text-brand-orange" />
|
||||
<h1 class="m-3 gap-2 text-3xl font-extrabold">
|
||||
|
||||
@@ -27,19 +27,18 @@
|
||||
@keyup.enter="saveEmail()"
|
||||
/>
|
||||
<div class="input-group push-right">
|
||||
<button class="iconified-button" @click="$refs.changeEmailModal.hide()">
|
||||
<XIcon />
|
||||
{{ formatMessage(commonMessages.cancelButton) }}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="iconified-button brand-button"
|
||||
:disabled="!email"
|
||||
@click="saveEmail()"
|
||||
>
|
||||
<SaveIcon />
|
||||
{{ formatMessage(messages.saveEmailButton) }}
|
||||
</button>
|
||||
<ButtonStyled>
|
||||
<button @click="$refs.changeEmailModal.hide()">
|
||||
<XIcon />
|
||||
{{ formatMessage(commonMessages.cancelButton) }}
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled color="brand">
|
||||
<button :disabled="!email" @click="saveEmail()">
|
||||
<SaveIcon />
|
||||
{{ formatMessage(messages.saveEmailButton) }}
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
@@ -108,44 +107,43 @@
|
||||
</template>
|
||||
<p></p>
|
||||
<div class="input-group push-right">
|
||||
<button class="iconified-button" @click="$refs.managePasswordModal.hide()">
|
||||
<XIcon />
|
||||
{{ formatMessage(commonMessages.cancelButton) }}
|
||||
</button>
|
||||
<template v-if="removePasswordMode">
|
||||
<button
|
||||
type="button"
|
||||
class="iconified-button danger-button"
|
||||
:disabled="!oldPassword"
|
||||
@click="savePassword"
|
||||
>
|
||||
<TrashIcon />
|
||||
{{ formatMessage(messages.removePasswordButton) }}
|
||||
<ButtonStyled>
|
||||
<button @click="$refs.managePasswordModal.hide()">
|
||||
<XIcon />
|
||||
{{ formatMessage(commonMessages.cancelButton) }}
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<template v-if="removePasswordMode">
|
||||
<ButtonStyled color="red">
|
||||
<button :disabled="!oldPassword" @click="savePassword">
|
||||
<TrashIcon />
|
||||
{{ formatMessage(messages.removePasswordButton) }}
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</template>
|
||||
<template v-else>
|
||||
<button
|
||||
<ButtonStyled
|
||||
v-if="auth.user.has_password && auth.user.auth_providers.length > 0"
|
||||
type="button"
|
||||
class="iconified-button danger-button"
|
||||
@click="removePasswordMode = true"
|
||||
color="red"
|
||||
>
|
||||
<TrashIcon />
|
||||
{{ formatMessage(messages.removePasswordButton) }}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="iconified-button brand-button"
|
||||
:disabled="
|
||||
newPassword.length == 0 ||
|
||||
(auth.user.has_password && oldPassword.length == 0) ||
|
||||
newPassword !== confirmNewPassword
|
||||
"
|
||||
@click="savePassword"
|
||||
>
|
||||
<SaveIcon />
|
||||
{{ formatMessage(messages.savePasswordButton) }}
|
||||
</button>
|
||||
<button @click="removePasswordMode = true">
|
||||
<TrashIcon />
|
||||
{{ formatMessage(messages.removePasswordButton) }}
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled color="brand">
|
||||
<button
|
||||
:disabled="
|
||||
newPassword.length == 0 ||
|
||||
(auth.user.has_password && oldPassword.length == 0) ||
|
||||
newPassword !== confirmNewPassword
|
||||
"
|
||||
@click="savePassword"
|
||||
>
|
||||
<SaveIcon />
|
||||
{{ formatMessage(messages.savePasswordButton) }}
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
@@ -173,14 +171,18 @@
|
||||
{{ formatMessage(messages.twoFactorIncorrectError) }}
|
||||
</p>
|
||||
<div class="input-group push-right">
|
||||
<button class="iconified-button" @click="$refs.manageTwoFactorModal.hide()">
|
||||
<XIcon />
|
||||
{{ formatMessage(commonMessages.cancelButton) }}
|
||||
</button>
|
||||
<button class="iconified-button danger-button" @click="removeTwoFactor">
|
||||
<TrashIcon />
|
||||
{{ formatMessage(messages.twoFactorRemoveButton) }}
|
||||
</button>
|
||||
<ButtonStyled>
|
||||
<button @click="$refs.manageTwoFactorModal.hide()">
|
||||
<XIcon />
|
||||
{{ formatMessage(commonMessages.cancelButton) }}
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled color="red">
|
||||
<button @click="removeTwoFactor">
|
||||
<TrashIcon />
|
||||
{{ formatMessage(messages.twoFactorRemoveButton) }}
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
@@ -247,34 +249,30 @@
|
||||
</ul>
|
||||
</template>
|
||||
<div class="input-group push-right">
|
||||
<button v-if="twoFactorStep === 1" class="iconified-button" @click="twoFactorStep = 0">
|
||||
<LeftArrowIcon />
|
||||
{{ formatMessage(commonMessages.backButton) }}
|
||||
</button>
|
||||
<button
|
||||
v-if="twoFactorStep !== 2"
|
||||
class="iconified-button"
|
||||
@click="$refs.manageTwoFactorModal.hide()"
|
||||
>
|
||||
<XIcon />
|
||||
{{ formatMessage(commonMessages.cancelButton) }}
|
||||
</button>
|
||||
<button
|
||||
v-if="twoFactorStep <= 1"
|
||||
class="iconified-button brand-button"
|
||||
@click="twoFactorStep === 1 ? verifyTwoFactorCode() : (twoFactorStep = 1)"
|
||||
>
|
||||
<RightArrowIcon />
|
||||
{{ formatMessage(commonMessages.continueButton) }}
|
||||
</button>
|
||||
<button
|
||||
v-if="twoFactorStep === 2"
|
||||
class="iconified-button brand-button"
|
||||
@click="$refs.manageTwoFactorModal.hide()"
|
||||
>
|
||||
<CheckIcon />
|
||||
{{ formatMessage(messages.completeSetupButton) }}
|
||||
</button>
|
||||
<ButtonStyled v-if="twoFactorStep === 1">
|
||||
<button @click="twoFactorStep = 0">
|
||||
<LeftArrowIcon />
|
||||
{{ formatMessage(commonMessages.backButton) }}
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled v-if="twoFactorStep !== 2">
|
||||
<button @click="$refs.manageTwoFactorModal.hide()">
|
||||
<XIcon />
|
||||
{{ formatMessage(commonMessages.cancelButton) }}
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled v-if="twoFactorStep <= 1" color="brand">
|
||||
<button @click="twoFactorStep === 1 ? verifyTwoFactorCode() : (twoFactorStep = 1)">
|
||||
<RightArrowIcon />
|
||||
{{ formatMessage(commonMessages.continueButton) }}
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled v-if="twoFactorStep === 2" color="brand">
|
||||
<button @click="$refs.manageTwoFactorModal.hide()">
|
||||
<CheckIcon />
|
||||
{{ formatMessage(messages.completeSetupButton) }}
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
@@ -295,29 +293,27 @@
|
||||
<span><component :is="provider.icon" /> {{ provider.display }}</span>
|
||||
</div>
|
||||
<div class="table-text manage table-cell">
|
||||
<button
|
||||
v-if="auth.user.auth_providers.includes(provider.id)"
|
||||
class="btn"
|
||||
@click="handleRemoveAuthProvider(provider.id)"
|
||||
>
|
||||
<TrashIcon /> {{ formatMessage(commonMessages.removeButton) }}
|
||||
</button>
|
||||
<a
|
||||
v-else
|
||||
class="btn"
|
||||
:href="`${getAuthUrl(provider.id, '/settings/account')}&token=${auth.token}`"
|
||||
>
|
||||
<ExternalIcon /> {{ formatMessage(messages.providerAddButton) }}
|
||||
</a>
|
||||
<ButtonStyled v-if="auth.user.auth_providers.includes(provider.id)">
|
||||
<button @click="handleRemoveAuthProvider(provider.id)">
|
||||
<TrashIcon /> {{ formatMessage(commonMessages.removeButton) }}
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled v-else>
|
||||
<a :href="`${getAuthUrl(provider.id, '/settings/account')}&token=${auth.token}`">
|
||||
<ExternalIcon /> {{ formatMessage(messages.providerAddButton) }}
|
||||
</a>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p></p>
|
||||
<div class="input-group push-right">
|
||||
<button class="iconified-button" @click="$refs.manageProvidersModal.hide()">
|
||||
<XIcon />
|
||||
{{ formatMessage(commonMessages.closeButton) }}
|
||||
</button>
|
||||
<ButtonStyled>
|
||||
<button @click="$refs.manageProvidersModal.hide()">
|
||||
<XIcon />
|
||||
{{ formatMessage(commonMessages.closeButton) }}
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
@@ -332,16 +328,18 @@
|
||||
}}</span>
|
||||
</label>
|
||||
<div>
|
||||
<button class="iconified-button" @click="$refs.changeEmailModal.show()">
|
||||
<template v-if="auth.user.email">
|
||||
<EditIcon />
|
||||
{{ formatMessage(messages.changeEmailButton) }}
|
||||
</template>
|
||||
<template v-else>
|
||||
<PlusIcon />
|
||||
{{ formatMessage(messages.addEmailButton) }}
|
||||
</template>
|
||||
</button>
|
||||
<ButtonStyled>
|
||||
<button @click="$refs.changeEmailModal.show()">
|
||||
<template v-if="auth.user.email">
|
||||
<EditIcon />
|
||||
{{ formatMessage(messages.changeEmailButton) }}
|
||||
</template>
|
||||
<template v-else>
|
||||
<PlusIcon />
|
||||
{{ formatMessage(messages.addEmailButton) }}
|
||||
</template>
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
</div>
|
||||
<div class="adjacent-input">
|
||||
@@ -359,24 +357,25 @@
|
||||
</span>
|
||||
</label>
|
||||
<div>
|
||||
<button
|
||||
class="iconified-button"
|
||||
@click="
|
||||
() => {
|
||||
oldPassword = ''
|
||||
newPassword = ''
|
||||
confirmNewPassword = ''
|
||||
removePasswordMode = false
|
||||
$refs.managePasswordModal.show()
|
||||
}
|
||||
"
|
||||
>
|
||||
<KeyIcon />
|
||||
<template v-if="auth.user.has_password">{{
|
||||
formatMessage(messages.changePasswordButton)
|
||||
}}</template>
|
||||
<template v-else> {{ formatMessage(messages.addPasswordButton) }} </template>
|
||||
</button>
|
||||
<ButtonStyled>
|
||||
<button
|
||||
@click="
|
||||
() => {
|
||||
oldPassword = ''
|
||||
newPassword = ''
|
||||
confirmNewPassword = ''
|
||||
removePasswordMode = false
|
||||
$refs.managePasswordModal.show()
|
||||
}
|
||||
"
|
||||
>
|
||||
<KeyIcon />
|
||||
<template v-if="auth.user.has_password">{{
|
||||
formatMessage(messages.changePasswordButton)
|
||||
}}</template>
|
||||
<template v-else> {{ formatMessage(messages.addPasswordButton) }} </template>
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
</div>
|
||||
<div class="adjacent-input">
|
||||
@@ -387,14 +386,16 @@
|
||||
}}</span>
|
||||
</label>
|
||||
<div>
|
||||
<button class="iconified-button" @click="showTwoFactorModal">
|
||||
<template v-if="auth.user.has_totp">
|
||||
<TrashIcon /> {{ formatMessage(messages.twoFactorRemoveButton) }}
|
||||
</template>
|
||||
<template v-else>
|
||||
<PlusIcon /> {{ formatMessage(messages.twoFactorSetupButton) }}
|
||||
</template>
|
||||
</button>
|
||||
<ButtonStyled>
|
||||
<button @click="showTwoFactorModal">
|
||||
<template v-if="auth.user.has_totp">
|
||||
<TrashIcon /> {{ formatMessage(messages.twoFactorRemoveButton) }}
|
||||
</template>
|
||||
<template v-else>
|
||||
<PlusIcon /> {{ formatMessage(messages.twoFactorSetupButton) }}
|
||||
</template>
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
</div>
|
||||
<div class="adjacent-input">
|
||||
@@ -405,9 +406,11 @@
|
||||
}}</span>
|
||||
</label>
|
||||
<div>
|
||||
<button class="iconified-button" @click="$refs.manageProvidersModal.show()">
|
||||
<SettingsIcon /> {{ formatMessage(messages.manageProvidersButton) }}
|
||||
</button>
|
||||
<ButtonStyled>
|
||||
<button @click="$refs.manageProvidersModal.show()">
|
||||
<SettingsIcon /> {{ formatMessage(messages.manageProvidersButton) }}
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -415,31 +418,33 @@
|
||||
<section id="data-export" class="universal-card">
|
||||
<h2>{{ formatMessage(messages.dataExportTitle) }}</h2>
|
||||
<p>{{ formatMessage(messages.dataExportDescription) }}</p>
|
||||
<a v-if="generated" class="iconified-button" :href="generated" download="export.json">
|
||||
<DownloadIcon />
|
||||
{{ formatMessage(messages.downloadExportButton) }}
|
||||
</a>
|
||||
<button v-else class="iconified-button" :disabled="generatingExport" @click="exportData">
|
||||
<template v-if="generatingExport">
|
||||
<UpdatedIcon /> {{ formatMessage(messages.generatingExportButton) }}
|
||||
</template>
|
||||
<template v-else>
|
||||
<UpdatedIcon /> {{ formatMessage(messages.generateExportButton) }}
|
||||
</template>
|
||||
</button>
|
||||
<ButtonStyled v-if="generated">
|
||||
<a :href="generated" download="export.json">
|
||||
<DownloadIcon />
|
||||
{{ formatMessage(messages.downloadExportButton) }}
|
||||
</a>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled v-else>
|
||||
<button :disabled="generatingExport" @click="exportData">
|
||||
<template v-if="generatingExport">
|
||||
<UpdatedIcon /> {{ formatMessage(messages.generatingExportButton) }}
|
||||
</template>
|
||||
<template v-else>
|
||||
<UpdatedIcon /> {{ formatMessage(messages.generateExportButton) }}
|
||||
</template>
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</section>
|
||||
|
||||
<section id="delete-account" class="universal-card">
|
||||
<h2>{{ formatMessage(messages.deleteAccountSectionTitle) }}</h2>
|
||||
<p>{{ formatMessage(messages.deleteAccountSectionDescription) }}</p>
|
||||
<button
|
||||
type="button"
|
||||
class="iconified-button danger-button"
|
||||
@click="$refs.modal_confirm.show()"
|
||||
>
|
||||
<TrashIcon />
|
||||
{{ formatMessage(messages.deleteAccountButton) }}
|
||||
</button>
|
||||
<ButtonStyled color="red">
|
||||
<button type="button" @click="$refs.modal_confirm.show()">
|
||||
<TrashIcon />
|
||||
{{ formatMessage(messages.deleteAccountButton) }}
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</section>
|
||||
</div>
|
||||
</template>
|
||||
@@ -460,6 +465,7 @@ import {
|
||||
XIcon,
|
||||
} from '@modrinth/assets'
|
||||
import {
|
||||
ButtonStyled,
|
||||
commonMessages,
|
||||
ConfirmModal,
|
||||
defineMessages,
|
||||
|
||||
@@ -24,15 +24,17 @@
|
||||
</label>
|
||||
<div v-if="editingId" class="icon-submission">
|
||||
<Avatar size="md" :src="icon" />
|
||||
<FileInput
|
||||
:max-size="262144"
|
||||
class="btn"
|
||||
:prompt="formatMessage(messages.uploadIcon)"
|
||||
accept="image/png,image/jpeg,image/gif,image/webp"
|
||||
@change="onImageSelection"
|
||||
>
|
||||
<UploadIcon />
|
||||
</FileInput>
|
||||
<ButtonStyled>
|
||||
<FileInput
|
||||
:max-size="262144"
|
||||
class="button-like"
|
||||
:prompt="formatMessage(messages.uploadIcon)"
|
||||
accept="image/png,image/jpeg,image/gif,image/webp"
|
||||
@change="onImageSelection"
|
||||
>
|
||||
<UploadIcon />
|
||||
</FileInput>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
<label v-if="editingId" for="app-url">
|
||||
<span class="label__title">{{ formatMessage(messages.urlLabel) }}</span>
|
||||
@@ -94,51 +96,46 @@
|
||||
autocomplete="off"
|
||||
:placeholder="formatMessage(messages.redirectUriPlaceholder)"
|
||||
/>
|
||||
<Button v-if="index !== 0" icon-only @click="() => redirectUris.splice(index, 1)">
|
||||
<TrashIcon />
|
||||
</Button>
|
||||
<Button
|
||||
v-if="index === 0"
|
||||
color="primary"
|
||||
icon-only
|
||||
@click="() => redirectUris.push('')"
|
||||
>
|
||||
<PlusIcon /> {{ formatMessage(messages.addMore) }}
|
||||
</Button>
|
||||
<ButtonStyled v-if="index !== 0" circular>
|
||||
<button @click="() => redirectUris.splice(index, 1)">
|
||||
<TrashIcon />
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled v-if="index === 0" color="brand">
|
||||
<button @click="() => redirectUris.push('')">
|
||||
<PlusIcon /> {{ formatMessage(messages.addMore) }}
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="redirectUris.length <= 0">
|
||||
<Button color="primary" icon-only @click="() => redirectUris.push('')">
|
||||
<PlusIcon /> {{ formatMessage(messages.addRedirectUri) }}
|
||||
</Button>
|
||||
<ButtonStyled color="brand">
|
||||
<button @click="() => redirectUris.push('')">
|
||||
<PlusIcon /> {{ formatMessage(messages.addRedirectUri) }}
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="submit-row input-group push-right">
|
||||
<button class="iconified-button" @click="$refs.appModal.hide()">
|
||||
<XIcon />
|
||||
{{ formatMessage(messages.cancel) }}
|
||||
</button>
|
||||
<button
|
||||
v-if="editingId"
|
||||
:disabled="!canSubmit"
|
||||
type="button"
|
||||
class="iconified-button brand-button"
|
||||
@click="editApp"
|
||||
>
|
||||
<SaveIcon />
|
||||
{{ formatMessage(messages.saveChanges) }}
|
||||
</button>
|
||||
<button
|
||||
v-else
|
||||
:disabled="!canSubmit"
|
||||
type="button"
|
||||
class="iconified-button brand-button"
|
||||
@click="createApp"
|
||||
>
|
||||
<PlusIcon />
|
||||
{{ formatMessage(messages.createApp) }}
|
||||
</button>
|
||||
<ButtonStyled>
|
||||
<button @click="$refs.appModal.hide()">
|
||||
<XIcon />
|
||||
{{ formatMessage(messages.cancel) }}
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled v-if="editingId" color="brand">
|
||||
<button :disabled="!canSubmit" @click="editApp">
|
||||
<SaveIcon />
|
||||
{{ formatMessage(messages.saveChanges) }}
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled v-else color="brand">
|
||||
<button :disabled="!canSubmit" @click="createApp">
|
||||
<PlusIcon />
|
||||
{{ formatMessage(messages.createApp) }}
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
@@ -147,22 +144,22 @@
|
||||
<div class="header__title">
|
||||
<h2 class="text-2xl">{{ formatMessage(commonSettingsMessages.applications) }}</h2>
|
||||
</div>
|
||||
<button
|
||||
class="btn btn-primary"
|
||||
@click="
|
||||
() => {
|
||||
name = null
|
||||
icon = null
|
||||
scopesVal = 0
|
||||
redirectUris = ['']
|
||||
editingId = null
|
||||
expires = null
|
||||
$refs.appModal.show()
|
||||
}
|
||||
"
|
||||
>
|
||||
<PlusIcon /> {{ formatMessage(messages.newApplication) }}
|
||||
</button>
|
||||
<ButtonStyled color="brand">
|
||||
<button
|
||||
@click="
|
||||
() => {
|
||||
name = null
|
||||
icon = null
|
||||
scopesVal = 0
|
||||
redirectUris = ['']
|
||||
editingId = null
|
||||
$refs.appModal.show()
|
||||
}
|
||||
"
|
||||
>
|
||||
<PlusIcon /> {{ formatMessage(messages.newApplication) }}
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
<p>
|
||||
<IntlFormatted :message-id="messages.descriptionIntro">
|
||||
@@ -210,34 +207,35 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<Button
|
||||
icon-only
|
||||
@click="
|
||||
() => {
|
||||
setForm({
|
||||
...app,
|
||||
redirect_uris: app.redirect_uris.map((u) => u.uri) || [],
|
||||
})
|
||||
$refs.appModal.show()
|
||||
}
|
||||
"
|
||||
>
|
||||
<EditIcon />
|
||||
{{ formatMessage(messages.edit) }}
|
||||
</Button>
|
||||
<Button
|
||||
color="danger"
|
||||
icon-only
|
||||
@click="
|
||||
() => {
|
||||
editingId = app.id
|
||||
$refs.modal_confirm.show()
|
||||
}
|
||||
"
|
||||
>
|
||||
<TrashIcon />
|
||||
{{ formatMessage(messages.delete) }}
|
||||
</Button>
|
||||
<ButtonStyled>
|
||||
<button
|
||||
@click="
|
||||
() => {
|
||||
setForm({
|
||||
...app,
|
||||
redirect_uris: app.redirect_uris.map((u) => u.uri) || [],
|
||||
})
|
||||
$refs.appModal.show()
|
||||
}
|
||||
"
|
||||
>
|
||||
<EditIcon />
|
||||
{{ formatMessage(messages.edit) }}
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled color="red">
|
||||
<button
|
||||
@click="
|
||||
() => {
|
||||
editingId = app.id
|
||||
$refs.modal_confirm.show()
|
||||
}
|
||||
"
|
||||
>
|
||||
<TrashIcon />
|
||||
{{ formatMessage(messages.delete) }}
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -246,7 +244,7 @@
|
||||
import { EditIcon, PlusIcon, SaveIcon, TrashIcon, UploadIcon, XIcon } from '@modrinth/assets'
|
||||
import {
|
||||
Avatar,
|
||||
Button,
|
||||
ButtonStyled,
|
||||
Checkbox,
|
||||
commonMessages,
|
||||
commonSettingsMessages,
|
||||
|
||||
@@ -69,19 +69,19 @@
|
||||
</div>
|
||||
|
||||
<div class="input-group">
|
||||
<Button
|
||||
color="danger"
|
||||
icon-only
|
||||
@click="
|
||||
() => {
|
||||
revokingId = authorization.app_id
|
||||
$refs.modal_confirm.show()
|
||||
}
|
||||
"
|
||||
>
|
||||
<TrashIcon />
|
||||
{{ formatMessage(messages.revokeAction) }}
|
||||
</Button>
|
||||
<ButtonStyled color="red">
|
||||
<button
|
||||
@click="
|
||||
() => {
|
||||
revokingId = authorization.app_id
|
||||
$refs.modal_confirm.show()
|
||||
}
|
||||
"
|
||||
>
|
||||
<TrashIcon />
|
||||
{{ formatMessage(messages.revokeAction) }}
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -90,7 +90,7 @@
|
||||
import { CheckIcon, TrashIcon } from '@modrinth/assets'
|
||||
import {
|
||||
Avatar,
|
||||
Button,
|
||||
ButtonStyled,
|
||||
commonMessages,
|
||||
commonSettingsMessages,
|
||||
ConfirmModal,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<ServersUpgradeModalWrapper ref="upgradeModal" />
|
||||
<ResubscribeModal ref="pyroResubscribeModal" @resubscribe="handlePyroResubscribeConfirm" />
|
||||
<section class="universal-card experimental-styles-within">
|
||||
<section class="universal-card">
|
||||
<h2>{{ formatMessage(messages.subscriptionTitle) }}</h2>
|
||||
<p>{{ formatMessage(messages.subscriptionDescription) }}</p>
|
||||
<div class="universal-card recessed">
|
||||
@@ -533,7 +533,7 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="universal-card experimental-styles-within">
|
||||
<section class="universal-card">
|
||||
<ConfirmModal
|
||||
ref="modal_confirm"
|
||||
:title="formatMessage(deleteModalMessages.title)"
|
||||
@@ -573,12 +573,16 @@
|
||||
<div class="header__title">
|
||||
<h2 class="text-2xl">{{ formatMessage(messages.paymentMethodTitle) }}</h2>
|
||||
</div>
|
||||
<nuxt-link class="btn" to="/settings/billing/charges">
|
||||
<HistoryIcon /> {{ formatMessage(messages.paymentMethodHistory) }}
|
||||
</nuxt-link>
|
||||
<button class="btn" @click="addPaymentMethod">
|
||||
<PlusIcon /> {{ formatMessage(messages.paymentMethodAdd) }}
|
||||
</button>
|
||||
<ButtonStyled>
|
||||
<nuxt-link to="/settings/billing/charges">
|
||||
<HistoryIcon /> {{ formatMessage(messages.paymentMethodHistory) }}
|
||||
</nuxt-link>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled>
|
||||
<button @click="addPaymentMethod">
|
||||
<PlusIcon /> {{ formatMessage(messages.paymentMethodAdd) }}
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
<div
|
||||
v-if="!paymentMethods || paymentMethods.length === 0"
|
||||
@@ -637,41 +641,43 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<OverflowMenu
|
||||
:dropdown-id="`${baseId}-payment-method-overflow-${index}`"
|
||||
class="btn icon-only transparent"
|
||||
:options="
|
||||
[
|
||||
{
|
||||
id: 'primary',
|
||||
action: () => editPaymentMethod(index, true),
|
||||
},
|
||||
{
|
||||
id: 'remove',
|
||||
action: () => {
|
||||
removePaymentMethodIndex = index
|
||||
$refs.modal_confirm.show()
|
||||
<ButtonStyled circular type="transparent">
|
||||
<OverflowMenu
|
||||
:dropdown-id="`${baseId}-payment-method-overflow-${index}`"
|
||||
class="btn-dropdown-animation !w-10"
|
||||
:options="
|
||||
[
|
||||
{
|
||||
id: 'primary',
|
||||
action: () => editPaymentMethod(index, true),
|
||||
},
|
||||
color: 'red',
|
||||
hoverOnly: true,
|
||||
},
|
||||
].slice(primaryPaymentMethodId === method.id ? 1 : 0, 2)
|
||||
"
|
||||
>
|
||||
<MoreVerticalIcon />
|
||||
<template #primary>
|
||||
<StarIcon />
|
||||
{{ formatMessage(messages.paymentMethodMakePrimary) }}
|
||||
</template>
|
||||
<template #edit>
|
||||
<EditIcon />
|
||||
{{ formatMessage(commonMessages.editButton) }}
|
||||
</template>
|
||||
<template #remove>
|
||||
<TrashIcon />
|
||||
{{ formatMessage(commonMessages.deleteLabel) }}
|
||||
</template>
|
||||
</OverflowMenu>
|
||||
{
|
||||
id: 'remove',
|
||||
action: () => {
|
||||
removePaymentMethodIndex = index
|
||||
$refs.modal_confirm.show()
|
||||
},
|
||||
color: 'red',
|
||||
hoverOnly: true,
|
||||
},
|
||||
].slice(primaryPaymentMethodId === method.id ? 1 : 0, 2)
|
||||
"
|
||||
>
|
||||
<MoreVerticalIcon />
|
||||
<template #primary>
|
||||
<StarIcon />
|
||||
{{ formatMessage(messages.paymentMethodMakePrimary) }}
|
||||
</template>
|
||||
<template #edit>
|
||||
<EditIcon />
|
||||
{{ formatMessage(commonMessages.editButton) }}
|
||||
</template>
|
||||
<template #remove>
|
||||
<TrashIcon />
|
||||
{{ formatMessage(commonMessages.deleteLabel) }}
|
||||
</template>
|
||||
</OverflowMenu>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -9,9 +9,11 @@
|
||||
</strong>
|
||||
</template>
|
||||
</IntlFormatted>
|
||||
<Button :action="() => disableDeveloperMode()">
|
||||
{{ formatMessage(developerModeBanner.deactivate) }}
|
||||
</Button>
|
||||
<ButtonStyled color="red" type="highlight">
|
||||
<button class="mt-3" @click="disableDeveloperMode()">
|
||||
{{ formatMessage(developerModeBanner.deactivate) }}
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</MessageBanner>
|
||||
<section class="universal-card">
|
||||
<h2 class="text-2xl">{{ formatMessage(colorTheme.title) }}</h2>
|
||||
@@ -178,7 +180,7 @@
|
||||
<script setup lang="ts">
|
||||
import { CodeIcon, RadioButtonCheckedIcon, RadioButtonIcon } from '@modrinth/assets'
|
||||
import {
|
||||
Button,
|
||||
ButtonStyled,
|
||||
defineMessages,
|
||||
injectNotificationManager,
|
||||
IntlFormatted,
|
||||
@@ -506,9 +508,5 @@ const listTypes = computed(() => {
|
||||
margin-bottom: 2px;
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
|
||||
.btn {
|
||||
margin-top: var(--gap-sm);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -66,31 +66,25 @@
|
||||
<p></p>
|
||||
</div>
|
||||
|
||||
<div class="input-group push-right">
|
||||
<button class="iconified-button" @click="$refs.patModal.hide()">
|
||||
<XIcon />
|
||||
{{ formatMessage(commonMessages.cancelButton) }}
|
||||
</button>
|
||||
<button
|
||||
v-if="editPatId !== null"
|
||||
:disabled="loading || !name || !expires"
|
||||
type="button"
|
||||
class="iconified-button brand-button"
|
||||
@click="editPat"
|
||||
>
|
||||
<SaveIcon />
|
||||
{{ formatMessage(commonMessages.saveChangesButton) }}
|
||||
</button>
|
||||
<button
|
||||
v-else
|
||||
:disabled="loading || !name || !expires"
|
||||
type="button"
|
||||
class="iconified-button brand-button"
|
||||
@click="createPat"
|
||||
>
|
||||
<PlusIcon />
|
||||
{{ formatMessage(createModalMessages.action) }}
|
||||
</button>
|
||||
<div class="ml-auto flex gap-2">
|
||||
<ButtonStyled type="outlined">
|
||||
<button @click="$refs.patModal.hide()">
|
||||
<XIcon />
|
||||
{{ formatMessage(commonMessages.cancelButton) }}
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled v-if="editPatId !== null" color="brand">
|
||||
<button :disabled="loading || !name || !expires" @click="editPat">
|
||||
<SaveIcon />
|
||||
{{ formatMessage(commonMessages.saveChangesButton) }}
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled v-else color="brand">
|
||||
<button :disabled="loading || !name || !expires" @click="createPat">
|
||||
<PlusIcon />
|
||||
{{ formatMessage(createModalMessages.action) }}
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
</div>
|
||||
</NewModal>
|
||||
@@ -99,20 +93,21 @@
|
||||
<div class="header__title">
|
||||
<h2 class="text-2xl">{{ formatMessage(commonSettingsMessages.pats) }}</h2>
|
||||
</div>
|
||||
<button
|
||||
class="btn btn-primary"
|
||||
@click="
|
||||
() => {
|
||||
name = null
|
||||
scopesVal = 0
|
||||
expires = null
|
||||
editPatId = null
|
||||
$refs.patModal.show()
|
||||
}
|
||||
"
|
||||
>
|
||||
<PlusIcon /> {{ formatMessage(messages.create) }}
|
||||
</button>
|
||||
<ButtonStyled color="brand">
|
||||
<button
|
||||
@click="
|
||||
() => {
|
||||
name = null
|
||||
scopesVal = 0
|
||||
expires = null
|
||||
editPatId = null
|
||||
$refs.patModal.show()
|
||||
}
|
||||
"
|
||||
>
|
||||
<PlusIcon /> {{ formatMessage(messages.create) }}
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
<p>
|
||||
<IntlFormatted :message-id="messages.description">
|
||||
@@ -172,31 +167,33 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="token-actions ml-auto flex flex-col gap-2">
|
||||
<button
|
||||
class="iconified-button raised-button"
|
||||
@click="
|
||||
() => {
|
||||
editPatId = pat.id
|
||||
name = pat.name
|
||||
scopesVal = pat.scopes
|
||||
expires = $dayjs(pat.expires).format('YYYY-MM-DD')
|
||||
$refs.patModal.show()
|
||||
}
|
||||
"
|
||||
>
|
||||
<EditIcon /> {{ formatMessage(tokenMessages.edit) }}
|
||||
</button>
|
||||
<button
|
||||
class="iconified-button raised-button"
|
||||
@click="
|
||||
() => {
|
||||
deletePatIndex = pat.id
|
||||
$refs.modal_confirm.show()
|
||||
}
|
||||
"
|
||||
>
|
||||
<TrashIcon /> {{ formatMessage(tokenMessages.revoke) }}
|
||||
</button>
|
||||
<ButtonStyled>
|
||||
<button
|
||||
@click="
|
||||
() => {
|
||||
editPatId = pat.id
|
||||
name = pat.name
|
||||
scopesVal = pat.scopes
|
||||
expires = $dayjs(pat.expires).format('YYYY-MM-DD')
|
||||
$refs.patModal.show()
|
||||
}
|
||||
"
|
||||
>
|
||||
<EditIcon /> {{ formatMessage(tokenMessages.edit) }}
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled>
|
||||
<button
|
||||
@click="
|
||||
() => {
|
||||
deletePatIndex = pat.id
|
||||
$refs.modal_confirm.show()
|
||||
}
|
||||
"
|
||||
>
|
||||
<TrashIcon /> {{ formatMessage(tokenMessages.revoke) }}
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -204,6 +201,7 @@
|
||||
<script setup>
|
||||
import { EditIcon, PlusIcon, SaveIcon, TrashIcon, XIcon } from '@modrinth/assets'
|
||||
import {
|
||||
ButtonStyled,
|
||||
Checkbox,
|
||||
commonMessages,
|
||||
commonSettingsMessages,
|
||||
|
||||
@@ -21,33 +21,38 @@
|
||||
circle
|
||||
:alt="auth.user.username"
|
||||
/>
|
||||
<div class="input-stack">
|
||||
<FileInput
|
||||
:max-size="262144"
|
||||
:show-icon="true"
|
||||
class="btn"
|
||||
:prompt="formatMessage(commonMessages.uploadImageButton)"
|
||||
accept="image/png,image/jpeg,image/gif,image/webp"
|
||||
@change="showPreviewImage"
|
||||
>
|
||||
<UploadIcon />
|
||||
</FileInput>
|
||||
<Button v-if="avatarUrl !== null" :action="removePreviewImage">
|
||||
<TrashIcon />
|
||||
{{ formatMessage(commonMessages.removeImageButton) }}
|
||||
</Button>
|
||||
<Button
|
||||
v-if="previewImage"
|
||||
:action="
|
||||
() => {
|
||||
icon = null
|
||||
previewImage = null
|
||||
}
|
||||
"
|
||||
>
|
||||
<UndoIcon />
|
||||
{{ formatMessage(commonMessages.resetButton) }}
|
||||
</Button>
|
||||
<div class="flex flex-col gap-2">
|
||||
<ButtonStyled>
|
||||
<FileInput
|
||||
:max-size="262144"
|
||||
:show-icon="true"
|
||||
class="button-like"
|
||||
:prompt="formatMessage(commonMessages.uploadImageButton)"
|
||||
accept="image/png,image/jpeg,image/gif,image/webp"
|
||||
@change="showPreviewImage"
|
||||
>
|
||||
<UploadIcon />
|
||||
</FileInput>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled v-if="avatarUrl !== null">
|
||||
<button @click="removePreviewImage">
|
||||
<TrashIcon />
|
||||
{{ formatMessage(commonMessages.removeImageButton) }}
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled v-if="previewImage">
|
||||
<button
|
||||
@click="
|
||||
() => {
|
||||
icon = null
|
||||
previewImage = null
|
||||
}
|
||||
"
|
||||
>
|
||||
<UndoIcon />
|
||||
{{ formatMessage(commonMessages.resetButton) }}
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
</div>
|
||||
<label for="username-field">
|
||||
@@ -65,9 +70,11 @@
|
||||
</label>
|
||||
<StyledInput id="bio-field" v-model="current.bio" multiline />
|
||||
<div class="input-group mt-4">
|
||||
<Button :link="`/user/${auth.user.username}`">
|
||||
<UserIcon /> {{ formatMessage(commonMessages.visitYourProfile) }}
|
||||
</Button>
|
||||
<ButtonStyled>
|
||||
<NuxtLink :to="`/user/${auth.user.username}`">
|
||||
<UserIcon /> {{ formatMessage(commonMessages.visitYourProfile) }}
|
||||
</NuxtLink>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
</section>
|
||||
<UnsavedChangesPopup
|
||||
@@ -84,7 +91,7 @@
|
||||
import { TrashIcon, UndoIcon, UploadIcon, UserIcon } from '@modrinth/assets'
|
||||
import {
|
||||
Avatar,
|
||||
Button,
|
||||
ButtonStyled,
|
||||
commonMessages,
|
||||
defineMessages,
|
||||
FileInput,
|
||||
|
||||
@@ -34,9 +34,11 @@
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<i v-if="session.current">{{ formatMessage(messages.currentSessionLabel) }}</i>
|
||||
<button v-else class="iconified-button raised-button" @click="revokeSession(session.id)">
|
||||
<XIcon /> {{ formatMessage(messages.revokeSessionButton) }}
|
||||
</button>
|
||||
<ButtonStyled v-else>
|
||||
<button @click="revokeSession(session.id)">
|
||||
<XIcon /> {{ formatMessage(messages.revokeSessionButton) }}
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -44,6 +46,7 @@
|
||||
<script setup>
|
||||
import { XIcon } from '@modrinth/assets'
|
||||
import {
|
||||
ButtonStyled,
|
||||
commonMessages,
|
||||
commonSettingsMessages,
|
||||
defineMessages,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div v-if="user" class="experimental-styles-within">
|
||||
<div v-if="user">
|
||||
<ModalCreation ref="modal_creation" />
|
||||
<CollectionCreateModal ref="modal_collection_creation" />
|
||||
<NewModal ref="editRoleModal" header="Edit role">
|
||||
|
||||
@@ -41,10 +41,6 @@
|
||||
.universal-body {
|
||||
@extend .universal-labels;
|
||||
|
||||
.multiselect {
|
||||
width: 15rem;
|
||||
}
|
||||
|
||||
> :where(input + *, .input-group + *, .chips + *, .input-div + *) {
|
||||
margin-block-start: var(--gap-md);
|
||||
}
|
||||
@@ -162,10 +158,6 @@
|
||||
max-width: 100%;
|
||||
align-items: center;
|
||||
|
||||
.multiselect {
|
||||
width: 15rem;
|
||||
}
|
||||
|
||||
input {
|
||||
flex-shrink: 2;
|
||||
}
|
||||
@@ -189,20 +181,6 @@
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.input-stack {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
> * {
|
||||
margin-bottom: var(--gap-md);
|
||||
}
|
||||
|
||||
> .multiselect {
|
||||
width: unset;
|
||||
height: inherit;
|
||||
}
|
||||
}
|
||||
|
||||
.standard-body {
|
||||
:last-child {
|
||||
margin-bottom: 0;
|
||||
@@ -1020,130 +998,6 @@ select {
|
||||
filter: brightness(1.2);
|
||||
}
|
||||
|
||||
.multiselect {
|
||||
color: var(--color-base) !important;
|
||||
outline: 2px solid transparent;
|
||||
width: 100% !important;
|
||||
|
||||
.multiselect__input:focus-visible {
|
||||
outline: none !important;
|
||||
box-shadow: none !important;
|
||||
padding: 0 !important;
|
||||
min-height: 0 !important;
|
||||
font-weight: normal !important;
|
||||
margin-left: 0.5rem;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
input {
|
||||
background: transparent;
|
||||
box-shadow: none;
|
||||
border: none !important;
|
||||
|
||||
&:focus {
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
|
||||
input::placeholder {
|
||||
color: var(--color-base);
|
||||
}
|
||||
|
||||
.multiselect__tags {
|
||||
border-radius: var(--radius-md);
|
||||
background: var(--color-button-bg);
|
||||
box-shadow: var(--shadow-inset-sm);
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
padding-left: 7px;
|
||||
padding-top: 10px;
|
||||
font-size: 1rem;
|
||||
|
||||
transition: background-color 0.1s ease-in-out;
|
||||
|
||||
&:active {
|
||||
filter: brightness(1.25);
|
||||
|
||||
.multiselect__spinner {
|
||||
filter: brightness(1.25);
|
||||
}
|
||||
}
|
||||
|
||||
.multiselect__single {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.multiselect__tag {
|
||||
border-radius: var(--radius-md);
|
||||
color: var(--color-base);
|
||||
background: transparent;
|
||||
border: 2px solid var(--color-brand);
|
||||
}
|
||||
|
||||
.multiselect__tag-icon {
|
||||
background: transparent;
|
||||
|
||||
&:after {
|
||||
color: var(--color-contrast);
|
||||
}
|
||||
}
|
||||
|
||||
.multiselect__placeholder {
|
||||
color: var(--color-base);
|
||||
margin-left: 0.5rem;
|
||||
margin-bottom: 8px;
|
||||
opacity: 0.6;
|
||||
font-size: 1rem;
|
||||
line-height: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.multiselect__content-wrapper {
|
||||
background: var(--color-button-bg);
|
||||
border: none;
|
||||
overflow-x: hidden;
|
||||
box-shadow: var(--shadow-inset-sm), var(--shadow-floating);
|
||||
width: 100%;
|
||||
|
||||
.multiselect__element {
|
||||
.multiselect__option--highlight {
|
||||
background: var(--color-button-bg);
|
||||
filter: brightness(1.25);
|
||||
color: var(--color-contrast);
|
||||
}
|
||||
|
||||
.multiselect__option--selected {
|
||||
background: var(--color-brand);
|
||||
font-weight: bold;
|
||||
color: var(--color-accent-contrast);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.multiselect__spinner {
|
||||
background: var(--color-button-bg);
|
||||
|
||||
&:active {
|
||||
filter: brightness(1.25);
|
||||
}
|
||||
}
|
||||
|
||||
&.multiselect--disabled {
|
||||
background: none;
|
||||
|
||||
.multiselect__current,
|
||||
.multiselect__select {
|
||||
background: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.multiselect--above .multiselect__content-wrapper {
|
||||
border-top: none !important;
|
||||
border-top-left-radius: var(--radius-md) !important;
|
||||
border-top-right-radius: var(--radius-md) !important;
|
||||
}
|
||||
|
||||
.preview-radio {
|
||||
width: 100% !important;
|
||||
border-radius: var(--radius-md);
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<img
|
||||
v-if="src && !failed"
|
||||
ref="img"
|
||||
class="`experimental-styles-within avatar shrink-0"
|
||||
class="avatar shrink-0"
|
||||
:style="`--_size: ${cssSize}`"
|
||||
:class="{
|
||||
circle: circle,
|
||||
@@ -18,7 +18,7 @@
|
||||
/>
|
||||
<svg
|
||||
v-else
|
||||
class="`experimental-styles-within avatar shrink-0"
|
||||
class="avatar shrink-0"
|
||||
:style="`--_size: ${cssSize}${tint ? `;--_tint:oklch(50% 75% ${tint})` : ''}`"
|
||||
:class="{
|
||||
tint: tint,
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import { DropdownIcon } from '@modrinth/assets'
|
||||
import { reactive } from 'vue'
|
||||
|
||||
import Button from './Button.vue'
|
||||
import ButtonStyled from './ButtonStyled.vue'
|
||||
|
||||
const props = defineProps({
|
||||
collapsible: {
|
||||
@@ -33,9 +33,11 @@ function toggleCollapsed() {
|
||||
<div v-if="!!$slots.header || collapsible" class="header">
|
||||
<slot name="header"></slot>
|
||||
<div v-if="collapsible" class="btn-group">
|
||||
<Button :action="toggleCollapsed">
|
||||
<DropdownIcon :style="{ transform: `rotate(${state.collapsed ? 0 : 180}deg)` }" />
|
||||
</Button>
|
||||
<ButtonStyled circular>
|
||||
<button @click="toggleCollapsed">
|
||||
<DropdownIcon :style="{ transform: `rotate(${state.collapsed ? 0 : 180}deg)` }" />
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
</div>
|
||||
<slot v-if="!state.collapsed" />
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user