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:
Prospector
2026-05-03 11:53:06 -07:00
committed by GitHub
parent 8a72ee9968
commit 7dbbbe590f
153 changed files with 2596 additions and 3817 deletions

View File

@@ -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">

View File

@@ -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>

View File

@@ -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>

View File

@@ -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'

View File

@@ -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 {

View File

@@ -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">

View File

@@ -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>

View File

@@ -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'

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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">

View File

@@ -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,

View File

@@ -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>

View File

@@ -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>

View File

@@ -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"
},

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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'

View File

@@ -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'

View File

@@ -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

View File

@@ -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);
}
}

View File

@@ -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: {

View File

@@ -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]"

View File

@@ -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>

View File

@@ -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"

View File

@@ -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

View File

@@ -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>

View File

@@ -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>

View File

@@ -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;
}

View File

@@ -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) }}

View File

@@ -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) }}

View File

@@ -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) }}

View File

@@ -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>

View File

@@ -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" />

View File

@@ -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>

View File

@@ -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')"
>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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,

View File

@@ -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,

View File

@@ -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>

View File

@@ -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;

View File

@@ -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);

View File

@@ -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,

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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,

View File

@@ -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"

View File

@@ -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,

View File

@@ -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,

View File

@@ -1,5 +1,5 @@
<template>
<section class="experimental-styles-within overflow-visible">
<section class="overflow-visible">
<!-- Loading state -->
<div
v-if="versionsLoading && !versions?.length"

View File

@@ -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"
>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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"
>

View File

@@ -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

View File

@@ -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;
}
}

View File

@@ -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,

View File

@@ -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,

View File

@@ -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,

View File

@@ -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,

View File

@@ -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>

View File

@@ -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,

View File

@@ -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>

View File

@@ -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,

View File

@@ -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);

View File

@@ -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,

View File

@@ -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'

View File

@@ -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>

View File

@@ -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) }}

View File

@@ -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>

View File

@@ -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">

View File

@@ -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"

View File

@@ -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>

View File

@@ -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 />

View File

@@ -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>

View File

@@ -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">

View File

@@ -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"

View File

@@ -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,

View File

@@ -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>

View File

@@ -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'

View File

@@ -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">

View File

@@ -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,

View File

@@ -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,

View File

@@ -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,

View File

@@ -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>

View File

@@ -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>

View File

@@ -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,

View File

@@ -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,

View File

@@ -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,

View File

@@ -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">

View File

@@ -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);

View File

@@ -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,

View File

@@ -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