From 0ee306d0cffa95b108749126e53e1977cb25b675 Mon Sep 17 00:00:00 2001 From: undefined Date: Fri, 17 Jul 2020 19:20:42 +0800 Subject: [PATCH] webpack hot reload support --- hydro/entry/common.ts | 2 -- hydro/handler/manage.ts | 18 +++++----- hydro/handler/user.ts | 14 ++++---- hydro/lib/rating.ts | 13 +++++++- hydro/script/rating.ts | 23 +++---------- hydro/service/server.ts | 26 +++++++++++---- ui/build/config/gulp.js | 1 - ui/build/config/webpack.js | 2 +- ui/build/main.js | 2 +- ui/build/runGulp.js | 11 +++---- .../autocomplete/DomainSelectAutoComplete.js | 2 +- .../autocomplete/UserSelectAutoComplete.js | 2 +- ui/components/notification/index.js | 2 +- ui/components/scratchpad/index.js | 1 + ui/components/socket/index.js | 33 +++++++++++++++---- 15 files changed, 89 insertions(+), 63 deletions(-) diff --git a/hydro/entry/common.ts b/hydro/entry/common.ts index 0fc3b3eeb..900192a26 100644 --- a/hydro/entry/common.ts +++ b/hydro/entry/common.ts @@ -220,11 +220,9 @@ export async function wiki(pending: string[], fail: string[]) { try { const categories = fs.readdirSync(p); for (const category of categories) { - console.log('category', category); if (!fs.statSync(p).isDirectory()) return; const pages = fs.readdirSync(path.join(p, category)); if (!global.Hydro.wiki[category]) global.Hydro.wiki[category] = {}; - console.log(pages); for (const page of pages) { const c = fs.readFileSync(path.join(p, category, page)).toString().split('\n'); const pagename = page.split('.')[0]; diff --git a/hydro/handler/manage.ts b/hydro/handler/manage.ts index 59b4788b9..7657a2f6f 100644 --- a/hydro/handler/manage.ts +++ b/hydro/handler/manage.ts @@ -57,9 +57,9 @@ class SystemCheckConnHandler extends ConnectionHandler { } async check() { - const log = (payload) => this.send({ type: 'log', payload }); - const warn = (payload) => this.send({ type: 'warn', payload }); - const error = (payload) => this.send({ type: 'error', payload }); + const log = (payload: any) => this.send({ type: 'log', payload }); + const warn = (payload: any) => this.send({ type: 'warn', payload }); + const error = (payload: any) => this.send({ type: 'error', payload }); await check.start(log, warn, error, (id) => { this.id = id; }); } @@ -108,10 +108,10 @@ class SystemScriptHandler extends SystemHandler { args = JSON.parse(args); validate(global.Hydro.script[id].validate, args); const rid = await record.add(domainId, { - pid: 1, + pid: -1, uid: this.user._id, - lang: null, - code: null, + lang: '-', + code: id, status: STATUS.STATUS_JUDGING, hidden: true, }, false); @@ -122,7 +122,7 @@ class SystemScriptHandler extends SystemHandler { setTimeout(() => { const start = new Date().getTime(); global.Hydro.script[id].run(args, report) - .then((ret) => { + .then((ret: any) => { const time = new Date().getTime() - start; judge.end({ domainId, @@ -146,7 +146,7 @@ class SystemScriptHandler extends SystemHandler { memory_kb: 0, }); }); - }, 500); + }, 300); this.response.body = { rid }; this.response.redirect = this.url('record_detail', { rid }); } @@ -192,4 +192,4 @@ async function apply() { Connection('manage_check', '/manage/check-conn', SystemCheckConnHandler); } -global.Hydro.handler.manage = module.exports = apply; +global.Hydro.handler.manage = apply; diff --git a/hydro/handler/user.ts b/hydro/handler/user.ts index c61c30004..5f792fc56 100644 --- a/hydro/handler/user.ts +++ b/hydro/handler/user.ts @@ -201,15 +201,15 @@ class UserSearchHandler extends Handler { @param('q', Types.String) @param('exectMatch', Types.Boolean, true) async get(domainId: string, q: string, exactMatch = false) { - let udocs; - if (exactMatch) udocs = []; - else udocs = await user.getPrefixList(q, 20); - const udoc = await user.getById(domainId, parseInt(q, 10)); + let udoc = await user.getById(domainId, parseInt(q, 10)); + const udocs = udoc ? [udoc] : []; + udoc = await user.getByUname(domainId, q); if (udoc) udocs.push(udoc); + udoc = await user.getByEmail(domainId, q); + if (udoc) udocs.push(udoc); + if (!exactMatch) udocs.push(...await user.getPrefixList(q, 20)); for (const i in udocs) { - if (udocs[i].gravatar) { - udocs[i].gravatar = misc.gravatar(udocs[i].gravatar); - } + udocs[i].gravatar = misc.gravatar(udocs[i].gravatar || ''); } this.response.body = udocs; } diff --git a/hydro/lib/rating.ts b/hydro/lib/rating.ts index ea476a9e2..c445dfcfc 100644 --- a/hydro/lib/rating.ts +++ b/hydro/lib/rating.ts @@ -90,7 +90,18 @@ class RatingCalculator { } } -function calculate(users: any[]) { +interface RatingInputUser { + old: number, + uid: number, + rank: number, +} + +interface RatingOutputUser { + new: number, + uid: number, +} + +function calculate(users: RatingInputUser[]): RatingOutputUser[] { const calculator = new RatingCalculator(users); return calculator.calculate(); } diff --git a/hydro/script/rating.ts b/hydro/script/rating.ts index 1278d5360..97bb1d957 100644 --- a/hydro/script/rating.ts +++ b/hydro/script/rating.ts @@ -23,36 +23,23 @@ function calc(udict: ND, rankedDocs: [number, number][]) { } } -async function runProblem(pdoc: Pdoc, udict: ND, report: Function): Promise -async function runProblem( - domainId: string, pid: number, udict: ND, report: Function -): Promise +async function runProblem(pdoc: Pdoc, udict: ND): Promise +async function runProblem(domainId: string, pid: number, udict: ND): Promise async function runProblem(...arg: Array) { - const start = new Date().getTime(); const pdoc: Pdoc = (typeof arg[0] === 'string') ? await contest.get(arg[0], arg[1], -1) : arg[0]; const udict: ND = (typeof arg[0] === 'string') ? arg[2] : arg[1]; - const report = (typeof arg[0] === 'string') ? arg[3] : arg[2]; // TODO maybe some other rules? // TODO pagination const psdocs = await problem.getMultiStatus( pdoc.domainId, { docId: pdoc.docId, status: STATUS.STATUS_ACCEPTED }, ).sort('rid', 1).toArray(); const ranked = []; - for (const index of psdocs) { + for (const index in psdocs) { ranked.push([index + 1, psdocs[index].uid]); } calc(udict, ranked); - await report({ - case: { - status: STATUS.STATUS_ACCEPTED, - judgeText: `Problem ${pdoc.title} finished`, - time_ms: new Date().getTime() - start, - memory_kb: 0, - score: 0, - }, - }); } async function runContest(tdoc: Tdoc, udict: ND, report: Function): Promise @@ -93,7 +80,7 @@ async function runInDomain(domainId: string, isSub: boolean, report: Function) { await report({ message: `Found ${problems.length} problems in ${domainId}` }); for (const i in problems) { const pdoc = problems[i]; - runProblem(pdoc, udict, report); + runProblem(pdoc, udict); if (!isSub) { await report({ progress: Math.floor(((parseInt(i, 10) + 1) / problems.length) * 100), @@ -147,4 +134,4 @@ export const validate = { ], }; -global.Hydro.script.recalcRating = { run, description, validate }; +global.Hydro.script.rating = { run, description, validate }; diff --git a/hydro/service/server.ts b/hydro/service/server.ts index dac953cd9..e5360cf5e 100644 --- a/hydro/service/server.ts +++ b/hydro/service/server.ts @@ -20,7 +20,7 @@ import { UserNotFoundError, BlacklistedError, PermissionError, UserFacingError, ValidationError, PrivilegeError, CsrfTokenError, InvalidOperationError, MethodNotAllowedError, - NotFoundError, + NotFoundError, HydroError, } from '../error'; import { render } from '../lib/template'; import hash from '../lib/hash.hydro'; @@ -169,9 +169,15 @@ export function requireCsrfToken(target: any, funcName: string, obj: any) { export async function prepare() { server = http.createServer(app.callback()); app.keys = await system.get('session.keys'); - app.use(cache(path.join(os.tmpdir(), 'hydro', 'public'), { - maxAge: 365 * 24 * 60 * 60, - })); + if (process.env.debug) { + app.use(cache(path.join(process.cwd(), 'public'), { + maxAge: 0, + })); + } else { + app.use(cache(path.join(os.tmpdir(), 'hydro', 'public'), { + maxAge: 365 * 24 * 60 * 60, + })); + } app.use(Body({ multipart: true, formidable: { @@ -553,8 +559,8 @@ async function handle(ctx, HandlerClass, checker) { } const Checker = (permPrivChecker) => { - let perm; - let priv; + let perm: bigint; + let priv: number; let checker = () => { }; for (const item of permPrivChecker) { if (typeof item === 'object') { @@ -694,8 +700,14 @@ export class ConnectionHandler { this.conn.close(code.toString(), reason); } - onerror(err: Error) { + onerror(err: HydroError) { console.error(err); + this.send({ + error: { + name: err.name, + params: err.params || [], + }, + }); this.close(1001, err.toString()); } diff --git a/ui/build/config/gulp.js b/ui/build/config/gulp.js index df1c34021..1cbd8e5d4 100644 --- a/ui/build/config/gulp.js +++ b/ui/build/config/gulp.js @@ -39,7 +39,6 @@ export default function ({ watch, production, errorHandler }) { .pipe(gulp.dest('misc/.iconfont')) .pipe(offsetMtimeAtFirstBuild()); - tasks.iconfont = () => gulp .src('misc/icons/*.svg') .pipe(plumber({ errorHandler })) diff --git a/ui/build/config/webpack.js b/ui/build/config/webpack.js index b900c7f25..7fb4ff11f 100644 --- a/ui/build/config/webpack.js +++ b/ui/build/config/webpack.js @@ -1,6 +1,6 @@ /* eslint-disable import/no-extraneous-dependencies */ import webpack from 'webpack'; -import fs from 'fs-extra'; +import fs from 'fs'; import MonacoWebpackPlugin from 'monaco-editor-webpack-plugin'; import FriendlyErrorsPlugin from 'friendly-errors-webpack-plugin'; diff --git a/ui/build/main.js b/ui/build/main.js index 5b6996264..5a912ed03 100644 --- a/ui/build/main.js +++ b/ui/build/main.js @@ -7,7 +7,7 @@ import runWebpack from './runWebpack'; async function main() { const dir = process.cwd(); process.chdir(root()); - await runGulp(argv); + await runGulp({}); await runWebpack(argv); process.chdir(dir); } diff --git a/ui/build/runGulp.js b/ui/build/runGulp.js index cba99692d..c12d8d9f7 100644 --- a/ui/build/runGulp.js +++ b/ui/build/runGulp.js @@ -4,12 +4,12 @@ import log from 'fancy-log'; import chalk from 'chalk'; import gulpConfig from './config/gulp'; -export default async function ({ watch, production }) { +export default async function () { function handleError(err) { - log(chalk.red('Error: %s'), chalk.reset(err.toString())); - if (err && !watch) { process.exit(1); } + log(chalk.red('Error: %s'), chalk.reset(err.toString() + err.stack)); + if (err) process.exit(1); } - const gulpTasks = gulpConfig({ watch, production, errorHandler: handleError }); + const gulpTasks = gulpConfig({ production: true, errorHandler: handleError }); return new Promise((resolve) => { const taskList = {}; @@ -22,9 +22,6 @@ export default async function ({ watch, production }) { taskList[uid] = false; if (Object.values(taskList).filter((b) => b).length === 0) { - if (watch) { - gulpTasks.watch(); - } resolve(); } }); diff --git a/ui/components/autocomplete/DomainSelectAutoComplete.js b/ui/components/autocomplete/DomainSelectAutoComplete.js index 3894dd924..170b42832 100644 --- a/ui/components/autocomplete/DomainSelectAutoComplete.js +++ b/ui/components/autocomplete/DomainSelectAutoComplete.js @@ -16,7 +16,7 @@ function renderItem(domain) { return tpl`
- +
${domain.name}
diff --git a/ui/components/autocomplete/UserSelectAutoComplete.js b/ui/components/autocomplete/UserSelectAutoComplete.js index 080e1a731..5c0d118c4 100644 --- a/ui/components/autocomplete/UserSelectAutoComplete.js +++ b/ui/components/autocomplete/UserSelectAutoComplete.js @@ -16,7 +16,7 @@ function renderItem(user) { return tpl`
- +
${user.uname}
diff --git a/ui/components/notification/index.js b/ui/components/notification/index.js index de1bd6351..385cf7536 100644 --- a/ui/components/notification/index.js +++ b/ui/components/notification/index.js @@ -22,7 +22,7 @@ export default class Notification { .appendTo('body'); this.$n.width(); // force reflow this.$n.removeClass('hide'); - this.autoHideTimer = setTimeout(this.hide.bind(this), duration); + if (duration) this.autoHideTimer = setTimeout(this.hide.bind(this), duration); } handleClick() { diff --git a/ui/components/scratchpad/index.js b/ui/components/scratchpad/index.js index 40e40df97..7f370ac4f 100644 --- a/ui/components/scratchpad/index.js +++ b/ui/components/scratchpad/index.js @@ -64,6 +64,7 @@ export default class ScratchpadContainer extends React.PureComponent { handleChangeSize(uiElement, size) { this.props.changeUiSize(uiElement, size); $('#scratchpad').trigger('vjScratchpadRelayout'); + this.props.ui.pretest.visible = !!this.props.ui.pretest.visible; this.forceUpdate(); } diff --git a/ui/components/socket/index.js b/ui/components/socket/index.js index 63703f698..174b561c6 100644 --- a/ui/components/socket/index.js +++ b/ui/components/socket/index.js @@ -1,19 +1,23 @@ import SockJS from 'sockjs-client'; -import Notification from 'vj/components/notification'; +import Notification from 'vj/components/notification/index'; import i18n from 'vj/utils/i18n'; export default class Sock { constructor(url) { this.url = url; - this.reconnect = false; + this.isreconnect = false; this.init(); + this.notification = null; } init() { this.sock = new SockJS(this.url); this.sock.onopen = () => { - if (this.reconnect) Notification.info(i18n('Reconnected to the server.')); - if (this.onopen) this.onopen(this.reconnect); + if (this.isreconnect) { + this.notification.hide(); + Notification.info(i18n('Reconnected to the server.')); + } + if (this.onopen) this.onopen(this.isreconnect); }; this.sock.onclose = (code, reason) => { console.warn('Connection closed, ', code, reason); @@ -21,12 +25,29 @@ export default class Sock { if (this.onclose) this.onclose(code, reason); return; } - Notification.warn(i18n('Disconnected from the server. Reconnecting...')); - this.reconnect = setTimeout(() => { this.init(); }, 3000); + if (!this.closed) { + if (!this.notification) { + this.notification = new Notification({ + message: i18n('Disconnected from the server. Reconnecting...'), + type: 'warn', + duration: 0, + action: this.init.bind(this), + }); + console.log(this.notification); + } + this.isreconnect = setTimeout(() => { this.init(); }, 3000); + } else { + if (this.notification) this.notification.hide(); + Notification.warn(i18n('Disconnected from the server.')); + } if (this.onclose) this.onclose(code, reason); }; this.sock.onmessage = (message) => { if (process.env.NODE_ENV !== 'production') console.log('Sock.onmessage: ', message); + const msg = JSON.parse(message); + if (msg.error === 'PermissionError' || msg.error === 'PrivilegeError') { + this.closed = true; + } if (this.onmessage) this.onmessage(message); }; }