兼容低内核版本的浏览器

This commit is contained in:
eafonyang
2026-06-03 15:42:58 +08:00
parent e17c096f0b
commit 56da3e0d1f
29 changed files with 1246 additions and 82 deletions
+37 -10
View File
@@ -1,12 +1,39 @@
<!doctype html>
<html lang="en" class="dark">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1" />
<title>Luxsin X9 Controller</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1" />
<title>Luxsin X9 Controller</title>
<style>
body { margin:0; display:flex; align-items:center; justify-content:center; min-height:100vh; background:#000; color:rgba(255,255,255,.5); font-family:-apple-system,sans-serif; }
.spinner { width:24px; height:24px; border:3px solid rgba(255,255,255,.15); border-top-color:#00FFF6; border-radius:50%; animation:spin .8s linear infinite; margin-right:12px; }
@keyframes spin { to { transform:rotate(360deg); } }
</style>
</head>
<body>
<div class="spinner"></div>
<span>Loading…</span>
<script>
(function () {
var ua = navigator.userAgent;
var chromeMatch = ua.match(/Chrome\/(\d+)/);
var isAndroid = /Android/.test(ua);
var chromeVersion = chromeMatch ? parseInt(chromeMatch[1], 10) : 999;
var currentPath = location.pathname;
var basePath = currentPath.substring(0, currentPath.lastIndexOf("/") + 1);
var target;
if (isAndroid && chromeVersion < 100) {
target = basePath + "legacy/i.legacy.html";
} else {
target = basePath + "modern/i.modern.html";
}
var qs = location.search || "";
var hash = location.hash || "";
location.replace(target + qs + hash);
})();
</script>
</body>
</html>
+12
View File
@@ -0,0 +1,12 @@
<!doctype html>
<html lang="en" class="dark">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1" />
<title>Luxsin X9 Controller (Legacy)</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.legacy.tsx"></script>
</body>
</html>
+12
View File
@@ -0,0 +1,12 @@
<!doctype html>
<html lang="en" class="dark">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1" />
<title>Luxsin X9 Controller</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 101 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 95 KiB

After

Width:  |  Height:  |  Size: 141 KiB

+3 -5
View File
@@ -21,7 +21,7 @@ import SelectPage from "./pages/SelectPage";
import CommunityPage from "./pages/CommunityPage";
import { AIDrawerProvider } from "./contexts/AIDrawerContext";
import DesktopAIShell from "./components/DesktopAIShell";
import { appShellColumnClass, appShellGutterClass } from "./lib/appShell";
import { appShellColumnClass, appShellOuterClass } from "./lib/appShell";
const EQPage = lazy(() => import("./pages/EQPage"));
const EffectsPage = lazy(() => import("./pages/EffectsPage"));
@@ -87,10 +87,8 @@ function App() {
<Toaster />
<Router hook={useHashLocation}>
<AIDrawerProvider>
<div
className={`min-h-dvh bg-[#0d1117] flex justify-center ${appShellGutterClass}`}
>
<div className={`min-h-dvh ${appShellColumnClass}`}>
<div className={appShellOuterClass}>
<div className={appShellColumnClass}>
<DesktopAIShell>
<AppRouter />
</DesktopAIShell>
+3 -5
View File
@@ -22,7 +22,7 @@ import {
} from "lucide-react";
import { Link, useLocation } from "wouter";
import { useMemo } from "react";
import { appShellColumnClass, appShellGutterClass } from "@/lib/appShell";
import { bottomNavInnerClass, bottomNavOuterClass } from "@/lib/appShell";
type BottomNavLocale = NonNullable<(typeof localeZh)["bottomNav"]>;
@@ -56,11 +56,9 @@ export default function BottomNav() {
}, [deviceState?.language]);
return (
<nav
className={`fixed inset-x-0 bottom-0 z-50 flex justify-center pointer-events-none ${appShellGutterClass}`}
>
<nav className={bottomNavOuterClass}>
<div
className={`pointer-events-auto ${appShellColumnClass}`}
className={bottomNavInnerClass}
style={{
background: "rgba(10, 10, 12, 0.96)",
backdropFilter: "blur(40px) saturate(200%)",
+8 -2
View File
@@ -65,6 +65,8 @@ function SplitDivider({
);
}
const isLegacyBuild = import.meta.env.VITE_X9_LEGACY === "1";
/**
* lg+: optional side-by-side AI panel with draggable divider. Mobile unchanged (router only).
*/
@@ -87,7 +89,9 @@ export default function DesktopAIShell({ children }: { children: ReactNode }) {
"w-full min-w-0",
showSplit
? "flex h-dvh max-h-dvh flex-row overflow-hidden"
: "min-h-dvh",
: isLegacyBuild
? "min-h-0"
: "min-h-dvh",
)}
>
<div
@@ -97,7 +101,9 @@ export default function DesktopAIShell({ children }: { children: ReactNode }) {
? "h-full min-h-0 overflow-y-auto overflow-x-hidden"
: isLgUp
? "min-h-dvh overflow-x-hidden"
: "min-h-dvh max-h-dvh overflow-x-hidden overflow-y-auto overscroll-y-contain [touch-action:pan-y]",
: isLegacyBuild
? "w-full overflow-x-hidden"
: "min-h-dvh max-h-dvh overflow-x-hidden overflow-y-auto overscroll-y-contain [touch-action:pan-y]",
)}
style={
showSplit
+57 -1
View File
@@ -119,6 +119,8 @@
html,
body,
#root {
width: 100%;
max-width: 100%;
min-height: 100%;
}
@@ -165,6 +167,60 @@
}
@layer components {
/* App shell: mobile full-bleed; desktop centered column with gutters (lg+). */
.x9-app-shell-outer {
min-height: 100dvh;
width: 100%;
min-width: 0;
display: flex;
justify-content: center;
background-color: #0d1117;
padding-left: 0;
padding-right: 0;
}
.x9-app-shell-column {
min-height: 100dvh;
width: 100%;
min-width: 0;
max-width: none;
}
.x9-bottom-nav-outer {
pointer-events: none;
position: fixed;
left: 0;
right: 0;
bottom: 0;
z-index: 50;
display: flex;
width: 100%;
min-width: 0;
justify-content: center;
padding-left: 0;
padding-right: 0;
}
.x9-bottom-nav-inner {
pointer-events: auto;
width: 100%;
min-width: 0;
max-width: none;
}
@media (min-width: 1024px) {
.x9-app-shell-outer,
.x9-bottom-nav-outer {
padding-left: 1.5rem;
padding-right: 1.5rem;
}
.x9-app-shell-column,
.x9-bottom-nav-inner {
max-width: 1180px;
}
}
.container {
width: 100%;
margin-left: auto;
@@ -374,7 +430,7 @@
padding: 16px 16px 8px;
position: sticky;
top: 0;
z-index: 10;
z-index: 50;
background: rgba(0, 0, 0, 0.85);
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
+346
View File
@@ -0,0 +1,346 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
/* Legacy baseline: Chrome 91 (no dvh/svh, no color-mix, no oklch). */
html,
body,
#root {
width: 100%;
max-width: 100%;
min-height: 100%;
}
@media (max-width: 1023px) {
html,
body,
#root {
height: auto;
}
html,
body {
overflow-x: hidden;
overflow-y: auto;
-webkit-overflow-scrolling: touch;
overscroll-behavior-x: none;
}
#root {
overflow-x: hidden;
overflow-y: visible;
}
/*
* Nested max-h-dvh + overflow-y-auto (DesktopAIShell on mobile) does not scroll
* reliably on Chrome 91 WebView — use document scrolling instead.
*/
.max-h-dvh {
max-height: none !important;
}
.h-dvh {
height: auto !important;
}
.min-h-dvh {
min-height: 0;
}
.x9-app-shell-outer {
min-height: 100vh;
min-height: -webkit-fill-available;
}
.x9-app-shell-column {
min-height: 0;
height: auto;
}
}
body {
background: #000;
color: #f2f2f2;
-webkit-font-smoothing: antialiased;
font-family: -apple-system, "SF Pro Display", "SF Pro Text", "Helvetica Neue",
sans-serif;
}
@layer utilities {
.min-h-dvh {
min-height: 100vh;
min-height: -webkit-fill-available;
}
.h-dvh {
height: 100vh;
height: -webkit-fill-available;
}
.max-h-dvh {
max-height: 100vh;
max-height: -webkit-fill-available;
}
.h-dvh-full {
height: 100vh;
height: -webkit-fill-available;
}
.min-h-dvh-full {
min-height: 100vh;
min-height: -webkit-fill-available;
}
}
@layer components {
.x9-app-shell-outer {
min-height: 100vh;
min-height: -webkit-fill-available;
width: 100%;
min-width: 0;
display: flex;
justify-content: center;
background-color: #0d1117;
padding-left: 0;
padding-right: 0;
}
.x9-app-shell-column {
min-height: 100vh;
min-height: -webkit-fill-available;
width: 100%;
min-width: 0;
max-width: none;
}
.x9-bottom-nav-outer {
pointer-events: none;
position: fixed;
left: 0;
right: 0;
bottom: 0;
z-index: 50;
display: flex;
width: 100%;
min-width: 0;
justify-content: center;
padding-left: 0;
padding-right: 0;
}
.x9-bottom-nav-inner {
pointer-events: auto;
width: 100%;
min-width: 0;
max-width: none;
}
@media (min-width: 1024px) {
.x9-app-shell-outer,
.x9-bottom-nav-outer {
padding-left: 1.5rem;
padding-right: 1.5rem;
}
.x9-app-shell-column,
.x9-bottom-nav-inner {
max-width: 1180px;
}
}
.glass-card {
background: rgba(28, 28, 30, 0.92);
-webkit-backdrop-filter: blur(20px) saturate(180%);
backdrop-filter: blur(20px) saturate(180%);
border-radius: 16px;
border: 1px solid rgba(255, 255, 255, 0.07);
}
.ios-list-group {
background: rgba(28, 28, 30, 0.92);
border-radius: 16px;
overflow: hidden;
border: 1px solid rgba(255, 255, 255, 0.06);
}
.ios-list-row {
display: flex;
align-items: center;
padding: 13px 16px;
position: relative;
transition: background 0.15s ease;
min-height: 52px;
}
.ios-list-row:active {
background: rgba(255, 255, 255, 0.05);
}
.ios-list-row + .ios-list-row::before {
content: "";
position: absolute;
top: 0;
left: 16px;
right: 0;
height: 1px;
transform: scaleY(0.5);
transform-origin: top left;
background: rgba(255, 255, 255, 0.08);
}
.ios-chevron {
color: rgba(255, 255, 255, 0.25);
flex-shrink: 0;
margin-left: 6px;
}
.ios-row-value {
color: rgba(255, 255, 255, 0.4);
font-size: 15px;
margin-right: 4px;
}
.cyan-slider {
-webkit-appearance: none;
appearance: none;
width: 100%;
height: 4px;
border-radius: 2px;
background: rgba(255, 255, 255, 0.12);
outline: none;
cursor: pointer;
touch-action: pan-y;
}
.cyan-slider::-webkit-slider-thumb {
-webkit-appearance: none;
width: 18px;
height: 18px;
border-radius: 9999px;
background: #ffffff;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.5);
cursor: pointer;
transition: transform 0.1s ease;
border: none;
}
.cyan-slider::-webkit-slider-thumb:active {
transform: scale(1.15);
}
.cyan-slider::-moz-range-track {
height: 4px;
border-radius: 2px;
background: rgba(255, 255, 255, 0.12);
}
.cyan-slider::-moz-range-thumb {
width: 18px;
height: 18px;
border-radius: 9999px;
background: #ffffff;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.5);
border: none;
cursor: pointer;
}
.cyan-slider-thumb-only {
cursor: default;
}
.cyan-slider-thumb-only::-webkit-slider-thumb {
cursor: grab;
}
.cyan-slider-thumb-only::-webkit-slider-thumb:active {
cursor: grabbing;
}
.cyan-slider-thumb-only::-moz-range-thumb {
cursor: grab;
}
.icon-circle-btn {
width: 64px;
height: 64px;
border-radius: 9999px;
background: rgba(44, 44, 46, 0.9);
border: 1px solid rgba(255, 255, 255, 0.12);
display: flex;
align-items: center;
justify-content: center;
transition: transform 0.15s ease, background 0.15s ease, box-shadow 0.15s ease,
border-color 0.15s ease;
cursor: pointer;
}
.icon-circle-btn:active {
transform: scale(0.93);
background: rgba(60, 60, 62, 0.95);
}
.icon-circle-btn.active {
background: rgba(0, 255, 246, 0.12);
border-color: rgba(0, 255, 246, 0.55);
box-shadow: 0 0 0 2px rgba(0, 255, 246, 0.18), 0 0 18px rgba(0, 255, 246, 0.28);
}
.ios-toggle {
position: relative;
display: inline-block;
width: 46px;
height: 28px;
flex-shrink: 0;
}
.ios-toggle input {
opacity: 0;
width: 0;
height: 0;
position: absolute;
}
.ios-toggle-track {
position: absolute;
inset: 0;
border-radius: 999px;
background: rgba(120, 120, 128, 0.32);
transition: background 0.25s ease;
cursor: pointer;
}
.ios-toggle input:checked ~ .ios-toggle-track {
background: #00fff6;
box-shadow: 0 0 12px rgba(0, 255, 246, 0.5);
}
.ios-toggle-thumb {
position: absolute;
top: 2px;
left: 2px;
width: 24px;
height: 24px;
border-radius: 50%;
background: #ffffff;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.4);
transition: transform 0.25s cubic-bezier(0.34, 1.56, 0.64, 1);
pointer-events: none;
}
.ios-toggle input:checked ~ .ios-toggle-track .ios-toggle-thumb {
transform: translateX(18px);
}
.ios-section-label {
font-size: 13px;
font-weight: 600;
letter-spacing: 0.04em;
color: #00fff6;
padding: 20px 4px 8px;
text-transform: uppercase;
}
.page-header {
display: flex;
align-items: center;
padding: 16px 16px 8px;
position: sticky;
top: 0;
z-index: 50;
background: rgba(0, 0, 0, 0.85);
-webkit-backdrop-filter: blur(20px);
backdrop-filter: blur(20px);
}
}
+5 -5
View File
@@ -1,8 +1,8 @@
/** Centered phone-style shell width (desktop / ultrawide). */
export const APP_SHELL_MAX_PX = 1180;
/** Inner column inside horizontal gutters — use with a flex/grid centering parent. */
export const appShellColumnClass =
"w-full min-w-0 max-w-[min(1180px,100%)]";
export const appShellGutterClass = "px-4 sm:px-6";
/** Responsive rules live in `.x9-app-shell-*` (index.css + index.legacy.css). */
export const appShellOuterClass = "x9-app-shell-outer";
export const appShellColumnClass = "x9-app-shell-column";
export const bottomNavOuterClass = "x9-bottom-nav-outer";
export const bottomNavInnerClass = "x9-bottom-nav-inner";
+24
View File
@@ -0,0 +1,24 @@
/**
* Minimal shims for Chrome 91 WebView (and similar legacy Chromium).
* Import once from main.legacy.tsx before the app mounts.
*/
if (typeof Object.hasOwn !== "function") {
Object.hasOwn = (obj, prop) =>
Object.prototype.hasOwnProperty.call(obj, prop);
}
if (!Array.prototype.at) {
// eslint-disable-next-line no-extend-native
Array.prototype.at = function at(index) {
const len = this.length;
const k = index >= 0 ? index : len + index;
if (k < 0 || k >= len) return undefined;
return this[k];
};
}
if (typeof globalThis.structuredClone !== "function") {
globalThis.structuredClone = <T>(value: T): T =>
JSON.parse(JSON.stringify(value)) as T;
}
+7 -1
View File
@@ -28,6 +28,12 @@ export function decodeCustomBase64(encoded: string): string {
const index = ALPHABET_CUSTOM.indexOf(char);
translated += index !== -1 ? ALPHABET_STANDARD.charAt(index) : char;
}
// Chrome 91 atob() is stricter than modern browsers.
translated = translated.replace(/\s/g, "");
const padNeeded = (4 - (translated.length % 4)) % 4;
if (padNeeded > 0) {
translated += "=".repeat(padNeeded);
}
const decoded = atob(translated);
const bytes = new Uint8Array(decoded.length);
for (let i = 0; i < decoded.length; i++) {
@@ -168,7 +174,7 @@ export interface PeqApplyPayload {
// ============================================================
// Input/Output Labels
// ============================================================
export const INPUT_LABELS = ["USB", "USB-C", "Coaxial", "Optical", "Bluetooth", "IIS", "RCA"];
export const INPUT_LABELS = ["USB", "USB-C", "Coaxial", "Optical", "Bluetooth", "HDMI-EARC", "RCA"];
export const OUTPUT_LABELS = ["XLR", "RCA", "Headset", "XLR/RCA"];
/** Device `input` index for Bluetooth source. */
+1
View File
@@ -2,6 +2,7 @@
"home": { "audioSet": "Audioeinstellungen" },
"source": {
"label": "Eingang & Ausgang",
"inputOptions": ["USB-B", "USB-C", "Koaxial", "Optisch", "Bluetooth", "HDMI-EARC", "RCA", "USB-Treiber"],
"input": {
"coaxial": "Koaxial",
"optical": "Optisch",
+2 -2
View File
@@ -55,8 +55,8 @@
"pageTitle": "I/O",
"inputSection": "Input",
"outputSection": "Output",
"inputOptions": ["USB-B", "USB-C", "Coaxial", "Optical", "Bluetooth", "IIS"],
"outputOptions": ["XLR", "RCA", "Headset", "XLR/RCA"],
"inputOptions": ["USB-B", "USB-C", "Coaxial", "Optical", "Bluetooth", "HDMI-EARC", "RCA", "USB Driver"],
"outputOptions": ["XLR", "RCA", "XLR/RCA", "Headset"],
"input": {
"coaxial": "COAXIAL",
"optical": "OPTICAL",
+1
View File
@@ -2,6 +2,7 @@
"home": { "audioSet": "Configuración de audio" },
"source": {
"label": "Entrada/Salida",
"inputOptions": ["USB-B", "USB-C", "Coaxial", "Óptico", "Bluetooth", "HDMI-EARC", "RCA", "Controlador USB"],
"input": {
"coaxial": "Coaxial",
"optical": "Óptico",
+1
View File
@@ -2,6 +2,7 @@
"home": { "audioSet": "Paramètres audio" },
"source": {
"label": "Entrée/Sortie",
"inputOptions": ["USB-B", "USB-C", "Coaxial", "Optique", "Bluetooth", "HDMI-EARC", "RCA", "Clé USB"],
"input": {
"coaxial": "Coaxial",
"optical": "Optique",
+2 -2
View File
@@ -55,8 +55,8 @@
"pageTitle": "輸入/輸出",
"inputSection": "輸入",
"outputSection": "輸出",
"inputOptions": ["USB-B", "USB-C", "同軸", "光纖", "藍牙", "IIS"],
"outputOptions": ["XLR", "RCA", "耳機", "XLR/RCA"],
"inputOptions": ["USB-B", "USB-C", "同軸", "光纖", "藍牙", "HDMI-EARC", "類比RCA", "USB隨身碟"],
"outputOptions": ["XLR", "RCA", "XLR/RCA", "耳機"],
"input": { "coaxial": "同軸", "optical": "光纖", "bluetooth": "藍牙", "rca": "類比RCA" },
"output": { "headset": "耳機" }
},
+2 -2
View File
@@ -55,8 +55,8 @@
"pageTitle": "输入输出",
"inputSection": "输入",
"outputSection": "输出",
"inputOptions": ["USB-B", "USB-C", "同轴", "光纤", "蓝牙", "IIS"],
"outputOptions": ["XLR", "RCA", "耳机", "XLR/RCA"],
"inputOptions": ["USB-B", "USB-C", "同轴", "光纤", "蓝牙", "HDMI-EARC", "模拟RCA", "U盘"],
"outputOptions": ["XLR", "RCA", "XLR/RCA", "耳机"],
"input": { "coaxial": "同轴", "optical": "光纤", "bluetooth": "蓝牙", "rca": "模拟RCA" },
"output": { "headset": "耳机" }
},
+6
View File
@@ -0,0 +1,6 @@
import "@/lib/legacyBrowser";
import { createRoot } from "react-dom/client";
import App from "./App";
import "./index.legacy.css";
createRoot(document.getElementById("root")!).render(<App />);
+10 -4
View File
@@ -1812,10 +1812,13 @@ export default function EQPage() {
</div>
{/* ── Headphone model selector ── */}
<div className="flex items-center justify-between px-1">
<div className="flex min-w-0 items-center gap-2 px-1">
<div
ref={headphoneMenuRef}
className="relative z-40 flex-1 mr-2 rounded-[12px] overflow-visible backdrop-blur-md transition-all"
className={cn(
"relative min-w-0 flex-1 max-w-[calc(100%-5.5rem)] rounded-[12px] overflow-visible backdrop-blur-md transition-all",
isHeadphoneMenuOpen && "z-30",
)}
style={{
background: "linear-gradient(180deg, rgba(36,38,42,0.92) 0%, rgba(24,26,30,0.92) 100%)",
border: "1px solid rgba(0,255,246,0.52)",
@@ -1863,7 +1866,7 @@ export default function EQPage() {
</div>
)}
</div>
<div className="flex items-center gap-2">
<div className="flex shrink-0 items-center gap-2">
<button
className="w-9 h-9 rounded-full flex items-center justify-center text-white/60 active:text-white transition-colors"
style={{ background: "rgba(44,44,46,0.8)", border: "1px solid rgba(255,255,255,0.1)" }}
@@ -1933,7 +1936,10 @@ export default function EQPage() {
<span className="text-[14px] text-white/50 font-medium">{peqCardLabels.filters?.label ?? "滤波器"}</span>
<div
ref={filterMenuRef}
className="relative z-40 w-[136px] rounded-[12px] overflow-visible backdrop-blur-md transition-all"
className={cn(
"relative w-[136px] rounded-[12px] overflow-visible backdrop-blur-md transition-all",
isFilterMenuOpen && "z-30",
)}
style={{
background: "linear-gradient(180deg, rgba(36,38,42,0.92) 0%, rgba(24,26,30,0.92) 100%)",
border: "1px solid rgba(0,255,246,0.52)",
+53 -21
View File
@@ -32,11 +32,17 @@ const INPUT_OPTION_DEFS: IOOptionDef[] = [
{
index: 0,
icon: (
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" className="w-7 h-7">
<rect x="8" y="6" width="8" height="12" rx="1.5" />
<line x1="10" y1="6" x2="10" y2="4" />
<line x1="14" y1="6" x2="14" y2="4" />
</svg>
<svg xmlns="http://www.w3.org/2000/svg" width="2em" height="2em" viewBox="0 0 24 24">
<path d="M0 0h24v24H0z" fill="none" />
<g fill="none">
<path d="M3 12h18v9H3zm2-9h14v9H5z" />
<g stroke="currentColor" stroke-width="2">
<path d="M3 12h18v9H3zm16 0H5V3h14z" />
<path d="M9 7.496h.004V7.5H9zm6 0h.004V7.5H15z" />
</g>
</g>
</svg>
),
},
{
@@ -76,15 +82,41 @@ const INPUT_OPTION_DEFS: IOOptionDef[] = [
},
{
index: 5,
icon: (
<svg xmlns="http://www.w3.org/2000/svg" width="2em" height="2em" viewBox="0 0 16 16">
<path d="M0 0h16v16H0z" fill="none" />
<g fill="currentColor">
<path d="M2.5 7a.5.5 0 0 0 0 1h11a.5.5 0 0 0 0-1z" />
<path d="M1 5a1 1 0 0 0-1 1v3a1 1 0 0 0 1 1h.293l.707.707a1 1 0 0 0 .707.293h10.586a1 1 0 0 0 .707-.293l.707-.707H15a1 1 0 0 0 1-1V6a1 1 0 0 0-1-1zm0 1h14v3h-.293a1 1 0 0 0-.707.293l-.707.707H2.707L2 9.293A1 1 0 0 0 1.293 9H1z" />
</g>
</svg>
),
},
{
index: 6,
icon: (
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" className="w-7 h-7">
<rect x="3" y="8" width="18" height="10" rx="1.5" />
<line x1="7" y1="8" x2="7" y2="18" />
<line x1="12" y1="8" x2="12" y2="18" />
<line x1="17" y1="8" x2="17" y2="18" />
<circle cx="12" cy="12" r="8" />
<circle cx="12" cy="12" r="3" fill="currentColor" stroke="none" />
</svg>
),
},
{
index: 7,
icon: (
<svg xmlns="http://www.w3.org/2000/svg" width="2em" height="2em" viewBox="0 0 24 24">
<path d="M0 0h24v24H0z" fill="none" />
<g fill="currentColor">
<path d="M10 4.5h1v2h-1zm4 0h-1v2h1z" />
<path fill-rule="evenodd" d="M7 8.5v-7h10v7h2v11a3 3 0 0 1-3 3H8a3 3 0 0 1-3-3v-11zm2-5h6v5H9zm8 7H7v9a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1z" clip-rule="evenodd" />
</g>
</svg>
),
},
];
const OUTPUT_OPTION_DEFS: IOOptionDef[] = [
@@ -108,16 +140,6 @@ const OUTPUT_OPTION_DEFS: IOOptionDef[] = [
</svg>
),
},
{
index: 2,
icon: (
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" className="w-7 h-7">
<path d="M3 18v-6a9 9 0 0 1 18 0v6" />
<path d="M21 19a2 2 0 0 1-2 2h-1a2 2 0 0 1-2-2v-3a2 2 0 0 1 2-2h3z" />
<path d="M3 19a2 2 0 0 0 2 2h1a2 2 0 0 0 2-2v-3a2 2 0 0 0-2-2H3z" />
</svg>
),
},
{
index: 3,
icon: (
@@ -129,10 +151,20 @@ const OUTPUT_OPTION_DEFS: IOOptionDef[] = [
</svg>
),
},
{
index: 2,
icon: (
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" className="w-7 h-7">
<path d="M3 18v-6a9 9 0 0 1 18 0v6" />
<path d="M21 19a2 2 0 0 1-2 2h-1a2 2 0 0 1-2-2v-3a2 2 0 0 1 2-2h3z" />
<path d="M3 19a2 2 0 0 0 2 2h1a2 2 0 0 0 2-2v-3a2 2 0 0 0-2-2H3z" />
</svg>
),
},
];
const FALLBACK_INPUT_LABELS = ["USB-B", "USB-C", "同轴", "光纤", "蓝牙", "IIS"];
const FALLBACK_OUTPUT_LABELS = ["XLR", "RCA", "耳机", "XLR/RCA"];
const FALLBACK_INPUT_LABELS = ["USB-B", "USB-C", "同轴", "光纤", "蓝牙", "HDMI-EARC", "RCA", "USB Driver"];
const FALLBACK_OUTPUT_LABELS = ["XLR", "RCA", "XLR/RCA", "耳机"];
function withLabels(defs: IOOptionDef[], labels: string[] | undefined, fallback: string[]): IOOption[] {
return defs.map((def, i) => ({