mirror of
https://gitcode.com/gh_mirrors/hy/Hydro.git
synced 2026-10-19 17:38:19 +08:00
ui: problem_files: pjax support
This commit is contained in:
1 parent
c061c1ec93
commit
3e1d4635c4
16 files changed
+234
-184
No files matched your search
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "hydrooj",
|
"name": "hydrooj",
|
||||||
"version": "2.26.0",
|
"version": "2.26.1",
|
||||||
"bin": "bin/hydrooj.js",
|
"bin": "bin/hydrooj.js",
|
||||||
"main": "dist/loader.js",
|
"main": "dist/loader.js",
|
||||||
"typings": "dist/loader.d.ts",
|
"typings": "dist/loader.d.ts",
|
||||||
|
|||||||
@@ -92,6 +92,7 @@ export const ValidationError = Err('ValidationError', ForbiddenError, function (
|
|||||||
return 'Field {0} or {1} validation failed.';
|
return 'Field {0} or {1} validation failed.';
|
||||||
});
|
});
|
||||||
export const ContestNotAttendedError = Err('ContestNotAttendedError', ForbiddenError, "You haven't attended this contest yet.");
|
export const ContestNotAttendedError = Err('ContestNotAttendedError', ForbiddenError, "You haven't attended this contest yet.");
|
||||||
|
export const RequireProError = Err('RequireProError', ForbiddenError, 'RequireProError');
|
||||||
export const ContestAlreadyAttendedError = Err('ContestAlreadyAttendedError', ForbiddenError, "You've already attended this contest.");
|
export const ContestAlreadyAttendedError = Err('ContestAlreadyAttendedError', ForbiddenError, "You've already attended this contest.");
|
||||||
export const ContestNotLiveError = Err('ContestNotLiveError', ForbiddenError, 'This contest is not live.');
|
export const ContestNotLiveError = Err('ContestNotLiveError', ForbiddenError, 'This contest is not live.');
|
||||||
export const ContestScoreboardHiddenError = Err('ContestScoreboardHiddenError', ForbiddenError, 'Contest scoreboard is not visible.');
|
export const ContestScoreboardHiddenError = Err('ContestScoreboardHiddenError', ForbiddenError, 'Contest scoreboard is not visible.');
|
||||||
@@ -142,6 +143,7 @@ global.Hydro.error = {
|
|||||||
OpcountExceededError,
|
OpcountExceededError,
|
||||||
PermissionError,
|
PermissionError,
|
||||||
PrivilegeError,
|
PrivilegeError,
|
||||||
|
RequireProError,
|
||||||
NoProblemError,
|
NoProblemError,
|
||||||
ValidationError,
|
ValidationError,
|
||||||
ProblemNotFoundError,
|
ProblemNotFoundError,
|
||||||
|
|||||||
@@ -379,13 +379,24 @@ export class ProblemEditHandler extends ProblemManageHandler {
|
|||||||
export class ProblemFilesHandler extends ProblemDetailHandler {
|
export class ProblemFilesHandler extends ProblemDetailHandler {
|
||||||
@param('testdata', Types.Boolean)
|
@param('testdata', Types.Boolean)
|
||||||
@param('additional_file', Types.Boolean)
|
@param('additional_file', Types.Boolean)
|
||||||
async get(domainId: string, getTestdata = true, getAdditionalFile = true) {
|
@param('pjax', Types.Boolean)
|
||||||
|
async get(domainId: string, getTestdata = true, getAdditionalFile = true, pjax = false) {
|
||||||
const canReadData = this.user.own(this.pdoc)
|
const canReadData = this.user.own(this.pdoc)
|
||||||
|| this.user.hasPriv(PRIV.PRIV_READ_PROBLEM_DATA)
|
|| this.user.hasPriv(PRIV.PRIV_READ_PROBLEM_DATA)
|
||||||
|| this.user.hasPerm(PERM.PERM_READ_PROBLEM_DATA);
|
|| this.user.hasPerm(PERM.PERM_READ_PROBLEM_DATA);
|
||||||
this.response.body.testdata = (getTestdata && canReadData) ? sortFiles(this.pdoc.data || []) : [];
|
this.response.body.testdata = (getTestdata && canReadData) ? sortFiles(this.pdoc.data || []) : [];
|
||||||
this.response.body.additional_file = getAdditionalFile ? sortFiles(this.pdoc.additional_file || []) : [];
|
this.response.body.additional_file = getAdditionalFile ? sortFiles(this.pdoc.additional_file || []) : [];
|
||||||
this.response.template = 'problem_files.html';
|
if (pjax) {
|
||||||
|
const { testdata, additional_file } = this.response.body;
|
||||||
|
this.response.body = {
|
||||||
|
fragments: (await Promise.all([
|
||||||
|
this.renderHTML('partials/problem_files-testdata.html', { testdata, pdoc: this.pdoc }),
|
||||||
|
this.renderHTML('partials/problem_files-additional_file.html', { additional_file, pdoc: this.pdoc }),
|
||||||
|
this.renderHTML('partials/problem-sidebar-information.html', { pdoc: this.pdoc }),
|
||||||
|
])).map((i) => ({ html: i })),
|
||||||
|
};
|
||||||
|
this.response.template = '';
|
||||||
|
} else this.response.template = 'problem_files.html';
|
||||||
}
|
}
|
||||||
|
|
||||||
@post('files', Types.Set)
|
@post('files', Types.Set)
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ for (const lang in global.Hydro.locales) {
|
|||||||
export const FLAG_HIDDEN = 1;
|
export const FLAG_HIDDEN = 1;
|
||||||
export const FLAG_DISABLED = 2;
|
export const FLAG_DISABLED = 2;
|
||||||
export const FLAG_SECRET = 4;
|
export const FLAG_SECRET = 4;
|
||||||
|
export const FLAG_PRO = 8;
|
||||||
|
|
||||||
export const PREFERENCE_SETTINGS: _Setting[] = [];
|
export const PREFERENCE_SETTINGS: _Setting[] = [];
|
||||||
export const ACCOUNT_SETTINGS: _Setting[] = [];
|
export const ACCOUNT_SETTINGS: _Setting[] = [];
|
||||||
@@ -167,7 +168,8 @@ SystemSetting(
|
|||||||
Setting('setting_server', 'server.center', 'https://hydro.undefined.moe:8443/center', 'text', 'server.center', '', FLAG_HIDDEN),
|
Setting('setting_server', 'server.center', 'https://hydro.undefined.moe:8443/center', 'text', 'server.center', '', FLAG_HIDDEN),
|
||||||
Setting('setting_server', 'server.proxy', false, 'boolean', 'server.proxy', 'Use hydro center proxy to send problems'),
|
Setting('setting_server', 'server.proxy', false, 'boolean', 'server.proxy', 'Use hydro center proxy to send problems'),
|
||||||
Setting('setting_server', 'server.name', 'Hydro', 'text', 'server.name', 'Server Name'),
|
Setting('setting_server', 'server.name', 'Hydro', 'text', 'server.name', 'Server Name'),
|
||||||
Setting('setting_server', 'server.worker', 1, 'number', 'server.worker', 'Server Workers Number'),
|
Setting('setting_server', 'server.displayName', 'Hydro', 'text', 'server.name', 'Server Name (Global Display)', FLAG_PRO),
|
||||||
|
Setting('setting_server', 'server.worker', 1, 'number', 'server.worker', 'Server Workers Number', FLAG_PRO),
|
||||||
Setting('setting_server', 'server.hostname', 'oj.undefined.moe', 'text', 'server.hostname', 'Server Hostname'),
|
Setting('setting_server', 'server.hostname', 'oj.undefined.moe', 'text', 'server.hostname', 'Server Hostname'),
|
||||||
Setting('setting_server', 'server.host', 'oj.undefined.moe', 'text', 'server.host', 'Server Host'),
|
Setting('setting_server', 'server.host', 'oj.undefined.moe', 'text', 'server.host', 'Server Host'),
|
||||||
Setting('setting_server', 'server.url', '/', 'text', 'server.url', 'Server BaseURL'),
|
Setting('setting_server', 'server.url', '/', 'text', 'server.url', 'Server BaseURL'),
|
||||||
@@ -176,9 +178,9 @@ SystemSetting(
|
|||||||
Setting('setting_server', 'server.xff', null, 'text', 'server.xff', 'IP Header'),
|
Setting('setting_server', 'server.xff', null, 'text', 'server.xff', 'IP Header'),
|
||||||
Setting('setting_server', 'server.xhost', null, 'text', 'server.xhost', 'Hostname Header'),
|
Setting('setting_server', 'server.xhost', null, 'text', 'server.xhost', 'Hostname Header'),
|
||||||
Setting('setting_server', 'server.language', 'zh_CN', langRange, 'server.language', 'Default display language'),
|
Setting('setting_server', 'server.language', 'zh_CN', langRange, 'server.language', 'Default display language'),
|
||||||
Setting('setting_server', 'server.login', true, 'boolean', 'server.login', 'Allow builtin-login'),
|
Setting('setting_server', 'server.login', true, 'boolean', 'server.login', 'Allow builtin-login', FLAG_PRO),
|
||||||
Setting('setting_limits', 'limit.problem_files_max', 100, 'number', 'limit.problem_files_max', 'Max files per problem'),
|
Setting('setting_limits', 'limit.problem_files_max', 100, 'number', 'limit.problem_files_max', 'Max files per problem'),
|
||||||
Setting('setting_basic', 'default.priv', builtin.PRIV.PRIV_DEFAULT, 'number', 'default.priv', 'Default Privilege'),
|
Setting('setting_basic', 'default.priv', builtin.PRIV.PRIV_DEFAULT, 'number', 'default.priv', 'Default Privilege', FLAG_PRO),
|
||||||
Setting('setting_basic', 'problem.categories', builtin.CATEGORIES, 'yaml', 'problem.categories', 'Problem Categories'),
|
Setting('setting_basic', 'problem.categories', builtin.CATEGORIES, 'yaml', 'problem.categories', 'Problem Categories'),
|
||||||
Setting('setting_basic', 'pagination.problem', 100, 'number', 'pagination.problem', 'Problems per page'),
|
Setting('setting_basic', 'pagination.problem', 100, 'number', 'pagination.problem', 'Problems per page'),
|
||||||
Setting('setting_basic', 'pagination.contest', 20, 'number', 'pagination.contest', 'Contests per page'),
|
Setting('setting_basic', 'pagination.contest', 20, 'number', 'pagination.contest', 'Contests per page'),
|
||||||
@@ -244,6 +246,7 @@ global.Hydro.model.setting = {
|
|||||||
FLAG_HIDDEN,
|
FLAG_HIDDEN,
|
||||||
FLAG_DISABLED,
|
FLAG_DISABLED,
|
||||||
FLAG_SECRET,
|
FLAG_SECRET,
|
||||||
|
FLAG_PRO,
|
||||||
PREFERENCE_SETTINGS,
|
PREFERENCE_SETTINGS,
|
||||||
ACCOUNT_SETTINGS,
|
ACCOUNT_SETTINGS,
|
||||||
SETTINGS,
|
SETTINGS,
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ export class StorageModel {
|
|||||||
|
|
||||||
static async list(target: string, recursive = true) {
|
static async list(target: string, recursive = true) {
|
||||||
if (target.includes('..') || target.includes('//')) throw new Error('Invalid path');
|
if (target.includes('..') || target.includes('//')) throw new Error('Invalid path');
|
||||||
if (target?.length && !target.endsWith('/')) target += '/';
|
if (target.length && !target.endsWith('/')) target += '/';
|
||||||
const results = recursive
|
const results = recursive
|
||||||
? await StorageModel.coll.find({ path: { $regex: new RegExp(`^${escapeRegExp(target)}`, 'i') } }).toArray()
|
? await StorageModel.coll.find({ path: { $regex: new RegExp(`^${escapeRegExp(target)}`, 'i') } }).toArray()
|
||||||
: await StorageModel.coll.find({ path: { $regex: new RegExp(`^${escapeRegExp(target)}[^/]+$`) } }).toArray();
|
: await StorageModel.coll.find({ path: { $regex: new RegExp(`^${escapeRegExp(target)}[^/]+$`) } }).toArray();
|
||||||
|
|||||||
@@ -1,6 +1,19 @@
|
|||||||
|
import { Position, Toaster, Intent } from '@blueprintjs/core';
|
||||||
import tpl from 'vj/utils/tpl';
|
import tpl from 'vj/utils/tpl';
|
||||||
import zIndexManager from 'vj/utils/zIndexManager';
|
import zIndexManager from 'vj/utils/zIndexManager';
|
||||||
|
|
||||||
|
const ToasterContainer = document.createElement('div');
|
||||||
|
ToasterContainer.style.position = 'fixed';
|
||||||
|
ToasterContainer.style.bottom = '0px';
|
||||||
|
ToasterContainer.style.zIndex = '9999';
|
||||||
|
document.body.append(ToasterContainer);
|
||||||
|
|
||||||
|
export const AppToaster = Toaster.create({
|
||||||
|
className: 'recipe-toaster',
|
||||||
|
position: Position.LEFT_BOTTOM,
|
||||||
|
usePortal: true,
|
||||||
|
}, ToasterContainer);
|
||||||
|
|
||||||
export default class Notification {
|
export default class Notification {
|
||||||
constructor({
|
constructor({
|
||||||
avatar, title, message, type = '', duration = 3000, action,
|
avatar, title, message, type = '', duration = 3000, action,
|
||||||
@@ -37,19 +50,19 @@ export default class Notification {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static success(message, duration) {
|
static success(message, duration) {
|
||||||
return new Notification({ type: 'success', message, duration }).show();
|
return AppToaster.show({ message, timeout: duration, intent: Intent.SUCCESS });
|
||||||
}
|
}
|
||||||
|
|
||||||
static info(message, duration) {
|
static info(message, duration) {
|
||||||
return new Notification({ type: 'info', message, duration }).show();
|
return AppToaster.show({ message, timeout: duration, intent: Intent.PRIMARY });
|
||||||
}
|
}
|
||||||
|
|
||||||
static warn(message, duration) {
|
static warn(message, duration) {
|
||||||
return new Notification({ type: 'warn', message, duration }).show();
|
return AppToaster.show({ message, timeout: duration, intent: Intent.WARNING });
|
||||||
}
|
}
|
||||||
|
|
||||||
static error(message, duration) {
|
static error(message, duration) {
|
||||||
return new Notification({ type: 'error', message, duration }).show();
|
return AppToaster.show({ message, timeout: duration, intent: Intent.DANGER });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
"allowSyntheticDefaultImports": true,
|
"allowSyntheticDefaultImports": true,
|
||||||
"target": "ES6",
|
"target": "ES6",
|
||||||
"baseUrl": ".",
|
"baseUrl": ".",
|
||||||
|
"moduleResolution": "node",
|
||||||
"paths": {
|
"paths": {
|
||||||
"vj/*": [
|
"vj/*": [
|
||||||
"./*"
|
"./*"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@hydrooj/ui-default",
|
"name": "@hydrooj/ui-default",
|
||||||
"version": "4.8.13",
|
"version": "4.8.14",
|
||||||
"author": "undefined <i@undefined.moe>",
|
"author": "undefined <i@undefined.moe>",
|
||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
"main": "hydro.js",
|
"main": "hydro.js",
|
||||||
@@ -22,6 +22,8 @@
|
|||||||
"@babel/preset-react": "^7.13.13",
|
"@babel/preset-react": "^7.13.13",
|
||||||
"@babel/register": "^7.13.14",
|
"@babel/register": "^7.13.14",
|
||||||
"@babel/runtime-corejs3": "^7.14.0",
|
"@babel/runtime-corejs3": "^7.14.0",
|
||||||
|
"@blueprintjs/core": "^3.44.2",
|
||||||
|
"@blueprintjs/icons": "^3.26.0",
|
||||||
"@hydrooj/utils": "^1.0.15",
|
"@hydrooj/utils": "^1.0.15",
|
||||||
"@undefined-moe/monaco-yaml": "^2.5.0",
|
"@undefined-moe/monaco-yaml": "^2.5.0",
|
||||||
"ansi_up": "^5.0.1",
|
"ansi_up": "^5.0.1",
|
||||||
|
|||||||
@@ -6,9 +6,9 @@ import { NamedPage } from 'vj/misc/Page';
|
|||||||
import Notification from 'vj/components/notification';
|
import Notification from 'vj/components/notification';
|
||||||
import { ConfirmDialog, ActionDialog } from 'vj/components/dialog/index';
|
import { ConfirmDialog, ActionDialog } from 'vj/components/dialog/index';
|
||||||
import request from 'vj/utils/request';
|
import request from 'vj/utils/request';
|
||||||
|
import pjax from 'vj/utils/pjax';
|
||||||
import pipeStream from 'vj/utils/pipeStream';
|
import pipeStream from 'vj/utils/pipeStream';
|
||||||
import tpl from 'vj/utils/tpl';
|
import tpl from 'vj/utils/tpl';
|
||||||
import delay from 'vj/utils/delay';
|
|
||||||
import i18n from 'vj/utils/i18n';
|
import i18n from 'vj/utils/i18n';
|
||||||
|
|
||||||
// Firefox have no WritableStream
|
// Firefox have no WritableStream
|
||||||
@@ -96,8 +96,7 @@ const page = new NamedPage('problem_files', () => {
|
|||||||
}
|
}
|
||||||
await Promise.all(tasks);
|
await Promise.all(tasks);
|
||||||
Notification.success('File uploaded successfully.');
|
Notification.success('File uploaded successfully.');
|
||||||
await delay(2000);
|
await pjax.request({ push: false });
|
||||||
window.location.reload();
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
Notification.error(`File upload fail: ${e.toString()}`);
|
Notification.error(`File upload fail: ${e.toString()}`);
|
||||||
@@ -127,8 +126,7 @@ const page = new NamedPage('problem_files', () => {
|
|||||||
type,
|
type,
|
||||||
});
|
});
|
||||||
Notification.success(i18n('Selected files have been deleted.'));
|
Notification.success(i18n('Selected files have been deleted.'));
|
||||||
await delay(2000);
|
await pjax.request({ push: false });
|
||||||
window.location.reload();
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
Notification.error(error.message);
|
Notification.error(error.message);
|
||||||
}
|
}
|
||||||
@@ -189,14 +187,22 @@ const page = new NamedPage('problem_files', () => {
|
|||||||
* @param {JQuery.ClickEvent<HTMLElement, undefined, HTMLElement, HTMLElement>} ev
|
* @param {JQuery.ClickEvent<HTMLElement, undefined, HTMLElement, HTMLElement>} ev
|
||||||
*/
|
*/
|
||||||
async function handleEdit(type, ev) {
|
async function handleEdit(type, ev) {
|
||||||
ev.preventDefault();
|
if (ev) ev.preventDefault();
|
||||||
const filename = ev.currentTarget.closest('[data-filename]').getAttribute('data-filename');
|
const filename = ev
|
||||||
const filesize = +ev.currentTarget.closest('[data-size]').getAttribute('data-size');
|
? ev.currentTarget.closest('[data-filename]').getAttribute('data-filename')
|
||||||
const link = $(ev.currentTarget).find('a').attr('href');
|
// eslint-disable-next-line no-alert
|
||||||
if (filesize > 8 * 1024 * 1024) Notification.error(i18n('file too large'));
|
: prompt('Filename');
|
||||||
Notification.info(i18n('Loading file...'));
|
const filesize = ev
|
||||||
const res = await request.get(link);
|
? +ev.currentTarget.closest('[data-size]').getAttribute('data-size')
|
||||||
const content = await request.get(res.url, undefined, { dataType: 'text' });
|
: 0;
|
||||||
|
let content = '';
|
||||||
|
if (ev) {
|
||||||
|
const link = $(ev.currentTarget).find('a').attr('href');
|
||||||
|
if (filesize > 8 * 1024 * 1024) Notification.error(i18n('file too large'));
|
||||||
|
Notification.info(i18n('Loading file...'));
|
||||||
|
const res = await request.get(link);
|
||||||
|
content = await request.get(res.url, undefined, { dataType: 'text' });
|
||||||
|
} else Notification.info(i18n('Loading editor...'));
|
||||||
const val = await startEdit(filename, content);
|
const val = await startEdit(filename, content);
|
||||||
console.log(val);
|
console.log(val);
|
||||||
if (typeof val !== 'string') return;
|
if (typeof val !== 'string') return;
|
||||||
@@ -208,8 +214,7 @@ const page = new NamedPage('problem_files', () => {
|
|||||||
data.append('operation', 'upload_file');
|
data.append('operation', 'upload_file');
|
||||||
await request.postFile('', data);
|
await request.postFile('', data);
|
||||||
Notification.success(i18n('File saved.'));
|
Notification.success(i18n('File saved.'));
|
||||||
await delay(2000);
|
await pjax.request({ push: false });
|
||||||
window.location.reload();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$('.problem-files-testdata .col--name').on('click', (ev) => handleEdit('testdata', ev));
|
$('.problem-files-testdata .col--name').on('click', (ev) => handleEdit('testdata', ev));
|
||||||
@@ -219,9 +224,11 @@ const page = new NamedPage('problem_files', () => {
|
|||||||
$('.problem-files-testdata').on('drop', (ev) => handleDrop('testdata', ev));
|
$('.problem-files-testdata').on('drop', (ev) => handleDrop('testdata', ev));
|
||||||
$('.problem-files-additional_file').on('drop', (ev) => handleDrop('additional_file', ev));
|
$('.problem-files-additional_file').on('drop', (ev) => handleDrop('additional_file', ev));
|
||||||
$('[name="upload_testdata"]').on('click', () => handleClickUpload('testdata'));
|
$('[name="upload_testdata"]').on('click', () => handleClickUpload('testdata'));
|
||||||
|
$('[name="create_testdata"]').on('click', () => handleEdit('testdata'));
|
||||||
$('[name="download_selected_testdata"]').on('click', () => handleClickDownloadSelected('testdata'));
|
$('[name="download_selected_testdata"]').on('click', () => handleClickDownloadSelected('testdata'));
|
||||||
$('[name="remove_selected_testdata"]').on('click', () => handleClickRemoveSelected('testdata'));
|
$('[name="remove_selected_testdata"]').on('click', () => handleClickRemoveSelected('testdata'));
|
||||||
$('[name="upload_file"]').on('click', () => handleClickUpload('additional_file'));
|
$('[name="upload_file"]').on('click', () => handleClickUpload('additional_file'));
|
||||||
|
$('[name="create_file"]').on('click', () => handleEdit('additional_file'));
|
||||||
$('[name="download_selected_file"]').on('click', () => handleClickDownloadSelected('additional_file'));
|
$('[name="download_selected_file"]').on('click', () => handleClickDownloadSelected('additional_file'));
|
||||||
$('[name="remove_selected_file"]').on('click', () => handleClickRemoveSelected('additional_file'));
|
$('[name="remove_selected_file"]').on('click', () => handleClickRemoveSelected('additional_file'));
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -0,0 +1,66 @@
|
|||||||
|
{% import "components/record.html" as record with context %}
|
||||||
|
{% import "components/problem.html" as problem with context %}
|
||||||
|
{% import "components/user.html" as user with context %}
|
||||||
|
<div class="section__body typo" data-fragment-id="problem-sidebar-information">
|
||||||
|
<dl class="large horizontal">
|
||||||
|
<dt>{{ _('ID') }}</dt>
|
||||||
|
<dd>{{ pdoc.docId }}</dd>
|
||||||
|
<dt>{{ _('Time') }}</dt>
|
||||||
|
<dd>{{ pdoc.config.timeMax if pdoc.config.timeMax == pdoc.config.timeMin else pdoc.config.timeMin+'~'+pdoc.config.timeMax }}ms</dd>
|
||||||
|
<dt>{{ _('Memory') }}</dt>
|
||||||
|
<dd>{{ pdoc.config.memoryMax if pdoc.config.memoryMax == pdoc.config.memoryMin else pdoc.config.memoryMin+'~'+pdoc.config.memoryMax }}MiB</dd>
|
||||||
|
{% if page_name != 'discussion_node' and page_name != 'discussion_detail' %}
|
||||||
|
{% if handler.user.hasPriv(PRIV.PRIV_USER_PROFILE) %}
|
||||||
|
<dt>{{ _('Submission') }}</dt>
|
||||||
|
<dd>
|
||||||
|
{% if pdoc.psdoc.status %}
|
||||||
|
<span class="icon record-status--icon {{ model.builtin.STATUS_CODES[pdoc['psdoc']['status']] }}"></span>
|
||||||
|
<a href="{{ url('record_detail', rid=pdoc.psdoc.rid) }}" class="record-status--text {{ model.builtin.STATUS_CODES[pdoc['psdoc']['status']] }}">
|
||||||
|
{{ model.builtin.STATUS_TEXTS[pdoc['psdoc']['status']] }}
|
||||||
|
</a>
|
||||||
|
{% else %}
|
||||||
|
{{ _('(None)') }}
|
||||||
|
{% endif %}
|
||||||
|
</dd>
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
<dt>{{ _('Difficulty') }}</dt>
|
||||||
|
<dd>{{ pdoc['difficulty'] or _('(None)') }}</dd>
|
||||||
|
<dt>{{ _('Category') }}</dt>
|
||||||
|
<dd>
|
||||||
|
{% if pdoc['category'] %}
|
||||||
|
<span class="hasjs--hide" name="problem-sidebar__categories">
|
||||||
|
{%- for category in pdoc['category'] -%}
|
||||||
|
<a href="{{ url('problem_category', category=category) }}">{{ category }}</a>
|
||||||
|
{% if loop.index < pdoc['category']|length %}
|
||||||
|
{{ _('problem-subcategory-delim') }}
|
||||||
|
{% endif %}
|
||||||
|
{%- endfor -%}
|
||||||
|
</span>
|
||||||
|
<span class="nojs--hide">
|
||||||
|
<a href="javascript:;" name="problem-sidebar__show-category">{{ _('page.problem_detail.sidebar.show_category') }}</a>
|
||||||
|
</span>
|
||||||
|
{% else %}
|
||||||
|
{{ _('(None)') }}
|
||||||
|
{% endif %}
|
||||||
|
</dd>
|
||||||
|
<dt>{{ _('Tags') }}</dt>
|
||||||
|
<dd>
|
||||||
|
{{ problem.render_problem_tags(pdoc, true) }}
|
||||||
|
</dd>
|
||||||
|
<dt>{{ _('# Submissions') }}</dt>
|
||||||
|
<dd><a href="{{ url('record_main', query={pid:pdoc.docId}) }}">{{ pdoc.nSubmit }}</a></dd>
|
||||||
|
{% if page_name != 'discussion_node' and page_name != 'discussion_detail' %}
|
||||||
|
{% if handler.user.hasPriv(PRIV.PRIV_USER_PROFILE) %}
|
||||||
|
<dt>{{ _('# My Submissions') }}</dt>
|
||||||
|
<dd><a href="{{ url('record_main', query={pid:pdoc.docId, uidOrName:handler.user._id}) }}">{{ pdoc['psdoc'].nSubmit|default(0) }}</a></dd>
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
<dt>{{ _('Accepted') }}</dt>
|
||||||
|
<dd>{{ pdoc.nAccept }}</dd>
|
||||||
|
<dt>{{ _('Accepted Ratio') }}</dt>
|
||||||
|
<dd>{% if pdoc.nSubmit > 0 %}{{ (100 * pdoc.nAccept / pdoc.nSubmit)|round|int }}%{% else %}{{ _('?') }}{% endif %}</dd>
|
||||||
|
<dt>{{ _('Uploaded By') }}</dt>
|
||||||
|
<dd>{{ user.render_inline(owner_udoc, badge=false) }}</dd>
|
||||||
|
</dl>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
{% import "components/nothing.html" as nothing with context %}
|
||||||
|
<div class="section__body no-padding problem-files-additional_file" data-fragment-id="problem-files-additional_file">
|
||||||
|
{% if additional_file.length %}
|
||||||
|
<table class="data-table">
|
||||||
|
<colgroup>
|
||||||
|
<col class="col--checkbox">
|
||||||
|
<col class="col--name">
|
||||||
|
<col class="col--size">
|
||||||
|
</colgroup>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class="col--checkbox">
|
||||||
|
<label class="compact checkbox">
|
||||||
|
<input type="checkbox" name="select_all" data-checkbox-toggle="additional_file">
|
||||||
|
</label>
|
||||||
|
</th>
|
||||||
|
<th class="col--name">{{ _('Filename') }}</th>
|
||||||
|
<th class="col--size">{{ _('Size') }}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{%- for file in additional_file -%}
|
||||||
|
<tr data-filename="{{ file.name }}" data-size="{{ file.size }}">
|
||||||
|
<td class="col--checkbox">
|
||||||
|
<label class="compact checkbox">
|
||||||
|
<input type="checkbox" data-checkbox-group="additional_file" data-checkbox-range>
|
||||||
|
</label>
|
||||||
|
</td>
|
||||||
|
<td class="col--name">
|
||||||
|
<a href="{{ url('problem_file_download', {pid: pdoc.docId, filename: file.name}) }}">{{ file.name }}</a>
|
||||||
|
</td>
|
||||||
|
<td class="col--size">{{ size(file.size) }}</td>
|
||||||
|
</tr>
|
||||||
|
{%- endfor -%}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
{% else %}
|
||||||
|
{{ nothing.render('No additional file at current.') }}
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
{% import "components/nothing.html" as nothing with context %}
|
||||||
|
<div class="section__body no-padding problem-files-testdata" data-fragment-id="problem-files-testdata">
|
||||||
|
{% if testdata.length %}
|
||||||
|
<table class="data-table">
|
||||||
|
<colgroup>
|
||||||
|
<col class="col--checkbox">
|
||||||
|
<col class="col--name">
|
||||||
|
<col class="col--size">
|
||||||
|
</colgroup>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class="col--checkbox">
|
||||||
|
<label class="compact checkbox">
|
||||||
|
<input type="checkbox" name="select_all" data-checkbox-toggle="testdata">
|
||||||
|
</label>
|
||||||
|
</th>
|
||||||
|
<th class="col--name">{{ _('Filename') }}</th>
|
||||||
|
<th class="col--size">{{ _('Size') }}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{%- for file in testdata -%}
|
||||||
|
<tr data-filename="{{ file.name }}" data-size="{{ file.size }}">
|
||||||
|
<td class="col--checkbox">
|
||||||
|
<label class="compact checkbox">
|
||||||
|
<input type="checkbox" data-checkbox-group="testdata" data-checkbox-range>
|
||||||
|
</label>
|
||||||
|
</td>
|
||||||
|
<td class="col--name">
|
||||||
|
<a href="{{ url('problem_file_download', {pid: pdoc.docId, filename: file.name, query:{type:'testdata'} }) }}">{{ file.name }}</a>
|
||||||
|
</td>
|
||||||
|
<td class="col--size">{{ size(file.size) }}</td>
|
||||||
|
</tr>
|
||||||
|
{%- endfor -%}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
{% else %}
|
||||||
|
{% if handler.user.own(pdoc) or handler.user.hasPriv(PRIV.PRIV_READ_PROBLEM_DATA) or handler.user.hasPerm(perm.PERM_READ_PROBLEM_DATA) %}
|
||||||
|
{{ nothing.render('No testdata at current.') }}
|
||||||
|
{% else %}
|
||||||
|
{{ nothing.render("You don't have permission to read testdata.")}}
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
@@ -110,69 +110,7 @@
|
|||||||
<div class="section__header">
|
<div class="section__header">
|
||||||
<h1 class="section__title">{{ _('Information') }}</h1>
|
<h1 class="section__title">{{ _('Information') }}</h1>
|
||||||
</div>
|
</div>
|
||||||
<div class="section__body typo">
|
{% include "partials/problem-sidebar-information.html" %}
|
||||||
<dl class="large horizontal">
|
|
||||||
<dt>{{ _('ID') }}</dt>
|
|
||||||
<dd>{{ pdoc.docId }}</dd>
|
|
||||||
<dt>{{ _('Time') }}</dt>
|
|
||||||
<dd>{{ pdoc.config.timeMax if pdoc.config.timeMax == pdoc.config.timeMin else pdoc.config.timeMin+'~'+pdoc.config.timeMax }}ms</dd>
|
|
||||||
<dt>{{ _('Memory') }}</dt>
|
|
||||||
<dd>{{ pdoc.config.memoryMax if pdoc.config.memoryMax == pdoc.config.memoryMin else pdoc.config.memoryMin+'~'+pdoc.config.memoryMax }}MiB</dd>
|
|
||||||
{% if page_name != 'discussion_node' and page_name != 'discussion_detail' %}
|
|
||||||
{% if handler.user.hasPriv(PRIV.PRIV_USER_PROFILE) %}
|
|
||||||
<dt>{{ _('Submission') }}</dt>
|
|
||||||
<dd>
|
|
||||||
{% if pdoc.psdoc.status %}
|
|
||||||
<span class="icon record-status--icon {{ model.builtin.STATUS_CODES[pdoc['psdoc']['status']] }}"></span>
|
|
||||||
<a href="{{ url('record_detail', rid=pdoc.psdoc.rid) }}" class="record-status--text {{ model.builtin.STATUS_CODES[pdoc['psdoc']['status']] }}">
|
|
||||||
{{ model.builtin.STATUS_TEXTS[pdoc['psdoc']['status']] }}
|
|
||||||
</a>
|
|
||||||
{% else %}
|
|
||||||
{{ _('(None)') }}
|
|
||||||
{% endif %}
|
|
||||||
</dd>
|
|
||||||
{% endif %}
|
|
||||||
{% endif %}
|
|
||||||
<dt>{{ _('Difficulty') }}</dt>
|
|
||||||
<dd>{{ pdoc['difficulty'] or _('(None)') }}</dd>
|
|
||||||
<dt>{{ _('Category') }}</dt>
|
|
||||||
<dd>
|
|
||||||
{% if pdoc['category'] %}
|
|
||||||
<span class="hasjs--hide" name="problem-sidebar__categories">
|
|
||||||
{%- for category in pdoc['category'] -%}
|
|
||||||
<a href="{{ url('problem_category', category=category) }}">{{ category }}</a>
|
|
||||||
{% if loop.index < pdoc['category']|length %}
|
|
||||||
{{ _('problem-subcategory-delim') }}
|
|
||||||
{% endif %}
|
|
||||||
{%- endfor -%}
|
|
||||||
</span>
|
|
||||||
<span class="nojs--hide">
|
|
||||||
<a href="javascript:;" name="problem-sidebar__show-category">{{ _('page.problem_detail.sidebar.show_category') }}</a>
|
|
||||||
</span>
|
|
||||||
{% else %}
|
|
||||||
{{ _('(None)') }}
|
|
||||||
{% endif %}
|
|
||||||
</dd>
|
|
||||||
<dt>{{ _('Tags') }}</dt>
|
|
||||||
<dd>
|
|
||||||
{{ problem.render_problem_tags(pdoc, true) }}
|
|
||||||
</dd>
|
|
||||||
<dt>{{ _('# Submissions') }}</dt>
|
|
||||||
<dd><a href="{{ url('record_main', query={pid:pdoc.docId}) }}">{{ pdoc.nSubmit }}</a></dd>
|
|
||||||
{% if page_name != 'discussion_node' and page_name != 'discussion_detail' %}
|
|
||||||
{% if handler.user.hasPriv(PRIV.PRIV_USER_PROFILE) %}
|
|
||||||
<dt>{{ _('# My Submissions') }}</dt>
|
|
||||||
<dd><a href="{{ url('record_main', query={pid:pdoc.docId, uidOrName:handler.user._id}) }}">{{ pdoc['psdoc'].nSubmit|default(0) }}</a></dd>
|
|
||||||
{% endif %}
|
|
||||||
{% endif %}
|
|
||||||
<dt>{{ _('Accepted') }}</dt>
|
|
||||||
<dd>{{ pdoc.nAccept }}</dd>
|
|
||||||
<dt>{{ _('Accepted Ratio') }}</dt>
|
|
||||||
<dd>{% if pdoc.nSubmit > 0 %}{{ (100 * pdoc.nAccept / pdoc.nSubmit)|round|int }}%{% else %}{{ _('?') }}{% endif %}</dd>
|
|
||||||
<dt>{{ _('Uploaded By') }}</dt>
|
|
||||||
<dd>{{ user.render_inline(owner_udoc, badge=false) }}</dd>
|
|
||||||
</dl>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
{% if page_name == 'problem_detail' and (tdocs or ctdocs) %}
|
{% if page_name == 'problem_detail' and (tdocs or ctdocs) %}
|
||||||
<div class="section side">
|
<div class="section side">
|
||||||
|
|||||||
@@ -7,54 +7,13 @@
|
|||||||
<h1 class="section__title">{{ _('Testdata') }}</h1>
|
<h1 class="section__title">{{ _('Testdata') }}</h1>
|
||||||
<div class="section__tools">
|
<div class="section__tools">
|
||||||
{% if handler.user.own(pdoc, perm.PERM_EDIT_PROBLEM_SELF) or handler.user.hasPerm(perm.PERM_EDIT_PROBLEM) %}
|
{% if handler.user.own(pdoc, perm.PERM_EDIT_PROBLEM_SELF) or handler.user.hasPerm(perm.PERM_EDIT_PROBLEM) %}
|
||||||
<button class="primary rounded button" name="upload_testdata">{{ _('Add File') }}</button>
|
<button class="primary rounded button" name="create_testdata">{{ _('Create File') }}</button>
|
||||||
|
<button class="primary rounded button" name="upload_testdata">{{ _('Upload File') }}</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{{ noscript_note.render() }}
|
{{ noscript_note.render() }}
|
||||||
<div class="section__body no-padding problem-files-testdata">
|
{% include "partials/problem_files-testdata.html" %}
|
||||||
{% if testdata.length %}
|
|
||||||
<table class="data-table">
|
|
||||||
<colgroup>
|
|
||||||
<col class="col--checkbox">
|
|
||||||
<col class="col--name">
|
|
||||||
<col class="col--size">
|
|
||||||
</colgroup>
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th class="col--checkbox">
|
|
||||||
<label class="compact checkbox">
|
|
||||||
<input type="checkbox" name="select_all" data-checkbox-toggle="testdata">
|
|
||||||
</label>
|
|
||||||
</th>
|
|
||||||
<th class="col--name">{{ _('Filename') }}</th>
|
|
||||||
<th class="col--size">{{ _('Size') }}</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{%- for file in testdata -%}
|
|
||||||
<tr data-filename="{{ file.name }}" data-size="{{ file.size }}">
|
|
||||||
<td class="col--checkbox">
|
|
||||||
<label class="compact checkbox">
|
|
||||||
<input type="checkbox" data-checkbox-group="testdata" data-checkbox-range>
|
|
||||||
</label>
|
|
||||||
</td>
|
|
||||||
<td class="col--name">
|
|
||||||
<a href="{{ url('problem_file_download', {pid: pdoc.docId, filename: file.name, query:{type:'testdata'} }) }}">{{ file.name }}</a>
|
|
||||||
</td>
|
|
||||||
<td class="col--size">{{ size(file.size) }}</td>
|
|
||||||
</tr>
|
|
||||||
{%- endfor -%}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
{% else %}
|
|
||||||
{% if handler.user.own(pdoc) or handler.user.hasPriv(PRIV.PRIV_READ_PROBLEM_DATA) or handler.user.hasPerm(perm.PERM_READ_PROBLEM_DATA) %}
|
|
||||||
{{ nothing.render('No testdata at current.') }}
|
|
||||||
{% else %}
|
|
||||||
{{ nothing.render("You don't have permission to read testdata.")}}
|
|
||||||
{% endif %}
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
{% if handler.user.own(pdoc, perm.PERM_EDIT_PROBLEM_SELF) or handler.user.hasPerm(perm.PERM_EDIT_PROBLEM) %}
|
{% if handler.user.own(pdoc, perm.PERM_EDIT_PROBLEM_SELF) or handler.user.hasPerm(perm.PERM_EDIT_PROBLEM) %}
|
||||||
{% if testdata.length %}
|
{% if testdata.length %}
|
||||||
<div class="section__body">
|
<div class="section__body">
|
||||||
@@ -71,49 +30,12 @@
|
|||||||
<h1 class="section__title">{{ _('Additional Files') }}</h1>
|
<h1 class="section__title">{{ _('Additional Files') }}</h1>
|
||||||
<div class="section__tools">
|
<div class="section__tools">
|
||||||
{% if handler.user.own(pdoc, perm.PERM_EDIT_PROBLEM_SELF) or handler.user.hasPerm(perm.PERM_EDIT_PROBLEM) %}
|
{% if handler.user.own(pdoc, perm.PERM_EDIT_PROBLEM_SELF) or handler.user.hasPerm(perm.PERM_EDIT_PROBLEM) %}
|
||||||
<button class="primary rounded button" name="upload_file">{{ _('Add File') }}</button>
|
<button class="primary rounded button" name="create_file">{{ _('Create File') }}</button>
|
||||||
|
<button class="primary rounded button" name="upload_file">{{ _('Upload File') }}</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="section__body no-padding problem-files-additional_file">
|
{% include "partials/problem_files-additional_file.html" %}
|
||||||
{% if additional_file.length %}
|
|
||||||
<table class="data-table">
|
|
||||||
<colgroup>
|
|
||||||
<col class="col--checkbox">
|
|
||||||
<col class="col--name">
|
|
||||||
<col class="col--size">
|
|
||||||
</colgroup>
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th class="col--checkbox">
|
|
||||||
<label class="compact checkbox">
|
|
||||||
<input type="checkbox" name="select_all" data-checkbox-toggle="additional_file">
|
|
||||||
</label>
|
|
||||||
</th>
|
|
||||||
<th class="col--name">{{ _('Filename') }}</th>
|
|
||||||
<th class="col--size">{{ _('Size') }}</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{%- for file in additional_file -%}
|
|
||||||
<tr data-filename="{{ file.name }}" data-size="{{ file.size }}">
|
|
||||||
<td class="col--checkbox">
|
|
||||||
<label class="compact checkbox">
|
|
||||||
<input type="checkbox" data-checkbox-group="additional_file" data-checkbox-range>
|
|
||||||
</label>
|
|
||||||
</td>
|
|
||||||
<td class="col--name">
|
|
||||||
<a href="{{ url('problem_file_download', {pid: pdoc.docId, filename: file.name}) }}">{{ file.name }}</a>
|
|
||||||
</td>
|
|
||||||
<td class="col--size">{{ size(file.size) }}</td>
|
|
||||||
</tr>
|
|
||||||
{%- endfor -%}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
{% else %}
|
|
||||||
{{ nothing.render('No additional file at current.') }}
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
{% if additional_file.length %}
|
{% if additional_file.length %}
|
||||||
<div class="section__body">
|
<div class="section__body">
|
||||||
<button class="rounded button" name="download_selected_file">{{ _('Download Selected') }}</button>
|
<button class="rounded button" name="download_selected_file">{{ _('Download Selected') }}</button>
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import 'pickadate/lib/themes/classic.css';
|
|||||||
import 'pickadate/lib/themes/classic.date.css';
|
import 'pickadate/lib/themes/classic.date.css';
|
||||||
import 'pickadate/lib/themes/classic.time.css';
|
import 'pickadate/lib/themes/classic.time.css';
|
||||||
import 'katex/dist/katex.min.css';
|
import 'katex/dist/katex.min.css';
|
||||||
|
import '@blueprintjs/core/lib/css/blueprint.css';
|
||||||
|
import '@blueprintjs/icons/lib/css/blueprint-icons.css';
|
||||||
|
|
||||||
import 'vj/misc/float.styl';
|
import 'vj/misc/float.styl';
|
||||||
import 'vj/misc/typography.styl';
|
import 'vj/misc/typography.styl';
|
||||||
|
|||||||
@@ -70,12 +70,15 @@ pjax.request = async (opt) => {
|
|||||||
}
|
}
|
||||||
incProgress();
|
incProgress();
|
||||||
try {
|
try {
|
||||||
const data = await request.ajax({
|
const params = {
|
||||||
beforeSend(xhr) {
|
beforeSend(xhr) {
|
||||||
currentXHR = xhr;
|
currentXHR = xhr;
|
||||||
},
|
},
|
||||||
...options,
|
...options,
|
||||||
});
|
};
|
||||||
|
if (params.url && params.url.includes('?')) params.url += '&pjax=1';
|
||||||
|
else params.url = `${params.url || ''}?pjax=1`;
|
||||||
|
const data = await request.ajax(params);
|
||||||
const meta = extractMetaData(options, data);
|
const meta = extractMetaData(options, data);
|
||||||
currentState = {
|
currentState = {
|
||||||
id: uuid(),
|
id: uuid(),
|
||||||
@@ -84,7 +87,7 @@ pjax.request = async (opt) => {
|
|||||||
if (options.push) {
|
if (options.push) {
|
||||||
window.history.replaceState(currentState, null, meta.url);
|
window.history.replaceState(currentState, null, meta.url);
|
||||||
}
|
}
|
||||||
document.title = meta.title;
|
if (meta.title) document.title = meta.title;
|
||||||
data.fragments.forEach((fragment) => {
|
data.fragments.forEach((fragment) => {
|
||||||
if (process.env.NODE_ENV !== 'production') {
|
if (process.env.NODE_ENV !== 'production') {
|
||||||
if (fragment.html === undefined) {
|
if (fragment.html === undefined) {
|
||||||
@@ -120,7 +123,7 @@ pjax.request = async (opt) => {
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (!err.aborted) {
|
if (!err.aborted) {
|
||||||
Notification.error(err.message);
|
Notification.error(err.message);
|
||||||
console.log(err);
|
console.error(err);
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
decProgress();
|
decProgress();
|
||||||
@@ -128,13 +131,9 @@ pjax.request = async (opt) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
function handlePopState(ev) {
|
function handlePopState(ev) {
|
||||||
if (!ev.originalEvent) {
|
if (!ev.originalEvent) return;
|
||||||
return;
|
|
||||||
}
|
|
||||||
const { state } = ev.originalEvent;
|
const { state } = ev.originalEvent;
|
||||||
if (!state) {
|
if (!state) return;
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!state.id || (currentState && state.id === currentState.id)) {
|
if (!state.id || (currentState && state.id === currentState.id)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user