Compare commits
34
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ee9d4fad33 | ||
|
|
140b4060e6 | ||
|
|
6f03fae233 | ||
|
|
22fc0c994d | ||
|
|
a1ccbc5757 | ||
|
|
053cf10198 | ||
|
|
257efd8ad7 | ||
|
|
b75cfc063b | ||
|
|
2d8420131d | ||
|
|
c793b68aed | ||
|
|
47af459f24 | ||
|
|
f10e0f2bf1 | ||
|
|
569d60cb57 | ||
|
|
74d36a6a2d | ||
|
|
ced073d26c | ||
|
|
cc34e69524 | ||
|
|
d4864deac5 | ||
|
|
125207880d | ||
|
|
a8f17f40f5 | ||
|
|
dbde3c4669 | ||
|
|
ba4fecb0cb | ||
|
|
ef04dcc37b | ||
|
|
65126b3a23 | ||
|
|
58495e6276 | ||
|
|
706976439d | ||
|
|
0a9ffd3dc8 | ||
|
|
fb30c0ba2b | ||
|
|
97e4d8e132 | ||
|
|
5bdff3929b | ||
|
|
a08562bfe2 | ||
|
|
2b4319ea55 | ||
|
|
c32405720d | ||
|
|
b9ba3cd3e8 | ||
|
|
31381c860b |
No files matched your search
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
# Windows has stack overflows when calling from Tauri, so we increase the default stack size used by the compiler
|
# Windows has stack overflows when calling from Tauri, so we increase the default stack size used by the compiler
|
||||||
[target.'cfg(windows)']
|
[target.'cfg(windows)']
|
||||||
rustflags = ["-C", "link-args=/STACK:16777220"]
|
rustflags = ["-C", "link-args=/STACK:16777220", "--cfg", "tokio_unstable"]
|
||||||
|
|
||||||
[build]
|
[build]
|
||||||
rustflags = ["--cfg", "tokio_unstable"]
|
rustflags = ["--cfg", "tokio_unstable"]
|
||||||
+1
-1
@@ -14,5 +14,5 @@ max_line_length = 100
|
|||||||
max_line_length = off
|
max_line_length = off
|
||||||
trim_trailing_whitespace = false
|
trim_trailing_whitespace = false
|
||||||
|
|
||||||
[*.rs]
|
[*.{rs,java,kts}]
|
||||||
indent_size = 4
|
indent_size = 4
|
||||||
@@ -9,14 +9,18 @@ on:
|
|||||||
- .github/workflows/theseus-release.yml
|
- .github/workflows/theseus-release.yml
|
||||||
- 'apps/app/**'
|
- 'apps/app/**'
|
||||||
- 'apps/app-frontend/**'
|
- 'apps/app-frontend/**'
|
||||||
- 'apps/labrinth/src/common/**'
|
|
||||||
- 'apps/labrinth/Cargo.toml'
|
|
||||||
- 'packages/app-lib/**'
|
- 'packages/app-lib/**'
|
||||||
- 'packages/app-macros/**'
|
- 'packages/app-macros/**'
|
||||||
- 'packages/assets/**'
|
- 'packages/assets/**'
|
||||||
- 'packages/ui/**'
|
- 'packages/ui/**'
|
||||||
- 'packages/utils/**'
|
- 'packages/utils/**'
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
sign-windows-binaries:
|
||||||
|
description: Sign Windows binaries
|
||||||
|
type: boolean
|
||||||
|
default: true
|
||||||
|
required: false
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
@@ -103,11 +107,21 @@ jobs:
|
|||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
sudo apt-get install -y libwebkit2gtk-4.1-dev build-essential curl wget file libxdo-dev libssl-dev pkg-config libayatana-appindicator3-dev librsvg2-dev
|
sudo apt-get install -y libwebkit2gtk-4.1-dev build-essential curl wget file libxdo-dev libssl-dev pkg-config libayatana-appindicator3-dev librsvg2-dev
|
||||||
|
|
||||||
|
- name: Install code signing client (Windows only)
|
||||||
|
if: startsWith(matrix.platform, 'windows')
|
||||||
|
run: choco install jsign --ignore-dependencies # GitHub runners come with a global Java installation already
|
||||||
|
|
||||||
- name: Install frontend dependencies
|
- name: Install frontend dependencies
|
||||||
run: pnpm install
|
run: pnpm install
|
||||||
|
|
||||||
|
- name: Disable Windows code signing for non-final release builds
|
||||||
|
if: ${{ startsWith(matrix.platform, 'windows') && !startsWith(github.ref, 'refs/tags/v') && !inputs.sign-windows-binaries }}
|
||||||
|
run: |
|
||||||
|
jq 'del(.bundle.windows.signCommand)' apps/app/tauri-release.conf.json > apps/app/tauri-release.conf.json.new
|
||||||
|
Move-Item -Path apps/app/tauri-release.conf.json.new -Destination apps/app/tauri-release.conf.json -Force
|
||||||
|
|
||||||
- name: build app (macos)
|
- name: build app (macos)
|
||||||
run: pnpm --filter=@modrinth/app run tauri build --target universal-apple-darwin --config "tauri-release.conf.json"
|
run: pnpm --filter=@modrinth/app run tauri build --target universal-apple-darwin --config tauri-release.conf.json
|
||||||
if: startsWith(matrix.platform, 'macos')
|
if: startsWith(matrix.platform, 'macos')
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
@@ -121,15 +135,30 @@ jobs:
|
|||||||
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
|
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
|
||||||
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
|
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
|
||||||
|
|
||||||
- name: build app
|
- name: build app (Linux)
|
||||||
run: pnpm --filter=@modrinth/app run tauri build --config "tauri-release.conf.json"
|
run: pnpm --filter=@modrinth/app run tauri build --config tauri-release.conf.json
|
||||||
id: build_os
|
if: startsWith(matrix.platform, 'ubuntu')
|
||||||
if: "!startsWith(matrix.platform, 'macos')"
|
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
|
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
|
||||||
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
|
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
|
||||||
|
|
||||||
|
- name: build app (Windows)
|
||||||
|
run: |
|
||||||
|
[System.Convert]::FromBase64String("$env:DIGICERT_ONE_SIGNER_CLIENT_CERTIFICATE_BASE64") | Set-Content -Path signer-client-cert.p12 -AsByteStream
|
||||||
|
$env:DIGICERT_ONE_SIGNER_CREDENTIALS = "$env:DIGICERT_ONE_SIGNER_API_KEY|$PWD\signer-client-cert.p12|$env:DIGICERT_ONE_SIGNER_CLIENT_CERTIFICATE_PASSWORD"
|
||||||
|
$env:JAVA_HOME = "$env:JAVA_HOME_11_X64"
|
||||||
|
pnpm --filter=@modrinth/app run tauri build --config tauri-release.conf.json --verbose --bundles 'nsis,updater'
|
||||||
|
Remove-Item -Path signer-client-cert.p12
|
||||||
|
if: startsWith(matrix.platform, 'windows')
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
|
||||||
|
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
|
||||||
|
DIGICERT_ONE_SIGNER_API_KEY: ${{ secrets.DIGICERT_ONE_SIGNER_API_KEY }}
|
||||||
|
DIGICERT_ONE_SIGNER_CLIENT_CERTIFICATE_BASE64: ${{ secrets.DIGICERT_ONE_SIGNER_CLIENT_CERTIFICATE_BASE64 }}
|
||||||
|
DIGICERT_ONE_SIGNER_CLIENT_CERTIFICATE_PASSWORD: ${{ secrets.DIGICERT_ONE_SIGNER_CLIENT_CERTIFICATE_PASSWORD }}
|
||||||
|
|
||||||
- name: upload ${{ matrix.platform }}
|
- name: upload ${{ matrix.platform }}
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
|
|||||||
Generated
+1
-1
@@ -8889,7 +8889,7 @@ dependencies = [
|
|||||||
"flate2",
|
"flate2",
|
||||||
"fs4",
|
"fs4",
|
||||||
"futures",
|
"futures",
|
||||||
"heck 0.5.0",
|
"hashlink",
|
||||||
"hickory-resolver",
|
"hickory-resolver",
|
||||||
"indicatif",
|
"indicatif",
|
||||||
"notify",
|
"notify",
|
||||||
|
|||||||
+1
-1
@@ -60,7 +60,7 @@ flate2 = "1.1.2"
|
|||||||
fs4 = { version = "0.13.1", default-features = false }
|
fs4 = { version = "0.13.1", default-features = false }
|
||||||
futures = { version = "0.3.31", default-features = false }
|
futures = { version = "0.3.31", default-features = false }
|
||||||
futures-util = "0.3.31"
|
futures-util = "0.3.31"
|
||||||
heck = "0.5.0"
|
hashlink = "0.10.0"
|
||||||
hex = "0.4.3"
|
hex = "0.4.3"
|
||||||
hickory-resolver = "0.25.2"
|
hickory-resolver = "0.25.2"
|
||||||
hmac = "0.12.1"
|
hmac = "0.12.1"
|
||||||
|
|||||||
@@ -10,12 +10,12 @@
|
|||||||
size="36px"
|
size="36px"
|
||||||
:src="
|
:src="
|
||||||
selectedAccount
|
selectedAccount
|
||||||
? `https://mc-heads.net/avatar/${selectedAccount.profile.id}/128`
|
? `https://mc-heads.net/avatar/${selectedAccount.id}/128`
|
||||||
: 'https://launcher-files.modrinth.com/assets/steve_head.png'
|
: 'https://launcher-files.modrinth.com/assets/steve_head.png'
|
||||||
"
|
"
|
||||||
/>
|
/>
|
||||||
<div class="flex flex-col w-full">
|
<div class="flex flex-col w-full">
|
||||||
<span>{{ selectedAccount ? selectedAccount.profile.name : 'Select account' }}</span>
|
<span>{{ selectedAccount ? selectedAccount.username : 'Select account' }}</span>
|
||||||
<span class="text-secondary text-xs">Minecraft account</span>
|
<span class="text-secondary text-xs">Minecraft account</span>
|
||||||
</div>
|
</div>
|
||||||
<DropdownIcon class="w-5 h-5 shrink-0" />
|
<DropdownIcon class="w-5 h-5 shrink-0" />
|
||||||
@@ -28,17 +28,12 @@
|
|||||||
:class="{ expanded: mode === 'expanded', isolated: mode === 'isolated' }"
|
:class="{ expanded: mode === 'expanded', isolated: mode === 'isolated' }"
|
||||||
>
|
>
|
||||||
<div v-if="selectedAccount" class="selected account">
|
<div v-if="selectedAccount" class="selected account">
|
||||||
<Avatar size="xs" :src="`https://mc-heads.net/avatar/${selectedAccount.profile.id}/128`" />
|
<Avatar size="xs" :src="`https://mc-heads.net/avatar/${selectedAccount.id}/128`" />
|
||||||
<div>
|
<div>
|
||||||
<h4>{{ selectedAccount.profile.name }}</h4>
|
<h4>{{ selectedAccount.username }}</h4>
|
||||||
<p>Selected</p>
|
<p>Selected</p>
|
||||||
</div>
|
</div>
|
||||||
<Button
|
<Button v-tooltip="'Log out'" icon-only color="raised" @click="logout(selectedAccount.id)">
|
||||||
v-tooltip="'Log out'"
|
|
||||||
icon-only
|
|
||||||
color="raised"
|
|
||||||
@click="logout(selectedAccount.profile.id)"
|
|
||||||
>
|
|
||||||
<TrashIcon />
|
<TrashIcon />
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
@@ -49,12 +44,12 @@
|
|||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="displayAccounts.length > 0" class="account-group">
|
<div v-if="displayAccounts.length > 0" class="account-group">
|
||||||
<div v-for="account in displayAccounts" :key="account.profile.id" class="account-row">
|
<div v-for="account in displayAccounts" :key="account.id" class="account-row">
|
||||||
<Button class="option account" @click="setAccount(account)">
|
<Button class="option account" @click="setAccount(account)">
|
||||||
<Avatar :src="`https://mc-heads.net/avatar/${account.profile.id}/128`" class="icon" />
|
<Avatar :src="`https://mc-heads.net/avatar/${account.id}/128`" class="icon" />
|
||||||
<p>{{ account.profile.name }}</p>
|
<p>{{ account.username }}</p>
|
||||||
</Button>
|
</Button>
|
||||||
<Button v-tooltip="'Log out'" icon-only @click="logout(account.profile.id)">
|
<Button v-tooltip="'Log out'" icon-only @click="logout(account.id)">
|
||||||
<TrashIcon />
|
<TrashIcon />
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
@@ -106,16 +101,16 @@ defineExpose({
|
|||||||
await refreshValues()
|
await refreshValues()
|
||||||
|
|
||||||
const displayAccounts = computed(() =>
|
const displayAccounts = computed(() =>
|
||||||
accounts.value.filter((account) => defaultUser.value !== account.profile.id),
|
accounts.value.filter((account) => defaultUser.value !== account.id),
|
||||||
)
|
)
|
||||||
|
|
||||||
const selectedAccount = computed(() =>
|
const selectedAccount = computed(() =>
|
||||||
accounts.value.find((account) => account.profile.id === defaultUser.value),
|
accounts.value.find((account) => account.id === defaultUser.value),
|
||||||
)
|
)
|
||||||
|
|
||||||
async function setAccount(account) {
|
async function setAccount(account) {
|
||||||
defaultUser.value = account.profile.id
|
defaultUser.value = account.id
|
||||||
await set_default_user(account.profile.id).catch(handleError)
|
await set_default_user(account.id).catch(handleError)
|
||||||
emit('change')
|
emit('change')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ async function loginMinecraft() {
|
|||||||
const loggedIn = await login_flow()
|
const loggedIn = await login_flow()
|
||||||
|
|
||||||
if (loggedIn) {
|
if (loggedIn) {
|
||||||
await set_default_user(loggedIn.profile.id).catch(handleError)
|
await set_default_user(loggedIn.id).catch(handleError)
|
||||||
}
|
}
|
||||||
|
|
||||||
await trackEvent('AccountLogIn', { source: 'ErrorModal' })
|
await trackEvent('AccountLogIn', { source: 'ErrorModal' })
|
||||||
|
|||||||
@@ -127,7 +127,7 @@ async function handleJavaFileInput() {
|
|||||||
const filePath = await open()
|
const filePath = await open()
|
||||||
|
|
||||||
if (filePath) {
|
if (filePath) {
|
||||||
let result = await get_jre(filePath.path ?? filePath)
|
let result = await get_jre(filePath.path ?? filePath).catch(handleError)
|
||||||
if (!result) {
|
if (!result) {
|
||||||
result = {
|
result = {
|
||||||
path: filePath.path ?? filePath,
|
path: filePath.path ?? filePath,
|
||||||
|
|||||||
@@ -27,10 +27,7 @@ pub async fn authenticate_run() -> theseus::Result<Credentials> {
|
|||||||
|
|
||||||
let credentials = minecraft_auth::finish_login(&input, login).await?;
|
let credentials = minecraft_auth::finish_login(&input, login).await?;
|
||||||
|
|
||||||
println!(
|
println!("Logged in user {}.", credentials.username);
|
||||||
"Logged in user {}.",
|
|
||||||
credentials.maybe_online_profile().await.name
|
|
||||||
);
|
|
||||||
Ok(credentials)
|
Ok(credentials)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -16,7 +16,7 @@ serde_json.workspace = true
|
|||||||
serde = { workspace = true, features = ["derive"] }
|
serde = { workspace = true, features = ["derive"] }
|
||||||
serde_with.workspace = true
|
serde_with.workspace = true
|
||||||
|
|
||||||
tauri = { workspace = true, features = ["devtools", "macos-private-api", "protocol-asset", "unstable"] }
|
tauri = { workspace = true, features = ["devtools", "macos-private-api", "protocol-asset"] }
|
||||||
tauri-plugin-window-state.workspace = true
|
tauri-plugin-window-state.workspace = true
|
||||||
tauri-plugin-deep-link.workspace = true
|
tauri-plugin-deep-link.workspace = true
|
||||||
tauri-plugin-os.workspace = true
|
tauri-plugin-os.workspace = true
|
||||||
|
|||||||
@@ -151,6 +151,7 @@ fn main() {
|
|||||||
"profile_update_managed_modrinth_version",
|
"profile_update_managed_modrinth_version",
|
||||||
"profile_repair_managed_modrinth",
|
"profile_repair_managed_modrinth",
|
||||||
"profile_run",
|
"profile_run",
|
||||||
|
"profile_run_credentials",
|
||||||
"profile_kill",
|
"profile_kill",
|
||||||
"profile_edit",
|
"profile_edit",
|
||||||
"profile_edit_icon",
|
"profile_edit_icon",
|
||||||
|
|||||||
@@ -41,8 +41,8 @@ pub async fn jre_find_filtered_jres(
|
|||||||
// Validates JRE at a given path
|
// Validates JRE at a given path
|
||||||
// Returns None if the path is not a valid JRE
|
// Returns None if the path is not a valid JRE
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn jre_get_jre(path: PathBuf) -> Result<Option<JavaVersion>> {
|
pub async fn jre_get_jre(path: PathBuf) -> Result<JavaVersion> {
|
||||||
jre::check_jre(path).await.map_err(|e| e.into())
|
Ok(jre::check_jre(path).await?)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tests JRE of a certain version
|
// Tests JRE of a certain version
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ pub fn init<R: tauri::Runtime>() -> tauri::plugin::TauriPlugin<R> {
|
|||||||
profile_update_managed_modrinth_version,
|
profile_update_managed_modrinth_version,
|
||||||
profile_repair_managed_modrinth,
|
profile_repair_managed_modrinth,
|
||||||
profile_run,
|
profile_run,
|
||||||
|
profile_run_credentials,
|
||||||
profile_kill,
|
profile_kill,
|
||||||
profile_edit,
|
profile_edit,
|
||||||
profile_edit_icon,
|
profile_edit_icon,
|
||||||
@@ -255,6 +256,22 @@ pub async fn profile_run(path: &str) -> Result<ProcessMetadata> {
|
|||||||
Ok(process)
|
Ok(process)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Run Minecraft using a profile using chosen credentials
|
||||||
|
// Returns the UUID, which can be used to poll
|
||||||
|
// for the actual Child in the state.
|
||||||
|
// invoke('plugin:profile|profile_run_credentials', {path, credentials})')
|
||||||
|
#[tauri::command]
|
||||||
|
pub async fn profile_run_credentials(
|
||||||
|
path: &str,
|
||||||
|
credentials: Credentials,
|
||||||
|
) -> Result<ProcessMetadata> {
|
||||||
|
let process =
|
||||||
|
profile::run_credentials(path, &credentials, &QuickPlayType::None)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(process)
|
||||||
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn profile_kill(path: &str) -> Result<()> {
|
pub async fn profile_kill(path: &str) -> Result<()> {
|
||||||
profile::kill(path).await?;
|
profile::kill(path).await?;
|
||||||
|
|||||||
@@ -1,6 +1,24 @@
|
|||||||
{
|
{
|
||||||
"bundle": {
|
"bundle": {
|
||||||
"createUpdaterArtifacts": "v1Compatible"
|
"createUpdaterArtifacts": "v1Compatible",
|
||||||
|
"windows": {
|
||||||
|
"signCommand": {
|
||||||
|
"cmd": "jsign",
|
||||||
|
"args": [
|
||||||
|
"sign",
|
||||||
|
"--verbose",
|
||||||
|
"--storetype",
|
||||||
|
"DIGICERTONE",
|
||||||
|
"--keystore",
|
||||||
|
"https://clientauth.one.digicert.com",
|
||||||
|
"--storepass",
|
||||||
|
"env:DIGICERT_ONE_SIGNER_CREDENTIALS",
|
||||||
|
"--tsaurl",
|
||||||
|
"https://timestamp.sectigo.com,http://timestamp.digicert.com",
|
||||||
|
"%1"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"build": {
|
"build": {
|
||||||
"features": ["updater"]
|
"features": ["updater"]
|
||||||
|
|||||||
@@ -14,9 +14,6 @@
|
|||||||
"externalBin": [],
|
"externalBin": [],
|
||||||
"icon": ["icons/128x128.png", "icons/128x128@2x.png", "icons/icon.icns", "icons/icon.ico"],
|
"icon": ["icons/128x128.png", "icons/128x128@2x.png", "icons/icon.icns", "icons/icon.ico"],
|
||||||
"windows": {
|
"windows": {
|
||||||
"certificateThumbprint": null,
|
|
||||||
"digestAlgorithm": "sha256",
|
|
||||||
"timestampUrl": "http://timestamp.digicert.com",
|
|
||||||
"nsis": {
|
"nsis": {
|
||||||
"installMode": "perMachine",
|
"installMode": "perMachine",
|
||||||
"installerHooks": "./nsis/hooks.nsi"
|
"installerHooks": "./nsis/hooks.nsi"
|
||||||
@@ -30,7 +27,6 @@
|
|||||||
"providerShortName": null,
|
"providerShortName": null,
|
||||||
"signingIdentity": null
|
"signingIdentity": null
|
||||||
},
|
},
|
||||||
"resources": [],
|
|
||||||
"shortDescription": "",
|
"shortDescription": "",
|
||||||
"linux": {
|
"linux": {
|
||||||
"deb": {
|
"deb": {
|
||||||
|
|||||||
@@ -85,11 +85,10 @@ During development, you might notice that changes made directly to entities in t
|
|||||||
|
|
||||||
#### CDN options
|
#### CDN options
|
||||||
|
|
||||||
`STORAGE_BACKEND`: Controls what storage backend is used. This can be either `local`, `backblaze`, or `s3`, but defaults to `local`
|
`STORAGE_BACKEND`: Controls what storage backend is used. This can be either `local` or `s3`, but defaults to `local`
|
||||||
|
|
||||||
The Backblaze and S3 configuration options are fairly self-explanatory in name, so here's simply their names:
|
The S3 configuration options are fairly self-explanatory in name, so here's simply their names:
|
||||||
`BACKBLAZE_KEY_ID`, `BACKBLAZE_KEY`, `BACKBLAZE_BUCKET_ID`
|
`S3_ACCESS_TOKEN`, `S3_SECRET`, `S3_URL`, `S3_REGION`, `S3_PUBLIC_BUCKET_NAME`, `S3_PRIVATE_BUCKET_NAME`, `S3_USES_PATH_STYLE_BUCKETS`
|
||||||
`S3_ACCESS_TOKEN`, `S3_SECRET`, `S3_URL`, `S3_REGION`, `S3_BUCKET_NAME`
|
|
||||||
|
|
||||||
#### Search, OAuth, and miscellaneous options
|
#### Search, OAuth, and miscellaneous options
|
||||||
|
|
||||||
|
|||||||
@@ -2,4 +2,3 @@ BASE_URL=http://127.0.0.1:8000/v2/
|
|||||||
BROWSER_BASE_URL=http://127.0.0.1:8000/v2/
|
BROWSER_BASE_URL=http://127.0.0.1:8000/v2/
|
||||||
PYRO_BASE_URL=https://staging-archon.modrinth.com
|
PYRO_BASE_URL=https://staging-archon.modrinth.com
|
||||||
PROD_OVERRIDE=true
|
PROD_OVERRIDE=true
|
||||||
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
<template>
|
|
||||||
<OmorphiaAvatar
|
|
||||||
:src="src"
|
|
||||||
:alt="alt"
|
|
||||||
:size="size"
|
|
||||||
:circle="circle"
|
|
||||||
:no-shadow="noShadow"
|
|
||||||
:loading="loading"
|
|
||||||
:raised="raised"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
import { Avatar as OmorphiaAvatar } from "@modrinth/ui";
|
|
||||||
|
|
||||||
const props = defineProps({
|
|
||||||
src: {
|
|
||||||
type: String,
|
|
||||||
default: null,
|
|
||||||
},
|
|
||||||
alt: {
|
|
||||||
type: String,
|
|
||||||
default: "",
|
|
||||||
},
|
|
||||||
size: {
|
|
||||||
type: String,
|
|
||||||
default: "2rem",
|
|
||||||
},
|
|
||||||
circle: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
noShadow: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
loading: {
|
|
||||||
type: String,
|
|
||||||
default: "eager",
|
|
||||||
},
|
|
||||||
raised: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
@@ -1,131 +0,0 @@
|
|||||||
<template>
|
|
||||||
<span
|
|
||||||
:class="
|
|
||||||
'badge flex items-center gap-1 font-semibold text-secondary ' + color + ' type--' + type
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<template v-if="color"> <span class="circle" /> {{ capitalizeString(type) }}</template>
|
|
||||||
|
|
||||||
<!-- User roles -->
|
|
||||||
<template v-else-if="type === 'admin'"> <ModrinthIcon /> Modrinth Team</template>
|
|
||||||
<template v-else-if="type === 'moderator'"> <ModeratorIcon /> Moderator</template>
|
|
||||||
<template v-else-if="type === 'creator'"><CreatorIcon /> Creator</template>
|
|
||||||
<template v-else-if="type === 'plus'"><PlusIcon /> Modrinth Plus</template>
|
|
||||||
|
|
||||||
<!-- Project statuses -->
|
|
||||||
<template v-else-if="type === 'approved'"><GlobeIcon /> Public</template>
|
|
||||||
<template v-else-if="type === 'approved-general'"><CheckIcon /> Approved</template>
|
|
||||||
<template v-else-if="type === 'unlisted' || type === 'withheld'"
|
|
||||||
><LinkIcon /> Unlisted</template
|
|
||||||
>
|
|
||||||
<template v-else-if="type === 'private'"><LockIcon /> Private</template>
|
|
||||||
<template v-else-if="type === 'scheduled'"> <CalendarIcon /> Scheduled</template>
|
|
||||||
<template v-else-if="type === 'draft'"><DraftIcon /> Draft</template>
|
|
||||||
<template v-else-if="type === 'archived'"> <ArchiveIcon /> Archived</template>
|
|
||||||
<template v-else-if="type === 'rejected'"><CrossIcon /> Rejected</template>
|
|
||||||
<template v-else-if="type === 'processing'"> <ProcessingIcon /> Under review</template>
|
|
||||||
|
|
||||||
<!-- Team members -->
|
|
||||||
<template v-else-if="type === 'accepted'"><CheckIcon /> Accepted</template>
|
|
||||||
<template v-else-if="type === 'pending'"> <ProcessingIcon /> Pending </template>
|
|
||||||
|
|
||||||
<!-- Transaction statuses -->
|
|
||||||
<template v-else-if="type === 'success'"><CheckIcon /> Success</template>
|
|
||||||
|
|
||||||
<!-- Report status -->
|
|
||||||
<template v-else-if="type === 'closed'"> <CloseIcon /> Closed</template>
|
|
||||||
|
|
||||||
<!-- Other -->
|
|
||||||
<template v-else> <span class="circle" /> {{ capitalizeString(type) }} </template>
|
|
||||||
</span>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
import {
|
|
||||||
GlobeIcon,
|
|
||||||
LinkIcon,
|
|
||||||
ModrinthIcon,
|
|
||||||
PlusIcon,
|
|
||||||
ScaleIcon as ModeratorIcon,
|
|
||||||
BoxIcon as CreatorIcon,
|
|
||||||
FileTextIcon as DraftIcon,
|
|
||||||
XIcon as CrossIcon,
|
|
||||||
ArchiveIcon,
|
|
||||||
UpdatedIcon as ProcessingIcon,
|
|
||||||
CheckIcon,
|
|
||||||
LockIcon,
|
|
||||||
CalendarIcon,
|
|
||||||
XCircleIcon as CloseIcon,
|
|
||||||
} from "@modrinth/assets";
|
|
||||||
import { capitalizeString } from "@modrinth/utils";
|
|
||||||
|
|
||||||
defineProps({
|
|
||||||
type: {
|
|
||||||
type: String,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
color: {
|
|
||||||
type: String,
|
|
||||||
default: "",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.badge {
|
|
||||||
.circle {
|
|
||||||
width: 0.5rem;
|
|
||||||
height: 0.5rem;
|
|
||||||
border-radius: 50%;
|
|
||||||
display: inline-block;
|
|
||||||
margin-right: 0.25rem;
|
|
||||||
background-color: var(--badge-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
svg {
|
|
||||||
vertical-align: -15%;
|
|
||||||
width: 1em;
|
|
||||||
height: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.type--closed,
|
|
||||||
&.type--withheld,
|
|
||||||
&.type--rejected,
|
|
||||||
&.red {
|
|
||||||
--badge-color: var(--color-red);
|
|
||||||
}
|
|
||||||
|
|
||||||
&.type--pending,
|
|
||||||
&.type--moderator,
|
|
||||||
&.type--processing,
|
|
||||||
&.type--scheduled,
|
|
||||||
&.orange {
|
|
||||||
--badge-color: var(--color-orange);
|
|
||||||
}
|
|
||||||
|
|
||||||
&.type--accepted,
|
|
||||||
&.type--admin,
|
|
||||||
&.type--success,
|
|
||||||
&.type--approved-general,
|
|
||||||
&.green {
|
|
||||||
--badge-color: var(--color-green);
|
|
||||||
}
|
|
||||||
|
|
||||||
&.type--creator,
|
|
||||||
&.blue {
|
|
||||||
--badge-color: var(--color-blue);
|
|
||||||
}
|
|
||||||
|
|
||||||
&.type--unlisted,
|
|
||||||
&.type--plus,
|
|
||||||
&.purple {
|
|
||||||
--badge-color: var(--color-purple);
|
|
||||||
}
|
|
||||||
|
|
||||||
&.type--private,
|
|
||||||
&.type--approved,
|
|
||||||
&.gray {
|
|
||||||
--badge-color: var(--color-secondary);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,75 +0,0 @@
|
|||||||
<template>
|
|
||||||
<button class="code" :class="{ copied }" title="Copy code to clipboard" @click="copyText">
|
|
||||||
<span>{{ text }}</span>
|
|
||||||
<CheckIcon v-if="copied" />
|
|
||||||
<ClipboardCopyIcon v-else />
|
|
||||||
</button>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import { CheckIcon, ClipboardCopyIcon } from "@modrinth/assets";
|
|
||||||
|
|
||||||
export default {
|
|
||||||
components: {
|
|
||||||
CheckIcon,
|
|
||||||
ClipboardCopyIcon,
|
|
||||||
},
|
|
||||||
props: {
|
|
||||||
text: {
|
|
||||||
type: String,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
copied: false,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
async copyText() {
|
|
||||||
await navigator.clipboard.writeText(this.text);
|
|
||||||
this.copied = true;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.code {
|
|
||||||
color: var(--color-text);
|
|
||||||
display: inline-flex;
|
|
||||||
grid-gap: 0.5rem;
|
|
||||||
font-family: var(--mono-font);
|
|
||||||
font-size: var(--font-size-sm);
|
|
||||||
margin: 0;
|
|
||||||
padding: 0.25rem 0.5rem;
|
|
||||||
background-color: var(--color-code-bg);
|
|
||||||
width: fit-content;
|
|
||||||
border-radius: 10px;
|
|
||||||
user-select: text;
|
|
||||||
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;
|
|
||||||
|
|
||||||
span {
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
}
|
|
||||||
|
|
||||||
svg {
|
|
||||||
width: 1em;
|
|
||||||
height: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
filter: brightness(0.85);
|
|
||||||
}
|
|
||||||
|
|
||||||
&:active {
|
|
||||||
transform: scale(0.95);
|
|
||||||
filter: brightness(0.8);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -104,13 +104,13 @@
|
|||||||
</nuxt-link>
|
</nuxt-link>
|
||||||
<template v-if="tags.rejectedStatuses.includes(notification.body.new_status)">
|
<template v-if="tags.rejectedStatuses.includes(notification.body.new_status)">
|
||||||
has been
|
has been
|
||||||
<Badge :type="notification.body.new_status" />
|
<ProjectStatusBadge :status="notification.body.new_status" />
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
updated from
|
updated from
|
||||||
<Badge :type="notification.body.old_status" />
|
<ProjectStatusBadge :status="notification.body.old_status" />
|
||||||
to
|
to
|
||||||
<Badge :type="notification.body.new_status" />
|
<ProjectStatusBadge :status="notification.body.new_status" />
|
||||||
</template>
|
</template>
|
||||||
by the moderators.
|
by the moderators.
|
||||||
</template>
|
</template>
|
||||||
@@ -331,16 +331,13 @@ import {
|
|||||||
XIcon,
|
XIcon,
|
||||||
ExternalIcon,
|
ExternalIcon,
|
||||||
} from "@modrinth/assets";
|
} from "@modrinth/assets";
|
||||||
import { useRelativeTime } from "@modrinth/ui";
|
import { Avatar, ProjectStatusBadge, CopyCode, useRelativeTime } from "@modrinth/ui";
|
||||||
import ThreadSummary from "~/components/ui/thread/ThreadSummary.vue";
|
import ThreadSummary from "~/components/ui/thread/ThreadSummary.vue";
|
||||||
import { getProjectLink, getVersionLink } from "~/helpers/projects.js";
|
import { getProjectLink, getVersionLink } from "~/helpers/projects.js";
|
||||||
import { getUserLink } from "~/helpers/users.js";
|
import { getUserLink } from "~/helpers/users.js";
|
||||||
import { acceptTeamInvite, removeSelfFromTeam } from "~/helpers/teams.js";
|
import { acceptTeamInvite, removeSelfFromTeam } from "~/helpers/teams.js";
|
||||||
import { markAsRead } from "~/helpers/notifications.ts";
|
import { markAsRead } from "~/helpers/notifications.ts";
|
||||||
import DoubleIcon from "~/components/ui/DoubleIcon.vue";
|
import DoubleIcon from "~/components/ui/DoubleIcon.vue";
|
||||||
import Avatar from "~/components/ui/Avatar.vue";
|
|
||||||
import Badge from "~/components/ui/Badge.vue";
|
|
||||||
import CopyCode from "~/components/ui/CopyCode.vue";
|
|
||||||
import Categories from "~/components/ui/search/Categories.vue";
|
import Categories from "~/components/ui/search/Categories.vue";
|
||||||
|
|
||||||
const app = useNuxtApp();
|
const app = useNuxtApp();
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="vue-notification-group experimental-styles-within">
|
<div
|
||||||
|
class="vue-notification-group experimental-styles-within"
|
||||||
|
:class="{ 'intercom-present': isIntercomPresent }"
|
||||||
|
>
|
||||||
<transition-group name="notifs">
|
<transition-group name="notifs">
|
||||||
<div
|
<div
|
||||||
v-for="(item, index) in notifications"
|
v-for="(item, index) in notifications"
|
||||||
@@ -80,6 +83,8 @@ import {
|
|||||||
} from "@modrinth/assets";
|
} from "@modrinth/assets";
|
||||||
const notifications = useNotifications();
|
const notifications = useNotifications();
|
||||||
|
|
||||||
|
const isIntercomPresent = ref(false);
|
||||||
|
|
||||||
function stopTimer(notif) {
|
function stopTimer(notif) {
|
||||||
clearTimeout(notif.timer);
|
clearTimeout(notif.timer);
|
||||||
}
|
}
|
||||||
@@ -106,6 +111,27 @@ const createNotifText = (notif) => {
|
|||||||
return text;
|
return text;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function checkIntercomPresence() {
|
||||||
|
isIntercomPresent.value = !!document.querySelector(".intercom-lightweight-app");
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
checkIntercomPresence();
|
||||||
|
|
||||||
|
const observer = new MutationObserver(() => {
|
||||||
|
checkIntercomPresence();
|
||||||
|
});
|
||||||
|
|
||||||
|
observer.observe(document.body, {
|
||||||
|
childList: true,
|
||||||
|
subtree: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
observer.disconnect();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
function copyToClipboard(notif) {
|
function copyToClipboard(notif) {
|
||||||
const text = createNotifText(notif);
|
const text = createNotifText(notif);
|
||||||
|
|
||||||
@@ -130,6 +156,10 @@ function copyToClipboard(notif) {
|
|||||||
bottom: 0.75rem;
|
bottom: 0.75rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.intercom-present {
|
||||||
|
bottom: 5rem;
|
||||||
|
}
|
||||||
|
|
||||||
.vue-notification-wrapper {
|
.vue-notification-wrapper {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|||||||
@@ -57,7 +57,7 @@
|
|||||||
<div class="table-cell">
|
<div class="table-cell">
|
||||||
<BoxIcon />
|
<BoxIcon />
|
||||||
<span>{{
|
<span>{{
|
||||||
$formatProjectType(
|
formatProjectType(
|
||||||
$getProjectTypeForDisplay(
|
$getProjectTypeForDisplay(
|
||||||
project.project_types?.[0] ?? "project",
|
project.project_types?.[0] ?? "project",
|
||||||
project.loaders,
|
project.loaders,
|
||||||
@@ -111,6 +111,7 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { BoxIcon, SettingsIcon, TransferIcon, XIcon } from "@modrinth/assets";
|
import { BoxIcon, SettingsIcon, TransferIcon, XIcon } from "@modrinth/assets";
|
||||||
import { Button, Modal, Checkbox, CopyCode, Avatar } from "@modrinth/ui";
|
import { Button, Modal, Checkbox, CopyCode, Avatar } from "@modrinth/ui";
|
||||||
|
import { formatProjectType } from "@modrinth/utils";
|
||||||
|
|
||||||
const modalOpen = ref(null);
|
const modalOpen = ref(null);
|
||||||
|
|
||||||
|
|||||||
@@ -1,196 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div v-if="count > 1" class="columns paginates">
|
|
||||||
<a
|
|
||||||
:class="{ disabled: page === 1 }"
|
|
||||||
:tabindex="page === 1 ? -1 : 0"
|
|
||||||
class="left-arrow paginate has-icon"
|
|
||||||
aria-label="Previous Page"
|
|
||||||
:href="linkFunction(page - 1)"
|
|
||||||
@click.prevent="page !== 1 ? switchPage(page - 1) : null"
|
|
||||||
>
|
|
||||||
<LeftArrowIcon />
|
|
||||||
</a>
|
|
||||||
<div
|
|
||||||
v-for="(item, index) in pages"
|
|
||||||
:key="'page-' + item + '-' + index"
|
|
||||||
:class="{
|
|
||||||
'page-number': page !== item,
|
|
||||||
shrink: item > 99,
|
|
||||||
}"
|
|
||||||
class="page-number-container"
|
|
||||||
>
|
|
||||||
<div v-if="item === '-'" class="has-icon">
|
|
||||||
<GapIcon />
|
|
||||||
</div>
|
|
||||||
<a
|
|
||||||
v-else
|
|
||||||
:class="{
|
|
||||||
'page-number current': page === item,
|
|
||||||
shrink: item > 99,
|
|
||||||
}"
|
|
||||||
:href="linkFunction(item)"
|
|
||||||
@click.prevent="page !== item ? switchPage(item) : null"
|
|
||||||
>
|
|
||||||
{{ item }}
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<a
|
|
||||||
:class="{
|
|
||||||
disabled: page === pages[pages.length - 1],
|
|
||||||
}"
|
|
||||||
:tabindex="page === pages[pages.length - 1] ? -1 : 0"
|
|
||||||
class="right-arrow paginate has-icon"
|
|
||||||
aria-label="Next Page"
|
|
||||||
:href="linkFunction(page + 1)"
|
|
||||||
@click.prevent="page !== pages[pages.length - 1] ? switchPage(page + 1) : null"
|
|
||||||
>
|
|
||||||
<RightArrowIcon />
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import { GapIcon, LeftArrowIcon, RightArrowIcon } from "@modrinth/assets";
|
|
||||||
|
|
||||||
export default {
|
|
||||||
components: {
|
|
||||||
GapIcon,
|
|
||||||
LeftArrowIcon,
|
|
||||||
RightArrowIcon,
|
|
||||||
},
|
|
||||||
props: {
|
|
||||||
page: {
|
|
||||||
type: Number,
|
|
||||||
default: 1,
|
|
||||||
},
|
|
||||||
count: {
|
|
||||||
type: Number,
|
|
||||||
default: 1,
|
|
||||||
},
|
|
||||||
linkFunction: {
|
|
||||||
type: Function,
|
|
||||||
default() {
|
|
||||||
return () => "/";
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
emits: ["switch-page"],
|
|
||||||
computed: {
|
|
||||||
pages() {
|
|
||||||
let pages = [];
|
|
||||||
|
|
||||||
if (this.count > 7) {
|
|
||||||
if (this.page + 3 >= this.count) {
|
|
||||||
pages = [
|
|
||||||
1,
|
|
||||||
"-",
|
|
||||||
this.count - 4,
|
|
||||||
this.count - 3,
|
|
||||||
this.count - 2,
|
|
||||||
this.count - 1,
|
|
||||||
this.count,
|
|
||||||
];
|
|
||||||
} else if (this.page > 5) {
|
|
||||||
pages = [1, "-", this.page - 1, this.page, this.page + 1, "-", this.count];
|
|
||||||
} else {
|
|
||||||
pages = [1, 2, 3, 4, 5, "-", this.count];
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
pages = Array.from({ length: this.count }, (_, i) => i + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
return pages;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
switchPage(newPage) {
|
|
||||||
this.$emit("switch-page", newPage);
|
|
||||||
if (newPage !== null && newPage !== "" && !isNaN(newPage)) {
|
|
||||||
this.$emit("switch-page", Math.min(Math.max(newPage, 1), this.count));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped lang="scss">
|
|
||||||
a {
|
|
||||||
position: relative;
|
|
||||||
color: var(--color-button-text);
|
|
||||||
box-shadow: var(--shadow-raised), var(--shadow-inset);
|
|
||||||
|
|
||||||
padding: 0.5rem 1rem;
|
|
||||||
margin: 0;
|
|
||||||
border-radius: 2rem;
|
|
||||||
background: var(--color-raised-bg);
|
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
&.page-number.current {
|
|
||||||
background: var(--color-brand);
|
|
||||||
color: var(--color-brand-inverted);
|
|
||||||
cursor: default;
|
|
||||||
outline: 2px solid transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.paginate.disabled {
|
|
||||||
background-color: transparent;
|
|
||||||
cursor: not-allowed;
|
|
||||||
filter: grayscale(50%);
|
|
||||||
opacity: 0.5;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.has-icon {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
svg {
|
|
||||||
width: 1em;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.page-number-container,
|
|
||||||
a,
|
|
||||||
.has-icon {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.paginates {
|
|
||||||
height: 2em;
|
|
||||||
margin: 0.5rem 0;
|
|
||||||
|
|
||||||
> div,
|
|
||||||
.has-icon {
|
|
||||||
margin: 0 0.3em;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.left-arrow {
|
|
||||||
margin-left: auto !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.right-arrow {
|
|
||||||
margin-right: auto !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media screen and (max-width: 400px) {
|
|
||||||
.paginates {
|
|
||||||
font-size: 80%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media screen and (max-width: 530px) {
|
|
||||||
a {
|
|
||||||
width: 2.5rem;
|
|
||||||
padding: 0.5rem 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -29,7 +29,7 @@
|
|||||||
{{ author }}
|
{{ author }}
|
||||||
</nuxt-link>
|
</nuxt-link>
|
||||||
</p>
|
</p>
|
||||||
<Badge v-if="status && status !== 'approved'" :type="status" class="status" />
|
<ProjectStatusBadge v-if="status && status !== 'approved'" :status="status" class="status" />
|
||||||
</div>
|
</div>
|
||||||
<p class="description">
|
<p class="description">
|
||||||
{{ description }}
|
{{ description }}
|
||||||
@@ -91,18 +91,16 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { CalendarIcon, UpdatedIcon, DownloadIcon, HeartIcon } from "@modrinth/assets";
|
import { CalendarIcon, UpdatedIcon, DownloadIcon, HeartIcon } from "@modrinth/assets";
|
||||||
|
import { Avatar, ProjectStatusBadge, useRelativeTime } from "@modrinth/ui";
|
||||||
import Categories from "~/components/ui/search/Categories.vue";
|
import Categories from "~/components/ui/search/Categories.vue";
|
||||||
import Badge from "~/components/ui/Badge.vue";
|
|
||||||
import EnvironmentIndicator from "~/components/ui/EnvironmentIndicator.vue";
|
import EnvironmentIndicator from "~/components/ui/EnvironmentIndicator.vue";
|
||||||
import Avatar from "~/components/ui/Avatar.vue";
|
|
||||||
import { useRelativeTime } from "@modrinth/ui";
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
|
ProjectStatusBadge,
|
||||||
EnvironmentIndicator,
|
EnvironmentIndicator,
|
||||||
Avatar,
|
Avatar,
|
||||||
Categories,
|
Categories,
|
||||||
Badge,
|
|
||||||
CalendarIcon,
|
CalendarIcon,
|
||||||
UpdatedIcon,
|
UpdatedIcon,
|
||||||
DownloadIcon,
|
DownloadIcon,
|
||||||
|
|||||||
@@ -118,7 +118,7 @@ import {
|
|||||||
ScaleIcon,
|
ScaleIcon,
|
||||||
DropdownIcon,
|
DropdownIcon,
|
||||||
} from "@modrinth/assets";
|
} from "@modrinth/assets";
|
||||||
import { formatProjectType } from "~/plugins/shorthands.js";
|
import { formatProjectType } from "@modrinth/utils";
|
||||||
import { acceptTeamInvite, removeTeamMember } from "~/helpers/teams.js";
|
import { acceptTeamInvite, removeTeamMember } from "~/helpers/teams.js";
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
|
|||||||
@@ -11,8 +11,8 @@
|
|||||||
<div class="stacked">
|
<div class="stacked">
|
||||||
<span class="title">{{ report.project.title }}</span>
|
<span class="title">{{ report.project.title }}</span>
|
||||||
<span>{{
|
<span>{{
|
||||||
$formatProjectType(
|
formatProjectType(
|
||||||
$getProjectTypeForUrl(report.project.project_type, report.project.loaders),
|
getProjectTypeForUrl(report.project.project_type, report.project.loaders),
|
||||||
)
|
)
|
||||||
}}</span>
|
}}</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -53,8 +53,8 @@
|
|||||||
<div class="stacked">
|
<div class="stacked">
|
||||||
<span class="title">{{ report.project.title }}</span>
|
<span class="title">{{ report.project.title }}</span>
|
||||||
<span>{{
|
<span>{{
|
||||||
$formatProjectType(
|
formatProjectType(
|
||||||
$getProjectTypeForUrl(report.project.project_type, report.project.loaders),
|
getProjectTypeForUrl(report.project.project_type, report.project.loaders),
|
||||||
)
|
)
|
||||||
}}</span>
|
}}</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -104,12 +104,11 @@
|
|||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ReportIcon, UnknownIcon, VersionIcon } from "@modrinth/assets";
|
import { ReportIcon, UnknownIcon, VersionIcon } from "@modrinth/assets";
|
||||||
|
import { Avatar, Badge, CopyCode, useRelativeTime } from "@modrinth/ui";
|
||||||
|
import { formatProjectType } from "@modrinth/utils";
|
||||||
import { renderHighlightedString } from "~/helpers/highlight.js";
|
import { renderHighlightedString } from "~/helpers/highlight.js";
|
||||||
import { useRelativeTime } from "@modrinth/ui";
|
|
||||||
import Avatar from "~/components/ui/Avatar.vue";
|
|
||||||
import Badge from "~/components/ui/Badge.vue";
|
|
||||||
import ThreadSummary from "~/components/ui/thread/ThreadSummary.vue";
|
import ThreadSummary from "~/components/ui/thread/ThreadSummary.vue";
|
||||||
import CopyCode from "~/components/ui/CopyCode.vue";
|
import { getProjectTypeForUrl } from "~/helpers/projects.js";
|
||||||
|
|
||||||
const formatRelativeTime = useRelativeTime();
|
const formatRelativeTime = useRelativeTime();
|
||||||
|
|
||||||
|
|||||||
@@ -4,12 +4,14 @@
|
|||||||
<span
|
<span
|
||||||
v-for="category in categoriesFiltered"
|
v-for="category in categoriesFiltered"
|
||||||
:key="category.name"
|
:key="category.name"
|
||||||
v-html="category.icon + $formatCategory(category.name)"
|
v-html="category.icon + formatCategory(category.name)"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import { formatCategory } from "@modrinth/utils";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: {
|
props: {
|
||||||
categories: {
|
categories: {
|
||||||
@@ -38,6 +40,7 @@ export default {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
methods: { formatCategory },
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import {
|
|||||||
} from "@modrinth/assets";
|
} from "@modrinth/assets";
|
||||||
import { ButtonStyled, commonMessages, OverflowMenu, ProgressBar } from "@modrinth/ui";
|
import { ButtonStyled, commonMessages, OverflowMenu, ProgressBar } from "@modrinth/ui";
|
||||||
import { defineMessages, useVIntl } from "@vintl/vintl";
|
import { defineMessages, useVIntl } from "@vintl/vintl";
|
||||||
import { ref } from "vue";
|
import { ref, computed } from "vue";
|
||||||
import type { Backup } from "@modrinth/utils";
|
import type { Backup } from "@modrinth/utils";
|
||||||
|
|
||||||
const flags = useFeatureFlags();
|
const flags = useFeatureFlags();
|
||||||
@@ -52,9 +52,10 @@ const failedToCreate = computed(() => props.backup.interrupted);
|
|||||||
const preparedDownloadStates = ["ready", "done"];
|
const preparedDownloadStates = ["ready", "done"];
|
||||||
const inactiveStates = ["failed", "cancelled"];
|
const inactiveStates = ["failed", "cancelled"];
|
||||||
|
|
||||||
const hasPreparedDownload = computed(() =>
|
const hasPreparedDownload = computed(() => {
|
||||||
preparedDownloadStates.includes(props.backup.task?.file?.state ?? ""),
|
const fileState = props.backup.task?.file?.state ?? "";
|
||||||
);
|
return preparedDownloadStates.includes(fileState);
|
||||||
|
});
|
||||||
|
|
||||||
const creating = computed(() => {
|
const creating = computed(() => {
|
||||||
const task = props.backup.task?.create;
|
const task = props.backup.task?.create;
|
||||||
@@ -81,6 +82,10 @@ const restoring = computed(() => {
|
|||||||
const initiatedPrepare = ref(false);
|
const initiatedPrepare = ref(false);
|
||||||
|
|
||||||
const preparingFile = computed(() => {
|
const preparingFile = computed(() => {
|
||||||
|
if (hasPreparedDownload.value) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
const task = props.backup.task?.file;
|
const task = props.backup.task?.file;
|
||||||
return (
|
return (
|
||||||
(!task && initiatedPrepare.value) ||
|
(!task && initiatedPrepare.value) ||
|
||||||
|
|||||||
@@ -39,7 +39,7 @@
|
|||||||
/>
|
/>
|
||||||
<XCircleIcon
|
<XCircleIcon
|
||||||
v-show="
|
v-show="
|
||||||
item.status === 'error' ||
|
item.status.includes('error') ||
|
||||||
item.status === 'cancelled' ||
|
item.status === 'cancelled' ||
|
||||||
item.status === 'incorrect-type'
|
item.status === 'incorrect-type'
|
||||||
"
|
"
|
||||||
@@ -54,9 +54,14 @@
|
|||||||
<template v-if="item.status === 'completed'">
|
<template v-if="item.status === 'completed'">
|
||||||
<span>Done</span>
|
<span>Done</span>
|
||||||
</template>
|
</template>
|
||||||
<template v-else-if="item.status === 'error'">
|
<template v-else-if="item.status === 'error-file-exists'">
|
||||||
<span class="text-red">Failed - File already exists</span>
|
<span class="text-red">Failed - File already exists</span>
|
||||||
</template>
|
</template>
|
||||||
|
<template v-else-if="item.status === 'error-generic'">
|
||||||
|
<span class="text-red"
|
||||||
|
>Failed - {{ item.error?.message || "An unexpected error occured." }}</span
|
||||||
|
>
|
||||||
|
</template>
|
||||||
<template v-else-if="item.status === 'incorrect-type'">
|
<template v-else-if="item.status === 'incorrect-type'">
|
||||||
<span class="text-red">Failed - Incorrect file type</span>
|
<span class="text-red">Failed - Incorrect file type</span>
|
||||||
</template>
|
</template>
|
||||||
@@ -104,9 +109,17 @@ import { FSModule } from "~/composables/servers/modules/fs.ts";
|
|||||||
interface UploadItem {
|
interface UploadItem {
|
||||||
file: File;
|
file: File;
|
||||||
progress: number;
|
progress: number;
|
||||||
status: "pending" | "uploading" | "completed" | "error" | "cancelled" | "incorrect-type";
|
status:
|
||||||
|
| "pending"
|
||||||
|
| "uploading"
|
||||||
|
| "completed"
|
||||||
|
| "error-file-exists"
|
||||||
|
| "error-generic"
|
||||||
|
| "cancelled"
|
||||||
|
| "incorrect-type";
|
||||||
size: string;
|
size: string;
|
||||||
uploader?: any;
|
uploader?: any;
|
||||||
|
error?: Error;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@@ -245,8 +258,18 @@ const uploadFile = async (file: File) => {
|
|||||||
console.error("Error uploading file:", error);
|
console.error("Error uploading file:", error);
|
||||||
const index = uploadQueue.value.findIndex((item) => item.file.name === file.name);
|
const index = uploadQueue.value.findIndex((item) => item.file.name === file.name);
|
||||||
if (index !== -1 && uploadQueue.value[index].status !== "cancelled") {
|
if (index !== -1 && uploadQueue.value[index].status !== "cancelled") {
|
||||||
uploadQueue.value[index].status =
|
const target = uploadQueue.value[index];
|
||||||
error instanceof Error && error.message === badFileTypeMsg ? "incorrect-type" : "error";
|
|
||||||
|
if (error instanceof Error) {
|
||||||
|
if (error.message === badFileTypeMsg) {
|
||||||
|
target.status = "incorrect-type";
|
||||||
|
} else if (target.progress === 100 && error.message.includes("401")) {
|
||||||
|
target.status = "error-file-exists";
|
||||||
|
} else {
|
||||||
|
target.status = "error-generic";
|
||||||
|
target.error = error;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setTimeout(async () => {
|
setTimeout(async () => {
|
||||||
|
|||||||
@@ -214,7 +214,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { OverflowMenu, MarkdownEditor } from "@modrinth/ui";
|
import { CopyCode, OverflowMenu, MarkdownEditor } from "@modrinth/ui";
|
||||||
import {
|
import {
|
||||||
DropdownIcon,
|
DropdownIcon,
|
||||||
ReplyIcon,
|
ReplyIcon,
|
||||||
@@ -226,7 +226,6 @@ import {
|
|||||||
ScaleIcon,
|
ScaleIcon,
|
||||||
} from "@modrinth/assets";
|
} from "@modrinth/assets";
|
||||||
import { useImageUpload } from "~/composables/image-upload.ts";
|
import { useImageUpload } from "~/composables/image-upload.ts";
|
||||||
import CopyCode from "~/components/ui/CopyCode.vue";
|
|
||||||
import ThreadMessage from "~/components/ui/thread/ThreadMessage.vue";
|
import ThreadMessage from "~/components/ui/thread/ThreadMessage.vue";
|
||||||
import { isStaff } from "~/helpers/users.js";
|
import { isStaff } from "~/helpers/users.js";
|
||||||
import { isApproved, isRejected } from "~/helpers/projects.js";
|
import { isApproved, isRejected } from "~/helpers/projects.js";
|
||||||
|
|||||||
@@ -103,10 +103,8 @@ import {
|
|||||||
ModrinthIcon,
|
ModrinthIcon,
|
||||||
ScaleIcon,
|
ScaleIcon,
|
||||||
} from "@modrinth/assets";
|
} from "@modrinth/assets";
|
||||||
import { AutoLink, OverflowMenu, useRelativeTime } from "@modrinth/ui";
|
import { AutoLink, Avatar, Badge, OverflowMenu, useRelativeTime } from "@modrinth/ui";
|
||||||
import { renderString } from "@modrinth/utils";
|
import { renderString } from "@modrinth/utils";
|
||||||
import Avatar from "~/components/ui/Avatar.vue";
|
|
||||||
import Badge from "~/components/ui/Badge.vue";
|
|
||||||
import { isStaff } from "~/helpers/users.js";
|
import { isStaff } from "~/helpers/users.js";
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
|
|||||||
@@ -31,6 +31,9 @@ export const DEFAULT_FEATURE_FLAGS = validateValues({
|
|||||||
projectBackground: false,
|
projectBackground: false,
|
||||||
searchBackground: false,
|
searchBackground: false,
|
||||||
advancedDebugInfo: false,
|
advancedDebugInfo: false,
|
||||||
|
showProjectPageDownloadModalServersPromo: true,
|
||||||
|
showProjectPageCreateServersTooltip: true,
|
||||||
|
showProjectPageQuickServerButton: false,
|
||||||
// advancedRendering: true,
|
// advancedRendering: true,
|
||||||
// externalLinksNewTab: true,
|
// externalLinksNewTab: true,
|
||||||
// notUsingBlockers: false,
|
// notUsingBlockers: false,
|
||||||
|
|||||||
@@ -124,8 +124,8 @@ export class ModrinthServer {
|
|||||||
return dataURL;
|
return dataURL;
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error instanceof ModrinthServerError && error.statusCode === 404 && iconUrl) {
|
if (error instanceof ModrinthServerError && error.statusCode === 404) {
|
||||||
// Handle external icon processing
|
if (iconUrl) {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(iconUrl);
|
const response = await fetch(iconUrl);
|
||||||
if (!response.ok) throw new Error("Failed to fetch icon");
|
if (!response.ok) throw new Error("Failed to fetch icon");
|
||||||
@@ -145,7 +145,9 @@ export class ModrinthServer {
|
|||||||
ctx?.drawImage(img, 0, 0, 64, 64);
|
ctx?.drawImage(img, 0, 0, 64, 64);
|
||||||
canvas.toBlob(async (blob) => {
|
canvas.toBlob(async (blob) => {
|
||||||
if (blob) {
|
if (blob) {
|
||||||
const scaledFile = new File([blob], "server-icon.png", { type: "image/png" });
|
const scaledFile = new File([blob], "server-icon.png", {
|
||||||
|
type: "image/png",
|
||||||
|
});
|
||||||
await useServersFetch(`/create?path=/server-icon.png&type=file`, {
|
await useServersFetch(`/create?path=/server-icon.png&type=file`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
contentType: "application/octet-stream",
|
contentType: "application/octet-stream",
|
||||||
@@ -169,13 +171,16 @@ export class ModrinthServer {
|
|||||||
});
|
});
|
||||||
return dataURL;
|
return dataURL;
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (externalError: any) {
|
||||||
console.error("Failed to process external icon:", error);
|
console.debug("Could not process external icon:", externalError.message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
throw error;
|
||||||
}
|
}
|
||||||
} catch (error) {
|
}
|
||||||
console.error("Failed to process server icon:", error);
|
} catch (error: any) {
|
||||||
|
console.debug("Icon processing failed:", error.message);
|
||||||
}
|
}
|
||||||
|
|
||||||
sharedImage.value = undefined;
|
sharedImage.value = undefined;
|
||||||
@@ -239,6 +244,18 @@ export class ModrinthServer {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
if (error instanceof ModrinthServerError) {
|
||||||
|
if (error.statusCode === 404 && ["fs", "content"].includes(module)) {
|
||||||
|
console.debug(`Optional ${module} resource not found:`, error.message);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error.statusCode === 503) {
|
||||||
|
console.debug(`Temporary ${module} unavailable:`, error.message);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
this.errors[module] = {
|
this.errors[module] = {
|
||||||
error:
|
error:
|
||||||
error instanceof ModrinthServerError
|
error instanceof ModrinthServerError
|
||||||
|
|||||||
@@ -155,6 +155,7 @@ export class GeneralModule extends ServerModule implements ServerGeneral {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async setMotd(motd: string): Promise<void> {
|
async setMotd(motd: string): Promise<void> {
|
||||||
|
try {
|
||||||
const props = (await this.server.fetchConfigFile("ServerProperties")) as any;
|
const props = (await this.server.fetchConfigFile("ServerProperties")) as any;
|
||||||
if (props) {
|
if (props) {
|
||||||
props.motd = motd;
|
props.motd = motd;
|
||||||
@@ -169,5 +170,10 @@ export class GeneralModule extends ServerModule implements ServerGeneral {
|
|||||||
override: auth,
|
override: auth,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
} catch {
|
||||||
|
console.error(
|
||||||
|
"[Modrinth Servers] [General] Failed to set MOTD due to lack of server properties file.",
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { formatBytes } from "~/plugins/shorthands.js";
|
import { formatBytes } from "@modrinth/utils";
|
||||||
|
|
||||||
export const fileIsValid = (file, validationOptions) => {
|
export const fileIsValid = (file, validationOptions) => {
|
||||||
const { maxSize, alertOnInvalid } = validationOptions;
|
const { maxSize, alertOnInvalid } = validationOptions;
|
||||||
|
|||||||
@@ -32,7 +32,7 @@
|
|||||||
<h1 class="wrap-as-needed">
|
<h1 class="wrap-as-needed">
|
||||||
{{ project.title }}
|
{{ project.title }}
|
||||||
</h1>
|
</h1>
|
||||||
<Badge :type="project.status" />
|
<ProjectStatusBadge :status="project.status" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<h2>Project settings</h2>
|
<h2>Project settings</h2>
|
||||||
@@ -452,6 +452,16 @@
|
|||||||
{{ formatCategory(currentPlatform) }}.
|
{{ formatCategory(currentPlatform) }}.
|
||||||
</p>
|
</p>
|
||||||
</AutomaticAccordion>
|
</AutomaticAccordion>
|
||||||
|
<ServersPromo
|
||||||
|
v-if="flags.showProjectPageDownloadModalServersPromo"
|
||||||
|
:link="`/servers#plan`"
|
||||||
|
@close="
|
||||||
|
() => {
|
||||||
|
flags.showProjectPageDownloadModalServersPromo = false;
|
||||||
|
saveFeatureFlags();
|
||||||
|
}
|
||||||
|
"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</NewModal>
|
</NewModal>
|
||||||
@@ -495,6 +505,64 @@
|
|||||||
</button>
|
</button>
|
||||||
</ButtonStyled>
|
</ButtonStyled>
|
||||||
</div>
|
</div>
|
||||||
|
<Tooltip
|
||||||
|
v-if="canCreateServerFrom && flags.showProjectPageQuickServerButton"
|
||||||
|
theme="dismissable-prompt"
|
||||||
|
:triggers="[]"
|
||||||
|
:shown="flags.showProjectPageCreateServersTooltip"
|
||||||
|
:auto-hide="false"
|
||||||
|
placement="bottom-start"
|
||||||
|
>
|
||||||
|
<ButtonStyled size="large" circular>
|
||||||
|
<nuxt-link
|
||||||
|
v-tooltip="'Create a server'"
|
||||||
|
:to="`/servers?project=${project.id}#plan`"
|
||||||
|
@click="
|
||||||
|
() => {
|
||||||
|
flags.showProjectPageCreateServersTooltip = false;
|
||||||
|
saveFeatureFlags();
|
||||||
|
}
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<ServerPlusIcon aria-hidden="true" />
|
||||||
|
</nuxt-link>
|
||||||
|
</ButtonStyled>
|
||||||
|
<template #popper>
|
||||||
|
<div class="experimental-styles-within flex max-w-60 flex-col gap-1">
|
||||||
|
<div class="flex items-center justify-between gap-4">
|
||||||
|
<h3 class="m-0 flex items-center gap-2 text-base font-bold text-contrast">
|
||||||
|
Create a server
|
||||||
|
<TagItem
|
||||||
|
:style="{
|
||||||
|
'--_color': 'var(--color-brand)',
|
||||||
|
'--_bg-color': 'var(--color-brand-highlight)',
|
||||||
|
}"
|
||||||
|
>New</TagItem
|
||||||
|
>
|
||||||
|
</h3>
|
||||||
|
<ButtonStyled size="small" circular>
|
||||||
|
<button
|
||||||
|
v-tooltip="`Don't show again`"
|
||||||
|
@click="
|
||||||
|
() => {
|
||||||
|
flags.showProjectPageCreateServersTooltip = false;
|
||||||
|
saveFeatureFlags();
|
||||||
|
}
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<XIcon aria-hidden="true" />
|
||||||
|
</button>
|
||||||
|
</ButtonStyled>
|
||||||
|
</div>
|
||||||
|
<p class="m-0 text-wrap text-sm font-medium leading-tight text-secondary">
|
||||||
|
Modrinth Servers is the easiest way to play with your friends without hassle!
|
||||||
|
</p>
|
||||||
|
<p class="m-0 text-wrap text-sm font-bold text-primary">
|
||||||
|
Starting at $5<span class="text-xs"> / month</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</Tooltip>
|
||||||
<ClientOnly>
|
<ClientOnly>
|
||||||
<ButtonStyled
|
<ButtonStyled
|
||||||
size="large"
|
size="large"
|
||||||
@@ -850,12 +918,14 @@ import {
|
|||||||
ReportIcon,
|
ReportIcon,
|
||||||
ScaleIcon,
|
ScaleIcon,
|
||||||
SearchIcon,
|
SearchIcon,
|
||||||
|
ServerPlusIcon,
|
||||||
SettingsIcon,
|
SettingsIcon,
|
||||||
TagsIcon,
|
TagsIcon,
|
||||||
UsersIcon,
|
UsersIcon,
|
||||||
VersionIcon,
|
VersionIcon,
|
||||||
WrenchIcon,
|
WrenchIcon,
|
||||||
ModrinthIcon,
|
ModrinthIcon,
|
||||||
|
XIcon,
|
||||||
} from "@modrinth/assets";
|
} from "@modrinth/assets";
|
||||||
import {
|
import {
|
||||||
Avatar,
|
Avatar,
|
||||||
@@ -870,17 +940,27 @@ import {
|
|||||||
ProjectSidebarCreators,
|
ProjectSidebarCreators,
|
||||||
ProjectSidebarDetails,
|
ProjectSidebarDetails,
|
||||||
ProjectSidebarLinks,
|
ProjectSidebarLinks,
|
||||||
|
ProjectStatusBadge,
|
||||||
ScrollablePanel,
|
ScrollablePanel,
|
||||||
|
TagItem,
|
||||||
|
ServersPromo,
|
||||||
useRelativeTime,
|
useRelativeTime,
|
||||||
} from "@modrinth/ui";
|
} from "@modrinth/ui";
|
||||||
import VersionSummary from "@modrinth/ui/src/components/version/VersionSummary.vue";
|
import VersionSummary from "@modrinth/ui/src/components/version/VersionSummary.vue";
|
||||||
import { formatCategory, isRejected, isStaff, isUnderReview, renderString } from "@modrinth/utils";
|
import {
|
||||||
|
formatCategory,
|
||||||
|
formatProjectType,
|
||||||
|
isRejected,
|
||||||
|
isStaff,
|
||||||
|
isUnderReview,
|
||||||
|
renderString,
|
||||||
|
} from "@modrinth/utils";
|
||||||
import { navigateTo } from "#app";
|
import { navigateTo } from "#app";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
|
import { Tooltip } from "floating-vue";
|
||||||
import Accordion from "~/components/ui/Accordion.vue";
|
import Accordion from "~/components/ui/Accordion.vue";
|
||||||
import AdPlaceholder from "~/components/ui/AdPlaceholder.vue";
|
import AdPlaceholder from "~/components/ui/AdPlaceholder.vue";
|
||||||
import AutomaticAccordion from "~/components/ui/AutomaticAccordion.vue";
|
import AutomaticAccordion from "~/components/ui/AutomaticAccordion.vue";
|
||||||
import Badge from "~/components/ui/Badge.vue";
|
|
||||||
import Breadcrumbs from "~/components/ui/Breadcrumbs.vue";
|
import Breadcrumbs from "~/components/ui/Breadcrumbs.vue";
|
||||||
import CollectionCreateModal from "~/components/ui/CollectionCreateModal.vue";
|
import CollectionCreateModal from "~/components/ui/CollectionCreateModal.vue";
|
||||||
import MessageBanner from "~/components/ui/MessageBanner.vue";
|
import MessageBanner from "~/components/ui/MessageBanner.vue";
|
||||||
@@ -891,6 +971,7 @@ import NavTabs from "~/components/ui/NavTabs.vue";
|
|||||||
import ProjectMemberHeader from "~/components/ui/ProjectMemberHeader.vue";
|
import ProjectMemberHeader from "~/components/ui/ProjectMemberHeader.vue";
|
||||||
import { userCollectProject } from "~/composables/user.js";
|
import { userCollectProject } from "~/composables/user.js";
|
||||||
import { reportProject } from "~/utils/report-helpers.ts";
|
import { reportProject } from "~/utils/report-helpers.ts";
|
||||||
|
import { saveFeatureFlags } from "~/composables/featureFlags.ts";
|
||||||
|
|
||||||
const data = useNuxtApp();
|
const data = useNuxtApp();
|
||||||
const route = useNativeRoute();
|
const route = useNativeRoute();
|
||||||
@@ -1286,7 +1367,7 @@ featuredVersions.value.sort((a, b) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const projectTypeDisplay = computed(() =>
|
const projectTypeDisplay = computed(() =>
|
||||||
data.$formatProjectType(
|
formatProjectType(
|
||||||
data.$getProjectTypeForDisplay(project.value.project_type, project.value.loaders),
|
data.$getProjectTypeForDisplay(project.value.project_type, project.value.loaders),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -1304,6 +1385,10 @@ const description = computed(
|
|||||||
} by ${members.value.find((x) => x.is_owner)?.user?.username || "a Creator"} on Modrinth`,
|
} by ${members.value.find((x) => x.is_owner)?.user?.username || "a Creator"} on Modrinth`,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const canCreateServerFrom = computed(() => {
|
||||||
|
return project.value.project_type === "modpack" && project.value.server_side !== "unsupported";
|
||||||
|
});
|
||||||
|
|
||||||
if (!route.name.startsWith("type-id-settings")) {
|
if (!route.name.startsWith("type-id-settings")) {
|
||||||
useSeoMeta({
|
useSeoMeta({
|
||||||
title: () => title.value,
|
title: () => title.value,
|
||||||
@@ -1672,4 +1757,33 @@ const navLinks = computed(() => {
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.servers-popup {
|
||||||
|
box-shadow:
|
||||||
|
0 0 12px 1px rgba(0, 175, 92, 0.6),
|
||||||
|
var(--shadow-floating);
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
border-left: 6px solid transparent;
|
||||||
|
border-right: 6px solid transparent;
|
||||||
|
border-bottom: 6px solid var(--color-button-bg);
|
||||||
|
content: " ";
|
||||||
|
position: absolute;
|
||||||
|
top: -7px;
|
||||||
|
left: 17px;
|
||||||
|
}
|
||||||
|
&::after {
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
border-left: 5px solid transparent;
|
||||||
|
border-right: 5px solid transparent;
|
||||||
|
border-bottom: 5px solid var(--color-raised-bg);
|
||||||
|
content: " ";
|
||||||
|
position: absolute;
|
||||||
|
top: -5px;
|
||||||
|
left: 18px;
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
@@ -104,7 +104,7 @@
|
|||||||
<span class="label__title">Client-side</span>
|
<span class="label__title">Client-side</span>
|
||||||
<span class="label__description">
|
<span class="label__description">
|
||||||
Select based on if the
|
Select based on if the
|
||||||
{{ $formatProjectType(project.project_type).toLowerCase() }} has functionality on the
|
{{ formatProjectType(project.project_type).toLowerCase() }} has functionality on the
|
||||||
client side. Just because a mod works in Singleplayer doesn't mean it has actual
|
client side. Just because a mod works in Singleplayer doesn't mean it has actual
|
||||||
client-side functionality.
|
client-side functionality.
|
||||||
</span>
|
</span>
|
||||||
@@ -128,7 +128,7 @@
|
|||||||
<span class="label__title">Server-side</span>
|
<span class="label__title">Server-side</span>
|
||||||
<span class="label__description">
|
<span class="label__description">
|
||||||
Select based on if the
|
Select based on if the
|
||||||
{{ $formatProjectType(project.project_type).toLowerCase() }} has functionality on the
|
{{ formatProjectType(project.project_type).toLowerCase() }} has functionality on the
|
||||||
<strong>logical</strong> server. Remember that Singleplayer contains an integrated
|
<strong>logical</strong> server. Remember that Singleplayer contains an integrated
|
||||||
server.
|
server.
|
||||||
</span>
|
</span>
|
||||||
@@ -239,11 +239,10 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { formatProjectStatus } from "@modrinth/utils";
|
import { formatProjectStatus, formatProjectType } from "@modrinth/utils";
|
||||||
import { UploadIcon, SaveIcon, TrashIcon, XIcon, IssuesIcon, CheckIcon } from "@modrinth/assets";
|
import { UploadIcon, SaveIcon, TrashIcon, XIcon, IssuesIcon, CheckIcon } from "@modrinth/assets";
|
||||||
import { Multiselect } from "vue-multiselect";
|
import { Multiselect } from "vue-multiselect";
|
||||||
import { ConfirmModal } from "@modrinth/ui";
|
import { ConfirmModal, Avatar } from "@modrinth/ui";
|
||||||
import Avatar from "~/components/ui/Avatar.vue";
|
|
||||||
import FileInput from "~/components/ui/FileInput.vue";
|
import FileInput from "~/components/ui/FileInput.vue";
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<p>
|
<p>
|
||||||
Accurate tagging is important to help people find your
|
Accurate tagging is important to help people find your
|
||||||
{{ $formatProjectType(project.project_type).toLowerCase() }}. Make sure to select all tags
|
{{ formatProjectType(project.project_type).toLowerCase() }}. Make sure to select all tags
|
||||||
that apply.
|
that apply.
|
||||||
</p>
|
</p>
|
||||||
<p v-if="project.versions.length === 0" class="known-errors">
|
<p v-if="project.versions.length === 0" class="known-errors">
|
||||||
@@ -18,25 +18,25 @@
|
|||||||
<template v-for="header in Object.keys(categoryLists)" :key="`categories-${header}`">
|
<template v-for="header in Object.keys(categoryLists)" :key="`categories-${header}`">
|
||||||
<div class="label">
|
<div class="label">
|
||||||
<h4>
|
<h4>
|
||||||
<span class="label__title">{{ $formatCategoryHeader(header) }}</span>
|
<span class="label__title">{{ formatCategoryHeader(header) }}</span>
|
||||||
</h4>
|
</h4>
|
||||||
<span class="label__description">
|
<span class="label__description">
|
||||||
<template v-if="header === 'categories'">
|
<template v-if="header === 'categories'">
|
||||||
Select all categories that reflect the themes or function of your
|
Select all categories that reflect the themes or function of your
|
||||||
{{ $formatProjectType(project.project_type).toLowerCase() }}.
|
{{ formatProjectType(project.project_type).toLowerCase() }}.
|
||||||
</template>
|
</template>
|
||||||
<template v-else-if="header === 'features'">
|
<template v-else-if="header === 'features'">
|
||||||
Select all of the features that your
|
Select all of the features that your
|
||||||
{{ $formatProjectType(project.project_type).toLowerCase() }} makes use of.
|
{{ formatProjectType(project.project_type).toLowerCase() }} makes use of.
|
||||||
</template>
|
</template>
|
||||||
<template v-else-if="header === 'resolutions'">
|
<template v-else-if="header === 'resolutions'">
|
||||||
Select the resolution(s) of textures in your
|
Select the resolution(s) of textures in your
|
||||||
{{ $formatProjectType(project.project_type).toLowerCase() }}.
|
{{ formatProjectType(project.project_type).toLowerCase() }}.
|
||||||
</template>
|
</template>
|
||||||
<template v-else-if="header === 'performance impact'">
|
<template v-else-if="header === 'performance impact'">
|
||||||
Select the realistic performance impact of your
|
Select the realistic performance impact of your
|
||||||
{{ $formatProjectType(project.project_type).toLowerCase() }}. Select multiple if the
|
{{ formatProjectType(project.project_type).toLowerCase() }}. Select multiple if the
|
||||||
{{ $formatProjectType(project.project_type).toLowerCase() }} is configurable to
|
{{ formatProjectType(project.project_type).toLowerCase() }} is configurable to
|
||||||
different levels of performance impact.
|
different levels of performance impact.
|
||||||
</template>
|
</template>
|
||||||
</span>
|
</span>
|
||||||
@@ -46,7 +46,7 @@
|
|||||||
v-for="category in categoryLists[header]"
|
v-for="category in categoryLists[header]"
|
||||||
:key="`category-${header}-${category.name}`"
|
:key="`category-${header}-${category.name}`"
|
||||||
:model-value="selectedTags.includes(category)"
|
:model-value="selectedTags.includes(category)"
|
||||||
:description="$formatCategory(category.name)"
|
:description="formatCategory(category.name)"
|
||||||
class="category-selector"
|
class="category-selector"
|
||||||
@update:model-value="toggleCategory(category)"
|
@update:model-value="toggleCategory(category)"
|
||||||
>
|
>
|
||||||
@@ -57,7 +57,7 @@
|
|||||||
class="icon"
|
class="icon"
|
||||||
v-html="category.icon"
|
v-html="category.icon"
|
||||||
/>
|
/>
|
||||||
<span aria-hidden="true"> {{ $formatCategory(category.name) }}</span>
|
<span aria-hidden="true"> {{ formatCategory(category.name) }}</span>
|
||||||
</div>
|
</div>
|
||||||
</Checkbox>
|
</Checkbox>
|
||||||
</div>
|
</div>
|
||||||
@@ -80,7 +80,7 @@
|
|||||||
:key="`featured-category-${category.name}`"
|
:key="`featured-category-${category.name}`"
|
||||||
class="category-selector"
|
class="category-selector"
|
||||||
:model-value="featuredTags.includes(category)"
|
:model-value="featuredTags.includes(category)"
|
||||||
:description="$formatCategory(category.name)"
|
:description="formatCategory(category.name)"
|
||||||
:disabled="featuredTags.length >= 3 && !featuredTags.includes(category)"
|
:disabled="featuredTags.length >= 3 && !featuredTags.includes(category)"
|
||||||
@update:model-value="toggleFeaturedCategory(category)"
|
@update:model-value="toggleFeaturedCategory(category)"
|
||||||
>
|
>
|
||||||
@@ -91,7 +91,7 @@
|
|||||||
class="icon"
|
class="icon"
|
||||||
v-html="category.icon"
|
v-html="category.icon"
|
||||||
/>
|
/>
|
||||||
<span aria-hidden="true"> {{ $formatCategory(category.name) }}</span>
|
<span aria-hidden="true"> {{ formatCategory(category.name) }}</span>
|
||||||
</div>
|
</div>
|
||||||
</Checkbox>
|
</Checkbox>
|
||||||
</div>
|
</div>
|
||||||
@@ -114,6 +114,7 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { StarIcon, SaveIcon } from "@modrinth/assets";
|
import { StarIcon, SaveIcon } from "@modrinth/assets";
|
||||||
|
import { formatCategory, formatCategoryHeader, formatProjectType } from "@modrinth/utils";
|
||||||
import Checkbox from "~/components/ui/Checkbox.vue";
|
import Checkbox from "~/components/ui/Checkbox.vue";
|
||||||
|
|
||||||
export default defineNuxtComponent({
|
export default defineNuxtComponent({
|
||||||
@@ -222,6 +223,9 @@ export default defineNuxtComponent({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
formatProjectType,
|
||||||
|
formatCategoryHeader,
|
||||||
|
formatCategory,
|
||||||
toggleCategory(category) {
|
toggleCategory(category) {
|
||||||
if (this.selectedTags.includes(category)) {
|
if (this.selectedTags.includes(category)) {
|
||||||
this.selectedTags = this.selectedTags.filter((x) => x !== category);
|
this.selectedTags = this.selectedTags.filter((x) => x !== category);
|
||||||
|
|||||||
@@ -144,7 +144,7 @@
|
|||||||
<div v-else class="input-group">
|
<div v-else class="input-group">
|
||||||
<ButtonStyled v-if="primaryFile" color="brand">
|
<ButtonStyled v-if="primaryFile" color="brand">
|
||||||
<a
|
<a
|
||||||
v-tooltip="primaryFile.filename + ' (' + $formatBytes(primaryFile.size) + ')'"
|
v-tooltip="primaryFile.filename + ' (' + formatBytes(primaryFile.size) + ')'"
|
||||||
:href="primaryFile.url"
|
:href="primaryFile.url"
|
||||||
@click="emit('onDownload')"
|
@click="emit('onDownload')"
|
||||||
>
|
>
|
||||||
@@ -320,7 +320,7 @@
|
|||||||
<FileIcon aria-hidden="true" />
|
<FileIcon aria-hidden="true" />
|
||||||
<span class="filename">
|
<span class="filename">
|
||||||
<strong>{{ replaceFile.name }}</strong>
|
<strong>{{ replaceFile.name }}</strong>
|
||||||
<span class="file-size">({{ $formatBytes(replaceFile.size) }})</span>
|
<span class="file-size">({{ formatBytes(replaceFile.size) }})</span>
|
||||||
</span>
|
</span>
|
||||||
<FileInput
|
<FileInput
|
||||||
class="iconified-button raised-button"
|
class="iconified-button raised-button"
|
||||||
@@ -345,7 +345,7 @@
|
|||||||
<FileIcon aria-hidden="true" />
|
<FileIcon aria-hidden="true" />
|
||||||
<span class="filename">
|
<span class="filename">
|
||||||
<strong>{{ file.filename }}</strong>
|
<strong>{{ file.filename }}</strong>
|
||||||
<span class="file-size">({{ $formatBytes(file.size) }})</span>
|
<span class="file-size">({{ formatBytes(file.size) }})</span>
|
||||||
<span v-if="primaryFile.hashes.sha1 === file.hashes.sha1" class="file-type">
|
<span v-if="primaryFile.hashes.sha1 === file.hashes.sha1" class="file-type">
|
||||||
Primary
|
Primary
|
||||||
</span>
|
</span>
|
||||||
@@ -412,7 +412,7 @@
|
|||||||
<FileIcon aria-hidden="true" />
|
<FileIcon aria-hidden="true" />
|
||||||
<span class="filename">
|
<span class="filename">
|
||||||
<strong>{{ file.name }}</strong>
|
<strong>{{ file.name }}</strong>
|
||||||
<span class="file-size">({{ $formatBytes(file.size) }})</span>
|
<span class="file-size">({{ formatBytes(file.size) }})</span>
|
||||||
</span>
|
</span>
|
||||||
<multiselect
|
<multiselect
|
||||||
v-if="version.loaders.some((x) => tags.loaderData.dataPackLoaders.includes(x))"
|
v-if="version.loaders.some((x) => tags.loaderData.dataPackLoaders.includes(x))"
|
||||||
@@ -533,7 +533,7 @@
|
|||||||
)
|
)
|
||||||
.map((it) => it.name)
|
.map((it) => it.name)
|
||||||
"
|
"
|
||||||
:custom-label="(value) => $formatCategory(value)"
|
:custom-label="formatCategory"
|
||||||
:loading="tags.loaders.length === 0"
|
:loading="tags.loaders.length === 0"
|
||||||
:multiple="true"
|
:multiple="true"
|
||||||
:searchable="true"
|
:searchable="true"
|
||||||
@@ -630,7 +630,15 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
import { ButtonStyled, ConfirmModal, MarkdownEditor } from "@modrinth/ui";
|
import {
|
||||||
|
Avatar,
|
||||||
|
Badge,
|
||||||
|
CopyCode,
|
||||||
|
Checkbox,
|
||||||
|
ButtonStyled,
|
||||||
|
ConfirmModal,
|
||||||
|
MarkdownEditor,
|
||||||
|
} from "@modrinth/ui";
|
||||||
import {
|
import {
|
||||||
FileIcon,
|
FileIcon,
|
||||||
TrashIcon,
|
TrashIcon,
|
||||||
@@ -649,6 +657,7 @@ import {
|
|||||||
ChevronRightIcon,
|
ChevronRightIcon,
|
||||||
} from "@modrinth/assets";
|
} from "@modrinth/assets";
|
||||||
import { Multiselect } from "vue-multiselect";
|
import { Multiselect } from "vue-multiselect";
|
||||||
|
import { formatBytes, formatCategory } from "@modrinth/utils";
|
||||||
import { acceptFileFromProjectType } from "~/helpers/fileUtils.js";
|
import { acceptFileFromProjectType } from "~/helpers/fileUtils.js";
|
||||||
import { inferVersionInfo } from "~/helpers/infer.js";
|
import { inferVersionInfo } from "~/helpers/infer.js";
|
||||||
import { createDataPackVersion } from "~/helpers/package.js";
|
import { createDataPackVersion } from "~/helpers/package.js";
|
||||||
@@ -656,13 +665,9 @@ import { renderHighlightedString } from "~/helpers/highlight.js";
|
|||||||
import { reportVersion } from "~/utils/report-helpers.ts";
|
import { reportVersion } from "~/utils/report-helpers.ts";
|
||||||
import { useImageUpload } from "~/composables/image-upload.ts";
|
import { useImageUpload } from "~/composables/image-upload.ts";
|
||||||
|
|
||||||
import Avatar from "~/components/ui/Avatar.vue";
|
|
||||||
import AdPlaceholder from "~/components/ui/AdPlaceholder.vue";
|
import AdPlaceholder from "~/components/ui/AdPlaceholder.vue";
|
||||||
import Badge from "~/components/ui/Badge.vue";
|
|
||||||
import Breadcrumbs from "~/components/ui/Breadcrumbs.vue";
|
import Breadcrumbs from "~/components/ui/Breadcrumbs.vue";
|
||||||
import CopyCode from "~/components/ui/CopyCode.vue";
|
|
||||||
import Categories from "~/components/ui/search/Categories.vue";
|
import Categories from "~/components/ui/search/Categories.vue";
|
||||||
import Checkbox from "~/components/ui/Checkbox.vue";
|
|
||||||
import FileInput from "~/components/ui/FileInput.vue";
|
import FileInput from "~/components/ui/FileInput.vue";
|
||||||
import Modal from "~/components/ui/Modal.vue";
|
import Modal from "~/components/ui/Modal.vue";
|
||||||
|
|
||||||
@@ -958,6 +963,8 @@ export default defineNuxtComponent({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
formatBytes,
|
||||||
|
formatCategory,
|
||||||
async onImageUpload(file) {
|
async onImageUpload(file) {
|
||||||
const response = await useImageUpload(file, { context: "version" });
|
const response = await useImageUpload(file, { context: "version" });
|
||||||
|
|
||||||
|
|||||||
@@ -8,13 +8,11 @@ import {
|
|||||||
DownloadIcon,
|
DownloadIcon,
|
||||||
LinkIcon,
|
LinkIcon,
|
||||||
} from "@modrinth/assets";
|
} from "@modrinth/assets";
|
||||||
import Avatar from "~/components/ui/Avatar.vue";
|
import { Avatar, Checkbox, Badge } from "@modrinth/ui";
|
||||||
import LogoAnimated from "~/components/brand/LogoAnimated.vue";
|
import LogoAnimated from "~/components/brand/LogoAnimated.vue";
|
||||||
import Badge from "~/components/ui/Badge.vue";
|
|
||||||
import PrismIcon from "~/assets/images/external/prism.svg?component";
|
import PrismIcon from "~/assets/images/external/prism.svg?component";
|
||||||
import ATLauncher from "~/assets/images/external/atlauncher.svg?component";
|
import ATLauncher from "~/assets/images/external/atlauncher.svg?component";
|
||||||
import CurseForge from "~/assets/images/external/curseforge.svg?component";
|
import CurseForge from "~/assets/images/external/curseforge.svg?component";
|
||||||
import Checkbox from "~/components/ui/Checkbox.vue";
|
|
||||||
|
|
||||||
import { homePageProjects } from "~/generated/state.json";
|
import { homePageProjects } from "~/generated/state.json";
|
||||||
|
|
||||||
|
|||||||
@@ -97,7 +97,7 @@
|
|||||||
</template>
|
</template>
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ChevronRightIcon, HistoryIcon } from "@modrinth/assets";
|
import { ChevronRightIcon, HistoryIcon } from "@modrinth/assets";
|
||||||
import Avatar from "~/components/ui/Avatar.vue";
|
import { Avatar } from "@modrinth/ui";
|
||||||
import NotificationItem from "~/components/ui/NotificationItem.vue";
|
import NotificationItem from "~/components/ui/NotificationItem.vue";
|
||||||
import { fetchExtraNotificationData, groupNotifications } from "~/helpers/notifications.ts";
|
import { fetchExtraNotificationData, groupNotifications } from "~/helpers/notifications.ts";
|
||||||
|
|
||||||
|
|||||||
@@ -26,7 +26,7 @@
|
|||||||
v-if="notifTypes.length > 1"
|
v-if="notifTypes.length > 1"
|
||||||
v-model="selectedType"
|
v-model="selectedType"
|
||||||
:items="notifTypes"
|
:items="notifTypes"
|
||||||
:format-label="(x) => (x === 'all' ? 'All' : $formatProjectType(x).replace('_', ' ') + 's')"
|
:format-label="(x) => (x === 'all' ? 'All' : formatProjectType(x).replace('_', ' ') + 's')"
|
||||||
:capitalize="false"
|
:capitalize="false"
|
||||||
/>
|
/>
|
||||||
<p v-if="pending">Loading notifications...</p>
|
<p v-if="pending">Loading notifications...</p>
|
||||||
@@ -49,13 +49,16 @@
|
|||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
<p v-else>You don't have any unread notifications.</p>
|
<p v-else>You don't have any unread notifications.</p>
|
||||||
|
<div class="flex justify-end">
|
||||||
<Pagination :page="page" :count="pages" @switch-page="changePage" />
|
<Pagination :page="page" :count="pages" @switch-page="changePage" />
|
||||||
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script setup>
|
<script setup>
|
||||||
import { Button, Chips } from "@modrinth/ui";
|
import { Button, Pagination, Chips } from "@modrinth/ui";
|
||||||
import { HistoryIcon, CheckCheckIcon } from "@modrinth/assets";
|
import { HistoryIcon, CheckCheckIcon } from "@modrinth/assets";
|
||||||
|
import { formatProjectType } from "@modrinth/utils";
|
||||||
import {
|
import {
|
||||||
fetchExtraNotificationData,
|
fetchExtraNotificationData,
|
||||||
groupNotifications,
|
groupNotifications,
|
||||||
@@ -63,7 +66,6 @@ import {
|
|||||||
} from "~/helpers/notifications.ts";
|
} from "~/helpers/notifications.ts";
|
||||||
import NotificationItem from "~/components/ui/NotificationItem.vue";
|
import NotificationItem from "~/components/ui/NotificationItem.vue";
|
||||||
import Breadcrumbs from "~/components/ui/Breadcrumbs.vue";
|
import Breadcrumbs from "~/components/ui/Breadcrumbs.vue";
|
||||||
import Pagination from "~/components/ui/Pagination.vue";
|
|
||||||
|
|
||||||
useHead({
|
useHead({
|
||||||
title: "Notifications - Modrinth",
|
title: "Notifications - Modrinth",
|
||||||
|
|||||||
@@ -239,7 +239,7 @@
|
|||||||
<div>
|
<div>
|
||||||
<nuxt-link
|
<nuxt-link
|
||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
:to="`/${$getProjectTypeForUrl(project.project_type, project.loaders)}/${
|
:to="`/${getProjectTypeForUrl(project.project_type, project.loaders)}/${
|
||||||
project.slug ? project.slug : project.id
|
project.slug ? project.slug : project.id
|
||||||
}`"
|
}`"
|
||||||
>
|
>
|
||||||
@@ -261,7 +261,7 @@
|
|||||||
|
|
||||||
<nuxt-link
|
<nuxt-link
|
||||||
class="hover-link wrap-as-needed"
|
class="hover-link wrap-as-needed"
|
||||||
:to="`/${$getProjectTypeForUrl(project.project_type, project.loaders)}/${
|
:to="`/${getProjectTypeForUrl(project.project_type, project.loaders)}/${
|
||||||
project.slug ? project.slug : project.id
|
project.slug ? project.slug : project.id
|
||||||
}`"
|
}`"
|
||||||
>
|
>
|
||||||
@@ -275,22 +275,23 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
{{ $formatProjectType($getProjectTypeForUrl(project.project_type, project.loaders)) }}
|
{{ formatProjectType(getProjectTypeForUrl(project.project_type, project.loaders)) }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<Badge v-if="project.status" :type="project.status" class="status" />
|
<ProjectStatusBadge v-if="project.status" :status="project.status" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
|
<ButtonStyled circular>
|
||||||
<nuxt-link
|
<nuxt-link
|
||||||
class="square-button"
|
:to="`/${getProjectTypeForUrl(project.project_type, project.loaders)}/${
|
||||||
:to="`/${$getProjectTypeForUrl(project.project_type, project.loaders)}/${
|
|
||||||
project.slug ? project.slug : project.id
|
project.slug ? project.slug : project.id
|
||||||
}/settings`"
|
}/settings`"
|
||||||
>
|
>
|
||||||
<SettingsIcon />
|
<SettingsIcon />
|
||||||
</nuxt-link>
|
</nuxt-link>
|
||||||
|
</ButtonStyled>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -312,19 +313,25 @@ import {
|
|||||||
SortAscendingIcon as AscendingIcon,
|
SortAscendingIcon as AscendingIcon,
|
||||||
SortDescendingIcon as DescendingIcon,
|
SortDescendingIcon as DescendingIcon,
|
||||||
} from "@modrinth/assets";
|
} from "@modrinth/assets";
|
||||||
import { commonMessages } from "@modrinth/ui";
|
import {
|
||||||
|
Avatar,
|
||||||
|
ButtonStyled,
|
||||||
|
Checkbox,
|
||||||
|
CopyCode,
|
||||||
|
ProjectStatusBadge,
|
||||||
|
commonMessages,
|
||||||
|
} from "@modrinth/ui";
|
||||||
|
import { formatProjectType } from "@modrinth/utils";
|
||||||
|
|
||||||
import Badge from "~/components/ui/Badge.vue";
|
|
||||||
import Checkbox from "~/components/ui/Checkbox.vue";
|
|
||||||
import Modal from "~/components/ui/Modal.vue";
|
import Modal from "~/components/ui/Modal.vue";
|
||||||
import Avatar from "~/components/ui/Avatar.vue";
|
|
||||||
import ModalCreation from "~/components/ui/ModalCreation.vue";
|
import ModalCreation from "~/components/ui/ModalCreation.vue";
|
||||||
import CopyCode from "~/components/ui/CopyCode.vue";
|
import { getProjectTypeForUrl } from "~/helpers/projects.js";
|
||||||
|
|
||||||
export default defineNuxtComponent({
|
export default defineNuxtComponent({
|
||||||
components: {
|
components: {
|
||||||
Avatar,
|
Avatar,
|
||||||
Badge,
|
ButtonStyled,
|
||||||
|
ProjectStatusBadge,
|
||||||
SettingsIcon,
|
SettingsIcon,
|
||||||
TrashIcon,
|
TrashIcon,
|
||||||
Checkbox,
|
Checkbox,
|
||||||
@@ -390,6 +397,8 @@ export default defineNuxtComponent({
|
|||||||
this.DELETE_PROJECT = 1 << 7;
|
this.DELETE_PROJECT = 1 << 7;
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
getProjectTypeForUrl,
|
||||||
|
formatProjectType,
|
||||||
updateDescending() {
|
updateDescending() {
|
||||||
this.descending = !this.descending;
|
this.descending = !this.descending;
|
||||||
this.projects = this.updateSort(this.projects, this.sortBy, this.descending);
|
this.projects = this.updateSort(this.projects, this.sortBy, this.descending);
|
||||||
|
|||||||
@@ -76,7 +76,7 @@
|
|||||||
</span>
|
</span>
|
||||||
<template v-if="payout.method">
|
<template v-if="payout.method">
|
||||||
<span>⋅</span>
|
<span>⋅</span>
|
||||||
<span>{{ $formatWallet(payout.method) }} ({{ payout.method_address }})</span>
|
<span>{{ formatWallet(payout.method) }} ({{ payout.method_address }})</span>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -95,7 +95,7 @@
|
|||||||
</template>
|
</template>
|
||||||
<script setup>
|
<script setup>
|
||||||
import { XIcon, PayPalIcon, UnknownIcon } from "@modrinth/assets";
|
import { XIcon, PayPalIcon, UnknownIcon } from "@modrinth/assets";
|
||||||
import { capitalizeString } from "@modrinth/utils";
|
import { capitalizeString, formatWallet } from "@modrinth/utils";
|
||||||
import { Badge, Breadcrumbs, DropdownSelect } from "@modrinth/ui";
|
import { Badge, Breadcrumbs, DropdownSelect } from "@modrinth/ui";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import TremendousIcon from "~/assets/images/external/tremendous.svg?component";
|
import TremendousIcon from "~/assets/images/external/tremendous.svg?component";
|
||||||
|
|||||||
@@ -139,8 +139,8 @@
|
|||||||
<template v-if="knownErrors.length === 0 && amount">
|
<template v-if="knownErrors.length === 0 && amount">
|
||||||
<Checkbox v-if="fees > 0" v-model="agreedFees" description="Consent to fee">
|
<Checkbox v-if="fees > 0" v-model="agreedFees" description="Consent to fee">
|
||||||
I acknowledge that an estimated
|
I acknowledge that an estimated
|
||||||
{{ $formatMoney(fees) }} will be deducted from the amount I receive to cover
|
{{ formatMoney(fees) }} will be deducted from the amount I receive to cover
|
||||||
{{ $formatWallet(selectedMethod.type) }} processing fees.
|
{{ formatWallet(selectedMethod.type) }} processing fees.
|
||||||
</Checkbox>
|
</Checkbox>
|
||||||
<Checkbox v-model="agreedTransfer" description="Confirm transfer">
|
<Checkbox v-model="agreedTransfer" description="Confirm transfer">
|
||||||
<template v-if="selectedMethod.type === 'tremendous'">
|
<template v-if="selectedMethod.type === 'tremendous'">
|
||||||
@@ -149,7 +149,7 @@
|
|||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
I confirm that I am initiating a transfer to the following
|
I confirm that I am initiating a transfer to the following
|
||||||
{{ $formatWallet(selectedMethod.type) }} account: {{ withdrawAccount }}
|
{{ formatWallet(selectedMethod.type) }} account: {{ withdrawAccount }}
|
||||||
</template>
|
</template>
|
||||||
</Checkbox>
|
</Checkbox>
|
||||||
<Checkbox v-model="agreedTerms" class="rewards-checkbox">
|
<Checkbox v-model="agreedTerms" class="rewards-checkbox">
|
||||||
@@ -198,6 +198,7 @@ import {
|
|||||||
} from "@modrinth/assets";
|
} from "@modrinth/assets";
|
||||||
import { Chips, Checkbox, Breadcrumbs } from "@modrinth/ui";
|
import { Chips, Checkbox, Breadcrumbs } from "@modrinth/ui";
|
||||||
import { all } from "iso-3166-1";
|
import { all } from "iso-3166-1";
|
||||||
|
import { formatMoney, formatWallet } from "@modrinth/utils";
|
||||||
import VenmoIcon from "~/assets/images/external/venmo.svg?component";
|
import VenmoIcon from "~/assets/images/external/venmo.svg?component";
|
||||||
|
|
||||||
const auth = await useAuth();
|
const auth = await useAuth();
|
||||||
@@ -360,9 +361,7 @@ async function withdraw() {
|
|||||||
text:
|
text:
|
||||||
selectedMethod.value.type === "tremendous"
|
selectedMethod.value.type === "tremendous"
|
||||||
? "An email has been sent to your account with further instructions on how to redeem your payout!"
|
? "An email has been sent to your account with further instructions on how to redeem your payout!"
|
||||||
: `Payment has been sent to your ${data.$formatWallet(
|
: `Payment has been sent to your ${formatWallet(selectedMethod.value.type)} account!`,
|
||||||
selectedMethod.value.type,
|
|
||||||
)} account!`,
|
|
||||||
type: "success",
|
type: "success",
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|||||||
@@ -527,7 +527,7 @@
|
|||||||
</template>
|
</template>
|
||||||
<script setup>
|
<script setup>
|
||||||
import { Multiselect } from "vue-multiselect";
|
import { Multiselect } from "vue-multiselect";
|
||||||
import { ButtonStyled, useRelativeTime } from "@modrinth/ui";
|
import { Avatar, ButtonStyled, useRelativeTime } from "@modrinth/ui";
|
||||||
import {
|
import {
|
||||||
CompassIcon,
|
CompassIcon,
|
||||||
LogInIcon,
|
LogInIcon,
|
||||||
@@ -539,7 +539,6 @@ import {
|
|||||||
} from "@modrinth/assets";
|
} from "@modrinth/assets";
|
||||||
import PrismLauncherLogo from "~/assets/images/external/prism.svg?component";
|
import PrismLauncherLogo from "~/assets/images/external/prism.svg?component";
|
||||||
import ATLauncherLogo from "~/assets/images/external/atlauncher.svg?component";
|
import ATLauncherLogo from "~/assets/images/external/atlauncher.svg?component";
|
||||||
import Avatar from "~/components/ui/Avatar.vue";
|
|
||||||
import ProjectCard from "~/components/ui/ProjectCard.vue";
|
import ProjectCard from "~/components/ui/ProjectCard.vue";
|
||||||
|
|
||||||
import { homePageProjects, homePageSearch, homePageNotifs } from "~/generated/state.json";
|
import { homePageProjects, homePageSearch, homePageNotifs } from "~/generated/state.json";
|
||||||
|
|||||||
@@ -32,7 +32,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script setup>
|
<script setup>
|
||||||
import { formatNumber } from "~/plugins/shorthands.js";
|
import { formatNumber } from "@modrinth/utils";
|
||||||
|
|
||||||
useHead({
|
useHead({
|
||||||
title: "Staff overview - Modrinth",
|
title: "Staff overview - Modrinth",
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
<Chips
|
<Chips
|
||||||
v-model="projectType"
|
v-model="projectType"
|
||||||
:items="projectTypes"
|
:items="projectTypes"
|
||||||
:format-label="(x) => (x === 'all' ? 'All' : $formatProjectType(x) + 's')"
|
:format-label="(x) => (x === 'all' ? 'All' : formatProjectType(x) + 's')"
|
||||||
/>
|
/>
|
||||||
<button v-if="oldestFirst" class="iconified-button push-right" @click="oldestFirst = false">
|
<button v-if="oldestFirst" class="iconified-button push-right" @click="oldestFirst = false">
|
||||||
<SortDescendingIcon />
|
<SortDescendingIcon />
|
||||||
@@ -56,7 +56,7 @@
|
|||||||
<Avatar :src="project.icon_url" size="xs" no-shadow raised />
|
<Avatar :src="project.icon_url" size="xs" no-shadow raised />
|
||||||
<span class="stacked">
|
<span class="stacked">
|
||||||
<span class="title">{{ project.name }}</span>
|
<span class="title">{{ project.name }}</span>
|
||||||
<span>{{ $formatProjectType(project.inferred_project_type) }}</span>
|
<span>{{ formatProjectType(project.inferred_project_type) }}</span>
|
||||||
</span>
|
</span>
|
||||||
</nuxt-link>
|
</nuxt-link>
|
||||||
</div>
|
</div>
|
||||||
@@ -81,7 +81,9 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="mobile-row">
|
<div class="mobile-row">
|
||||||
is requesting to be
|
is requesting to be
|
||||||
<Badge :type="project.requested_status ? project.requested_status : 'approved'" />
|
<ProjectStatusBadge
|
||||||
|
:status="project.requested_status ? project.requested_status : 'approved'"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
@@ -103,7 +105,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { Chips, useRelativeTime } from "@modrinth/ui";
|
import { Avatar, ProjectStatusBadge, Chips, useRelativeTime } from "@modrinth/ui";
|
||||||
import {
|
import {
|
||||||
UnknownIcon,
|
UnknownIcon,
|
||||||
EyeIcon,
|
EyeIcon,
|
||||||
@@ -112,9 +114,7 @@ import {
|
|||||||
IssuesIcon,
|
IssuesIcon,
|
||||||
ScaleIcon,
|
ScaleIcon,
|
||||||
} from "@modrinth/assets";
|
} from "@modrinth/assets";
|
||||||
import Avatar from "~/components/ui/Avatar.vue";
|
import { formatProjectType } from "@modrinth/utils";
|
||||||
import Badge from "~/components/ui/Badge.vue";
|
|
||||||
import { formatProjectType } from "~/plugins/shorthands.js";
|
|
||||||
import { asEncodedJsonArray, fetchSegmented } from "~/utils/fetch-helpers.ts";
|
import { asEncodedJsonArray, fetchSegmented } from "~/utils/fetch-helpers.ts";
|
||||||
|
|
||||||
useHead({
|
useHead({
|
||||||
|
|||||||
@@ -272,7 +272,7 @@
|
|||||||
<div class="table-cell">
|
<div class="table-cell">
|
||||||
<BoxIcon />
|
<BoxIcon />
|
||||||
<span>{{
|
<span>{{
|
||||||
$formatProjectType(
|
formatProjectType(
|
||||||
$getProjectTypeForDisplay(project.project_types[0] ?? "project", project.loaders),
|
$getProjectTypeForDisplay(project.project_types[0] ?? "project", project.loaders),
|
||||||
)
|
)
|
||||||
}}</span>
|
}}</span>
|
||||||
@@ -313,6 +313,7 @@ import {
|
|||||||
} from "@modrinth/assets";
|
} from "@modrinth/assets";
|
||||||
import { Button, Modal, Avatar, CopyCode, Badge, Checkbox, commonMessages } from "@modrinth/ui";
|
import { Button, Modal, Avatar, CopyCode, Badge, Checkbox, commonMessages } from "@modrinth/ui";
|
||||||
|
|
||||||
|
import { formatProjectType } from "@modrinth/utils";
|
||||||
import ModalCreation from "~/components/ui/ModalCreation.vue";
|
import ModalCreation from "~/components/ui/ModalCreation.vue";
|
||||||
import OrganizationProjectTransferModal from "~/components/ui/OrganizationProjectTransferModal.vue";
|
import OrganizationProjectTransferModal from "~/components/ui/OrganizationProjectTransferModal.vue";
|
||||||
|
|
||||||
|
|||||||
@@ -520,7 +520,6 @@ async function serverInstall(project) {
|
|||||||
|
|
||||||
if (projectType.value.id === "modpack") {
|
if (projectType.value.id === "modpack") {
|
||||||
await server.value.general.reinstall(
|
await server.value.general.reinstall(
|
||||||
server.value.serverId,
|
|
||||||
false,
|
false,
|
||||||
project.project_id,
|
project.project_id,
|
||||||
version.id,
|
version.id,
|
||||||
|
|||||||
@@ -500,6 +500,7 @@
|
|||||||
|
|
||||||
<section
|
<section
|
||||||
id="plan"
|
id="plan"
|
||||||
|
pyro-hash="plan"
|
||||||
class="relative mt-24 flex flex-col bg-[radial-gradient(65%_50%_at_50%_-10%,var(--color-brand-highlight)_0%,var(--color-accent-contrast)_100%)] px-3 pt-24 md:mt-48 md:pt-48"
|
class="relative mt-24 flex flex-col bg-[radial-gradient(65%_50%_at_50%_-10%,var(--color-brand-highlight)_0%,var(--color-accent-contrast)_100%)] px-3 pt-24 md:mt-48 md:pt-48"
|
||||||
>
|
>
|
||||||
<div class="faded-brand-line absolute left-0 top-0 h-[1px] w-full"></div>
|
<div class="faded-brand-line absolute left-0 top-0 h-[1px] w-full"></div>
|
||||||
@@ -603,7 +604,9 @@
|
|||||||
<RightArrowIcon class="shrink-0" />
|
<RightArrowIcon class="shrink-0" />
|
||||||
</button>
|
</button>
|
||||||
</ButtonStyled>
|
</ButtonStyled>
|
||||||
<p class="m-0 text-sm">Starting at $3/GB RAM</p>
|
<p v-if="lowestPrice" class="m-0 text-sm">
|
||||||
|
Starting at {{ formatPrice(locale, lowestPrice, selectedCurrency, true) }} / month
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -622,20 +625,34 @@ import {
|
|||||||
VersionIcon,
|
VersionIcon,
|
||||||
ServerIcon,
|
ServerIcon,
|
||||||
} from "@modrinth/assets";
|
} from "@modrinth/assets";
|
||||||
|
import { computed } from "vue";
|
||||||
|
import { monthsInInterval } from "@modrinth/ui/src/utils/billing.ts";
|
||||||
|
import { formatPrice } from "@modrinth/utils";
|
||||||
|
import { useVIntl } from "@vintl/vintl";
|
||||||
import { products } from "~/generated/state.json";
|
import { products } from "~/generated/state.json";
|
||||||
import { useServersFetch } from "~/composables/servers/servers-fetch.ts";
|
import { useServersFetch } from "~/composables/servers/servers-fetch.ts";
|
||||||
import LoaderIcon from "~/components/ui/servers/icons/LoaderIcon.vue";
|
import LoaderIcon from "~/components/ui/servers/icons/LoaderIcon.vue";
|
||||||
import ServerPlanSelector from "~/components/ui/servers/marketing/ServerPlanSelector.vue";
|
import ServerPlanSelector from "~/components/ui/servers/marketing/ServerPlanSelector.vue";
|
||||||
import OptionGroup from "~/components/ui/OptionGroup.vue";
|
import OptionGroup from "~/components/ui/OptionGroup.vue";
|
||||||
|
|
||||||
|
const { locale } = useVIntl();
|
||||||
|
|
||||||
const billingPeriods = ref(["monthly", "quarterly"]);
|
const billingPeriods = ref(["monthly", "quarterly"]);
|
||||||
const billingPeriod = ref(billingPeriods.value.includes("quarterly") ? "quarterly" : "monthly");
|
const billingPeriod = ref(billingPeriods.value.includes("quarterly") ? "quarterly" : "monthly");
|
||||||
|
|
||||||
const pyroProducts = products.filter((p) => p.metadata.type === "pyro");
|
const pyroProducts = products
|
||||||
|
.filter((p) => p.metadata.type === "pyro")
|
||||||
|
.sort((a, b) => a.metadata.ram - b.metadata.ram);
|
||||||
const pyroPlanProducts = pyroProducts.filter(
|
const pyroPlanProducts = pyroProducts.filter(
|
||||||
(p) => p.metadata.ram === 4096 || p.metadata.ram === 6144 || p.metadata.ram === 8192,
|
(p) => p.metadata.ram === 4096 || p.metadata.ram === 6144 || p.metadata.ram === 8192,
|
||||||
);
|
);
|
||||||
pyroPlanProducts.sort((a, b) => a.metadata.ram - b.metadata.ram);
|
|
||||||
|
const lowestPrice = computed(() => {
|
||||||
|
const amount = pyroProducts[0]?.prices?.find(
|
||||||
|
(price) => price.currency_code === selectedCurrency.value,
|
||||||
|
)?.prices?.intervals?.[billingPeriod.value];
|
||||||
|
return amount ? amount / monthsInInterval[billingPeriod.value] : undefined;
|
||||||
|
});
|
||||||
|
|
||||||
const title = "Modrinth Servers";
|
const title = "Modrinth Servers";
|
||||||
const description =
|
const description =
|
||||||
@@ -799,6 +816,8 @@ async function fetchPaymentData() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const selectedProjectId = ref();
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const isAtCapacity = computed(
|
const isAtCapacity = computed(
|
||||||
() => isSmallAtCapacity.value && isMediumAtCapacity.value && isLargeAtCapacity.value,
|
() => isSmallAtCapacity.value && isMediumAtCapacity.value && isLargeAtCapacity.value,
|
||||||
@@ -817,7 +836,12 @@ const scrollToFaq = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
onMounted(scrollToFaq);
|
onMounted(() => {
|
||||||
|
scrollToFaq();
|
||||||
|
if (route.query?.project) {
|
||||||
|
selectedProjectId.value = route.query?.project;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
watch(() => route.hash, scrollToFaq);
|
watch(() => route.hash, scrollToFaq);
|
||||||
|
|
||||||
@@ -876,9 +900,9 @@ const selectProduct = async (product) => {
|
|||||||
await nextTick();
|
await nextTick();
|
||||||
|
|
||||||
if (product === "custom") {
|
if (product === "custom") {
|
||||||
purchaseModal.value?.show(billingPeriod.value);
|
purchaseModal.value?.show(billingPeriod.value, undefined, selectedProjectId.value);
|
||||||
} else {
|
} else {
|
||||||
purchaseModal.value?.show(billingPeriod.value, selectedProduct.value);
|
purchaseModal.value?.show(billingPeriod.value, selectedProduct.value, selectedProjectId.value);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -18,48 +18,25 @@
|
|||||||
v-if="serverData?.status === 'suspended' && serverData.suspension_reason === 'upgrading'"
|
v-if="serverData?.status === 'suspended' && serverData.suspension_reason === 'upgrading'"
|
||||||
class="flex min-h-[calc(100vh-4rem)] items-center justify-center text-contrast"
|
class="flex min-h-[calc(100vh-4rem)] items-center justify-center text-contrast"
|
||||||
>
|
>
|
||||||
<div class="flex max-w-lg flex-col items-center rounded-3xl bg-bg-raised p-6 shadow-xl">
|
<ErrorInformationCard
|
||||||
<div class="flex flex-col items-center text-center">
|
title="Server upgrading"
|
||||||
<div class="flex flex-col items-center gap-4">
|
description="Your server's hardware is currently being upgraded and will be back online shortly!"
|
||||||
<div class="grid place-content-center rounded-full bg-bg-blue p-4">
|
:icon="TransferIcon"
|
||||||
<TransferIcon class="size-12 text-blue" />
|
icon-color="blue"
|
||||||
</div>
|
:action="generalErrorAction"
|
||||||
<h1 class="m-0 mb-2 w-fit text-4xl font-bold">Server upgrading</h1>
|
/>
|
||||||
</div>
|
|
||||||
<p class="text-lg text-secondary">
|
|
||||||
Your server's hardware is currently being upgraded and will be back online shortly!
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-else-if="serverData?.status === 'suspended'"
|
v-else-if="serverData?.status === 'suspended'"
|
||||||
class="flex min-h-[calc(100vh-4rem)] items-center justify-center text-contrast"
|
class="flex min-h-[calc(100vh-4rem)] items-center justify-center text-contrast"
|
||||||
>
|
>
|
||||||
<div class="flex max-w-lg flex-col items-center rounded-3xl bg-bg-raised p-6 shadow-xl">
|
<ErrorInformationCard
|
||||||
<div class="flex flex-col items-center text-center">
|
title="Server suspended"
|
||||||
<div class="flex flex-col items-center gap-4">
|
:description="suspendedDescription"
|
||||||
<div class="grid place-content-center rounded-full bg-bg-orange p-4">
|
:icon="LockIcon"
|
||||||
<LockIcon class="size-12 text-orange" />
|
icon-color="orange"
|
||||||
</div>
|
:action="suspendedAction"
|
||||||
<h1 class="m-0 mb-2 w-fit text-4xl font-bold">Server suspended</h1>
|
/>
|
||||||
</div>
|
|
||||||
<p class="text-lg text-secondary">
|
|
||||||
{{
|
|
||||||
serverData.suspension_reason === "cancelled"
|
|
||||||
? "Your subscription has been cancelled."
|
|
||||||
: serverData.suspension_reason
|
|
||||||
? `Your server has been suspended: ${serverData.suspension_reason}`
|
|
||||||
: "Your server has been suspended."
|
|
||||||
}}
|
|
||||||
<br />
|
|
||||||
Contact Modrinth Support if you believe this is an error.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<ButtonStyled size="large" color="brand" @click="() => router.push('/settings/billing')">
|
|
||||||
<button class="mt-6 !w-full">Go to billing settings</button>
|
|
||||||
</ButtonStyled>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-else-if="
|
v-else-if="
|
||||||
@@ -68,110 +45,69 @@
|
|||||||
"
|
"
|
||||||
class="flex min-h-[calc(100vh-4rem)] items-center justify-center text-contrast"
|
class="flex min-h-[calc(100vh-4rem)] items-center justify-center text-contrast"
|
||||||
>
|
>
|
||||||
<div class="flex max-w-lg flex-col items-center rounded-3xl bg-bg-raised p-6 shadow-xl">
|
<ErrorInformationCard
|
||||||
<div class="flex flex-col items-center text-center">
|
title="An error occured."
|
||||||
<div class="flex flex-col items-center gap-4">
|
description="Please contact Modrinth Support."
|
||||||
<div class="grid place-content-center rounded-full bg-bg-orange p-4">
|
:icon="TransferIcon"
|
||||||
<TransferIcon class="size-12 text-orange" />
|
icon-color="orange"
|
||||||
</div>
|
:error-details="generalErrorDetails"
|
||||||
<h1 class="m-0 mb-2 w-fit text-4xl font-bold">Server not found</h1>
|
:action="generalErrorAction"
|
||||||
</div>
|
/>
|
||||||
<p class="text-lg text-secondary">
|
|
||||||
You don't have permission to view this server or it no longer exists. If you believe this
|
|
||||||
is an error, please contact Modrinth Support.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<UiCopyCode :text="JSON.stringify(server.moduleErrors?.general?.error)" />
|
|
||||||
|
|
||||||
<ButtonStyled size="large" color="brand" @click="() => router.push('/servers/manage')">
|
|
||||||
<button class="mt-6 !w-full">Go back to all servers</button>
|
|
||||||
</ButtonStyled>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-else-if="server.moduleErrors?.general?.error.statusCode === 503"
|
v-else-if="server.moduleErrors?.general?.error.statusCode === 503"
|
||||||
class="flex min-h-[calc(100vh-4rem)] items-center justify-center text-contrast"
|
class="flex min-h-[calc(100vh-4rem)] items-center justify-center text-contrast"
|
||||||
>
|
>
|
||||||
<div class="flex max-w-lg flex-col items-center rounded-3xl bg-bg-raised p-6 shadow-xl">
|
<ErrorInformationCard
|
||||||
<div class="flex flex-col items-center text-center">
|
title="Server Node Unavailable"
|
||||||
<div class="flex flex-col items-center gap-4">
|
:icon="PanelErrorIcon"
|
||||||
<div class="grid place-content-center rounded-full bg-bg-red p-4">
|
icon-color="red"
|
||||||
<UiServersIconsPanelErrorIcon class="size-12 text-red" />
|
:action="nodeUnavailableAction"
|
||||||
</div>
|
:error-details="nodeUnavailableDetails"
|
||||||
<h1 class="m-0 mb-4 w-fit text-4xl font-bold">Server Node Unavailable</h1>
|
>
|
||||||
</div>
|
<template #description>
|
||||||
<p class="m-0 mb-4 leading-[170%] text-secondary">
|
<div class="text-md space-y-4">
|
||||||
|
<p class="leading-[170%] text-secondary">
|
||||||
Your server's node, where your Modrinth Server is physically hosted, is experiencing
|
Your server's node, where your Modrinth Server is physically hosted, is experiencing
|
||||||
issues. We are working with our datacenter to resolve the issue as quickly as possible.
|
issues. We are working with our datacenter to resolve the issue as quickly as possible.
|
||||||
</p>
|
</p>
|
||||||
<p class="m-0 mb-4 leading-[170%] text-secondary">
|
<p class="leading-[170%] text-secondary">
|
||||||
Your data is safe and will not be lost, and your server will be back online as soon as the
|
Your data is safe and will not be lost, and your server will be back online as soon as
|
||||||
issue is resolved.
|
the issue is resolved.
|
||||||
</p>
|
</p>
|
||||||
<p class="m-0 mb-4 leading-[170%] text-secondary">
|
<p class="leading-[170%] text-secondary">
|
||||||
For updates, please join the Modrinth Discord or contact Modrinth Support via the chat
|
For updates, please join the Modrinth Discord or contact Modrinth Support via the chat
|
||||||
bubble in the bottom right corner and we'll be happy to help.
|
bubble in the bottom right corner and we'll be happy to help.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div class="flex flex-col gap-2">
|
|
||||||
<UiCopyCode :text="'Server ID: ' + server.serverId" />
|
|
||||||
<UiCopyCode :text="'Node: ' + server.general?.datacenter" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<ButtonStyled
|
|
||||||
size="large"
|
|
||||||
color="standard"
|
|
||||||
@click="
|
|
||||||
() =>
|
|
||||||
navigateTo('https://discord.modrinth.com', {
|
|
||||||
external: true,
|
|
||||||
})
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<button class="mt-6 !w-full">Join Modrinth Discord</button>
|
|
||||||
</ButtonStyled>
|
|
||||||
<ButtonStyled
|
|
||||||
:disabled="formattedTime !== '00'"
|
|
||||||
size="large"
|
|
||||||
color="standard"
|
|
||||||
@click="() => reloadNuxtApp()"
|
|
||||||
>
|
|
||||||
<button class="mt-3 !w-full">Reload</button>
|
|
||||||
</ButtonStyled>
|
|
||||||
</div>
|
</div>
|
||||||
|
</template>
|
||||||
|
</ErrorInformationCard>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-else-if="server.moduleErrors?.general?.error"
|
v-else-if="server.moduleErrors?.general?.error"
|
||||||
class="flex min-h-[calc(100vh-4rem)] items-center justify-center text-contrast"
|
class="flex min-h-[calc(100vh-4rem)] items-center justify-center text-contrast"
|
||||||
>
|
>
|
||||||
<div class="flex max-w-lg flex-col items-center rounded-3xl bg-bg-raised p-6 shadow-xl">
|
<ErrorInformationCard
|
||||||
<div class="flex flex-col items-center text-center">
|
title="Connection lost"
|
||||||
<div class="flex flex-col items-center gap-4">
|
description=""
|
||||||
<div class="grid place-content-center rounded-full bg-bg-orange p-4">
|
:icon="TransferIcon"
|
||||||
<TransferIcon class="size-12 text-orange" />
|
icon-color="orange"
|
||||||
</div>
|
:action="connectionLostAction"
|
||||||
<h1 class="m-0 mb-2 w-fit text-4xl font-bold">Connection lost</h1>
|
>
|
||||||
|
<template #description>
|
||||||
|
<div class="space-y-4">
|
||||||
<div class="text-center text-secondary">
|
<div class="text-center text-secondary">
|
||||||
{{
|
{{
|
||||||
formattedTime == "00" ? "Reconnecting..." : `Retrying in ${formattedTime} seconds...`
|
formattedTime == "00" ? "Reconnecting..." : `Retrying in ${formattedTime} seconds...`
|
||||||
}}
|
}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
<p class="text-lg text-secondary">
|
<p class="text-lg text-secondary">
|
||||||
Something went wrong, and we couldn't connect to your server. This is likely due to a
|
Something went wrong, and we couldn't connect to your server. This is likely due to a
|
||||||
temporary network issue. You'll be reconnected automatically.
|
temporary network issue. You'll be reconnected automatically.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<UiCopyCode :text="JSON.stringify(server.moduleErrors?.general?.error)" />
|
</template>
|
||||||
<ButtonStyled
|
</ErrorInformationCard>
|
||||||
:disabled="formattedTime !== '00'"
|
|
||||||
size="large"
|
|
||||||
color="brand"
|
|
||||||
@click="() => reloadNuxtApp()"
|
|
||||||
>
|
|
||||||
<button class="mt-6 !w-full">Reload</button>
|
|
||||||
</ButtonStyled>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<!-- SERVER START -->
|
<!-- SERVER START -->
|
||||||
<div
|
<div
|
||||||
@@ -432,7 +368,7 @@ import {
|
|||||||
LockIcon,
|
LockIcon,
|
||||||
} from "@modrinth/assets";
|
} from "@modrinth/assets";
|
||||||
import DOMPurify from "dompurify";
|
import DOMPurify from "dompurify";
|
||||||
import { ButtonStyled, ServerNotice } from "@modrinth/ui";
|
import { ButtonStyled, ErrorInformationCard, ServerNotice } from "@modrinth/ui";
|
||||||
import { Intercom, shutdown } from "@intercom/messenger-js-sdk";
|
import { Intercom, shutdown } from "@intercom/messenger-js-sdk";
|
||||||
import type { MessageDescriptor } from "@vintl/vintl";
|
import type { MessageDescriptor } from "@vintl/vintl";
|
||||||
import type {
|
import type {
|
||||||
@@ -448,6 +384,7 @@ import { useModrinthServersConsole } from "~/store/console.ts";
|
|||||||
import { useServersFetch } from "~/composables/servers/servers-fetch.ts";
|
import { useServersFetch } from "~/composables/servers/servers-fetch.ts";
|
||||||
import { ModrinthServer, useModrinthServers } from "~/composables/servers/modrinth-servers.ts";
|
import { ModrinthServer, useModrinthServers } from "~/composables/servers/modrinth-servers.ts";
|
||||||
import ServerInstallation from "~/components/ui/servers/ServerInstallation.vue";
|
import ServerInstallation from "~/components/ui/servers/ServerInstallation.vue";
|
||||||
|
import PanelErrorIcon from "~/components/ui/servers/icons/PanelErrorIcon.vue";
|
||||||
|
|
||||||
const app = useNuxtApp() as unknown as { $notify: any };
|
const app = useNuxtApp() as unknown as { $notify: any };
|
||||||
|
|
||||||
@@ -760,7 +697,7 @@ const startUptimeUpdates = () => {
|
|||||||
const stopUptimeUpdates = () => {
|
const stopUptimeUpdates = () => {
|
||||||
if (uptimeIntervalId) {
|
if (uptimeIntervalId) {
|
||||||
clearInterval(uptimeIntervalId);
|
clearInterval(uptimeIntervalId);
|
||||||
intervalId = null;
|
pollingIntervalId = null;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1055,7 +992,7 @@ const notifyError = (title: string, text: string) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
let intervalId: ReturnType<typeof setInterval> | null = null;
|
let pollingIntervalId: ReturnType<typeof setInterval> | null = null;
|
||||||
const countdown = ref(15);
|
const countdown = ref(15);
|
||||||
|
|
||||||
const formattedTime = computed(() => {
|
const formattedTime = computed(() => {
|
||||||
@@ -1099,23 +1036,142 @@ const backupInProgress = computed(() => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const stopPolling = () => {
|
const stopPolling = () => {
|
||||||
if (intervalId) {
|
if (pollingIntervalId) {
|
||||||
clearInterval(intervalId);
|
clearTimeout(pollingIntervalId);
|
||||||
intervalId = null;
|
pollingIntervalId = null;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const startPolling = () => {
|
const startPolling = () => {
|
||||||
countdown.value = 15;
|
stopPolling();
|
||||||
intervalId = setInterval(() => {
|
|
||||||
if (countdown.value <= 0) {
|
let retryCount = 0;
|
||||||
reloadNuxtApp();
|
const maxRetries = 10;
|
||||||
} else {
|
|
||||||
countdown.value--;
|
const poll = async () => {
|
||||||
|
try {
|
||||||
|
await server.refresh(["general", "ws"]);
|
||||||
|
|
||||||
|
if (!server.moduleErrors?.general?.error) {
|
||||||
|
stopPolling();
|
||||||
|
connectWebSocket();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
retryCount++;
|
||||||
|
if (retryCount >= maxRetries) {
|
||||||
|
console.error("Max retries reached, stopping polling");
|
||||||
|
stopPolling();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exponential backoff: 3s, 6s, 12s, 24s, etc.
|
||||||
|
const delay = Math.min(3000 * Math.pow(2, retryCount - 1), 60000);
|
||||||
|
|
||||||
|
pollingIntervalId = setTimeout(poll, delay);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Polling failed:", error);
|
||||||
|
retryCount++;
|
||||||
|
|
||||||
|
if (retryCount < maxRetries) {
|
||||||
|
const delay = Math.min(3000 * Math.pow(2, retryCount - 1), 60000);
|
||||||
|
pollingIntervalId = setTimeout(poll, delay);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}, 1000);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
poll();
|
||||||
|
};
|
||||||
|
|
||||||
|
const nodeUnavailableDetails = computed(() => [
|
||||||
|
{
|
||||||
|
label: "Server ID",
|
||||||
|
value: server.serverId,
|
||||||
|
type: "inline" as const,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Node",
|
||||||
|
value: server.general?.datacenter ?? "Unknown! Please contact support!",
|
||||||
|
type: "inline" as const,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
const suspendedDescription = computed(() => {
|
||||||
|
if (serverData.value?.suspension_reason === "cancelled") {
|
||||||
|
return "Your subscription has been cancelled.\nContact Modrinth Support if you believe this is an error.";
|
||||||
|
}
|
||||||
|
if (serverData.value?.suspension_reason) {
|
||||||
|
return `Your server has been suspended: ${serverData.value.suspension_reason}\nContact Modrinth Support if you believe this is an error.`;
|
||||||
|
}
|
||||||
|
return "Your server has been suspended.\nContact Modrinth Support if you believe this is an error.";
|
||||||
|
});
|
||||||
|
|
||||||
|
const generalErrorDetails = computed(() => [
|
||||||
|
{
|
||||||
|
label: "Server ID",
|
||||||
|
value: server.serverId,
|
||||||
|
type: "inline" as const,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Timestamp",
|
||||||
|
value: String(server.moduleErrors?.general?.timestamp),
|
||||||
|
type: "inline" as const,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Error Name",
|
||||||
|
value: server.moduleErrors?.general?.error.name,
|
||||||
|
type: "inline" as const,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Error Message",
|
||||||
|
value: server.moduleErrors?.general?.error.message,
|
||||||
|
type: "block" as const,
|
||||||
|
},
|
||||||
|
...(server.moduleErrors?.general?.error.originalError
|
||||||
|
? [
|
||||||
|
{
|
||||||
|
label: "Original Error",
|
||||||
|
value: String(server.moduleErrors.general.error.originalError),
|
||||||
|
type: "hidden" as const,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
: []),
|
||||||
|
...(server.moduleErrors?.general?.error.stack
|
||||||
|
? [
|
||||||
|
{
|
||||||
|
label: "Stack Trace",
|
||||||
|
value: server.moduleErrors.general.error.stack,
|
||||||
|
type: "hidden" as const,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
: []),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const suspendedAction = computed(() => ({
|
||||||
|
label: "Go to billing settings",
|
||||||
|
onClick: () => router.push("/settings/billing"),
|
||||||
|
color: "brand" as const,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const generalErrorAction = computed(() => ({
|
||||||
|
label: "Go back to all servers",
|
||||||
|
onClick: () => router.push("/servers/manage"),
|
||||||
|
color: "brand" as const,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const nodeUnavailableAction = computed(() => ({
|
||||||
|
label: "Join Modrinth Discord",
|
||||||
|
onClick: () => navigateTo("https://discord.modrinth.com", { external: true }),
|
||||||
|
color: "standard" as const,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const connectionLostAction = computed(() => ({
|
||||||
|
label: "Reload",
|
||||||
|
onClick: () => reloadNuxtApp(),
|
||||||
|
color: "brand" as const,
|
||||||
|
disabled: formattedTime.value !== "00",
|
||||||
|
}));
|
||||||
|
|
||||||
const copyServerDebugInfo = () => {
|
const copyServerDebugInfo = () => {
|
||||||
const debugInfo = `Server ID: ${serverData.value?.server_id}\nError: ${errorMessage.value}\nKind: ${serverData.value?.upstream?.kind}\nProject ID: ${serverData.value?.upstream?.project_id}\nVersion ID: ${serverData.value?.upstream?.version_id}\nLog: ${errorLog.value}`;
|
const debugInfo = `Server ID: ${serverData.value?.server_id}\nError: ${errorMessage.value}\nKind: ${serverData.value?.upstream?.kind}\nProject ID: ${serverData.value?.upstream?.project_id}\nVersion ID: ${serverData.value?.upstream?.version_id}\nLog: ${errorLog.value}`;
|
||||||
navigator.clipboard.writeText(debugInfo);
|
navigator.clipboard.writeText(debugInfo);
|
||||||
|
|||||||
@@ -155,16 +155,11 @@ const saveGeneral = async () => {
|
|||||||
if (serverSubdomain.value !== data.value?.net?.domain) {
|
if (serverSubdomain.value !== data.value?.net?.domain) {
|
||||||
try {
|
try {
|
||||||
// type shit backend makes me do
|
// type shit backend makes me do
|
||||||
const response = await props.server.network?.checkSubdomainAvailability(
|
const available = await props.server.network?.checkSubdomainAvailability(
|
||||||
serverSubdomain.value,
|
serverSubdomain.value,
|
||||||
);
|
);
|
||||||
if (response === undefined) {
|
|
||||||
throw new Error("Failed to check subdomain availability");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof response === "object" && response !== null && "available" in response) {
|
if (!available) {
|
||||||
const typedResponse = response as { available: boolean };
|
|
||||||
if (!typedResponse.available) {
|
|
||||||
addNotification({
|
addNotification({
|
||||||
group: "serverOptions",
|
group: "serverOptions",
|
||||||
type: "error",
|
type: "error",
|
||||||
@@ -173,9 +168,6 @@ const saveGeneral = async () => {
|
|||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
throw new Error("Invalid response format from availability check");
|
|
||||||
}
|
|
||||||
|
|
||||||
await props.server.network?.changeSubdomain(serverSubdomain.value);
|
await props.server.network?.changeSubdomain(serverSubdomain.value);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -330,8 +330,7 @@
|
|||||||
<ButtonStyled
|
<ButtonStyled
|
||||||
v-if="
|
v-if="
|
||||||
getPyroCharge(subscription) &&
|
getPyroCharge(subscription) &&
|
||||||
getPyroCharge(subscription).status !== 'cancelled' &&
|
getPyroCharge(subscription).status !== 'cancelled'
|
||||||
getPyroCharge(subscription).status !== 'failed'
|
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
<button @click="showCancellationSurvey(subscription)">
|
<button @click="showCancellationSurvey(subscription)">
|
||||||
|
|||||||
@@ -200,9 +200,9 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { CodeIcon, RadioButtonCheckedIcon, RadioButtonIcon } from "@modrinth/assets";
|
import { CodeIcon, RadioButtonCheckedIcon, RadioButtonIcon } from "@modrinth/assets";
|
||||||
import { Button, ThemeSelector } from "@modrinth/ui";
|
import { Button, ThemeSelector } from "@modrinth/ui";
|
||||||
|
import { formatProjectType } from "@modrinth/utils";
|
||||||
import MessageBanner from "~/components/ui/MessageBanner.vue";
|
import MessageBanner from "~/components/ui/MessageBanner.vue";
|
||||||
import type { DisplayLocation } from "~/plugins/cosmetics";
|
import type { DisplayLocation } from "~/plugins/cosmetics";
|
||||||
import { formatProjectType } from "~/plugins/shorthands.js";
|
|
||||||
import { isDarkTheme, type Theme } from "~/plugins/theme/index.ts";
|
import { isDarkTheme, type Theme } from "~/plugins/theme/index.ts";
|
||||||
|
|
||||||
useHead({
|
useHead({
|
||||||
|
|||||||
@@ -205,6 +205,7 @@
|
|||||||
import { PlusIcon, XIcon, TrashIcon, EditIcon, SaveIcon } from "@modrinth/assets";
|
import { PlusIcon, XIcon, TrashIcon, EditIcon, SaveIcon } from "@modrinth/assets";
|
||||||
import {
|
import {
|
||||||
Checkbox,
|
Checkbox,
|
||||||
|
CopyCode,
|
||||||
ConfirmModal,
|
ConfirmModal,
|
||||||
commonSettingsMessages,
|
commonSettingsMessages,
|
||||||
commonMessages,
|
commonMessages,
|
||||||
@@ -219,7 +220,6 @@ import {
|
|||||||
getScopeValue,
|
getScopeValue,
|
||||||
} from "~/composables/auth/scopes.ts";
|
} from "~/composables/auth/scopes.ts";
|
||||||
|
|
||||||
import CopyCode from "~/components/ui/CopyCode.vue";
|
|
||||||
import Modal from "~/components/ui/Modal.vue";
|
import Modal from "~/components/ui/Modal.vue";
|
||||||
|
|
||||||
const { formatMessage } = useVIntl();
|
const { formatMessage } = useVIntl();
|
||||||
|
|||||||
@@ -355,6 +355,7 @@ import {
|
|||||||
GlobeIcon,
|
GlobeIcon,
|
||||||
} from "@modrinth/assets";
|
} from "@modrinth/assets";
|
||||||
import {
|
import {
|
||||||
|
Avatar,
|
||||||
OverflowMenu,
|
OverflowMenu,
|
||||||
ButtonStyled,
|
ButtonStyled,
|
||||||
ContentPageHeader,
|
ContentPageHeader,
|
||||||
@@ -377,7 +378,6 @@ import BetaTesterBadge from "~/assets/images/badges/beta-tester.svg?component";
|
|||||||
|
|
||||||
import UpToDate from "~/assets/images/illustrations/up_to_date.svg?component";
|
import UpToDate from "~/assets/images/illustrations/up_to_date.svg?component";
|
||||||
import ModalCreation from "~/components/ui/ModalCreation.vue";
|
import ModalCreation from "~/components/ui/ModalCreation.vue";
|
||||||
import Avatar from "~/components/ui/Avatar.vue";
|
|
||||||
import CollectionCreateModal from "~/components/ui/CollectionCreateModal.vue";
|
import CollectionCreateModal from "~/components/ui/CollectionCreateModal.vue";
|
||||||
import AdPlaceholder from "~/components/ui/AdPlaceholder.vue";
|
import AdPlaceholder from "~/components/ui/AdPlaceholder.vue";
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,10 @@ export default defineNuxtPlugin((nuxtApp) => {
|
|||||||
instantMove: true,
|
instantMove: true,
|
||||||
distance: 8,
|
distance: 8,
|
||||||
},
|
},
|
||||||
|
"dismissable-prompt": {
|
||||||
|
$extend: "dropdown",
|
||||||
|
placement: "bottom-start",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -13,11 +13,6 @@ export default defineNuxtPlugin((nuxtApp) => {
|
|||||||
|
|
||||||
return cosmeticsStore.externalLinksNewTab ? "_blank" : "";
|
return cosmeticsStore.externalLinksNewTab ? "_blank" : "";
|
||||||
});
|
});
|
||||||
nuxtApp.provide("formatBytes", formatBytes);
|
|
||||||
nuxtApp.provide("formatWallet", formatWallet);
|
|
||||||
nuxtApp.provide("formatProjectType", formatProjectType);
|
|
||||||
nuxtApp.provide("formatCategory", formatCategory);
|
|
||||||
nuxtApp.provide("formatCategoryHeader", formatCategoryHeader);
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Only use on the complete list of versions for a project, partial lists will generate
|
Only use on the complete list of versions for a project, partial lists will generate
|
||||||
@@ -156,89 +151,10 @@ export const formatMoney = (number, abbreviate = false) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const formatBytes = (bytes, decimals = 2) => {
|
|
||||||
if (bytes === 0) {
|
|
||||||
return "0 Bytes";
|
|
||||||
}
|
|
||||||
|
|
||||||
const k = 1024;
|
|
||||||
const dm = decimals < 0 ? 0 : decimals;
|
|
||||||
const sizes = ["Bytes", "KiB", "MiB", "GiB"];
|
|
||||||
|
|
||||||
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
||||||
|
|
||||||
return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + " " + sizes[i];
|
|
||||||
};
|
|
||||||
|
|
||||||
export const capitalizeString = (name) => {
|
export const capitalizeString = (name) => {
|
||||||
return name ? name.charAt(0).toUpperCase() + name.slice(1) : name;
|
return name ? name.charAt(0).toUpperCase() + name.slice(1) : name;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const formatWallet = (name) => {
|
|
||||||
if (name === "paypal") {
|
|
||||||
return "PayPal";
|
|
||||||
}
|
|
||||||
return capitalizeString(name);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const formatProjectType = (name) => {
|
|
||||||
if (name === "resourcepack") {
|
|
||||||
return "Resource Pack";
|
|
||||||
} else if (name === "datapack") {
|
|
||||||
return "Data Pack";
|
|
||||||
}
|
|
||||||
|
|
||||||
return capitalizeString(name);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const formatCategory = (name) => {
|
|
||||||
if (name === "modloader") {
|
|
||||||
return "Risugami's ModLoader";
|
|
||||||
} else if (name === "bungeecord") {
|
|
||||||
return "BungeeCord";
|
|
||||||
} else if (name === "liteloader") {
|
|
||||||
return "LiteLoader";
|
|
||||||
} else if (name === "neoforge") {
|
|
||||||
return "NeoForge";
|
|
||||||
} else if (name === "game-mechanics") {
|
|
||||||
return "Game Mechanics";
|
|
||||||
} else if (name === "worldgen") {
|
|
||||||
return "World Generation";
|
|
||||||
} else if (name === "core-shaders") {
|
|
||||||
return "Core Shaders";
|
|
||||||
} else if (name === "gui") {
|
|
||||||
return "GUI";
|
|
||||||
} else if (name === "8x-") {
|
|
||||||
return "8x or lower";
|
|
||||||
} else if (name === "512x+") {
|
|
||||||
return "512x or higher";
|
|
||||||
} else if (name === "kitchen-sink") {
|
|
||||||
return "Kitchen Sink";
|
|
||||||
} else if (name === "path-tracing") {
|
|
||||||
return "Path Tracing";
|
|
||||||
} else if (name === "pbr") {
|
|
||||||
return "PBR";
|
|
||||||
} else if (name === "datapack") {
|
|
||||||
return "Data Pack";
|
|
||||||
} else if (name === "colored-lighting") {
|
|
||||||
return "Colored Lighting";
|
|
||||||
} else if (name === "optifine") {
|
|
||||||
return "OptiFine";
|
|
||||||
} else if (name === "mrpack") {
|
|
||||||
return "Modpack";
|
|
||||||
} else if (name === "minecraft") {
|
|
||||||
return "Resource Pack";
|
|
||||||
} else if (name === "vanilla") {
|
|
||||||
return "Vanilla Shader";
|
|
||||||
}
|
|
||||||
|
|
||||||
return capitalizeString(name);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const formatCategoryHeader = (name) => {
|
|
||||||
return capitalizeString(name);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const formatVersions = (tag, versionArray) => {
|
export const formatVersions = (tag, versionArray) => {
|
||||||
const allVersions = tag.value.gameVersions.slice().reverse();
|
const allVersions = tag.value.gameVersions.slice().reverse();
|
||||||
const allReleases = allVersions.filter((x) => x.version_type === "release");
|
const allReleases = allVersions.filter((x) => x.version_type === "release");
|
||||||
|
|||||||
+206
-1355
File diff suppressed because it is too large.
Load diff
@@ -1,84 +0,0 @@
|
|||||||
<!doctype html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8" />
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
||||||
<title>Modrinth App Ad</title>
|
|
||||||
<script type="module" src="//js.rev.iq" data-domain="modrinth.com"></script>
|
|
||||||
<style>
|
|
||||||
* {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
overflow: hidden;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ads-container {
|
|
||||||
width: 100vw;
|
|
||||||
height: 100vh;
|
|
||||||
overflow: hidden;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
#plus-link {
|
|
||||||
width: 100vw;
|
|
||||||
height: 100vh;
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
z-index: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
#modrinth-rail-1 {
|
|
||||||
border-radius: 1rem;
|
|
||||||
position: absolute;
|
|
||||||
left: 0;
|
|
||||||
top: 0;
|
|
||||||
z-index: 2;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="ads-container">
|
|
||||||
<div id="plus-link"></div>
|
|
||||||
<div id="modrinth-rail-1" data-ad="app-rail" />
|
|
||||||
</div>
|
|
||||||
<script>
|
|
||||||
// window.addEventListener(
|
|
||||||
// "message",
|
|
||||||
// (event) => {
|
|
||||||
// if (event.data.modrinthAdClick && window.__TAURI_INTERNALS__) {
|
|
||||||
// window.__TAURI_INTERNALS__.invoke("plugin:ads|record_ads_click", {});
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// if (event.data.modrinthOpenUrl && window.__TAURI_INTERNALS__) {
|
|
||||||
// window.__TAURI_INTERNALS__.invoke("plugin:ads|open_link", {
|
|
||||||
// path: event.data.modrinthOpenUrl,
|
|
||||||
// origin: event.origin,
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
// },
|
|
||||||
// false,
|
|
||||||
// );
|
|
||||||
|
|
||||||
window.addEventListener("mousewheel", (event) => {
|
|
||||||
if (window.__TAURI_INTERNALS__) {
|
|
||||||
window.__TAURI_INTERNALS__.invoke("plugin:ads|scroll_ads_window", {
|
|
||||||
scroll: event.deltaY,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
document.addEventListener("contextmenu", (event) => event.preventDefault());
|
|
||||||
|
|
||||||
const plusLink = document.getElementById("plus-link");
|
|
||||||
plusLink.addEventListener("click", function () {
|
|
||||||
window.__TAURI_INTERNALS__.invoke("plugin:ads|record_ads_click", {});
|
|
||||||
window.__TAURI_INTERNALS__.invoke("plugin:ads|open_link", {
|
|
||||||
path: "https://modrinth.com/plus",
|
|
||||||
origin: "https://modrinth.com",
|
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
+14
-10
@@ -3,7 +3,8 @@ RUST_LOG=info,sqlx::query=warn
|
|||||||
SENTRY_DSN=none
|
SENTRY_DSN=none
|
||||||
|
|
||||||
SITE_URL=http://localhost:3000
|
SITE_URL=http://localhost:3000
|
||||||
CDN_URL=https://staging-cdn.modrinth.com
|
# This CDN URL matches the local storage backend set below, which uses MOCK_FILE_PATH
|
||||||
|
CDN_URL=file:///tmp/modrinth
|
||||||
LABRINTH_ADMIN_KEY=feedbeef
|
LABRINTH_ADMIN_KEY=feedbeef
|
||||||
RATE_LIMIT_IGNORE_KEY=feedbeef
|
RATE_LIMIT_IGNORE_KEY=feedbeef
|
||||||
|
|
||||||
@@ -25,18 +26,21 @@ PUBLIC_DISCORD_WEBHOOK=
|
|||||||
CLOUDFLARE_INTEGRATION=false
|
CLOUDFLARE_INTEGRATION=false
|
||||||
|
|
||||||
STORAGE_BACKEND=local
|
STORAGE_BACKEND=local
|
||||||
|
|
||||||
MOCK_FILE_PATH=/tmp/modrinth
|
MOCK_FILE_PATH=/tmp/modrinth
|
||||||
|
|
||||||
BACKBLAZE_KEY_ID=none
|
S3_PUBLIC_BUCKET_NAME=none
|
||||||
BACKBLAZE_KEY=none
|
S3_PUBLIC_USES_PATH_STYLE_BUCKET=false
|
||||||
BACKBLAZE_BUCKET_ID=none
|
S3_PUBLIC_REGION=none
|
||||||
|
S3_PUBLIC_URL=none
|
||||||
|
S3_PUBLIC_ACCESS_TOKEN=none
|
||||||
|
S3_PUBLIC_SECRET=none
|
||||||
|
|
||||||
S3_ACCESS_TOKEN=none
|
S3_PRIVATE_BUCKET_NAME=none
|
||||||
S3_SECRET=none
|
S3_PRIVATE_USES_PATH_STYLE_BUCKET=false
|
||||||
S3_URL=none
|
S3_PRIVATE_REGION=none
|
||||||
S3_REGION=none
|
S3_PRIVATE_URL=none
|
||||||
S3_BUCKET_NAME=none
|
S3_PRIVATE_ACCESS_TOKEN=none
|
||||||
|
S3_PRIVATE_SECRET=none
|
||||||
|
|
||||||
# 1 hour
|
# 1 hour
|
||||||
LOCAL_INDEX_INTERVAL=3600
|
LOCAL_INDEX_INTERVAL=3600
|
||||||
|
|||||||
Generated
-14
@@ -1,14 +0,0 @@
|
|||||||
{
|
|
||||||
"db_name": "PostgreSQL",
|
|
||||||
"query": "\n UPDATE mods\n SET moderation_message = NULL, moderation_message_body = NULL, queued = NOW()\n WHERE (id = $1)\n ",
|
|
||||||
"describe": {
|
|
||||||
"columns": [],
|
|
||||||
"parameters": {
|
|
||||||
"Left": [
|
|
||||||
"Int8"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"nullable": []
|
|
||||||
},
|
|
||||||
"hash": "010cafcafb6adc25b00e3c81d844736b0245e752a90334c58209d8a02536c800"
|
|
||||||
}
|
|
||||||
Generated
+16
@@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"db_name": "PostgreSQL",
|
||||||
|
"query": "\n INSERT INTO shared_instance_users (user_id, shared_instance_id, permissions)\n VALUES ($1, $2, $3)\n ",
|
||||||
|
"describe": {
|
||||||
|
"columns": [],
|
||||||
|
"parameters": {
|
||||||
|
"Left": [
|
||||||
|
"Int8",
|
||||||
|
"Int8",
|
||||||
|
"Int8"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"nullable": []
|
||||||
|
},
|
||||||
|
"hash": "09ebec1a568edf1959f20b33d8ba2b8edb55d93ada8f2243448865163f555d8d"
|
||||||
|
}
|
||||||
Generated
-14
@@ -1,14 +0,0 @@
|
|||||||
{
|
|
||||||
"db_name": "PostgreSQL",
|
|
||||||
"query": "\n UPDATE mods\n SET webhook_sent = TRUE\n WHERE id = $1\n ",
|
|
||||||
"describe": {
|
|
||||||
"columns": [],
|
|
||||||
"parameters": {
|
|
||||||
"Left": [
|
|
||||||
"Int8"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"nullable": []
|
|
||||||
},
|
|
||||||
"hash": "124fbf0544ea6989d6dc5e840405dbc76d7385276a38ad79d9093c53c73bbde2"
|
|
||||||
}
|
|
||||||
Generated
-15
@@ -1,15 +0,0 @@
|
|||||||
{
|
|
||||||
"db_name": "PostgreSQL",
|
|
||||||
"query": "\n DELETE FROM mods_links\n WHERE joining_mod_id = $1 AND joining_platform_id IN (\n SELECT id FROM link_platforms WHERE name = ANY($2)\n )\n ",
|
|
||||||
"describe": {
|
|
||||||
"columns": [],
|
|
||||||
"parameters": {
|
|
||||||
"Left": [
|
|
||||||
"Int8",
|
|
||||||
"TextArray"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"nullable": []
|
|
||||||
},
|
|
||||||
"hash": "186d0e933ece20163915926293a01754ff571de4f06e521bb4f7c0207268e03b"
|
|
||||||
}
|
|
||||||
Generated
-15
@@ -1,15 +0,0 @@
|
|||||||
{
|
|
||||||
"db_name": "PostgreSQL",
|
|
||||||
"query": "\n UPDATE mods\n SET license = $1\n WHERE (id = $2)\n ",
|
|
||||||
"describe": {
|
|
||||||
"columns": [],
|
|
||||||
"parameters": {
|
|
||||||
"Left": [
|
|
||||||
"Varchar",
|
|
||||||
"Int8"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"nullable": []
|
|
||||||
},
|
|
||||||
"hash": "19dc22c4d6d14222f8e8bace74c2961761c53b7375460ade15af921754d5d7da"
|
|
||||||
}
|
|
||||||
Generated
+46
@@ -0,0 +1,46 @@
|
|||||||
|
{
|
||||||
|
"db_name": "PostgreSQL",
|
||||||
|
"query": "\n SELECT id, title, owner_id, public, current_version_id\n FROM shared_instances\n WHERE id = $1\n ",
|
||||||
|
"describe": {
|
||||||
|
"columns": [
|
||||||
|
{
|
||||||
|
"ordinal": 0,
|
||||||
|
"name": "id",
|
||||||
|
"type_info": "Int8"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 1,
|
||||||
|
"name": "title",
|
||||||
|
"type_info": "Varchar"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 2,
|
||||||
|
"name": "owner_id",
|
||||||
|
"type_info": "Int8"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 3,
|
||||||
|
"name": "public",
|
||||||
|
"type_info": "Bool"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 4,
|
||||||
|
"name": "current_version_id",
|
||||||
|
"type_info": "Int8"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"parameters": {
|
||||||
|
"Left": [
|
||||||
|
"Int8"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"nullable": [
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
true
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"hash": "1ebe19b7b4f10039065967a0b1ca4bb38acc54e4ea5de020fffef7457000fa6e"
|
||||||
|
}
|
||||||
Generated
+46
@@ -0,0 +1,46 @@
|
|||||||
|
{
|
||||||
|
"db_name": "PostgreSQL",
|
||||||
|
"query": "\n SELECT id, shared_instance_id, size, sha512, created\n FROM shared_instance_versions\n WHERE shared_instance_id = $1\n ORDER BY created DESC\n ",
|
||||||
|
"describe": {
|
||||||
|
"columns": [
|
||||||
|
{
|
||||||
|
"ordinal": 0,
|
||||||
|
"name": "id",
|
||||||
|
"type_info": "Int8"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 1,
|
||||||
|
"name": "shared_instance_id",
|
||||||
|
"type_info": "Int8"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 2,
|
||||||
|
"name": "size",
|
||||||
|
"type_info": "Int8"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 3,
|
||||||
|
"name": "sha512",
|
||||||
|
"type_info": "Bytea"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 4,
|
||||||
|
"name": "created",
|
||||||
|
"type_info": "Timestamptz"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"parameters": {
|
||||||
|
"Left": [
|
||||||
|
"Int8"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"nullable": [
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"hash": "265c4d6f33714c8a5cf3137c429e2b57e917e9507942d65f40c1b733209cabf0"
|
||||||
|
}
|
||||||
Generated
+14
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"db_name": "PostgreSQL",
|
||||||
|
"query": "\n DELETE FROM mods_categories\n WHERE joining_mod_id = $1 AND is_additional = FALSE\n ",
|
||||||
|
"describe": {
|
||||||
|
"columns": [],
|
||||||
|
"parameters": {
|
||||||
|
"Left": [
|
||||||
|
"Int8"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"nullable": []
|
||||||
|
},
|
||||||
|
"hash": "299b8ea6e7a0048fa389cc4432715dc2a09e227d2f08e91167a43372a7ac6e35"
|
||||||
|
}
|
||||||
Generated
+15
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"db_name": "PostgreSQL",
|
||||||
|
"query": "\n UPDATE mods\n SET side_types_migration_review_status = $1\n WHERE id = $2\n ",
|
||||||
|
"describe": {
|
||||||
|
"columns": [],
|
||||||
|
"parameters": {
|
||||||
|
"Left": [
|
||||||
|
"Varchar",
|
||||||
|
"Int8"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"nullable": []
|
||||||
|
},
|
||||||
|
"hash": "374c234b92b838b0bd65de100a9008b0fb78c79976fd858e0599e1ccb7f08b82"
|
||||||
|
}
|
||||||
Generated
+14
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"db_name": "PostgreSQL",
|
||||||
|
"query": "\n DELETE FROM mods_categories\n WHERE joining_mod_id = $1 AND is_additional = TRUE\n ",
|
||||||
|
"describe": {
|
||||||
|
"columns": [],
|
||||||
|
"parameters": {
|
||||||
|
"Left": [
|
||||||
|
"Int8"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"nullable": []
|
||||||
|
},
|
||||||
|
"hash": "3fcfed18cbfb37866e0fa57a4e95efb326864f8219941d1b696add39ed333ad1"
|
||||||
|
}
|
||||||
Generated
-14
@@ -1,14 +0,0 @@
|
|||||||
{
|
|
||||||
"db_name": "PostgreSQL",
|
|
||||||
"query": "\n DELETE FROM mods_categories\n WHERE joining_mod_id = $1 AND is_additional = TRUE\n ",
|
|
||||||
"describe": {
|
|
||||||
"columns": [],
|
|
||||||
"parameters": {
|
|
||||||
"Left": [
|
|
||||||
"Int8"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"nullable": []
|
|
||||||
},
|
|
||||||
"hash": "40f7c5bec98fe3503d6bd6db2eae5a4edb8d5d6efda9b9dc124f344ae5c60e08"
|
|
||||||
}
|
|
||||||
Generated
+14
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"db_name": "PostgreSQL",
|
||||||
|
"query": "\n DELETE FROM shared_instance_versions\n WHERE id = $1\n ",
|
||||||
|
"describe": {
|
||||||
|
"columns": [],
|
||||||
|
"parameters": {
|
||||||
|
"Left": [
|
||||||
|
"Int8"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"nullable": []
|
||||||
|
},
|
||||||
|
"hash": "47130ef29ce5914528e5424fe516a9158a3ea08f8720f6df5b4902cd8094d3bb"
|
||||||
|
}
|
||||||
Generated
+15
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"db_name": "PostgreSQL",
|
||||||
|
"query": "UPDATE shared_instances SET current_version_id = $1 WHERE id = $2",
|
||||||
|
"describe": {
|
||||||
|
"columns": [],
|
||||||
|
"parameters": {
|
||||||
|
"Left": [
|
||||||
|
"Int8",
|
||||||
|
"Int8"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"nullable": []
|
||||||
|
},
|
||||||
|
"hash": "47ec9f179f1c52213bd32b37621ab13ae43d180b8c86cb2a6fab0253dd4eba55"
|
||||||
|
}
|
||||||
Generated
-15
@@ -1,15 +0,0 @@
|
|||||||
{
|
|
||||||
"db_name": "PostgreSQL",
|
|
||||||
"query": "\n UPDATE mods\n SET description = $1\n WHERE (id = $2)\n ",
|
|
||||||
"describe": {
|
|
||||||
"columns": [],
|
|
||||||
"parameters": {
|
|
||||||
"Left": [
|
|
||||||
"Varchar",
|
|
||||||
"Int8"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"nullable": []
|
|
||||||
},
|
|
||||||
"hash": "4e9f9eafbfd705dfc94571018cb747245a98ea61bad3fae4b3ce284229d99955"
|
|
||||||
}
|
|
||||||
Generated
-15
@@ -1,15 +0,0 @@
|
|||||||
{
|
|
||||||
"db_name": "PostgreSQL",
|
|
||||||
"query": "\n UPDATE mods\n SET slug = LOWER($1)\n WHERE (id = $2)\n ",
|
|
||||||
"describe": {
|
|
||||||
"columns": [],
|
|
||||||
"parameters": {
|
|
||||||
"Left": [
|
|
||||||
"Text",
|
|
||||||
"Int8"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"nullable": []
|
|
||||||
},
|
|
||||||
"hash": "4fb5bd341369b4beb6b4a88de296b608ea5441a96db9f7360fbdccceb4628202"
|
|
||||||
}
|
|
||||||
Generated
+15
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"db_name": "PostgreSQL",
|
||||||
|
"query": "\n UPDATE mods\n SET summary = $1\n WHERE (id = $2)\n ",
|
||||||
|
"describe": {
|
||||||
|
"columns": [],
|
||||||
|
"parameters": {
|
||||||
|
"Left": [
|
||||||
|
"Varchar",
|
||||||
|
"Int8"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"nullable": []
|
||||||
|
},
|
||||||
|
"hash": "595f4e7432d5b41002988c6cc6b0b1f09273ad02c319e6631c74d80a9b278328"
|
||||||
|
}
|
||||||
Generated
-16
@@ -1,16 +0,0 @@
|
|||||||
{
|
|
||||||
"db_name": "PostgreSQL",
|
|
||||||
"query": "\n INSERT INTO mods_links (joining_mod_id, joining_platform_id, url)\n VALUES ($1, $2, $3)\n ",
|
|
||||||
"describe": {
|
|
||||||
"columns": [],
|
|
||||||
"parameters": {
|
|
||||||
"Left": [
|
|
||||||
"Int8",
|
|
||||||
"Int4",
|
|
||||||
"Varchar"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"nullable": []
|
|
||||||
},
|
|
||||||
"hash": "6366891bb34a14278f1ae857b8d6f68dff44badae9ae5c5aceba3c32e8d00356"
|
|
||||||
}
|
|
||||||
Generated
-15
@@ -1,15 +0,0 @@
|
|||||||
{
|
|
||||||
"db_name": "PostgreSQL",
|
|
||||||
"query": "\n UPDATE mods\n SET requested_status = $1\n WHERE (id = $2)\n ",
|
|
||||||
"describe": {
|
|
||||||
"columns": [],
|
|
||||||
"parameters": {
|
|
||||||
"Left": [
|
|
||||||
"Varchar",
|
|
||||||
"Int8"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"nullable": []
|
|
||||||
},
|
|
||||||
"hash": "66b06ddcd0a4cf01e716331befa393a12631fe6752a7d078bda06b24d50daae2"
|
|
||||||
}
|
|
||||||
Generated
+15
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"db_name": "PostgreSQL",
|
||||||
|
"query": "\n UPDATE shared_instances\n SET public = $1\n WHERE id = $2\n ",
|
||||||
|
"describe": {
|
||||||
|
"columns": [],
|
||||||
|
"parameters": {
|
||||||
|
"Left": [
|
||||||
|
"Bool",
|
||||||
|
"Int8"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"nullable": []
|
||||||
|
},
|
||||||
|
"hash": "6b166d129b0ee028898620054a58fa4c3641eb2221e522bf50abad4f5e977599"
|
||||||
|
}
|
||||||
Generated
+17
@@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"db_name": "PostgreSQL",
|
||||||
|
"query": "\n INSERT INTO shared_instances (id, title, owner_id, current_version_id)\n VALUES ($1, $2, $3, $4)\n ",
|
||||||
|
"describe": {
|
||||||
|
"columns": [],
|
||||||
|
"parameters": {
|
||||||
|
"Left": [
|
||||||
|
"Int8",
|
||||||
|
"Varchar",
|
||||||
|
"Int8",
|
||||||
|
"Int8"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"nullable": []
|
||||||
|
},
|
||||||
|
"hash": "6f72c853e139f23322fe6f1f02e4e07e5ae80b5dfca6dc041a03c0c7a30a5cf1"
|
||||||
|
}
|
||||||
Generated
+15
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"db_name": "PostgreSQL",
|
||||||
|
"query": "\n UPDATE mods\n SET name = $1\n WHERE (id = $2)\n ",
|
||||||
|
"describe": {
|
||||||
|
"columns": [],
|
||||||
|
"parameters": {
|
||||||
|
"Left": [
|
||||||
|
"Varchar",
|
||||||
|
"Int8"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"nullable": []
|
||||||
|
},
|
||||||
|
"hash": "70be97b02e402de0490ade5866c47232f9c341add2f3838cc3ae1a07a310d561"
|
||||||
|
}
|
||||||
Generated
+15
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"db_name": "PostgreSQL",
|
||||||
|
"query": "\n UPDATE shared_instances\n SET owner_id = $1\n WHERE owner_id = $2\n ",
|
||||||
|
"describe": {
|
||||||
|
"columns": [],
|
||||||
|
"parameters": {
|
||||||
|
"Left": [
|
||||||
|
"Int8",
|
||||||
|
"Int8"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"nullable": []
|
||||||
|
},
|
||||||
|
"hash": "72ae0e8debd06067894a2f7bea279446dd964da4efa49c5464cebde57860f741"
|
||||||
|
}
|
||||||
Generated
+15
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"db_name": "PostgreSQL",
|
||||||
|
"query": "\n UPDATE mods\n SET moderation_message_body = $1\n WHERE (id = $2)\n ",
|
||||||
|
"describe": {
|
||||||
|
"columns": [],
|
||||||
|
"parameters": {
|
||||||
|
"Left": [
|
||||||
|
"Varchar",
|
||||||
|
"Int8"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"nullable": []
|
||||||
|
},
|
||||||
|
"hash": "79040825457845cc078be7b3293804d6fb2e05ffce07e7b4248d8705d6fc6e61"
|
||||||
|
}
|
||||||
Generated
-15
@@ -1,15 +0,0 @@
|
|||||||
{
|
|
||||||
"db_name": "PostgreSQL",
|
|
||||||
"query": "\n UPDATE mods\n SET monetization_status = $1\n WHERE (id = $2)\n ",
|
|
||||||
"describe": {
|
|
||||||
"columns": [],
|
|
||||||
"parameters": {
|
|
||||||
"Left": [
|
|
||||||
"Varchar",
|
|
||||||
"Int8"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"nullable": []
|
|
||||||
},
|
|
||||||
"hash": "7916fe4f04067324ae05598ec9dc6f97f18baf9eda30c64f32677158ada87478"
|
|
||||||
}
|
|
||||||
+9
-3
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"db_name": "PostgreSQL",
|
"db_name": "PostgreSQL",
|
||||||
"query": "\n SELECT m.id id, m.name name, m.summary summary, m.downloads downloads, m.follows follows,\n m.icon_url icon_url, m.raw_icon_url raw_icon_url, m.description description, m.published published,\n m.approved approved, m.queued, m.status status, m.requested_status requested_status,\n m.license_url license_url,\n m.team_id team_id, m.organization_id organization_id, m.license license, m.slug slug, m.moderation_message moderation_message, m.moderation_message_body moderation_message_body,\n m.webhook_sent, m.color,\n t.id thread_id, m.monetization_status monetization_status,\n ARRAY_AGG(DISTINCT c.category) filter (where c.category is not null and mc.is_additional is false) categories,\n ARRAY_AGG(DISTINCT c.category) filter (where c.category is not null and mc.is_additional is true) additional_categories\n FROM mods m\n INNER JOIN threads t ON t.mod_id = m.id\n LEFT JOIN mods_categories mc ON mc.joining_mod_id = m.id\n LEFT JOIN categories c ON mc.joining_category_id = c.id\n WHERE m.id = ANY($1) OR m.slug = ANY($2)\n GROUP BY t.id, m.id;\n ",
|
"query": "\n SELECT m.id id, m.name name, m.summary summary, m.downloads downloads, m.follows follows,\n m.icon_url icon_url, m.raw_icon_url raw_icon_url, m.description description, m.published published,\n m.approved approved, m.queued, m.status status, m.requested_status requested_status,\n m.license_url license_url,\n m.team_id team_id, m.organization_id organization_id, m.license license, m.slug slug, m.moderation_message moderation_message, m.moderation_message_body moderation_message_body,\n m.webhook_sent, m.color,\n t.id thread_id, m.monetization_status monetization_status,\n m.side_types_migration_review_status side_types_migration_review_status,\n ARRAY_AGG(DISTINCT c.category) filter (where c.category is not null and mc.is_additional is false) categories,\n ARRAY_AGG(DISTINCT c.category) filter (where c.category is not null and mc.is_additional is true) additional_categories\n FROM mods m\n INNER JOIN threads t ON t.mod_id = m.id\n LEFT JOIN mods_categories mc ON mc.joining_mod_id = m.id\n LEFT JOIN categories c ON mc.joining_category_id = c.id\n WHERE m.id = ANY($1) OR m.slug = ANY($2)\n GROUP BY t.id, m.id;\n ",
|
||||||
"describe": {
|
"describe": {
|
||||||
"columns": [
|
"columns": [
|
||||||
{
|
{
|
||||||
@@ -125,11 +125,16 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ordinal": 24,
|
"ordinal": 24,
|
||||||
|
"name": "side_types_migration_review_status",
|
||||||
|
"type_info": "Varchar"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 25,
|
||||||
"name": "categories",
|
"name": "categories",
|
||||||
"type_info": "VarcharArray"
|
"type_info": "VarcharArray"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ordinal": 25,
|
"ordinal": 26,
|
||||||
"name": "additional_categories",
|
"name": "additional_categories",
|
||||||
"type_info": "VarcharArray"
|
"type_info": "VarcharArray"
|
||||||
}
|
}
|
||||||
@@ -165,9 +170,10 @@
|
|||||||
true,
|
true,
|
||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
|
false,
|
||||||
null,
|
null,
|
||||||
null
|
null
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"hash": "5f75c0c48083de27f853ee877aac070567fd2ed2be4a9a038821b790dd7cb763"
|
"hash": "7a6d6a91e6bd27f7be34b8cc7955a66c4175ebd1c55e437f187f61efca681c62"
|
||||||
}
|
}
|
||||||
+2
-2
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"db_name": "PostgreSQL",
|
"db_name": "PostgreSQL",
|
||||||
"query": "\n SELECT EXISTS(SELECT 1 FROM mods WHERE id=$1)\n ",
|
"query": "SELECT EXISTS(SELECT 1 FROM shared_instance_versions WHERE id=$1)",
|
||||||
"describe": {
|
"describe": {
|
||||||
"columns": [
|
"columns": [
|
||||||
{
|
{
|
||||||
@@ -18,5 +18,5 @@
|
|||||||
null
|
null
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"hash": "f34bbe639ad21801258dc8beaab9877229a451761be07f85a1dd04d027832329"
|
"hash": "7c445073f61e30723416a9690aa9d227d95f2a8f2eb9852833e14c723903988b"
|
||||||
}
|
}
|
||||||
Generated
+15
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"db_name": "PostgreSQL",
|
||||||
|
"query": "\n UPDATE mods\n SET slug = LOWER($1)\n WHERE (id = $2)\n ",
|
||||||
|
"describe": {
|
||||||
|
"columns": [],
|
||||||
|
"parameters": {
|
||||||
|
"Left": [
|
||||||
|
"Text",
|
||||||
|
"Int8"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"nullable": []
|
||||||
|
},
|
||||||
|
"hash": "7e403d399ddd3279c4c65db7b9ea850cdd9fef3df1b3f7d5f62e079b4522f2ca"
|
||||||
|
}
|
||||||
Generated
+14
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"db_name": "PostgreSQL",
|
||||||
|
"query": "\n UPDATE mods\n SET approved = NOW()\n WHERE id = $1 AND approved IS NULL\n ",
|
||||||
|
"describe": {
|
||||||
|
"columns": [],
|
||||||
|
"parameters": {
|
||||||
|
"Left": [
|
||||||
|
"Int8"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"nullable": []
|
||||||
|
},
|
||||||
|
"hash": "92d805d2e13cfc0f2220f15b0a35ff71e654e5e6b386766e6c6047cf3861b26e"
|
||||||
|
}
|
||||||
Generated
+15
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"db_name": "PostgreSQL",
|
||||||
|
"query": "\n UPDATE mods\n SET license_url = $1\n WHERE (id = $2)\n ",
|
||||||
|
"describe": {
|
||||||
|
"columns": [],
|
||||||
|
"parameters": {
|
||||||
|
"Left": [
|
||||||
|
"Varchar",
|
||||||
|
"Int8"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"nullable": []
|
||||||
|
},
|
||||||
|
"hash": "9482a3419337911ac6a10eeaf065e29589ee1b707729344e81d183c713aa0d28"
|
||||||
|
}
|
||||||
Generated
+46
@@ -0,0 +1,46 @@
|
|||||||
|
{
|
||||||
|
"db_name": "PostgreSQL",
|
||||||
|
"query": "\n -- See https://github.com/launchbadge/sqlx/issues/1266 for why we need all the \"as\"\n SELECT\n id as \"id!\",\n title as \"title!\",\n public as \"public!\",\n owner_id as \"owner_id!\",\n current_version_id\n FROM shared_instances\n WHERE owner_id = $1\n UNION\n SELECT\n id as \"id!\",\n title as \"title!\",\n public as \"public!\",\n owner_id as \"owner_id!\",\n current_version_id\n FROM shared_instances\n JOIN shared_instance_users ON id = shared_instance_id\n WHERE user_id = $1\n ",
|
||||||
|
"describe": {
|
||||||
|
"columns": [
|
||||||
|
{
|
||||||
|
"ordinal": 0,
|
||||||
|
"name": "id!",
|
||||||
|
"type_info": "Int8"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 1,
|
||||||
|
"name": "title!",
|
||||||
|
"type_info": "Varchar"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 2,
|
||||||
|
"name": "public!",
|
||||||
|
"type_info": "Bool"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 3,
|
||||||
|
"name": "owner_id!",
|
||||||
|
"type_info": "Int8"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 4,
|
||||||
|
"name": "current_version_id",
|
||||||
|
"type_info": "Int8"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"parameters": {
|
||||||
|
"Left": [
|
||||||
|
"Int8"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"nullable": [
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"hash": "9c6e18cb19251e54b3b96446ab88d84842152b82c9a0032d1db587d7099b8550"
|
||||||
|
}
|
||||||
Generated
+15
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"db_name": "PostgreSQL",
|
||||||
|
"query": "\n UPDATE shared_instances\n SET title = $1\n WHERE id = $2\n ",
|
||||||
|
"describe": {
|
||||||
|
"columns": [],
|
||||||
|
"parameters": {
|
||||||
|
"Left": [
|
||||||
|
"Varchar",
|
||||||
|
"Int8"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"nullable": []
|
||||||
|
},
|
||||||
|
"hash": "9ccaf8ea52b1b6f0880d34cdb4a9405e28c265bef6121b457c4f39cacf00683f"
|
||||||
|
}
|
||||||
Generated
-14
@@ -1,14 +0,0 @@
|
|||||||
{
|
|
||||||
"db_name": "PostgreSQL",
|
|
||||||
"query": "\n DELETE FROM mods_categories\n WHERE joining_mod_id = $1 AND is_additional = FALSE\n ",
|
|
||||||
"describe": {
|
|
||||||
"columns": [],
|
|
||||||
"parameters": {
|
|
||||||
"Left": [
|
|
||||||
"Int8"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"nullable": []
|
|
||||||
},
|
|
||||||
"hash": "a0148ff25855202e7bb220b6a2bc9220a95e309fb0dae41d9a05afa86e6b33af"
|
|
||||||
}
|
|
||||||
Generated
-14
@@ -1,14 +0,0 @@
|
|||||||
{
|
|
||||||
"db_name": "PostgreSQL",
|
|
||||||
"query": "\n UPDATE mods\n SET approved = NOW()\n WHERE id = $1 AND approved IS NULL\n ",
|
|
||||||
"describe": {
|
|
||||||
"columns": [],
|
|
||||||
"parameters": {
|
|
||||||
"Left": [
|
|
||||||
"Int8"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"nullable": []
|
|
||||||
},
|
|
||||||
"hash": "a11d613479d09dff5fcdc45ab7a0341fb1b4738f0ede71572d939ef0984bd65f"
|
|
||||||
}
|
|
||||||
Generated
+15
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"db_name": "PostgreSQL",
|
||||||
|
"query": "\n UPDATE mods\n SET license = $1\n WHERE (id = $2)\n ",
|
||||||
|
"describe": {
|
||||||
|
"columns": [],
|
||||||
|
"parameters": {
|
||||||
|
"Left": [
|
||||||
|
"Varchar",
|
||||||
|
"Int8"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"nullable": []
|
||||||
|
},
|
||||||
|
"hash": "a5ae1fe0ca4ca8432736398fed25687173b2fbde3405340a5579c5ef68cb5218"
|
||||||
|
}
|
||||||
Generated
+15
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"db_name": "PostgreSQL",
|
||||||
|
"query": "\n UPDATE mods\n SET status = $1\n WHERE (id = $2)\n ",
|
||||||
|
"describe": {
|
||||||
|
"columns": [],
|
||||||
|
"parameters": {
|
||||||
|
"Left": [
|
||||||
|
"Varchar",
|
||||||
|
"Int8"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"nullable": []
|
||||||
|
},
|
||||||
|
"hash": "a74230ad1bb1b13bab850e204436e7746a96f9605afe2ca62d6d8337530cb5ad"
|
||||||
|
}
|
||||||
Generated
+34
@@ -0,0 +1,34 @@
|
|||||||
|
{
|
||||||
|
"db_name": "PostgreSQL",
|
||||||
|
"query": "\n SELECT shared_instance_id, user_id, permissions\n FROM shared_instance_users\n WHERE shared_instance_id = ANY($1)\n ",
|
||||||
|
"describe": {
|
||||||
|
"columns": [
|
||||||
|
{
|
||||||
|
"ordinal": 0,
|
||||||
|
"name": "shared_instance_id",
|
||||||
|
"type_info": "Int8"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 1,
|
||||||
|
"name": "user_id",
|
||||||
|
"type_info": "Int8"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 2,
|
||||||
|
"name": "permissions",
|
||||||
|
"type_info": "Int8"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"parameters": {
|
||||||
|
"Left": [
|
||||||
|
"Int8Array"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"nullable": [
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"hash": "aec58041cf5e5e68501652336581b8c709645ef29f3b5fb6e8e07fc212b36798"
|
||||||
|
}
|
||||||
Generated
-15
@@ -1,15 +0,0 @@
|
|||||||
{
|
|
||||||
"db_name": "PostgreSQL",
|
|
||||||
"query": "\n UPDATE mods\n SET summary = $1\n WHERE (id = $2)\n ",
|
|
||||||
"describe": {
|
|
||||||
"columns": [],
|
|
||||||
"parameters": {
|
|
||||||
"Left": [
|
|
||||||
"Varchar",
|
|
||||||
"Int8"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"nullable": []
|
|
||||||
},
|
|
||||||
"hash": "b677e66031752e66d2219079a559e368c6cea1800da8a5f9d50ba5b1ac3a15fc"
|
|
||||||
}
|
|
||||||
Loaded 100 of 227 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user