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": { "dependencies": {
"@mantine/core": "^8.2.7", "@mantine/core": "^8.2.7",
"@mantine/hooks": "^8.2.7", "@mantine/hooks": "^8.2.7",
"ansi-to-html": "^0.7.2",
"execa": "^9.6.0", "execa": "^9.6.0",
"got": "^14.4.7", "got": "^14.4.7",
"lucide-react": "^0.541.0", "lucide-react": "^0.541.0",

View file

@ -8,6 +8,7 @@ import {
Loader, Loader,
Progress, Progress,
rem, rem,
useComputedColorScheme,
} from '@mantine/core'; } from '@mantine/core';
import { Download } from 'lucide-react'; import { Download } from 'lucide-react';
import { MouseEvent } from 'react'; import { MouseEvent } from 'react';
@ -46,6 +47,10 @@ export const DownloadCard = ({
onMakeCurrent, onMakeCurrent,
onUpdate, onUpdate,
}: DownloadCardProps) => { }: DownloadCardProps) => {
const computedColorScheme = useComputedColorScheme('light', {
getInitialValueInEffect: false,
});
const isDark = computedColorScheme === 'dark';
const renderActionButton = () => { const renderActionButton = () => {
if (hasUpdate && onUpdate) { if (hasUpdate && onUpdate) {
return ( return (
@ -107,9 +112,8 @@ export const DownloadCard = ({
radius="sm" radius="sm"
padding="sm" padding="sm"
{...(isCurrent && { {...(isCurrent && {
c: 'blue', bg: isDark ? 'dark.6' : 'gray.0',
bg: 'blue.0', bd: `2px solid var(--mantine-color-${isDark ? 'blue-4' : 'blue-6'})`,
bd: '2px solid blue',
})} })}
> >
<Group justify="space-between" align="center"> <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 { import {
Box, Box,
ScrollArea, ScrollArea,
Text, Text,
ActionIcon, ActionIcon,
useMantineColorScheme, useComputedColorScheme,
} from '@mantine/core'; } from '@mantine/core';
import { ChevronDown } from 'lucide-react'; import { ChevronDown } from 'lucide-react';
import Convert from 'ansi-to-html';
import styles from '@/styles/layout.module.css'; import styles from '@/styles/layout.module.css';
import { UI } from '@/constants'; import { UI } from '@/constants';
@ -39,19 +38,20 @@ const handleCarriageReturns = (
} else { } else {
const lines = result.split('\n'); const lines = result.split('\n');
if (lines.length > 0) { if (lines.length > 0) {
const lastLineIndex = lines.length - 1;
const restOfData = newData.slice(i + 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[lines.length - 1] = replacementText;
lines[lastLineIndex] = restOfData; result = lines.join('\n');
result = lines.join('\n');
break; i += 1 + replacementText.length;
} else { if (nextNewlinePos !== -1) {
const replacement = restOfData.slice(0, nextCrOrLfIndex); result += '\n';
lines[lastLineIndex] = replacement; i += 1;
result = lines.join('\n');
i += 1 + nextCrOrLfIndex;
} }
} else { } else {
i++; i++;
@ -64,64 +64,24 @@ const handleCarriageReturns = (
} }
return result; return result;
} catch { } catch (error) {
window.electronAPI.logs.logError('Terminal CR Error', error as Error);
return prevContent + newData; return prevContent + newData;
} }
}; };
export const TerminalTab = ({ onServerReady }: TerminalTabProps) => { export const TerminalTab = ({ onServerReady }: TerminalTabProps) => {
const { colorScheme } = useMantineColorScheme(); const computedColorScheme = useComputedColorScheme('light', {
getInitialValueInEffect: false,
});
const [terminalContent, setTerminalContent] = useState<string>(''); const [terminalContent, setTerminalContent] = useState<string>('');
const [formattedContent, setFormattedContent] = useState<string>('');
const [isUserScrolling, setIsUserScrolling] = useState<boolean>(false); const [isUserScrolling, setIsUserScrolling] = useState<boolean>(false);
const [shouldAutoScroll, setShouldAutoScroll] = useState<boolean>(true); const [shouldAutoScroll, setShouldAutoScroll] = useState<boolean>(true);
const scrollAreaRef = useRef<HTMLDivElement>(null); const scrollAreaRef = useRef<HTMLDivElement>(null);
const viewportRef = useRef<HTMLDivElement>(null); const viewportRef = useRef<HTMLDivElement>(null);
const lastScrollTop = useRef<number>(0); const lastScrollTop = useRef<number>(0);
const updateTimeoutRef = useRef<number | null>(null);
const pendingContentRef = useRef<string>('');
const debouncedUpdateContent = useCallback((newContent: string) => { const isDark = computedColorScheme === 'dark';
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 handleScroll = ({ y }: { y: number }) => { const handleScroll = ({ y }: { y: number }) => {
if (!viewportRef.current) return; if (!viewportRef.current) return;
@ -145,7 +105,7 @@ export const TerminalTab = ({ onServerReady }: TerminalTabProps) => {
const viewport = viewportRef.current; const viewport = viewportRef.current;
viewport.scrollTop = viewport.scrollHeight; viewport.scrollTop = viewport.scrollHeight;
} }
}, [formattedContent, shouldAutoScroll, isUserScrolling]); }, [terminalContent, shouldAutoScroll, isUserScrolling]);
useEffect(() => { useEffect(() => {
const cleanup = window.electronAPI.kobold.onKoboldOutput((data: string) => { const cleanup = window.electronAPI.kobold.onKoboldOutput((data: string) => {
@ -165,19 +125,12 @@ export const TerminalTab = ({ onServerReady }: TerminalTabProps) => {
} }
} }
const newContent = handleCarriageReturns(prev, newData); return handleCarriageReturns(prev, newData);
if (newData.includes('\r') && !newData.includes('\n')) {
debouncedUpdateContent(newContent);
return prev;
}
return newContent;
}); });
}); });
return cleanup; return cleanup;
}, [onServerReady, debouncedUpdateContent]); }, [onServerReady]);
const scrollToBottom = () => { const scrollToBottom = () => {
if (viewportRef.current) { if (viewportRef.current) {
@ -194,10 +147,9 @@ export const TerminalTab = ({ onServerReady }: TerminalTabProps) => {
height: `calc(100vh - ${UI.HEADER_HEIGHT}px)`, height: `calc(100vh - ${UI.HEADER_HEIGHT}px)`,
display: 'flex', display: 'flex',
flexDirection: 'column', flexDirection: 'column',
backgroundColor: backgroundColor: isDark
colorScheme === 'dark' ? 'var(--mantine-color-dark-filled)'
? 'var(--mantine-color-dark-filled)' : 'var(--mantine-color-gray-0)',
: 'var(--mantine-color-gray-0)',
borderRadius: 'inherit', borderRadius: 'inherit',
position: 'relative', position: 'relative',
}} }}
@ -219,18 +171,19 @@ export const TerminalTab = ({ onServerReady }: TerminalTabProps) => {
<div <div
style={{ style={{
margin: 0, margin: 0,
fontFamily: 'inherit', fontFamily:
fontSize: 'inherit', 'ui-monospace, SFMono-Regular, "SF Mono", Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace',
lineHeight: 'inherit', fontSize: '14px',
color: lineHeight: 1.4,
colorScheme === 'dark' color: isDark
? 'var(--mantine-color-gray-0)' ? 'var(--mantine-color-gray-0)'
: 'var(--mantine-color-dark-filled)', : 'var(--mantine-color-dark-filled)',
whiteSpace: 'pre-wrap', whiteSpace: 'pre-wrap',
wordBreak: 'break-word', wordBreak: 'break-word',
}} }}
dangerouslySetInnerHTML={{ __html: formattedContent }} >
/> {terminalContent}
</div>
)} )}
</Box> </Box>
</ScrollArea> </ScrollArea>

View file

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

View file

@ -2233,17 +2233,6 @@ __metadata:
languageName: node languageName: node
linkType: hard 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": "app-builder-bin@npm:5.0.0-alpha.12":
version: 5.0.0-alpha.12 version: 5.0.0-alpha.12
resolution: "app-builder-bin@npm:5.0.0-alpha.12" resolution: "app-builder-bin@npm:5.0.0-alpha.12"
@ -3603,13 +3592,6 @@ __metadata:
languageName: node languageName: node
linkType: hard 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": "env-paths@npm:^2.2.0":
version: 2.2.1 version: 2.2.1
resolution: "env-paths@npm: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/eslint-plugin": "npm:^8.40.0"
"@typescript-eslint/parser": "npm:^8.40.0" "@typescript-eslint/parser": "npm:^8.40.0"
"@vitejs/plugin-react": "npm:^5.0.1" "@vitejs/plugin-react": "npm:^5.0.1"
ansi-to-html: "npm:^0.7.2"
cross-env: "npm:^10.0.0" cross-env: "npm:^10.0.0"
cspell: "npm:^9.2.0" cspell: "npm:^9.2.0"
electron: "npm:^37.3.1" electron: "npm:^37.3.1"