mirror of
https://gitcode.com/gh_mirrors/hy/Hydro.git
synced 2026-10-17 17:36:08 +08:00
ui: drop blueprintjs 6 (#1073)
This commit is contained in:
1 parent
40114323e4
commit
3996f6f2ef
27 files changed
+764
-652
No files matched your search
@@ -1,7 +1,5 @@
|
||||
import { readSubtasksFromFiles } from '@hydrooj/common';
|
||||
import {
|
||||
Button, Classes, Dialog, DialogBody, DialogFooter, Icon,
|
||||
} from '@blueprintjs/core';
|
||||
import { Button, Modal, Text } from '@mantine/core';
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
import { useSelector, useStore } from 'react-redux';
|
||||
import { i18n } from 'vj/utils';
|
||||
@@ -83,14 +81,14 @@ export function AddTestcase() {
|
||||
}
|
||||
|
||||
return (<>
|
||||
<li className={Classes.TREE_NODE} onClick={auto}>
|
||||
<div className={`${Classes.TREE_NODE_CONTENT} ${Classes.TREE_NODE_CONTENT}-0`}>
|
||||
<Icon icon="clean" />
|
||||
<span className={Classes.TREE_NODE_LABEL}>{i18n('Auto detect')}</span>
|
||||
</div>
|
||||
</li>
|
||||
<li
|
||||
className={Classes.TREE_NODE}
|
||||
<div
|
||||
style={{ cursor: 'pointer', padding: '6px 0' }}
|
||||
onClick={auto}
|
||||
>
|
||||
<Text><i className="icon icon-wrench" /> {i18n('Auto detect')}</Text>
|
||||
</div>
|
||||
<div
|
||||
style={{ cursor: 'pointer', padding: '6px 0' }}
|
||||
onClick={() => {
|
||||
setInput('');
|
||||
setOutput('');
|
||||
@@ -99,47 +97,41 @@ export function AddTestcase() {
|
||||
setOpen(true);
|
||||
}}
|
||||
>
|
||||
<div className={`${Classes.TREE_NODE_CONTENT} ${Classes.TREE_NODE_CONTENT}-0`}>
|
||||
<Icon icon="clean" />
|
||||
<span className={Classes.TREE_NODE_LABEL}>{i18n('Add testcase')}</span>
|
||||
</div>
|
||||
</li>
|
||||
<Dialog title="Add testcase" icon="cog" isOpen={open} onClose={() => setOpen(false)}>
|
||||
<DialogBody>
|
||||
<div className="row">
|
||||
<div className="columns medium-6">
|
||||
<FileSelectAutoComplete
|
||||
ref={refInput}
|
||||
data={testdata}
|
||||
label="Input"
|
||||
width="100%"
|
||||
onChange={(e) => setInput(e)}
|
||||
placeholder={i18n('Input')}
|
||||
value={input || ''}
|
||||
/>
|
||||
</div>
|
||||
<div className="columns medium-6">
|
||||
<FileSelectAutoComplete
|
||||
ref={refOutput}
|
||||
data={testdata}
|
||||
label="Output"
|
||||
width="100%"
|
||||
onChange={(e) => setOutput(e)}
|
||||
placeholder={i18n('Output')}
|
||||
value={input || ''}
|
||||
/>
|
||||
</div>
|
||||
<Text><i className="icon icon-add" /> {i18n('Add testcase')}</Text>
|
||||
</div>
|
||||
<Modal
|
||||
opened={open}
|
||||
onClose={() => setOpen(false)}
|
||||
title={i18n('Add testcase')}
|
||||
styles={{ body: { overflow: 'visible' }, content: { overflow: 'visible' } }}
|
||||
>
|
||||
<div className="row" style={{ overflow: 'visible' }}>
|
||||
<div className="columns medium-6" style={{ overflow: 'visible' }}>
|
||||
<FileSelectAutoComplete
|
||||
ref={refInput}
|
||||
data={testdata}
|
||||
label="Input"
|
||||
width="100%"
|
||||
onChange={(e) => setInput(e)}
|
||||
placeholder={i18n('Input')}
|
||||
value={input || ''}
|
||||
/>
|
||||
</div>
|
||||
</DialogBody>
|
||||
<DialogFooter
|
||||
actions={<Button
|
||||
className={`primary rounded button${valid ? '' : ' disabled'}`}
|
||||
onClick={onConfirm}
|
||||
disabled={!valid}
|
||||
intent="primary"
|
||||
text="Save"
|
||||
/>}
|
||||
/>
|
||||
</Dialog>
|
||||
<div className="columns medium-6" style={{ overflow: 'visible' }}>
|
||||
<FileSelectAutoComplete
|
||||
ref={refOutput}
|
||||
data={testdata}
|
||||
label="Output"
|
||||
width="100%"
|
||||
onChange={(e) => setOutput(e)}
|
||||
placeholder={i18n('Output')}
|
||||
value={output || ''}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ display: 'flex', justifyContent: 'flex-end', marginTop: 12 }}>
|
||||
<Button onClick={onConfirm} disabled={!valid}> {i18n('Save')}</Button>
|
||||
</div>
|
||||
</Modal>
|
||||
</>);
|
||||
}
|
||||
@@ -1,10 +1,6 @@
|
||||
import { CustomSelectAutoComplete } from '@hydrooj/components';
|
||||
import { parseMemoryMB, parseTimeMS } from '@hydrooj/utils/lib/common';
|
||||
import {
|
||||
Button, Classes, ControlGroup,
|
||||
Dialog, DialogBody, DialogFooter,
|
||||
Icon, InputGroup, Tag,
|
||||
} from '@blueprintjs/core';
|
||||
import { Button, Group, Modal, Text, TextInput } from '@mantine/core';
|
||||
import { isEqual } from 'lodash';
|
||||
import React, { useEffect } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
@@ -61,83 +57,71 @@ export function SubtaskSettings(props: SubtaskSettingsProps) {
|
||||
}
|
||||
|
||||
return (<>
|
||||
<Dialog title={i18n('Set time and memory limits')} icon="cog" isOpen={open} onClose={() => setOpen(false)}>
|
||||
<DialogBody>
|
||||
<ControlGroup fill={true} vertical={false}>
|
||||
<InputGroup
|
||||
leftElement={<Icon icon="time" />}
|
||||
rightElement={<Tag minimal>ms</Tag>}
|
||||
onChange={(ev) => setTime(ev.currentTarget.value ? `${ev.currentTarget.value}ms` : '')}
|
||||
placeholder={parseTimeMS(props.time, false).toString() || '1000'}
|
||||
value={ctime ? parseTimeMS(ctime, false).toString() || '' : ''}
|
||||
/>
|
||||
<InputGroup
|
||||
leftElement={<Icon icon="comparison" />}
|
||||
rightElement={<Tag minimal>MB</Tag>}
|
||||
onChange={(ev) => setMemory(ev.currentTarget.value ? `${ev.currentTarget.value}MB` : '')}
|
||||
placeholder={parseMemoryMB(props.memory, false).toString() || '256'}
|
||||
value={cmemory ? parseMemoryMB(cmemory, false).toString() || '' : ''}
|
||||
/>
|
||||
<InputGroup
|
||||
leftElement={<Icon icon="star" />}
|
||||
onChange={(ev) => setScore(+ev.target.value || 0)}
|
||||
placeholder="Score"
|
||||
type="number"
|
||||
value={cscore.toString()}
|
||||
/>
|
||||
</ControlGroup>
|
||||
</DialogBody>
|
||||
<DialogFooter actions={<Button className="primary rounded button" onClick={onConfirm} intent="primary" text="Save" />} />
|
||||
</Dialog>
|
||||
<Dialog title={i18n('Set dependencies')} icon="cog" isOpen={depsOpen} onClose={() => setDepsOpen(false)}>
|
||||
<DialogBody>
|
||||
<CustomSelectAutoComplete
|
||||
data={subtaskIds.map((i) => ({ _id: i, name: `${i18n('Subtask {0}', i)}` }))}
|
||||
setSelectItems={cdeps.split(',').map((i) => i.trim()).filter((i) => +i).map((i) => +i)}
|
||||
onChange={(items) => setDeps(items)}
|
||||
placeholder="dependencies"
|
||||
multi
|
||||
<Modal opened={open} onClose={() => setOpen(false)} title={i18n('Set time and memory limits')}>
|
||||
<Group grow>
|
||||
<TextInput
|
||||
leftSection={<i className="icon icon-stopwatch" />}
|
||||
rightSection={<Text size="sm">ms</Text>}
|
||||
rightSectionWidth={60}
|
||||
onChange={(ev) => setTime(ev.currentTarget.value ? `${ev.currentTarget.value}ms` : '')}
|
||||
placeholder={parseTimeMS(props.time, false).toString() || '1000'}
|
||||
value={ctime ? parseTimeMS(ctime, false).toString() || '' : ''}
|
||||
/>
|
||||
</DialogBody>
|
||||
<DialogFooter actions={<Button className="primary rounded button" onClick={onConfirm} intent="primary" text="Save" />} />
|
||||
</Dialog>
|
||||
<li className={Classes.TREE_NODE} onClick={() => setOpen(true)}>
|
||||
<div className={Classes.TREE_NODE_CONTENT}>
|
||||
<span className={`${Classes.TREE_NODE_CARET_NONE} ${Classes.ICON_STANDARD}`}></span>
|
||||
<Icon icon="time" />
|
||||
|
||||
<span className={`${Classes.TREE_NODE_LABEL}${time ? '' : ' text-gray'}`}>{time || props.time || '1s'}</span>
|
||||
<Icon icon="comparison" />
|
||||
|
||||
<span className={`${Classes.TREE_NODE_LABEL}${memory ? '' : ' text-gray'}`}>{memory || props.memory || '256m'}</span>
|
||||
<Icon icon="star" />
|
||||
{' '}
|
||||
<span className={Classes.TREE_NODE_SECONDARY_LABEL}>{score || 0}</span>
|
||||
<TextInput
|
||||
leftSection={<i className="icon icon-comparison" />}
|
||||
rightSection={<Text size="sm">MB</Text>}
|
||||
rightSectionWidth={60}
|
||||
onChange={(ev) => setMemory(ev.currentTarget.value ? `${ev.currentTarget.value}MB` : '')}
|
||||
placeholder={parseMemoryMB(props.memory, false).toString() || '256'}
|
||||
value={cmemory ? parseMemoryMB(cmemory, false).toString() || '' : ''}
|
||||
/>
|
||||
<TextInput
|
||||
leftSection={<i className="icon icon-star" />}
|
||||
onChange={(ev) => setScore(+ev.target.value || 0)}
|
||||
placeholder="Score"
|
||||
type="number"
|
||||
value={cscore.toString()}
|
||||
/>
|
||||
</Group>
|
||||
<div style={{ display: 'flex', justifyContent: 'flex-end', marginTop: 12 }}>
|
||||
<Button color="blue" onClick={onConfirm}>{i18n('Save')}</Button>
|
||||
</div>
|
||||
</li>
|
||||
<li className={Classes.TREE_NODE} onClick={() => setDepsOpen(true)}>
|
||||
<div className={Classes.TREE_NODE_CONTENT}>
|
||||
<span className={`${Classes.TREE_NODE_CARET_NONE} ${Classes.ICON_STANDARD}`}></span>
|
||||
<Icon icon="diagram-tree" />
|
||||
|
||||
<span className={Classes.TREE_NODE_LABEL}>{i18n('Dependencies')}: {deps.length ? deps.join(', ') : i18n('(None)')}</span>
|
||||
</Modal>
|
||||
<Modal opened={depsOpen} onClose={() => setDepsOpen(false)} title={i18n('Set dependencies')}>
|
||||
<CustomSelectAutoComplete
|
||||
data={subtaskIds.map((i) => ({ _id: i, name: `${i18n('Subtask {0}', i)}` }))}
|
||||
setSelectItems={cdeps.split(',').map((i) => i.trim()).filter((i) => +i).map((i) => +i)}
|
||||
onChange={(items) => setDeps(items)}
|
||||
placeholder="dependencies"
|
||||
multi
|
||||
/>
|
||||
<div style={{ display: 'flex', justifyContent: 'flex-end', marginTop: 12 }}>
|
||||
<Button color="blue" onClick={onConfirm}>{i18n('Save')}</Button>
|
||||
</div>
|
||||
</li>
|
||||
<li className={Classes.TREE_NODE}>
|
||||
<div className={Classes.TREE_NODE_CONTENT}>
|
||||
<span className={`${Classes.TREE_NODE_CARET_NONE} ${Classes.ICON_STANDARD}`}></span>
|
||||
<Icon icon="asterisk" />
|
||||
|
||||
<span className={Classes.TREE_NODE_LABEL}>{i18n('Scoring method')}</span>
|
||||
<span className={Classes.TREE_NODE_SECONDARY_LABEL}>
|
||||
<select className="compact select" value={ctype} onChange={(e) => setType(e.target.value)}>
|
||||
<option value="min">Min</option>
|
||||
<option value="max">Max</option>
|
||||
<option value="sum">Sum</option>
|
||||
</select>
|
||||
</span>
|
||||
</div>
|
||||
</li>
|
||||
</Modal>
|
||||
<div style={{ paddingLeft: 22, display: 'flex', cursor: 'pointer', gap: 8 }} onClick={() => setOpen(true)}>
|
||||
<Text><i className="icon icon-stopwatch" /></Text>
|
||||
<Text className={time ? '' : 'text-gray'}>{time || props.time || '1s'} </Text>
|
||||
<Text><i className="icon icon-comparison" /></Text>
|
||||
<Text className={memory ? '' : 'text-gray'}>{memory || props.memory || '256m'} </Text>
|
||||
<Text><i className="icon icon-star" /></Text>
|
||||
<Text>{score || 0}</Text>
|
||||
</div>
|
||||
<div style={{ paddingLeft: 22, display: 'flex', alignItems: 'center', cursor: 'pointer', gap: 8 }} onClick={() => setDepsOpen(true)}>
|
||||
<Text><i className="icon icon-diagram-tree" /></Text>
|
||||
<Text>{i18n('Dependencies')}: {deps.length ? deps.join(', ') : i18n('(None)')}</Text>
|
||||
</div>
|
||||
<div style={{ paddingLeft: 22, display: 'flex', alignItems: 'center', gap: 8 }}>
|
||||
<i className="icon icon-asterisk" />
|
||||
<Text>{i18n('Scoring method')}</Text>
|
||||
<span style={{ marginLeft: 'auto' }}>
|
||||
<select className="compact select" value={ctype} onChange={(e) => setType(e.target.value)}>
|
||||
<option value="min">Min</option>
|
||||
<option value="max">Max</option>
|
||||
<option value="sum">Sum</option>
|
||||
</select>
|
||||
</span>
|
||||
</div>
|
||||
</>);
|
||||
}
|
||||
|
||||
@@ -163,36 +147,34 @@ export function GlobalSettings() {
|
||||
setOpen(false);
|
||||
}
|
||||
return (<>
|
||||
<Dialog title={i18n('Set time and memory limits')} icon="cog" isOpen={open} onClose={() => setOpen(false)}>
|
||||
<DialogBody>
|
||||
<ControlGroup fill={true} vertical={false}>
|
||||
<InputGroup
|
||||
leftElement={<Icon icon="time" />}
|
||||
rightElement={<Tag minimal>ms</Tag>}
|
||||
onChange={(ev) => setTime(ev.currentTarget.value ? `${ev.currentTarget.value}ms` : '')}
|
||||
placeholder="1000"
|
||||
value={ctime ? parseTimeMS(ctime, false).toString() || '' : ''}
|
||||
/>
|
||||
<InputGroup
|
||||
leftElement={<Icon icon="comparison" />}
|
||||
rightElement={<Tag minimal>MB</Tag>}
|
||||
onChange={(ev) => setMemory(ev.currentTarget.value ? `${ev.currentTarget.value}MB` : '')}
|
||||
placeholder="256"
|
||||
value={cmemory ? parseMemoryMB(cmemory, false).toString() || '' : ''}
|
||||
/>
|
||||
</ControlGroup>
|
||||
</DialogBody>
|
||||
<DialogFooter actions={<Button className="primary rounded button" onClick={onConfirm} intent="primary" text="Save" />} />
|
||||
</Dialog>
|
||||
<li className="bp6-tree-node" onClick={() => setOpen(true)}>
|
||||
<div className="bp6-tree-node-content">
|
||||
<Icon icon="time" />
|
||||
|
||||
<span className={`bp6-tree-node-label${time ? '' : ' text-gray'}`}>{time || '1s'}</span>
|
||||
<Icon icon="comparison" />
|
||||
{' '}
|
||||
<span className={`bp6-tree-node-secondary-label${memory ? '' : ' text-gray'}`}>{memory || '256MB'}</span>
|
||||
<Modal opened={open} onClose={() => setOpen(false)} title={i18n('Set time and memory limits')}>
|
||||
<Group grow>
|
||||
<TextInput
|
||||
leftSection={<i className="icon icon-stopwatch" />}
|
||||
rightSection={<Text size="sm">ms</Text>}
|
||||
rightSectionWidth={60}
|
||||
onChange={(ev) => setTime(ev.currentTarget.value ? `${ev.currentTarget.value}ms` : '')}
|
||||
placeholder="1000"
|
||||
value={ctime ? parseTimeMS(ctime, false).toString() || '' : ''}
|
||||
/>
|
||||
<TextInput
|
||||
leftSection={<i className="icon icon-comparison" />}
|
||||
rightSection={<Text size="sm">MB</Text>}
|
||||
rightSectionWidth={60}
|
||||
onChange={(ev) => setMemory(ev.currentTarget.value ? `${ev.currentTarget.value}MB` : '')}
|
||||
placeholder="256"
|
||||
value={cmemory ? parseMemoryMB(cmemory, false).toString() || '' : ''}
|
||||
/>
|
||||
</Group>
|
||||
<div style={{ display: 'flex', justifyContent: 'flex-end', marginTop: 12 }}>
|
||||
<Button color="blue" onClick={onConfirm}>{i18n('Save')}</Button>
|
||||
</div>
|
||||
</li>
|
||||
</Modal>
|
||||
<div style={{ paddingLeft: 22, display: 'flex', alignItems: 'center', cursor: 'pointer', gap: 8 }} onClick={() => setOpen(true)}>
|
||||
<i className="icon icon-stopwatch" />
|
||||
<Text className={time ? '' : 'text-gray'}>{time || '1s'}</Text>
|
||||
<i className="icon icon-comparison" />
|
||||
<Text className={memory ? '' : 'text-gray'}>{memory || '256MB'}</Text>
|
||||
</div>
|
||||
</>);
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
import { TestCaseConfig } from 'hydrooj';
|
||||
import {
|
||||
ContextMenu, Menu, MenuItem, TreeNode,
|
||||
} from '@blueprintjs/core';
|
||||
import { Text } from '@mantine/core';
|
||||
import { omit } from 'lodash';
|
||||
import { useContextMenu } from 'mantine-contextmenu';
|
||||
import React from 'react';
|
||||
import { useDrag } from 'react-dnd';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { i18n } from 'vj/utils';
|
||||
|
||||
interface TestcaseNodeProps {
|
||||
@@ -20,37 +20,35 @@ interface TestcaseNodeProps {
|
||||
|
||||
export function TestcaseNode(props: TestcaseNodeProps) {
|
||||
const {
|
||||
c, selected, onClick, subtaskIds, subtaskId,
|
||||
c, selected, onClick, subtaskId,
|
||||
} = props;
|
||||
let display = `${c.input} / ${c.output}`;
|
||||
const minlength = Math.min(c.input.length, c.output.length);
|
||||
for (let i = minlength; i >= 0; i--) {
|
||||
const prefix = c.input.slice(0, i);
|
||||
if (c.input.startsWith(prefix) && c.output.startsWith(prefix)) {
|
||||
display = `${prefix}(${c.input.substring(i)}/${c.output.substring(i)})`;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return (
|
||||
<ContextMenu
|
||||
onContextMenu={onClick}
|
||||
<div
|
||||
onClick={onClick}
|
||||
data-subtaskid={subtaskId}
|
||||
data-index={c.input}
|
||||
data-selected={selected}
|
||||
content={
|
||||
<Menu>
|
||||
<MenuItem icon="drawer-left" text={i18n('Move to subtask')}>
|
||||
{subtaskIds.filter((i) => i !== subtaskId).map((i) => (
|
||||
<MenuItem key={i} text={i18n('Subtask {0}', i)} />
|
||||
))}
|
||||
{subtaskIds.length <= 1 && (
|
||||
<MenuItem icon="disable" disabled text={i18n('No target available')} />
|
||||
)}
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
}
|
||||
style={{
|
||||
paddingLeft: 22,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
cursor: 'pointer',
|
||||
background: selected ? 'var(--mantine-color-blue-light, #ebf3ff)' : 'transparent',
|
||||
borderRadius: 4,
|
||||
fontFamily: 'var(--mantine-font-family-monospace)',
|
||||
}}
|
||||
>
|
||||
<TreeNode
|
||||
depth={0}
|
||||
id={c.input}
|
||||
isSelected={selected}
|
||||
onClick={onClick}
|
||||
icon="document"
|
||||
label={<> {c.input} / {c.output}</>}
|
||||
path={[0]}
|
||||
/>
|
||||
</ContextMenu>
|
||||
<Text ml="xs"><i className="icon icon-file" /> {display}</Text>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -61,8 +59,11 @@ interface TestcaseGroupProps extends Omit<TestcaseNodeProps, 'c'> {
|
||||
|
||||
export function TestcaseGroup(props: TestcaseGroupProps) {
|
||||
const {
|
||||
cases, subtaskId, onClick, index,
|
||||
cases, subtaskId, subtaskIds, onClick, index,
|
||||
} = props;
|
||||
const dispatch = useDispatch();
|
||||
const { showContextMenu } = useContextMenu();
|
||||
const moveTargets = subtaskIds.filter((i) => i !== subtaskId);
|
||||
const [collected, drag] = useDrag(() => ({
|
||||
type: 'cases',
|
||||
collect: (monitor) => ({
|
||||
@@ -71,7 +72,25 @@ export function TestcaseGroup(props: TestcaseGroupProps) {
|
||||
canDrag: props.selected,
|
||||
item: { cases, subtaskId },
|
||||
}), [JSON.stringify(cases), subtaskId]);
|
||||
return <div ref={drag} onClick={onClick} onMouseDown={props.onMouseDown} style={{ opacity: collected.isDragging ? 0.5 : 1 }}>
|
||||
return <div
|
||||
ref={drag}
|
||||
onClick={onClick}
|
||||
onMouseDown={props.onMouseDown}
|
||||
onContextMenu={showContextMenu(
|
||||
moveTargets.length
|
||||
? moveTargets.map((id) => ({
|
||||
key: `move-${id}`,
|
||||
icon: <i className="icon icon-send" />,
|
||||
title: `${i18n('Move to')} ${i18n('Subtask {0}', id)}`,
|
||||
onClick: () => dispatch({
|
||||
type: 'problemconfig/moveTestcases',
|
||||
payload: { target: id, source: subtaskId, cases },
|
||||
}),
|
||||
}))
|
||||
: [{ key: 'no-target', title: i18n('No target available'), onClick: () => { }, disabled: true }],
|
||||
)}
|
||||
style={{ opacity: collected.isDragging ? 0.5 : 1 }}
|
||||
>
|
||||
{cases.map((c, id) => (
|
||||
<TestcaseNode c={c} key={`${c.input}@${index + id}`} {...omit(props, 'onClick')} index={index + id} />
|
||||
))}
|
||||
|
||||
Reference in new issue
Block a user