-
+ {{
userDialog.id !== currentUser.id &&
userDialog.ref.profilePicOverride &&
userDialog.ref.currentAvatarImageUrl
- "
- class="name">
- {{ t('dialog.user.info.avatar_info_last_seen') }}
-
- {{ t('dialog.user.info.avatar_info') }}
-
@@ -1385,6 +1368,7 @@
import { userDialogWorldOrderOptions, userDialogWorldSortingOptions } from '../../../shared/constants/';
import { database } from '../../../service/database';
+ import InstanceActionBar from '../../InstanceActionBar.vue';
import SendInviteDialog from '../InviteDialog/SendInviteDialog.vue';
import UserSummaryHeader from './UserSummaryHeader.vue';
diff --git a/src/components/dialogs/UserDialog/UserSummaryHeader.vue b/src/components/dialogs/UserDialog/UserSummaryHeader.vue
index 1c30622a..308fdd4f 100644
--- a/src/components/dialogs/UserDialog/UserSummaryHeader.vue
+++ b/src/components/dialogs/UserDialog/UserSummaryHeader.vue
@@ -111,7 +111,10 @@
v-if="userDialog.mutualFriendCount"
side="top"
:content="t('dialog.user.tags.mutual_friends')">
-
+
{{ userDialog.mutualFriendCount }}
diff --git a/src/components/dialogs/WorldDialog/WorldDialog.vue b/src/components/dialogs/WorldDialog/WorldDialog.vue
index defa3d5b..4e3435b8 100644
--- a/src/components/dialogs/WorldDialog/WorldDialog.vue
+++ b/src/components/dialogs/WorldDialog/WorldDialog.vue
@@ -184,12 +184,17 @@
-
+
-
-
-
-
-
-
-
-
-
-
+ :friendcount="room.friendCount"
+ :refresh-tooltip="t('dialog.world.instances.refresh_instance_info')"
+ :show-history="!!instanceJoinHistory.get(room.$location.tag)"
+ :history-tooltip="t('dialog.previous_instances.info')"
+ :on-refresh="() => refreshInstancePlayerCount(room.tag)"
+ :on-history="() => showPreviousInstancesInfoDialog(room.location)" />
import('../NewInstanceDialog.vue'));
diff --git a/src/components/ui/button/index.js b/src/components/ui/button/index.js
index f1411dcc..16a1b76f 100644
--- a/src/components/ui/button/index.js
+++ b/src/components/ui/button/index.js
@@ -3,7 +3,7 @@ import { cva } from 'class-variance-authority';
export { default as Button } from './Button.vue';
export const buttonVariants = cva(
- "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all cursor-pointer disabled:pointer-events-none disabled:bg-muted disabled:text-muted-foreground disabled:shadow-none disabled:opacity-100 aria-disabled:pointer-events-none aria-disabled:bg-muted aria-disabled:text-muted-foreground aria-disabled:shadow-none aria-disabled:opacity-100 data-[disabled]:pointer-events-none data-[disabled]:bg-muted data-[disabled]:text-muted-foreground data-[disabled]:shadow-none data-[disabled]:opacity-100 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
+ "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:bg-muted disabled:text-muted-foreground disabled:shadow-none disabled:opacity-100 aria-disabled:pointer-events-none aria-disabled:bg-muted aria-disabled:text-muted-foreground aria-disabled:shadow-none aria-disabled:opacity-100 data-[disabled]:pointer-events-none data-[disabled]:bg-muted data-[disabled]:text-muted-foreground data-[disabled]:shadow-none data-[disabled]:opacity-100 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
{
variants: {
variant: {
diff --git a/src/components/ui/tooltip/TooltipWrapper.vue b/src/components/ui/tooltip/TooltipWrapper.vue
index eaa17b4a..b0fcce28 100644
--- a/src/components/ui/tooltip/TooltipWrapper.vue
+++ b/src/components/ui/tooltip/TooltipWrapper.vue
@@ -16,6 +16,7 @@
sideOffset: { type: Number, required: false },
delayDuration: { type: Number, required: false },
disableHoverableContent: { type: Boolean, required: false },
+ ignoreNonKeyboardFocus: { type: Boolean, required: false },
disabled: { type: Boolean, required: false },
triggerAsChild: { type: Boolean, required: false, default: true },
contentClass: { type: null, required: false }
@@ -27,7 +28,11 @@
-
+
diff --git a/src/lib/table/useVrcxVueTable.js b/src/lib/table/useVrcxVueTable.js
index 59048c50..46ca4b2c 100644
--- a/src/lib/table/useVrcxVueTable.js
+++ b/src/lib/table/useVrcxVueTable.js
@@ -89,7 +89,6 @@ function withSpacerColumn(columns, enabled, spacerId, stretchAfterId) {
header: () => null,
cell: () => null,
enableSorting: false,
- enableResizing: false,
size: 0,
minSize: 0,
meta: { thClass: 'p-0', tdClass: 'p-0' }
diff --git a/src/localization/en.json b/src/localization/en.json
index 8152b31a..6b0bf50b 100644
--- a/src/localization/en.json
+++ b/src/localization/en.json
@@ -559,6 +559,9 @@
"font_family_source_sans_3": "Source Sans 3",
"font_family_ibm_plex_sans": "IBM Plex Sans",
"font_family_harmonyos_sans": "HarmonyOS Sans",
+ "font_family_jetbrains_mono": "JetBrains Mono",
+ "font_family_roboto": "Roboto",
+ "font_family_fantasque_sans_mono": "Fantasque Sans Mono",
"font_family_system_ui": "System Font",
"theme_mode_system": "System",
"theme_mode_light": "Light",
diff --git a/src/plugin/components.js b/src/plugin/components.js
index 45f3e79b..86273551 100644
--- a/src/plugin/components.js
+++ b/src/plugin/components.js
@@ -3,10 +3,6 @@ import { TooltipWrapper } from '../components/ui/tooltip';
import AvatarInfo from '../components/AvatarInfo.vue';
import CountdownTimer from '../components/CountdownTimer.vue';
import DisplayName from '../components/DisplayName.vue';
-import InstanceInfo from '../components/InstanceInfo.vue';
-import InviteYourself from '../components/InviteYourself.vue';
-import LastJoin from '../components/LastJoin.vue';
-import Launch from '../components/Launch.vue';
import Location from '../components/Location.vue';
import LocationWorld from '../components/LocationWorld.vue';
import Timer from '../components/Timer.vue';
@@ -14,13 +10,9 @@ import Timer from '../components/Timer.vue';
export function initComponents(app) {
app.component('Location', Location);
app.component('Timer', Timer);
- app.component('InstanceInfo', InstanceInfo);
- app.component('LastJoin', LastJoin);
app.component('CountdownTimer', CountdownTimer);
app.component('AvatarInfo', AvatarInfo);
app.component('DisplayName', DisplayName);
- app.component('InviteYourself', InviteYourself);
- app.component('Launch', Launch);
app.component('LocationWorld', LocationWorld);
app.component('TooltipWrapper', TooltipWrapper);
}
diff --git a/src/shared/constants/fonts.js b/src/shared/constants/fonts.js
index 410acd5a..03d8f729 100644
--- a/src/shared/constants/fonts.js
+++ b/src/shared/constants/fonts.js
@@ -25,6 +25,21 @@ const APP_FONT_CONFIG = Object.freeze({
cssImport:
"@import url('https://fonts.cdnfonts.com/css/harmonyos-sans');"
},
+ jetbrains_mono: {
+ cssName: "'JetBrains Mono'",
+ cssImport:
+ "@import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:ital,wght@0,100..800;1,100..800&display=swap');"
+ },
+ roboto: {
+ cssName: "'Roboto'",
+ 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');"
+ },
+ fantasque_sans_mono: {
+ cssName: "'Fantasque Sans Mono'",
+ cssImport:
+ "@import url('https://fonts.cdnfonts.com/css/fantasque-sans-mono');"
+ },
system_ui: {
cssName: 'system-ui',
link: null
diff --git a/src/shared/constants/themes.js b/src/shared/constants/themes.js
index 962ee7f2..4564fcfa 100644
--- a/src/shared/constants/themes.js
+++ b/src/shared/constants/themes.js
@@ -10,11 +10,12 @@ export const THEME_CONFIG = {
dark: {
isDark: true,
name: 'Dark'
+ },
+ midnight: {
+ isDark: true,
+ name: 'Midnight',
+ file: 'midnight.css'
}
- // midnight: {
- // isDark: true,
- // name: 'Midnight'
- // }
};
export const THEME_COLORS = [
diff --git a/src/shared/utils/base/ui.js b/src/shared/utils/base/ui.js
index 06bc3aa8..0c653754 100644
--- a/src/shared/utils/base/ui.js
+++ b/src/shared/utils/base/ui.js
@@ -17,6 +17,7 @@ import configRepository from '../../../service/config.js';
const THEME_COLOR_STORAGE_KEY = 'VRCX_themeColor';
const THEME_COLOR_STYLE_ID = 'app-theme-color-style';
+const THEME_MODE_STYLE_ID = 'app-theme-mode-style';
const DEFAULT_THEME_COLOR_KEY = 'default';
const APP_FONT_LINK_ATTR = 'data-app-font';
@@ -139,6 +140,33 @@ function applyThemeFonts(themeKey, fontLinks = []) {
});
}
+function applyThemeModeStyle(themeMode) {
+ const themeConfig = THEME_CONFIG[themeMode];
+ const themeFile = themeConfig?.file;
+ let styleEl = document.getElementById(THEME_MODE_STYLE_ID);
+
+ if (!themeFile) {
+ styleEl?.remove();
+ return;
+ }
+
+ const themeHref = new URL(
+ `../../../styles/themes/${themeFile}`,
+ import.meta.url
+ ).href;
+
+ if (!styleEl) {
+ styleEl = document.createElement('link');
+ styleEl.id = THEME_MODE_STYLE_ID;
+ styleEl.rel = 'stylesheet';
+ document.head.appendChild(styleEl);
+ }
+
+ if (styleEl.getAttribute('href') !== themeHref) {
+ styleEl.setAttribute('href', themeHref);
+ }
+}
+
function resolveAppFontFamily(fontKey) {
const normalized = String(fontKey || '')
.trim()
@@ -209,6 +237,7 @@ function changeAppThemeStyle(themeMode) {
}
applyThemeFonts(themeMode, themeConfig.fontLinks);
+ applyThemeModeStyle(themeMode);
document.documentElement.setAttribute('data-theme', themeMode);
diff --git a/src/stores/settings/appearance.js b/src/stores/settings/appearance.js
index df88258d..5c08b8f9 100644
--- a/src/stores/settings/appearance.js
+++ b/src/stores/settings/appearance.js
@@ -13,7 +13,8 @@ import {
} from '../../shared/utils/base/ui';
import {
APP_FONT_DEFAULT_KEY,
- APP_FONT_FAMILIES
+ APP_FONT_FAMILIES,
+ THEME_CONFIG
} from '../../shared/constants';
import { database } from '../../service/database';
import { getNameColour } from '../../shared/utils';
@@ -51,6 +52,7 @@ export const useAppearanceSettingsStore = defineStore(
const appLanguage = ref('en');
const themeMode = ref('');
const isDarkMode = ref(false);
+ const lastDarkTheme = ref('dark');
const appFontFamily = ref('inter');
const displayVRCPlusIconsAsAvatar = ref(false);
const hideNicknames = ref(false);
@@ -107,9 +109,20 @@ export const useAppearanceSettingsStore = defineStore(
return Math.min(max, Math.max(min, n));
};
+ const resolveLastDarkTheme = (value, fallback = 'dark') => {
+ const normalized = String(value || '').trim();
+ return THEME_CONFIG[normalized]?.isDark === true
+ ? normalized
+ : fallback;
+ };
+
async function initAppearanceSettings() {
const { initThemeMode, isDarkMode: initDarkMode } =
await getThemeMode(configRepository);
+ const fallbackDarkTheme =
+ THEME_CONFIG[initThemeMode]?.isDark === true
+ ? initThemeMode
+ : 'dark';
const [
appLanguageConfig,
displayVRCPlusIconsAsAvatarConfig,
@@ -137,7 +150,8 @@ export const useAppearanceSettingsStore = defineStore(
trustColorConfig,
notificationIconDotConfig,
navIsCollapsedConfig,
- appFontFamilyConfig
+ appFontFamilyConfig,
+ lastDarkThemeConfig
] = await Promise.all([
configRepository.getString('VRCX_appLanguage'),
configRepository.getBool('displayVRCPlusIconsAsAvatar', true),
@@ -196,6 +210,10 @@ export const useAppearanceSettingsStore = defineStore(
configRepository.getString(
'VRCX_fontFamily',
APP_FONT_DEFAULT_KEY
+ ),
+ configRepository.getString(
+ 'VRCX_lastDarkTheme',
+ fallbackDarkTheme
)
]);
@@ -216,6 +234,10 @@ export const useAppearanceSettingsStore = defineStore(
themeMode.value = initThemeMode;
isDarkMode.value = initDarkMode;
+ lastDarkTheme.value = resolveLastDarkTheme(
+ lastDarkThemeConfig,
+ fallbackDarkTheme
+ );
appFontFamily.value = normalizeAppFontFamily(appFontFamilyConfig);
applyAppFontFamily(appFontFamily.value);
@@ -463,12 +485,24 @@ export const useAppearanceSettingsStore = defineStore(
function setThemeMode(mode) {
themeMode.value = mode;
configRepository.setString('VRCX_ThemeMode', mode);
+ if (THEME_CONFIG[mode]?.isDark === true) {
+ const normalized = resolveLastDarkTheme(mode);
+ lastDarkTheme.value = normalized;
+ configRepository.setString('VRCX_lastDarkTheme', normalized);
+ }
const { isDark } = changeAppThemeStyle(mode);
isDarkMode.value = isDark;
vrStore.updateVRConfigVars();
updateTrustColor(undefined, undefined);
}
+ function toggleThemeMode() {
+ const nextMode = isDarkMode.value
+ ? 'light'
+ : resolveLastDarkTheme(lastDarkTheme.value);
+ setThemeMode(nextMode);
+ }
+
function normalizeAppFontFamily(value) {
return APP_FONT_FAMILIES.includes(value)
? value
@@ -914,7 +948,8 @@ export const useAppearanceSettingsStore = defineStore(
setNavCollapsed,
toggleNavCollapsed,
setAppFontFamily,
- setThemeMode
+ setThemeMode,
+ toggleThemeMode
};
}
);
diff --git a/src/styles/globals.css b/src/styles/globals.css
index 07234397..182c8f57 100644
--- a/src/styles/globals.css
+++ b/src/styles/globals.css
@@ -84,7 +84,8 @@
}
.dark {
- --background: oklch(0.145 0 0);
+ /* --background: oklch(0.145 0 0); */
+ --background: oklch(0.205 0 0);
--foreground: oklch(0.985 0 0);
--card: oklch(0.145 0 0);
--card-foreground: oklch(0.985 0 0);
diff --git a/src/styles/themes/midnight.css b/src/styles/themes/midnight.css
new file mode 100644
index 00000000..2a87780e
--- /dev/null
+++ b/src/styles/themes/midnight.css
@@ -0,0 +1,35 @@
+.dark {
+ --background: oklch(0.145 0 0);
+ --foreground: oklch(0.985 0 0);
+ --card: oklch(0.145 0 0);
+ --card-foreground: oklch(0.985 0 0);
+ --popover: oklch(0.145 0 0);
+ --popover-foreground: oklch(0.985 0 0);
+ --primary: oklch(0.985 0 0);
+ --primary-foreground: oklch(0.205 0 0);
+ --secondary: oklch(0.269 0 0);
+ --secondary-foreground: oklch(0.985 0 0);
+ --muted: oklch(0.269 0 0);
+ --muted-foreground: oklch(0.708 0 0);
+ --accent: oklch(0.269 0 0);
+ --accent-foreground: oklch(0.985 0 0);
+ --destructive: oklch(0.396 0.141 25.723);
+ --destructive-foreground: oklch(0.637 0.237 25.331);
+ --border: oklch(0.269 0 0);
+ --input: oklch(0.269 0 0);
+ --ring: oklch(0.439 0 0);
+ --chart-1: oklch(0.488 0.243 264.376);
+ --chart-2: oklch(0.696 0.17 162.48);
+ --chart-3: oklch(0.769 0.188 70.08);
+ --chart-4: oklch(0.627 0.265 303.9);
+ --chart-5: oklch(0.645 0.246 16.439);
+ --sidebar: oklch(0.145 0 0);
+ /* --sidebar: oklch(0.205 0 0); */
+ --sidebar-foreground: oklch(0.985 0 0);
+ --sidebar-primary: oklch(0.488 0.243 264.376);
+ --sidebar-primary-foreground: oklch(0.985 0 0);
+ --sidebar-accent: oklch(0.269 0 0);
+ --sidebar-accent-foreground: oklch(0.985 0 0);
+ --sidebar-border: oklch(0.269 0 0);
+ --sidebar-ring: oklch(0.439 0 0);
+}
\ No newline at end of file
diff --git a/src/views/Feed/columns.jsx b/src/views/Feed/columns.jsx
index ad31b8e0..d91d2ec1 100644
--- a/src/views/Feed/columns.jsx
+++ b/src/views/Feed/columns.jsx
@@ -280,7 +280,7 @@ export const columns = [
const original = row.original;
return (
showUserDialog(original.userId)}
>
{original.displayName}
diff --git a/src/views/FriendList/columns.jsx b/src/views/FriendList/columns.jsx
index 6ce48049..920771ad 100644
--- a/src/views/FriendList/columns.jsx
+++ b/src/views/FriendList/columns.jsx
@@ -115,7 +115,6 @@ export const createColumns = ({
header: () => null,
size: 55,
enableSorting: false,
- enableResizing: false,
meta: {
thClass: 'p-0',
tdClass: 'p-0'
diff --git a/src/views/FriendLog/columns.jsx b/src/views/FriendLog/columns.jsx
index 57696d08..07e5abdf 100644
--- a/src/views/FriendLog/columns.jsx
+++ b/src/views/FriendLog/columns.jsx
@@ -118,7 +118,6 @@ export const createColumns = ({ onDelete, onDeletePrompt }) => {
meta: {
class: 'w-[80px] max-w-[80px] text-right'
},
- enableResizing: false,
size: 80,
maxSize: 80,
header: () => t('table.friendLog.action'),
diff --git a/src/views/GameLog/columns.jsx b/src/views/GameLog/columns.jsx
index d3c20b1c..3a192301 100644
--- a/src/views/GameLog/columns.jsx
+++ b/src/views/GameLog/columns.jsx
@@ -241,7 +241,6 @@ export const createColumns = ({ getCreatedAt, onDelete, onDeletePrompt }) => {
meta: {
class: 'text-right'
},
- enableResizing: false,
size: 90,
minSize: 90,
maxSize: 90,
diff --git a/src/views/Moderation/columns.jsx b/src/views/Moderation/columns.jsx
index f49f9131..554e21d9 100644
--- a/src/views/Moderation/columns.jsx
+++ b/src/views/Moderation/columns.jsx
@@ -124,7 +124,6 @@ export const createColumns = ({ onDelete, onDeletePrompt }) => {
minSize: 80,
maxSize: 80,
enableSorting: false,
- enableResizing: false,
header: () => t('table.moderation.action'),
cell: ({ row }) => {
const original = row.original;
diff --git a/src/views/Notifications/columns.jsx b/src/views/Notifications/columns.jsx
index ccb348ce..56ef985c 100644
--- a/src/views/Notifications/columns.jsx
+++ b/src/views/Notifications/columns.jsx
@@ -389,7 +389,6 @@ export const createColumns = ({
},
{
accessorKey: 'photo',
- enableResizing: false,
size: 80,
header: () => t('table.notification.photo'),
cell: ({ row }) => {
@@ -502,7 +501,6 @@ export const createColumns = ({
size: 120,
minSize: 120,
maxSize: 120,
- enableResizing: false,
header: () => t('table.notification.action'),
enableSorting: false,
cell: ({ row }) => {
diff --git a/src/views/Settings/components/Tabs/AppearanceTab.vue b/src/views/Settings/components/Tabs/AppearanceTab.vue
index eae90105..2caf3328 100644
--- a/src/views/Settings/components/Tabs/AppearanceTab.vue
+++ b/src/views/Settings/components/Tabs/AppearanceTab.vue
@@ -21,12 +21,12 @@
{{ t('view.settings.appearance.appearance.theme_mode') }}