mirror of
https://github.com/LizardByte/Sunshine.git
synced 2026-08-07 10:20:46 +00:00
feat(web-ui): UI consistency / layout uplifts (#5225)
Co-authored-by: David Lane <42013603+ReenigneArcher@users.noreply.github.com>
This commit is contained in:
@@ -4,17 +4,25 @@ import { onMounted } from 'vue'
|
||||
import {
|
||||
CloudMoon,
|
||||
CloudRain,
|
||||
Coffee,
|
||||
Contrast,
|
||||
Droplet,
|
||||
Flame,
|
||||
Flower,
|
||||
Flower2,
|
||||
Ghost,
|
||||
Layers,
|
||||
Milk,
|
||||
MonitorSmartphone,
|
||||
Moon,
|
||||
Mountain,
|
||||
Shuffle,
|
||||
Sparkles,
|
||||
Sprout,
|
||||
Sun,
|
||||
Sunrise,
|
||||
Sunset,
|
||||
TreePine,
|
||||
Trees,
|
||||
Waves,
|
||||
} from '@lucide/vue'
|
||||
@@ -38,96 +46,104 @@ onMounted(() => {
|
||||
</span>
|
||||
<span id="bd-theme-text">{{ $t('navbar.toggle_theme') }}</span>
|
||||
</a>
|
||||
<ul class="dropdown-menu dropdown-menu-end" aria-labelledby="bd-theme-text">
|
||||
<li>
|
||||
<ul class="dropdown-menu dropdown-menu-end theme-menu" aria-labelledby="bd-theme-text">
|
||||
<li class="theme-menu-full">
|
||||
<button type="button" class="dropdown-item d-flex align-items-center active" data-bs-theme-value="auto" aria-pressed="true">
|
||||
<MonitorSmartphone :size="18" class="theme-icon icon"></MonitorSmartphone>
|
||||
{{ $t('navbar.theme_auto') }}
|
||||
</button>
|
||||
<button type="button" id="bd-theme-random" class="dropdown-item d-flex align-items-center">
|
||||
<Shuffle :size="18" class="theme-icon icon"></Shuffle>
|
||||
{{ $t('navbar.theme_random') }}
|
||||
</button>
|
||||
</li>
|
||||
<li><hr class="dropdown-divider"></li>
|
||||
<!-- Dark Themes -->
|
||||
<li>
|
||||
<li class="theme-menu-group">
|
||||
<h6 class="dropdown-header">{{ $t('navbar.theme_group_dark') }}</h6>
|
||||
<button type="button" class="dropdown-item d-flex align-items-center" data-bs-theme-value="dark" aria-pressed="false">
|
||||
<Moon :size="18" class="theme-icon icon"></Moon>
|
||||
{{ $t('navbar.theme_dark') }}
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<button type="button" class="dropdown-item d-flex align-items-center" data-bs-theme-value="slate" aria-pressed="false">
|
||||
<Layers :size="18" class="theme-icon icon"></Layers>
|
||||
{{ $t('navbar.theme_slate') }}
|
||||
<button type="button" class="dropdown-item d-flex align-items-center" data-bs-theme-value="dracula" aria-pressed="false">
|
||||
<Ghost :size="18" class="theme-icon icon"></Ghost>
|
||||
{{ $t('navbar.theme_dracula') }}
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<button type="button" class="dropdown-item d-flex align-items-center" data-bs-theme-value="moonlight" aria-pressed="false">
|
||||
<CloudMoon :size="18" class="theme-icon icon"></CloudMoon>
|
||||
{{ $t('navbar.theme_moonlight') }}
|
||||
<button type="button" class="dropdown-item d-flex align-items-center" data-bs-theme-value="mocha" aria-pressed="false">
|
||||
<Coffee :size="18" class="theme-icon icon"></Coffee>
|
||||
{{ $t('navbar.theme_mocha') }}
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<button type="button" class="dropdown-item d-flex align-items-center" data-bs-theme-value="midnight" aria-pressed="false">
|
||||
<CloudRain :size="18" class="theme-icon icon"></CloudRain>
|
||||
{{ $t('navbar.theme_midnight') }}
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<button type="button" class="dropdown-item d-flex align-items-center" data-bs-theme-value="ember" aria-pressed="false">
|
||||
<Flame :size="18" class="theme-icon icon"></Flame>
|
||||
{{ $t('navbar.theme_ember') }}
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<button type="button" class="dropdown-item d-flex align-items-center" data-bs-theme-value="rose-pine" aria-pressed="false">
|
||||
<TreePine :size="18" class="theme-icon icon"></TreePine>
|
||||
{{ $t('navbar.theme_rose_pine') }}
|
||||
</button>
|
||||
<button type="button" class="dropdown-item d-flex align-items-center" data-bs-theme-value="moonlight" aria-pressed="false">
|
||||
<CloudMoon :size="18" class="theme-icon icon"></CloudMoon>
|
||||
{{ $t('navbar.theme_moonlight') }}
|
||||
</button>
|
||||
<button type="button" class="dropdown-item d-flex align-items-center" data-bs-theme-value="slate" aria-pressed="false">
|
||||
<Layers :size="18" class="theme-icon icon"></Layers>
|
||||
{{ $t('navbar.theme_slate') }}
|
||||
</button>
|
||||
<button type="button" class="dropdown-item d-flex align-items-center" data-bs-theme-value="midnight" aria-pressed="false">
|
||||
<CloudRain :size="18" class="theme-icon icon"></CloudRain>
|
||||
{{ $t('navbar.theme_midnight') }}
|
||||
</button>
|
||||
<button type="button" class="dropdown-item d-flex align-items-center" data-bs-theme-value="nord" aria-pressed="false">
|
||||
<Mountain :size="18" class="theme-icon icon"></Mountain>
|
||||
{{ $t('navbar.theme_nord') }}
|
||||
</button>
|
||||
</li>
|
||||
<li><hr class="dropdown-divider"></li>
|
||||
<!-- Light Themes -->
|
||||
<li>
|
||||
<li class="theme-menu-group">
|
||||
<h6 class="dropdown-header">{{ $t('navbar.theme_group_light') }}</h6>
|
||||
<button type="button" class="dropdown-item d-flex align-items-center" data-bs-theme-value="light" aria-pressed="false">
|
||||
<Sun :size="18" class="theme-icon icon"></Sun>
|
||||
{{ $t('navbar.theme_light') }}
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<button type="button" class="dropdown-item d-flex align-items-center" data-bs-theme-value="indigo" aria-pressed="false">
|
||||
<Sparkles :size="18" class="theme-icon icon"></Sparkles>
|
||||
{{ $t('navbar.theme_indigo') }}
|
||||
<button type="button" class="dropdown-item d-flex align-items-center" data-bs-theme-value="alucard" aria-pressed="false">
|
||||
<Droplet :size="18" class="theme-icon icon"></Droplet>
|
||||
{{ $t('navbar.theme_alucard') }}
|
||||
</button>
|
||||
<button type="button" class="dropdown-item d-flex align-items-center" data-bs-theme-value="latte" aria-pressed="false">
|
||||
<Milk :size="18" class="theme-icon icon"></Milk>
|
||||
{{ $t('navbar.theme_latte') }}
|
||||
</button>
|
||||
<button type="button" class="dropdown-item d-flex align-items-center" data-bs-theme-value="ember-light" aria-pressed="false">
|
||||
<Sunset :size="18" class="theme-icon icon"></Sunset>
|
||||
{{ $t('navbar.theme_ember_light') }}
|
||||
</button>
|
||||
<button type="button" class="dropdown-item d-flex align-items-center" data-bs-theme-value="rose-pine-dawn" aria-pressed="false">
|
||||
<Sprout :size="18" class="theme-icon icon"></Sprout>
|
||||
{{ $t('navbar.theme_rose_pine_dawn') }}
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<button type="button" class="dropdown-item d-flex align-items-center" data-bs-theme-value="sunshine" aria-pressed="false">
|
||||
<Sunrise :size="18" class="theme-icon icon"></Sunrise>
|
||||
{{ $t('navbar.theme_sunshine') }}
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<button type="button" class="dropdown-item d-flex align-items-center" data-bs-theme-value="indigo" aria-pressed="false">
|
||||
<Sparkles :size="18" class="theme-icon icon"></Sparkles>
|
||||
{{ $t('navbar.theme_indigo') }}
|
||||
</button>
|
||||
<button type="button" class="dropdown-item d-flex align-items-center" data-bs-theme-value="ocean" aria-pressed="false">
|
||||
<Waves :size="18" class="theme-icon icon"></Waves>
|
||||
{{ $t('navbar.theme_ocean') }}
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<button type="button" class="dropdown-item d-flex align-items-center" data-bs-theme-value="forest" aria-pressed="false">
|
||||
<Trees :size="18" class="theme-icon icon"></Trees>
|
||||
{{ $t('navbar.theme_forest') }}
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<button type="button" class="dropdown-item d-flex align-items-center" data-bs-theme-value="rose" aria-pressed="false">
|
||||
<Flower :size="18" class="theme-icon icon"></Flower>
|
||||
{{ $t('navbar.theme_rose') }}
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<button type="button" class="dropdown-item d-flex align-items-center" data-bs-theme-value="lavender" aria-pressed="false">
|
||||
<Flower2 :size="18" class="theme-icon icon"></Flower2>
|
||||
{{ $t('navbar.theme_lavender') }}
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<button type="button" class="dropdown-item d-flex align-items-center" data-bs-theme-value="monochrome" aria-pressed="false">
|
||||
<Contrast :size="18" class="theme-icon icon"></Contrast>
|
||||
{{ $t('navbar.theme_monochrome') }}
|
||||
|
||||
@@ -7,14 +7,14 @@
|
||||
|
||||
<body id="app" v-cloak>
|
||||
<Navbar></Navbar>
|
||||
<div class="container">
|
||||
<div id="content" class="container">
|
||||
<div class="my-4">
|
||||
<h1>{{ $t('apps.applications_title') }}<span v-if="apps.length"> ({{ appCountLabel }})</span></h1>
|
||||
<p>{{ $t('apps.applications_desc') }}</p>
|
||||
</div>
|
||||
|
||||
<!-- Actions toolbar -->
|
||||
<div class="apps-toolbar d-flex flex-wrap justify-content-between align-items-center gap-2 mb-3">
|
||||
<div class="toolbar apps-toolbar d-flex flex-wrap justify-content-between align-items-center gap-2 mb-3">
|
||||
<!-- Left side actions -->
|
||||
<div class="d-flex align-items-center gap-2">
|
||||
<!-- Add new application -->
|
||||
@@ -63,19 +63,26 @@
|
||||
<div v-else class="app-poster-placeholder">
|
||||
<span class="app-initial">{{ app.name.charAt(0).toUpperCase() }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body d-flex flex-column">
|
||||
<h5 class="card-title mb-2">{{ app.name }}</h5>
|
||||
<div class="app-details text-muted small mb-3">
|
||||
<div v-if="app.output" class="text-truncate">
|
||||
<file-text :size="14" class="icon me-1"></file-text>
|
||||
{{ app.output }}
|
||||
</div>
|
||||
<div v-if="app.cmd" class="text-truncate">
|
||||
<div class="app-poster-overlay">
|
||||
<div v-if="app.cmd" class="app-overlay-row" :title="app.cmd">
|
||||
<terminal :size="14" class="icon me-1"></terminal>
|
||||
{{ app.cmd }}
|
||||
<span class="app-detail-text">{{ app.cmd }}</span>
|
||||
</div>
|
||||
<div v-if="app['working-dir']" class="app-overlay-row" :title="app['working-dir']">
|
||||
<folder :size="14" class="icon me-1"></folder>
|
||||
<span class="app-detail-text">{{ app['working-dir'] }}</span>
|
||||
</div>
|
||||
<div class="app-overlay-badges">
|
||||
<span v-if="app.elevated" class="badge app-flag-badge">{{ $t('apps.badge_admin') }}</span>
|
||||
<span v-if="app.detached && app.detached.length" class="badge app-flag-badge">{{ $t('apps.badge_detached') }}</span>
|
||||
<span v-if="app['prep-cmd'] && app['prep-cmd'].length" class="badge app-flag-badge">{{ $t('apps.badge_app_prep') }}</span>
|
||||
<span v-if="app['exclude-global-prep-cmd']" class="badge app-flag-badge">{{ $t('apps.badge_no_global_prep') }}</span>
|
||||
<span v-if="app['auto-detach']" class="badge app-flag-badge">{{ $t('apps.badge_auto_detach') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body d-flex flex-column">
|
||||
<h5 class="card-title mb-3">{{ app.name }}</h5>
|
||||
<div class="mt-auto d-flex gap-2">
|
||||
<button class="btn btn-sm btn-primary flex-fill" @click="editApp(index)">
|
||||
<edit :size="16" class="icon"></edit>
|
||||
|
||||
@@ -7,12 +7,15 @@
|
||||
|
||||
<body id="app" v-cloak>
|
||||
<Navbar></Navbar>
|
||||
<div class="container">
|
||||
<h1 class="my-4">{{ $t('config.configuration') }}</h1>
|
||||
<div id="content" class="container">
|
||||
<div class="my-4">
|
||||
<h1>{{ $t('config.configuration') }}</h1>
|
||||
<p>{{ $t('config.configuration_desc') }}</p>
|
||||
</div>
|
||||
|
||||
<!-- Search Bar with Autocomplete -->
|
||||
<div class="mb-3">
|
||||
<div class="input-group">
|
||||
<div class="toolbar mb-3 d-flex flex-wrap align-items-center gap-3">
|
||||
<div class="input-group config-search">
|
||||
<span class="input-group-text">
|
||||
<search :size="18" class="icon"></search>
|
||||
</span>
|
||||
@@ -30,25 +33,60 @@
|
||||
{{ option.tab }} - {{ option.label }}
|
||||
</option>
|
||||
</datalist>
|
||||
<div v-if="searchQuery && searchResults.length === 0" class="text-muted small mt-1">
|
||||
<span v-if="searchQuery && searchResults.length === 0" class="text-muted small flex-shrink-0">
|
||||
No results found for "{{ searchQuery }}"
|
||||
</div>
|
||||
<div v-else-if="searchQuery" class="text-muted small mt-1">
|
||||
</span>
|
||||
<span v-else-if="searchQuery" class="text-muted small flex-shrink-0">
|
||||
Found {{ searchResults.length }} result(s)
|
||||
</div>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="form" v-if="config">
|
||||
<!-- Header -->
|
||||
<ul class="nav nav-tabs">
|
||||
<li class="nav-item" v-for="tab in tabs" :key="tab.id">
|
||||
<a class="nav-link" :class="{'active': tab.id === currentTab}" href="#"
|
||||
@click="currentTab = tab.id">
|
||||
<component :is="getTabIcon(tab.id)" :size="18" class="icon"></component>
|
||||
{{tab.name}}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="config-layout">
|
||||
<!-- Sidebar navigation -->
|
||||
<nav class="config-nav">
|
||||
<ul class="nav config-nav-list">
|
||||
<li class="nav-item" v-for="tab in generalTabs" :key="tab.id">
|
||||
<a class="nav-link" :class="{'active': tab.id === currentTab}" href="#"
|
||||
@click="currentTab = tab.id">
|
||||
<component :is="getTabIcon(tab.id)" :size="18" class="icon"></component>
|
||||
{{tab.name}}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<template v-if="encoderTabs.length">
|
||||
<div class="config-nav-heading">{{ $t('config.encoders') }}</div>
|
||||
<ul class="nav config-nav-list">
|
||||
<li class="nav-item" v-for="tab in encoderTabs" :key="tab.id">
|
||||
<a class="nav-link" :class="{'active': tab.id === currentTab}" href="#"
|
||||
@click="currentTab = tab.id">
|
||||
<component :is="getTabIcon(tab.id)" :size="18" class="icon"></component>
|
||||
{{tab.name}}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</template>
|
||||
|
||||
<div class="config-actions">
|
||||
<button class="btn btn-primary" @click="save">
|
||||
<save :size="18" class="icon"></save>
|
||||
{{ $t('_common.save') }}
|
||||
</button>
|
||||
<button class="btn btn-success" @click="apply" v-if="saved && !restarted">
|
||||
<check :size="18" class="icon"></check>
|
||||
{{ $t('_common.apply') }}
|
||||
</button>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- Tab content -->
|
||||
<div class="config-content">
|
||||
<div class="alert alert-success mb-4" v-if="saved && !restarted">
|
||||
<b>{{ $t('_common.success') }}</b> {{ $t('config.apply_note') }}
|
||||
</div>
|
||||
<div class="alert alert-success mb-4" v-if="restarted">
|
||||
<b>{{ $t('_common.success') }}</b> {{ $t('config.restart_note') }}
|
||||
</div>
|
||||
|
||||
<!-- General Tab -->
|
||||
<general
|
||||
@@ -98,25 +136,10 @@
|
||||
:config="config"
|
||||
:platform="platform">
|
||||
</container-encoders>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Save and Apply buttons -->
|
||||
<div class="alert alert-success my-4" v-if="saved && !restarted">
|
||||
<b>{{ $t('_common.success') }}</b> {{ $t('config.apply_note') }}
|
||||
</div>
|
||||
<div class="alert alert-success my-4" v-if="restarted">
|
||||
<b>{{ $t('_common.success') }}</b> {{ $t('config.restart_note') }}
|
||||
</div>
|
||||
<div class="mb-3 d-flex gap-2 mt-4">
|
||||
<button class="btn btn-primary" @click="save">
|
||||
<save :size="18" class="icon"></save>
|
||||
{{ $t('_common.save') }}
|
||||
</button>
|
||||
<button class="btn btn-success" @click="apply" v-if="saved && !restarted">
|
||||
<check :size="18" class="icon"></check>
|
||||
{{ $t('_common.apply') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
@@ -148,6 +171,8 @@
|
||||
Volume2,
|
||||
} from '@lucide/vue'
|
||||
|
||||
const ENCODER_TAB_IDS = new Set(["nv", "amd", "qsv", "vaapi", "vt", "vulkan", "sw"]);
|
||||
|
||||
const app = createApp({
|
||||
components: {
|
||||
Navbar,
|
||||
@@ -360,6 +385,12 @@
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
generalTabs() {
|
||||
return this.tabs.filter(tab => !ENCODER_TAB_IDS.has(tab.id));
|
||||
},
|
||||
encoderTabs() {
|
||||
return this.tabs.filter(tab => ENCODER_TAB_IDS.has(tab.id));
|
||||
},
|
||||
allConfigOptions() {
|
||||
const options = [];
|
||||
this.tabs.forEach(tab => {
|
||||
|
||||
@@ -7,14 +7,14 @@
|
||||
|
||||
<body id="app" v-cloak>
|
||||
<Navbar></Navbar>
|
||||
<div class="container">
|
||||
<div id="content" class="container">
|
||||
<div class="my-4">
|
||||
<h1>{{ $t('featured.title') }}</h1>
|
||||
<p>{{ $t('featured.description') }}</p>
|
||||
</div>
|
||||
|
||||
<!-- Category Filter -->
|
||||
<div class="mb-4">
|
||||
<div class="toolbar mb-4">
|
||||
<div class="btn-group" role="group" aria-label="Category filter">
|
||||
<button
|
||||
type="button"
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
import i18n from './locale'
|
||||
|
||||
import 'bootstrap/dist/css/bootstrap.min.css'
|
||||
// Load Sunshine.css after bootstrap to override some of the styles.
|
||||
// Makes themes load and style correctly.
|
||||
import './sunshine.css'
|
||||
|
||||
// must import even if not implicitly using here
|
||||
// https://github.com/aurelia/skeleton-navigation/issues/894
|
||||
// https://discourse.aurelia.io/t/bootstrap-import-bootstrap-breaks-dropdown-menu-in-navbar/641/9
|
||||
|
||||
@@ -27,8 +27,11 @@
|
||||
<script type="module">
|
||||
import { createApp } from 'vue'
|
||||
import { initApp } from './init'
|
||||
import { loadAutoTheme } from './theme'
|
||||
import { LogIn } from '@lucide/vue'
|
||||
|
||||
loadAutoTheme();
|
||||
|
||||
const app = createApp({
|
||||
components: {
|
||||
LogIn,
|
||||
|
||||
@@ -7,8 +7,11 @@
|
||||
|
||||
<body id="app" v-cloak>
|
||||
<Navbar></Navbar>
|
||||
<div class="container">
|
||||
<h1 class="my-4">{{ $t('password.password_change') }}</h1>
|
||||
<div id="content" class="container">
|
||||
<div class="my-4">
|
||||
<h1>{{ $t('password.password_change') }}</h1>
|
||||
<p>{{ $t('password.password_change_desc') }}</p>
|
||||
</div>
|
||||
<form @submit.prevent="save">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
|
||||
@@ -42,6 +42,11 @@
|
||||
"applications_title": "Applications",
|
||||
"auto_detach": "Continue streaming if the application exits quickly",
|
||||
"auto_detach_desc": "This will attempt to automatically detect launcher-type apps that close quickly after launching another program or instance of themselves. When a launcher-type app is detected, it is treated as a detached app.",
|
||||
"badge_admin": "Admin",
|
||||
"badge_app_prep": "App prep",
|
||||
"badge_auto_detach": "Auto-detach",
|
||||
"badge_detached": "Detached",
|
||||
"badge_no_global_prep": "No global prep",
|
||||
"cmd": "Command",
|
||||
"cmd_desc": "The main application to start. If blank, no application will be started.",
|
||||
"cmd_note": "If the path to the command executable contains spaces, you must enclose it in double quotes.",
|
||||
@@ -169,6 +174,7 @@
|
||||
"coder_cabac": "cabac -- context adaptive binary arithmetic coding - higher quality",
|
||||
"coder_cavlc": "cavlc -- context adaptive variable-length coding - faster decode",
|
||||
"configuration": "Configuration",
|
||||
"configuration_desc": "Configure Sunshine's host settings to match your streaming setup.",
|
||||
"controller": "Enable Gamepad Input",
|
||||
"controller_desc": "Allows guests to control the host system with a gamepad / controller",
|
||||
"credentials_file": "Credentials File",
|
||||
@@ -223,6 +229,7 @@
|
||||
"encoder": "Force a Specific Encoder",
|
||||
"encoder_desc": "Force a specific encoder, otherwise Sunshine will select the best available option. Note: If you specify a hardware encoder on Windows, it must match the GPU where the display is connected.",
|
||||
"encoder_software": "Software",
|
||||
"encoders": "Encoders",
|
||||
"external_ip": "External IP",
|
||||
"external_ip_desc": "If no external IP address is given, Sunshine will automatically detect external IP",
|
||||
"fec_percentage": "FEC Percentage",
|
||||
@@ -440,19 +447,29 @@
|
||||
"logout": "Log Out",
|
||||
"password": "Change Password",
|
||||
"pin": "PIN",
|
||||
"theme_alucard": "Alucard",
|
||||
"theme_auto": "Auto",
|
||||
"theme_dark": "Dark",
|
||||
"theme_dracula": "Dracula",
|
||||
"theme_ember": "Ember",
|
||||
"theme_ember_light": "Ember Light",
|
||||
"theme_forest": "Forest",
|
||||
"theme_group_dark": "Dark Themes",
|
||||
"theme_group_light": "Light Themes",
|
||||
"theme_indigo": "Indigo",
|
||||
"theme_latte": "Latte",
|
||||
"theme_lavender": "Lavender",
|
||||
"theme_light": "Light",
|
||||
"theme_midnight": "Midnight",
|
||||
"theme_mocha": "Mocha",
|
||||
"theme_monochrome": "Monochrome",
|
||||
"theme_moonlight": "Moonlight",
|
||||
"theme_nord": "Nord",
|
||||
"theme_ocean": "Ocean",
|
||||
"theme_random": "Random",
|
||||
"theme_rose": "Rose",
|
||||
"theme_rose_pine": "Rosé Pine",
|
||||
"theme_rose_pine_dawn": "Rosé Pine Dawn",
|
||||
"theme_slate": "Slate",
|
||||
"theme_sunshine": "Sunshine",
|
||||
"toggle_theme": "Theme",
|
||||
@@ -469,6 +486,7 @@
|
||||
"new_creds": "New Credentials",
|
||||
"new_username_desc": "If not specified, the username will not change",
|
||||
"password_change": "Password Change",
|
||||
"password_change_desc": "Update the username and password used to sign in to the Sunshine web UI. You'll need to sign in again after changing them.",
|
||||
"success_msg": "Password has been changed successfully! This page will reload soon, your browser will ask you for the new credentials."
|
||||
},
|
||||
"pin": {
|
||||
|
||||
+719
-145
File diff suppressed because it is too large
Load Diff
@@ -4,5 +4,3 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Sunshine</title>
|
||||
<link rel="icon" type="image/x-icon" href="./images/sunshine.ico">
|
||||
<link href="bootstrap/dist/css/bootstrap.min.css" rel="stylesheet" />
|
||||
<link href="./assets/css/sunshine.css" rel="stylesheet" />
|
||||
|
||||
@@ -13,10 +13,13 @@ export const getPreferredTheme = () => {
|
||||
// Define which themes are dark (for Bootstrap compatibility)
|
||||
const darkThemes = new Set([
|
||||
'dark',
|
||||
'dracula',
|
||||
'ember',
|
||||
'midnight',
|
||||
'mocha',
|
||||
'moonlight',
|
||||
'nord',
|
||||
'rose-pine',
|
||||
'slate',
|
||||
])
|
||||
|
||||
@@ -78,17 +81,31 @@ export const showActiveTheme = (theme, focus = false) => {
|
||||
}
|
||||
}
|
||||
|
||||
const applyTheme = theme => {
|
||||
setStoredTheme(theme)
|
||||
setTheme(theme)
|
||||
showActiveTheme(theme, true)
|
||||
}
|
||||
|
||||
const pickRandomTheme = () => {
|
||||
const current = getStoredTheme()
|
||||
const values = Array.from(document.querySelectorAll('[data-bs-theme-value]'))
|
||||
.map(el => el.dataset.bsThemeValue)
|
||||
.filter(value => value !== 'auto' && value !== current)
|
||||
return values[Math.floor(Math.random() * values.length)] // NOSONAR(javascript:S2245) random not used for cryptography here
|
||||
}
|
||||
|
||||
export function setupThemeToggleListener() {
|
||||
document.querySelectorAll('[data-bs-theme-value]')
|
||||
.forEach(toggle => {
|
||||
toggle.addEventListener('click', () => {
|
||||
const theme = toggle.getAttribute('data-bs-theme-value')
|
||||
setStoredTheme(theme)
|
||||
setTheme(theme)
|
||||
showActiveTheme(theme, true)
|
||||
})
|
||||
toggle.addEventListener('click', () => applyTheme(toggle.dataset.bsThemeValue))
|
||||
})
|
||||
|
||||
const randomToggle = document.querySelector('#bd-theme-random')
|
||||
if (randomToggle) {
|
||||
randomToggle.addEventListener('click', () => applyTheme(pickRandomTheme()))
|
||||
}
|
||||
|
||||
showActiveTheme(getPreferredTheme(), false)
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
<body id="app" v-cloak>
|
||||
<Navbar></Navbar>
|
||||
<div class="container">
|
||||
<div id="content" class="container">
|
||||
<h1 class="my-4">{{ $t('troubleshooting.troubleshooting') }}</h1>
|
||||
<!-- ViGEmBus Installation -->
|
||||
<div class="card my-4" v-if="platform === 'windows' && controllerEnabled">
|
||||
|
||||
Reference in New Issue
Block a user