1- import { Suspense , useCallback , useEffect , useMemo , useState } from "react" ;
1+ import {
2+ Suspense ,
3+ useCallback ,
4+ useEffect ,
5+ useMemo ,
6+ useRef ,
7+ useState ,
8+ } from "react" ;
29import { graphql , useLazyLoadQuery , useMutation } from "react-relay" ;
310
411import {
@@ -75,6 +82,8 @@ export const ExamplesSplitMenu = ({
7582 const [ mode , setMode ] = useState < "filter" | "apply" | "create" > ( ( ) =>
7683 getInitialMode ( selectedExampleIds )
7784 ) ;
85+ const [ isMenuOpen , setIsMenuOpen ] = useState ( false ) ; // used to keep the menu open when a split is applied
86+ const shouldKeepMenuOpenRef = useRef ( false ) ; // required as menu is no longer multi-select
7887 useEffect ( ( ) => {
7988 setMode ( getInitialMode ( selectedExampleIds ) ) ;
8089 } , [ selectedExampleIds ] ) ;
@@ -92,10 +101,21 @@ export const ExamplesSplitMenu = ({
92101 const selectedPartialExamples = useMemo ( ( ) => {
93102 return selectedExampleIds . map ( ( id ) => examplesCache [ id ] ) . filter ( Boolean ) ;
94103 } , [ selectedExampleIds , examplesCache ] ) ;
104+ const requestKeepMenuOpen = useCallback ( ( ) => {
105+ shouldKeepMenuOpenRef . current = true ;
106+ } , [ ] ) ;
95107
96108 return (
97109 < MenuTrigger
110+ isOpen = { isMenuOpen }
98111 onOpenChange = { ( open ) => {
112+ if ( ! open && shouldKeepMenuOpenRef . current ) {
113+ shouldKeepMenuOpenRef . current = false ;
114+ setIsMenuOpen ( true ) ;
115+ return ;
116+ }
117+ shouldKeepMenuOpenRef . current = false ;
118+ setIsMenuOpen ( open ) ;
99119 if ( ! open ) {
100120 setMode ( getInitialMode ( selectedExampleIds ) ) ;
101121 }
@@ -118,6 +138,7 @@ export const ExamplesSplitMenu = ({
118138 onExampleSelectionChange = { onExampleSelectionChange }
119139 selectedPartialExamples = { selectedPartialExamples }
120140 setMode = { setMode }
141+ onRequestKeepMenuOpen = { requestKeepMenuOpen }
121142 />
122143 ) }
123144 { mode === "create" && (
@@ -144,6 +165,7 @@ const SplitMenu = ({
144165 onSelectionChange,
145166 selectedPartialExamples,
146167 setMode,
168+ onRequestKeepMenuOpen,
147169} : {
148170 selectedSplitIds : string [ ] ;
149171 selectedExampleIds : string [ ] ;
@@ -154,6 +176,7 @@ const SplitMenu = ({
154176 datasetSplits : { id : string ; name : string } [ ] ;
155177 } [ ] ;
156178 setMode : ( mode : "filter" | "apply" | "create" ) => void ;
179+ onRequestKeepMenuOpen : ( ) => void ;
157180} ) => {
158181 const { contains } = useFilter ( { sensitivity : "base" } ) ;
159182 const data = useLazyLoadQuery < ExamplesSplitMenuQuery > (
@@ -174,28 +197,12 @@ const SplitMenu = ({
174197 // fetch when menu is opened, but show cache data first to prevent flickering
175198 { fetchPolicy : "store-and-network" }
176199 ) ;
177- const [ addExamplesToSplits ] = useMutation ( graphql `
178- mutation ExamplesSplitMenuAddDatasetExamplesToDatasetSplitsMutation (
179- $input: AddDatasetExamplesToDatasetSplitsInput !
200+ const [ setExampleSplits ] = useMutation ( graphql `
201+ mutation ExamplesSplitMenuSetDatasetExampleSplitsMutation (
202+ $input: SetDatasetExampleSplitsInput !
180203 ) {
181- addDatasetExamplesToDatasetSplits(input: $input) {
182- examples {
183- id
184- datasetSplits {
185- id
186- name
187- color
188- }
189- }
190- }
191- }
192- ` ) ;
193- const [ removeExamplesFromSplit ] = useMutation ( graphql `
194- mutation ExamplesSplitMenuRemoveDatasetExamplesFromDatasetSplitMutation(
195- $input: RemoveDatasetExamplesFromDatasetSplitsInput!
196- ) {
197- removeDatasetExamplesFromDatasetSplits(input: $input) {
198- examples {
204+ setDatasetExampleSplits(input: $input) {
205+ example {
199206 id
200207 datasetSplits {
201208 id
@@ -207,33 +214,17 @@ const SplitMenu = ({
207214 }
208215 ` ) ;
209216 const onUpdateSplits = useCallback (
210- ( changes : {
211- selectedExampleIds : string [ ] ;
212- addSplitIds ?: string [ ] ;
213- removeSplitIds ?: string [ ] ;
214- } ) => {
215- if ( changes . addSplitIds ) {
216- addExamplesToSplits ( {
217- variables : {
218- input : {
219- exampleIds : changes . selectedExampleIds ,
220- datasetSplitIds : changes . addSplitIds ,
221- } ,
217+ ( changes : { exampleId : string ; splitIds : string [ ] } ) => {
218+ setExampleSplits ( {
219+ variables : {
220+ input : {
221+ exampleId : changes . exampleId ,
222+ datasetSplitIds : changes . splitIds ,
222223 } ,
223- } ) ;
224- }
225- if ( changes . removeSplitIds ) {
226- removeExamplesFromSplit ( {
227- variables : {
228- input : {
229- exampleIds : changes . selectedExampleIds ,
230- datasetSplitIds : changes . removeSplitIds ,
231- } ,
232- } ,
233- } ) ;
234- }
224+ } ,
225+ } ) ;
235226 } ,
236- [ addExamplesToSplits , removeExamplesFromSplit ]
227+ [ setExampleSplits ]
237228 ) ;
238229 const splits = useMemo ( ( ) => {
239230 return data . datasetSplits . edges . map ( ( edge ) => edge . split ) ;
@@ -274,6 +265,7 @@ const SplitMenu = ({
274265 onSelectionChange = { onUpdateSplits }
275266 splits = { splits as Mutable < typeof splits > }
276267 selectedPartialExamples = { selectedPartialExamples }
268+ onRequestKeepMenuOpen = { onRequestKeepMenuOpen }
277269 />
278270 ) }
279271 </ Autocomplete >
@@ -331,17 +323,18 @@ const SplitMenuApplyContent = ({
331323 onSelectionChange,
332324 splits,
333325 selectedPartialExamples,
326+ onRequestKeepMenuOpen,
334327} : {
335328 onSelectionChange : ( changes : {
336- selectedExampleIds : string [ ] ;
337- addSplitIds ?: string [ ] ;
338- removeSplitIds ?: string [ ] ;
329+ exampleId : string ;
330+ splitIds : string [ ] ;
339331 } ) => void ;
340332 splits : { id : string ; name : string ; color : string } [ ] ;
341333 selectedPartialExamples : {
342334 id : string ;
343335 datasetSplits : { id : string ; name : string } [ ] ;
344336 } [ ] ;
337+ onRequestKeepMenuOpen : ( ) => void ;
345338} ) => {
346339 // derive checkbox states for each split based on the selectedPartialExamples
347340 type SplitState = Record < string , "checked" | "indeterminate" | "unchecked" > ;
@@ -368,32 +361,44 @@ const SplitMenuApplyContent = ({
368361 return acc ;
369362 } , { } as SplitState ) ;
370363 } , [ splits , selectedPartialExamples ] ) ;
364+ const handleSplitToggle = useCallback (
365+ ( selectedId : string ) => {
366+ // because the menu is no longer multi-select, we need to keep the menu open when a split is applied
367+ onRequestKeepMenuOpen ( ) ;
368+ for ( const example of selectedPartialExamples ) {
369+ const currentSplitIds = example . datasetSplits . map ( ( s ) => s . id ) ;
370+ let newSplitIds : string [ ] ;
371+
372+ if ( splitStates [ selectedId ] === "checked" ) {
373+ newSplitIds = currentSplitIds . filter ( ( id ) => id !== selectedId ) ;
374+ } else {
375+ newSplitIds = currentSplitIds . includes ( selectedId )
376+ ? currentSplitIds
377+ : [ ...currentSplitIds , selectedId ] ;
378+ }
379+
380+ onSelectionChange ( {
381+ exampleId : example . id ,
382+ splitIds : newSplitIds ,
383+ } ) ;
384+ }
385+ } ,
386+ [
387+ onRequestKeepMenuOpen ,
388+ onSelectionChange ,
389+ selectedPartialExamples ,
390+ splitStates ,
391+ ]
392+ ) ;
371393 return (
372394 < Menu
373395 items = { splits }
374396 renderEmptyState = { ( ) => "No splits found" }
375- // hack to keep the menu open when splits are changed
376- selectedKeys = { [ ] }
377- selectionMode = "multiple"
397+ // NOTE: Menu is no longer multi-select, so we track the menu open state manually
398+ selectionMode = "none"
378399 // ensure that menu items are re-rendered when splitStates changes
379400 dependencies = { [ splitStates ] }
380- // update selection state externally, the menu does not actually know what is selected
381- onSelectionChange = { ( keys ) => {
382- const selectedId = Array . from ( keys as Set < string > ) [ 0 ] ;
383- if ( splitStates [ selectedId ] === "checked" ) {
384- // remove split from all selected examples
385- onSelectionChange ( {
386- selectedExampleIds : selectedPartialExamples . map ( ( e ) => e . id ) ,
387- removeSplitIds : [ selectedId ] ,
388- } ) ;
389- } else {
390- // state is indeterminate or unchecked, add split to all selected examples
391- onSelectionChange ( {
392- selectedExampleIds : selectedPartialExamples . map ( ( e ) => e . id ) ,
393- addSplitIds : [ selectedId ] ,
394- } ) ;
395- }
396- } }
401+ onAction = { ( key ) => handleSplitToggle ( key as string ) }
397402 >
398403 { ( { id, name, color } ) => (
399404 < MenuItem id = { id } textValue = { name } >
0 commit comments