模块化,添加webpack支持
parent
4ec2a46877
commit
75ebc1a746
@ -0,0 +1,53 @@
|
||||
const fs = require('fs');
|
||||
const webpack = require('webpack');
|
||||
const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin');
|
||||
const root = require('./root');
|
||||
const exist = (name) => {
|
||||
try {
|
||||
fs.statSync(root(name));
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
const build = async () => {
|
||||
const modules = fs.readdirSync(root('module'));
|
||||
const config = {
|
||||
mode: 'production',
|
||||
entry: {},
|
||||
output: {
|
||||
filename: 'module/[name].js',
|
||||
path: root('.build')
|
||||
},
|
||||
target: 'node',
|
||||
module: {},
|
||||
plugins: [
|
||||
new webpack.ProgressPlugin(),
|
||||
new FriendlyErrorsPlugin(),
|
||||
]
|
||||
};
|
||||
for (let i of modules) {
|
||||
if (!i.startsWith('.')) {
|
||||
if (exist(`module/${i}/model.js`)) {
|
||||
config.entry[`${i}/model`] = root(`module/${i}/model.js`);
|
||||
}
|
||||
if (exist(`module/${i}/handler.js`)) {
|
||||
config.entry[`${i}/handler`] = root(`module/${i}/handler.js`);
|
||||
}
|
||||
}
|
||||
}
|
||||
const compiler = webpack(config);
|
||||
await new Promise((resolve, reject) => {
|
||||
compiler.run((err, stats) => {
|
||||
if (err) {
|
||||
console.error(err.stack || err);
|
||||
if (err.details) console.error(err.details);
|
||||
reject();
|
||||
}
|
||||
if (stats.hasErrors()) process.exitCode = 1;
|
||||
resolve();
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = build;
|
@ -0,0 +1,43 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
let installed;
|
||||
function root(name) {
|
||||
return path.resolve(process.cwd(), name);
|
||||
}
|
||||
function exist(name) {
|
||||
try {
|
||||
fs.statSync(root(name));
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
const superRequire = (name) => (__non_webpack_require__
|
||||
? __non_webpack_require__(root(name))
|
||||
: require(root(name)));
|
||||
|
||||
async function prepare() {
|
||||
installed = fs.readdirSync(root('.build/module'));
|
||||
}
|
||||
|
||||
async function handler() {
|
||||
for (const i of installed) {
|
||||
if (exist(`.build/module/${i}/handler.js`)) {
|
||||
superRequire(`.build/module/${i}/handler.js`);
|
||||
}
|
||||
console.log(`Handler init: ${i}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function model() {
|
||||
for (const i of installed) {
|
||||
if (exist(`.build/module/${i}/model.js`)) {
|
||||
superRequire(`.build/module/${i}/model.js`);
|
||||
}
|
||||
console.log(`Model init: ${i}`);
|
||||
}
|
||||
}
|
||||
|
||||
global.Hydro['lib.loader'] = module.exports = { prepare, handler, model };
|
@ -0,0 +1,60 @@
|
||||
const builtin = require('./builtin');
|
||||
const options = require('../options');
|
||||
const i18n = require('../lib/i18n');
|
||||
|
||||
const Setting = (
|
||||
family, key, range = null,
|
||||
value = null, ui = 'text', name = '',
|
||||
desc = '', imageClass = '',
|
||||
) => ({
|
||||
family, key, range, value, ui, name, desc, imageClass,
|
||||
});
|
||||
|
||||
const PREFERENCE_SETTINGS = [
|
||||
Setting('setting_display', 'view_lang', i18n,
|
||||
options.default_locale, 'select', 'UI Language'),
|
||||
Setting('setting_display', 'timezone', [], // TODO(masnn) timezone
|
||||
'Asia/Shanghai', 'select', 'Timezone'),
|
||||
Setting('setting_usage', 'code_lang', builtin.LANG_TEXTS,
|
||||
null, 'select', 'Default Code Language'),
|
||||
Setting('setting_usage', 'code_template', null,
|
||||
null, 'textarea', 'Default Code Template',
|
||||
'If left blank, the built-in template of the corresponding language will be used.'),
|
||||
];
|
||||
|
||||
const ACCOUNT_SETTINGS = [
|
||||
Setting('setting_info', 'gravatar', null,
|
||||
null, null, 'Gravatar Email',
|
||||
'We use <a href="https://en.gravatar.com/" target="_blank">Gravatar</a> to present your avatar icon.'),
|
||||
Setting('setting_info', 'qq', null,
|
||||
null, null, 'QQ'),
|
||||
Setting('setting_info', 'wechat', null,
|
||||
null, null, 'WeChat'),
|
||||
Setting('setting_info', 'gender', builtin.USER_GENDER_RANGE,
|
||||
null, 'select', 'Gender'),
|
||||
Setting('setting_info', 'bio', null,
|
||||
null, 'markdown', 'Bio'),
|
||||
Setting('setting_privacy', 'show_mail', builtin.PRIVACY_RANGE,
|
||||
null, 'select', 'Email Visibility'),
|
||||
Setting('setting_privacy', 'show_qq', builtin.PRIVACY_RANGE,
|
||||
null, 'select', 'QQ Visibility'),
|
||||
Setting('setting_privacy', 'show_wechat', builtin.PRIVACY_RANGE,
|
||||
null, 'select', 'WeChat Visibility'),
|
||||
Setting('setting_privacy', 'show_gender', builtin.PRIVACY_RANGE,
|
||||
null, 'select', 'Gender Visibility'),
|
||||
Setting('setting_privacy', 'show_bio', builtin.PRIVACY_RANGE,
|
||||
null, 'select', 'Bio Visibility'),
|
||||
Setting('setting_customize', 'background_img', builtin.BACKGROUND_RANGE,
|
||||
null, 'image_radio', 'Profile Background Image',
|
||||
'Choose the background image in your profile page.',
|
||||
'user-profile-bg--thumbnail-{0}'),
|
||||
];
|
||||
|
||||
const SETTINGS = [...PREFERENCE_SETTINGS, ...ACCOUNT_SETTINGS];
|
||||
const SETTINGS_BY_KEY = {};
|
||||
|
||||
for (const setting in SETTINGS) SETTINGS_BY_KEY[setting.key] = setting;
|
||||
|
||||
module.exports = {
|
||||
PREFERENCE_SETTINGS, ACCOUNT_SETTINGS, SETTINGS, SETTINGS_BY_KEY,
|
||||
};
|
@ -1,111 +0,0 @@
|
||||
module.exports = {
|
||||
TEXT: 'ACM/ICPC',
|
||||
check: () => { },
|
||||
showScoreboard: () => true,
|
||||
showRecord: (tdoc, now) => now > tdoc.endAt,
|
||||
stat: (tdoc, journal) => {
|
||||
const naccept = {};
|
||||
const effective = {};
|
||||
const detail = [];
|
||||
let accept = 0;
|
||||
let time = 0;
|
||||
for (const j in journal) {
|
||||
if (tdoc.pids.includes(j.pid)
|
||||
&& !(effective.includes(j.pid) && effective[j.pid].accept)) {
|
||||
effective[j.pid] = j;
|
||||
}
|
||||
if (!j.accept) naccept[j.pid]++;
|
||||
}
|
||||
function _time(jdoc) {
|
||||
const real = jdoc.rid.generationTime - Math.floor(tdoc.begin_at / 1000);
|
||||
const penalty = 20 * 60 * naccept[jdoc.pid];
|
||||
return real + penalty;
|
||||
}
|
||||
for (const j of effective) detail.push({ ...j, naccept: naccept[j.pid], time: _time(j) });
|
||||
for (const d of detail) {
|
||||
accept += d.accept;
|
||||
if (d.accept) time += d.time;
|
||||
}
|
||||
return { accept, time, detail };
|
||||
},
|
||||
scoreboard(isExport, _, tdoc, rankedTsdocs, udict, pdict) {
|
||||
const columns = [
|
||||
{ type: 'rank', value: _('Rank') },
|
||||
{ type: 'user', value: _('User') },
|
||||
{ type: 'solved_problems', value: _('Solved Problems') },
|
||||
];
|
||||
if (isExport) {
|
||||
columns.push({ type: 'total_time', value: _('Total Time (Seconds)') });
|
||||
columns.push({ type: 'total_time_str', value: _('Total Time') });
|
||||
}
|
||||
for (const i in tdoc.pids) {
|
||||
if (isExport) {
|
||||
columns.push({
|
||||
type: 'problem_flag',
|
||||
value: '#{0} {1}'.format(i + 1, pdict[tdoc.pids[i]].title),
|
||||
});
|
||||
columns.push({
|
||||
type: 'problem_time',
|
||||
value: '#{0} {1}'.format(i + 1, _('Time (Seconds)')),
|
||||
});
|
||||
columns.push({
|
||||
type: 'problem_time_str',
|
||||
value: '#{0} {1}'.format(i + 1, _('Time')),
|
||||
});
|
||||
} else {
|
||||
columns.push({
|
||||
type: 'problem_detail',
|
||||
value: '#{0}'.format(i + 1),
|
||||
raw: pdict[tdoc.pids[i]],
|
||||
});
|
||||
}
|
||||
}
|
||||
const rows = [columns];
|
||||
for (const [rank, tsdoc] of rankedTsdocs) {
|
||||
const tsddict = {};
|
||||
if (tdoc.detail) { for (const item of tsdoc.detail) tsddict[item.pid] = item; }
|
||||
const row = [];
|
||||
row.push(
|
||||
{ type: 'string', value: rank },
|
||||
{ type: 'user', value: udict[tsdoc.uid].uname, raw: udict[tsdoc.uid] },
|
||||
{ type: 'string', value: tsdoc.accept || 0 },
|
||||
);
|
||||
if (isExport) {
|
||||
row.push(
|
||||
{ type: 'string', value: tsdoc.time || 0.0 },
|
||||
{ type: 'string', value: tsdoc.time || 0.0 },
|
||||
);
|
||||
}
|
||||
for (const pid of tdoc.pids) {
|
||||
let rdoc;
|
||||
let colAccepted;
|
||||
let colTime;
|
||||
let colTimeStr;
|
||||
if ((tsddict[pid] || {}).accept) {
|
||||
rdoc = tsddict[pid].rid;
|
||||
colAccepted = _('Accepted');
|
||||
colTime = tsddict[pid].time;
|
||||
colTimeStr = colTime;
|
||||
} else {
|
||||
rdoc = null;
|
||||
colAccepted = '-';
|
||||
colTime = '-';
|
||||
colTimeStr = '-';
|
||||
}
|
||||
if (isExport) {
|
||||
row.push({ type: 'string', value: colAccepted });
|
||||
row.push({ type: 'string', value: colTime });
|
||||
row.push({ type: 'string', value: colTimeStr });
|
||||
} else {
|
||||
row.push({
|
||||
type: 'record',
|
||||
value: '{0}\n{1}'.format(colAccepted, colTimeStr),
|
||||
raw: rdoc,
|
||||
});
|
||||
}
|
||||
rows.push(row);
|
||||
}
|
||||
}
|
||||
return rows;
|
||||
},
|
||||
};
|
@ -1,95 +0,0 @@
|
||||
module.exports = {
|
||||
TEXT: 'Assignment',
|
||||
check() {},
|
||||
stat(tdoc, journal) {
|
||||
const effective = {};
|
||||
const detail = [];
|
||||
let score = 0;
|
||||
let time = 0;
|
||||
for (const j in journal) {
|
||||
if (tdoc.pids.includes(j.pid)
|
||||
&& !(effective.includes(j.pid) && effective[j.pid].accept)) {
|
||||
effective[j.pid] = j;
|
||||
}
|
||||
}
|
||||
const _time = (jdoc) => jdoc.rid.generationTime - Math.floor(tdoc.beginAt / 1000);
|
||||
for (const j in effective) {
|
||||
detail.push({
|
||||
...effective[j],
|
||||
time: _time(effective[j]),
|
||||
});
|
||||
}
|
||||
for (const d of detail) {
|
||||
score += d.score;
|
||||
time += d.time;
|
||||
}
|
||||
return {
|
||||
score, time, detail,
|
||||
};
|
||||
},
|
||||
scoreboard(isExport, _, tdoc, rankedTsdocs, udict, pdict) {
|
||||
const columns = [
|
||||
{ type: 'rank', value: _('Rank') },
|
||||
{ type: 'user', value: _('User') },
|
||||
{ type: 'display_name', value: _('Display Name') },
|
||||
{ type: 'total_score', value: _('Score') },
|
||||
];
|
||||
if (isExport) {
|
||||
columns.push(
|
||||
{ type: 'total_original_score', value: _('Original Score') },
|
||||
{ type: 'total_time', value: _('Total Time (Seconds)') },
|
||||
);
|
||||
}
|
||||
columns.push({ type: 'total_time_str', value: _('Total Time') });
|
||||
for (const i in tdoc.pids) {
|
||||
if (isExport) {
|
||||
columns.push(
|
||||
{ type: 'problem_score', value: '#{0} {1}'.format(i + 1, pdict[tdoc.pids[i]].title) },
|
||||
{ type: 'problem_original_score', value: '#{0} {1}'.format(i + 1, _('Original Score')) },
|
||||
{ type: 'problem_time', value: '#{0} {1}'.format(i + 1, _('Time (Seconds)')) },
|
||||
{ type: 'problem_time_str', value: '#{0} {1}'.format(i + 1, _('Time')) },
|
||||
);
|
||||
} else columns.push({ type: 'problem_detail', value: '#{0}'.format(i + 1), raw: pdict[tdoc.pids[i]] });
|
||||
}
|
||||
const rows = [columns];
|
||||
for (const [rank, tsdoc] in rankedTsdocs) {
|
||||
const tsddict = {};
|
||||
if (tsdoc.detail) { for (const item of tsdoc.detail) tsddict[item.pid] = item; }
|
||||
const row = [
|
||||
{ type: 'string', value: rank },
|
||||
{ type: 'user', value: udict[tsdoc.uid].uname, raw: udict[tsdoc.uid] },
|
||||
{ type: 'string', value: tsdoc.penalty_score || 0 },
|
||||
];
|
||||
if (isExport) {
|
||||
row.push(
|
||||
{ type: 'string', value: tsdoc.score || 0 },
|
||||
{ type: 'string', value: tsdoc.time || 0.0 },
|
||||
);
|
||||
}
|
||||
row.push({ type: 'string', value: tsdoc.time || 0 });
|
||||
for (const pid of tdoc.pids) {
|
||||
const rdoc = (tsddict[pid] || {}).rid || null;
|
||||
const colScore = (tsddict[pid] || {}).penalty_score || '-';
|
||||
const colOriginalScore = (tsddict[pid] || {}).score || '-';
|
||||
const colTime = (tsddict[pid] || {}).time || '-';
|
||||
const colTimeStr = colTime !== '-' ? colTime : '-';
|
||||
if (isExport) {
|
||||
row.push(
|
||||
{ type: 'string', value: colScore },
|
||||
{ type: 'string', value: colOriginalScore },
|
||||
{ type: 'string', value: colTime },
|
||||
{ type: 'string', value: colTimeStr },
|
||||
);
|
||||
} else {
|
||||
row.push({
|
||||
type: 'record',
|
||||
value: '{0} / {1}\n{2}'.format(colScore, colOriginalScore, colTimeStr),
|
||||
raw: rdoc,
|
||||
});
|
||||
}
|
||||
}
|
||||
rows.push(row);
|
||||
}
|
||||
return rows;
|
||||
},
|
||||
};
|
@ -1,63 +0,0 @@
|
||||
const ranked = require('../../lib/rank');
|
||||
|
||||
module.exports = {
|
||||
TEXT: 'OI',
|
||||
check: () => { },
|
||||
stat: (tdoc, journal) => {
|
||||
const detail = {};
|
||||
let score = 0;
|
||||
for (const j in journal) {
|
||||
if (tdoc.pids.includes(j.pid)) {
|
||||
detail[j.pid] = j;
|
||||
score += j.score;
|
||||
}
|
||||
}
|
||||
return { score, detail };
|
||||
},
|
||||
showScoreboard(tdoc, now) {
|
||||
return now > tdoc.endAt;
|
||||
},
|
||||
showRecord(tdoc, now) {
|
||||
return now > tdoc.endAt;
|
||||
},
|
||||
scoreboard(isExport, _, tdoc, rankedTsdocs, udict, pdict) {
|
||||
const columns = [
|
||||
{ type: 'rank', value: _('Rank') },
|
||||
{ type: 'user', value: _('User') },
|
||||
{ type: 'total_score', value: _('Total Score') },
|
||||
];
|
||||
for (const i in tdoc.pids) {
|
||||
if (isExport) {
|
||||
columns.push({
|
||||
type: 'problem_score',
|
||||
value: '#{0} {1}'.format(i + 1, pdict[tdoc.pids[i]].title),
|
||||
});
|
||||
} else {
|
||||
columns.push({
|
||||
type: 'problem_detail',
|
||||
value: '#{0}'.format(i + 1),
|
||||
raw: pdict[tdoc.pids[i]],
|
||||
});
|
||||
}
|
||||
}
|
||||
const rows = [columns];
|
||||
for (const [rank, tsdoc] of rankedTsdocs) {
|
||||
const tsddict = {};
|
||||
if (tsdoc.journal) { for (const item of tsdoc.journal) tsddict[item.pid] = item; }
|
||||
const row = [];
|
||||
row.push({ type: 'string', value: rank });
|
||||
row.push({ type: 'user', value: udict[tsdoc.uid].uname, raw: udict[tsdoc.uid] });
|
||||
row.push({ type: 'string', value: tsdoc.score || 0 });
|
||||
for (const pid of tdoc.pids) {
|
||||
row.push({
|
||||
type: 'record',
|
||||
value: (tsddict[pid] || {}).score || '-',
|
||||
raw: (tsddict[pid] || {}).rid || null,
|
||||
});
|
||||
}
|
||||
rows.push(row);
|
||||
}
|
||||
return rows;
|
||||
},
|
||||
rank: (tdocs) => ranked(tdocs, (a, b) => a.score === b.score),
|
||||
};
|
Loading…
Reference in New Issue