diff --git a/package.json b/package.json index a084f85b..2b6dfe99 100644 --- a/package.json +++ b/package.json @@ -55,21 +55,21 @@ "globby": "13.1.3", "inspectpack": "^4.7.1", "latest-version": "7.0.0", - "mini-css-extract-plugin": "^2.7.3", + "mini-css-extract-plugin": "^2.7.5", "monaco-editor-webpack-plugin": "^7.0.1", "mongodb": "^5.1.0", "mongodb-memory-server": "^8.12.0", "nyc": "^15.1.0", "ora": "^6.1.2", - "postcss-loader": "^7.0.2", + "postcss-loader": "^7.1.0", "semver": "^7.3.8", "style-loader": "^3.3.2", "stylus": "^0.59.0", "stylus-loader": "^7.1.0", "supertest": "^6.3.3", "ts-loader": "^9.4.2", - "typescript": "4.9.5", - "webpack": "^5.76.1", + "typescript": "5.0.2", + "webpack": "^5.76.2", "webpack-bundle-analyzer": "^4.8.0", "webpack-dev-server": "^4.12.0", "webpack-manifest-plugin": "^5.0.0", @@ -80,6 +80,7 @@ "@types/node": "18.14.1", "@types/react": "18.0.28", "cosmokit": "1.4.1", + "schemastery": "3.7.2", "prettier": "npm:not-installable-package@1.0.0" } } diff --git a/packages/hydrooj/package.json b/packages/hydrooj/package.json index b52a5dea..fc8b615b 100644 --- a/packages/hydrooj/package.json +++ b/packages/hydrooj/package.json @@ -19,7 +19,7 @@ "@aws-sdk/s3-request-presigner": "^3.290.0", "@graphql-tools/schema": "^9.0.17", "@hydrooj/utils": "workspace:*", - "@simplewebauthn/server": "^7.0.1", + "@simplewebauthn/server": "^7.2.0", "adm-zip": "0.5.5", "cac": "^6.7.14", "cordis": "^2.7.4", @@ -28,7 +28,7 @@ "emojis-list": "2.1.0", "fs-extra": "^11.1.0", "graphql": "^16.6.0", - "graphql-scalars": "1.20.1", + "graphql-scalars": "1.20.4", "js-yaml": "^4.1.0", "koa": "^2.14.1", "koa-body": "^6.0.1", diff --git a/packages/hydrooj/src/interface.ts b/packages/hydrooj/src/interface.ts index 78840446..3e108da1 100644 --- a/packages/hydrooj/src/interface.ts +++ b/packages/hydrooj/src/interface.ts @@ -5,6 +5,7 @@ import type fs from 'fs'; import type { Dictionary, NumericDictionary } from 'lodash'; import type { Binary, FindCursor, ObjectId } from 'mongodb'; import type { Context } from './context'; +import type { DocStatusType } from './model/document'; import type { ProblemDoc } from './model/problem'; import type { Handler } from './service/server'; @@ -649,7 +650,9 @@ declare module './service/db' { 'domain.user': any; 'record': RecordDoc; 'document': any; - 'document.status': StatusDocBase; + 'document.status': StatusDocBase & { + [K in keyof DocStatusType]: { docType: K } & DocStatusType[K]; + }[keyof DocStatusType]; 'discussion.history': DiscussionHistoryDoc; 'user': Udoc; 'user.preference': UserPreferenceDoc; diff --git a/packages/hydrooj/src/model/document.ts b/packages/hydrooj/src/model/document.ts index ae5c6803..bad2e6a7 100644 --- a/packages/hydrooj/src/model/document.ts +++ b/packages/hydrooj/src/model/document.ts @@ -44,6 +44,7 @@ export interface DocType { export interface DocStatusType { [TYPE_PROBLEM]: ProblemStatusDoc, + // FIXME: this need to be typed [key: number]: any } @@ -396,6 +397,7 @@ export async function revPushStatus( if (!res.value) { res = await collStatus.findOneAndUpdate( { domainId, docType, docId, uid }, + // @ts-ignore { $push: { [key]: value }, $inc: { rev: 1 } }, { upsert: true, returnDocument: 'after' }, ); diff --git a/packages/hydrooj/src/service/decorators.ts b/packages/hydrooj/src/service/decorators.ts index 40151dc0..2e8792e8 100644 --- a/packages/hydrooj/src/service/decorators.ts +++ b/packages/hydrooj/src/service/decorators.ts @@ -5,7 +5,8 @@ import { EventMap } from './bus'; import type { Handler } from './server'; type MethodDecorator = (target: any, funcName: string, obj: any) => any; -type ClassDecorator = any>(Class: T) => T extends new (...args: infer R) => infer S ? (...args: R) => S : never; +type ClassDecorator = any>(Class: T) => T extends new (...args: infer R) => infer S + ? new (...args: R) => S : never; export interface ParamOption { name: string, source: 'all' | 'get' | 'post' | 'route', @@ -85,7 +86,7 @@ export const post: DescriptorBuilder = (name, ...args) => _descriptor(_buildPara export const route: DescriptorBuilder = (name, ...args) => _descriptor(_buildParam(name, 'route', ...args)); export const param: DescriptorBuilder = (name, ...args) => _descriptor(_buildParam(name, 'all', ...args)); -export const subscribe: (name: keyof EventMap) => MethodDecorator | ClassDecorator = (name) => (target, funcName, obj) => { +export const subscribe: (name: keyof EventMap) => MethodDecorator & ClassDecorator = (name) => (target, funcName?, obj?) => { if (funcName) { target.__subscribe ||= []; target.__subscribe.push({ name, target: obj.value }); diff --git a/packages/ui-default/components/autocomplete/components/AutoComplete.tsx b/packages/ui-default/components/autocomplete/components/AutoComplete.tsx index f67a4f87..ee0fd0c1 100644 --- a/packages/ui-default/components/autocomplete/components/AutoComplete.tsx +++ b/packages/ui-default/components/autocomplete/components/AutoComplete.tsx @@ -34,6 +34,7 @@ export interface AutoCompleteProps { allowEmptyQuery?: boolean; freeSolo?: boolean; freeSoloConverter?: (value: string) => string; + alwaysShowList?: boolean; } export interface AutoCompleteHandle { @@ -97,6 +98,7 @@ const AutoComplete = forwardRef(function Impl(props: AutoCompleteProps, re const allowEmptyQuery = props.allowEmptyQuery ?? false; const freeSolo = props.freeSolo ?? false; const freeSoloConverter = freeSolo ? props.freeSoloConverter ?? ((i) => i) : ((i) => i); + const alwaysShowList = props.alwaysShowList ?? false; const [focused, setFocused] = useState(false); // is focused const [selected, setSelected] = useState([]); // selected items @@ -152,7 +154,7 @@ const AutoComplete = forwardRef(function Impl(props: AutoCompleteProps, re const handleInputChange = debounce((e?) => queryList(e ? e.target.value : ''), 300); const toggleItem = (item: T, key = itemKey(item), preserve = false) => { - const shouldKeepOpen = allowEmptyQuery && inputRef.current.value === '' && !multi; + const shouldKeepOpen = (multi ? alwaysShowList : allowEmptyQuery) && inputRef.current.value === ''; if (multi) { const idx = selectedKeys.indexOf(key); if (idx !== -1) { diff --git a/packages/ui-default/components/autocomplete/components/CustomSelectAutoComplete.tsx b/packages/ui-default/components/autocomplete/components/CustomSelectAutoComplete.tsx index 9afc9b17..76d4f4cb 100644 --- a/packages/ui-default/components/autocomplete/components/CustomSelectAutoComplete.tsx +++ b/packages/ui-default/components/autocomplete/components/CustomSelectAutoComplete.tsx @@ -46,6 +46,7 @@ CustomSelectAutoComplete.defaultProps = { selectedKeys: [], freeSolo: false, freeSoloConverter: (input) => input, + alwaysShowList: true, }; export default CustomSelectAutoComplete; diff --git a/packages/ui-default/components/autocomplete/components/FileSelectAutoComplete.tsx b/packages/ui-default/components/autocomplete/components/FileSelectAutoComplete.tsx index 2c13d905..49e34520 100644 --- a/packages/ui-default/components/autocomplete/components/FileSelectAutoComplete.tsx +++ b/packages/ui-default/components/autocomplete/components/FileSelectAutoComplete.tsx @@ -46,6 +46,7 @@ FileSelectAutoComplete.defaultProps = { selectedKeys: [], freeSolo: false, freeSoloConverter: (input) => input, + alwaysShowList: true, }; export default FileSelectAutoComplete; diff --git a/packages/ui-default/components/problemconfig/ProblemConfigForm.tsx b/packages/ui-default/components/problemconfig/ProblemConfigForm.tsx index e606ceed..c9c95079 100644 --- a/packages/ui-default/components/problemconfig/ProblemConfigForm.tsx +++ b/packages/ui-default/components/problemconfig/ProblemConfigForm.tsx @@ -85,7 +85,7 @@ function LangConfig() { { const value = val.split(','); diff --git a/packages/ui-default/components/problemconfig/ProblemConfigTree.tsx b/packages/ui-default/components/problemconfig/ProblemConfigTree.tsx index 34a75589..fcbd219d 100644 --- a/packages/ui-default/components/problemconfig/ProblemConfigTree.tsx +++ b/packages/ui-default/components/problemconfig/ProblemConfigTree.tsx @@ -79,7 +79,7 @@ export function SubtaskNode(props: { subtaskId: number }) { }
    - {subtaskId !== -1 && } + {subtaskId !== -1 && } {expand ? : `${i.instancePath}: ${i.message}`) }; } const subtasks = (data as any).subtasks; - for (const subtask of subtasks || []) { + for (const subtask of subtasks) { if (typeof subtask.id !== 'number') { for (let i = 1; ; i++) { if (!subtasks.find((s) => s.id === i)) { diff --git a/packages/ui-default/components/problemconfig/tree/AddTestcase.tsx b/packages/ui-default/components/problemconfig/tree/AddTestcase.tsx index abfbea4b..32e63841 100644 --- a/packages/ui-default/components/problemconfig/tree/AddTestcase.tsx +++ b/packages/ui-default/components/problemconfig/tree/AddTestcase.tsx @@ -13,31 +13,47 @@ export function AddTestcase() { const [input, setInput] = React.useState(''); const [output, setOutput] = React.useState(''); const [valid, setValid] = React.useState(false); + const [autoInput, setAutoInput] = React.useState(true); + const [autoOutput, setAutoOutput] = React.useState(true); const testdata = useSelector((state: RootState) => state.testdata); const store = useStore(); const refInput = useRef(); const refOutput = useRef(); useEffect(() => { - setValid(testdata.find((i) => i.name === input) && testdata.find((i) => i.name === output)); - if (input && !output) { + const inputValid = testdata.find((i) => i.name === input); + if (input) setAutoInput(false); + else setAutoInput(true); + setValid(inputValid); + if (inputValid && autoOutput) { const filename = input.substring(0, input.lastIndexOf('.')); let outputFile = ''; if (testdata.find((i) => i.name === `${filename}.out`)) outputFile = `${filename}.out`; else if (testdata.find((i) => i.name === `${filename}.ans`)) outputFile = `${filename}.ans`; - // @ts-ignore - refOutput.current!.setSelectedItems([outputFile]); - setOutput(outputFile); + if (outputFile) { + // @ts-ignore + refOutput.current!.setSelectedItems([outputFile]); + setOutput(outputFile); + setAutoOutput(true); + } } - if (output && !input) { + }, [input]); + + useEffect(() => { + const outputValid = testdata.find((i) => i.name === output); + if (output) setAutoOutput(false); + else setAutoOutput(true); + setValid(outputValid); + if (outputValid && autoInput) { const filename = output.substring(0, output.lastIndexOf('.')); if (testdata.find((i) => i.name === `${filename}.in`)) { // @ts-ignore refInput.current!.setSelectedItems([`${filename}.in`]); setInput(`${filename}.in`); + setAutoInput(true); } } - }, [input, output]); + }, [output]); function onConfirm() { store.dispatch({ @@ -81,6 +97,8 @@ export function AddTestcase() { onClick={() => { setInput(''); setOutput(''); + setAutoInput(true); + setAutoOutput(true); setOpen(true); }} > diff --git a/packages/ui-default/components/problemconfig/tree/SubtaskSettings.tsx b/packages/ui-default/components/problemconfig/tree/SubtaskSettings.tsx index 8caf21aa..f90802be 100644 --- a/packages/ui-default/components/problemconfig/tree/SubtaskSettings.tsx +++ b/packages/ui-default/components/problemconfig/tree/SubtaskSettings.tsx @@ -8,9 +8,11 @@ import { isEqual } from 'lodash'; import React, { useEffect } from 'react'; import { useDispatch, useSelector } from 'react-redux'; import { i18n } from 'vj/utils'; +import CustomSelectAutoComplete from '../../autocomplete/components/CustomSelectAutoComplete'; import { RootState } from '../reducer'; interface SubtaskSettingsProps { + subtaskIds: number[]; subtaskId: number; time: string; memory: string; @@ -19,6 +21,7 @@ interface SubtaskSettingsProps { export function SubtaskSettings(props: SubtaskSettingsProps) { const [open, setOpen] = React.useState(false); const [depsOpen, setDepsOpen] = React.useState(false); + const subtaskIds = props.subtaskIds.filter((i) => i !== props.subtaskId); const score = useSelector((state: RootState) => state.config.subtasks.find((i) => i.id === props.subtaskId).score); const time = useSelector((state: RootState) => state.config.subtasks.find((i) => i.id === props.subtaskId).time); const memory = useSelector((state: RootState) => state.config.subtasks.find((i) => i.id === props.subtaskId).memory); @@ -88,14 +91,13 @@ export function SubtaskSettings(props: SubtaskSettingsProps) { setDepsOpen(false)}> - - } - onChange={(ev) => setDeps(ev.currentTarget.value)} - placeholder={'Dependencies'} - value={cdeps || ''} - /> - + ({ _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 + /> } /> diff --git a/packages/ui-default/package.json b/packages/ui-default/package.json index 8b3e44f1..56d7bfca 100644 --- a/packages/ui-default/package.json +++ b/packages/ui-default/package.json @@ -1,6 +1,6 @@ { "name": "@hydrooj/ui-default", - "version": "4.48.5", + "version": "4.48.6", "author": "undefined ", "license": "AGPL-3.0", "main": "index.ts", @@ -25,7 +25,7 @@ "@fontsource/source-code-pro": "^4.5.14", "@fontsource/ubuntu-mono": "^4.5.11", "@hydrooj/utils": "workspace:*", - "@simplewebauthn/browser": "^7.1.0", + "@simplewebauthn/browser": "^7.2.0", "@svgr/webpack": "^6.5.1", "@types/gulp-if": "^0.0.34", "@types/jquery": "^3.5.16", @@ -37,8 +37,8 @@ "@types/react": "^18.0.28", "@types/react-dom": "^18.0.11", "@types/redux-logger": "^3.0.9", - "@types/serviceworker": "^0.0.65", - "@types/sharedworker": "^0.0.94", + "@types/serviceworker": "^0.0.66", + "@types/sharedworker": "^0.0.95", "@types/webpack-env": "^1.18.0", "@vscode/codicons": "^0.0.32", "ajv": "^8.12.0", diff --git a/packages/ui-default/pages/problem_config.page.styl b/packages/ui-default/pages/problem_config.page.styl index 19c6be2b..94ff29e7 100644 --- a/packages/ui-default/pages/problem_config.page.styl +++ b/packages/ui-default/pages/problem_config.page.styl @@ -29,6 +29,9 @@ .button margin: 0 + input::placeholder + color: rgba(95,107,124,.6)!important + .bp4-input-group input appearance: none @@ -46,6 +49,12 @@ transition-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1) box-shadow: none + .bp4-tag.bp4-minimal + background: transparent + + .bp4-input-action + pointer-events: none + .problem-config-form font-family: var(--code-font-family) @@ -54,25 +63,6 @@ input[type="number"] -moz-appearance: textfield - .bp4-tag.bp4-minimal - background: transparent - .bp4-input-action - pointer-events: none - - .data-table - tr - border-top: 0 - border-bottom: 0 - height: auto - td - padding: 0 - .thead td - text-align: center - td input, td div - background: transparent - .hover-row.status-bar display: none - - input::placeholder - color: rgba(95,107,124,.6)!important \ No newline at end of file + \ No newline at end of file