Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -91,4 +91,7 @@ KT_PROM_PATH=/metrics
# Tempo query API (for reading traces with curl)
MAIN_VITE_GRAFANA_TEMPO_QUERY_URL=https://tempo-prod-xx-prod-us-east-x.grafana.net/tempo
MAIN_VITE_GRAFANA_TEMPO_QUERY_USER=your_grafana_user_id
MAIN_VITE_GRAFANA_TEMPO_QUERY_TOKEN=glc_your_grafana_cloud_access_policy_token_here
MAIN_VITE_GRAFANA_TEMPO_QUERY_TOKEN=glc_your_grafana_cloud_access_policy_token_here

# Debug flags (disable by default, enable for troubleshooting)
# VITE_DEBUG_7TV_WS=true # Enable verbose 7TV WebSocket message logging
2 changes: 1 addition & 1 deletion .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
"port": 9222,
"request": "attach",
"type": "chrome",
"webRoot": "${workspaceFolder}/src/renderer",
"webRoot": "${workspaceFolder}/src/renderer/src",
"timeout": 60000,
"presentation": {
"hidden": true
Expand Down
1 change: 1 addition & 0 deletions electron.vite.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ export default defineConfig({
},
renderer: {
build: {
sourcemap: true,
rollupOptions: {
input: {
index: resolve("src/renderer/index.html"),
Expand Down
36 changes: 36 additions & 0 deletions src/renderer/src/assets/styles/components/Chat/Message.scss
Original file line number Diff line number Diff line change
Expand Up @@ -1034,3 +1034,39 @@
}

/** [End of Emote Set Update Message] **/

.emote-placeholder {
display: inline-flex;
align-items: center;
justify-content: center;
background-color: #2a2a2a;
border-radius: 4px;
color: #666;
font-size: 10px;
font-family: monospace;
border: 1px solid rgba(255, 255, 255, 0.1);

&.error {
background-color: rgba(255, 100, 100, 0.1);
border-color: rgba(255, 100, 100, 0.3);
color: rgba(255, 100, 100, 0.8);
}
}

.emote-progressive {
.emote {
transition: opacity 0.2s ease-in-out;

&.loading {
opacity: 0;
}

&.loaded {
opacity: 1;
}

&.error {
opacity: 0;
}
}
}
74 changes: 72 additions & 2 deletions src/renderer/src/components/Cosmetics/Emote.jsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,64 @@
import { memo, useCallback, useState, useMemo } from "react";
import EmoteTooltip from "./EmoteTooltip";

// Progressive Loading Hook for Emotes
const useProgressiveEmoteLoading = (emote, type) => {
const [loadState, setLoadState] = useState('loading'); // loading, loaded, error
const [showFallback, setShowFallback] = useState(false);

// Define fallback placeholder (prevents layout shift)
const placeholder = useMemo(() => {
const placeholderWidth = type === "stv" ? (emote.width || 28) : 32;
const placeholderHeight = type === "stv" ? (emote.height || 28) : 32;

return {
width: placeholderWidth,
height: placeholderHeight,
backgroundColor: '#2a2a2a',
display: 'inline-flex',
alignItems: 'center',
justifyContent: 'center',
borderRadius: '4px',
color: '#666',
fontSize: '10px',
fontFamily: 'monospace'
};
}, [emote, type]);

const handleImageLoad = useCallback(() => {
setLoadState('loaded');
setShowFallback(false);
}, []);

const handleImageError = useCallback(() => {
setLoadState('error');
setShowFallback(true);
}, []);

return {
loadState,
showFallback,
placeholder,
handleImageLoad,
handleImageError
};
};

const Emote = memo(({ emote, overlaidEmotes = [], scale = 1, type }) => {
const { id, name, width, height } = emote;

const [showEmoteInfo, setShowEmoteInfo] = useState(false);
const [mousePos, setMousePos] = useState({ x: null, y: null });

// Use progressive loading hook
const {
loadState,
showFallback,
placeholder,
handleImageLoad,
handleImageError
} = useProgressiveEmoteLoading(emote, type);

const emoteSrcSet = useCallback(
(emote) => {
if (type === "stv") {
Expand Down Expand Up @@ -58,18 +110,36 @@ const Emote = memo(({ emote, overlaidEmotes = [], scale = 1, type }) => {
height: type === "stv" ? height : "32px",
}}>
<div
className="chatroomEmote"
className={`chatroomEmote emote-progressive ${loadState}`}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
onMouseMove={handleMouseMove}>
{showFallback || loadState === 'error' ? (
// Fallback placeholder to prevent layout shift
<div
className={`emote-placeholder error`}
style={placeholder}
title={`${name} (failed to load)`}
>
{name.slice(0, 2)}
</div>
) : null}

{/* Always render image but control visibility with CSS */}
<img
className={type === "stv" ? "stvEmote emote" : "kickEmote emote"}
className={`${type === "stv" ? "stvEmote" : "kickEmote"} emote ${loadState}`}
src={emoteImageSrc}
srcSet={type === "stv" ? emoteSrcSet(emote) : null}
alt={name}
loading="lazy"
fetchpriority="low"
decoding="async"
onLoad={handleImageLoad}
onError={handleImageError}
style={{
opacity: loadState === 'loaded' ? 1 : 0,
transition: 'opacity 0.2s ease-in-out'
}}
/>
</div>

Expand Down
Loading