From 80ac44d98e4198f2820fdcb1679a704fa565b884 Mon Sep 17 00:00:00 2001 From: undefined Date: Fri, 7 May 2021 22:22:24 +0800 Subject: [PATCH] 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",