mirror of
https://gitcode.com/gh_mirrors/hy/Hydro.git
synced 2026-10-20 01:38:19 +08:00
core&ui: add progress callback (beta)
This commit is contained in:
7 files changed
+40
-5
No files matched your search
@@ -251,6 +251,7 @@ export class ProblemMainHandler extends Handler {
|
|||||||
|
|
||||||
@param('pids', Types.NumericArray)
|
@param('pids', Types.NumericArray)
|
||||||
async postDelete(domainId: string, pids: number[]) {
|
async postDelete(domainId: string, pids: number[]) {
|
||||||
|
let i = 0;
|
||||||
for (const pid of pids) {
|
for (const pid of pids) {
|
||||||
// eslint-disable-next-line no-await-in-loop
|
// eslint-disable-next-line no-await-in-loop
|
||||||
const pdoc = await problem.get(domainId, pid);
|
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);
|
if (!this.user.own(pdoc, PERM.PERM_EDIT_PROBLEM_SELF)) this.checkPerm(PERM.PERM_EDIT_PROBLEM);
|
||||||
// eslint-disable-next-line no-await-in-loop
|
// eslint-disable-next-line no-await-in-loop
|
||||||
await problem.del(domainId, pid);
|
await problem.del(domainId, pid);
|
||||||
|
i++;
|
||||||
|
this.progress(`Deleting: (${i}/${pids.length})`);
|
||||||
}
|
}
|
||||||
this.back();
|
this.back();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ class MessageModel {
|
|||||||
static FLAG_UNREAD = 1;
|
static FLAG_UNREAD = 1;
|
||||||
static FLAG_ALERT = 2;
|
static FLAG_ALERT = 2;
|
||||||
static FLAG_RICHTEXT = 4;
|
static FLAG_RICHTEXT = 4;
|
||||||
|
static FLAG_INFO = 8;
|
||||||
|
|
||||||
static coll = db.collection('message');
|
static coll = db.collection('message');
|
||||||
|
|
||||||
@@ -29,6 +30,14 @@ class MessageModel {
|
|||||||
return mdoc;
|
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) {
|
static async get(_id: ObjectId) {
|
||||||
return await MessageModel.coll.findOne({ _id });
|
return await MessageModel.coll.findOne({ _id });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import yaml from 'js-yaml';
|
|||||||
import { escapeRegExp, pick } from 'lodash';
|
import { escapeRegExp, pick } from 'lodash';
|
||||||
import { Filter, ObjectId } from 'mongodb';
|
import { Filter, ObjectId } from 'mongodb';
|
||||||
import type { Readable } from 'stream';
|
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 { FileUploadError, ProblemNotFoundError, ValidationError } from '../error';
|
||||||
import type {
|
import type {
|
||||||
Document, ProblemDict, ProblemStatusDoc, User,
|
Document, ProblemDict, ProblemStatusDoc, User,
|
||||||
@@ -28,6 +28,7 @@ import user from './user';
|
|||||||
export interface ProblemDoc extends Document { }
|
export interface ProblemDoc extends Document { }
|
||||||
export type Field = keyof ProblemDoc;
|
export type Field = keyof ProblemDoc;
|
||||||
|
|
||||||
|
const logger = new Logger('problem');
|
||||||
function sortable(source: string) {
|
function sortable(source: string) {
|
||||||
return source.replace(/(\d+)/g, (str) => (str.length >= 6 ? str : ('0'.repeat(6 - str.length) + str)));
|
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) {
|
static async import(domainId: string, filepath: string, operator: number) {
|
||||||
let tmpdir = '';
|
let tmpdir = '';
|
||||||
|
let del = false;
|
||||||
if (filepath.endsWith('.zip')) {
|
if (filepath.endsWith('.zip')) {
|
||||||
tmpdir = path.join(os.tmpdir(), 'hydro', `${Math.random()}.import`);
|
tmpdir = path.join(os.tmpdir(), 'hydro', `${Math.random()}.import`);
|
||||||
let zip: AdmZip;
|
let zip: AdmZip;
|
||||||
@@ -416,6 +418,7 @@ export class ProblemModel {
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw new ValidationError('zip', null, e.message);
|
throw new ValidationError('zip', null, e.message);
|
||||||
}
|
}
|
||||||
|
del = true;
|
||||||
await new Promise((resolve, reject) => {
|
await new Promise((resolve, reject) => {
|
||||||
zip.extractAllToAsync(tmpdir, true, (err) => {
|
zip.extractAllToAsync(tmpdir, true, (err) => {
|
||||||
if (err) reject(err);
|
if (err) reject(err);
|
||||||
@@ -444,7 +447,7 @@ export class ProblemModel {
|
|||||||
}
|
}
|
||||||
const overrideContent = findOverrideContent(path.join(tmpdir, i));
|
const overrideContent = findOverrideContent(path.join(tmpdir, i));
|
||||||
const docId = await ProblemModel.add(
|
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,
|
operator || pdoc.owner, pdoc.tag || [], pdoc.hidden,
|
||||||
);
|
);
|
||||||
if (files.includes('testdata')) {
|
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 {
|
} finally {
|
||||||
await fs.remove(tmpdir);
|
if (del) await fs.remove(tmpdir);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -180,6 +180,11 @@ export class Handler extends HandlerCommon {
|
|||||||
if (name) this.response.disposition = `attachment; filename="${encodeRFC5987ValueChars(name)}"`;
|
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() {
|
async init() {
|
||||||
if (this.request.method === 'post' && this.request.headers.referer && !this.context.cors && !this.allowCors) {
|
if (this.request.method === 'post' && this.request.headers.referer && !this.context.cors && !this.allowCors) {
|
||||||
const host = new URL(this.request.headers.referer).host;
|
const host = new URL(this.request.headers.referer).host;
|
||||||
|
|||||||
@@ -2,10 +2,11 @@ import { nanoid } from 'nanoid';
|
|||||||
import ReconnectingWebsocket from 'reconnecting-websocket';
|
import ReconnectingWebsocket from 'reconnecting-websocket';
|
||||||
import { InfoDialog } from 'vj/components/dialog';
|
import { InfoDialog } from 'vj/components/dialog';
|
||||||
import VjNotification from 'vj/components/notification/index';
|
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 { AutoloadPage } from 'vj/misc/Page';
|
||||||
import { i18n, tpl } from 'vj/utils';
|
import { i18n, tpl } from 'vj/utils';
|
||||||
|
|
||||||
|
let previous: VjNotification;
|
||||||
const onmessage = (msg) => {
|
const onmessage = (msg) => {
|
||||||
console.log('Received message', msg);
|
console.log('Received message', msg);
|
||||||
if (msg.mdoc.flag & FLAG_ALERT) {
|
if (msg.mdoc.flag & FLAG_ALERT) {
|
||||||
@@ -20,10 +21,19 @@ const onmessage = (msg) => {
|
|||||||
}).open();
|
}).open();
|
||||||
return true;
|
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;
|
if (document.hidden) return false;
|
||||||
// Is message
|
// Is message
|
||||||
new VjNotification({
|
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.') }
|
? { message: i18n('You received a system message, click here to view.') }
|
||||||
: {
|
: {
|
||||||
title: msg.udoc.uname,
|
title: msg.udoc.uname,
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
/// <reference lib="ES2015" />
|
/// <reference lib="ES2015" />
|
||||||
/// <reference types="@types/sharedworker" />
|
/// <reference types="@types/sharedworker" />
|
||||||
import ReconnectingWebsocket from 'reconnecting-websocket';
|
import ReconnectingWebsocket from 'reconnecting-websocket';
|
||||||
|
import { FLAG_INFO } from 'vj/constant/message';
|
||||||
|
|
||||||
console.log('SharedWorker init');
|
console.log('SharedWorker init');
|
||||||
|
|
||||||
@@ -55,6 +56,7 @@ function initConn(path: string, port: MessagePort, cookie: any) {
|
|||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
delete ack[payload.mdoc.id];
|
delete ack[payload.mdoc.id];
|
||||||
if (acked) return;
|
if (acked) return;
|
||||||
|
if (payload.mdoc.flag & FLAG_INFO) return;
|
||||||
if (Notification?.permission !== 'granted') {
|
if (Notification?.permission !== 'granted') {
|
||||||
console.log('Notification permission denied');
|
console.log('Notification permission denied');
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -1,2 +1,4 @@
|
|||||||
export const FLAG_UNREAD = 1;
|
export const FLAG_UNREAD = 1;
|
||||||
export const FLAG_ALERT = 2;
|
export const FLAG_ALERT = 2;
|
||||||
|
export const FLAG_RICHTEXT = 4;
|
||||||
|
export const FLAG_INFO = 8;
|
||||||
Reference in new issue
Block a user