mirror of
https://github.com/voideditor/void
synced 2026-05-24 09:58:23 +00:00
add responsive dark mode
This commit is contained in:
parent
c485d2c478
commit
c44497510b
9 changed files with 77 additions and 41 deletions
|
|
@ -177,11 +177,6 @@ export type SettingName = UnionOfKeys<SettingsOfProvider[ProviderName]>
|
|||
|
||||
|
||||
|
||||
type DisplayInfo = {
|
||||
title: string,
|
||||
type: string,
|
||||
placeholder: string,
|
||||
}
|
||||
|
||||
export const titleOfProviderName = (providerName: ProviderName) => {
|
||||
if (providerName === 'anthropic')
|
||||
|
|
@ -202,11 +197,14 @@ export const titleOfProviderName = (providerName: ProviderName) => {
|
|||
throw new Error(`descOfProviderName: Unknown provider name: "${providerName}"`)
|
||||
}
|
||||
|
||||
type DisplayInfo = {
|
||||
title: string,
|
||||
placeholder: string,
|
||||
}
|
||||
export const displayInfoOfSettingName = (providerName: ProviderName, settingName: SettingName): DisplayInfo => {
|
||||
if (settingName === 'apiKey') {
|
||||
return {
|
||||
title: 'API Key',
|
||||
type: 'string',
|
||||
placeholder: providerName === 'anthropic' ? 'sk-ant-key...' : // sk-ant-api03-key
|
||||
providerName === 'openAI' ? 'sk-proj-key...' :
|
||||
providerName === 'openRouter' ? 'sk-or-key...' : // sk-or-v1-key
|
||||
|
|
@ -221,7 +219,6 @@ export const displayInfoOfSettingName = (providerName: ProviderName, settingName
|
|||
title: providerName === 'ollama' ? 'Your Ollama endpoint' :
|
||||
providerName === 'openAICompatible' ? 'baseURL' // (do not include /chat/completions)
|
||||
: '(never)',
|
||||
type: 'string',
|
||||
placeholder: providerName === 'ollama' ? voidProviderDefaults.ollama.endpoint
|
||||
: providerName === 'openAICompatible' ? 'https://my-website.com/v1'
|
||||
: '(never)',
|
||||
|
|
@ -229,15 +226,13 @@ export const displayInfoOfSettingName = (providerName: ProviderName, settingName
|
|||
}
|
||||
else if (settingName === 'enabled') {
|
||||
return {
|
||||
title: 'Enabled?',
|
||||
type: 'boolean',
|
||||
title: '(never)',
|
||||
placeholder: '(never)',
|
||||
}
|
||||
}
|
||||
else if (settingName === 'models') {
|
||||
return {
|
||||
title: 'Available Models',
|
||||
type: '(never)',
|
||||
title: '(never)',
|
||||
placeholder: '(never)',
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import { mountFnGenerator } from '../util/mountFnGenerator.js'
|
|||
// import { SidebarSettings } from './SidebarSettings.js';
|
||||
|
||||
|
||||
import { useSidebarState } from '../util/services.js';
|
||||
import { useIsDark, useSidebarState } from '../util/services.js';
|
||||
// import { SidebarThreadSelector } from './SidebarThreadSelector.js';
|
||||
// import { SidebarChat } from './SidebarChat.js';
|
||||
|
||||
|
|
@ -17,12 +17,12 @@ import { SidebarThreadSelector } from './SidebarThreadSelector.js';
|
|||
import { SidebarChat } from './SidebarChat.js';
|
||||
import ErrorBoundary from './ErrorBoundary.js';
|
||||
|
||||
export const Sidebar = () => {
|
||||
export const Sidebar = ({ className }: { className: string }) => {
|
||||
const sidebarState = useSidebarState()
|
||||
const { isHistoryOpen, currentTab: tab } = sidebarState
|
||||
|
||||
// className='@@void-scope'
|
||||
return <div className='@@void-scope' style={{ width: '100%', height: '100%' }}>
|
||||
const isDark = useIsDark()
|
||||
return <div className={`@@void-scope ${isDark ? 'dark' : ''}`} style={{ width: '100%', height: '100%' }}>
|
||||
<div className={`flex flex-col px-2 py-2 w-full h-full`}>
|
||||
|
||||
{/* <span onClick={() => {
|
||||
|
|
|
|||
|
|
@ -21,9 +21,8 @@ import { ErrorDisplay } from './ErrorDisplay.js';
|
|||
import { OnError, ServiceSendLLMMessageParams } from '../../../../../../../platform/void/common/llmMessageTypes.js';
|
||||
import { getCmdKey } from '../../../helpers/getCmdKey.js'
|
||||
import { HistoryInputBox, InputBox } from '../../../../../../../base/browser/ui/inputbox/inputBox.js';
|
||||
import { VoidInputBox, VoidScrollableElt } from '../util/inputs.js';
|
||||
import { VoidInputBox } from '../util/inputs.js';
|
||||
import { ModelDropdown } from '../void-settings-tsx/ModelDropdown.js';
|
||||
import { ScrollbarVisibility } from '../../../../../../../base/common/scrollable.js';
|
||||
|
||||
|
||||
const IconX = ({ size, className = '' }: { size: number, className?: string }) => {
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
* Void Editor additions licensed under the AGPL 3.0 License.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import React, { useCallback, useEffect, useRef } from 'react';
|
||||
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { useService } from './services.js';
|
||||
import { InputBox } from '../../../../../../../base/browser/ui/inputbox/inputBox.js';
|
||||
import { defaultInputBoxStyles, defaultSelectBoxStyles } from '../../../../../../../platform/theme/browser/defaultStyles.js';
|
||||
|
|
@ -145,21 +145,32 @@ export const VoidSelectBox = <T,>({ onChangeSelection, onCreateInstance, selectB
|
|||
};
|
||||
|
||||
|
||||
export const VoidScrollableElt = ({ options, children }: { options: ScrollableElementCreationOptions, children: React.ReactNode }) => {
|
||||
// export const VoidScrollableElt = ({ options, children }: { options: ScrollableElementCreationOptions, children: React.ReactNode }) => {
|
||||
// const instanceRef = useRef<DomScrollableElement | null>(null);
|
||||
// const [childrenPortal, setChildrenPortal] = useState<React.ReactNode | null>(null)
|
||||
|
||||
return <WidgetComponent
|
||||
ctor={DomScrollableElement}
|
||||
propsFn={useCallback((container) => {
|
||||
return [container, options] as const;
|
||||
}, [options])}
|
||||
onCreateInstance={useCallback(() => { return [] }, [])}
|
||||
dispose={useCallback((instance: DomScrollableElement) => {
|
||||
console.log('calling dispose!!!!')
|
||||
// instance.dispose();
|
||||
// instance.getDomNode().remove()
|
||||
}, [])}
|
||||
>abcdefg</WidgetComponent>
|
||||
}
|
||||
// return <>
|
||||
// <WidgetComponent
|
||||
// ctor={DomScrollableElement}
|
||||
// propsFn={useCallback((container) => {
|
||||
// return [container, options] as const;
|
||||
// }, [options])}
|
||||
// onCreateInstance={useCallback((instance: DomScrollableElement) => {
|
||||
// instanceRef.current = instance;
|
||||
// setChildrenPortal(createPortal(children, instance.getDomNode()))
|
||||
// return []
|
||||
// }, [setChildrenPortal, children])}
|
||||
// dispose={useCallback((instance: DomScrollableElement) => {
|
||||
// console.log('calling dispose!!!!')
|
||||
// // instance.dispose();
|
||||
// // instance.getDomNode().remove()
|
||||
// }, [])}
|
||||
// >{children}</WidgetComponent>
|
||||
|
||||
// {childrenPortal}
|
||||
|
||||
// </>
|
||||
// }
|
||||
|
||||
// export const VoidSelectBox = <T,>({ onChangeSelection, initVal, selectBoxRef, options }: {
|
||||
// initVal: T;
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import { _registerServices } from './services.js';
|
|||
import { ReactServicesType } from '../../../helpers/reactServicesHelper.js';
|
||||
|
||||
|
||||
export const mountFnGenerator = (Component: React.FC) => (rootElement: HTMLElement, services: ReactServicesType) => {
|
||||
export const mountFnGenerator = (Component: (params: any) => React.ReactNode) => (rootElement: HTMLElement, services: ReactServicesType) => {
|
||||
if (typeof document === 'undefined') {
|
||||
console.error('index.tsx error: document was undefined')
|
||||
return
|
||||
|
|
@ -17,8 +17,9 @@ export const mountFnGenerator = (Component: React.FC) => (rootElement: HTMLEleme
|
|||
|
||||
const disposables = _registerServices(services)
|
||||
|
||||
|
||||
const root = ReactDOM.createRoot(rootElement)
|
||||
root.render(<Component />);
|
||||
root.render(<Component />); // tailwind dark theme indicator
|
||||
|
||||
return disposables
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import { IDisposable } from '../../../../../../../base/common/lifecycle.js'
|
|||
import { ReactServicesType } from '../../../helpers/reactServicesHelper.js'
|
||||
import { VoidSidebarState } from '../../../sidebarStateService.js'
|
||||
import { VoidSettingsState } from '../../../../../../../platform/void/common/voidSettingsService.js'
|
||||
import { ColorScheme } from '../../../../../../../platform/theme/common/theme.js'
|
||||
|
||||
|
||||
// normally to do this you'd use a useEffect that calls .onDidChangeState(), but useEffect mounts too late and misses initial state changes
|
||||
|
|
@ -31,7 +32,8 @@ const settingsStateListeners: Set<(s: VoidSettingsState) => void> = new Set()
|
|||
let refreshModelState: RefreshModelState
|
||||
const refreshModelStateListeners: Set<(s: RefreshModelState) => void> = new Set()
|
||||
|
||||
|
||||
let colorThemeState: ColorScheme
|
||||
const colorThemeStateListeners: Set<(s: ColorScheme) => void> = new Set()
|
||||
|
||||
// must call this before you can use any of the hooks below
|
||||
// this should only be called ONCE! this is the only place you don't need to dispose onDidChange. If you use state.onDidChange anywhere else, make sure to dispose it!
|
||||
|
|
@ -48,7 +50,7 @@ export const _registerServices = (services_: ReactServicesType) => {
|
|||
wasCalled = true
|
||||
|
||||
services = services_
|
||||
const { sidebarStateService, settingsStateService, threadsStateService, refreshModelService } = services
|
||||
const { sidebarStateService, settingsStateService, threadsStateService, refreshModelService, themeService } = services
|
||||
|
||||
sidebarState = sidebarStateService.state
|
||||
disposables.push(
|
||||
|
|
@ -82,6 +84,14 @@ export const _registerServices = (services_: ReactServicesType) => {
|
|||
})
|
||||
)
|
||||
|
||||
colorThemeState = themeService.getColorTheme().type
|
||||
disposables.push(
|
||||
themeService.onDidColorThemeChange(theme => {
|
||||
colorThemeState = theme.type
|
||||
colorThemeStateListeners.forEach(l => l(colorThemeState))
|
||||
})
|
||||
)
|
||||
|
||||
return disposables
|
||||
}
|
||||
|
||||
|
|
@ -139,3 +149,18 @@ export const useRefreshModelState = () => {
|
|||
|
||||
|
||||
|
||||
|
||||
|
||||
export const useIsDark = () => {
|
||||
const [s, ss] = useState(colorThemeState)
|
||||
useEffect(() => {
|
||||
ss(colorThemeState)
|
||||
colorThemeStateListeners.add(ss)
|
||||
return () => { colorThemeStateListeners.delete(ss) }
|
||||
}, [ss])
|
||||
|
||||
// s is the theme, return isDark instead of s
|
||||
const isDark = s === ColorScheme.DARK || s === ColorScheme.HIGH_CONTRAST_DARK
|
||||
return isDark
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { InputBox } from '../../../../../../../base/browser/ui/inputbox/inputBox
|
|||
import { ProviderName, SettingName, displayInfoOfSettingName, titleOfProviderName, providerNames, ModelInfo } from '../../../../../../../platform/void/common/voidSettingsTypes.js'
|
||||
import ErrorBoundary from '../sidebar-tsx/ErrorBoundary.js'
|
||||
import { VoidInputBox } from '../util/inputs.js'
|
||||
import { useRefreshModelState, useService, useSettingsState } from '../util/services.js'
|
||||
import { useIsDark, useRefreshModelState, useService, useSettingsState } from '../util/services.js'
|
||||
|
||||
|
||||
|
||||
|
|
@ -44,7 +44,7 @@ export const ModelMenu = () => {
|
|||
{modelDump.map(m => {
|
||||
const { isHidden, isDefault, modelName, providerName } = m
|
||||
|
||||
return <div key={`${modelName}${providerName}`} className='flex items-center justify-between gap-4'>
|
||||
return <div key={`${modelName}${providerName}`} className='flex items-center justify-between gap-4 hover:bg-black/10 dark:hover:bg-white/10'>
|
||||
<span>{modelName} {isDefault ? '' : '(custom)'}</span>
|
||||
<span>{providerName}</span>
|
||||
<span onClick={() => { settingsStateService.toggleModelHidden(providerName, modelName) }}>{isHidden ? 'hidden' : '✅'}</span>
|
||||
|
|
@ -59,7 +59,7 @@ export const ModelMenu = () => {
|
|||
|
||||
const ProviderSetting = ({ providerName, settingName }: { providerName: ProviderName, settingName: SettingName }) => {
|
||||
|
||||
const { title, type, placeholder } = displayInfoOfSettingName(providerName, settingName)
|
||||
const { title, placeholder } = displayInfoOfSettingName(providerName, settingName)
|
||||
const voidSettingsService = useService('settingsStateService')
|
||||
|
||||
|
||||
|
|
@ -110,7 +110,6 @@ const SettingsForProvider = ({ providerName }: { providerName: ProviderName }) =
|
|||
|
||||
|
||||
export const VoidProviderSettings = () => {
|
||||
|
||||
return <>
|
||||
{providerNames.map(providerName =>
|
||||
<SettingsForProvider key={providerName} providerName={providerName} />
|
||||
|
|
@ -123,7 +122,8 @@ export const VoidProviderSettings = () => {
|
|||
// full settings
|
||||
|
||||
export const Settings = () => {
|
||||
return <div className='@@void-scope'>
|
||||
const isDark = useIsDark()
|
||||
return <div className={`@@void-scope ${isDark ? 'dark' : ''} px-2 lg:px-10`}>
|
||||
<div className='w-full h-full'>
|
||||
|
||||
<div className='max-w-3xl mx-auto'>
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
module.exports = {
|
||||
darkMode: 'selector', // '{prefix-}dark' className is used to identify `dark:`
|
||||
content: ['./src2/**/*.{jsx,tsx}'], // uses these files to decide how to transform the css file
|
||||
theme: {
|
||||
extend: {
|
||||
|
|
|
|||
|
|
@ -76,6 +76,7 @@ class VoidSettingsPane extends EditorPane {
|
|||
// parent.style.overflow = 'auto'
|
||||
parent.style.userSelect = 'text'
|
||||
|
||||
|
||||
// gets set immediately
|
||||
this.instantiationService.invokeFunction(accessor => {
|
||||
const services = getReactServices(accessor)
|
||||
|
|
@ -91,6 +92,9 @@ class VoidSettingsPane extends EditorPane {
|
|||
container.style.width = `${dimension.width}px`;
|
||||
container.style.height = `${dimension.height}px`;
|
||||
}
|
||||
|
||||
override get minimumWidth() { return 512 }
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue