fix some dark mode issues, still battling the terminal to display wget progress displays

This commit is contained in:
lone-cloud 2025-08-22 22:00:42 -07:00
parent 86d38819bb
commit 8ccd9595f5
5 changed files with 52 additions and 114 deletions

View file

@ -88,7 +88,6 @@
"dependencies": {
"@mantine/core": "^8.2.7",
"@mantine/hooks": "^8.2.7",
"ansi-to-html": "^0.7.2",
"execa": "^9.6.0",
"got": "^14.4.7",
"lucide-react": "^0.541.0",

View file

@ -8,6 +8,7 @@ import {
Loader,
Progress,
rem,
useComputedColorScheme,
} from '@mantine/core';
import { Download } from 'lucide-react';
import { MouseEvent } from 'react';
@ -46,6 +47,10 @@ export const DownloadCard = ({
onMakeCurrent,
onUpdate,
}: DownloadCardProps) => {
const computedColorScheme = useComputedColorScheme('light', {
getInitialValueInEffect: false,
});
const isDark = computedColorScheme === 'dark';
const renderActionButton = () => {
if (hasUpdate && onUpdate) {
return (
@ -107,9 +112,8 @@ export const DownloadCard = ({
radius="sm"
padding="sm"
{...(isCurrent && {
c: 'blue',
bg: 'blue.0',
bd: '2px solid blue',
bg: isDark ? 'dark.6' : 'gray.0',
bd: `2px solid var(--mantine-color-${isDark ? 'blue-4' : 'blue-6'})`,
})}
>
<Group justify="space-between" align="center">

View file

@ -1,13 +1,12 @@
import { useState, useEffect, useRef, useCallback } from 'react';
import { useState, useEffect, useRef } from 'react';
import {
Box,
ScrollArea,
Text,
ActionIcon,
useMantineColorScheme,
useComputedColorScheme,
} from '@mantine/core';
import { ChevronDown } from 'lucide-react';
import Convert from 'ansi-to-html';
import styles from '@/styles/layout.module.css';
import { UI } from '@/constants';
@ -39,19 +38,20 @@ const handleCarriageReturns = (
} else {
const lines = result.split('\n');
if (lines.length > 0) {
const lastLineIndex = lines.length - 1;
const restOfData = newData.slice(i + 1);
const nextCrOrLfIndex = restOfData.search(/[\r\n]/);
const nextNewlinePos = restOfData.indexOf('\n');
const replacementText =
nextNewlinePos === -1
? restOfData
: restOfData.slice(0, nextNewlinePos);
if (nextCrOrLfIndex === -1) {
lines[lastLineIndex] = restOfData;
result = lines.join('\n');
break;
} else {
const replacement = restOfData.slice(0, nextCrOrLfIndex);
lines[lastLineIndex] = replacement;
result = lines.join('\n');
i += 1 + nextCrOrLfIndex;
lines[lines.length - 1] = replacementText;
result = lines.join('\n');
i += 1 + replacementText.length;
if (nextNewlinePos !== -1) {
result += '\n';
i += 1;
}
} else {
i++;
@ -64,64 +64,24 @@ const handleCarriageReturns = (
}
return result;
} catch {
} catch (error) {
window.electronAPI.logs.logError('Terminal CR Error', error as Error);
return prevContent + newData;
}
};
export const TerminalTab = ({ onServerReady }: TerminalTabProps) => {
const { colorScheme } = useMantineColorScheme();
const computedColorScheme = useComputedColorScheme('light', {
getInitialValueInEffect: false,
});
const [terminalContent, setTerminalContent] = useState<string>('');
const [formattedContent, setFormattedContent] = useState<string>('');
const [isUserScrolling, setIsUserScrolling] = useState<boolean>(false);
const [shouldAutoScroll, setShouldAutoScroll] = useState<boolean>(true);
const scrollAreaRef = useRef<HTMLDivElement>(null);
const viewportRef = useRef<HTMLDivElement>(null);
const lastScrollTop = useRef<number>(0);
const updateTimeoutRef = useRef<number | null>(null);
const pendingContentRef = useRef<string>('');
const debouncedUpdateContent = useCallback((newContent: string) => {
pendingContentRef.current = newContent;
if (updateTimeoutRef.current) {
clearTimeout(updateTimeoutRef.current);
}
updateTimeoutRef.current = window.setTimeout(() => {
setTerminalContent(pendingContentRef.current);
updateTimeoutRef.current = null;
}, 16);
}, []);
const converter = useRef(
new Convert({
fg: colorScheme === 'dark' ? '#ffffff' : '#000000',
bg: colorScheme === 'dark' ? '#1a1b1e' : '#ffffff',
newline: false,
escapeXML: true,
stream: false,
})
);
useEffect(() => {
converter.current = new Convert({
fg: colorScheme === 'dark' ? '#ffffff' : '#000000',
bg: colorScheme === 'dark' ? '#1a1b1e' : '#ffffff',
newline: false,
escapeXML: true,
stream: false,
});
if (terminalContent) {
setFormattedContent(converter.current.toHtml(terminalContent));
}
}, [colorScheme, terminalContent]);
useEffect(() => {
if (terminalContent) {
setFormattedContent(converter.current.toHtml(terminalContent));
}
}, [terminalContent]);
const isDark = computedColorScheme === 'dark';
const handleScroll = ({ y }: { y: number }) => {
if (!viewportRef.current) return;
@ -145,7 +105,7 @@ export const TerminalTab = ({ onServerReady }: TerminalTabProps) => {
const viewport = viewportRef.current;
viewport.scrollTop = viewport.scrollHeight;
}
}, [formattedContent, shouldAutoScroll, isUserScrolling]);
}, [terminalContent, shouldAutoScroll, isUserScrolling]);
useEffect(() => {
const cleanup = window.electronAPI.kobold.onKoboldOutput((data: string) => {
@ -165,19 +125,12 @@ export const TerminalTab = ({ onServerReady }: TerminalTabProps) => {
}
}
const newContent = handleCarriageReturns(prev, newData);
if (newData.includes('\r') && !newData.includes('\n')) {
debouncedUpdateContent(newContent);
return prev;
}
return newContent;
return handleCarriageReturns(prev, newData);
});
});
return cleanup;
}, [onServerReady, debouncedUpdateContent]);
}, [onServerReady]);
const scrollToBottom = () => {
if (viewportRef.current) {
@ -194,10 +147,9 @@ export const TerminalTab = ({ onServerReady }: TerminalTabProps) => {
height: `calc(100vh - ${UI.HEADER_HEIGHT}px)`,
display: 'flex',
flexDirection: 'column',
backgroundColor:
colorScheme === 'dark'
? 'var(--mantine-color-dark-filled)'
: 'var(--mantine-color-gray-0)',
backgroundColor: isDark
? 'var(--mantine-color-dark-filled)'
: 'var(--mantine-color-gray-0)',
borderRadius: 'inherit',
position: 'relative',
}}
@ -219,18 +171,19 @@ export const TerminalTab = ({ onServerReady }: TerminalTabProps) => {
<div
style={{
margin: 0,
fontFamily: 'inherit',
fontSize: 'inherit',
lineHeight: 'inherit',
color:
colorScheme === 'dark'
? 'var(--mantine-color-gray-0)'
: 'var(--mantine-color-dark-filled)',
fontFamily:
'ui-monospace, SFMono-Regular, "SF Mono", Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace',
fontSize: '14px',
lineHeight: 1.4,
color: isDark
? 'var(--mantine-color-gray-0)'
: 'var(--mantine-color-dark-filled)',
whiteSpace: 'pre-wrap',
wordBreak: 'break-word',
}}
dangerouslySetInnerHTML={{ __html: formattedContent }}
/>
>
{terminalContent}
</div>
)}
</Box>
</ScrollArea>

View file

@ -1,9 +1,13 @@
import { Stack, Text, Group, SegmentedControl, rem } from '@mantine/core';
import { useMantineColorScheme } from '@mantine/core';
import { useMantineColorScheme, useComputedColorScheme } from '@mantine/core';
import { Sun, Moon, Monitor } from 'lucide-react';
export const AppearanceTab = () => {
const { colorScheme, setColorScheme } = useMantineColorScheme();
const computedColorScheme = useComputedColorScheme('light', {
getInitialValueInEffect: false,
});
const isDark = computedColorScheme === 'dark';
return (
<Stack gap="lg" h="100%">
@ -22,14 +26,11 @@ export const AppearanceTab = () => {
}
styles={(theme) => ({
indicator: {
backgroundColor:
colorScheme === 'dark'
? theme.colors.dark[5]
: theme.colors.gray[2],
backgroundColor: isDark
? theme.colors.dark[5]
: theme.colors.gray[2],
border: `1px solid ${
colorScheme === 'dark'
? theme.colors.dark[4]
: theme.colors.gray[4]
isDark ? theme.colors.dark[4] : theme.colors.gray[4]
}`,
},
})}

View file

@ -2233,17 +2233,6 @@ __metadata:
languageName: node
linkType: hard
"ansi-to-html@npm:^0.7.2":
version: 0.7.2
resolution: "ansi-to-html@npm:0.7.2"
dependencies:
entities: "npm:^2.2.0"
bin:
ansi-to-html: bin/ansi-to-html
checksum: 10c0/031da78f716e7c6b0e391c64f7bc5e95f2d37123dcc3237d8c592dc35830dd0da05e0c3f3e3f8179856cfe5fd85c689d2ad85024b71b50014da9ef6e8fa021cf
languageName: node
linkType: hard
"app-builder-bin@npm:5.0.0-alpha.12":
version: 5.0.0-alpha.12
resolution: "app-builder-bin@npm:5.0.0-alpha.12"
@ -3603,13 +3592,6 @@ __metadata:
languageName: node
linkType: hard
"entities@npm:^2.2.0":
version: 2.2.0
resolution: "entities@npm:2.2.0"
checksum: 10c0/7fba6af1f116300d2ba1c5673fc218af1961b20908638391b4e1e6d5850314ee2ac3ec22d741b3a8060479911c99305164aed19b6254bde75e7e6b1b2c3f3aa3
languageName: node
linkType: hard
"env-paths@npm:^2.2.0":
version: 2.2.1
resolution: "env-paths@npm:2.2.1"
@ -4409,7 +4391,6 @@ __metadata:
"@typescript-eslint/eslint-plugin": "npm:^8.40.0"
"@typescript-eslint/parser": "npm:^8.40.0"
"@vitejs/plugin-react": "npm:^5.0.1"
ansi-to-html: "npm:^0.7.2"
cross-env: "npm:^10.0.0"
cspell: "npm:^9.2.0"
electron: "npm:^37.3.1"