mirror of
https://github.com/fluxerapp/fluxer
synced 2026-10-17 13:05:32 +09:00
fix(app): apply instance branding to system DM (#3355)
This commit is contained in:
1 parent
7197371123
commit
6b6ae13919
11 files changed
+178
-12
No files matched your search
@@ -8,6 +8,7 @@ import {applyPatchToRow, buildPatchFromData, executeVersionedUpdate} from '@app/
|
|||||||
import type {UserRow} from '@app/api/database/types/UserTypes';
|
import type {UserRow} from '@app/api/database/types/UserTypes';
|
||||||
import {EMPTY_USER_ROW, USER_COLUMNS} from '@app/api/database/types/UserTypes';
|
import {EMPTY_USER_ROW, USER_COLUMNS} from '@app/api/database/types/UserTypes';
|
||||||
import {emitAccountChangedIfRelevant} from '@app/api/infrastructure/activity/AccountChangeEvents';
|
import {emitAccountChangedIfRelevant} from '@app/api/infrastructure/activity/AccountChangeEvents';
|
||||||
|
import {getInstanceProductName} from '@app/api/instance/ProductName';
|
||||||
import {User} from '@app/api/models/User';
|
import {User} from '@app/api/models/User';
|
||||||
import {Users} from '@app/api/Tables';
|
import {Users} from '@app/api/Tables';
|
||||||
import {shiftGiftExtensionPastPremiumUntil} from '@app/api/user/GiftExtensionShift';
|
import {shiftGiftExtensionPastPremiumUntil} from '@app/api/user/GiftExtensionShift';
|
||||||
@@ -67,7 +68,7 @@ export class UserDataRepository {
|
|||||||
return new User({
|
return new User({
|
||||||
...EMPTY_USER_ROW,
|
...EMPTY_USER_ROW,
|
||||||
user_id: createUserID(FLUXER_BOT_USER_ID),
|
user_id: createUserID(FLUXER_BOT_USER_ID),
|
||||||
username: 'Fluxer',
|
username: getInstanceProductName(),
|
||||||
discriminator: 0,
|
discriminator: 0,
|
||||||
bot: true,
|
bot: true,
|
||||||
system: true,
|
system: true,
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ interface TokenDef {
|
|||||||
lightness?: number;
|
lightness?: number;
|
||||||
alpha?: number;
|
alpha?: number;
|
||||||
useSaturationFactor?: boolean;
|
useSaturationFactor?: boolean;
|
||||||
|
instanceOverride?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Config {
|
interface Config {
|
||||||
@@ -457,10 +458,28 @@ hsl(258, calc(10% * var(--saturation-factor)), 2%) 10%
|
|||||||
{name: '--control-button-active-text', value: 'var(--text-primary)'},
|
{name: '--control-button-active-text', value: 'var(--text-primary)'},
|
||||||
{name: '--control-button-danger-text', hue: 1, saturation: 77, useSaturationFactor: true, lightness: 60},
|
{name: '--control-button-danger-text', hue: 1, saturation: 77, useSaturationFactor: true, lightness: 60},
|
||||||
{name: '--control-button-danger-hover-bg', hue: 1, saturation: 77, useSaturationFactor: true, lightness: 20},
|
{name: '--control-button-danger-hover-bg', hue: 1, saturation: 77, useSaturationFactor: true, lightness: 20},
|
||||||
{name: '--brand-primary', family: 'brand', lightness: 55},
|
{name: '--brand-primary', family: 'brand', lightness: 55, instanceOverride: '--instance-brand-primary'},
|
||||||
{name: '--brand-secondary', family: 'brand', saturation: 60, lightness: 49},
|
{
|
||||||
{name: '--brand-primary-light', family: 'brand', saturation: 100, lightness: 84},
|
name: '--brand-secondary',
|
||||||
{name: '--brand-primary-fill', hue: 0, saturation: 0, lightness: 100},
|
family: 'brand',
|
||||||
|
saturation: 60,
|
||||||
|
lightness: 49,
|
||||||
|
instanceOverride: '--instance-brand-secondary',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '--brand-primary-light',
|
||||||
|
family: 'brand',
|
||||||
|
saturation: 100,
|
||||||
|
lightness: 84,
|
||||||
|
instanceOverride: '--instance-brand-primary-light',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '--brand-primary-fill',
|
||||||
|
hue: 0,
|
||||||
|
saturation: 0,
|
||||||
|
lightness: 100,
|
||||||
|
instanceOverride: '--instance-brand-primary-fill',
|
||||||
|
},
|
||||||
{name: '--status-online', family: 'statusOnline', lightness: 40},
|
{name: '--status-online', family: 'statusOnline', lightness: 40},
|
||||||
{name: '--status-idle', family: 'statusIdle', lightness: 50},
|
{name: '--status-idle', family: 'statusIdle', lightness: 50},
|
||||||
{name: '--status-dnd', family: 'statusDnd', lightness: 60},
|
{name: '--status-dnd', family: 'statusDnd', lightness: 60},
|
||||||
@@ -474,7 +493,13 @@ hsl(258, calc(10% * var(--saturation-factor)), 2%) 10%
|
|||||||
{name: '--plutonium-icon', family: 'brandIcon', lightness: 50},
|
{name: '--plutonium-icon', family: 'brandIcon', lightness: 50},
|
||||||
{name: '--invite-verified-icon-color', value: 'var(--text-on-brand-primary)'},
|
{name: '--invite-verified-icon-color', value: 'var(--text-on-brand-primary)'},
|
||||||
{name: '--text-link', family: 'link', lightness: 70},
|
{name: '--text-link', family: 'link', lightness: 70},
|
||||||
{name: '--text-on-brand-primary', hue: 0, saturation: 0, lightness: 98},
|
{
|
||||||
|
name: '--text-on-brand-primary',
|
||||||
|
hue: 0,
|
||||||
|
saturation: 0,
|
||||||
|
lightness: 98,
|
||||||
|
instanceOverride: '--instance-text-on-brand-primary',
|
||||||
|
},
|
||||||
...DARK_CODE_TOKENS,
|
...DARK_CODE_TOKENS,
|
||||||
{name: '--text-selection', hue: 198, saturation: 92, useSaturationFactor: true, lightness: 70, alpha: 0.35},
|
{name: '--text-selection', hue: 198, saturation: 92, useSaturationFactor: true, lightness: 70, alpha: 0.35},
|
||||||
{name: '--markup-mention-text', value: 'var(--text-link)'},
|
{name: '--markup-mention-text', value: 'var(--text-link)'},
|
||||||
@@ -792,6 +817,7 @@ interface OutputToken {
|
|||||||
alpha?: number;
|
alpha?: number;
|
||||||
useSaturationFactor?: boolean;
|
useSaturationFactor?: boolean;
|
||||||
value?: string;
|
value?: string;
|
||||||
|
instanceOverride?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
function clamp01(value: number): number {
|
function clamp01(value: number): number {
|
||||||
@@ -866,6 +892,7 @@ function expandTokens(defs: Array<TokenDef>, scales: Record<string, Scale>): Arr
|
|||||||
lightness: def.lightness,
|
lightness: def.lightness,
|
||||||
alpha: def.alpha,
|
alpha: def.alpha,
|
||||||
useSaturationFactor: def.useSaturationFactor,
|
useSaturationFactor: def.useSaturationFactor,
|
||||||
|
instanceOverride: def.instanceOverride,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -919,7 +946,8 @@ function formatTone(token: OutputToken, families: Record<string, ColorFamily>):
|
|||||||
|
|
||||||
function formatValue(token: OutputToken, families: Record<string, ColorFamily>): string {
|
function formatValue(token: OutputToken, families: Record<string, ColorFamily>): string {
|
||||||
if (token.type === 'tone') {
|
if (token.type === 'tone') {
|
||||||
return formatTone(token, families);
|
const tone = formatTone(token, families);
|
||||||
|
return token.instanceOverride ? `var(${token.instanceOverride}, ${tone})` : tone;
|
||||||
}
|
}
|
||||||
return token.value!.trim();
|
return token.value!.trim();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import {usePlatformClasses} from '@app/features/app/hooks/usePlatformClasses';
|
|||||||
import {useServiceWorkerBadge} from '@app/features/app/hooks/useServiceWorkerBadge';
|
import {useServiceWorkerBadge} from '@app/features/app/hooks/useServiceWorkerBadge';
|
||||||
import {useTabKeyFocusGuard} from '@app/features/app/hooks/useTabKeyFocusGuard';
|
import {useTabKeyFocusGuard} from '@app/features/app/hooks/useTabKeyFocusGuard';
|
||||||
import {type LayoutVariant, LayoutVariantProvider} from '@app/features/app/state/LayoutVariantContext';
|
import {type LayoutVariant, LayoutVariantProvider} from '@app/features/app/state/LayoutVariantContext';
|
||||||
|
import RuntimeConfig from '@app/features/app/state/RuntimeConfig';
|
||||||
import RuntimeCrash from '@app/features/app/state/RuntimeCrash';
|
import RuntimeCrash from '@app/features/app/state/RuntimeCrash';
|
||||||
import {showMyselfTypingHelper} from '@app/features/devtools/utils/ShowMyselfTypingHelper';
|
import {showMyselfTypingHelper} from '@app/features/devtools/utils/ShowMyselfTypingHelper';
|
||||||
import GatewayConnection from '@app/features/gateway/transport/GatewayConnection';
|
import GatewayConnection from '@app/features/gateway/transport/GatewayConnection';
|
||||||
@@ -190,6 +191,9 @@ export const AppWrapper = observer(({children}: AppWrapperProps) => {
|
|||||||
messageGutter: Accessibility.messageGutter,
|
messageGutter: Accessibility.messageGutter,
|
||||||
messageGroupSpacing: Accessibility.getMessageGroupSpacingValue(messageDisplayCompact),
|
messageGroupSpacing: Accessibility.getMessageGroupSpacingValue(messageDisplayCompact),
|
||||||
hdrDisplayMode: Accessibility.hdrDisplayMode,
|
hdrDisplayMode: Accessibility.hdrDisplayMode,
|
||||||
|
instanceThemeColor: RuntimeConfig.isSelfHosted()
|
||||||
|
? (RuntimeConfig.getSnapshotOrNull()?.appPublic.branding.theme_color ?? null)
|
||||||
|
: null,
|
||||||
});
|
});
|
||||||
useCustomThemeStyle({
|
useCustomThemeStyle({
|
||||||
enabledThemeCss: ThemeLibrary.activeThemeCss,
|
enabledThemeCss: ThemeLibrary.activeThemeCss,
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ import {UserProfileBadges} from '@app/features/user/components/popouts/UserProfi
|
|||||||
import type {Profile} from '@app/features/user/models/Profile';
|
import type {Profile} from '@app/features/user/models/Profile';
|
||||||
import Users from '@app/features/user/state/Users';
|
import Users from '@app/features/user/state/Users';
|
||||||
import * as NicknameUtils from '@app/features/user/utils/NicknameUtils';
|
import * as NicknameUtils from '@app/features/user/utils/NicknameUtils';
|
||||||
|
import {FLUXERBOT_ID} from '@fluxer/constants/src/AppConstants';
|
||||||
import {RelationshipTypes} from '@fluxer/constants/src/UserConstants';
|
import {RelationshipTypes} from '@fluxer/constants/src/UserConstants';
|
||||||
import {msg, ph, plural} from '@lingui/core/macro';
|
import {msg, ph, plural} from '@lingui/core/macro';
|
||||||
import {Trans, useLingui} from '@lingui/react/macro';
|
import {Trans, useLingui} from '@lingui/react/macro';
|
||||||
@@ -96,7 +97,9 @@ export const DMWelcomeSection: React.FC<DMWelcomeSectionProps> = observer(functi
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const displayName = NicknameUtils.getNickname(user, null, channel?.id);
|
const displayName = NicknameUtils.getNickname(user, null, channel?.id);
|
||||||
const fluxerTag = NicknameUtils.formatTagForStreamerMode(user.tag);
|
const fluxerTag = NicknameUtils.formatTagForStreamerMode(
|
||||||
|
user.id === FLUXERBOT_ID ? `${displayName}#${user.discriminator}` : user.tag,
|
||||||
|
);
|
||||||
const handleSendFriendRequest = () => {
|
const handleSendFriendRequest = () => {
|
||||||
RelationshipActionUtils.sendFriendRequest(i18n, user.id);
|
RelationshipActionUtils.sendFriendRequest(i18n, user.id);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -55,6 +55,7 @@ function ThemedApp({
|
|||||||
messageGutter: 16,
|
messageGutter: 16,
|
||||||
messageGroupSpacing: groupSpacing,
|
messageGroupSpacing: groupSpacing,
|
||||||
hdrDisplayMode: HdrDisplayMode.FULL,
|
hdrDisplayMode: HdrDisplayMode.FULL,
|
||||||
|
instanceThemeColor: null,
|
||||||
});
|
});
|
||||||
return (
|
return (
|
||||||
<MeasuringChild
|
<MeasuringChild
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
import {HdrDisplayMode} from '@app/features/accessibility/state/Accessibility';
|
import {HdrDisplayMode} from '@app/features/accessibility/state/Accessibility';
|
||||||
import {remFromPx} from '@app/features/theme/layout/RemFromPx';
|
import {remFromPx} from '@app/features/theme/layout/RemFromPx';
|
||||||
|
import {getInstanceBrandVariables, INSTANCE_BRAND_VARIABLES} from '@app/features/theme/utils/InstanceBrandColors';
|
||||||
import {useLayoutEffect} from 'react';
|
import {useLayoutEffect} from 'react';
|
||||||
|
|
||||||
interface ThemeCssVariablesOptions {
|
interface ThemeCssVariablesOptions {
|
||||||
@@ -14,6 +15,7 @@ interface ThemeCssVariablesOptions {
|
|||||||
messageGutter: number;
|
messageGutter: number;
|
||||||
messageGroupSpacing: number;
|
messageGroupSpacing: number;
|
||||||
hdrDisplayMode: HdrDisplayMode;
|
hdrDisplayMode: HdrDisplayMode;
|
||||||
|
instanceThemeColor: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useThemeCssVariables({
|
export function useThemeCssVariables({
|
||||||
@@ -26,6 +28,7 @@ export function useThemeCssVariables({
|
|||||||
messageGutter,
|
messageGutter,
|
||||||
messageGroupSpacing,
|
messageGroupSpacing,
|
||||||
hdrDisplayMode,
|
hdrDisplayMode,
|
||||||
|
instanceThemeColor,
|
||||||
}: ThemeCssVariablesOptions): void {
|
}: ThemeCssVariablesOptions): void {
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
const htmlNode = document.documentElement;
|
const htmlNode = document.documentElement;
|
||||||
@@ -63,6 +66,20 @@ export function useThemeCssVariables({
|
|||||||
messageGroupSpacing,
|
messageGroupSpacing,
|
||||||
hdrDisplayMode,
|
hdrDisplayMode,
|
||||||
]);
|
]);
|
||||||
|
useLayoutEffect(() => {
|
||||||
|
if (instanceThemeColor === null) return;
|
||||||
|
const variables = getInstanceBrandVariables(instanceThemeColor);
|
||||||
|
if (variables === null) return;
|
||||||
|
const htmlNode = document.documentElement;
|
||||||
|
for (const name of INSTANCE_BRAND_VARIABLES) {
|
||||||
|
htmlNode.style.setProperty(name, variables[name]);
|
||||||
|
}
|
||||||
|
return () => {
|
||||||
|
for (const name of INSTANCE_BRAND_VARIABLES) {
|
||||||
|
htmlNode.style.removeProperty(name);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, [instanceThemeColor]);
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
const htmlNode = document.documentElement;
|
const htmlNode = document.documentElement;
|
||||||
return () => {
|
return () => {
|
||||||
|
|||||||
@@ -0,0 +1,83 @@
|
|||||||
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
|
export const INSTANCE_BRAND_VARIABLES = [
|
||||||
|
'--instance-brand-primary',
|
||||||
|
'--instance-brand-secondary',
|
||||||
|
'--instance-brand-primary-light',
|
||||||
|
'--instance-brand-primary-fill',
|
||||||
|
'--instance-text-on-brand-primary',
|
||||||
|
] as const;
|
||||||
|
|
||||||
|
type InstanceBrandVariable = (typeof INSTANCE_BRAND_VARIABLES)[number];
|
||||||
|
|
||||||
|
const HEX_COLOR_PATTERN = /^#(?:[0-9a-f]{3}|[0-9a-f]{6})$/i;
|
||||||
|
|
||||||
|
function parseHexColor(value: string): [number, number, number] | null {
|
||||||
|
const trimmed = value.trim();
|
||||||
|
if (!HEX_COLOR_PATTERN.test(trimmed)) return null;
|
||||||
|
const digits =
|
||||||
|
trimmed.length === 4
|
||||||
|
? trimmed
|
||||||
|
.slice(1)
|
||||||
|
.split('')
|
||||||
|
.map((digit) => digit + digit)
|
||||||
|
.join('')
|
||||||
|
: trimmed.slice(1);
|
||||||
|
return [
|
||||||
|
Number.parseInt(digits.slice(0, 2), 16),
|
||||||
|
Number.parseInt(digits.slice(2, 4), 16),
|
||||||
|
Number.parseInt(digits.slice(4, 6), 16),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
function rgbToHsl(red: number, green: number, blue: number): [number, number, number] {
|
||||||
|
const r = red / 255;
|
||||||
|
const g = green / 255;
|
||||||
|
const b = blue / 255;
|
||||||
|
const max = Math.max(r, g, b);
|
||||||
|
const min = Math.min(r, g, b);
|
||||||
|
const lightness = (max + min) / 2;
|
||||||
|
const delta = max - min;
|
||||||
|
if (delta === 0) return [0, 0, lightness * 100];
|
||||||
|
const saturation = delta / (1 - Math.abs(2 * lightness - 1));
|
||||||
|
let hue: number;
|
||||||
|
if (max === r) {
|
||||||
|
hue = ((g - b) / delta) % 6;
|
||||||
|
} else if (max === g) {
|
||||||
|
hue = (b - r) / delta + 2;
|
||||||
|
} else {
|
||||||
|
hue = (r - g) / delta + 4;
|
||||||
|
}
|
||||||
|
return [(hue * 60 + 360) % 360, saturation * 100, lightness * 100];
|
||||||
|
}
|
||||||
|
|
||||||
|
function relativeLuminance(red: number, green: number, blue: number): number {
|
||||||
|
const linear = (channel: number) => {
|
||||||
|
const value = channel / 255;
|
||||||
|
return value <= 0.03928 ? value / 12.92 : ((value + 0.055) / 1.055) ** 2.4;
|
||||||
|
};
|
||||||
|
return 0.2126 * linear(red) + 0.7152 * linear(green) + 0.0722 * linear(blue);
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatNumber(value: number): string {
|
||||||
|
return String(Math.round(value * 100) / 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
function hsl(hue: number, saturation: number, lightness: number): string {
|
||||||
|
return `hsl(${formatNumber(hue)}, calc(${formatNumber(saturation)}% * var(--saturation-factor)), ${formatNumber(lightness)}%)`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getInstanceBrandVariables(themeColor: string): Record<InstanceBrandVariable, string> | null {
|
||||||
|
const rgb = parseHexColor(themeColor);
|
||||||
|
if (rgb === null) return null;
|
||||||
|
const [hue, saturation, lightness] = rgbToHsl(...rgb);
|
||||||
|
const luminance = relativeLuminance(...rgb);
|
||||||
|
const prefersDarkText = (luminance + 0.05) / 0.05 > 1.05 / (luminance + 0.05);
|
||||||
|
return {
|
||||||
|
'--instance-brand-primary': hsl(hue, saturation, lightness),
|
||||||
|
'--instance-brand-secondary': hsl(hue, saturation * (6 / 7), lightness * 0.89),
|
||||||
|
'--instance-brand-primary-light': hsl(hue, saturation, lightness + (100 - lightness) * 0.65),
|
||||||
|
'--instance-brand-primary-fill': prefersDarkText ? 'hsl(0, 0%, 8%)' : 'hsl(0, 0%, 100%)',
|
||||||
|
'--instance-text-on-brand-primary': prefersDarkText ? 'hsl(0, 0%, 8%)' : 'hsl(0, 0%, 98%)',
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -64,12 +64,14 @@ const AvatarComponent = React.forwardRef<HTMLDivElement, AvatarProps>(
|
|||||||
const {i18n} = useLingui();
|
const {i18n} = useLingui();
|
||||||
const userId = user.id;
|
const userId = user.id;
|
||||||
const userAvatar = user.avatar;
|
const userAvatar = user.avatar;
|
||||||
|
const systemAvatarUrl = AvatarUtils.getSystemUserBrandingAvatarURL(userId);
|
||||||
const guildMember = GuildMembers.getMember(guildId || '', userId);
|
const guildMember = GuildMembers.getMember(guildId || '', userId);
|
||||||
const hasGuildMemberAvatarSource = Boolean(guildId) && guildMember != null;
|
const hasGuildMemberAvatarSource = Boolean(guildId) && guildMember != null;
|
||||||
const memberAvatar = guildMember?.avatar ?? null;
|
const memberAvatar = guildMember?.avatar ?? null;
|
||||||
const memberAvatarUnset = guildMember?.isAvatarUnset() ?? false;
|
const memberAvatarUnset = guildMember?.isAvatarUnset() ?? false;
|
||||||
const avatarUrl = useMemo(() => {
|
const avatarUrl = useMemo(() => {
|
||||||
if (customAvatarUrl !== undefined) return customAvatarUrl;
|
if (customAvatarUrl !== undefined) return customAvatarUrl;
|
||||||
|
if (systemAvatarUrl !== null) return systemAvatarUrl;
|
||||||
if (guildId && hasGuildMemberAvatarSource) {
|
if (guildId && hasGuildMemberAvatarSource) {
|
||||||
return AvatarUtils.getGuildMemberDisplayAvatarURL({
|
return AvatarUtils.getGuildMemberDisplayAvatarURL({
|
||||||
guildId,
|
guildId,
|
||||||
@@ -88,11 +90,13 @@ const AvatarComponent = React.forwardRef<HTMLDivElement, AvatarProps>(
|
|||||||
memberAvatar,
|
memberAvatar,
|
||||||
memberAvatarUnset,
|
memberAvatarUnset,
|
||||||
mediaSize,
|
mediaSize,
|
||||||
|
systemAvatarUrl,
|
||||||
userAvatar,
|
userAvatar,
|
||||||
userId,
|
userId,
|
||||||
]);
|
]);
|
||||||
const hoverAvatarUrl = useMemo(() => {
|
const hoverAvatarUrl = useMemo(() => {
|
||||||
if (customHoverAvatarUrl !== undefined) return customHoverAvatarUrl;
|
if (customHoverAvatarUrl !== undefined) return customHoverAvatarUrl;
|
||||||
|
if (systemAvatarUrl !== null) return systemAvatarUrl;
|
||||||
if (guildId && hasGuildMemberAvatarSource) {
|
if (guildId && hasGuildMemberAvatarSource) {
|
||||||
return AvatarUtils.getGuildMemberDisplayAvatarURL({
|
return AvatarUtils.getGuildMemberDisplayAvatarURL({
|
||||||
guildId,
|
guildId,
|
||||||
@@ -111,6 +115,7 @@ const AvatarComponent = React.forwardRef<HTMLDivElement, AvatarProps>(
|
|||||||
memberAvatar,
|
memberAvatar,
|
||||||
memberAvatarUnset,
|
memberAvatarUnset,
|
||||||
mediaSize,
|
mediaSize,
|
||||||
|
systemAvatarUrl,
|
||||||
userAvatar,
|
userAvatar,
|
||||||
userId,
|
userId,
|
||||||
]);
|
]);
|
||||||
|
|||||||
+15
-4
@@ -1,11 +1,14 @@
|
|||||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
|
import RuntimeConfig from '@app/features/app/state/RuntimeConfig';
|
||||||
import {getUserAccentColor} from '@app/features/theme/utils/AccentColorUtils';
|
import {getUserAccentColor} from '@app/features/theme/utils/AccentColorUtils';
|
||||||
import {ProfileBody} from '@app/features/user/components/modals/user_profile_modal/ProfileBody';
|
import {ProfileBody} from '@app/features/user/components/modals/user_profile_modal/ProfileBody';
|
||||||
import {ProfileMediaHeader} from '@app/features/user/components/modals/user_profile_modal/ProfileMediaHeader';
|
import {ProfileMediaHeader} from '@app/features/user/components/modals/user_profile_modal/ProfileMediaHeader';
|
||||||
import type {ProfileModalContentProps} from '@app/features/user/components/modals/user_profile_modal/UserProfileModalShared';
|
import type {ProfileModalContentProps} from '@app/features/user/components/modals/user_profile_modal/UserProfileModalShared';
|
||||||
|
import * as AvatarUtils from '@app/features/user/utils/AvatarUtils';
|
||||||
import * as ProfileDisplayUtils from '@app/features/user/utils/ProfileDisplayUtils';
|
import * as ProfileDisplayUtils from '@app/features/user/utils/ProfileDisplayUtils';
|
||||||
import {resolveProfileGuildMembership, toProfileDisplayContext} from '@app/features/user/utils/ProfileGuildMembership';
|
import {resolveProfileGuildMembership, toProfileDisplayContext} from '@app/features/user/utils/ProfileGuildMembership';
|
||||||
|
import {FLUXERBOT_ID} from '@fluxer/constants/src/AppConstants';
|
||||||
import {
|
import {
|
||||||
MEDIA_PROXY_AVATAR_SIZE_PROFILE,
|
MEDIA_PROXY_AVATAR_SIZE_PROFILE,
|
||||||
MEDIA_PROXY_PROFILE_BANNER_SIZE_MODAL,
|
MEDIA_PROXY_PROFILE_BANNER_SIZE_MODAL,
|
||||||
@@ -26,7 +29,12 @@ export const ProfileModalContent: React.FC<ProfileModalContentProps> = observer(
|
|||||||
showProfileDataWarning,
|
showProfileDataWarning,
|
||||||
}) => {
|
}) => {
|
||||||
const effectiveProfile = profile?.getEffectiveProfile() ?? null;
|
const effectiveProfile = profile?.getEffectiveProfile() ?? null;
|
||||||
const bannerColor = getUserAccentColor(user, effectiveProfile?.accent_color);
|
const isSystemUser = user.id === FLUXERBOT_ID;
|
||||||
|
const systemThemeColor =
|
||||||
|
isSystemUser && RuntimeConfig.isSelfHosted()
|
||||||
|
? RuntimeConfig.getSnapshotOrNull()?.appPublic.branding.theme_color
|
||||||
|
: null;
|
||||||
|
const bannerColor = systemThemeColor ?? getUserAccentColor(user, effectiveProfile?.accent_color);
|
||||||
const membership = resolveProfileGuildMembership(profile);
|
const membership = resolveProfileGuildMembership(profile);
|
||||||
const profileContext = useMemo<ProfileDisplayUtils.ProfileDisplayContext>(
|
const profileContext = useMemo<ProfileDisplayUtils.ProfileDisplayContext>(
|
||||||
() =>
|
() =>
|
||||||
@@ -38,10 +46,13 @@ export const ProfileModalContent: React.FC<ProfileModalContentProps> = observer(
|
|||||||
}),
|
}),
|
||||||
[user, profile, membership],
|
[user, profile, membership],
|
||||||
);
|
);
|
||||||
const {avatarUrl, hoverAvatarUrl} = useMemo(
|
const {avatarUrl: profileAvatarUrl, hoverAvatarUrl: profileHoverAvatarUrl} = useMemo(
|
||||||
() => ProfileDisplayUtils.getProfileAvatarUrls(profileContext, previewOverrides, MEDIA_PROXY_AVATAR_SIZE_PROFILE),
|
() => ProfileDisplayUtils.getProfileAvatarUrls(profileContext, previewOverrides, MEDIA_PROXY_AVATAR_SIZE_PROFILE),
|
||||||
[profileContext, previewOverrides],
|
[profileContext, previewOverrides],
|
||||||
);
|
);
|
||||||
|
const systemAvatarUrl = AvatarUtils.getSystemUserBrandingAvatarURL(user.id);
|
||||||
|
const avatarUrl = systemAvatarUrl ?? profileAvatarUrl;
|
||||||
|
const hoverAvatarUrl = systemAvatarUrl ?? profileHoverAvatarUrl;
|
||||||
const {bannerUrl, hoverBannerUrl} = useMemo(
|
const {bannerUrl, hoverBannerUrl} = useMemo(
|
||||||
() =>
|
() =>
|
||||||
ProfileDisplayUtils.getProfileBannerUrls(
|
ProfileDisplayUtils.getProfileBannerUrls(
|
||||||
@@ -59,8 +70,8 @@ export const ProfileModalContent: React.FC<ProfileModalContentProps> = observer(
|
|||||||
profileContext={profileContext}
|
profileContext={profileContext}
|
||||||
previewOverrides={previewOverrides}
|
previewOverrides={previewOverrides}
|
||||||
bannerColor={bannerColor}
|
bannerColor={bannerColor}
|
||||||
bannerUrl={bannerUrl}
|
bannerUrl={isSystemUser ? null : bannerUrl}
|
||||||
hoverBannerUrl={hoverBannerUrl}
|
hoverBannerUrl={isSystemUser ? null : hoverBannerUrl}
|
||||||
avatarUrl={avatarUrl}
|
avatarUrl={avatarUrl}
|
||||||
hoverAvatarUrl={hoverAvatarUrl}
|
hoverAvatarUrl={hoverAvatarUrl}
|
||||||
renderActionButtons={renderActionButtons}
|
renderActionButtons={renderActionButtons}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import {
|
|||||||
parseAvatarHash,
|
parseAvatarHash,
|
||||||
} from '@app/features/user/utils/AvatarMediaUtils';
|
} from '@app/features/user/utils/AvatarMediaUtils';
|
||||||
import {getDefaultAvatarAssetURL} from '@app/features/user/utils/DefaultAvatars';
|
import {getDefaultAvatarAssetURL} from '@app/features/user/utils/DefaultAvatars';
|
||||||
|
import {FLUXERBOT_ID} from '@fluxer/constants/src/AppConstants';
|
||||||
import {
|
import {
|
||||||
MEDIA_PROXY_AVATAR_SIZE_DEFAULT,
|
MEDIA_PROXY_AVATAR_SIZE_DEFAULT,
|
||||||
MEDIA_PROXY_ICON_SIZE_DEFAULT,
|
MEDIA_PROXY_ICON_SIZE_DEFAULT,
|
||||||
@@ -75,6 +76,12 @@ const getViewportSplashSize = (): MediaProxyImageSize => {
|
|||||||
|
|
||||||
export const getDefaultAvatarURLForIndex = (index: number): string => getDefaultAvatarAssetURL(index);
|
export const getDefaultAvatarURLForIndex = (index: number): string => getDefaultAvatarAssetURL(index);
|
||||||
|
|
||||||
|
export function getSystemUserBrandingAvatarURL(userId: string): string | null {
|
||||||
|
if (userId !== FLUXERBOT_ID || !RuntimeConfig.isSelfHosted()) return null;
|
||||||
|
const branding = RuntimeConfig.getSnapshotOrNull()?.appPublic.branding;
|
||||||
|
return branding?.icon_url ?? branding?.logo_url ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
export function getDefaultAvatarPrimaryColor(id: string) {
|
export function getDefaultAvatarPrimaryColor(id: string) {
|
||||||
return getSharedDefaultAvatarPrimaryColor(id);
|
return getSharedDefaultAvatarPrimaryColor(id);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
|
import RuntimeConfig from '@app/features/app/state/RuntimeConfig';
|
||||||
import Channels from '@app/features/channel/state/Channels';
|
import Channels from '@app/features/channel/state/Channels';
|
||||||
import GuildMembers from '@app/features/member/state/GuildMembers';
|
import GuildMembers from '@app/features/member/state/GuildMembers';
|
||||||
import SelectedGuild from '@app/features/navigation/state/SelectedGuild';
|
import SelectedGuild from '@app/features/navigation/state/SelectedGuild';
|
||||||
@@ -8,6 +9,7 @@ import StreamerMode from '@app/features/streamer_mode/state/StreamerMode';
|
|||||||
import {noteText} from '@app/features/theme/fonts/ScriptFontLoader';
|
import {noteText} from '@app/features/theme/fonts/ScriptFontLoader';
|
||||||
import type {User} from '@app/features/user/models/User';
|
import type {User} from '@app/features/user/models/User';
|
||||||
import {formatUserTag} from '@app/features/user/utils/UserTagUtils';
|
import {formatUserTag} from '@app/features/user/utils/UserTagUtils';
|
||||||
|
import {FLUXERBOT_ID} from '@fluxer/constants/src/AppConstants';
|
||||||
|
|
||||||
export interface UserDisplayNameLike {
|
export interface UserDisplayNameLike {
|
||||||
username: string;
|
username: string;
|
||||||
@@ -51,6 +53,10 @@ export function getDisplayName(user: UserDisplayNameLike): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function getNickname(user: User, guildId?: string | null, channelId?: string): string {
|
export function getNickname(user: User, guildId?: string | null, channelId?: string): string {
|
||||||
|
if (user.id === FLUXERBOT_ID) {
|
||||||
|
const productName = RuntimeConfig.getSnapshotOrNull()?.appPublic.branding.product_name;
|
||||||
|
if (productName) return formatNicknameForStreamerMode(productName);
|
||||||
|
}
|
||||||
let name = user.displayName || user.globalName || user.username || user.id || '';
|
let name = user.displayName || user.globalName || user.username || user.id || '';
|
||||||
const relationship = Relationships.getRelationship(user.id);
|
const relationship = Relationships.getRelationship(user.id);
|
||||||
if (relationship?.nickname) {
|
if (relationship?.nickname) {
|
||||||
|
|||||||
Reference in new issue
Block a user