From 16db95ee87e1e762febbd8db7f98c85b1babdb8a Mon Sep 17 00:00:00 2001 From: undefined Date: Sun, 9 Apr 2023 23:55:29 +0800 Subject: [PATCH] core&ui: add progress callback (beta) --- packages/hydrooj/src/handler/problem.ts | 3 +++ packages/hydrooj/src/model/message.ts | 9 +++++++++ packages/hydrooj/src/model/problem.ts | 10 +++++++--- packages/hydrooj/src/service/server.ts | 5 +++++ .../ui-default/components/message/index.page.ts | 14 ++++++++++++-- packages/ui-default/components/message/worker.ts | 2 ++ packages/ui-default/constant/message.js | 2 ++ 7 files changed, 40 insertions(+), 5 deletions(-) diff --git a/packages/hydrooj/src/handler/problem.ts b/packages/hydrooj/src/handler/problem.ts index a63aa75ad..b47f00c83 100644 --- a/packages/hydrooj/src/handler/problem.ts +++ b/packages/hydrooj/src/handler/problem.ts @@ -251,6 +251,7 @@ export class ProblemMainHandler extends Handler { @param('pids', Types.NumericArray) async postDelete(domainId: string, pids: number[]) { + let i = 0; for (const pid of pids) { // eslint-disable-next-line no-await-in-loop const pdoc = await problem.get(domainId, pid); @@ -258,6 +259,8 @@ export class ProblemMainHandler extends Handler { if (!this.user.own(pdoc, PERM.PERM_EDIT_PROBLEM_SELF)) this.checkPerm(PERM.PERM_EDIT_PROBLEM); // eslint-disable-next-line no-await-in-loop await problem.del(domainId, pid); + i++; + this.progress(`Deleting: (${i}/${pids.length})`); } this.back(); } diff --git a/packages/hydrooj/src/model/message.ts b/packages/hydrooj/src/model/message.ts index bc20d3a38..1bd76a0f9 100644 --- a/packages/hydrooj/src/model/message.ts +++ b/packages/hydrooj/src/model/message.ts @@ -11,6 +11,7 @@ class MessageModel { static FLAG_UNREAD = 1; static FLAG_ALERT = 2; static FLAG_RICHTEXT = 4; + static FLAG_INFO = 8; static coll = db.collection('message'); @@ -29,6 +30,14 @@ class MessageModel { return mdoc; } + static async sendInfo(to: number, content: string) { + const _id = new ObjectId(); + const mdoc: MessageDoc = { + _id, from: 1, to, content, flag: MessageModel.FLAG_INFO, + }; + bus.broadcast('user/message', to, mdoc); + } + static async get(_id: ObjectId) { return await MessageModel.coll.findOne({ _id }); } diff --git a/packages/hydrooj/src/model/problem.ts b/packages/hydrooj/src/model/problem.ts index c8a3dae54..e6e5a9460 100644 --- a/packages/hydrooj/src/model/problem.ts +++ b/packages/hydrooj/src/model/problem.ts @@ -8,7 +8,7 @@ import yaml from 'js-yaml'; import { escapeRegExp, pick } from 'lodash'; import { Filter, ObjectId } from 'mongodb'; import type { Readable } from 'stream'; -import { size, streamToBuffer } from '@hydrooj/utils/lib/utils'; +import { Logger, size, streamToBuffer } from '@hydrooj/utils/lib/utils'; import { FileUploadError, ProblemNotFoundError, ValidationError } from '../error'; import type { Document, ProblemDict, ProblemStatusDoc, User, @@ -28,6 +28,7 @@ import user from './user'; export interface ProblemDoc extends Document { } export type Field = keyof ProblemDoc; +const logger = new Logger('problem'); function sortable(source: string) { return source.replace(/(\d+)/g, (str) => (str.length >= 6 ? str : ('0'.repeat(6 - str.length) + str))); } @@ -408,6 +409,7 @@ export class ProblemModel { static async import(domainId: string, filepath: string, operator: number) { let tmpdir = ''; + let del = false; if (filepath.endsWith('.zip')) { tmpdir = path.join(os.tmpdir(), 'hydro', `${Math.random()}.import`); let zip: AdmZip; @@ -416,6 +418,7 @@ export class ProblemModel { } catch (e) { throw new ValidationError('zip', null, e.message); } + del = true; await new Promise((resolve, reject) => { zip.extractAllToAsync(tmpdir, true, (err) => { if (err) reject(err); @@ -444,7 +447,7 @@ export class ProblemModel { } const overrideContent = findOverrideContent(path.join(tmpdir, i)); const docId = await ProblemModel.add( - domainId, pid, pdoc.title, overrideContent || pdoc.content, + domainId, pid, pdoc.title.trim(), overrideContent || pdoc.content || 'No content', operator || pdoc.owner, pdoc.tag || [], pdoc.hidden, ); if (files.includes('testdata')) { @@ -466,9 +469,10 @@ export class ProblemModel { } } } + if (process.env.HYDRO_CLI) logger.info(`Imported problem ${pdoc.pid} (${pdoc.title})`); } } finally { - await fs.remove(tmpdir); + if (del) await fs.remove(tmpdir); } } diff --git a/packages/hydrooj/src/service/server.ts b/packages/hydrooj/src/service/server.ts index fc2af3671..242948dd0 100644 --- a/packages/hydrooj/src/service/server.ts +++ b/packages/hydrooj/src/service/server.ts @@ -180,6 +180,11 @@ export class Handler extends HandlerCommon { if (name) this.response.disposition = `attachment; filename="${encodeRFC5987ValueChars(name)}"`; } + // This is beta API, may be changed in the future. + progress(message: string) { + Hydro.model.message.sendInfo(this.user._id, message); + } + async init() { if (this.request.method === 'post' && this.request.headers.referer && !this.context.cors && !this.allowCors) { const host = new URL(this.request.headers.referer).host; diff --git a/packages/ui-default/components/message/index.page.ts b/packages/ui-default/components/message/index.page.ts index b73698002..d3272b88e 100644 --- a/packages/ui-default/components/message/index.page.ts +++ b/packages/ui-default/components/message/index.page.ts @@ -2,10 +2,11 @@ import { nanoid } from 'nanoid'; import ReconnectingWebsocket from 'reconnecting-websocket'; import { InfoDialog } from 'vj/components/dialog'; import VjNotification from 'vj/components/notification/index'; -import { FLAG_ALERT } from 'vj/constant/message'; +import { FLAG_ALERT, FLAG_INFO, FLAG_RICHTEXT } from 'vj/constant/message'; import { AutoloadPage } from 'vj/misc/Page'; import { i18n, tpl } from 'vj/utils'; +let previous: VjNotification; const onmessage = (msg) => { console.log('Received message', msg); if (msg.mdoc.flag & FLAG_ALERT) { @@ -20,10 +21,19 @@ const onmessage = (msg) => { }).open(); return true; } + if (msg.mdoc.flag & FLAG_INFO) { + if (previous) previous.hide(); + previous = new VjNotification({ + message: i18n(msg.mdoc.content), + duration: 3000, + }); + previous.show(); + return true; + } if (document.hidden) return false; // Is message new VjNotification({ - ...(msg.udoc._id === 1 && msg.mdoc.flag & 4) + ...(msg.udoc._id === 1 && msg.mdoc.flag & FLAG_RICHTEXT) ? { message: i18n('You received a system message, click here to view.') } : { title: msg.udoc.uname, diff --git a/packages/ui-default/components/message/worker.ts b/packages/ui-default/components/message/worker.ts index 6fbaf5c60..413cb1506 100644 --- a/packages/ui-default/components/message/worker.ts +++ b/packages/ui-default/components/message/worker.ts @@ -3,6 +3,7 @@ /// /// import ReconnectingWebsocket from 'reconnecting-websocket'; +import { FLAG_INFO } from 'vj/constant/message'; console.log('SharedWorker init'); @@ -55,6 +56,7 @@ function initConn(path: string, port: MessagePort, cookie: any) { setTimeout(() => { delete ack[payload.mdoc.id]; if (acked) return; + if (payload.mdoc.flag & FLAG_INFO) return; if (Notification?.permission !== 'granted') { console.log('Notification permission denied'); return; diff --git a/packages/ui-default/constant/message.js b/packages/ui-default/constant/message.js index 15ecb3b06..073420a21 100644 --- a/packages/ui-default/constant/message.js +++ b/packages/ui-default/constant/message.js @@ -1,2 +1,4 @@ export const FLAG_UNREAD = 1; export const FLAG_ALERT = 2; +export const FLAG_RICHTEXT = 4; +export const FLAG_INFO = 8;