mirror of
https://github.com/voideditor/void
synced 2026-05-24 09:58:23 +00:00
fix services in React
This commit is contained in:
parent
50d21828c2
commit
dbea46ba32
9 changed files with 94 additions and 38 deletions
|
|
@ -1,6 +1,7 @@
|
||||||
import React, { JSX, useCallback, useEffect, useState } from 'react'
|
import React, { JSX, useCallback, useEffect, useState } from 'react'
|
||||||
import { marked, MarkedToken, Token } from 'marked'
|
import { marked, MarkedToken, Token } from 'marked'
|
||||||
import { BlockCode } from './BlockCode.js'
|
import { BlockCode } from './BlockCode.js'
|
||||||
|
import { useService } from '../util/contextForServices.js'
|
||||||
|
|
||||||
|
|
||||||
enum CopyButtonState {
|
enum CopyButtonState {
|
||||||
|
|
@ -13,6 +14,7 @@ const COPY_FEEDBACK_TIMEOUT = 1000 // amount of time to say 'Copied!'
|
||||||
|
|
||||||
const CodeButtonsOnHover = ({ diffRepr: text }: { diffRepr: string }) => {
|
const CodeButtonsOnHover = ({ diffRepr: text }: { diffRepr: string }) => {
|
||||||
const [copyButtonState, setCopyButtonState] = useState(CopyButtonState.Copy)
|
const [copyButtonState, setCopyButtonState] = useState(CopyButtonState.Copy)
|
||||||
|
const inlineDiffService = useService('inlineDiffService')
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (copyButtonState !== CopyButtonState.Copy) {
|
if (copyButtonState !== CopyButtonState.Copy) {
|
||||||
|
|
@ -43,7 +45,8 @@ const CodeButtonsOnHover = ({ diffRepr: text }: { diffRepr: string }) => {
|
||||||
<button
|
<button
|
||||||
className="btn btn-secondary btn-sm border border-vscode-input-border rounded"
|
className="btn btn-secondary btn-sm border border-vscode-input-border rounded"
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
getVSCodeAPI().postMessage({ type: "applyChanges", diffRepr: text })
|
|
||||||
|
inlineDiffService.startStreaming('ctrl+l', text)
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Apply
|
Apply
|
||||||
|
|
|
||||||
|
|
@ -8,9 +8,10 @@ import { useSidebarState } from '../util/contextForServices.js';
|
||||||
|
|
||||||
import '../styles.css'
|
import '../styles.css'
|
||||||
import { SidebarThreadSelector } from './SidebarThreadSelector.js';
|
import { SidebarThreadSelector } from './SidebarThreadSelector.js';
|
||||||
|
import { SidebarChat } from './SidebarChat.js';
|
||||||
|
|
||||||
const Sidebar = () => {
|
const Sidebar = () => {
|
||||||
const [sidebarState, sidebarStateService] = useSidebarState()
|
const sidebarState = useSidebarState()
|
||||||
const { isHistoryOpen, currentTab: tab } = sidebarState
|
const { isHistoryOpen, currentTab: tab } = sidebarState
|
||||||
|
|
||||||
return <div className='@@void-scope'>
|
return <div className='@@void-scope'>
|
||||||
|
|
@ -27,7 +28,7 @@ const Sidebar = () => {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={`${tab === 'chat' ? '' : 'hidden'}`}>
|
<div className={`${tab === 'chat' ? '' : 'hidden'}`}>
|
||||||
{/* <SidebarChat /> */}
|
<SidebarChat />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={`${tab === 'settings' ? '' : 'hidden'}`}>
|
<div className={`${tab === 'settings' ? '' : 'hidden'}`}>
|
||||||
|
|
|
||||||
|
|
@ -119,17 +119,16 @@ export const SidebarChat = () => {
|
||||||
|
|
||||||
// ----- HIGHER STATE -----
|
// ----- HIGHER STATE -----
|
||||||
// sidebar state
|
// sidebar state
|
||||||
const [sidebarState, sidebarStateService] = useSidebarState()
|
const sidebarStateService = useService('sidebarStateService')
|
||||||
const { isHistoryOpen, currentTab: tab } = sidebarState
|
|
||||||
sidebarStateService.onDidFocusChat(() => { chatInputRef.current?.focus() })
|
sidebarStateService.onDidFocusChat(() => { chatInputRef.current?.focus() })
|
||||||
sidebarStateService.onDidBlurChat(() => { chatInputRef.current?.blur() })
|
sidebarStateService.onDidBlurChat(() => { chatInputRef.current?.blur() })
|
||||||
|
|
||||||
// config state
|
// config state
|
||||||
const [configState, configStateService] = useConfigState()
|
const configState = useConfigState()
|
||||||
const { voidConfig } = configState
|
const { voidConfig } = configState
|
||||||
|
|
||||||
// threads state
|
// threads state
|
||||||
const [threadsState, threadsStateService] = useThreadsState()
|
const threadsStateService = useService('threadsStateService')
|
||||||
|
|
||||||
// ----- SIDEBAR CHAT state (local) -----
|
// ----- SIDEBAR CHAT state (local) -----
|
||||||
// state of current message
|
// state of current message
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { useConfigState } from '../util/contextForServices.js';
|
import { useConfigState, useService } from '../util/contextForServices.js';
|
||||||
import { IVoidConfigStateService, nonDefaultConfigFields, PartialVoidConfig, VoidConfig, VoidConfigField, VoidConfigInfo, SetFieldFnType, ConfigState } from '../../../registerConfig.js';
|
import { IVoidConfigStateService, nonDefaultConfigFields, PartialVoidConfig, VoidConfig, VoidConfigField, VoidConfigInfo, SetFieldFnType, ConfigState } from '../../../registerConfig.js';
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -61,7 +61,9 @@ const SettingOfFieldAndParam = ({ field, param, configState, configStateService
|
||||||
|
|
||||||
export const SidebarSettings = () => {
|
export const SidebarSettings = () => {
|
||||||
|
|
||||||
const [configState, configStateService] = useConfigState()
|
const configState = useConfigState()
|
||||||
|
const configStateService = useService('configStateService')
|
||||||
|
|
||||||
const { voidConfig } = configState
|
const { voidConfig } = configState
|
||||||
const current_field = voidConfig.default['whichApi'] as VoidConfigField
|
const current_field = voidConfig.default['whichApi'] as VoidConfigField
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
// import React from "react";
|
import React from "react";
|
||||||
import { useSidebarState, useThreadsState } from '../util/contextForServices.js';
|
import { useService, useThreadsState } from '../util/contextForServices.js';
|
||||||
|
|
||||||
|
|
||||||
const truncate = (s: string) => {
|
const truncate = (s: string) => {
|
||||||
|
|
@ -12,9 +12,9 @@ const truncate = (s: string) => {
|
||||||
|
|
||||||
|
|
||||||
export const SidebarThreadSelector = () => {
|
export const SidebarThreadSelector = () => {
|
||||||
const [threadsState, threadsStateService] = useThreadsState()
|
const threadsState = useThreadsState()
|
||||||
|
const threadsStateService = useService('threadsStateService')
|
||||||
const [sidebarState, sidebarStateService] = useSidebarState()
|
const sidebarStateService = useService('sidebarStateService')
|
||||||
|
|
||||||
const { allThreads } = threadsState
|
const { allThreads } = threadsState
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,20 @@
|
||||||
import React, { createContext, useContext, useEffect, useState } from 'react'
|
import React, { createContext, useContext, useEffect, useState } from 'react'
|
||||||
import { ReactServicesType, VoidSidebarState } from '../../../registerSidebar.js';
|
import { ReactServicesType, VoidSidebarState } from '../../../registerSidebar.js';
|
||||||
import { ConfigState } from '../../../registerConfig.js';
|
import { ConfigState } from '../../../registerConfig.js';
|
||||||
|
import { ThreadsState } from '../../../registerThreads.js';
|
||||||
|
|
||||||
const AccessorContext = createContext<ReactServicesType | undefined>(undefined)
|
const AccessorContext = createContext<ReactServicesType | undefined>(undefined)
|
||||||
|
|
||||||
export const AccessorProvider = ({ children, services }: { children: React.ReactNode; services: ReactServicesType }) => {
|
export const AccessorProvider = ({ children, services }: { children: React.ReactNode; services: ReactServicesType }) => {
|
||||||
|
registerStateListeners(services)
|
||||||
return <AccessorContext.Provider value={services}>
|
return <AccessorContext.Provider value={services}>
|
||||||
{children}
|
{children}
|
||||||
</AccessorContext.Provider>
|
</AccessorContext.Provider>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// -- services --
|
||||||
|
|
||||||
const useServices = (): ReactServicesType => {
|
const useServices = (): ReactServicesType => {
|
||||||
const context = useContext(AccessorContext)
|
const context = useContext(AccessorContext)
|
||||||
if (context === undefined) {
|
if (context === undefined) {
|
||||||
|
|
@ -18,33 +23,76 @@ const useServices = (): ReactServicesType => {
|
||||||
return context;
|
return context;
|
||||||
}
|
}
|
||||||
|
|
||||||
// -- these use useServices() --
|
export const useService = <T extends keyof ReactServicesType,>(serviceName: T) => {
|
||||||
|
const services = useServices()
|
||||||
|
return services[serviceName] as ReactServicesType[T]
|
||||||
|
}
|
||||||
|
|
||||||
|
// -- state of services --
|
||||||
|
// normally to do this you'd use a useEffect that calls .onDidChangeState(), but here, useEffect mounts too late and misses initial state changes
|
||||||
|
let sidebarState: VoidSidebarState | null = null
|
||||||
|
let configState: ConfigState | null = null
|
||||||
|
let threadsState: ThreadsState | null = null
|
||||||
|
|
||||||
|
const sidebarStateListeners: Set<(s: VoidSidebarState) => void> = new Set()
|
||||||
|
const configStateListeners: Set<(s: ConfigState) => void> = new Set()
|
||||||
|
const threadsStateListeners: Set<(s: ThreadsState) => void> = new Set()
|
||||||
|
|
||||||
|
let isRegistered = false
|
||||||
|
const registerStateListeners = (context: ReactServicesType) => {
|
||||||
|
if (isRegistered) return
|
||||||
|
isRegistered = true
|
||||||
|
|
||||||
|
const { sidebarStateService, configStateService, threadsStateService, } = context
|
||||||
|
|
||||||
|
sidebarState = sidebarStateService.state
|
||||||
|
sidebarStateService.onDidChangeState(() => {
|
||||||
|
sidebarState = sidebarStateService.state
|
||||||
|
sidebarStateListeners.forEach(l => l(sidebarState))
|
||||||
|
})
|
||||||
|
|
||||||
|
configState = configStateService.state
|
||||||
|
configStateService.onDidChangeState(() => {
|
||||||
|
configState = configStateService.state
|
||||||
|
configStateListeners.forEach(l => l(configState))
|
||||||
|
})
|
||||||
|
|
||||||
|
threadsState = threadsStateService.state
|
||||||
|
threadsStateService.onDidChangeCurrentThread(() => {
|
||||||
|
threadsState = threadsStateService.state
|
||||||
|
threadsStateListeners.forEach(l => l(threadsState))
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// track the config state using React state so visual updates happen
|
// track the config state using React state so visual updates happen
|
||||||
export const useSidebarState = () => {
|
export const useSidebarState = () => {
|
||||||
const { sidebarStateService } = useServices()
|
const [s, ss] = useState(sidebarState)
|
||||||
const [sidebarState, setSideBarState] = useState<VoidSidebarState>(sidebarStateService.state)
|
useEffect(() => {
|
||||||
useEffect(() => { sidebarStateService.onDidChangeState(() => setSideBarState(sidebarStateService.state)) }, [sidebarStateService])
|
ss(sidebarState)
|
||||||
return [sidebarState, sidebarStateService] as const
|
sidebarStateListeners.add(ss)
|
||||||
|
return () => { sidebarStateListeners.delete(ss) }
|
||||||
|
}, [ss])
|
||||||
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useConfigState = () => {
|
export const useConfigState = () => {
|
||||||
const { configStateService } = useServices()
|
const [s, ss] = useState(configState)
|
||||||
const [configState, setConfigState] = useState<ConfigState>(configStateService.state)
|
useEffect(() => {
|
||||||
useEffect(() => { configStateService.onDidChangeState(() => setConfigState(configStateService.state)) }, [configStateService])
|
ss(configState)
|
||||||
return [configState, configStateService] as const
|
configStateListeners.add(ss)
|
||||||
|
return () => { configStateListeners.delete(ss) }
|
||||||
|
}, [ss])
|
||||||
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useThreadsState = () => {
|
export const useThreadsState = () => {
|
||||||
const { threadsStateService } = useServices()
|
const [s, ss] = useState(threadsState)
|
||||||
const [threadsState, setThreadsState] = useState(threadsStateService.state)
|
useEffect(() => {
|
||||||
useEffect(() => { threadsStateService.onDidChangeCurrentThread(() => setThreadsState(threadsStateService.state)) }, [threadsStateService])
|
ss(threadsState)
|
||||||
return [threadsState, threadsStateService] as const
|
threadsStateListeners.add(ss)
|
||||||
|
return () => { threadsStateListeners.delete(ss) }
|
||||||
|
}, [ss])
|
||||||
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
// -- other services --
|
|
||||||
type PublicServiceName = 'fileService'
|
|
||||||
export const useService = (serviceName: Extract<keyof ReactServicesType, PublicServiceName>) => {
|
|
||||||
const services = useServices()
|
|
||||||
return services[serviceName]
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,4 @@
|
||||||
|
|
||||||
import posthog from 'posthog-js';
|
import posthog from 'posthog-js';
|
||||||
|
|
||||||
|
|
||||||
export { posthog }
|
export { posthog }
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -93,6 +93,8 @@ type StreamingState = {
|
||||||
|
|
||||||
export interface IInlineDiffsService {
|
export interface IInlineDiffsService {
|
||||||
readonly _serviceBrand: undefined;
|
readonly _serviceBrand: undefined;
|
||||||
|
startStreaming(type: 'ctrl+k' | 'ctrl+l', userMessage: string): void;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const IInlineDiffsService = createDecorator<IInlineDiffsService>('inlineDiffsService');
|
export const IInlineDiffsService = createDecorator<IInlineDiffsService>('inlineDiffsService');
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,7 @@ import mountFn from './react/out/sidebar-tsx/Sidebar.js';
|
||||||
|
|
||||||
import { IVoidConfigStateService } from './registerConfig.js';
|
import { IVoidConfigStateService } from './registerConfig.js';
|
||||||
import { IFileService } from '../../../../platform/files/common/files.js';
|
import { IFileService } from '../../../../platform/files/common/files.js';
|
||||||
|
import { IInlineDiffsService } from './registerInlineDiffs.js';
|
||||||
// import { IClipboardService } from '../../../../platform/clipboard/common/clipboardService.js';
|
// import { IClipboardService } from '../../../../platform/clipboard/common/clipboardService.js';
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -59,6 +60,7 @@ export type ReactServicesType = {
|
||||||
configStateService: IVoidConfigStateService;
|
configStateService: IVoidConfigStateService;
|
||||||
threadsStateService: IThreadHistoryService;
|
threadsStateService: IThreadHistoryService;
|
||||||
fileService: IFileService;
|
fileService: IFileService;
|
||||||
|
inlineDiffService: IInlineDiffsService;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------- Define viewpane ----------
|
// ---------- Define viewpane ----------
|
||||||
|
|
@ -101,6 +103,7 @@ class VoidSidebarViewPane extends ViewPane {
|
||||||
sidebarStateService: accessor.get(IVoidSidebarStateService),
|
sidebarStateService: accessor.get(IVoidSidebarStateService),
|
||||||
threadsStateService: accessor.get(IThreadHistoryService),
|
threadsStateService: accessor.get(IThreadHistoryService),
|
||||||
fileService: accessor.get(IFileService),
|
fileService: accessor.get(IFileService),
|
||||||
|
inlineDiffService: accessor.get(IInlineDiffsService),
|
||||||
}
|
}
|
||||||
mountFn(root, services);
|
mountFn(root, services);
|
||||||
});
|
});
|
||||||
|
|
@ -288,7 +291,7 @@ registerAction2(class extends Action2 {
|
||||||
}
|
}
|
||||||
async run(accessor: ServicesAccessor): Promise<void> {
|
async run(accessor: ServicesAccessor): Promise<void> {
|
||||||
const stateService = accessor.get(IVoidSidebarStateService)
|
const stateService = accessor.get(IVoidSidebarStateService)
|
||||||
stateService.setState({ isHistoryOpen: !stateService.state.isHistoryOpen })
|
stateService.setState({ isHistoryOpen: !stateService.state.isHistoryOpen, currentTab: 'chat' })
|
||||||
stateService.fireBlurChat()
|
stateService.fireBlurChat()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue