diff --git a/packages/hydrooj/src/handler/problem.ts b/packages/hydrooj/src/handler/problem.ts index 9629037ea..a9aad08d8 100644 --- a/packages/hydrooj/src/handler/problem.ts +++ b/packages/hydrooj/src/handler/problem.ts @@ -671,19 +671,23 @@ export class ProblemFilesHandler extends ProblemDetailHandler { @param('testdata', Types.Boolean) @param('additional_file', Types.Boolean) @param('pjax', Types.Boolean) - async get(domainId: string, getTestdata = true, getAdditionalFile = true, pjax = false) { + @param('sidebar', Types.Boolean) + async get(domainId: string, getTestdata = true, getAdditionalFile = true, pjax = false, sidebar = false) { this.response.body.testdata = getTestdata ? sortFiles(this.pdoc.data || []) : []; this.response.body.reference = getTestdata ? this.pdoc.reference : ''; this.response.body.additional_file = getAdditionalFile ? sortFiles(this.pdoc.additional_file || []) : []; if (pjax) { const { testdata, additional_file } = this.response.body; const owner = await user.getById(domainId, this.pdoc.owner); + const args = { + testdata, additional_file, pdoc: this.pdoc, owner_udoc: owner, sidebar, + }; + const tasks = []; + if (getTestdata) tasks.push(this.renderHTML('partials/problem_files.html', { ...args, filetype: 'testdata' })); + if (getAdditionalFile) tasks.push(this.renderHTML('partials/problem_files.html', { ...args, filetype: 'additional_file' })); + if (!sidebar) tasks.push(this.renderHTML('partials/problem-sidebar-information.html', args)); 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, owner_udoc: owner }), - ])).map((i) => ({ html: i })), + fragments: (await Promise.all(tasks)).map((i) => ({ html: i })), }; this.response.template = ''; } else this.response.template = 'problem_files.html'; diff --git a/packages/hydrooj/src/service/decorators.ts b/packages/hydrooj/src/service/decorators.ts index 9116740ac..780505aa9 100644 --- a/packages/hydrooj/src/service/decorators.ts +++ b/packages/hydrooj/src/service/decorators.ts @@ -64,7 +64,7 @@ export const Types: Types = { Float: [(v) => +v, (v) => Number.isFinite(+v)], // eslint-disable-next-line no-shadow ObjectID: [(v) => new ObjectID(v), ObjectID.isValid], - Boolean: [(v) => !!v, null, true], + Boolean: [(v) => v && !['false', 'off'].includes(v), null, true], Date: [ (v) => { const d = v.split('-'); diff --git a/packages/ui-default/components/preview/preview.page.ts b/packages/ui-default/components/preview/preview.page.ts index 6857a503a..c9d1b54a4 100644 --- a/packages/ui-default/components/preview/preview.page.ts +++ b/packages/ui-default/components/preview/preview.page.ts @@ -4,9 +4,8 @@ import { nanoid } from 'nanoid'; import { ActionDialog, InfoDialog } from 'vj/components/dialog/index'; import Notification from 'vj/components/notification'; import { AutoloadPage } from 'vj/misc/Page'; -import { - i18n, pjax, request, tpl, -} from 'vj/utils'; +import { i18n, request, tpl } from 'vj/utils'; +import uploadFiles from '../upload'; async function startEdit(filename, value, fileCategory = 'file') { const { default: Editor } = await import('vj/components/editor/index'); @@ -144,17 +143,17 @@ export async function previewFile(ev, type = '') { } else Notification.info(i18n('Loading editor...')); const val = await startEdit(filename, content, type || 'file'); if (typeof val !== 'string') return null; - Notification.info(i18n('Saving file...')); - const data = new FormData(); - data.append('filename', filename); - data.append('file', new Blob([val], { type: 'text/plain' })); - if (type) data.append('type', type); - data.append('operation', 'upload_file'); - const postUrl = !window.location.href.endsWith('/files') - ? `${window.location.href.substring(0, window.location.href.lastIndexOf('/'))}/files` : ''; - await request.postFile(postUrl, data); - Notification.success(i18n('File saved.')); - return pjax.request({ push: false }); + const file = new File([val], filename, { type: 'text/plain' }); + const endpoint = new URL(!window.location.href.endsWith('/files') + ? `${window.location.href.substring(0, window.location.href.lastIndexOf('/'))}/files` : '', window.location.href); + const sidebarType = $(ev.currentTarget).closest('[data-fragment-id]').data('type'); + const sidebar = $(ev.currentTarget).closest('[data-fragment-id]').data('sidebar') !== undefined; + await uploadFiles(endpoint.toString(), [file], { + type: sidebarType || type, + sidebar, + pjax: true, + }); + return null; } const dataPreviewPage = new AutoloadPage('dataPreview', () => { diff --git a/packages/ui-default/components/upload.ts b/packages/ui-default/components/upload.ts index 83131f0ea..915022ee5 100644 --- a/packages/ui-default/components/upload.ts +++ b/packages/ui-default/components/upload.ts @@ -11,6 +11,7 @@ function onBeforeUnload(e) { interface UploadOptions { type?: string; pjax?: boolean; + sidebar?: boolean; singleFileUploadCallback?: (file: File) => any; } export default async function uploadFiles(endpoint = '', files: File[] | FileList = [], options: UploadOptions = {}) { @@ -65,7 +66,12 @@ export default async function uploadFiles(endpoint = '', files: File[] | FileLis } window.removeEventListener('beforeunload', onBeforeUnload); Notification.success(i18n('File uploaded successfully.')); - if (options.pjax) await pjax.request({ push: false }); + if (options.pjax) { + let params = ''; + if (options.type) params += `?testdata=${options.type === 'testdata'}&additional_file=${options.type === 'additional_file'}`; + if (options.sidebar) params += `${params ? '&' : '?'}sidebar=true`; + await pjax.request({ push: false, url: `${endpoint}${params || ''}` }); + } } catch (e) { console.error(e); Notification.error(i18n('File upload failed: {0}', e.toString())); diff --git a/packages/ui-default/locales/zh.yaml b/packages/ui-default/locales/zh.yaml index 20e587c7b..553dfbd7b 100644 --- a/packages/ui-default/locales/zh.yaml +++ b/packages/ui-default/locales/zh.yaml @@ -68,6 +68,7 @@ Add module: 添加模块 Add new data: 添加新数据 Add User: 添加用户 Add: 添加 +additional_file: 附加文件 All {0} Contests: 所有 {0} 比赛 All Contests: 所有比赛 All Homeworks: 所有作业 @@ -529,8 +530,7 @@ New file: 新文件 New Password: 新密码 New Training Plan: 新训练计划 New: 创建 -No additional file at current: 当前没有附加文件 -No additional file at current.: 暂无附加文件。 +No {0} at current.: 当前没有{0}。 No comments so far...: 目前还没有评论... no discussion yet...: 目前没有讨论… No Permission to Attend Contest: 您没有权限参加比赛 @@ -814,6 +814,7 @@ Temporary session: 临时会话 Terms of Service: 服务条款 Test data comes from: 测试数据来自 Testdata Config: 测试数据配置 +testdata: 测试数据 Testdata: 测试数据 Text: 文本 The 'default' role applies to ALL REGISTERED USER.: default 角色作用于所有已注册用户 diff --git a/packages/ui-default/pages/problem_config.page.styl b/packages/ui-default/pages/problem_config.page.styl index ef43e08a1..964b69cd1 100644 --- a/packages/ui-default/pages/problem_config.page.styl +++ b/packages/ui-default/pages/problem_config.page.styl @@ -1,8 +1,7 @@ .page--problem_config - .section--problem-sidebar-testdata - .problem-sidebar-testdata__detail - max-height: 270px - overflow-y: auto + .problem-sidebar-testdata + max-height: 270px + overflow-y: auto .col--size width: 80px diff --git a/packages/ui-default/pages/problem_config.page.tsx b/packages/ui-default/pages/problem_config.page.tsx index 525adc5c0..29c4f44d3 100644 --- a/packages/ui-default/pages/problem_config.page.tsx +++ b/packages/ui-default/pages/problem_config.page.tsx @@ -1,4 +1,4 @@ -import { normalizeSubtasks, readSubtasksFromFiles, size } from '@hydrooj/utils/lib/common'; +import { normalizeSubtasks, readSubtasksFromFiles } from '@hydrooj/utils/lib/common'; import type { SubtaskType } from 'hydrooj/src/interface'; import $ from 'jquery'; import yaml from 'js-yaml'; @@ -11,7 +11,7 @@ import uploadFiles from 'vj/components/upload'; import download from 'vj/components/zipDownloader'; import { NamedPage } from 'vj/misc/Page'; import { - i18n, loadReactRedux, request, tpl, + i18n, loadReactRedux, pjax, request, tpl, } from 'vj/utils'; const page = new NamedPage('problem_config', () => { @@ -25,6 +25,8 @@ const page = new NamedPage('problem_config', () => { await new Promise((resolve) => { input.onchange = resolve; }); await uploadFiles('./files', input.files, { type: 'testdata', + sidebar: true, + pjax: true, singleFileUploadCallback: (file) => { reduxStore.dispatch({ type: 'CONFIG_ADD_TESTDATA', @@ -34,13 +36,6 @@ const page = new NamedPage('problem_config', () => { size: file.size, }, }); - $('.testdata-table tbody').append( - $(tpl`
-{% endif %} {% if typeof(pdoc.config) == 'string' %} diff --git a/packages/ui-default/templates/partials/problem_files-additional_file.html b/packages/ui-default/templates/partials/problem_files-additional_file.html deleted file mode 100644 index a3feae9d5..000000000 --- a/packages/ui-default/templates/partials/problem_files-additional_file.html +++ /dev/null @@ -1,40 +0,0 @@ -{% import "components/nothing.html" as nothing with context %} -{{ _('No testdata at current.') }}
+{{ _('No {0} at current.').format(_("testdata")) }}
| - - | -{{ _('Filename') }} | -{{ _('Size') }} | -
|---|---|---|
| - - | -- {{ file.name }} - | -{{ size(file.size) }} | -
-- {% elif testdata.length %} -{{ _('This is a copy of another problem.') }}
-
| - - | -{{ _('Filename') }} | -{{ _('Size') }} | -
|---|---|---|
| - - | -
- {% if handler.user.own(pdoc) or handler.user.hasPriv(PRIV.PRIV_READ_PROBLEM_DATA) or handler.user.hasPerm(perm.PERM_READ_PROBLEM_DATA) %}
- {{ file.name }}
- {% else %}
- {{ file.name }} - {% endif %} - |
- {{ size(file.size) }} | -