mirror of
https://gitcode.com/gh_mirrors/hy/Hydro.git
synced 2026-10-17 17:36:08 +08:00
core&ui: add contest language limit (#1014)
Co-authored-by: panda <panda_dtdyy@outlook.com>
This commit is contained in:
1 parent
0699e17ce6
commit
4165e9b153
23 files changed
+175
-232
No files matched your search
@@ -1,55 +1,25 @@
|
||||
import { CustomSelectAutoComplete as CustomSelectAutoCompleteFC } from '@hydrooj/components';
|
||||
import React from 'react';
|
||||
import AutoComplete, { AutoCompleteOptions } from '.';
|
||||
|
||||
interface CustomSelectOptions {
|
||||
data: any[]
|
||||
}
|
||||
|
||||
const Component = React.forwardRef<any, any>((props, ref) => {
|
||||
const [value, setValue] = React.useState(props.value);
|
||||
return (
|
||||
<CustomSelectAutoCompleteFC
|
||||
ref={ref as any}
|
||||
height="auto"
|
||||
selectedKeys={value.split(',').map((i) => i.trim()).filter((i) => i)}
|
||||
onChange={(v) => {
|
||||
setValue(v);
|
||||
props.onChange(v);
|
||||
}}
|
||||
data={props.data}
|
||||
multi={props.multi}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
export default class CustomSelectAutoComplete<Multi extends boolean> extends AutoComplete<CustomSelectOptions> {
|
||||
static DOMAttachKey = 'ucwCustomSelectAutoCompleteInstance';
|
||||
|
||||
constructor($dom, options: CustomSelectOptions & AutoCompleteOptions<Multi>) {
|
||||
super($dom, {
|
||||
classes: 'custom-select',
|
||||
component: CustomSelectAutoCompleteFC,
|
||||
props: {
|
||||
data: options.data,
|
||||
multi: options.multi,
|
||||
height: 'auto',
|
||||
},
|
||||
...options,
|
||||
});
|
||||
}
|
||||
|
||||
value(): Multi extends true ? number[] : string {
|
||||
if (this.options.multi) return this.ref?.getSelectedItemKeys().map((i) => +i) ?? this.$dom.val();
|
||||
return this.ref?.getSelectedItems()[0] ?? null;
|
||||
}
|
||||
|
||||
attach() {
|
||||
const value = this.$dom.val();
|
||||
this.component.render(
|
||||
<Component
|
||||
ref={(ref) => { this.ref = ref; }}
|
||||
data={this.options.data}
|
||||
value={value}
|
||||
multi={this.options.multi}
|
||||
onChange={this.onChange}
|
||||
/>,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
window.Hydro.components.CustomSelectAutoComplete = CustomSelectAutoComplete;
|
||||
@@ -1,42 +1,20 @@
|
||||
import React from 'react';
|
||||
import AutoComplete from '.';
|
||||
import AutoComplete, { AutoCompleteOptions } from '.';
|
||||
import DomainSelectAutoCompleteFC from './components/DomainSelectAutoComplete';
|
||||
|
||||
const Component = React.forwardRef((props: { value: string, multi: boolean, onChange: (v: string) => void }, ref) => {
|
||||
const [value, setValue] = React.useState(props.value ?? '');
|
||||
return (
|
||||
<DomainSelectAutoCompleteFC
|
||||
ref={ref as any}
|
||||
height="34px"
|
||||
selectedKeys={value.split(',').map((i) => i.trim()).filter((i) => i)}
|
||||
onChange={(v) => {
|
||||
setValue(v);
|
||||
props.onChange(v);
|
||||
}}
|
||||
multi={props.multi}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
export default class DomainSelectAutoComplete extends AutoComplete {
|
||||
export default class DomainSelectAutoComplete<Multi extends boolean> extends AutoComplete {
|
||||
static DOMAttachKey = 'ucwDomainSelectAutoCompleteInstance';
|
||||
|
||||
constructor($dom, options) {
|
||||
constructor($dom, options: AutoCompleteOptions<Multi> = {}) {
|
||||
super($dom, {
|
||||
classes: 'domain-select',
|
||||
component: DomainSelectAutoCompleteFC,
|
||||
props: {
|
||||
multi: options.multi,
|
||||
height: '34px',
|
||||
},
|
||||
...options,
|
||||
});
|
||||
}
|
||||
|
||||
attach() {
|
||||
const value = this.$dom.val();
|
||||
this.component.render(<Component
|
||||
ref={(ref) => { this.ref = ref; }}
|
||||
value={value}
|
||||
onChange={this.onChange}
|
||||
multi={this.options.multi}
|
||||
/>);
|
||||
}
|
||||
}
|
||||
|
||||
window.Hydro.components.DomainSelectAutoComplete = DomainSelectAutoComplete;
|
||||
@@ -1,4 +1,3 @@
|
||||
import React from 'react';
|
||||
import AutoComplete, { AutoCompleteOptions } from '.';
|
||||
import FileSelectAutoCompleteFC from './components/FileSelectAutoComplete';
|
||||
|
||||
@@ -9,50 +8,21 @@ interface FileSelectOptions {
|
||||
}[];
|
||||
}
|
||||
|
||||
const Component = React.forwardRef<any, any>((props, ref) => {
|
||||
const [value, setValue] = React.useState(props.value);
|
||||
return (
|
||||
<FileSelectAutoCompleteFC
|
||||
ref={ref as any}
|
||||
height="auto"
|
||||
selectedKeys={value.split(',').map((i) => i.trim()).filter((i) => i)}
|
||||
onChange={(v) => {
|
||||
setValue(v);
|
||||
props.onChange(v);
|
||||
}}
|
||||
data={props.data}
|
||||
multi={props.multi}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
export default class FileSelectAutoComplete<Multi extends boolean> extends AutoComplete<FileSelectOptions> {
|
||||
static DOMAttachKey = 'ucwFileSelectAutoCompleteInstance';
|
||||
|
||||
constructor($dom, options: FileSelectOptions & AutoCompleteOptions<Multi>) {
|
||||
super($dom, {
|
||||
classes: 'custom-select',
|
||||
classes: 'file-select',
|
||||
component: FileSelectAutoCompleteFC,
|
||||
props: {
|
||||
data: options.data,
|
||||
multi: options.multi,
|
||||
height: 'auto',
|
||||
},
|
||||
...options,
|
||||
});
|
||||
}
|
||||
|
||||
value(): Multi extends true ? number[] : string {
|
||||
if (this.options.multi) return this.ref?.getSelectedItemKeys().map((i) => +i) ?? this.$dom.val();
|
||||
return this.ref?.getSelectedItems()[0] ?? null;
|
||||
}
|
||||
|
||||
attach() {
|
||||
const value = this.$dom.val();
|
||||
this.component.render(
|
||||
<Component
|
||||
ref={(ref) => { this.ref = ref; }}
|
||||
data={this.options.data}
|
||||
value={value}
|
||||
multi={this.options.multi}
|
||||
onChange={this.onChange}
|
||||
/>,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
window.Hydro.components.FileSelectAutoComplete = FileSelectAutoComplete;
|
||||
@@ -0,0 +1,26 @@
|
||||
import AutoComplete, { AutoCompleteOptions } from '.';
|
||||
import LanguageSelectAutoCompleteFC from './components/LanguageSelectAutoComplete';
|
||||
|
||||
interface LanguageSelectOptions {
|
||||
multi?: boolean;
|
||||
withAuto?: boolean;
|
||||
}
|
||||
|
||||
export default class LanguageSelectAutoComplete<Multi extends boolean> extends AutoComplete<LanguageSelectOptions> {
|
||||
static DOMAttachKey = 'ucwLanguageSelectAutoCompleteInstance';
|
||||
|
||||
constructor($dom, options: LanguageSelectOptions & AutoCompleteOptions<Multi>) {
|
||||
super($dom, {
|
||||
classes: 'language-select',
|
||||
component: LanguageSelectAutoCompleteFC,
|
||||
props: {
|
||||
multi: options.multi,
|
||||
withAuto: options.withAuto,
|
||||
height: 'auto',
|
||||
},
|
||||
...options,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
window.Hydro.components.LanguageSelectAutoComplete = LanguageSelectAutoComplete;
|
||||
@@ -1,44 +1,20 @@
|
||||
import React from 'react';
|
||||
import AutoComplete from '.';
|
||||
import ProblemSelectAutoCompleteFC from './components/ProblemSelectAutoComplete';
|
||||
|
||||
const Component = React.forwardRef<any, any>((props, ref) => {
|
||||
const [value, setValue] = React.useState(props.value ?? '');
|
||||
return (
|
||||
<ProblemSelectAutoCompleteFC
|
||||
ref={ref as any}
|
||||
height="auto"
|
||||
selectedKeys={value.split(',').map((i) => i.trim()).filter((i) => i)}
|
||||
onChange={(v) => {
|
||||
setValue(v);
|
||||
props.onChange(v);
|
||||
}}
|
||||
multi={props.multi}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
export default class ProblemSelectAutoComplete extends AutoComplete {
|
||||
static DOMAttachKey = 'ucwProblemSelectAutoCompleteInstance';
|
||||
|
||||
constructor($dom, options) {
|
||||
super($dom, {
|
||||
classes: 'problem-select',
|
||||
component: ProblemSelectAutoCompleteFC,
|
||||
props: {
|
||||
multi: options.multi,
|
||||
height: 'auto',
|
||||
},
|
||||
...options,
|
||||
});
|
||||
}
|
||||
|
||||
attach() {
|
||||
const value = this.$dom.val();
|
||||
this.component.render(
|
||||
<Component
|
||||
ref={(ref) => { this.ref = ref; }}
|
||||
value={value}
|
||||
multi={this.options.multi}
|
||||
onChange={this.onChange}
|
||||
/>,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
window.Hydro.components.ProblemSelectAutoComplete = ProblemSelectAutoComplete;
|
||||
@@ -1,29 +1,17 @@
|
||||
import React from 'react';
|
||||
import AutoComplete, { AutoCompleteOptions } from '.';
|
||||
import UserSelectAutoCompleteFC from './components/UserSelectAutoComplete';
|
||||
|
||||
const Component = React.forwardRef<any, any>((props, ref) => {
|
||||
const [value, setValue] = React.useState(props.value ?? '');
|
||||
return (
|
||||
<UserSelectAutoCompleteFC
|
||||
ref={ref as any}
|
||||
height="auto"
|
||||
selectedKeys={value.split(',').map((i) => i.trim()).filter((i) => i)}
|
||||
onChange={(v) => {
|
||||
setValue(v);
|
||||
props.onChange(v);
|
||||
}}
|
||||
multi={props.multi}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
export default class UserSelectAutoComplete<Multi extends boolean> extends AutoComplete {
|
||||
static DOMAttachKey = 'ucwUserSelectAutoCompleteInstance';
|
||||
|
||||
constructor($dom, options: AutoCompleteOptions<Multi>) {
|
||||
constructor($dom, options: AutoCompleteOptions<Multi> = {}) {
|
||||
super($dom, {
|
||||
classes: 'user-select',
|
||||
component: UserSelectAutoCompleteFC,
|
||||
props: {
|
||||
multi: options.multi,
|
||||
height: 'auto',
|
||||
},
|
||||
...options,
|
||||
});
|
||||
}
|
||||
@@ -32,18 +20,6 @@ export default class UserSelectAutoComplete<Multi extends boolean> extends AutoC
|
||||
if (this.options.multi) return this.ref?.getSelectedItemKeys().map((i) => +i) ?? this.$dom.val();
|
||||
return this.ref?.getSelectedItems()[0] ?? null;
|
||||
}
|
||||
|
||||
attach() {
|
||||
const value = this.$dom.val();
|
||||
this.component.render(
|
||||
<Component
|
||||
ref={(ref) => { this.ref = ref; }}
|
||||
value={value}
|
||||
multi={this.options.multi}
|
||||
onChange={this.onChange}
|
||||
/>,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
window.Hydro.components.UserSelectAutoComplete = UserSelectAutoComplete;
|
||||
@@ -0,0 +1,43 @@
|
||||
import { CustomSelectAutoComplete } from '@hydrooj/components';
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
|
||||
const prefixes = new Set(Object.keys(window.LANGS).filter((i) => i.includes('.')).map((i) => i.split('.')[0]));
|
||||
const data = Object.keys(window.LANGS).filter((i) => !prefixes.has(i))
|
||||
.map((i) => ({ name: `${i.includes('.') ? `${window.LANGS[i.split('.')[0]].display || ''}/` : ''}${window.LANGS[i].display}`, _id: i }));
|
||||
const withAuto = [...data, { name: 'Auto', _id: 'auto' }];
|
||||
|
||||
const LanguageSelectAutoComplete = React.forwardRef<any, any>((props, ref) => (
|
||||
<CustomSelectAutoComplete
|
||||
ref={ref as any}
|
||||
{...props}
|
||||
data={props.withAuto ? withAuto : data}
|
||||
renderItem={(item) => (prefixes.has(item._id) ? '' : `${item.name}`)}
|
||||
onChange={(val) => {
|
||||
let value = val.split(',');
|
||||
const active = new Set(value.filter((i) => i.includes('.')).map((i) => i.split('.')[0]));
|
||||
value = value.filter((i) => !prefixes.has(i) || active.has(i));
|
||||
for (const i of active) if (!value.includes(i)) value.push(i);
|
||||
props.onChange(value.join(','));
|
||||
}}
|
||||
multi={!!props.multi}
|
||||
/>
|
||||
));
|
||||
|
||||
LanguageSelectAutoComplete.propTypes = {
|
||||
width: PropTypes.string,
|
||||
height: PropTypes.string,
|
||||
listStyle: PropTypes.object,
|
||||
onChange: PropTypes.func.isRequired,
|
||||
multi: PropTypes.bool,
|
||||
selectedKeys: PropTypes.arrayOf(PropTypes.string),
|
||||
allowEmptyQuery: PropTypes.bool,
|
||||
freeSolo: PropTypes.bool,
|
||||
freeSoloConverter: PropTypes.func,
|
||||
data: PropTypes.arrayOf(PropTypes.any),
|
||||
withAuto: PropTypes.bool,
|
||||
};
|
||||
|
||||
LanguageSelectAutoComplete.displayName = 'LanguageSelectAutoComplete';
|
||||
|
||||
export default LanguageSelectAutoComplete;
|
||||
@@ -1,4 +1,5 @@
|
||||
import { AutoComplete as AutoCompleteFC } from '@hydrooj/components';
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom/client';
|
||||
import DOMAttachedObject from 'vj/components/DOMAttachedObject';
|
||||
|
||||
@@ -18,23 +19,23 @@ export interface AutoCompleteOptions<Multi extends boolean = boolean> {
|
||||
text?: () => string;
|
||||
}
|
||||
|
||||
export default class AutoComplete<Options extends Record<string, any> = {}> extends DOMAttachedObject {
|
||||
export default class AutoComplete<Options extends Record<string, any> = {}, Multi extends boolean = boolean> extends DOMAttachedObject {
|
||||
static DOMAttachKey = 'ucwAutoCompleteInstance';
|
||||
ref = null;
|
||||
container = document.createElement('div');
|
||||
options: AutoCompleteOptions & Options;
|
||||
options: AutoCompleteOptions<Multi> & Options;
|
||||
component = ReactDOM.createRoot(this.container);
|
||||
changeListener = [
|
||||
(val) => this.$dom.val(val),
|
||||
];
|
||||
|
||||
constructor($dom, options = {} as Options) {
|
||||
constructor($dom, options = {} as Options & { component?: React.ComponentType<any>, props?: Record<string, any> }) {
|
||||
super($dom);
|
||||
this.options = {
|
||||
items: async () => [],
|
||||
render: () => '',
|
||||
text: () => null,
|
||||
multi: false,
|
||||
multi: false as Multi,
|
||||
...options,
|
||||
};
|
||||
this.clear = this.clear.bind(this);
|
||||
@@ -63,20 +64,21 @@ export default class AutoComplete<Options extends Record<string, any> = {}> exte
|
||||
}
|
||||
|
||||
attach() {
|
||||
const value = this.$dom.val();
|
||||
this.component.render(
|
||||
<AutoCompleteFC
|
||||
const Component = this.options.component || AutoCompleteFC;
|
||||
const Wrapper = (props) => {
|
||||
const [value, setValue] = React.useState(props.value);
|
||||
return <Component
|
||||
ref={(ref) => { this.ref = ref; }}
|
||||
onChange={(v) => {
|
||||
setValue(v);
|
||||
this.onChange(v);
|
||||
}}
|
||||
selectedKeys={(Array.isArray(value) ? value : value.split(',')).map((i) => i.trim()).filter((i) => i)}
|
||||
height="34px"
|
||||
queryItems={this.options.items}
|
||||
renderItem={this.options.render}
|
||||
itemText={this.options.text}
|
||||
selectedKeys={value.split(',').map((i) => i.trim())}
|
||||
onChange={this.onChange}
|
||||
multi={this.options.multi}
|
||||
freeSolo={this.options.multi}
|
||||
/>,
|
||||
);
|
||||
{...this.options.props}
|
||||
/>;
|
||||
};
|
||||
this.component.render(<Wrapper value={this.$dom.val()} />);
|
||||
}
|
||||
|
||||
open() {
|
||||
@@ -89,8 +91,8 @@ export default class AutoComplete<Options extends Record<string, any> = {}> exte
|
||||
this.ref.closeList();
|
||||
}
|
||||
|
||||
value(): any {
|
||||
if (this.options.multi) return this.$dom.val();
|
||||
value(): Multi extends true ? (string | number)[] : string {
|
||||
if (this.options.multi) return this.ref?.getSelectedItemKeys() ?? this.$dom.val();
|
||||
return this.ref?.getSelectedItems()[0] ?? null;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { CustomSelectAutoComplete } from '@hydrooj/components';
|
||||
import React from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { i18n } from 'vj/utils';
|
||||
import FileSelectAutoComplete from '../autocomplete/components/FileSelectAutoComplete';
|
||||
import LanguageSelectAutoComplete from '../autocomplete/components/LanguageSelectAutoComplete';
|
||||
import type { RootState } from './reducer/index';
|
||||
|
||||
export function FormItem({
|
||||
@@ -56,11 +56,6 @@ export function ManagedSelect({ options, formKey }: { options: string[], formKey
|
||||
);
|
||||
}
|
||||
|
||||
const prefixes = new Set(Object.keys(window.LANGS).filter((i) => i.includes('.')).map((i) => i.split('.')[0]));
|
||||
const data = Object.keys(window.LANGS).filter((i) => !prefixes.has(i))
|
||||
.map((i) => ({ name: `${i.includes('.') ? `${window.LANGS[i.split('.')[0]].display || ''}/` : ''}${window.LANGS[i].display}`, _id: i }));
|
||||
data.unshift({ name: 'Auto', _id: 'auto' });
|
||||
|
||||
export function SingleFileSelect({ formKey, withLang = false, label = 'Checker' }: { formKey: FileSelectKey, withLang?: boolean, label?: string }) {
|
||||
const value = useSelector((state: RootState) => state.config[formKey]);
|
||||
const Files = useSelector((state: RootState) => state.testdata);
|
||||
@@ -82,10 +77,10 @@ export function SingleFileSelect({ formKey, withLang = false, label = 'Checker'
|
||||
/>
|
||||
</FormItem>
|
||||
<FormItem columns={3} label="Language">
|
||||
<CustomSelectAutoComplete
|
||||
data={data}
|
||||
<LanguageSelectAutoComplete
|
||||
selectedKeys={[selectedLang]}
|
||||
onChange={(val) => update(selectedFile, val)}
|
||||
withAuto
|
||||
/>
|
||||
</FormItem>
|
||||
</>) : (<FileSelectAutoComplete
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { CustomSelectAutoComplete } from '@hydrooj/components';
|
||||
import {
|
||||
Card, InputGroup, Tag,
|
||||
} from '@blueprintjs/core';
|
||||
@@ -7,6 +6,7 @@ import React from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { i18n } from 'vj/utils';
|
||||
import FileSelectAutoComplete from '../autocomplete/components/FileSelectAutoComplete';
|
||||
import LanguageSelectAutoComplete from '../autocomplete/components/LanguageSelectAutoComplete';
|
||||
import { FormItem } from './BasicForm';
|
||||
import ProblemType from './ProblemType';
|
||||
import type { RootState } from './reducer/index';
|
||||
@@ -67,25 +67,16 @@ function ExtraFilesConfig() {
|
||||
|
||||
function LangConfig() {
|
||||
const langs = useSelector((state: RootState) => state.config.langs) || [];
|
||||
const prefixes = new Set(Object.keys(window.LANGS).filter((i) => i.includes('.')).map((i) => i.split('.')[0]));
|
||||
const data = Object.keys(window.LANGS).filter((i) => !prefixes.has(i))
|
||||
.map((i) => ({ name: `${i.includes('.') ? `${window.LANGS[i.split('.')[0]].display || ''}/` : ''}${window.LANGS[i].display}`, _id: i }));
|
||||
const dispatch = useDispatch();
|
||||
const selectedKeys = langs.filter((i) => !prefixes.has(i));
|
||||
return (
|
||||
<FormItem columns={12} label="langs" disableLabel>
|
||||
<Card style={{ padding: 10 }}>
|
||||
<div className="row">
|
||||
<FormItem columns={12} label="langs">
|
||||
<CustomSelectAutoComplete
|
||||
data={data}
|
||||
placeholder={!selectedKeys.length ? i18n('Unlimited') : i18n('Code language')}
|
||||
selectedKeys={selectedKeys}
|
||||
onChange={(val) => {
|
||||
const value = val.split(',');
|
||||
value.push(...Array.from(new Set(value.filter((i) => i.includes('.')).map((i) => i.split('.')[0]))));
|
||||
dispatch({ type: 'CONFIG_FORM_UPDATE', key: 'langs', value });
|
||||
}}
|
||||
<LanguageSelectAutoComplete
|
||||
placeholder={!langs.length ? i18n('Unlimited') : i18n('Code language')}
|
||||
selectedKeys={langs}
|
||||
onChange={(value) => dispatch({ type: 'CONFIG_FORM_UPDATE', key: 'langs', value })}
|
||||
multi
|
||||
/>
|
||||
</FormItem>
|
||||
|
||||
Reference in new issue
Block a user