| 15 | } |
| 16 | |
| 17 | export default function ProblemConfig(props: Props) { |
| 18 | const [selected, setSelected] = React.useState('basic'); |
| 19 | const [container, setContainer] = React.useState<HTMLDivElement | null>(null); |
| 20 | const [tabs, setTabs] = React.useState<HTMLDivElement | null>(null); |
| 21 | const [editor, setEditor] = React.useState<any>(null); |
| 22 | const store = useStore<RootState>(); |
| 23 | const valid = useSelector((state: RootState) => state.config.__valid); |
| 24 | const errors = useSelector((state: RootState) => state.config.__errors, (a, b) => JSON.stringify(a) === JSON.stringify(b)); |
| 25 | |
| 26 | const handleSave = React.useCallback(async () => { |
| 27 | if (valid) props.onSave(); |
| 28 | else if (await confirm(i18n('Errors detected in the config. Confirm save?'))) props.onSave(); |
| 29 | }, [valid, props.onSave]); |
| 30 | |
| 31 | React.useEffect(() => { |
| 32 | if (!container) return () => { }; |
| 33 | container.style.height ||= '600px'; |
| 34 | const callback = () => { |
| 35 | setTimeout(() => { |
| 36 | const target = Math.max(container.clientHeight, tabs?.clientHeight || 0); |
| 37 | container.style.height = `${target}px`; |
| 38 | if (editor) editor.layout(); |
| 39 | }, 50); |
| 40 | }; |
| 41 | const dispose = store.subscribe(callback); |
| 42 | window.addEventListener('resize', callback); |
| 43 | return () => { |
| 44 | dispose(); |
| 45 | window.removeEventListener('resize', callback); |
| 46 | }; |
| 47 | }, [container, tabs, store]); |
| 48 | |
| 49 | return <ContextMenuProvider> |
| 50 | <div ref={setContainer}> |
| 51 | <Allotment |
| 52 | defaultSizes={[2, 3]} |
| 53 | className="problem-config-allotment"> |
| 54 | <Allotment.Pane> |
| 55 | <div style={{ height: '100%', overflow: 'auto' }}> |
| 56 | <ProblemConfigEditor ref={setEditor} /> |
| 57 | </div> |
| 58 | </Allotment.Pane> |
| 59 | <Allotment.Pane> |
| 60 | <Tabs |
| 61 | ref={setTabs} |
| 62 | value={valid ? selected : 'errors'} |
| 63 | keepMounted={false} |
| 64 | style={{ paddingLeft: 20 }} |
| 65 | onChange={(t) => (t !== 'errors' && t && setSelected(t.toString()))} |
| 66 | > |
| 67 | <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}> |
| 68 | <Tabs.List> |
| 69 | <Tabs.Tab value="basic" disabled={!valid}>{i18n('Basic')}</Tabs.Tab> |
| 70 | <Tabs.Tab value="subtasks" disabled={!valid}>{i18n('Subtasks')}</Tabs.Tab> |
| 71 | <Tabs.Tab value="errors" disabled={valid}>{errors.length ? `Errors(${errors.length})` : 'No Errors'}</Tabs.Tab> |
| 72 | </Tabs.List> |
| 73 | <Button onClick={handleSave}> |
| 74 | {i18n('Save')} |