diff --git a/src/project-settings/assets/compiler/features/CompilerConfigurationView.tsx b/src/project-settings/assets/compiler/features/CompilerConfigurationView.tsx index 28d702f9..fa60a71f 100644 --- a/src/project-settings/assets/compiler/features/CompilerConfigurationView.tsx +++ b/src/project-settings/assets/compiler/features/CompilerConfigurationView.tsx @@ -11,7 +11,7 @@ import "@vscode-elements/elements/dist/vscode-table-row/index.js"; import "@vscode-elements/elements/dist/vscode-table-cell/index.js"; -import { Dispatch, useEffect } from "react"; +import { Dispatch, useCallback, useEffect, useRef } from "react"; import { useDispatch, useSelector } from "react-redux"; import { updateCompilerSettings, updateAvailableComplianceLevels } from "./compilerConfigurationViewSlice"; import { CompilerRequest } from "../../vscode/utils"; @@ -54,6 +54,10 @@ const CompilerConfigurationView = (): JSX.Element | null => { Number(sourceLevel) > currentJdkComplianceLevel || Number(targetLevel) > currentJdkComplianceLevel; + const complianceRef = useRef(null); + const sourceRef = useRef(null); + const targetRef = useRef(null); + const onMessage = (event: any) => { const message = event.data; if (message.command === "compiler.onDidGetAvailableComplianceLevels") { @@ -73,7 +77,60 @@ const CompilerConfigurationView = (): JSX.Element | null => { } }, []); - const jdkLevels = (selectedLevel: string, label: string, onClick: (value: string) => void) => { + // The vscode-single-select element renders its options in the shadow DOM and + // only emits a native `change` event on the host element, so the click handlers + // on the slotted vscode-option children never fire. Listen to `change` instead. + const useSelectChange = (ref: React.RefObject, onChange: (value: string) => void) => { + useEffect(() => { + const el = ref.current; + if (!el) { + return; + } + const handler = (e: Event) => { + const value = (e.target as any).value; + if (value) { + onChange(value); + } + }; + el.addEventListener("change", handler); + return () => el.removeEventListener("change", handler); + }, [ref, onChange]); + }; + + const onChangeComplianceLevel = useCallback((value: string) => { + dispatch(updateCompilerSettings({ activeProjectIndex, complianceLevel: value })); + }, [dispatch, activeProjectIndex]); + const onChangeSourceLevel = useCallback((value: string) => { + dispatch(updateCompilerSettings({ activeProjectIndex, sourceLevel: value })); + }, [dispatch, activeProjectIndex]); + const onChangeTargetLevel = useCallback((value: string) => { + dispatch(updateCompilerSettings({ activeProjectIndex, targetLevel: value })); + }, [dispatch, activeProjectIndex]); + + useSelectChange(complianceRef, onChangeComplianceLevel); + useSelectChange(sourceRef, onChangeSourceLevel); + useSelectChange(targetRef, onChangeTargetLevel); + + // Keep the rendered selection in sync with the redux state. Set the value + // unconditionally (even when the level is "") so the dropdown always reflects + // state instead of keeping the previous project's selection. + useEffect(() => { + if (complianceRef.current) { + (complianceRef.current as any).value = complianceLevel; + } + }, [complianceLevel, availableComplianceLevels]); + useEffect(() => { + if (sourceRef.current) { + (sourceRef.current as any).value = sourceLevel; + } + }, [sourceLevel, availableComplianceLevels]); + useEffect(() => { + if (targetRef.current) { + (targetRef.current as any).value = targetLevel; + } + }, [targetLevel, availableComplianceLevels]); + + const jdkLevels = (selectedLevel: string, label: string) => { return availableComplianceLevels.map((level) => { return ( @@ -81,8 +138,7 @@ const CompilerConfigurationView = (): JSX.Element | null => { className="setting-section-option" key={`${label}-${level}`} value={level} - selected={level === selectedLevel} - onClick={() => onClick(level)} + selected={level === selectedLevel ? true : undefined} > {level} @@ -104,27 +160,6 @@ const CompilerConfigurationView = (): JSX.Element | null => { })); }; - const onClickComplianceLevel = (value: string) => { - dispatch(updateCompilerSettings({ - activeProjectIndex, - complianceLevel: value - })); - }; - - const onClickSourceLevel = (value: string) => { - dispatch(updateCompilerSettings({ - activeProjectIndex, - sourceLevel: value - })); - }; - - const onClickTargetLevel = (value: string) => { - dispatch(updateCompilerSettings({ - activeProjectIndex, - targetLevel: value - })); - }; - const onClickGenerateDebugInfo = (e: any) => { dispatch(updateCompilerSettings({ activeProjectIndex, @@ -158,8 +193,8 @@ const CompilerConfigurationView = (): JSX.Element | null => { Bytecode version: - - {jdkLevels(complianceLevel, "compliance", onClickComplianceLevel)} + + {jdkLevels(complianceLevel, "compliance")} @@ -168,8 +203,8 @@ const CompilerConfigurationView = (): JSX.Element | null => { Source compatibility: - - {jdkLevels(sourceLevel, "source", onClickSourceLevel)} + + {jdkLevels(sourceLevel, "source")} @@ -178,8 +213,8 @@ const CompilerConfigurationView = (): JSX.Element | null => { Target compatibility: - - {jdkLevels(targetLevel, "target", onClickTargetLevel)} + + {jdkLevels(targetLevel, "target")} diff --git a/src/project-settings/assets/mainpage/features/component/ProjectSelector.tsx b/src/project-settings/assets/mainpage/features/component/ProjectSelector.tsx index cc1aedf9..085627ce 100644 --- a/src/project-settings/assets/mainpage/features/component/ProjectSelector.tsx +++ b/src/project-settings/assets/mainpage/features/component/ProjectSelector.tsx @@ -4,7 +4,7 @@ import "@vscode-elements/elements/dist/vscode-single-select/index.js"; import "@vscode-elements/elements/dist/vscode-option/index.js"; -import { useEffect } from "react"; +import { useCallback, useEffect, useRef } from "react"; import { useSelector, useDispatch } from "react-redux"; import { ProjectInfo } from "../../../../types"; import { Dispatch } from "@reduxjs/toolkit"; @@ -17,10 +17,37 @@ const ProjectSelector = (): JSX.Element | null => { const projects: ProjectInfo[] = useSelector((state: any) => state.commonConfig.data.projects); const dispatch: Dispatch = useDispatch(); + const selectRef = useRef(null); - const handleActiveProjectChange = (index: number) => { + const handleActiveProjectChange = useCallback((index: number) => { dispatch(activeProjectChange(index)); - }; + }, [dispatch]); + + // The vscode-single-select element renders its options in the shadow DOM and + // only emits a native `change` event on the host element, so the click handlers + // on the slotted vscode-option children never fire. Listen to `change` instead. + useEffect(() => { + const el = selectRef.current; + if (!el) { + return; + } + const onChange = (e: Event) => { + const index = (e.target as any).selectedIndex; + if (typeof index === "number" && index >= 0) { + handleActiveProjectChange(index); + } + }; + el.addEventListener("change", onChange); + return () => el.removeEventListener("change", onChange); + }, [handleActiveProjectChange]); + + // Keep the rendered selection in sync with the active project index. + useEffect(() => { + const el = selectRef.current; + if (el && projects.length > 0) { + (el as any).selectedIndex = activeProjectIndex; + } + }, [activeProjectIndex, projects]); useEffect(() => { if (projects.length === 0) { @@ -38,7 +65,12 @@ const ProjectSelector = (): JSX.Element | null => { } return ( - handleActiveProjectChange(index)}> + {project.name} ); @@ -48,7 +80,7 @@ const ProjectSelector = (): JSX.Element | null => {
Project: - + {projectSelections}