mirror of
https://github.com/MrUnknownDE/VRCX.git
synced 2026-05-07 06:56:04 +02:00
refactor: custom fonts
This commit is contained in:
@@ -703,7 +703,7 @@
|
|||||||
"bio_language": "Target Language",
|
"bio_language": "Target Language",
|
||||||
"theme_mode": "Theme",
|
"theme_mode": "Theme",
|
||||||
"font_family": "Font",
|
"font_family": "Font",
|
||||||
"font_family_tooltip": "Only the system font affects CJK characters",
|
"cjk_font_pack": "CJK Font",
|
||||||
"font_family_inter": "Inter",
|
"font_family_inter": "Inter",
|
||||||
"font_family_noto_sans": "Noto Sans",
|
"font_family_noto_sans": "Noto Sans",
|
||||||
"font_family_source_sans_3": "Source Sans 3",
|
"font_family_source_sans_3": "Source Sans 3",
|
||||||
@@ -713,6 +713,12 @@
|
|||||||
"font_family_roboto": "Roboto",
|
"font_family_roboto": "Roboto",
|
||||||
"font_family_fantasque_sans_mono": "Fantasque Sans Mono",
|
"font_family_fantasque_sans_mono": "Fantasque Sans Mono",
|
||||||
"font_family_system_ui": "System Font",
|
"font_family_system_ui": "System Font",
|
||||||
|
"font_family_custom": "Custom",
|
||||||
|
"font_family_custom_dialog_title": "Custom Font Family",
|
||||||
|
"font_family_custom_dialog_description": "Enter a valid CSS font-family value",
|
||||||
|
"font_family_custom_invalid": "Invalid font-family. Use comma-separated font names, e.g. 'My Font', Arial, sans-serif",
|
||||||
|
"cjk_font_pack_noto": "Noto Sans CJK",
|
||||||
|
"cjk_font_pack_pht": "PuHuiTi CJK",
|
||||||
"theme_mode_system": "System",
|
"theme_mode_system": "System",
|
||||||
"theme_mode_light": "Light",
|
"theme_mode_light": "Light",
|
||||||
"theme_mode_dark": "Dark",
|
"theme_mode_dark": "Dark",
|
||||||
|
|||||||
@@ -564,7 +564,6 @@
|
|||||||
"bio_language": "翻訳先の言語",
|
"bio_language": "翻訳先の言語",
|
||||||
"theme_mode": "テーマ",
|
"theme_mode": "テーマ",
|
||||||
"font_family": "フォント",
|
"font_family": "フォント",
|
||||||
"font_family_tooltip": "システムフォントのみがCJK文字に影響します",
|
|
||||||
"font_family_inter": "Inter",
|
"font_family_inter": "Inter",
|
||||||
"font_family_noto_sans": "Noto Sans",
|
"font_family_noto_sans": "Noto Sans",
|
||||||
"font_family_source_sans_3": "Source Sans 3",
|
"font_family_source_sans_3": "Source Sans 3",
|
||||||
|
|||||||
@@ -563,7 +563,6 @@
|
|||||||
"bio_language": "目标语言",
|
"bio_language": "目标语言",
|
||||||
"theme_mode": "主题",
|
"theme_mode": "主题",
|
||||||
"font_family": "字体",
|
"font_family": "字体",
|
||||||
"font_family_tooltip": "只有系统字体会影响 CJK (也就是中/日/韩文)字符。此外,由于部分字体使用了 fonts.gstatic.com 源,所以在网络受限地区可能需要魔法",
|
|
||||||
"font_family_inter": "Inter",
|
"font_family_inter": "Inter",
|
||||||
"font_family_noto_sans": "Noto Sans",
|
"font_family_noto_sans": "Noto Sans",
|
||||||
"font_family_source_sans_3": "Source Sans 3",
|
"font_family_source_sans_3": "Source Sans 3",
|
||||||
|
|||||||
@@ -559,7 +559,6 @@
|
|||||||
"bio_language": "目標語言",
|
"bio_language": "目標語言",
|
||||||
"theme_mode": "主題",
|
"theme_mode": "主題",
|
||||||
"font_family": "字型",
|
"font_family": "字型",
|
||||||
"font_family_tooltip": "只有系統字型會影響 CJK(中日韓)字元顯示",
|
|
||||||
"font_family_inter": "Inter",
|
"font_family_inter": "Inter",
|
||||||
"font_family_noto_sans": "Noto Sans",
|
"font_family_noto_sans": "Noto Sans",
|
||||||
"font_family_source_sans_3": "Source Sans 3",
|
"font_family_source_sans_3": "Source Sans 3",
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { initDayjs } from './dayjs';
|
import { initDayjs } from './dayjs';
|
||||||
import { initInteropApi } from './interopApi';
|
import { initInteropApi } from './interopApi';
|
||||||
import { initNoty } from './noty';
|
import { initNoty } from './noty';
|
||||||
import { initUi } from './ui';
|
import { initUi, initUiForVrOverlay } from './ui';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {boolean} isVrOverlay
|
* @param {boolean} isVrOverlay
|
||||||
@@ -11,6 +11,8 @@ export async function initPlugins(isVrOverlay = false) {
|
|||||||
await initInteropApi(isVrOverlay);
|
await initInteropApi(isVrOverlay);
|
||||||
if (!isVrOverlay) {
|
if (!isVrOverlay) {
|
||||||
await initUi();
|
await initUi();
|
||||||
|
} else {
|
||||||
|
await initUiForVrOverlay();
|
||||||
}
|
}
|
||||||
initDayjs();
|
initDayjs();
|
||||||
if (isVrOverlay) {
|
if (isVrOverlay) {
|
||||||
|
|||||||
+34
-5
@@ -1,12 +1,16 @@
|
|||||||
import {
|
import {
|
||||||
// changeAppDarkStyle,
|
applyAppCjkFontPack,
|
||||||
|
applyAppFontFamily,
|
||||||
changeAppThemeStyle,
|
changeAppThemeStyle,
|
||||||
changeHtmlLangAttribute,
|
changeHtmlLangAttribute,
|
||||||
getThemeMode,
|
getThemeMode,
|
||||||
initThemeColor,
|
initThemeColor,
|
||||||
refreshCustomCss
|
refreshCustomCss
|
||||||
// setLoginContainerStyle
|
|
||||||
} from '../shared/utils/base/ui';
|
} from '../shared/utils/base/ui';
|
||||||
|
import {
|
||||||
|
APP_CJK_FONT_PACK_DEFAULT_KEY,
|
||||||
|
APP_FONT_DEFAULT_KEY
|
||||||
|
} from '../shared/constants';
|
||||||
import { i18n, loadLocalizedStrings } from './i18n';
|
import { i18n, loadLocalizedStrings } from './i18n';
|
||||||
|
|
||||||
import configRepository from '../services/config';
|
import configRepository from '../services/config';
|
||||||
@@ -22,9 +26,7 @@ export async function initUi() {
|
|||||||
await loadLocalizedStrings(language);
|
await loadLocalizedStrings(language);
|
||||||
changeHtmlLangAttribute(language);
|
changeHtmlLangAttribute(language);
|
||||||
|
|
||||||
const { initThemeMode, isDarkMode } =
|
const { initThemeMode } = await getThemeMode(configRepository);
|
||||||
await getThemeMode(configRepository);
|
|
||||||
// setLoginContainerStyle(isDarkMode);
|
|
||||||
changeAppThemeStyle(initThemeMode);
|
changeAppThemeStyle(initThemeMode);
|
||||||
await initThemeColor();
|
await initThemeColor();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -33,3 +35,30 @@ export async function initUi() {
|
|||||||
|
|
||||||
refreshCustomCss();
|
refreshCustomCss();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function initUiForVrOverlay() {
|
||||||
|
try {
|
||||||
|
const [language, fontFamily, customFontFamily, cjkFontPack] =
|
||||||
|
await Promise.all([
|
||||||
|
configRepository.getString('VRCX_appLanguage', 'en'),
|
||||||
|
configRepository.getString(
|
||||||
|
'VRCX_fontFamily',
|
||||||
|
APP_FONT_DEFAULT_KEY
|
||||||
|
),
|
||||||
|
configRepository.getString('VRCX_customFontFamily', ''),
|
||||||
|
configRepository.getString(
|
||||||
|
'VRCX_cjkFontPack',
|
||||||
|
APP_CJK_FONT_PACK_DEFAULT_KEY
|
||||||
|
)
|
||||||
|
]);
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
i18n.locale = language;
|
||||||
|
await loadLocalizedStrings(language);
|
||||||
|
changeHtmlLangAttribute(language);
|
||||||
|
applyAppFontFamily(fontFamily, customFontFamily);
|
||||||
|
applyAppCjkFontPack(cjkFontPack);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error initializing VR locale and fonts:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
const APP_FONT_DEFAULT_KEY = 'inter';
|
const APP_FONT_DEFAULT_KEY = 'inter';
|
||||||
|
const APP_CJK_FONT_PACK_DEFAULT_KEY = 'noto';
|
||||||
|
|
||||||
const APP_FONT_CONFIG = Object.freeze({
|
const APP_FONT_CONFIG = Object.freeze({
|
||||||
inter: {
|
inter: {
|
||||||
@@ -8,7 +9,7 @@ const APP_FONT_CONFIG = Object.freeze({
|
|||||||
noto_sans: {
|
noto_sans: {
|
||||||
cssName: "'Noto Sans'",
|
cssName: "'Noto Sans'",
|
||||||
cssImport:
|
cssImport:
|
||||||
"@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:ital,wght@0,100..700;1,100..700&family=Noto+Sans:ital,wght@0,100..900;1,100..900&family=Source+Sans+3:ital,wght@0,200..900;1,200..900&display=swap');"
|
"@import url('https://fonts.googleapis.com/css2?family=Noto+Sans:ital,wght@0,100..900;1,100..900&display=swap');"
|
||||||
},
|
},
|
||||||
source_sans_3: {
|
source_sans_3: {
|
||||||
cssName: "'Source Sans 3'",
|
cssName: "'Source Sans 3'",
|
||||||
@@ -18,7 +19,7 @@ const APP_FONT_CONFIG = Object.freeze({
|
|||||||
ibm_plex_sans: {
|
ibm_plex_sans: {
|
||||||
cssName: "'IBM Plex Sans'",
|
cssName: "'IBM Plex Sans'",
|
||||||
cssImport:
|
cssImport:
|
||||||
"@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:ital,wght@0,100..700;1,100..700&family=Source+Sans+3:ital,wght@0,200..900;1,200..900&display=swap');"
|
"@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:ital,wght@0,100..700;1,100..700&display=swap');"
|
||||||
},
|
},
|
||||||
harmonyos_sans: {
|
harmonyos_sans: {
|
||||||
cssName: "'HarmonyOS Sans'",
|
cssName: "'HarmonyOS Sans'",
|
||||||
@@ -33,7 +34,7 @@ const APP_FONT_CONFIG = Object.freeze({
|
|||||||
roboto: {
|
roboto: {
|
||||||
cssName: "'Roboto'",
|
cssName: "'Roboto'",
|
||||||
cssImport:
|
cssImport:
|
||||||
"@import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:ital,wght@0,100..800;1,100..800&family=Roboto:ital,wght@0,100..900;1,100..900&display=swap');"
|
"@import url('https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,100..900;1,100..900&display=swap');"
|
||||||
},
|
},
|
||||||
fantasque_sans_mono: {
|
fantasque_sans_mono: {
|
||||||
cssName: "'Fantasque Sans Mono'",
|
cssName: "'Fantasque Sans Mono'",
|
||||||
@@ -43,9 +44,69 @@ const APP_FONT_CONFIG = Object.freeze({
|
|||||||
system_ui: {
|
system_ui: {
|
||||||
cssName: 'system-ui',
|
cssName: 'system-ui',
|
||||||
link: null
|
link: null
|
||||||
|
},
|
||||||
|
custom: {
|
||||||
|
cssName: '',
|
||||||
|
link: null
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const APP_FONT_FAMILIES = Object.freeze(Object.keys(APP_FONT_CONFIG));
|
const APP_FONT_FAMILIES = Object.freeze(Object.keys(APP_FONT_CONFIG));
|
||||||
|
|
||||||
export { APP_FONT_CONFIG, APP_FONT_DEFAULT_KEY, APP_FONT_FAMILIES };
|
const APP_CJK_FONT_PACK_CONFIG = Object.freeze({
|
||||||
|
noto: {
|
||||||
|
cssName: Object.freeze({
|
||||||
|
jp: "'Noto Sans JP Variable'",
|
||||||
|
kr: "'Noto Sans KR Variable'",
|
||||||
|
sc: "'Noto Sans SC Variable'",
|
||||||
|
tc: "'Noto Sans TC Variable'"
|
||||||
|
}),
|
||||||
|
link: null
|
||||||
|
},
|
||||||
|
pht: {
|
||||||
|
cssName: Object.freeze({
|
||||||
|
jp: "'PHT Sans JP'",
|
||||||
|
kr: "'PHT Sans KR'",
|
||||||
|
sc: "'PHT Sans SC'",
|
||||||
|
tc: "'PHT Sans TC'"
|
||||||
|
}),
|
||||||
|
cssImport: [
|
||||||
|
'/* Simplified Chinese */',
|
||||||
|
"@font-face { font-family: 'PHT Sans SC'; src: url('https://cdn.jsdelivr.net/gh/map1en/pht@1.0.0/sc/phtsansSC-Regular.woff2') format('woff2'); font-weight: 400; font-display: swap; }",
|
||||||
|
"@font-face { font-family: 'PHT Sans SC'; src: url('https://cdn.jsdelivr.net/gh/map1en/pht@1.0.0/sc/phtsansSC-Medium.woff2') format('woff2'); font-weight: 500; font-display: swap; }",
|
||||||
|
"@font-face { font-family: 'PHT Sans SC'; src: url('https://cdn.jsdelivr.net/gh/map1en/pht@1.0.0/sc/phtsansSC-SemiBold.woff2') format('woff2'); font-weight: 600; font-display: swap; }",
|
||||||
|
"@font-face { font-family: 'PHT Sans SC'; src: url('https://cdn.jsdelivr.net/gh/map1en/pht@1.0.0/sc/phtsansSC-Bold.woff2') format('woff2'); font-weight: 700; font-display: swap; }",
|
||||||
|
'/* Traditional Chinese */',
|
||||||
|
"@font-face { font-family: 'PHT Sans TC'; src: url('https://cdn.jsdelivr.net/gh/map1en/pht@1.0.0/tc/phtsansTC-55.woff2') format('woff2'); font-weight: 400; font-display: swap; }",
|
||||||
|
"@font-face { font-family: 'PHT Sans TC'; src: url('https://cdn.jsdelivr.net/gh/map1en/pht@1.0.0/tc/phtsansTC-75.woff2') format('woff2'); font-weight: 600; font-display: swap; }",
|
||||||
|
'/* Japanese */',
|
||||||
|
"@font-face { font-family: 'PHT Sans JP'; src: url('https://cdn.jsdelivr.net/gh/map1en/pht@1.0.0/jp/phtsansJP-Regular.woff2') format('woff2'); font-weight: 400; font-display: swap; }",
|
||||||
|
"@font-face { font-family: 'PHT Sans JP'; src: url('https://cdn.jsdelivr.net/gh/map1en/pht@1.0.0/jp/phtsansJP-Medium.woff2') format('woff2'); font-weight: 500; font-display: swap; }",
|
||||||
|
"@font-face { font-family: 'PHT Sans JP'; src: url('https://cdn.jsdelivr.net/gh/map1en/pht@1.0.0/jp/phtsansJP-Bold.woff2') format('woff2'); font-weight: 700; font-display: swap; }",
|
||||||
|
'/* Korean */',
|
||||||
|
"@font-face { font-family: 'PHT Sans KR'; src: url('https://cdn.jsdelivr.net/gh/map1en/pht@1.0.0/kr/phtsansKR-Regular.woff2') format('woff2'); font-weight: 400; font-display: swap; }",
|
||||||
|
"@font-face { font-family: 'PHT Sans KR'; src: url('https://cdn.jsdelivr.net/gh/map1en/pht@1.0.0/kr/phtsansKR-Medium.woff2') format('woff2'); font-weight: 500; font-display: swap; }",
|
||||||
|
"@font-face { font-family: 'PHT Sans KR'; src: url('https://cdn.jsdelivr.net/gh/map1en/pht@1.0.0/kr/phtsansKR-Bold.woff2') format('woff2'); font-weight: 700; font-display: swap; }"
|
||||||
|
].join('\n')
|
||||||
|
},
|
||||||
|
system: {
|
||||||
|
cssName: Object.freeze({
|
||||||
|
jp: 'system-ui',
|
||||||
|
kr: 'system-ui',
|
||||||
|
sc: 'system-ui',
|
||||||
|
tc: 'system-ui'
|
||||||
|
}),
|
||||||
|
link: null
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const APP_CJK_FONT_PACKS = Object.freeze(Object.keys(APP_CJK_FONT_PACK_CONFIG));
|
||||||
|
|
||||||
|
export {
|
||||||
|
APP_FONT_CONFIG,
|
||||||
|
APP_FONT_DEFAULT_KEY,
|
||||||
|
APP_FONT_FAMILIES,
|
||||||
|
APP_CJK_FONT_PACK_CONFIG,
|
||||||
|
APP_CJK_FONT_PACK_DEFAULT_KEY,
|
||||||
|
APP_CJK_FONT_PACKS
|
||||||
|
};
|
||||||
|
|||||||
+66
-17
@@ -2,6 +2,8 @@ import { ref } from 'vue';
|
|||||||
import { toast } from 'vue-sonner';
|
import { toast } from 'vue-sonner';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
APP_CJK_FONT_PACK_CONFIG,
|
||||||
|
APP_CJK_FONT_PACK_DEFAULT_KEY,
|
||||||
APP_FONT_CONFIG,
|
APP_FONT_CONFIG,
|
||||||
APP_FONT_DEFAULT_KEY,
|
APP_FONT_DEFAULT_KEY,
|
||||||
THEME_COLORS,
|
THEME_COLORS,
|
||||||
@@ -19,6 +21,7 @@ const THEME_MODE_STYLE_ID = 'app-theme-mode-style';
|
|||||||
const DEFAULT_THEME_COLOR_KEY = 'default';
|
const DEFAULT_THEME_COLOR_KEY = 'default';
|
||||||
|
|
||||||
const APP_FONT_LINK_ATTR = 'data-app-font';
|
const APP_FONT_LINK_ATTR = 'data-app-font';
|
||||||
|
const APP_CJK_FONT_PACK_LINK_ATTR = 'data-app-cjk-font-pack';
|
||||||
|
|
||||||
const themeColors = THEME_COLORS.map((theme) => ({
|
const themeColors = THEME_COLORS.map((theme) => ({
|
||||||
...theme,
|
...theme,
|
||||||
@@ -166,44 +169,89 @@ function resolveAppFontFamily(fontKey) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function ensureAppFontLinks(fontKey) {
|
function ensureDynamicFontStyle(attrName, styleKey, cssImport) {
|
||||||
const head = document.head;
|
const head = document.head;
|
||||||
if (!head) {
|
if (!head) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
document
|
document.querySelectorAll(`style[${attrName}]`).forEach((styleEl) => {
|
||||||
.querySelectorAll(`style[${APP_FONT_LINK_ATTR}]`)
|
if (styleEl.getAttribute(attrName) !== styleKey) {
|
||||||
.forEach((styleEl) => {
|
styleEl.remove();
|
||||||
if (styleEl.getAttribute(APP_FONT_LINK_ATTR) !== fontKey) {
|
}
|
||||||
styleEl.remove();
|
});
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const config = APP_FONT_CONFIG[fontKey];
|
if (!cssImport) {
|
||||||
if (!config?.cssImport) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const existing = document.querySelector(
|
const existing = document.querySelector(`style[${attrName}="${styleKey}"]`);
|
||||||
`style[${APP_FONT_LINK_ATTR}="${fontKey}"]`
|
|
||||||
);
|
|
||||||
if (existing) {
|
if (existing) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const styleEl = document.createElement('style');
|
const styleEl = document.createElement('style');
|
||||||
styleEl.setAttribute(APP_FONT_LINK_ATTR, fontKey);
|
styleEl.setAttribute(attrName, styleKey);
|
||||||
styleEl.textContent = config.cssImport;
|
styleEl.textContent = cssImport;
|
||||||
head.appendChild(styleEl);
|
head.appendChild(styleEl);
|
||||||
}
|
}
|
||||||
|
|
||||||
function applyAppFontFamily(fontKey) {
|
function resolveAppCjkFontPack(packKey) {
|
||||||
|
const normalized = String(packKey || '')
|
||||||
|
.trim()
|
||||||
|
.toLowerCase();
|
||||||
|
if (APP_CJK_FONT_PACK_CONFIG[normalized]) {
|
||||||
|
return { key: normalized, ...APP_CJK_FONT_PACK_CONFIG[normalized] };
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
key: APP_CJK_FONT_PACK_DEFAULT_KEY,
|
||||||
|
...APP_CJK_FONT_PACK_CONFIG[APP_CJK_FONT_PACK_DEFAULT_KEY]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function ensureAppCjkFontPackLinks(packKey) {
|
||||||
|
const config = APP_CJK_FONT_PACK_CONFIG[packKey];
|
||||||
|
ensureDynamicFontStyle(
|
||||||
|
APP_CJK_FONT_PACK_LINK_ATTR,
|
||||||
|
packKey,
|
||||||
|
config?.cssImport
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyAppFontFamily(fontKey, customCssName) {
|
||||||
|
if (fontKey === 'custom') {
|
||||||
|
const cssName = String(customCssName || '').trim() || 'system-ui';
|
||||||
|
const root = document.documentElement;
|
||||||
|
root.style.setProperty('--font-western-primary', cssName);
|
||||||
|
ensureDynamicFontStyle(APP_FONT_LINK_ATTR, 'custom', null);
|
||||||
|
return {
|
||||||
|
key: 'custom',
|
||||||
|
...APP_FONT_CONFIG.custom,
|
||||||
|
cssName
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
const resolved = resolveAppFontFamily(fontKey);
|
const resolved = resolveAppFontFamily(fontKey);
|
||||||
const root = document.documentElement;
|
const root = document.documentElement;
|
||||||
root.style.setProperty('--font-western-primary', resolved.cssName);
|
root.style.setProperty('--font-western-primary', resolved.cssName);
|
||||||
|
ensureDynamicFontStyle(
|
||||||
|
APP_FONT_LINK_ATTR,
|
||||||
|
resolved.key,
|
||||||
|
resolved.cssImport
|
||||||
|
);
|
||||||
|
|
||||||
ensureAppFontLinks(resolved.key);
|
return resolved;
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyAppCjkFontPack(packKey) {
|
||||||
|
const resolved = resolveAppCjkFontPack(packKey);
|
||||||
|
const root = document.documentElement;
|
||||||
|
root.style.setProperty('--font-cjk-jp-primary', resolved.cssName.jp);
|
||||||
|
root.style.setProperty('--font-cjk-sc-primary', resolved.cssName.sc);
|
||||||
|
root.style.setProperty('--font-cjk-kr-primary', resolved.cssName.kr);
|
||||||
|
root.style.setProperty('--font-cjk-tc-primary', resolved.cssName.tc);
|
||||||
|
|
||||||
|
ensureAppCjkFontPackLinks(resolved.key);
|
||||||
|
|
||||||
return resolved;
|
return resolved;
|
||||||
}
|
}
|
||||||
@@ -461,6 +509,7 @@ export {
|
|||||||
refreshCustomCss,
|
refreshCustomCss,
|
||||||
refreshCustomScript,
|
refreshCustomScript,
|
||||||
applyAppFontFamily,
|
applyAppFontFamily,
|
||||||
|
applyAppCjkFontPack,
|
||||||
HueToHex,
|
HueToHex,
|
||||||
HSVtoRGB,
|
HSVtoRGB,
|
||||||
formatJsonVars,
|
formatJsonVars,
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import { useI18n } from 'vue-i18n';
|
|||||||
import { useRouter } from 'vue-router';
|
import { useRouter } from 'vue-router';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
APP_CJK_FONT_PACK_DEFAULT_KEY,
|
||||||
|
APP_CJK_FONT_PACKS,
|
||||||
APP_FONT_DEFAULT_KEY,
|
APP_FONT_DEFAULT_KEY,
|
||||||
APP_FONT_FAMILIES,
|
APP_FONT_FAMILIES,
|
||||||
SEARCH_LIMIT_MAX,
|
SEARCH_LIMIT_MAX,
|
||||||
@@ -13,6 +15,7 @@ import {
|
|||||||
THEME_CONFIG
|
THEME_CONFIG
|
||||||
} from '../../shared/constants';
|
} from '../../shared/constants';
|
||||||
import {
|
import {
|
||||||
|
applyAppCjkFontPack,
|
||||||
HueToHex,
|
HueToHex,
|
||||||
applyAppFontFamily,
|
applyAppFontFamily,
|
||||||
changeAppThemeStyle,
|
changeAppThemeStyle,
|
||||||
@@ -55,6 +58,8 @@ export const useAppearanceSettingsStore = defineStore(
|
|||||||
const isDarkMode = ref(false);
|
const isDarkMode = ref(false);
|
||||||
const lastDarkTheme = ref('dark');
|
const lastDarkTheme = ref('dark');
|
||||||
const appFontFamily = ref('inter');
|
const appFontFamily = ref('inter');
|
||||||
|
const customFontFamily = ref('');
|
||||||
|
const appCjkFontPack = ref(APP_CJK_FONT_PACK_DEFAULT_KEY);
|
||||||
const displayVRCPlusIconsAsAvatar = ref(false);
|
const displayVRCPlusIconsAsAvatar = ref(false);
|
||||||
const hideNicknames = ref(false);
|
const hideNicknames = ref(false);
|
||||||
const showInstanceIdInLocation = ref(false);
|
const showInstanceIdInLocation = ref(false);
|
||||||
@@ -168,6 +173,8 @@ export const useAppearanceSettingsStore = defineStore(
|
|||||||
dataTableStripedConfig,
|
dataTableStripedConfig,
|
||||||
showPointerOnHoverConfig,
|
showPointerOnHoverConfig,
|
||||||
appFontFamilyConfig,
|
appFontFamilyConfig,
|
||||||
|
customFontFamilyConfig,
|
||||||
|
appCjkFontPackConfig,
|
||||||
lastDarkThemeConfig
|
lastDarkThemeConfig
|
||||||
] = await Promise.all([
|
] = await Promise.all([
|
||||||
configRepository.getString('VRCX_appLanguage'),
|
configRepository.getString('VRCX_appLanguage'),
|
||||||
@@ -234,6 +241,11 @@ export const useAppearanceSettingsStore = defineStore(
|
|||||||
'VRCX_fontFamily',
|
'VRCX_fontFamily',
|
||||||
APP_FONT_DEFAULT_KEY
|
APP_FONT_DEFAULT_KEY
|
||||||
),
|
),
|
||||||
|
configRepository.getString('VRCX_customFontFamily', ''),
|
||||||
|
configRepository.getString(
|
||||||
|
'VRCX_cjkFontPack',
|
||||||
|
APP_CJK_FONT_PACK_DEFAULT_KEY
|
||||||
|
),
|
||||||
configRepository.getString(
|
configRepository.getString(
|
||||||
'VRCX_lastDarkTheme',
|
'VRCX_lastDarkTheme',
|
||||||
fallbackDarkTheme
|
fallbackDarkTheme
|
||||||
@@ -262,7 +274,11 @@ export const useAppearanceSettingsStore = defineStore(
|
|||||||
fallbackDarkTheme
|
fallbackDarkTheme
|
||||||
);
|
);
|
||||||
appFontFamily.value = normalizeAppFontFamily(appFontFamilyConfig);
|
appFontFamily.value = normalizeAppFontFamily(appFontFamilyConfig);
|
||||||
applyAppFontFamily(appFontFamily.value);
|
customFontFamily.value = customFontFamilyConfig || '';
|
||||||
|
appCjkFontPack.value =
|
||||||
|
normalizeAppCjkFontPack(appCjkFontPackConfig);
|
||||||
|
applyAppFontFamily(appFontFamily.value, customFontFamily.value);
|
||||||
|
applyAppCjkFontPack(appCjkFontPack.value);
|
||||||
|
|
||||||
displayVRCPlusIconsAsAvatar.value =
|
displayVRCPlusIconsAsAvatar.value =
|
||||||
displayVRCPlusIconsAsAvatarConfig;
|
displayVRCPlusIconsAsAvatarConfig;
|
||||||
@@ -508,11 +524,22 @@ export const useAppearanceSettingsStore = defineStore(
|
|||||||
* @param value
|
* @param value
|
||||||
*/
|
*/
|
||||||
function normalizeAppFontFamily(value) {
|
function normalizeAppFontFamily(value) {
|
||||||
|
if (value === 'custom') return 'custom';
|
||||||
return APP_FONT_FAMILIES.includes(value)
|
return APP_FONT_FAMILIES.includes(value)
|
||||||
? value
|
? value
|
||||||
: APP_FONT_DEFAULT_KEY;
|
: APP_FONT_DEFAULT_KEY;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param value
|
||||||
|
*/
|
||||||
|
function normalizeAppCjkFontPack(value) {
|
||||||
|
return APP_CJK_FONT_PACKS.includes(value)
|
||||||
|
? value
|
||||||
|
: APP_CJK_FONT_PACK_DEFAULT_KEY;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param value
|
* @param value
|
||||||
@@ -521,7 +548,26 @@ export const useAppearanceSettingsStore = defineStore(
|
|||||||
const normalized = normalizeAppFontFamily(value);
|
const normalized = normalizeAppFontFamily(value);
|
||||||
appFontFamily.value = normalized;
|
appFontFamily.value = normalized;
|
||||||
configRepository.setString('VRCX_fontFamily', normalized);
|
configRepository.setString('VRCX_fontFamily', normalized);
|
||||||
applyAppFontFamily(normalized);
|
applyAppFontFamily(normalized, customFontFamily.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
function setCustomFontFamily(value) {
|
||||||
|
customFontFamily.value = value;
|
||||||
|
configRepository.setString('VRCX_customFontFamily', value);
|
||||||
|
if (appFontFamily.value === 'custom') {
|
||||||
|
applyAppFontFamily('custom', value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param value
|
||||||
|
*/
|
||||||
|
function setAppCjkFontPack(value) {
|
||||||
|
const normalized = normalizeAppCjkFontPack(value);
|
||||||
|
appCjkFontPack.value = normalized;
|
||||||
|
configRepository.setString('VRCX_cjkFontPack', normalized);
|
||||||
|
applyAppCjkFontPack(normalized);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1062,6 +1108,7 @@ export const useAppearanceSettingsStore = defineStore(
|
|||||||
themeMode,
|
themeMode,
|
||||||
isDarkMode,
|
isDarkMode,
|
||||||
appFontFamily,
|
appFontFamily,
|
||||||
|
appCjkFontPack,
|
||||||
displayVRCPlusIconsAsAvatar,
|
displayVRCPlusIconsAsAvatar,
|
||||||
hideNicknames,
|
hideNicknames,
|
||||||
showInstanceIdInLocation,
|
showInstanceIdInLocation,
|
||||||
@@ -1142,6 +1189,9 @@ export const useAppearanceSettingsStore = defineStore(
|
|||||||
setNavCollapsed,
|
setNavCollapsed,
|
||||||
toggleNavCollapsed,
|
toggleNavCollapsed,
|
||||||
setAppFontFamily,
|
setAppFontFamily,
|
||||||
|
customFontFamily,
|
||||||
|
setCustomFontFamily,
|
||||||
|
setAppCjkFontPack,
|
||||||
setThemeMode,
|
setThemeMode,
|
||||||
toggleThemeMode
|
toggleThemeMode
|
||||||
};
|
};
|
||||||
|
|||||||
+16
-11
@@ -12,37 +12,42 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
|
/* Keep these bootstrap defaults aligned with src/shared/constants/fonts.js */
|
||||||
--font-western-primary: 'Inter Variable';
|
--font-western-primary: 'Inter Variable';
|
||||||
|
--font-cjk-jp-primary: 'Noto Sans JP Variable';
|
||||||
|
--font-cjk-sc-primary: 'Noto Sans SC Variable';
|
||||||
|
--font-cjk-kr-primary: 'Noto Sans KR Variable';
|
||||||
|
--font-cjk-tc-primary: 'Noto Sans TC Variable';
|
||||||
--font-western:
|
--font-western:
|
||||||
'ellipsis-font', -apple-system, var(--font-western-primary), 'Segoe UI',
|
'ellipsis-font', -apple-system, var(--font-western-primary), 'Segoe UI',
|
||||||
'Roboto', 'Ubuntu', 'Cantarell', 'DejaVu Sans', sans-serif;
|
'Roboto', 'Ubuntu', 'Cantarell', 'DejaVu Sans';
|
||||||
--font-symbol: 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol';
|
--font-symbol: 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol';
|
||||||
--font-fallback-cjk: sans-serif;
|
--font-fallback-cjk: sans-serif;
|
||||||
--font-primary-cjk:
|
--font-primary-cjk:
|
||||||
'Noto Sans JP Variable', 'Noto Sans SC Variable',
|
var(--font-cjk-jp-primary), var(--font-cjk-sc-primary),
|
||||||
'Noto Sans KR Variable', 'Noto Sans TC Variable';
|
var(--font-cjk-kr-primary), var(--font-cjk-tc-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
:root[lang='zh-CN'] {
|
:root[lang='zh-CN'] {
|
||||||
--font-primary-cjk:
|
--font-primary-cjk:
|
||||||
'Noto Sans SC Variable', 'Noto Sans JP Variable',
|
var(--font-cjk-sc-primary), var(--font-cjk-jp-primary),
|
||||||
'Noto Sans KR Variable', 'Noto Sans TC Variable';
|
var(--font-cjk-kr-primary), var(--font-cjk-tc-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
:root[lang='ja'] {
|
:root[lang='ja'] {
|
||||||
--font-primary-cjk:
|
--font-primary-cjk:
|
||||||
'Noto Sans JP Variable', 'Noto Sans KR Variable',
|
var(--font-cjk-jp-primary), var(--font-cjk-kr-primary),
|
||||||
'Noto Sans TC Variable', 'Noto Sans SC Variable';
|
var(--font-cjk-tc-primary), var(--font-cjk-sc-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
:root[lang='ko'] {
|
:root[lang='ko'] {
|
||||||
--font-primary-cjk:
|
--font-primary-cjk:
|
||||||
'Noto Sans KR Variable', 'Noto Sans JP Variable',
|
var(--font-cjk-kr-primary), var(--font-cjk-jp-primary),
|
||||||
'Noto Sans TC Variable', 'Noto Sans SC Variable';
|
var(--font-cjk-tc-primary), var(--font-cjk-sc-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
:root[lang='zh-TW'] {
|
:root[lang='zh-TW'] {
|
||||||
--font-primary-cjk:
|
--font-primary-cjk:
|
||||||
'Noto Sans TC Variable', 'Noto Sans JP Variable',
|
var(--font-cjk-tc-primary), var(--font-cjk-jp-primary),
|
||||||
'Noto Sans KR Variable', 'Noto Sans SC Variable';
|
var(--font-cjk-kr-primary), var(--font-cjk-sc-primary);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,32 +19,61 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="options-container-item">
|
<div class="options-container-item">
|
||||||
<span class="name flex! items-center!">
|
<span class="name">
|
||||||
{{ t('view.settings.appearance.appearance.font_family') }}
|
{{ t('view.settings.appearance.appearance.font_family') }}
|
||||||
|
|
||||||
<TooltipWrapper
|
|
||||||
class="ml-1.5"
|
|
||||||
side="top"
|
|
||||||
:content="t('view.settings.appearance.appearance.font_family_tooltip')">
|
|
||||||
<Info />
|
|
||||||
</TooltipWrapper>
|
|
||||||
</span>
|
</span>
|
||||||
<Select :model-value="appFontFamily" @update:modelValue="setAppFontFamily">
|
<DropdownMenu>
|
||||||
<SelectTrigger size="sm">
|
<DropdownMenuTrigger as-child>
|
||||||
<SelectValue
|
<Button variant="outline" size="sm" class="min-w-[180px] justify-between font-normal">
|
||||||
:placeholder="t(`view.settings.appearance.appearance.font_family_${appFontFamily}`)" />
|
<span class="truncate">{{ fontDropdownDisplayText }}</span>
|
||||||
</SelectTrigger>
|
<ChevronDown class="ml-2 size-4 shrink-0 opacity-50" />
|
||||||
<SelectContent>
|
</Button>
|
||||||
<SelectGroup>
|
</DropdownMenuTrigger>
|
||||||
<template v-for="option in appFontFamilyOptions" :key="option.key">
|
<DropdownMenuContent align="end">
|
||||||
<SelectSeparator v-if="option.type === 'separator'" />
|
<DropdownMenuCheckboxItem
|
||||||
<SelectItem v-else :value="option.key">
|
v-for="option in westernFontItems"
|
||||||
{{ t(`view.settings.appearance.appearance.font_family_${option.key}`) }}
|
:key="option.key"
|
||||||
</SelectItem>
|
:model-value="appFontFamily === option.key"
|
||||||
</template>
|
@select="handleSelectWesternFont(option.key)">
|
||||||
</SelectGroup>
|
{{ option.label }}
|
||||||
</SelectContent>
|
</DropdownMenuCheckboxItem>
|
||||||
</Select>
|
<DropdownMenuSeparator />
|
||||||
|
<DropdownMenuCheckboxItem
|
||||||
|
v-for="option in cjkFontItems"
|
||||||
|
:key="option.key"
|
||||||
|
:model-value="appCjkFontPack === option.key && appFontFamily !== 'custom'"
|
||||||
|
@select="handleSelectCjkFont(option.key)">
|
||||||
|
{{ option.label }}
|
||||||
|
</DropdownMenuCheckboxItem>
|
||||||
|
<DropdownMenuSeparator />
|
||||||
|
<DropdownMenuCheckboxItem
|
||||||
|
:model-value="appFontFamily === 'custom'"
|
||||||
|
@select="handleSelectCustomFont">
|
||||||
|
{{ t('view.settings.appearance.appearance.font_family_custom') }}
|
||||||
|
</DropdownMenuCheckboxItem>
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
<Dialog v-model:open="customFontDialogOpen">
|
||||||
|
<DialogContent class="sm:max-w-md">
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>{{
|
||||||
|
t('view.settings.appearance.appearance.font_family_custom_dialog_title')
|
||||||
|
}}</DialogTitle>
|
||||||
|
<DialogDescription>{{
|
||||||
|
t('view.settings.appearance.appearance.font_family_custom_dialog_description')
|
||||||
|
}}</DialogDescription>
|
||||||
|
</DialogHeader>
|
||||||
|
<Input v-model="customFontInput" placeholder="'My Font', Arial, sans-serif" />
|
||||||
|
<DialogFooter>
|
||||||
|
<Button variant="outline" @click="customFontDialogOpen = false">
|
||||||
|
{{ t('dialog.alertdialog.cancel') }}
|
||||||
|
</Button>
|
||||||
|
<Button @click="saveCustomFont">
|
||||||
|
{{ t('dialog.alertdialog.ok') }}
|
||||||
|
</Button>
|
||||||
|
</DialogFooter>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="!isLinux" class="options-container-item">
|
<div v-if="!isLinux" class="options-container-item">
|
||||||
<span class="name">{{ t('view.settings.appearance.appearance.zoom') }}</span>
|
<span class="name">{{ t('view.settings.appearance.appearance.zoom') }}</span>
|
||||||
@@ -332,6 +361,13 @@
|
|||||||
SelectTrigger,
|
SelectTrigger,
|
||||||
SelectValue
|
SelectValue
|
||||||
} from '@/components/ui/select';
|
} from '@/components/ui/select';
|
||||||
|
import {
|
||||||
|
DropdownMenu,
|
||||||
|
DropdownMenuCheckboxItem,
|
||||||
|
DropdownMenuContent,
|
||||||
|
DropdownMenuSeparator,
|
||||||
|
DropdownMenuTrigger
|
||||||
|
} from '@/components/ui/dropdown-menu';
|
||||||
import { ListboxContent, ListboxFilter, ListboxItem, ListboxItemIndicator, ListboxRoot, useFilter } from 'reka-ui';
|
import { ListboxContent, ListboxFilter, ListboxItem, ListboxItemIndicator, ListboxRoot, useFilter } from 'reka-ui';
|
||||||
import {
|
import {
|
||||||
NumberField,
|
NumberField,
|
||||||
@@ -347,13 +383,22 @@
|
|||||||
TagsInputItemDelete,
|
TagsInputItemDelete,
|
||||||
TagsInputItemText
|
TagsInputItemText
|
||||||
} from '@/components/ui/tags-input';
|
} from '@/components/ui/tags-input';
|
||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogContent,
|
||||||
|
DialogDescription,
|
||||||
|
DialogFooter,
|
||||||
|
DialogHeader,
|
||||||
|
DialogTitle
|
||||||
|
} from '@/components/ui/dialog';
|
||||||
|
import { Input } from '@/components/ui/input';
|
||||||
import { Popover, PopoverAnchor, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
|
import { Popover, PopoverAnchor, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
|
||||||
import { computed, onBeforeUnmount, ref, watch } from 'vue';
|
import { computed, onBeforeUnmount, ref, watch } from 'vue';
|
||||||
import { CheckIcon, ChevronDown, Info } from 'lucide-vue-next';
|
import { CheckIcon, ChevronDown } from 'lucide-vue-next';
|
||||||
import { useAppearanceSettingsStore, useFavoriteStore, useVrStore } from '@/stores';
|
import { useAppearanceSettingsStore, useFavoriteStore, useVrStore } from '@/stores';
|
||||||
import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group';
|
import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group';
|
||||||
import { getLanguageName, languageCodes } from '@/localization';
|
import { getLanguageName, languageCodes } from '@/localization';
|
||||||
import { APP_FONT_FAMILIES } from '@/shared/constants';
|
import { APP_CJK_FONT_PACKS, APP_FONT_CONFIG, APP_FONT_DEFAULT_KEY, APP_FONT_FAMILIES } from '@/shared/constants';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { storeToRefs } from 'pinia';
|
import { storeToRefs } from 'pinia';
|
||||||
import { toast } from 'vue-sonner';
|
import { toast } from 'vue-sonner';
|
||||||
@@ -374,6 +419,8 @@
|
|||||||
appLanguage,
|
appLanguage,
|
||||||
displayVRCPlusIconsAsAvatar,
|
displayVRCPlusIconsAsAvatar,
|
||||||
appFontFamily,
|
appFontFamily,
|
||||||
|
customFontFamily,
|
||||||
|
appCjkFontPack,
|
||||||
hideNicknames,
|
hideNicknames,
|
||||||
showInstanceIdInLocation,
|
showInstanceIdInLocation,
|
||||||
isAgeGatedInstancesVisible,
|
isAgeGatedInstancesVisible,
|
||||||
@@ -412,18 +459,80 @@
|
|||||||
setTablePageSizes,
|
setTablePageSizes,
|
||||||
toggleStripedDataTable,
|
toggleStripedDataTable,
|
||||||
togglePointerOnHover,
|
togglePointerOnHover,
|
||||||
setAppFontFamily
|
setAppFontFamily,
|
||||||
|
setCustomFontFamily,
|
||||||
|
setAppCjkFontPack
|
||||||
} = appearanceSettingsStore;
|
} = appearanceSettingsStore;
|
||||||
|
|
||||||
const appFontFamilyOptions = computed(() => {
|
const fontDropdownDisplayText = computed(() => {
|
||||||
const fontKeys = APP_FONT_FAMILIES.filter((key) => key !== 'system_ui');
|
if (appFontFamily.value === 'custom') {
|
||||||
return [
|
return t('view.settings.appearance.appearance.font_family_custom');
|
||||||
...fontKeys.map((key) => ({ type: 'item', key })),
|
}
|
||||||
{ type: 'separator', key: 'separator-system-ui' },
|
const western = t(`view.settings.appearance.appearance.font_family_${appFontFamily.value}`);
|
||||||
{ type: 'item', key: 'system_ui' }
|
const cjk =
|
||||||
];
|
appCjkFontPack.value === 'system'
|
||||||
|
? t('view.settings.appearance.appearance.font_family_system_ui')
|
||||||
|
: t(`view.settings.appearance.appearance.cjk_font_pack_${appCjkFontPack.value}`);
|
||||||
|
return `${western} / ${cjk}`;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const westernFontItems = computed(() => {
|
||||||
|
return APP_FONT_FAMILIES.filter((key) => key !== 'custom' && key !== 'system_ui').map((key) => ({
|
||||||
|
key,
|
||||||
|
label: t(`view.settings.appearance.appearance.font_family_${key}`)
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
const cjkFontItems = computed(() => {
|
||||||
|
return APP_CJK_FONT_PACKS.map((key) => ({
|
||||||
|
key,
|
||||||
|
label:
|
||||||
|
key === 'system'
|
||||||
|
? t('view.settings.appearance.appearance.font_family_system_ui')
|
||||||
|
: t(`view.settings.appearance.appearance.cjk_font_pack_${key}`)
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
const FONT_FAMILY_REGEX =
|
||||||
|
/^\s*(([-_\p{L}][\p{L}\p{N}_\s-]*)|'[^']+'|"[^"]+")\s*(,\s*(([-_\p{L}][\p{L}\p{N}_\s-]*)|'[^']+'|"[^"]+")\s*)*$/u;
|
||||||
|
|
||||||
|
const customFontDialogOpen = ref(false);
|
||||||
|
const customFontInput = ref('');
|
||||||
|
|
||||||
|
function handleSelectWesternFont(key) {
|
||||||
|
setAppFontFamily(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleSelectCjkFont(key) {
|
||||||
|
if (appFontFamily.value === 'custom') {
|
||||||
|
setAppFontFamily(APP_FONT_DEFAULT_KEY);
|
||||||
|
}
|
||||||
|
setAppCjkFontPack(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleSelectCustomFont() {
|
||||||
|
const cssVarValue = getComputedStyle(document.documentElement)
|
||||||
|
.getPropertyValue('--font-western-primary')
|
||||||
|
.trim();
|
||||||
|
const currentKey = String(appFontFamily.value || APP_FONT_DEFAULT_KEY)
|
||||||
|
.trim()
|
||||||
|
.toLowerCase();
|
||||||
|
const fallbackFont = APP_FONT_CONFIG[currentKey]?.cssName || APP_FONT_CONFIG[APP_FONT_DEFAULT_KEY].cssName;
|
||||||
|
customFontInput.value = customFontFamily.value?.trim() || cssVarValue || fallbackFont;
|
||||||
|
customFontDialogOpen.value = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveCustomFont() {
|
||||||
|
const trimmed = customFontInput.value.trim();
|
||||||
|
if (!trimmed || !FONT_FAMILY_REGEX.test(trimmed)) {
|
||||||
|
toast.error(t('view.settings.appearance.appearance.font_family_custom_invalid'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setCustomFontFamily(trimmed);
|
||||||
|
setAppFontFamily('custom');
|
||||||
|
customFontDialogOpen.value = false;
|
||||||
|
}
|
||||||
|
|
||||||
const zoomLevel = ref(100);
|
const zoomLevel = ref(100);
|
||||||
const isLinux = computed(() => LINUX);
|
const isLinux = computed(() => LINUX);
|
||||||
let cleanupWheel = null;
|
let cleanupWheel = null;
|
||||||
|
|||||||
+3
-32
@@ -22,16 +22,8 @@ body {
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Font variables are shared from ../styles/fonts.css */
|
||||||
:root {
|
:root {
|
||||||
--font-western:
|
|
||||||
'ellipsis-font', -apple-system, 'Inter Variable', 'Segoe UI', 'Roboto',
|
|
||||||
'Ubuntu', 'Cantarell', 'DejaVu Sans', sans-serif;
|
|
||||||
--font-symbol: 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol';
|
|
||||||
--font-fallback-cjk: sans-serif;
|
|
||||||
--font-primary-cjk:
|
|
||||||
'Noto Sans JP Variable', 'Noto Sans SC Variable',
|
|
||||||
'Noto Sans KR Variable', 'Noto Sans TC Variable';
|
|
||||||
|
|
||||||
/* VRChat Status Colors (duplicated from globals.css for VR panel independence) */
|
/* VRChat Status Colors (duplicated from globals.css for VR panel independence) */
|
||||||
--status-online: #67c23a;
|
--status-online: #67c23a;
|
||||||
--status-joinme: #00b8ff;
|
--status-joinme: #00b8ff;
|
||||||
@@ -46,26 +38,6 @@ body {
|
|||||||
--platform-quest: #3ddc84;
|
--platform-quest: #3ddc84;
|
||||||
--platform-ios: #8e8e93;
|
--platform-ios: #8e8e93;
|
||||||
}
|
}
|
||||||
:root[lang='zh-CN'] {
|
|
||||||
--font-primary-cjk:
|
|
||||||
'Noto Sans SC Variable', 'Noto Sans JP Variable',
|
|
||||||
'Noto Sans KR Variable', 'Noto Sans TC Variable';
|
|
||||||
}
|
|
||||||
:root[lang='ja'] {
|
|
||||||
--font-primary-cjk:
|
|
||||||
'Noto Sans JP Variable', 'Noto Sans KR Variable',
|
|
||||||
'Noto Sans TC Variable', 'Noto Sans SC Variable';
|
|
||||||
}
|
|
||||||
:root[lang='ko'] {
|
|
||||||
--font-primary-cjk:
|
|
||||||
'Noto Sans KR Variable', 'Noto Sans JP Variable',
|
|
||||||
'Noto Sans TC Variable', 'Noto Sans SC Variable';
|
|
||||||
}
|
|
||||||
:root[lang='zh-TW'] {
|
|
||||||
--font-primary-cjk:
|
|
||||||
'Noto Sans TC Variable', 'Noto Sans JP Variable',
|
|
||||||
'Noto Sans KR Variable', 'Noto Sans SC Variable';
|
|
||||||
}
|
|
||||||
body {
|
body {
|
||||||
font-family:
|
font-family:
|
||||||
var(--font-western), var(--font-symbol), var(--font-primary-cjk),
|
var(--font-western), var(--font-symbol), var(--font-primary-cjk),
|
||||||
@@ -230,9 +202,8 @@ textarea,
|
|||||||
select,
|
select,
|
||||||
button {
|
button {
|
||||||
font-family:
|
font-family:
|
||||||
'ellipsis-font', 'Noto Sans JP Variable', 'Noto Sans KR Variable',
|
var(--font-western), var(--font-symbol), var(--font-primary-cjk),
|
||||||
'Noto Sans TC Variable', 'Noto Sans SC Variable', 'Meiryo UI',
|
var(--font-fallback-cjk);
|
||||||
'Malgun Gothic', 'Segoe UI', sans-serif;
|
|
||||||
line-height: normal;
|
line-height: normal;
|
||||||
text-shadow:
|
text-shadow:
|
||||||
#000 0px 0px 3px,
|
#000 0px 0px 3px,
|
||||||
|
|||||||
Reference in New Issue
Block a user