11import parser , { SearchParserResult } from '@hydrooj/utils/lib/search' ;
22import $ from 'jquery' ;
33import _ from 'lodash' ;
4+ import React from 'react' ;
5+ import ReactDOM from 'react-dom/client' ;
6+ import ProblemSelectAutoComplete from 'vj/components/autocomplete/components/ProblemSelectAutoComplete' ;
47import DomainSelectAutoComplete from 'vj/components/autocomplete/DomainSelectAutoComplete' ;
58import { ActionDialog , ConfirmDialog , Dialog } from 'vj/components/dialog' ;
69import Dropdown from 'vj/components/dropdown/Dropdown' ;
@@ -18,6 +21,9 @@ const pinned: Record<string, string[]> = { category: [], difficulty: [] };
1821const selections = { category : { } , difficulty : { } } ;
1922const selectedTags : Record < string , string [ ] > = { category : [ ] , difficulty : [ ] } ;
2023
24+ let selectedPids : string [ ] = [ ] ;
25+ let clearSelectionHandler : ( ( ) => void ) | null = null ;
26+
2127function setDomSelected ( $dom , selected , icon ?) {
2228 if ( selected ) {
2329 $dom . addClass ( 'selected' ) ;
@@ -163,15 +169,11 @@ function parseCategorySelection() {
163169}
164170
165171function ensureAndGetSelectedPids ( ) {
166- const pids = _ . map (
167- $ ( 'tbody [data-checkbox-group="problem"]:checked' ) ,
168- ( ch ) => $ ( ch ) . closest ( 'tr' ) . attr ( 'data-pid' ) ,
169- ) ;
170- if ( pids . length === 0 ) {
172+ if ( selectedPids . length === 0 ) {
171173 Notification . error ( i18n ( 'Please select at least one problem to perform this operation.' ) ) ;
172174 return null ;
173175 }
174- return pids ;
176+ return selectedPids ;
175177}
176178
177179async function handleOperation ( operation ) {
@@ -204,6 +206,7 @@ async function handleOperation(operation) {
204206 await request . post ( '' , { operation, pids, ...payload } ) ;
205207 Notification . success ( i18n ( `Selected problems have been ${ operation === 'copy' ? 'copie' : operation } d.` ) ) ;
206208 await delay ( 2000 ) ;
209+ clearSelectionHandler ?.( ) ;
207210 loadQuery ( ) ;
208211 } catch ( error ) {
209212 Notification . error ( error . message ) ;
@@ -282,6 +285,94 @@ function processElement(ele) {
282285 createHint ( 'Hint::icon::difficulty' , $ ( ele ) . find ( 'th.col--difficulty' ) ) ;
283286}
284287
288+ function ProblemSelectionDisplay ( props ) { // eslint-disable-line
289+ const [ pids , setPids ] = React . useState < string [ ] > ( [ ] ) ;
290+ const [ dialogOpen , setDialogOpen ] = React . useState ( false ) ;
291+
292+ React . useEffect ( ( ) => {
293+ props . onClear ?.( ( ) => {
294+ setPids ( [ ] ) ;
295+ } ) ;
296+ } , [ props . onClear ] ) ;
297+
298+ React . useEffect ( ( ) => {
299+ $ ( document ) . on ( 'click' , '[data-checkbox-group="problem"]:checked' , ( ev ) => {
300+ const $checkbox = $ ( ev . currentTarget ) ;
301+ const pid = $checkbox . closest ( 'tr' ) . attr ( 'data-pid' ) ;
302+ setPids ( ( o ) => Array . from ( new Set ( [ ...o , pid ] ) ) ) ;
303+ } ) ;
304+ $ ( document ) . on ( 'click' , '[data-checkbox-group="problem"]:not(:checked)' , ( ev ) => {
305+ const $checkbox = $ ( ev . currentTarget ) ;
306+ const pid = $checkbox . closest ( 'tr' ) . attr ( 'data-pid' ) ;
307+ setPids ( ( o ) => o . filter ( ( i ) => i !== pid ) ) ;
308+ } ) ;
309+ $ ( document ) . on ( 'click' , '[data-checkbox-toggle="problem"]:checked' , ( ) => {
310+ const all = $ ( '[data-checkbox-group="problem"]' ) . map ( ( _index , i ) => $ ( i ) . closest ( 'tr' ) . attr ( 'data-pid' ) ) . get ( ) ;
311+ setPids ( ( o ) => Array . from ( new Set ( [ ...o , ...all ] ) ) ) ;
312+ } ) ;
313+ $ ( document ) . on ( 'click' , '[data-checkbox-toggle="problem"]:not(:checked)' , ( ) => {
314+ const all = $ ( '[data-checkbox-group="problem"]' ) . map ( ( _index , i ) => $ ( i ) . closest ( 'tr' ) . attr ( 'data-pid' ) ) . get ( ) ;
315+ setPids ( ( o ) => o . filter ( ( i ) => ! all . includes ( i ) ) ) ;
316+ } ) ;
317+ } , [ ] ) ;
318+
319+ const updateCheckboxSelection = React . useCallback ( ( ) => {
320+ for ( const i of $ ( '[data-checkbox-group="problem"]:checked' ) ) {
321+ if ( ! pids . includes ( i . closest ( 'tr' ) . dataset . pid ) ) {
322+ $ ( i ) . prop ( 'checked' , false ) ;
323+ }
324+ }
325+ for ( const i of pids ) {
326+ $ ( `[data-pid="${ i } "]` ) . find ( '[data-checkbox-group="problem"]' ) ?. prop ( 'checked' , true ) ;
327+ }
328+ } , [ pids ] ) ;
329+
330+ React . useEffect ( ( ) => {
331+ updateCheckboxSelection ( ) ;
332+ props . onChange ( pids ) ;
333+ $ ( document ) . on ( 'vjContentNew' , updateCheckboxSelection ) ;
334+ return ( ) => {
335+ $ ( document ) . off ( 'vjContentNew' , updateCheckboxSelection ) ;
336+ } ;
337+ } , [ pids ] ) ;
338+
339+ return ( < >
340+ < a href = "javascript:;" className = "menu__link display-mode-hide" onClick = { ( ) => setDialogOpen ( true ) } >
341+ < span className = "icon icon-stack" > </ span >
342+ { ' ' } { i18n ( '{0} problem(s) selected' , pids . length ) }
343+ </ a >
344+ < div className = "dialog withBg" style = { { display : dialogOpen ? 'flex' : 'none' , zIndex : 1000 , opacity : 1 } } onClick = { ( ) => setDialogOpen ( false ) } >
345+ < div className = "dialog__content" style = { { transform : 'scale(1, 1)' } } onClick = { ( ev ) => ev . stopPropagation ( ) } >
346+ < div className = "dialog__body" style = { { height : 'calc(100% - 45px)' } } >
347+ < div className = "row" >
348+ < div className = "columns" >
349+ < h1 > Select Problems</ h1 >
350+ </ div >
351+ </ div >
352+ < div className = "row" >
353+ < div className = "columns" >
354+ < ProblemSelectAutoComplete
355+ multi
356+ onChange = { ( v ) => setPids ( v . split ( ',' ) . filter ( ( i ) => i . trim ( ) ) ) }
357+ selectedKeys = { pids }
358+ />
359+ < style > { '.autocomplete-wrapper { max-height: 50vh; }' } </ style >
360+ </ div >
361+ </ div >
362+ </ div >
363+ < div className = "row" >
364+ < div className = "columns clearfix" >
365+ < div className = "float-right dialog__action" >
366+ < button className = "rounded button" onClick = { ( ) => setPids ( [ ] ) } > { i18n ( 'Clear' ) } </ button > { ' ' }
367+ < button className = "primary rounded button" onClick = { ( ) => setDialogOpen ( false ) } > { i18n ( 'Ok' ) } </ button >
368+ </ div >
369+ </ div >
370+ </ div >
371+ </ div >
372+ </ div >
373+ </ > ) ;
374+ }
375+
285376const page = new NamedPage ( [ 'problem_main' ] , ( ) => {
286377 const $body = $ ( 'body' ) ;
287378 $body . addClass ( 'display-mode' ) ;
@@ -344,6 +435,12 @@ const page = new NamedPage(['problem_main'], () => {
344435 $ ( document ) . on ( 'vjContentNew' , ( e ) => processElement ( e . target ) ) ;
345436 processElement ( document ) ;
346437
438+ ReactDOM . createRoot ( document . getElementById ( 'problem_selection' ) )
439+ . render ( < ProblemSelectionDisplay
440+ onChange = { ( pids ) => { selectedPids = pids ; } }
441+ onClear = { ( handler ) => { clearSelectionHandler = handler ; } }
442+ /> ) ;
443+
347444 addSpeculationRules ( {
348445 prerender : [ {
349446 'where' : {
0 commit comments