Allow for logos to be used as instance names

This commit is contained in:
SCOPEDD committed 2026-09-29 01:58:53 -04:00
1 parent a1f19e86c6
commit 522e3a5717
10 files changed
+36 -8

No files matched your search

+1
View File
@@ -332,6 +332,7 @@ pub struct InstanceSummary {
pub description: String, pub description: String,
pub icon_url: Option<String>, pub icon_url: Option<String>,
pub banner_url: Option<String>, pub banner_url: Option<String>,
pub logo_url: Option<String>,
pub mc_version: String, pub mc_version: String,
pub loader: Loader, pub loader: Loader,
pub loader_version: Option<String>, pub loader_version: Option<String>,
+3 -3
View File
@@ -63,9 +63,9 @@ const manifest: Manifest = {
custom_css: '', custom_css: '',
}, },
instances: [ instances: [
{ id: 'survival-smp', name: 'Survival SMP', description: 'Our main survival world — season 4. Claims, shops & weekend events.', icon_url: null, banner_url: null, mc_version: '1.21.1', loader: 'fabric', loader_version: '0.16.9', revision: 3, server: { name: 'SCOPENET SMP', address: 'play.scopenet.gg', port: 25565, auto_join: true, inject: true }, memory: { min_mb: 2048, max_mb: 6144 }, jvm_args: '', featured: true, source_label: 'Modrinth · Fabulously Optimized 6.2', file_count: 48, total_size: 184_000_000 }, { id: 'survival-smp', name: 'Survival SMP', description: 'Our main survival world — season 4. Claims, shops & weekend events.', icon_url: null, banner_url: null, logo_url: null, mc_version: '1.21.1', loader: 'fabric', loader_version: '0.16.9', revision: 3, server: { name: 'SCOPENET SMP', address: 'play.scopenet.gg', port: 25565, auto_join: true, inject: true }, memory: { min_mb: 2048, max_mb: 6144 }, jvm_args: '', featured: true, source_label: 'Modrinth · Fabulously Optimized 6.2', file_count: 48, total_size: 184_000_000 },
{ id: 'creative', name: 'Creative Build', description: 'Plots, WorldEdit and weekly build contests.', icon_url: null, banner_url: null, mc_version: '1.20.1', loader: 'vanilla', loader_version: null, revision: 1, server: { name: 'Creative', address: 'creative.scopenet.gg', port: 25565, auto_join: false, inject: true }, memory: { min_mb: 1024, max_mb: 4096 }, jvm_args: '', featured: false, source_label: 'Minecraft 1.20.1', file_count: 0, total_size: 0 }, { id: 'creative', name: 'Creative Build', description: 'Plots, WorldEdit and weekly build contests.', icon_url: null, banner_url: null, logo_url: null, mc_version: '1.20.1', loader: 'vanilla', loader_version: null, revision: 1, server: { name: 'Creative', address: 'creative.scopenet.gg', port: 25565, auto_join: false, inject: true }, memory: { min_mb: 1024, max_mb: 4096 }, jvm_args: '', featured: false, source_label: 'Minecraft 1.20.1', file_count: 0, total_size: 0 },
{ id: 'modded', name: 'Create: Above & Beyond', description: 'Factory building with Create and friends.', icon_url: null, banner_url: null, mc_version: '1.20.1', loader: 'forge', loader_version: '1.20.1-47.3.0', revision: 2, server: null, memory: { min_mb: 4096, max_mb: 8192 }, jvm_args: '', featured: false, source_label: 'CurseForge · Create: Above & Beyond 1.4', file_count: 212, total_size: 612_000_000 }, { id: 'modded', name: 'Create: Above & Beyond', description: 'Factory building with Create and friends.', icon_url: null, banner_url: null, logo_url: null, mc_version: '1.20.1', loader: 'forge', loader_version: '1.20.1-47.3.0', revision: 2, server: null, memory: { min_mb: 4096, max_mb: 8192 }, jvm_args: '', featured: false, source_label: 'CurseForge · Create: Above & Beyond 1.4', file_count: 212, total_size: 612_000_000 },
], ],
}; };
+1
View File
@@ -8,6 +8,7 @@ export interface Instance {
description: string; description: string;
icon_url: string | null; icon_url: string | null;
banner_url: string | null; banner_url: string | null;
logo_url: string | null;
mc_version: string; mc_version: string;
loader: Loader; loader: Loader;
loader_version: string | null; loader_version: string | null;
+9 -1
View File
@@ -4,7 +4,7 @@
import ServerStatus from '../components/ServerStatus.svelte'; import ServerStatus from '../components/ServerStatus.svelte';
import NewsFeed from '../components/NewsFeed.svelte'; import NewsFeed from '../components/NewsFeed.svelte';
import Console from '../components/Console.svelte'; import Console from '../components/Console.svelte';
import { activeAccount, app, instances, selectedInstance } from '../lib/store.svelte'; import { abs, activeAccount, app, instances, selectedInstance } from '../lib/store.svelte';
import { loaderName, shortLoaderVersion } from '../lib/format'; import { loaderName, shortLoaderVersion } from '../lib/format';
import { openUrl } from '../lib/tauri'; import { openUrl } from '../lib/tauri';
@@ -26,7 +26,13 @@
<span class="chip">{loaderName[inst.loader]} {shortLoaderVersion(inst.mc_version, inst.loader_version)}</span> <span class="chip">{loaderName[inst.loader]} {shortLoaderVersion(inst.mc_version, inst.loader_version)}</span>
{#if inst.file_count}<span class="chip"><Package size={12} /> {inst.file_count} mods & files</span>{/if} {#if inst.file_count}<span class="chip"><Package size={12} /> {inst.file_count} mods & files</span>{/if}
</div> </div>
{#if inst.logo_url}
<div class="logo-wrap">
<img class="inst-logo" src={abs(inst.logo_url)} alt={inst.name} />
</div>
{:else}
<h1>{inst.name}</h1> <h1>{inst.name}</h1>
{/if}
<p class="desc">{inst.description || b.tagline}</p> <p class="desc">{inst.description || b.tagline}</p>
</section> </section>
@@ -67,6 +73,8 @@
.greet { font-size: 0.88rem; } .greet { font-size: 0.88rem; }
.greet strong { color: var(--text); font-weight: 560; } .greet strong { color: var(--text); font-weight: 560; }
.chips { display: flex; gap: 0.45rem; flex-wrap: wrap; } .chips { display: flex; gap: 0.45rem; flex-wrap: wrap; }
.logo-wrap { max-width: 100%; display: flex; align-items: flex-end; min-height: 3rem; margin: 0.25rem 0; }
.inst-logo { max-height: clamp(3.8rem, 8vw, 6.2rem); max-width: min(100%, 30rem); object-fit: contain; object-position: left bottom; filter: drop-shadow(0 4px 16px rgba(0, 0, 0, 0.5)); }
h1 { font-size: clamp(2.2rem, 4.4vw, 3.2rem); line-height: 1.05; font-weight: 700; letter-spacing: -0.025em; } h1 { font-size: clamp(2.2rem, 4.4vw, 3.2rem); line-height: 1.05; font-weight: 700; letter-spacing: -0.025em; }
.desc { font-size: 0.95rem; line-height: 1.6; color: var(--muted); max-width: 32rem; } .desc { font-size: 0.95rem; line-height: 1.6; color: var(--muted); max-width: 32rem; }
.actions { grid-column: 1; grid-row: 2; display: flex; flex-direction: column; gap: 0.8rem; animation: fade 0.3s ease both; } .actions { grid-column: 1; grid-row: 2; display: flex; flex-direction: column; gap: 0.8rem; animation: fade 0.3s ease both; }
+4
View File
@@ -215,6 +215,10 @@ const MIGRATIONS: &[&str] = &[
WHEN NEW.uuid <> '' WHEN NEW.uuid <> ''
BEGIN INSERT OR IGNORE INTO reserved_usernames VALUES (NEW.username, NEW.uuid); END; BEGIN INSERT OR IGNORE INTO reserved_usernames VALUES (NEW.username, NEW.uuid); END;
"#, "#,
// 5: instance logo url.
r#"
ALTER TABLE instances ADD COLUMN logo_url TEXT;
"#,
]; ];
pub async fn connect(data_dir: &Path) -> Result<SqlitePool> { pub async fn connect(data_dir: &Path) -> Result<SqlitePool> {
+6 -3
View File
@@ -349,6 +349,7 @@ pub struct InstanceInput {
description: String, description: String,
icon_url: Option<String>, icon_url: Option<String>,
banner_url: Option<String>, banner_url: Option<String>,
logo_url: Option<String>,
mc_version: String, mc_version: String,
loader: Loader, loader: Loader,
loader_version: Option<String>, loader_version: Option<String>,
@@ -403,15 +404,16 @@ pub async fn create_instance(
let now = crate::db::now(); let now = crate::db::now();
let mem = input.memory.unwrap_or_default(); let mem = input.memory.unwrap_or_default();
sqlx::query( sqlx::query(
"INSERT INTO instances (id, name, description, icon_url, banner_url, mc_version, loader, loader_version, source_kind, source_label, "INSERT INTO instances (id, name, description, icon_url, banner_url, logo_url, mc_version, loader, loader_version, source_kind, source_label,
visibility, allowed_groups, memory_min, memory_max, jvm_args, server, featured, enabled, sort, created_at, updated_at) visibility, allowed_groups, memory_min, memory_max, jvm_args, server, featured, enabled, sort, created_at, updated_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, 'vanilla', ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 'vanilla', ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
) )
.bind(&id) .bind(&id)
.bind(&input.name) .bind(&input.name)
.bind(&input.description) .bind(&input.description)
.bind(&input.icon_url) .bind(&input.icon_url)
.bind(&input.banner_url) .bind(&input.banner_url)
.bind(&input.logo_url)
.bind(&input.mc_version) .bind(&input.mc_version)
.bind(input.loader.as_str()) .bind(input.loader.as_str())
.bind(&input.loader_version) .bind(&input.loader_version)
@@ -461,7 +463,7 @@ pub async fn update_instance(
// admin explicitly changes them here. // admin explicitly changes them here.
let label = if existing.source_kind == "vanilla" { format!("Minecraft {}", input.mc_version) } else { existing.source_label.clone() }; let label = if existing.source_kind == "vanilla" { format!("Minecraft {}", input.mc_version) } else { existing.source_label.clone() };
sqlx::query( sqlx::query(
"UPDATE instances SET name = ?, description = ?, icon_url = ?, banner_url = ?, mc_version = ?, loader = ?, loader_version = ?, "UPDATE instances SET name = ?, description = ?, icon_url = ?, banner_url = ?, logo_url = ?, mc_version = ?, loader = ?, loader_version = ?,
source_label = ?, visibility = ?, allowed_groups = ?, memory_min = ?, memory_max = ?, jvm_args = ?, server = ?, featured = ?, source_label = ?, visibility = ?, allowed_groups = ?, memory_min = ?, memory_max = ?, jvm_args = ?, server = ?, featured = ?,
enabled = ?, sort = ?, revision = revision + 1, updated_at = ? WHERE id = ?", enabled = ?, sort = ?, revision = revision + 1, updated_at = ? WHERE id = ?",
) )
@@ -469,6 +471,7 @@ pub async fn update_instance(
.bind(&input.description) .bind(&input.description)
.bind(input.icon_url.filter(|s| !s.is_empty())) .bind(input.icon_url.filter(|s| !s.is_empty()))
.bind(input.banner_url.filter(|s| !s.is_empty())) .bind(input.banner_url.filter(|s| !s.is_empty()))
.bind(input.logo_url.filter(|s| !s.is_empty()))
.bind(&input.mc_version) .bind(&input.mc_version)
.bind(input.loader.as_str()) .bind(input.loader.as_str())
.bind(&input.loader_version) .bind(&input.loader_version)
+4
View File
@@ -63,6 +63,7 @@ pub struct InstanceRow {
pub description: String, pub description: String,
pub icon_url: Option<String>, pub icon_url: Option<String>,
pub banner_url: Option<String>, pub banner_url: Option<String>,
pub logo_url: Option<String>,
pub mc_version: String, pub mc_version: String,
pub loader: String, pub loader: String,
pub loader_version: Option<String>, pub loader_version: Option<String>,
@@ -92,6 +93,7 @@ pub struct AdminInstance {
pub description: String, pub description: String,
pub icon_url: Option<String>, pub icon_url: Option<String>,
pub banner_url: Option<String>, pub banner_url: Option<String>,
pub logo_url: Option<String>,
pub mc_version: String, pub mc_version: String,
pub loader: Loader, pub loader: Loader,
pub loader_version: Option<String>, pub loader_version: Option<String>,
@@ -123,6 +125,7 @@ impl InstanceRow {
description: self.description.clone(), description: self.description.clone(),
icon_url: self.icon_url.clone(), icon_url: self.icon_url.clone(),
banner_url: self.banner_url.clone(), banner_url: self.banner_url.clone(),
logo_url: self.logo_url.clone(),
mc_version: self.mc_version.clone(), mc_version: self.mc_version.clone(),
loader: Loader::parse(&self.loader).unwrap_or_default(), loader: Loader::parse(&self.loader).unwrap_or_default(),
loader_version: self.loader_version.clone(), loader_version: self.loader_version.clone(),
@@ -154,6 +157,7 @@ impl InstanceRow {
description: a.description, description: a.description,
icon_url: a.icon_url, icon_url: a.icon_url,
banner_url: a.banner_url, banner_url: a.banner_url,
logo_url: a.logo_url,
mc_version: a.mc_version, mc_version: a.mc_version,
loader: a.loader, loader: a.loader,
loader_version: a.loader_version, loader_version: a.loader_version,
@@ -4,7 +4,7 @@
import { Play, Settings } from '@lucide/svelte'; import { Play, Settings } from '@lucide/svelte';
import type { Branding } from '../lib/types'; import type { Branding } from '../lib/types';
let { b, instanceName = 'Survival SMP' }: { b: Branding; instanceName?: string } = $props(); let { b, instanceName = 'Survival SMP', instanceLogo = null }: { b: Branding; instanceName?: string; instanceLogo?: string | null } = $props();
const fontFamily = $derived(`'${b.font} Variable', '${b.font}', 'Inter Variable', sans-serif`); const fontFamily = $derived(`'${b.font} Variable', '${b.font}', 'Inter Variable', sans-serif`);
const media = $derived(b.background.kind !== 'gradient' && b.background.url ? b.background.url : null); const media = $derived(b.background.kind !== 'gradient' && b.background.url ? b.background.url : null);
@@ -53,7 +53,11 @@
<div class="main"> <div class="main">
<div class="hero"> <div class="hero">
<div class="chips"><span>1.21.1</span><span>Fabric</span></div> <div class="chips"><span>1.21.1</span><span>Fabric</span></div>
{#if instanceLogo}
<img class="hero-logo" src={instanceLogo} alt={instanceName} />
{:else}
<h1>{instanceName}</h1> <h1>{instanceName}</h1>
{/if}
<p>{b.tagline}</p> <p>{b.tagline}</p>
<div class="row"> <div class="row">
<span class="play"><Play size={9} fill="currentColor" /> Play</span> <span class="play"><Play size={9} fill="currentColor" /> Play</span>
@@ -103,6 +107,7 @@
.avatar { width: 55%; aspect-ratio: 1; border-radius: 25%; background: #3fb97c; } .avatar { width: 55%; aspect-ratio: 1; border-radius: 25%; background: #3fb97c; }
.main { position: absolute; left: 6.2%; top: 5.6%; right: 0; bottom: 0; padding: 4% 4%; z-index: 1; display: flex; gap: 6%; } .main { position: absolute; left: 6.2%; top: 5.6%; right: 0; bottom: 0; padding: 4% 4%; z-index: 1; display: flex; gap: 6%; }
.hero { flex: 1; align-self: flex-end; display: flex; flex-direction: column; gap: 0.6em; } .hero { flex: 1; align-self: flex-end; display: flex; flex-direction: column; gap: 0.6em; }
.hero-logo { max-height: 2.8em; max-width: 80%; object-fit: contain; object-position: left bottom; }
.hero h1 { font-size: 3.1em; line-height: 1.05; margin: 0; letter-spacing: -0.025em; font-weight: 700; } .hero h1 { font-size: 3.1em; line-height: 1.05; margin: 0; letter-spacing: -0.025em; font-weight: 700; }
.hero p { color: var(--mut); font-size: 0.95em; margin: 0; } .hero p { color: var(--mut); font-size: 0.95em; margin: 0; }
.chips { display: flex; gap: 0.4em; } .chips { display: flex; gap: 0.4em; }
+1
View File
@@ -7,6 +7,7 @@ export interface Instance {
description: string; description: string;
icon_url: string | null; icon_url: string | null;
banner_url: string | null; banner_url: string | null;
logo_url: string | null;
mc_version: string; mc_version: string;
loader: string; loader: string;
loader_version: string | null; loader_version: string | null;
@@ -251,6 +251,7 @@
<label class="field">Name<input bind:value={form.name} /></label> <label class="field">Name<input bind:value={form.name} /></label>
<label class="field">Description<textarea bind:value={form.description} rows="3" placeholder="What makes this instance special?"></textarea></label> <label class="field">Description<textarea bind:value={form.description} rows="3" placeholder="What makes this instance special?"></textarea></label>
<ImageInput bind:value={form.icon_url} label="Icon" help="Square image, shown in the launcher sidebar." /> <ImageInput bind:value={form.icon_url} label="Icon" help="Square image, shown in the launcher sidebar." />
<ImageInput bind:value={form.logo_url} label="Logo (optional)" help="Wide logo or wordmark image used in place of the instance title text." />
<ImageInput bind:value={form.banner_url} label="Banner / background" help="Wide image shown behind this instance. Overrides the launcher background." /> <ImageInput bind:value={form.banner_url} label="Banner / background" help="Wide image shown behind this instance. Overrides the launcher background." />
</section> </section>
<section class="card col"> <section class="card col">