From 377ef55d49a721d090b3e926f78334f93374cbbf Mon Sep 17 00:00:00 2001 From: undefined Date: Fri, 7 May 2021 20:00:04 +0800 Subject: [PATCH 1/5] =?UTF-8?q?feats::=20=E7=BB=9F=E4=B8=80=E8=AF=AD?= =?UTF-8?q?=E8=A8=80=E9=85=8D=E7=BD=AE=E4=BD=8D=E7=BD=AE=20=E6=94=AF?= =?UTF-8?q?=E6=8C=81langs=E7=BB=A7=E6=89=BF=20=E6=94=AF=E6=8C=81=E5=BD=92?= =?UTF-8?q?=E7=B1=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/hydrojudge/package.json | 5 +- packages/hydrojudge/setting.yaml | 100 +---- packages/hydrojudge/src/check.ts | 4 +- packages/hydrojudge/src/compile.ts | 30 +- packages/hydrojudge/src/hosts/hydro.ts | 25 +- packages/hydrojudge/src/hosts/vj4.ts | 420 +----------------- packages/hydrojudge/src/judge/default.ts | 16 +- packages/hydrojudge/src/judge/hack.ts | 7 +- packages/hydrojudge/src/judge/interactive.ts | 15 +- packages/hydrojudge/src/judge/run.ts | 2 +- packages/hydrojudge/src/service.ts | 7 + packages/hydrooj/locales/zh.yaml | 4 +- packages/hydrooj/package.json | 4 +- packages/hydrooj/setting.yaml | 98 ++++ packages/hydrooj/src/handler/judge.ts | 17 +- packages/hydrooj/src/model/builtin.ts | 18 - packages/hydrooj/src/model/setting.ts | 24 +- packages/hydrooj/src/model/system.ts | 7 + .../scratchpad/ScratchpadEditorContainer.jsx | 2 +- .../scratchpad/ScratchpadToolbarContainer.jsx | 4 +- packages/ui-default/handler.js | 10 +- packages/ui-default/locales/zh.yaml | 1 - packages/ui-default/package.json | 4 +- packages/ui-default/setting.yaml | 38 -- .../ui-default/templates/record_detail.html | 2 +- packages/utils/lib/lang.ts | 45 ++ packages/utils/package.json | 2 +- 27 files changed, 253 insertions(+), 658 deletions(-) create mode 100644 packages/hydrooj/setting.yaml create mode 100644 packages/utils/lib/lang.ts diff --git a/packages/hydrojudge/package.json b/packages/hydrojudge/package.json index 71a027912..252f2e1c4 100644 --- a/packages/hydrojudge/package.json +++ b/packages/hydrojudge/package.json @@ -1,13 +1,12 @@ { "name": "@hydrooj/hydrojudge", "bin": "bin/hydrojudge.js", - "version": "2.5.21", + "version": "2.6.0", "main": "package.json", "author": "undefined ", "repository": "https://github.com/hydro-dev/Hydro.git", "dependencies": { - "@hydrooj/utils": "^1.0.9", - "adm-zip": "^0.5.5", + "@hydrooj/utils": "^1.0.14", "axios": "^0.21.1", "bson": "^4.3.0", "fs-extra": "^9.1.0", diff --git a/packages/hydrojudge/setting.yaml b/packages/hydrojudge/setting.yaml index 6fede66ee..e549c01a7 100644 --- a/packages/hydrojudge/setting.yaml +++ b/packages/hydrojudge/setting.yaml @@ -51,102 +51,4 @@ disable: type: boolean name: disable desc: Disable builtin judge - default: false -langs: - type: textarea - name: langs - desc: Language file settings - default: | - ccWithoutO2: # Do not remove this entry - type: compiler - compile: /usr/bin/g++ -Wall -o ${name} foo.cc - code_file: foo.cc - execute: ${dir}/${name} - c: - type: compiler - compile: /usr/bin/gcc -O2 -Wall -std=c99 -o ${name} foo.c -lm - code_file: foo.c - execute: ${dir}/${name} - cc: - type: compiler - compile: /usr/bin/g++ -O2 -Wall -std=c++11 -o ${name} foo.cc -lm - code_file: foo.cc - execute: ${dir}/${name} - cc98: - type: compiler - compile: /usr/bin/g++ -O2 -Wall -std=c++98 -o ${name} foo.cc -lm - code_file: foo.cc - execute: ${dir}/${name} - cc11: - type: compiler - compile: /usr/bin/g++ -O2 -Wall -std=c++11 -o ${name} foo.cc -lm - code_file: foo.cc - execute: ${dir}/${name} - cc17: - type: compiler - compile: /usr/bin/g++ -O2 -Wall -std=c++17 -o ${name} foo.cc -lm - code_file: foo.cc - execute: ${dir}/${name} - pas: - type: compiler - compile: /usr/bin/fpc -O2 -o${dir}/${name} foo.pas - code_file: foo.pas - execute: ${dir}/${name} - java: - type: compiler - compile: /usr/bin/javac -d ${dir} -encoding utf8 ./Main.java - code_file: Main.java - target: Main.class - execute: /usr/bin/java Main - time_limit_rate: 2 - py: - type: compiler - compile: /usr/bin/python -c "import py_compile; py_compile.compile('${dir}/foo.py', '${dir}/${name}', doraise=True)" - code_file: foo.py - execute: /usr/bin/python ${name} - py2: - type: compiler - compile: /usr/bin/python -c "import py_compile; py_compile.compile('${dir}/foo.py', '${dir}/${name}', doraise=True)" - code_file: foo.py - execute: /usr/bin/python ${name} - py3: - type: compiler - compile: /usr/bin/python3 -c "import py_compile; py_compile.compile('${dir}/foo.py', '${dir}/${name}', doraise=True)" - code_file: foo.py - execute: /usr/bin/python3 ${name} - php: - type: interpreter - code_file: foo.php - execute: /usr/bin/php foo.php - rs: - type: compiler - compile: /usr/bin/rustc -O -o ${dir}/${name} ${dir}/foo.rs - code_file: foo.rs - execute: ${dir}/${name} - hs: - type: compiler - compile: /usr/bin/ghc -O -outputdir /tmp -o ${name} foo.hs - code_file: foo.hs - execute: ${dir}/${name} - js: - type: interpreter - code_file: foo.js - execute: /usr/bin/jsc ${dir}/foo.js - go: - type: compiler - compile: /usr/bin/go build -o ${name} foo.go - code_file: foo.go - execute: ${dir}/${name} - rb: - type: interpreter - code_file: foo.rb - execute: /usr/bin/ruby foo.rb - cs: - type: compiler - compile: /usr/bin/mcs -optimize+ -out:${dir}/${name} ${dir}/foo.cs - code_file: foo.cs - execute: /usr/bin/mono ${name} - jl: - type: interpreter - code_file: foo.jl - execute: /usr/bin/julia ${dir}/foo.jl + default: false \ No newline at end of file diff --git a/packages/hydrojudge/src/check.ts b/packages/hydrojudge/src/check.ts index 6085cba5a..85b8b6573 100644 --- a/packages/hydrojudge/src/check.ts +++ b/packages/hydrojudge/src/check.ts @@ -24,10 +24,10 @@ export async function check(config): Promise<[number, number, string]> { return [status, score, message]; } -export async function compileChecker(checkerType: string, checker: string, copyIn: any) { +export async function compileChecker(getLang: Function, checkerType: string, checker: string, copyIn: any) { if (!checkers[checkerType]) throw new SystemError('Unknown checker type {0}.', [checkerType]); if (checkerType === 'testlib') copyIn['testlib.h'] = { src: testlibSrc }; const file = await fs.readFile(checker); // TODO cache compiled checker - return await compile(parseFilename(checker).split('.')[1], file.toString(), 'checker', copyIn); + return await compile(getLang(parseFilename(checker).split('.')[1]), file.toString(), 'checker', copyIn); } diff --git a/packages/hydrojudge/src/compile.ts b/packages/hydrojudge/src/compile.ts index ea2d644f2..cd53fe7ed 100644 --- a/packages/hydrojudge/src/compile.ts +++ b/packages/hydrojudge/src/compile.ts @@ -1,25 +1,21 @@ -import yaml from 'js-yaml'; +import { LangConfig } from '@hydrooj/utils/lib/lang'; import * as STATUS from './status'; import { run, del } from './sandbox'; -import { CompileError, SystemError } from './error'; +import { CompileError } from './error'; import { compilerText } from './utils'; import { Execute } from './interface'; -import { getConfig } from './config'; export = async function compile( - lang: string, code: string, target: string, copyIn: any, next?: Function, + lang: LangConfig, code: string, target: string, copyIn: any, next?: Function, ): Promise { - const LANGS = yaml.load(getConfig('langs')); - if (!LANGS[lang]) throw new SystemError('Unsupported language {0}.', [lang]); - const info = LANGS[lang]; - target = info.target || target; + target = lang.target || target; const f = {}; - if (info.type === 'compiler') { - copyIn[info.code_file] = { content: code }; + if (lang.compile) { + copyIn[lang.code_file] = { content: code }; const { status, stdout, stderr, fileIds, } = await run( - info.compile.replace(/\$\{name\}/g, target), + lang.compile.replace(/\$\{name\}/g, target), { copyIn, copyOutCached: [target] }, ); if (status !== STATUS.STATUS_ACCEPTED) throw new CompileError({ status, stdout, stderr }); @@ -27,13 +23,11 @@ export = async function compile( if (next) next({ compiler_text: compilerText(stdout, stderr) }); f[target] = { fileId: fileIds[target] }; return { - execute: info.execute, copyIn: f, clean: () => del(fileIds[target]), time: info.time || 1, - }; - } if (info.type === 'interpreter') { - f[target] = { content: code }; - return { - execute: info.execute, copyIn: f, clean: () => Promise.resolve(null), time: info.time || 1, + execute: lang.execute, copyIn: f, clean: () => del(fileIds[target]), time: lang.time_limit_rate || 1, }; } - throw new SystemError('Unknown language type.'); + f[target] = { content: code }; + return { + execute: lang.execute, copyIn: f, clean: () => Promise.resolve(null), time: lang.time_limit_rate || 1, + }; }; diff --git a/packages/hydrojudge/src/hosts/hydro.ts b/packages/hydrojudge/src/hosts/hydro.ts index f9e87de13..734eb26b0 100644 --- a/packages/hydrojudge/src/hosts/hydro.ts +++ b/packages/hydrojudge/src/hosts/hydro.ts @@ -6,6 +6,7 @@ import WebSocket from 'ws'; import { argv } from 'yargs'; import { noop } from 'lodash'; import { ObjectID } from 'bson'; +import { LangConfig } from '@hydrooj/utils/lib/lang'; import * as tmpfs from '../tmpfs'; import log from '../log'; import { compilerText, Queue } from '../utils'; @@ -37,6 +38,7 @@ class JudgeTask { config: any; nextId = 1; nextWaiting = []; + getLang: (name: string) => LangConfig; constructor(session: Hydro, request, ws: WebSocket) { this.stat = {}; @@ -45,6 +47,7 @@ class JudgeTask { this.host = session.config.host; this.request = request; this.ws = ws; + this.getLang = session.getLang; } async handle() { @@ -160,10 +163,9 @@ class JudgeTask { export default class Hydro { config: any; - axios: any; - ws: WebSocket; + language: Record; constructor(config) { this.config = config; @@ -172,6 +174,7 @@ export default class Hydro { this.config.last_update_at = this.config.last_update_at || 0; if (!this.config.server_url.startsWith('http')) this.config.server_url = `http://${this.config.server_url}`; if (!this.config.server_url.endsWith('/')) this.config.server_url = `${this.config.server_url}/`; + this.getLang = this.getLang.bind(this); } async init() { @@ -224,6 +227,11 @@ export default class Hydro { return filePath; } + getLang(name: string) { + if (this.language[name]) return this.language[name]; + throw new SystemError('Unsupported language {0}', [name]); + } + async consume(queue: Queue) { log.info('正在连接 %sjudge/conn', this.config.server_url); const res = await this.axios.get('judge/conn/info'); @@ -238,19 +246,16 @@ export default class Hydro { }); this.ws.on('message', (data) => { const request = JSON.parse(data.toString()); + if (request.language) this.language = request.language; if (request.task) queue.push(new JudgeTask(this, request.task, this.ws)); }); this.ws.on('close', (data, reason) => { log.warn(`[${this.config.host}] Websocket 断开:`, data, reason); - setTimeout(() => { - this.retry(queue); - }, 30000); + setTimeout(() => this.retry(queue), 30000); }); this.ws.on('error', (e) => { log.error(`[${this.config.host}] Websocket 错误:`, e); - setTimeout(() => { - this.retry(queue); - }, 30000); + setTimeout(() => this.retry(queue), 30000); }); await new Promise((resolve) => { this.ws.once('open', async () => { @@ -327,9 +332,7 @@ export default class Hydro { async retry(queue: Queue) { this.consume(queue).catch(() => { - setTimeout(() => { - this.retry(queue); - }, 30000); + setTimeout(() => this.retry(queue), 30000); }); } } diff --git a/packages/hydrojudge/src/hosts/vj4.ts b/packages/hydrojudge/src/hosts/vj4.ts index c770f1980..c82a18b6f 100644 --- a/packages/hydrojudge/src/hosts/vj4.ts +++ b/packages/hydrojudge/src/hosts/vj4.ts @@ -1,419 +1,7 @@ -/* eslint-disable no-await-in-loop */ -import path from 'path'; -import axios from 'axios'; -import AdmZip from 'adm-zip'; -import fs from 'fs-extra'; -import WebSocket from 'ws'; -import { noop } from 'lodash'; -import * as tmpfs from '../tmpfs'; -import log from '../log'; -import { compilerText, Queue } from '../utils'; -import { getConfig } from '../config'; -import { FormatError, CompileError, SystemError } from '../error'; -import { STATUS_COMPILE_ERROR, STATUS_SYSTEM_ERROR } from '../status'; -import readCases from '../cases'; -import judge from '../judge'; - -class JudgeTask { - stat: Record; - session: any; - host: string; - request: any; - ws: WebSocket; - tag: any; - type: any; - domain_id: string; - pid: string; - rid: string; - lang: string; - code: string; - tmpdir: string; - clean: Function[]; - folder: string; - config: any; - nextId = 1; - nextWaiting = []; - - constructor(session: VJ4, request, ws: WebSocket) { - this.stat = {}; - this.stat.receive = new Date(); - this.session = session; - this.host = session.config.host; - this.request = request; - this.ws = ws; - } - - async handle() { - this.next = this.next.bind(this); - this.end = this.end.bind(this); - this.stat.handle = new Date(); - this.tag = this.request.tag; - this.type = this.request.type; - this.domain_id = this.request.domain_id; - this.pid = this.request.pid; - this.rid = this.request.rid; - this.lang = this.request.lang; - this.code = this.request.code; - this.tmpdir = path.resolve(getConfig('tmp_dir'), 'tmp', this.host, this.rid); - this.clean = []; - fs.ensureDirSync(this.tmpdir); - tmpfs.mount(this.tmpdir, '512m'); - log.info('Submission: %s/%s/%s pid=%s', this.host, this.domain_id, this.rid, this.pid); - try { - if (this.type === 0) await this.doSubmission(); - else if (this.type === 1) await this.doPretest(); - else throw new SystemError(`Unsupported type: ${this.type}`); - } catch (e) { - if (e instanceof CompileError) { - this.next({ compiler_text: compilerText(e.stdout, e.stderr) }); - this.end({ - status: STATUS_COMPILE_ERROR, score: 0, time_ms: 0, memory_kb: 0, - }); - } else if (e instanceof FormatError) { - this.next({ judge_text: '题目配置出现错误。请联系题目上传者。' }); - this.next({ judge_text: `${e.message}\n${JSON.stringify(e.params)}` }); - this.end({ - status: STATUS_SYSTEM_ERROR, score: 0, time_ms: 0, memory_kb: 0, - }); - } else { - log.error(e); - this.next({ judge_text: `${e.message}\n${e.stack}\n${JSON.stringify(e.params)}` }); - this.end({ - status: STATUS_SYSTEM_ERROR, score: 0, time_ms: 0, memory_kb: 0, - }); - } - } - for (const clean of this.clean) await clean().catch(noop); - tmpfs.umount(this.tmpdir); - fs.removeSync(this.tmpdir); - } - - async doSubmission() { - this.stat.cache_start = new Date(); - this.folder = await this.session.cacheOpen(this.domain_id, this.pid, this.next); - this.stat.read_cases = new Date(); - this.config = await readCases( - this.folder, - { detail: this.session.config.detail }, - { next: this.next }, - ); - this.stat.judge = new Date(); - await judge[this.config.type || 'default'].judge(this); - } - - async doPretest() { - this.folder = path.resolve(this.tmpdir, 'data'); - await this.session.recordPretestData(this.rid, this.folder); - this.config = await readCases( - this.folder, - { detail: this.session.config.detail }, - { next: this.next }, - ); - await judge[this.config.type || 'default'].judge(this); - } - - next(data, id?: number) { - if (data.message !== undefined) { - data.judge_text = data.message; - delete data.message; - } - if (data.judge_text && typeof data.judge_text !== 'string') { - data.judge_text = data.judge_text.message.format(data.judge_text.params); - } - if (data.case?.message !== undefined) { - data.case.judge_text = data.case.message; - delete data.case.message; - } - if (data.case?.judge_text && typeof data.case?.judge_text !== 'string') { - data.case.judge_text = data.case.judge_text.message.format(data.case.judge_text.params); - } - data.key = 'next'; - data.tag = this.tag; - if (id) { - if (id === this.nextId) { - this.ws.send(JSON.stringify(data)); - this.nextId++; - let t = true; - while (t) { - t = false; - for (const i in this.nextWaiting) { - if (this.nextId === this.nextWaiting[i].id) { - this.ws.send(JSON.stringify(this.nextWaiting[i].data)); - this.nextId++; - this.nextWaiting.splice(+i, 1); - t = true; - } - } - } - } else this.nextWaiting.push({ data, id }); - } else this.ws.send(JSON.stringify(data)); - } - - end(data) { - this.ws.send(JSON.stringify({ - key: 'next', - tag: this.tag, - judge_text: '自豪地采用 HydroJudge(https://github.com/hydro-dev/Hydro) 进行评测。', - })); - if (data.message !== undefined) { - data.judge_text = data.message; - delete data.message; - } - data.key = 'end'; - data.tag = this.tag; - log.info('Status=%d Score=%s Time=%dms Memory=%dkb', data.status, data.score, data.time_ms, data.memory_kb); - this.ws.send(JSON.stringify(data)); - } -} - export default class VJ4 { - config: any; - axios: any; - ws: WebSocket; - - constructor(config) { - this.config = config; - this.config.detail = this.config.detail ?? true; - this.config.cookie = this.config.cookie || ''; - this.config.last_update_at = this.config.last_update_at || 0; - if (!this.config.server_url.startsWith('http')) this.config.server_url = `http://${this.config.server_url}`; - if (!this.config.server_url.endsWith('/')) this.config.server_url = `${this.config.server_url}/`; - } - - async init() { - await this.setCookie(this.config.cookie || ''); - await this.ensureLogin(); - setInterval(() => { this.axios.get('judge/noop'); }, 30000000); - } - - async problemDataVersion(domainId: string, pid: string, retry = 3) { - let location: string; - let err: Error; - await this.ensureLogin(); - try { - await this.axios.get(`d/${domainId}/p/${pid}/data`, { maxRedirects: 0 }); - } catch (res) { - res.response = res.response || {}; - if (res.response.status === 302) { - location = res.response.headers.location; - if (location.includes('/fs/')) return location; - } else if (res.response.status === 404) { - throw new FormatError(`没有找到测试数据 ${domainId}/${pid}`); - } else { - if (retry) return await this.problemDataVersion(domainId, pid, retry - 1); - res.config = res.request = null; - err = res; - log.error('%o', err); - } - } - if (!location) return 'unknown'; - try { - await this.axios.get(location, { maxRedirects: 0 }); - } catch (res) { - res.response = res.response || {}; - if (res.response.status === 302) return res.response.headers.location; - if (res.response.status === 404) throw new FormatError(`没有找到测试数据 ${domainId}/${pid}`); - else { - if (retry) return await this.problemDataVersion(domainId, pid, retry - 1); - res.config = res.request = null; - err = res; - log.error(err); - } - } - return 'unknown'; - } - - async problemData(domainId: string, pid: string, savePath: string, retry = 3, next?) { - log.info(`Getting problem data: ${this.config.host}/${domainId}/${pid}`); - await this.ensureLogin(); - if (next) next({ judge_text: '正在同步测试数据,请稍后' }); - const tmpFilePath = path.resolve(getConfig('cache_dir'), `download_${this.config.host}_${domainId}_${pid}`); - try { - const res = await this.axios.get( - `${this.config.server_url}d/${domainId}/p/${pid}/data`, - { responseType: 'stream' }, - ); - const w = fs.createWriteStream(tmpFilePath); - res.data.pipe(w); - await new Promise((resolve, reject) => { - w.on('finish', resolve); - w.on('error', reject); - }); - fs.ensureDirSync(path.dirname(savePath)); - const zip = new AdmZip(tmpFilePath); - const entries = zip.getEntries(); - if (entries.length > 512) throw new FormatError('Too many files'); - await new Promise((resolve, reject) => { - zip.extractAllToAsync(savePath, true, (e) => { - if (e) reject(e); - else resolve(null); - }); - }); - await fs.unlink(tmpFilePath); - await this.processData(savePath).catch(noop); - } catch (e) { - if (retry) await this.problemData(domainId, pid, savePath, retry - 1); - else throw e; - } - return savePath; - } - - async recordPretestData(rid: string, savePath: string) { - log.info(`Getting pretest data: ${this.config.host}/${rid}`); - const tmpFilePath = path.resolve(getConfig('cache_dir'), `download_${this.config.host}_${rid}`); - await this.ensureLogin(); - const res = await this.axios.get(`records/${rid}/data`, { responseType: 'stream' }); - const w = fs.createWriteStream(tmpFilePath); - res.data.pipe(w); - await new Promise((resolve, reject) => { - w.on('finish', resolve); - w.on('error', reject); - }); - fs.ensureDirSync(path.dirname(savePath)); - const zip = new AdmZip(tmpFilePath); - const entries = zip.getEntries(); - if (entries.length > 512) throw new FormatError('Too many files'); - await new Promise((resolve, reject) => { - zip.extractAllToAsync(savePath, true, (e) => { - if (e) reject(e); - else resolve(null); - }); - }); - await fs.unlink(tmpFilePath); - await this.processData(savePath); - return savePath; - } - - async consume(queue: Queue) { - log.info('正在连接 %sjudge/consume-conn', this.config.server_url); - const res = await this.axios.get('judge/consume-conn/info'); - this.ws = new WebSocket(`${this.config.server_url.replace(/^http/i, 'ws')}judge/consume-conn/websocket?t=${res.data.entropy}`, { - headers: { cookie: this.config.cookie }, - }); - this.ws.on('message', (data) => { - const request = JSON.parse(data.toString()); - if (!request.event) queue.push(new JudgeTask(this, request, this.ws)); - }); - this.ws.on('close', (data, reason) => { - log.warn(`[${this.config.host}] Websocket 断开:`, data, reason); - setTimeout(() => { - this.retry(queue); - }, 30000); - }); - this.ws.on('error', (e) => { - log.error(`[${this.config.host}] Websocket 错误:`, e); - setTimeout(() => { - this.retry(queue); - }, 30000); - }); - await new Promise((resolve) => { - this.ws.once('open', () => { resolve(null); }); - }); - log.info(`[${this.config.host}] 已连接`); - } - - async setCookie(cookie: string) { - this.config.cookie = cookie; - this.axios = axios.create({ - baseURL: this.config.server_url, - timeout: 30000, - headers: { - accept: 'application/json', - 'Content-Type': 'application/x-www-form-urlencoded', - cookie: this.config.cookie, - }, - transformRequest: [ - (data) => { - let ret = ''; - for (const it in data) { - ret += `${encodeURIComponent(it)}=${encodeURIComponent(data[it])}&`; - } - return ret; - }, - ], - }); - } - - async login() { - log.info('[%s] Updating session', this.config.host); - const res = await this.axios.post('login', { - uname: this.config.uname, password: this.config.password, rememberme: 'on', - }); - await this.setCookie(res.headers['set-cookie'].join(';')); - } - - async ensureLogin() { - try { - await this.axios.get('judge/noop'); - } catch (e) { - await this.login(); - } - } - - async processData(folder: string) { // eslint-disable-line class-methods-use-this - let files = await fs.readdir(folder); - let ini = false; - if (files.length <= 2) { - if (files.length === 2) files.splice(files.indexOf('version'), 1); - const s = fs.statSync(path.resolve(folder, files[0])); - if (s.isDirectory()) folder = path.resolve(folder, files[0]); - } - files = await fs.readdir(folder); - for (const i of files) { - if (i.toLowerCase() === 'config.ini') { - ini = true; - await fs.rename(`${folder}/${i}`, `${folder}/config.ini`); - break; - } - } - if (ini) { - for (const i of files) { - if (i.toLowerCase() === 'input') await fs.rename(`${folder}/${i}`, `${folder}/input`); - else if (i.toLowerCase() === 'output') await fs.rename(`${folder}/${i}`, `${folder}/output`); - } - files = await fs.readdir(`${folder}/input`); - for (const i of files) { - if (fs.statSync(`${folder}/input/${i}`).size > 64 * 1024 * 1024) { - await fs.rename(`${folder}/input/${i}`, `${folder}/input/${i.toLowerCase()}`); - } else { - const buffer = await fs.readFile(`${folder}/input/${i}`); - const data = buffer.toString().replace(/\r/g, ''); - await fs.unlink(`${folder}/input/${i}`); - await fs.writeFile(`${folder}/input/${i.toLowerCase()}`, data); - } - } - files = await fs.readdir(`${folder}/output`); - for (const i of files) await fs.rename(`${folder}/output/${i}`, `${folder}/output/${i.toLowerCase()}`); - } - } - - async cacheOpen(domainId: string, pid: string, next) { - const domainDir = path.join(getConfig('cache_dir'), this.config.host, domainId); - const filePath = path.join(domainDir, pid); - const version = await this.problemDataVersion(domainId, pid); - if (fs.existsSync(filePath)) { - let ver; - try { - ver = fs.readFileSync(path.join(filePath, 'version')).toString(); - } catch (e) { /* ignore */ } - if (version === ver) { - fs.writeFileSync(path.join(filePath, 'lastUsage'), new Date().getTime().toString()); - return filePath; - } - fs.removeSync(filePath); - } - fs.ensureDirSync(domainDir); - await this.problemData(domainId, pid, filePath, 3, next); - fs.writeFileSync(path.join(filePath, 'version'), version); - fs.writeFileSync(path.join(filePath, 'lastUsage'), new Date().getTime().toString()); - return filePath; - } - - async retry(queue: Queue) { - this.consume(queue).catch(() => { - setTimeout(() => { - this.retry(queue); - }, 30000); - }); + constructor() { + console.log('新版本不再支持 VJ4。请降级至 hydrojudge@2.5.21 或迁移到 Hydro。'); + console.log('详细文档: https://hydro.js.org/ '); + process.exit(1); } } diff --git a/packages/hydrojudge/src/judge/default.ts b/packages/hydrojudge/src/judge/default.ts index f6f638aff..782affd44 100644 --- a/packages/hydrojudge/src/judge/default.ts +++ b/packages/hydrojudge/src/judge/default.ts @@ -1,10 +1,9 @@ import Queue from 'p-queue'; import path from 'path'; -import yaml from 'js-yaml'; import fs from 'fs-extra'; import { argv } from 'yargs'; import * as STATUS from '../status'; -import { CompileError, SystemError } from '../error'; +import { SystemError } from '../error'; import { parseFilename } from '../utils'; import { run } from '../sandbox'; import compile from '../compile'; @@ -128,15 +127,7 @@ function judgeSubtask(subtask, sid) { export const judge = async (ctx) => { if (!ctx.config.subtasks.length) throw new SystemError('Problem data not found.'); - if (ctx.config.template) { - if (ctx.config.template[ctx.lang]) { - const tpl = ctx.config.template[ctx.lang]; - ctx.code = tpl[0] + ctx.code + tpl[1]; - } else throw new CompileError('Language not supported by provided templates'); - } - const LANGS = yaml.load(getConfig('langs')); - if (!LANGS[ctx.lang]) throw new SystemError('Unsupported language {0}.', [ctx.lang]); - const info = LANGS[ctx.lang]; + const info = ctx.getLang(ctx.lang); ctx.time_limit_rate = info.time_limit_rate || 1; ctx.next({ status: STATUS.STATUS_COMPILING }); [ctx.execute, ctx.checker] = await Promise.all([ @@ -145,7 +136,7 @@ export const judge = async (ctx) => { for (const file of ctx.config.user_extra_files) { copyIn[parseFilename(file)] = { src: file }; } - return await compile(ctx.lang, ctx.code, 'code', copyIn, ctx.next); + return await compile(ctx.getLang(ctx.lang), ctx.code, 'code', copyIn, ctx.next); })(), (async () => { if (!ctx.config.checker_type || ctx.config.checker_type === 'default') { @@ -156,6 +147,7 @@ export const judge = async (ctx) => { copyIn[parseFilename(file)] = { src: file }; } return await compileChecker( + ctx.getLang, ctx.config.checker_type, ctx.config.checker, copyIn, diff --git a/packages/hydrojudge/src/judge/hack.ts b/packages/hydrojudge/src/judge/hack.ts index 8e81be8dc..da0992c31 100644 --- a/packages/hydrojudge/src/judge/hack.ts +++ b/packages/hydrojudge/src/judge/hack.ts @@ -25,7 +25,7 @@ export const judge = async (ctx) => { for (const file of ctx.config.user_extra_files) { copyIn[parseFilename(file)] = { src: file }; } - return await compile(ctx.lang, ctx.code, 'code', copyIn, ctx.next); + return await compile(ctx.getLang(ctx.lang), ctx.code, 'code', copyIn, ctx.next); })(), // Validator (async () => { @@ -34,7 +34,7 @@ export const judge = async (ctx) => { copyIn[parseFilename(file)] = { src: file }; } const file = await fs.readFile(ctx.config.validator); - return await compile(parseFilename(ctx.config.validator).split('.')[1], file.toString(), 'validator', copyIn); + return await compile(ctx.getLang(parseFilename(ctx.config.validator).split('.')[1]), file.toString(), 'validator', copyIn); })(), // Std (async () => { @@ -43,7 +43,7 @@ export const judge = async (ctx) => { copyIn[parseFilename(file)] = { src: file }; } const file = await fs.readFile(ctx.config.std); - return await compile(parseFilename(ctx.config.std).split('.')[1], file.toString(), 'std', copyIn); + return await compile(ctx.getLang(parseFilename(ctx.config.std).split('.')[1]), file.toString(), 'std', copyIn); })(), // Checker (async () => { @@ -53,6 +53,7 @@ export const judge = async (ctx) => { copyIn[parseFilename(file)] = { src: file }; } return await compileChecker( + ctx.getLang, ctx.config.checker_type, ctx.config.checker, copyIn, diff --git a/packages/hydrojudge/src/judge/interactive.ts b/packages/hydrojudge/src/judge/interactive.ts index 1794c89b1..0344ca1ff 100644 --- a/packages/hydrojudge/src/judge/interactive.ts +++ b/packages/hydrojudge/src/judge/interactive.ts @@ -1,7 +1,6 @@ import Queue from 'p-queue'; import fs from 'fs-extra'; import { argv } from 'yargs'; -import yaml from 'js-yaml'; import * as STATUS from '../status'; import { parse } from '../testlib'; import { findFileSync, parseFilename } from '../utils'; @@ -9,7 +8,6 @@ import { run } from '../sandbox'; import compile from '../compile'; import signals from '../signals'; import { getConfig } from '../config'; -import { SystemError } from '../error'; const testlibSrc = findFileSync('@hydrooj/hydrojudge/vendor/testlib/testlib.h'); const Score = { @@ -93,27 +91,22 @@ function judgeSubtask(subtask) { export const judge = async (ctx) => { ctx.next({ status: STATUS.STATUS_COMPILING }); - const LANGS = yaml.load(getConfig('langs')); - if (!LANGS[ctx.lang]) throw new SystemError('Unsupported language {0}.', [ctx.lang]); - const info = LANGS[ctx.lang]; - ctx.time_limit_rate = info.time_limit_rate || 1; + ctx.time_limit_rate = ctx.getLang(ctx.lang).time_limit_rate; [ctx.executeUser, ctx.executeInteractor] = await Promise.all([ (async () => { const copyIn = {}; for (const file of ctx.config.user_extra_files) { copyIn[parseFilename(file)] = { src: file }; } - return await compile(ctx.lang, ctx.code, 'code', copyIn, ctx.next); + return await compile(ctx.getLang(ctx.lang), ctx.code, 'code', copyIn, ctx.next); })(), (async () => { - const copyIn = { - 'testlib.h': testlibSrc, - }; + const copyIn = { 'testlib.h': testlibSrc }; for (const file of ctx.config.judge_extra_files) { copyIn[parseFilename(file)] = { src: file }; } return await compile( - parseFilename(ctx.config.interactor).split('.')[1], + ctx.getLang(parseFilename(ctx.config.interactor).split('.')[1]), fs.readFileSync(ctx.config.interactor).toString(), 'interactor', copyIn, diff --git a/packages/hydrojudge/src/judge/run.ts b/packages/hydrojudge/src/judge/run.ts index f80fd4f3e..9066f9a35 100644 --- a/packages/hydrojudge/src/judge/run.ts +++ b/packages/hydrojudge/src/judge/run.ts @@ -12,7 +12,7 @@ export const judge = async (ctx) => { ctx.stat.judge = new Date(); ctx.next({ status: STATUS.STATUS_COMPILING }); try { - ctx.execute = await compile(ctx.lang, ctx.code, 'code', {}, ctx.next); + ctx.execute = await compile(ctx.getLang(ctx.lang), ctx.code, 'code', {}, ctx.next); } catch (e) { if (e instanceof CompileError) { ctx.next({ diff --git a/packages/hydrojudge/src/service.ts b/packages/hydrojudge/src/service.ts index 11c9e77c0..0a1170a02 100644 --- a/packages/hydrojudge/src/service.ts +++ b/packages/hydrojudge/src/service.ts @@ -153,6 +153,11 @@ async function postInit() { }; } + function getLang(lang: string) { + if (system.lang[lang]) return system.lang[lang]; + throw new SystemError('Unsupported language {0}.', [lang]); + } + class JudgeTask { stat: any; request: any; @@ -170,11 +175,13 @@ async function postInit() { tmpdir: string; clean: any[]; folder: string; + getLang: (lang: string) => any; constructor(request) { this.stat = {}; this.stat.receive = new Date(); this.request = request; + this.getLang = getLang; logger.debug('%o', request); } diff --git a/packages/hydrooj/locales/zh.yaml b/packages/hydrooj/locales/zh.yaml index f3e599a27..cd6ccff2d 100644 --- a/packages/hydrooj/locales/zh.yaml +++ b/packages/hydrooj/locales/zh.yaml @@ -125,6 +125,7 @@ Click here to chat with me: 点击这里与我聊天 click to add: 点击添加 CLOSE: 关闭 Code blocks: 代码区块 +Code language settings: 编程语言设置 Code language: 代码语言 Code: 代码 collapse: 收缩 @@ -416,9 +417,6 @@ Judged At: 评测时间 Judged By: 评测机 Judging Queue: 评测队列 Keep current expiration: 保持当前过期设置 -LANG_TEXTS: 编译语言代号 -> 语言名称 -Language file settings: 评测机语言(langs.yaml) -Language Highlight ID: 编译语言代号 -> 高亮语言代号 Language: 语言 last active at: 最后活动于 last login at: 最后登录于 diff --git a/packages/hydrooj/package.json b/packages/hydrooj/package.json index a492d6072..83b1e80aa 100644 --- a/packages/hydrooj/package.json +++ b/packages/hydrooj/package.json @@ -1,6 +1,6 @@ { "name": "hydrooj", - "version": "2.24.22", + "version": "2.25.0", "bin": "bin/hydrooj.js", "main": "dist/loader.js", "typings": "dist/loader.d.ts", @@ -11,7 +11,7 @@ "node": ">=14" }, "dependencies": { - "@hydrooj/utils": "^1.0.13", + "@hydrooj/utils": "^1.0.14", "adm-zip": "^0.5.5", "ansi_up": "^5.0.1", "cookies": "^0.8.0", diff --git a/packages/hydrooj/setting.yaml b/packages/hydrooj/setting.yaml new file mode 100644 index 000000000..dcfabbcf1 --- /dev/null +++ b/packages/hydrooj/setting.yaml @@ -0,0 +1,98 @@ +langs: + type: yaml + desc: Code language settings + default: + __hint__: + - All keys started with _ will be ignored + - Entries named like [foo], [foo].[bar] [foo].[baz] will be merged into one category. + - A entry named [foo].[bar] will inhert all options defined in entry [foo] if not overwritten + - code_file option defaults to foo.[name] + - highlight option defaults to [name] + - if highlight option was set, monaco option defaults to [highlight], otherwise defaults to [name] + - Compile step will be ignored if compile option was not set + - Key marked with a ? means the option is optional + - (name): + compile?: Compile Command + code_file?: Where to write code file when compiling + execute?: Command to execute the program + highlight?: PrismJS highlight mode + monaco?: Monaco editor mode + time_limit_rate: Time limit rate + target: Compiler output file + display: Display name + c: + compile: /usr/bin/gcc -Wall --std=c99 -o ${name} foo.c -lm + highlight: c astyle-c + monaco: cpp + display: C + cc: + compile: /usr/bin/g++ -Wall -std=c++11 -o ${name} foo.cc -lm + code_file: foo.cc + highlight: cpp astyle-c + monaco: cpp + display: C++ + cc.std98: + compile: /usr/bin/g++ -Wall -std=c++98 -o ${name} foo.cc -lm + display: C++ 98 + cc.std11: + display: C++ 11 + cc.std17: + compile: /usr/bin/g++ -Wall -std=c++17 -o ${name} foo.cc -lm + display: C++ 17 + pas: + compile: /usr/bin/fpc -O2 -o${dir}/${name} foo.pas + highlight: pascal + display: Pascal + java: + compile: /usr/bin/javac -d ${dir} -encoding utf8 ./Main.java + code_file: Main.java + target: Main.class + execute: /usr/bin/java Main + time_limit_rate: 2 + highlight: java astyle-java + monaco: java + display: Java + py: + compile: /usr/bin/python -c "import py_compile; py_compile.compile('${dir}/foo.py', '${dir}/${name}', doraise=True)" + code_file: foo.py + execute: /usr/bin/python ${name} + highlight: python + display: Python + py.py2: + display: Python 2 + py.py3: + compile: /usr/bin/python3 -c "import py_compile; py_compile.compile('${dir}/foo.py', '${dir}/${name}', doraise=True)" + execute: /usr/bin/python3 ${name} + display: Python 3 + php: + execute: /usr/bin/php foo.php + display: PHP + rs: + compile: /usr/bin/rustc -O -o ${dir}/${name} ${dir}/foo.rs + highlight: rust + display: Rust + hs: + compile: /usr/bin/ghc -O -outputdir /tmp -o ${name} foo.hs + highlight: haskell + display: Haskell + js: + execute: /usr/bin/jsc ${dir}/foo.js + highlight: javascript + display: Javascript (JSC) + go: + compile: /usr/bin/go build -o ${name} foo.go + display: Golang + rb: + execute: /usr/bin/ruby foo.rb + highlight: ruby + display: Ruby + cs: + compile: /usr/bin/mcs -optimize+ -out:${dir}/${name} ${dir}/foo.cs + execute: /usr/bin/mono ${name} + highlight: csharp astyle-cs + monaco: csharp + display: Csharp + jl: + execute: /usr/bin/julia ${dir}/foo.jl + highlight: julia + display: Julia diff --git a/packages/hydrooj/src/handler/judge.ts b/packages/hydrooj/src/handler/judge.ts index 7f15106a4..8962a182f 100644 --- a/packages/hydrooj/src/handler/judge.ts +++ b/packages/hydrooj/src/handler/judge.ts @@ -1,4 +1,5 @@ import { ObjectID } from 'mongodb'; +import { parseLang } from '@hydrooj/utils/lib/lang'; import { JudgeResultBody, Rdoc, TestCase } from '../interface'; import { sleep } from '../utils'; import { Logger } from '../logger'; @@ -9,6 +10,7 @@ import * as builtin from '../model/builtin'; import * as contest from '../model/contest'; import domain from '../model/domain'; import task from '../model/task'; +import * as system from '../model/system'; import * as bus from '../service/bus'; import { Route, Handler, Connection, ConnectionHandler, post, Types, @@ -119,12 +121,22 @@ class JudgeConnectionHandler extends ConnectionHandler { processing: any = null; closed = false; query: any = { type: 'judge' }; + ip: string; async prepare() { - logger.info('Judge daemon connected from ', this.request.ip); + const xff = system.get('server.xff'); + this.ip = xff ? this.request.headers[xff] || this.request.ip : this.request.ip; + logger.info('Judge daemon connected from ', this.ip); + this.send({ language: system.lang }); + this.sendLanguageConfig = this.sendLanguageConfig.bind(this); + bus.on('system/setting', this.sendLanguageConfig); this.newTask(); } + async sendLanguageConfig() { + this.send({ language: system.lang }); + } + async newTask() { if (this.processing) return; let t; @@ -157,7 +169,8 @@ class JudgeConnectionHandler extends ConnectionHandler { } async cleanup() { - logger.info('Judge daemon disconnected from ', this.request.ip); + logger.info('Judge daemon disconnected from ', this.ip); + bus.off('system/setting', this.sendLanguageConfig); if (this.processing) { await record.reset(this.processing.domainId, this.processing.rid, false); await task.add(this.processing); diff --git a/packages/hydrooj/src/model/builtin.ts b/packages/hydrooj/src/model/builtin.ts index e37da03ef..7f39e27ba 100644 --- a/packages/hydrooj/src/model/builtin.ts +++ b/packages/hydrooj/src/model/builtin.ts @@ -519,23 +519,6 @@ export const CATEGORIES = { ], }; -export const LANG_TEXTS = { - c: 'C', - cc: 'C++', - cs: 'C#', - pas: 'Pascal', - java: 'Java', - py: 'Python', - py3: 'Python 3', - php: 'PHP', - rs: 'Rust', - hs: 'Haskell', - js: 'JavaScript', - go: 'Go', - rb: 'Ruby', - jl: 'Julia', -}; - global.Hydro.model.builtin = { Permission, PERM, @@ -547,7 +530,6 @@ global.Hydro.model.builtin = { BUILTIN_ROLES, DEFAULT_NODES, CATEGORIES, - LANG_TEXTS, STATUS, STATUS_TEXTS, STATUS_CODES, diff --git a/packages/hydrooj/src/model/setting.ts b/packages/hydrooj/src/model/setting.ts index 0a1135ecb..f10f268e0 100644 --- a/packages/hydrooj/src/model/setting.ts +++ b/packages/hydrooj/src/model/setting.ts @@ -3,6 +3,7 @@ import moment from 'moment-timezone'; import { Dictionary } from 'lodash'; import yaml from 'js-yaml'; import { retry } from '@hydrooj/utils/lib/utils'; +import { parseLang } from '@hydrooj/utils/lib/lang'; import * as builtin from './builtin'; import { Setting as _Setting } from '../interface'; import { Logger } from '../logger'; @@ -98,10 +99,30 @@ export const SystemSetting = (...settings: _Setting[]) => { } }; +const LangSettingNode = { + family: 'setting_usage', + key: 'codeLang', + value: 'c', + name: 'codeLang', + desc: 'Default Code Language', + flag: 0, + subType: '', + type: 'select', + range: {}, +}; + +bus.on('system/setting', (args) => { + if (!args['hydrooj.lang']) return; + const lang = parseLang(args['hydrooj.lang']); + const range = {}; + for (const key in lang) range[key] = lang[key].display; + LangSettingNode.range = range; +}); + PreferenceSetting( Setting('setting_display', 'viewLang', null, langRange, 'UI Language'), Setting('setting_display', 'timeZone', 'Asia/Shanghai', timezones, 'Timezone'), - Setting('setting_usage', 'codeLang', 'c', builtin.LANG_TEXTS, 'Default Code Language'), + LangSettingNode, Setting('setting_usage', 'codeTemplate', '', 'textarea', 'Default Code Template', 'If left blank, the built-in template of the corresponding language will be used.'), ); @@ -165,7 +186,6 @@ SystemSetting( Setting('setting_limits', 'limit.problem_files_max', 100, 'number', 'limit.problem_files_max', 'Max files per problem'), Setting('setting_basic', 'default.priv', builtin.PRIV.PRIV_DEFAULT, 'number', 'default.priv', 'Default Privilege'), Setting('setting_basic', 'problem.categories', builtin.CATEGORIES, 'yaml', 'problem.categories', 'Problem Categories'), - Setting('setting_basic', 'lang.texts', builtin.LANG_TEXTS, 'yaml', 'lang.texts', 'LANG_TEXTS'), Setting('setting_basic', 'pagination.problem', 100, 'number', 'pagination.problem', 'Problems per page'), Setting('setting_basic', 'pagination.contest', 20, 'number', 'pagination.contest', 'Contests per page'), Setting('setting_basic', 'pagination.discussion', 50, 'number', 'pagination.discussion', 'Discussions per page'), diff --git a/packages/hydrooj/src/model/system.ts b/packages/hydrooj/src/model/system.ts index 9c1a41327..d005757ac 100644 --- a/packages/hydrooj/src/model/system.ts +++ b/packages/hydrooj/src/model/system.ts @@ -1,3 +1,4 @@ +import { parseLang } from '@hydrooj/utils/lib/lang'; import { SYSTEM_SETTINGS } from './setting'; import { NumberKeys } from '../typeutils'; import { SystemKeys } from '../interface'; @@ -70,8 +71,13 @@ export async function runConfig() { await bus.emit('database/config'); } +// eslint-disable-next-line import/no-mutable-exports +export let lang = {}; + bus.on('system/setting', (args) => { for (const key in args) set(key, args[key], false); + if (!args['hydrooj.lang']) return; + lang = parseLang(args['hydrooj.lang']); }); global.Hydro.model.system = { @@ -80,4 +86,5 @@ global.Hydro.model.system = { getMany, inc, set, + lang, }; diff --git a/packages/ui-default/components/scratchpad/ScratchpadEditorContainer.jsx b/packages/ui-default/components/scratchpad/ScratchpadEditorContainer.jsx index 3689440da..967447902 100644 --- a/packages/ui-default/components/scratchpad/ScratchpadEditorContainer.jsx +++ b/packages/ui-default/components/scratchpad/ScratchpadEditorContainer.jsx @@ -4,7 +4,7 @@ import * as monaco from 'monaco-editor/esm/vs/editor/editor.api'; const mapStateToProps = (state) => ({ value: state.editor.code, - language: window.MONACO_MODES[state.editor.lang], + language: window.LANGS[state.editor.lang].monaco, theme: 'vs-light', mainSize: state.ui.main.size, pretestSize: state.ui.pretest.size, diff --git a/packages/ui-default/components/scratchpad/ScratchpadToolbarContainer.jsx b/packages/ui-default/components/scratchpad/ScratchpadToolbarContainer.jsx index fd62aec7d..b7fef7b7f 100644 --- a/packages/ui-default/components/scratchpad/ScratchpadToolbarContainer.jsx +++ b/packages/ui-default/components/scratchpad/ScratchpadToolbarContainer.jsx @@ -136,8 +136,8 @@ export default connect(mapStateToProps, mapDispatchToProps)(class ScratchpadTool value={this.props.editorLang} onChange={(ev) => this.props.setEditorLanguage(ev.target.value)} > - {_.map(window.LANG_TEXTS, (val, key) => ( - + {_.map(window.LANGS, (val, key) => ( + ))} diff --git a/packages/ui-default/handler.js b/packages/ui-default/handler.js index 19946e000..eb12b7263 100644 --- a/packages/ui-default/handler.js +++ b/packages/ui-default/handler.js @@ -53,15 +53,7 @@ class UiConstantsHandler extends Handler { } async get() { - const [LANG_TEXTS, LANG_HIGHLIGHT_ID, MONACO_MODES] = system.getMany([ - 'lang.texts', - 'ui-default.lang_highlight_id', - 'ui-default.lang_monaco_modes', - ]); - this.response.body = `\ -window.LANG_TEXTS=${JSON.stringify(yaml.load(LANG_TEXTS))}; -window.LANG_HIGHLIGHT_ID=${JSON.stringify(yaml.load(LANG_HIGHLIGHT_ID))}; -window.MONACO_MODES=${JSON.stringify(yaml.load(MONACO_MODES))}`; + this.response.body = `window.LANGS=${JSON.stringify(system.langs)}`; this.response.type = 'text/javascript'; this.ctx.set('nolog', '1'); } diff --git a/packages/ui-default/locales/zh.yaml b/packages/ui-default/locales/zh.yaml index 32cfeee63..962d2578d 100644 --- a/packages/ui-default/locales/zh.yaml +++ b/packages/ui-default/locales/zh.yaml @@ -433,7 +433,6 @@ Memory: 内存 Message {0} not found.: 消息 {0} 不存在。 Messages: 消息 Method: 方法 -Monaco language modes: 编译语言代号 -> Monaco 编辑器模式 Month: 月 Monthly Popular: 月度最受欢迎 Most Upvoted Solutions: 最被赞同的题解 diff --git a/packages/ui-default/package.json b/packages/ui-default/package.json index 7de42bec6..4b8a2bf47 100644 --- a/packages/ui-default/package.json +++ b/packages/ui-default/package.json @@ -1,6 +1,6 @@ { "name": "@hydrooj/ui-default", - "version": "4.7.8", + "version": "4.8.0", "author": "undefined ", "license": "AGPL-3.0", "main": "hydro.js", @@ -22,7 +22,7 @@ "@babel/preset-react": "^7.13.13", "@babel/register": "^7.13.14", "@babel/runtime-corejs2": "^7.14.0", - "@hydrooj/utils": "^1.0.13", + "@hydrooj/utils": "^1.0.14", "ansi_up": "^5.0.1", "autoprefixer": "^9.8.6", "babel-loader": "^8.2.2", diff --git a/packages/ui-default/setting.yaml b/packages/ui-default/setting.yaml index a4d042bfa..a177e18bd 100644 --- a/packages/ui-default/setting.yaml +++ b/packages/ui-default/setting.yaml @@ -43,44 +43,6 @@ nav_logo_dark_2x: name: nav_logo_dark_2x desc: Nav Logo Dark@2x default: /components/navigation/nav-logo-small@2x_dark.png -lang_highlight_id: - type: yaml - name: lang_highlight_id - desc: Language Highlight ID - default: - c: c astyle-c - cc: cpp astyle-c - cs: csharp astyle-cs - pas: pascal - java: java astyle-java - py: python - py3: python - php: php - rs: rust - hs: haskell - js: javascript - go: go - rb: ruby - jl: julia -lang_monaco_modes: - type: yaml - name: lang_monaco_modes - desc: Monaco language modes - default: - c: cpp - cc: cpp - cs: csharp - pas: pascal - java: java - py: python - py3: python - php: php - rs: rust - hs: haskell - js: javascript - go: go - rb: ruby - jl: julia preferredEditorType: type: wysiwyg: WYSIWYG diff --git a/packages/ui-default/templates/record_detail.html b/packages/ui-default/templates/record_detail.html index 7726c31b2..4bddcf168 100644 --- a/packages/ui-default/templates/record_detail.html +++ b/packages/ui-default/templates/record_detail.html @@ -11,7 +11,7 @@

{{ _('Code') }}

-
{{ rdoc['code'] }}
+
{{ rdoc['code'] }}
{% endif %} diff --git a/packages/utils/lib/lang.ts b/packages/utils/lib/lang.ts new file mode 100644 index 000000000..3eabc3c6e --- /dev/null +++ b/packages/utils/lib/lang.ts @@ -0,0 +1,45 @@ +import yaml from 'js-yaml'; + +export interface LangConfig { + compile?: string; + execute: string; + code_file: string; + highlight: string; + monaco: string; + time_limit_rate: number; + display: string; + target?: string; + sub?: LangConfig[]; + key: string; +} + +export function parseLang(config: string): Record { + const file = yaml.load(config) as Record; + if (typeof file === 'undefined' || typeof file === 'string' || typeof file === 'number') throw new Error(); + Object.keys(file).filter((i) => i.startsWith('_')).forEach((k) => delete file[k]); + for (const key in file) { + const entry = file[key]; + if (key.includes('.')) { + const base = key.split('.')[0]; + const baseCfg = file[base]; + if (baseCfg) { + for (const bkey in baseCfg) { + if (!(bkey in entry)) entry[bkey] = baseCfg[bkey]; + } + if (baseCfg.sub) baseCfg.sub.push(entry); + else baseCfg.sub = [entry]; + } + } + } + for (const key in file) { + const entry = file[key]; + entry.highlight = entry.highlight || key; + entry.monaco = entry.monaco || entry.highlight; + entry.time_limit_rate = entry.time_limit_rate || 1; + entry.code_file = entry.code_file || `foo.${key}`; + // eslint-disable-next-line no-template-curly-in-string + entry.execute = entry.execute || '${dir}/${name}'; + entry.key = key; + } + return file; +} diff --git a/packages/utils/package.json b/packages/utils/package.json index edfb34436..355ff4605 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -1,6 +1,6 @@ { "name": "@hydrooj/utils", - "version": "1.0.13", + "version": "1.0.14", "description": "hydrooj utils", "main": "package.json", "repository": "https://github.com/hydro-dev/Hydro.git", From 80ac44d98e4198f2820fdcb1679a704fa565b884 Mon Sep 17 00:00:00 2001 From: undefined Date: Fri, 7 May 2021 22:22:24 +0800 Subject: [PATCH 2/5] ui: enhance language selection --- install/install.js | 58 ++++++++++++------ packages/hydrojudge/src/service.ts | 4 +- packages/hydrooj/package.json | 2 +- packages/hydrooj/src/entry/common.ts | 1 + packages/hydrooj/src/handler/judge.ts | 6 +- packages/hydrooj/src/model/setting.ts | 27 ++++---- packages/hydrooj/src/model/system.ts | 6 -- packages/ui-default/handler.js | 28 ++++----- packages/ui-default/package.json | 4 +- .../ui-default/pages/home_preference.page.jsx | 61 +++++++++++++++++++ .../ui-default/pages/problem_submit.page.js | 33 ++++++++++ .../ui-default/templates/components/form.html | 2 + .../ui-default/templates/problem_submit.html | 32 ++++++++-- .../ui-default/templates/record_detail.html | 2 +- .../ui-default/templates/record_main_tr.html | 2 +- packages/utils/lib/lang.ts | 1 + packages/utils/package.json | 2 +- 17 files changed, 201 insertions(+), 70 deletions(-) diff --git a/install/install.js b/install/install.js index e03f77dd6..d38edfdf3 100644 --- a/install/install.js +++ b/install/install.js @@ -6,8 +6,13 @@ let MINIO_ACCESS_KEY = randomstring(32); let MINIO_SECRET_KEY = randomstring(32); let DATABASE_PASSWORD = randomstring(32); +const source_nvm = `\ +# load nvm env (by hydro installer) +export NVM_DIR="$HOME/.nvm" +[ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh"`; + if (__arch !== 'amd64') log.fatal('不支持的架构 %s ,请尝试手动安装', __arch); -const china = (cli.prompt('此服务器是否位于国内?(Y/n)') || 'y') === 'y'; +const china = (cli.prompt('此服务器是否位于中国大陆?(Y/n) ') || 'y').toLowerCase().trim() === 'y'; const NVM_NODEJS_ORG_MIRROR = china ? 'https://mirrors.tuna.tsinghua.edu.cn/nodejs-release' : 'https://nodejs.org/dist'; @@ -26,11 +31,12 @@ const osinfoFile = fs.readfile('/etc/os-release'); const lines = osinfoFile.split('\n'); const values = {}; for (const line of lines) { + if (!line.trim()) continue; const d = line.split('='); - if (d[1].startsWith('"')) values[d[0]] = d[1].substr(1, d[1].length - 2); - else values[d[0]] = d[1]; + if (d[1].startsWith('"')) values[d[0].toLowerCase()] = d[1].substr(1, d[1].length - 2); + else values[d[0].toLowerCase()] = d[1]; } -if (['ubuntu', 'arch'].includes(values.id)) log.fatal('不支持的系统'); +if (!['ubuntu', 'arch'].includes(values.id)) log.fatal('不支持的系统'); const Arch = values.id === 'arch'; const steps = [ @@ -38,24 +44,32 @@ const steps = [ init: '正在初始化安装 / Preparing', operations: [ 'mkdir -p /data/db /data/file ~/.hydro', - 'apt-get -qq update', - 'apt-get install -y curl wget gnupg', + Arch ? 'pacman --needed --quiet --noconfirm -Sy' : 'apt-get -qq update', + Arch ? 'pacman --needed --quiet --noconfirm -S gnupg curl' : 'apt-get install -y curl wget gnupg', ], }, { init: '正在安装 MongoDB / Installing MongoDB', skip: () => fs.exist('/usr/bin/mongo'), - operations: [ - () => { - fs.writefile( - '/etc/apt/sources.list.d/mongodb-org-4.4.list', - `deb [ arch=amd64 ] ${MONGODB_REPO} ${values.UBUNTU_CODENAME}/mongodb-org/4.4 multiverse`, - ); - }, - 'wget -qO - https://www.mongodb.org/static/pgp/server-4.4.asc | apt-key add -', - 'apt-get -qq update', - 'apt-get -q install -y mongodb-org', - ], + operations: Arch + ? [ + 'curl -fSLO https://s3.undefined.moe/hydro/arch/libcurl-openssl-1.0-7.76.0-1-x86_64.pkg.tar.zst', + 'curl -fSLO https://s3.undefined.moe/hydro/arch/mongodb-bin-4.4.5-1-x86_64.pkg.tar.zst', + 'curl -fSLO https://s3.undefined.moe/hydro/arch/mongodb-tools-bin-100.3.1-1-x86_64.pkg.tar.zst', + 'pacman --noconfirm -U libcurl-openssl-1.0-7.76.0-1-x86_64.pkg.tar.zst' + + 'mongodb-bin-4.4.5-1-x86_64.pkg.tar.zst mongodb-tools-bin-100.3.1-1-x86_64.pkg.tar.zst', + ] + : [ + () => { + fs.writefile( + '/etc/apt/sources.list.d/mongodb-org-4.4.list', + `deb [ arch=amd64 ] ${MONGODB_REPO} ${values.ubuntu_codename}/mongodb-org/4.4 multiverse`, + ); + }, + 'wget -qO - https://www.mongodb.org/static/pgp/server-4.4.asc | apt-key add -', + 'apt-get -qq update', + 'apt-get -q install -y mongodb-org', + ], }, { init: '正在安装 NVM / Installing NVM', @@ -81,6 +95,12 @@ const steps = [ const res = exec1('bash -c "source /root/.nvm/nvm.sh && nvm install 14"', { NVM_NODEJS_ORG_MIRROR }); const ver = res.output.split('Now using node v')[1].split(' ')[0]; setenv('PATH', `/root/.nvm/versions/node/v${ver}/bin:${__env.PATH}`); + const rc_path = `/root/.${__env['SHELL'].endsWith('zsh') ? 'zsh' : 'bash'}rc`; + if (!fs.exist(rc_path)) fs.writefile(rc_path, source_nvm); + else { + const rc_file = fs.readfile(rc_path); + if (!rc_file.includes(source_nvm)) fs.appendfile(rc_path, source_nvm); + } }, 'npm i yarn -g', ], @@ -118,7 +138,7 @@ const steps = [ init: '正在安装 MinIO / Installing MinIO', skip: () => fs.exist('/root/.hydro/env'), operations: [ - `wget ${MINIO_DOWNLOAD} -O /usr/bin/minio`, + `curl -fSL ${MINIO_DOWNLOAD} -o /usr/bin/minio`, 'chmod +x /usr/bin/minio', () => fs.writefile( '/root/.hydro/env', @@ -129,7 +149,7 @@ const steps = [ { init: '正在安装编译器 / Installing compiler', operations: [ - 'apt-get install -y g++ fp-compiler >/dev/null', + Arch ? 'pacman --needed --quiet --noconfirm -S gcc fpc' : 'apt-get install -y g++ fp-compiler >/dev/null', ], }, { diff --git a/packages/hydrojudge/src/service.ts b/packages/hydrojudge/src/service.ts index 0a1170a02..1df16a54a 100644 --- a/packages/hydrojudge/src/service.ts +++ b/packages/hydrojudge/src/service.ts @@ -39,7 +39,7 @@ async function postInit() { if (!global.Hydro.isFirstWorker) return; const judge = require('./judge'); - const { task, system } = global.Hydro.model; + const { task, system, setting } = global.Hydro.model; const { storage } = global.Hydro.service; const _judge = global.Hydro.handler.judge as any; @@ -154,7 +154,7 @@ async function postInit() { } function getLang(lang: string) { - if (system.lang[lang]) return system.lang[lang]; + if (setting.langs[lang]) return setting.langs[lang]; throw new SystemError('Unsupported language {0}.', [lang]); } diff --git a/packages/hydrooj/package.json b/packages/hydrooj/package.json index 83b1e80aa..0b951f2b8 100644 --- a/packages/hydrooj/package.json +++ b/packages/hydrooj/package.json @@ -1,6 +1,6 @@ { "name": "hydrooj", - "version": "2.25.0", + "version": "2.25.1", "bin": "bin/hydrooj.js", "main": "dist/loader.js", "typings": "dist/loader.d.ts", diff --git a/packages/hydrooj/src/entry/common.ts b/packages/hydrooj/src/entry/common.ts index 8f0663ce8..94fb3800a 100644 --- a/packages/hydrooj/src/entry/common.ts +++ b/packages/hydrooj/src/entry/common.ts @@ -115,6 +115,7 @@ export async function setting(pending: string[], fail: string[], modelSetting: t ), ); } + logger.info('Config load: %s', i); } catch (e) { logger.error('Config Load Fail: %s', i); logger.error(e); diff --git a/packages/hydrooj/src/handler/judge.ts b/packages/hydrooj/src/handler/judge.ts index 8962a182f..bd0cd2ad5 100644 --- a/packages/hydrooj/src/handler/judge.ts +++ b/packages/hydrooj/src/handler/judge.ts @@ -1,11 +1,11 @@ import { ObjectID } from 'mongodb'; -import { parseLang } from '@hydrooj/utils/lib/lang'; import { JudgeResultBody, Rdoc, TestCase } from '../interface'; import { sleep } from '../utils'; import { Logger } from '../logger'; import difficultyAlgorithm from '../lib/difficulty'; import record from '../model/record'; import problem from '../model/problem'; +import * as setting from '../model/setting'; import * as builtin from '../model/builtin'; import * as contest from '../model/contest'; import domain from '../model/domain'; @@ -127,14 +127,14 @@ class JudgeConnectionHandler extends ConnectionHandler { const xff = system.get('server.xff'); this.ip = xff ? this.request.headers[xff] || this.request.ip : this.request.ip; logger.info('Judge daemon connected from ', this.ip); - this.send({ language: system.lang }); + this.send({ language: setting.langs }); this.sendLanguageConfig = this.sendLanguageConfig.bind(this); bus.on('system/setting', this.sendLanguageConfig); this.newTask(); } async sendLanguageConfig() { - this.send({ language: system.lang }); + this.send({ language: setting.langs }); } async newTask() { diff --git a/packages/hydrooj/src/model/setting.ts b/packages/hydrooj/src/model/setting.ts index f10f268e0..916a61e69 100644 --- a/packages/hydrooj/src/model/setting.ts +++ b/packages/hydrooj/src/model/setting.ts @@ -111,14 +111,6 @@ const LangSettingNode = { range: {}, }; -bus.on('system/setting', (args) => { - if (!args['hydrooj.lang']) return; - const lang = parseLang(args['hydrooj.lang']); - const range = {}; - for (const key in lang) range[key] = lang[key].display; - LangSettingNode.range = range; -}); - PreferenceSetting( Setting('setting_display', 'viewLang', null, langRange, 'UI Language'), Setting('setting_display', 'timeZone', 'Asia/Shanghai', timezones, 'Timezone'), @@ -207,6 +199,9 @@ SystemSetting( Setting('setting_storage', 'installid', String.random(64), 'text', 'installid', 'Installation ID', FLAG_HIDDEN | FLAG_DISABLED), ); +// eslint-disable-next-line import/no-mutable-exports +export let langs = {}; + bus.once('app/started', async () => { logger.debug('Ensuring settings'); const system = global.Hydro.model.system; @@ -219,13 +214,22 @@ bus.once('app/started', async () => { } } try { - SETTINGS_BY_KEY['codeLang'].range = yaml.load(system.get('lang.texts')) as any; + langs = parseLang(system.get('hydrooj.langs')); + global.Hydro.model.setting.langs = langs; + const range = {}; + for (const key in langs) range[key] = langs[key].display; + LangSettingNode.range = range; } catch (e) { /* Ignore */ } }); bus.on('system/setting', (args) => { - if (!args.lang?.texts) return; - SETTINGS_BY_KEY['codeLang'].range = yaml.load(args.lang.texts) as any; + if (args.hydrooj?.langs) { + langs = parseLang(args.hydrooj.langs); + global.Hydro.model.setting.langs = langs; + const range = {}; + for (const key in langs) range[key] = langs[key].display; + LangSettingNode.range = range; + } }); global.Hydro.model.setting = { @@ -248,4 +252,5 @@ global.Hydro.model.setting = { DOMAIN_SETTINGS_BY_KEY, DOMAIN_USER_SETTINGS, DOMAIN_USER_SETTINGS_BY_KEY, + langs, }; diff --git a/packages/hydrooj/src/model/system.ts b/packages/hydrooj/src/model/system.ts index d005757ac..550354051 100644 --- a/packages/hydrooj/src/model/system.ts +++ b/packages/hydrooj/src/model/system.ts @@ -71,13 +71,8 @@ export async function runConfig() { await bus.emit('database/config'); } -// eslint-disable-next-line import/no-mutable-exports -export let lang = {}; - bus.on('system/setting', (args) => { for (const key in args) set(key, args[key], false); - if (!args['hydrooj.lang']) return; - lang = parseLang(args['hydrooj.lang']); }); global.Hydro.model.system = { @@ -86,5 +81,4 @@ global.Hydro.model.system = { getMany, inc, set, - lang, }; diff --git a/packages/ui-default/handler.js b/packages/ui-default/handler.js index eb12b7263..4af960370 100644 --- a/packages/ui-default/handler.js +++ b/packages/ui-default/handler.js @@ -2,12 +2,13 @@ const { readdirSync, readFileSync } = require('fs'); const { join } = require('path'); const crypto = require('crypto'); -const yaml = require('js-yaml'); const { tmpdir } = require('os'); const bus = require('hydrooj/dist/service/bus'); const markdown = require('./backendlib/markdown.js'); -const { system, domain, user } = global.Hydro.model; +const { + system, domain, user, setting, +} = global.Hydro.model; const { Route, Handler, UiContextBase } = global.Hydro.service.server; class WikiHelpHandler extends Handler { @@ -17,20 +18,13 @@ class WikiHelpHandler extends Handler { } async get() { - const [langstr, langtexts] = system.getMany(['hydrojudge.langs', 'lang.texts']); - if (langstr) { - const languages = {}; - const LANGS = yaml.load(langstr); - const TEXTS = yaml.load(langtexts); - // eslint-disable-next-line guard-for-in - for (const key in LANGS) { - const name = TEXTS[key] || key; - languages[name] = LANGS[key].type === 'compiler' - ? LANGS[key].compile - : LANGS[key].execute; - } - this.response.body = { languages }; + const LANGS = system.get('hydrooj.langs'); + const languages = {}; + // eslint-disable-next-line guard-for-in + for (const key in LANGS) { + languages[LANGS[key].display] = LANGS[key].compile || LANGS[key].execute; } + this.response.body = { languages }; this.response.template = 'wiki_help.html'; } } @@ -53,7 +47,7 @@ class UiConstantsHandler extends Handler { } async get() { - this.response.body = `window.LANGS=${JSON.stringify(system.langs)}`; + this.response.body = `window.LANGS=${JSON.stringify(setting.langs)}`; this.response.type = 'text/javascript'; this.ctx.set('nolog', '1'); } @@ -71,7 +65,7 @@ class UiSettingsHandler extends Handler { nav_logo_dark, nav_logo_light, nav_logo_dark_2x, nav_logo_light_2x, header_background, header_background_2x, - ] = await system.getMany([ + ] = system.getMany([ 'ui-default.header_logo', 'ui-default.header_logo_2x', 'ui-default.nav_logo_dark', 'ui-default.nav_logo_light', 'ui-default.nav_logo_dark_2x', 'ui-default.nav_logo_light_2x', diff --git a/packages/ui-default/package.json b/packages/ui-default/package.json index 4b8a2bf47..2777bafbd 100644 --- a/packages/ui-default/package.json +++ b/packages/ui-default/package.json @@ -1,6 +1,6 @@ { "name": "@hydrooj/ui-default", - "version": "4.8.0", + "version": "4.8.1", "author": "undefined ", "license": "AGPL-3.0", "main": "hydro.js", @@ -22,7 +22,7 @@ "@babel/preset-react": "^7.13.13", "@babel/register": "^7.13.14", "@babel/runtime-corejs2": "^7.14.0", - "@hydrooj/utils": "^1.0.14", + "@hydrooj/utils": "^1.0.15", "ansi_up": "^5.0.1", "autoprefixer": "^9.8.6", "babel-loader": "^8.2.2", diff --git a/packages/ui-default/pages/home_preference.page.jsx b/packages/ui-default/pages/home_preference.page.jsx index 8552983f3..5d0f97d3a 100644 --- a/packages/ui-default/pages/home_preference.page.jsx +++ b/packages/ui-default/pages/home_preference.page.jsx @@ -2,6 +2,7 @@ import { NamedPage } from 'vj/misc/Page'; import load from 'vj/components/wastyle/index'; import code from 'vj/components/highlighter/code-example'; import i18n from 'vj/utils/i18n'; +import tpl from 'vj/utils/tpl'; const page = new NamedPage('home_preference', async () => { async function mountComponent() { @@ -60,6 +61,66 @@ const page = new NamedPage('home_preference', async () => { } } + function setOptions($el, options) { + $el.empty(); + $.each(options, (key, value) => { + $el.append($('').attr('value', key).text(value)); + }); + } + + const $el = $(tpl`\ +
+
+ +
+ +
+`); + $('[name="codeLang"]') + .parent().parent().parent() + .parent() + .hide() + .after($el); + + $('#codelang-main-select').on('change', function () { + $('[name="codeLang"]').val(this.value); + const options = {}; + for (const key in window.LANGS) { + if (`${key.startsWith(this.value)}.` && key !== this.value) options[key] = window.LANGS[key].display; + } + if (Object.keys(options).length > 1) { + setOptions($('#codelang-sub-select'), options); + $('#codelang-sub-container').show(); + } else $('#codelang-sub-container').hide(); + }); + $('#codelang-sub-select').on('change', function () { + $('[name="codeLang"]').val(this.value); + }); + const main = {}; + for (const key in window.LANGS) { + if (!key.includes('.')) main[key] = window.LANGS[key].display; + } + setOptions($('#codelang-main-select'), main); + const current = $('[name="codeLang"]').val(); + console.log(current); + if (current.includes('.')) { + const [m, s] = current.split('.'); + $('#codelang-main-select').val(m); + $('#codelang-sub-select').val(s); + } else $('#codelang-main-select').val(current); + if (success) { render( , diff --git a/packages/ui-default/pages/problem_submit.page.js b/packages/ui-default/pages/problem_submit.page.js index b2ed28d90..4ee2888ca 100644 --- a/packages/ui-default/pages/problem_submit.page.js +++ b/packages/ui-default/pages/problem_submit.page.js @@ -1,10 +1,43 @@ import { NamedPage } from 'vj/misc/Page'; +function setOptions($el, options) { + $el.empty(); + $.each(options, (key, value) => { + $el.append($('').attr('value', key).text(value)); + }); +} + const page = new NamedPage('problem_submit', async () => { $(document).on('click', '[name="problem-sidebar__show-category"]', (ev) => { $(ev.currentTarget).hide(); $('[name="problem-sidebar__categories"]').show(); }); + $('#codelang-main-select').on('change', function () { + $('[name="lang"]').val(this.value); + const options = {}; + for (const key in window.LANGS) { + if (`${key.startsWith(this.value)}.` && key !== this.value) options[key] = window.LANGS[key].display; + } + if (Object.keys(options).length > 1) { + setOptions($('#codelang-sub-select'), options); + $('#codelang-sub-container').show(); + } else $('#codelang-sub-container').hide(); + }); + $('#codelang-sub-select').on('change', function () { + $('[name="lang"]').val(this.value); + }); + const main = {}; + for (const key in window.LANGS) { + if (!key.includes('.')) main[key] = window.LANGS[key].display; + } + setOptions($('#codelang-main-select'), main); + const current = $('[name="lang"]').val(); + console.log(current); + if (current.includes('.')) { + const [m, s] = current.split('.'); + $('#codelang-main-select').val(m); + $('#codelang-sub-select').val(s); + } else $('#codelang-main-select').val(current); }); export default page; diff --git a/packages/ui-default/templates/components/form.html b/packages/ui-default/templates/components/form.html index dccad2f4c..731183ac6 100644 --- a/packages/ui-default/templates/components/form.html +++ b/packages/ui-default/templates/components/form.html @@ -50,6 +50,7 @@ {% set noAutocomplete = args.noAutocomplete or false %} {% set extra_style = args.extra_style or false %} {% set extra_class = args.extra_class %} +{% set extra_attr = args.extra_attr %} {% set date = args.date or false %} {% set time = args.time or false %} class="{{ form_class }}{% if extra_class %} {{ extra_class }}{% endif %}" @@ -59,6 +60,7 @@ {% if noAutocomplete %}autocomplete="off"{% endif %} {% if date %}data-pick-date{% endif %} {% if time %}data-pick-time{% endif %} + {% if extra_attr %}{{ extra_attr }}{% endif %} {% if extra_style %}style="{{extra_style}}"{% endif %} {% endmacro %} diff --git a/packages/ui-default/templates/problem_submit.html b/packages/ui-default/templates/problem_submit.html index f23d3710c..bb58c2fcf 100644 --- a/packages/ui-default/templates/problem_submit.html +++ b/packages/ui-default/templates/problem_submit.html @@ -42,12 +42,32 @@
- {{ form.form_select({ - label:'Code language', - options:model.system.get('lang.texts')|parseYaml, - name:'lang', - value:handler.user.codeLang - }) }} +
+ {{ form.form_select({ + label:'Code language', + options:model.setting.SETTINGS_BY_KEY['codeLang'].range, + name:'lang', + value:handler.user.codeLang + }) }} +
+
+
+ +
+ +
{{ form.form_textarea({label:'Code', columns:12, name:'code', extra_class:'monospace', autofocus:true}) }}
diff --git a/packages/ui-default/templates/record_detail.html b/packages/ui-default/templates/record_detail.html index 4bddcf168..983eb607a 100644 --- a/packages/ui-default/templates/record_detail.html +++ b/packages/ui-default/templates/record_detail.html @@ -63,7 +63,7 @@ {% endif %}
{{ _('Language') }}
- {{ (model.system.get('lang.texts')|parseYaml)[rdoc['lang']] }} + {{ model.setting.langs[rdoc.lang].display }}
{{ _('Submit At') }} diff --git a/packages/ui-default/templates/record_main_tr.html b/packages/ui-default/templates/record_main_tr.html index 1b7653d68..1f859cdcc 100644 --- a/packages/ui-default/templates/record_main_tr.html +++ b/packages/ui-default/templates/record_main_tr.html @@ -30,6 +30,6 @@ {% if rdoc['status'] == STATUS.STATUS_TIME_LIMIT_EXCEEDED or rdoc['status'] == STATUS.STATUS_MEMORY_LIMIT_EXCEEDED or rdoc['status'] == STATUS.STATUS_OUTPUT_LIMIT_EXCEEDED %}≥{% endif %}{{ rdoc.time|round|int }}ms {% if rdoc['status'] == STATUS.STATUS_TIME_LIMIT_EXCEEDED or rdoc['status'] == STATUS.STATUS_MEMORY_LIMIT_EXCEEDED or rdoc['status'] == STATUS.STATUS_OUTPUT_LIMIT_EXCEEDED %}≥{% endif %}{{ size(rdoc.memory, 1024) }} - {{ (model.system.get('lang.texts')|parseYaml)[rdoc['lang']] }} + {{ model.setting.langs[rdoc['lang']].display }} {{ datetimeSpan(rdoc['_id'])|safe }} diff --git a/packages/utils/lib/lang.ts b/packages/utils/lib/lang.ts index 3eabc3c6e..1b997ba95 100644 --- a/packages/utils/lib/lang.ts +++ b/packages/utils/lib/lang.ts @@ -24,6 +24,7 @@ export function parseLang(config: string): Record { const baseCfg = file[base]; if (baseCfg) { for (const bkey in baseCfg) { + if (bkey === 'sub') continue; if (!(bkey in entry)) entry[bkey] = baseCfg[bkey]; } if (baseCfg.sub) baseCfg.sub.push(entry); diff --git a/packages/utils/package.json b/packages/utils/package.json index 355ff4605..c6ed867e6 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -1,6 +1,6 @@ { "name": "@hydrooj/utils", - "version": "1.0.14", + "version": "1.0.15", "description": "hydrooj utils", "main": "package.json", "repository": "https://github.com/hydro-dev/Hydro.git", From 0af07a5d164bd45538331f28af880a99e3eb5044 Mon Sep 17 00:00:00 2001 From: undefined Date: Fri, 7 May 2021 22:25:18 +0800 Subject: [PATCH 3/5] ui: fix cond --- packages/ui-default/package.json | 2 +- packages/ui-default/pages/home_preference.page.jsx | 2 +- packages/ui-default/pages/problem_submit.page.js | 3 +-- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/ui-default/package.json b/packages/ui-default/package.json index 2777bafbd..acd650137 100644 --- a/packages/ui-default/package.json +++ b/packages/ui-default/package.json @@ -1,6 +1,6 @@ { "name": "@hydrooj/ui-default", - "version": "4.8.1", + "version": "4.8.2", "author": "undefined ", "license": "AGPL-3.0", "main": "hydro.js", diff --git a/packages/ui-default/pages/home_preference.page.jsx b/packages/ui-default/pages/home_preference.page.jsx index 5d0f97d3a..c5ad01000 100644 --- a/packages/ui-default/pages/home_preference.page.jsx +++ b/packages/ui-default/pages/home_preference.page.jsx @@ -98,7 +98,7 @@ const page = new NamedPage('home_preference', async () => { $('[name="codeLang"]').val(this.value); const options = {}; for (const key in window.LANGS) { - if (`${key.startsWith(this.value)}.` && key !== this.value) options[key] = window.LANGS[key].display; + if (key.startsWith(`${this.value}.`) && key !== this.value) options[key] = window.LANGS[key].display; } if (Object.keys(options).length > 1) { setOptions($('#codelang-sub-select'), options); diff --git a/packages/ui-default/pages/problem_submit.page.js b/packages/ui-default/pages/problem_submit.page.js index 4ee2888ca..c18be6119 100644 --- a/packages/ui-default/pages/problem_submit.page.js +++ b/packages/ui-default/pages/problem_submit.page.js @@ -16,7 +16,7 @@ const page = new NamedPage('problem_submit', async () => { $('[name="lang"]').val(this.value); const options = {}; for (const key in window.LANGS) { - if (`${key.startsWith(this.value)}.` && key !== this.value) options[key] = window.LANGS[key].display; + if (key.startsWith(`${this.value}.`) && key !== this.value) options[key] = window.LANGS[key].display; } if (Object.keys(options).length > 1) { setOptions($('#codelang-sub-select'), options); @@ -32,7 +32,6 @@ const page = new NamedPage('problem_submit', async () => { } setOptions($('#codelang-main-select'), main); const current = $('[name="lang"]').val(); - console.log(current); if (current.includes('.')) { const [m, s] = current.split('.'); $('#codelang-main-select').val(m); From 29c8fded622f85fbf396aa47fbfe834302c9e13c Mon Sep 17 00:00:00 2001 From: undefined Date: Fri, 7 May 2021 22:29:59 +0800 Subject: [PATCH 4/5] remove unused import --- packages/hydrooj/src/model/system.ts | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/packages/hydrooj/src/model/system.ts b/packages/hydrooj/src/model/system.ts index 550354051..4efb0bfb3 100644 --- a/packages/hydrooj/src/model/system.ts +++ b/packages/hydrooj/src/model/system.ts @@ -1,4 +1,3 @@ -import { parseLang } from '@hydrooj/utils/lib/lang'; import { SYSTEM_SETTINGS } from './setting'; import { NumberKeys } from '../typeutils'; import { SystemKeys } from '../interface'; @@ -16,22 +15,22 @@ export function get(key: string): any { export function getMany< A extends keyof SystemKeys, B extends keyof SystemKeys, ->(keys: [A, B]): [SystemKeys[A], SystemKeys[B]]; + >(keys: [A, B]): [SystemKeys[A], SystemKeys[B]]; export function getMany< A extends keyof SystemKeys, B extends keyof SystemKeys, C extends keyof SystemKeys, ->(keys: [A, B, C]): [SystemKeys[A], SystemKeys[B], SystemKeys[C]]; + >(keys: [A, B, C]): [SystemKeys[A], SystemKeys[B], SystemKeys[C]]; export function getMany< A extends keyof SystemKeys, B extends keyof SystemKeys, C extends keyof SystemKeys, D extends keyof SystemKeys, ->(keys: [A, B, C, D]): [SystemKeys[A], SystemKeys[B], SystemKeys[C], SystemKeys[D]]; + >(keys: [A, B, C, D]): [SystemKeys[A], SystemKeys[B], SystemKeys[C], SystemKeys[D]]; export function getMany< A extends keyof SystemKeys, B extends keyof SystemKeys, C extends keyof SystemKeys, D extends keyof SystemKeys, E extends keyof SystemKeys, ->(keys: [A, B, C, D, E]): [SystemKeys[A], SystemKeys[B], SystemKeys[C], SystemKeys[D], SystemKeys[E]]; + >(keys: [A, B, C, D, E]): [SystemKeys[A], SystemKeys[B], SystemKeys[C], SystemKeys[D], SystemKeys[E]]; export function getMany< A extends keyof SystemKeys, B extends keyof SystemKeys, C extends keyof SystemKeys, D extends keyof SystemKeys, E extends keyof SystemKeys, F extends keyof SystemKeys, ->(keys: [A, B, C, D, E, F]): [SystemKeys[A], SystemKeys[B], SystemKeys[C], SystemKeys[D], SystemKeys[E], SystemKeys[F]]; + >(keys: [A, B, C, D, E, F]): [SystemKeys[A], SystemKeys[B], SystemKeys[C], SystemKeys[D], SystemKeys[E], SystemKeys[F]]; export function getMany(keys: (keyof SystemKeys)[]): any[]; export function getMany(keys: string[]): any[] { return keys.map((key) => cache[key]); From 03cddc00b2694866cfa8d9934e94392202528cf4 Mon Sep 17 00:00:00 2001 From: undefined Date: Fri, 7 May 2021 23:14:34 +0800 Subject: [PATCH 5/5] =?UTF-8?q?ui:=20=E4=BC=98=E5=8C=96=E7=B3=BB=E7=BB=9F?= =?UTF-8?q?=E8=AE=BE=E7=BD=AE=E9=A1=B5=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/hydrooj/package.json | 2 +- packages/hydrooj/setting.yaml | 19 +++++++++++-------- .../components/editor/cmeditor.page.js | 6 ++++-- .../ui-default/components/editor/index.js | 6 ++++++ packages/ui-default/package.json | 2 +- .../ui-default/templates/components/form.html | 2 +- .../templates/partials/setting.html | 3 ++- 7 files changed, 26 insertions(+), 14 deletions(-) diff --git a/packages/hydrooj/package.json b/packages/hydrooj/package.json index 0b951f2b8..d464a30c2 100644 --- a/packages/hydrooj/package.json +++ b/packages/hydrooj/package.json @@ -1,6 +1,6 @@ { "name": "hydrooj", - "version": "2.25.1", + "version": "2.25.2", "bin": "bin/hydrooj.js", "main": "dist/loader.js", "typings": "dist/loader.d.ts", diff --git a/packages/hydrooj/setting.yaml b/packages/hydrooj/setting.yaml index dcfabbcf1..88bbfac6a 100644 --- a/packages/hydrooj/setting.yaml +++ b/packages/hydrooj/setting.yaml @@ -3,14 +3,17 @@ langs: desc: Code language settings default: __hint__: - - All keys started with _ will be ignored - - Entries named like [foo], [foo].[bar] [foo].[baz] will be merged into one category. - - A entry named [foo].[bar] will inhert all options defined in entry [foo] if not overwritten - - code_file option defaults to foo.[name] - - highlight option defaults to [name] - - if highlight option was set, monaco option defaults to [highlight], otherwise defaults to [name] - - Compile step will be ignored if compile option was not set - - Key marked with a ? means the option is optional + - ~ All keys starting with _ will be ignored + - ~ Entries named like [foo], [foo].[bar] [foo].[baz] + - will be merged into one category. + - ~ A entry named [foo].[bar] will inhert all options + - defined in entry [foo] if not overwritten + - ~ code_file option defaults to foo.[name] + - ~ highlight option defaults to [name] + - ~ if highlight option was set, monaco option defaults + - to [highlight], otherwise defaults to [name] + - ~ Compiling be be ignored if compile option wasn't set + - ~ Key marked with a ? means the option is optional - (name): compile?: Compile Command code_file?: Where to write code file when compiling diff --git a/packages/ui-default/components/editor/cmeditor.page.js b/packages/ui-default/components/editor/cmeditor.page.js index 8d4459c1a..c59e7aed3 100644 --- a/packages/ui-default/components/editor/cmeditor.page.js +++ b/packages/ui-default/components/editor/cmeditor.page.js @@ -3,10 +3,12 @@ import delay from 'vj/utils/delay'; import CmEditor from '.'; function runSubstitute($container) { - const selector = ['textarea[data-markdown]']; - $container.find(selector.join(', ')).get().forEach((element) => { + $container.find('textarea[data-markdown]').get().forEach((element) => { CmEditor.getOrConstruct($(element)); }); + $container.find('textarea[data-yaml]').get().forEach((element) => { + CmEditor.getOrConstruct($(element), { language: 'yaml' }); + }); } const cmEditorPage = new AutoloadPage('cmEditorPage', () => { diff --git a/packages/ui-default/components/editor/index.js b/packages/ui-default/components/editor/index.js index 66c84a2a9..85a4d0316 100644 --- a/packages/ui-default/components/editor/index.js +++ b/packages/ui-default/components/editor/index.js @@ -58,10 +58,16 @@ export default class Editor extends DOMAttachedObject { lightbulb: { enabled: true }, model: this.model, automaticLayout: true, + wordWarp: 'bounded', + minimap: { enabled: false }, + hideCursorInOverviewRuler: true, + overviewRulerLanes: 0, + overviewRulerBorder: false, }; if (autoResize) { cfg.scrollbar = { vertical: 'hidden', + horizontal: 'hidden', handleMouseWheel: false, }; cfg.scrollBeyondLastLine = false; diff --git a/packages/ui-default/package.json b/packages/ui-default/package.json index acd650137..4004fcb69 100644 --- a/packages/ui-default/package.json +++ b/packages/ui-default/package.json @@ -1,6 +1,6 @@ { "name": "@hydrooj/ui-default", - "version": "4.8.2", + "version": "4.8.3", "author": "undefined ", "license": "AGPL-3.0", "main": "hydro.js", diff --git a/packages/ui-default/templates/components/form.html b/packages/ui-default/templates/components/form.html index 731183ac6..d90194778 100644 --- a/packages/ui-default/templates/components/form.html +++ b/packages/ui-default/templates/components/form.html @@ -183,7 +183,7 @@ {% endmacro %} {% macro form_textarea(args) %} -{{ form_begin({columns:8}|assign(args)) }} +{{ form_begin({columns:10}|assign(args)) }} {{ textarea(args) }} {{ form_end(args) }} {% endmacro %} diff --git a/packages/ui-default/templates/partials/setting.html b/packages/ui-default/templates/partials/setting.html index 7744009b2..3abd8dab6 100644 --- a/packages/ui-default/templates/partials/setting.html +++ b/packages/ui-default/templates/partials/setting.html @@ -52,7 +52,8 @@ name:setting.key, value:'' if secret else (current[setting.key]|default(setting.value)), disabled:setting.disabled, - placeholder:_('(Not changed)') if secret else '' + placeholder:_('(Not changed)') if secret else '', + extra_attr: 'data-yaml' if setting.subType == 'yaml' else '' }) }} {% elif setting.type == 'markdown' %} {{ form.form_textarea({