diff --git a/packages/ui-default/breakpoints.json b/packages/ui-default/breakpoints.json index 58aba875..807afb51 100644 --- a/packages/ui-default/breakpoints.json +++ b/packages/ui-default/breakpoints.json @@ -1,5 +1,5 @@ { "mobile": 600, "desktop": 1000, - "hd": 1250 + "hd": 1280 } diff --git a/packages/ui-default/components/star/star.page.js b/packages/ui-default/components/star/star.page.js index 249ef8aa..ddf60003 100644 --- a/packages/ui-default/components/star/star.page.js +++ b/packages/ui-default/components/star/star.page.js @@ -14,7 +14,9 @@ const starPage = new AutoloadPage('starPage', () => { const $button = $(ev.currentTarget); const currentState = $button.hasClass('activated'); const $form = $button.closest('form'); - $form.find('[name="operation"]').val(currentState ? 'unstar' : 'star'); + const $op = $form.find('[name="operation"]'); + if (!['star', 'unstar'].includes($op.val())) return; + $op.val(currentState ? 'unstar' : 'star'); setStarButtonState($button, !currentState); request .post($form.attr('action'), $form) @@ -25,7 +27,6 @@ const starPage = new AutoloadPage('starPage', () => { // TODO: notify failure setStarButtonState($button, currentState); }); - return false; }); }); diff --git a/packages/ui-default/package.json b/packages/ui-default/package.json index f873e048..6abd37d0 100644 --- a/packages/ui-default/package.json +++ b/packages/ui-default/package.json @@ -1,6 +1,6 @@ { "name": "@hydrooj/ui-default", - "version": "4.31.14", + "version": "4.31.15", "author": "undefined ", "license": "AGPL-3.0", "main": "hydro.js", diff --git a/packages/ui-default/pages/file.page.js b/packages/ui-default/pages/file.page.js new file mode 100644 index 00000000..4b8a816c --- /dev/null +++ b/packages/ui-default/pages/file.page.js @@ -0,0 +1,174 @@ +import _ from 'lodash'; +import Clipboard from 'clipboard'; +import { NamedPage } from 'vj/misc/Page'; +import Notification from 'vj/components/notification'; +import { ConfirmDialog, Dialog } from 'vj/components/dialog/index'; +import request from 'vj/utils/request'; +import pjax from 'vj/utils/pjax'; +import tpl from 'vj/utils/tpl'; +import i18n from 'vj/utils/i18n'; + +function onBeforeUnload(e) { + e.returnValue = ''; +} + +function ensureAndGetSelectedFiles() { + const files = _.map( + $('.home-files tbody [data-checkbox-group="user_files"]:checked'), + (ch) => $(ch).closest('tr').attr('data-filename'), + ); + if (files.length === 0) { + Notification.error(i18n('Please select at least one file to perform this operation.')); + return null; + } + return files; +} + +async function handleClickUpload(files) { + if (!files) { + const input = document.createElement('input'); + input.type = 'file'; + input.multiple = true; + input.click(); + await new Promise((resolve) => { input.onchange = resolve; }); + files = input.files; + } + if (!files.length) { + Notification.warn(i18n('No file selected.')); + return; + } + const dialog = new Dialog({ + $body: ` +
+
+
+
+
+
+
+
`, + }); + try { + Notification.info(i18n('Uploading files...')); + window.addEventListener('beforeunload', onBeforeUnload); + dialog.open(); + const $uploadLabel = dialog.$dom.find('.dialog__body .upload-label'); + const $uploadProgress = dialog.$dom.find('.dialog__body .upload-progress'); + const $fileLabel = dialog.$dom.find('.dialog__body .file-label'); + const $fileProgress = dialog.$dom.find('.dialog__body .file-progress'); + for (const i in files) { + if (Number.isNaN(+i)) continue; + const file = files[i]; + const data = new FormData(); + data.append('filename', file.name); + data.append('file', file); + data.append('operation', 'upload_file'); + await request.postFile('', data, { + xhr() { + const xhr = new XMLHttpRequest(); + xhr.upload.addEventListener('loadstart', () => { + $fileLabel.text(`[${+i + 1}/${files.length}] ${file.name}`); + $fileProgress.width(`${Math.round((+i + 1) / files.length * 100)}%`); + $uploadLabel.text(i18n('Uploading... ({0}%)', 0)); + $uploadProgress.width(0); + }); + xhr.upload.addEventListener('progress', (e) => { + if (e.lengthComputable) { + const percentComplete = Math.round((e.loaded / e.total) * 100); + if (percentComplete === 100) $uploadLabel.text(i18n('Processing...')); + else $uploadLabel.text(i18n('Uploading... ({0}%)', percentComplete)); + $uploadProgress.width(`${percentComplete}%`); + } + }, false); + return xhr; + }, + }); + } + window.removeEventListener('beforeunload', onBeforeUnload); + Notification.success(i18n('File uploaded successfully.')); + await pjax.request({ push: false }); + } catch (e) { + console.error(e); + Notification.error(i18n('File upload failed: {0}', e.toString())); + } finally { + dialog.close(); + } +} + +async function handleClickRemoveSelected() { + const selectedFiles = ensureAndGetSelectedFiles(); + if (selectedFiles === null) return; + const action = await new ConfirmDialog({ + $body: tpl` +
+

${i18n('Confirm to delete the selected files?')}

+
`, + }).open(); + if (action !== 'yes') return; + try { + await request.post('', { + operation: 'delete_files', + files: selectedFiles, + }); + Notification.success(i18n('Selected files have been deleted.')); + await pjax.request({ push: false }); + } catch (error) { + Notification.error(error.message); + } +} + +/** + * @param {JQuery.DragOverEvent} ev + */ +function handleDragOver(ev) { + ev.preventDefault(); + // TODO display a drag-drop allowed hint +} + +/** + * @param {JQuery.DropEvent} ev + */ +function handleDrop(ev) { + ev.preventDefault(); + if (!$('[name="upload_file"]').length) { + Notification.error(i18n("You don't have permission to upload file.")); + return; + } + ev = ev.originalEvent; + const files = []; + if (ev.dataTransfer.items) { + for (let i = 0; i < ev.dataTransfer.items.length; i++) { + if (ev.dataTransfer.items[i].kind === 'file') { + const file = ev.dataTransfer.items[i].getAsFile(); + files.push(file); + } + } + } else { + for (let i = 0; i < ev.dataTransfer.files.length; i++) { + files.push(ev.dataTransfer.files[i]); + } + } + handleClickUpload(files); +} + +const page = new NamedPage('home_files', () => { + const clip = new Clipboard('.home-files .col--name', { + text: (trigger) => { + const filename = trigger.closest('[data-filename]').getAttribute('data-filename'); + return new URL(`/file/${UserContext._id}/${filename}`, window.location.href).toString(); + }, + }); + clip.on('success', () => { + Notification.success(i18n('Download link copied to clipboard!'), 1000); + }); + clip.on('error', () => { + Notification.error(i18n('Copy failed :(')); + }); + $(document).on('click', '.home-files .col--name', (ev) => ev.preventDefault()); + $(document).on('click', '[name="upload_file"]', () => handleClickUpload()); + $(document).on('click', '[name="remove_selected"]', () => handleClickRemoveSelected()); + $(document).on('dragover', '.home-files', (ev) => handleDragOver(ev)); + $(document).on('drop', '.home-files', (ev) => handleDrop(ev)); +}); + +export default page; diff --git a/packages/ui-default/pages/problem_main.page.styl b/packages/ui-default/pages/problem_main.page.styl index a966eef9..f1c8bbe7 100644 --- a/packages/ui-default/pages/problem_main.page.styl +++ b/packages/ui-default/pages/problem_main.page.styl @@ -9,15 +9,15 @@ .col--status width: rem(140px) position: relative + border-right: 1px solid $table-border-color - .col--tried, .col--ac, .col--difficulty + .col--difficulty width: rem(70px) text-align: center - .col--status - border-right: 1px solid $table-border-color - - .col--tried, .col--difficulty + .col--ac-tried + width: rem(100px) + text-align: center border-left: 1px solid $table-border-color +mobile() diff --git a/packages/ui-default/templates/components/problem.html b/packages/ui-default/templates/components/problem.html index d00f7bb7..e0be07c4 100644 --- a/packages/ui-default/templates/components/problem.html +++ b/packages/ui-default/templates/components/problem.html @@ -5,24 +5,16 @@ {% if pdoc.domainId !== handler.domainId %}{{ set(_linkArgs, 'domainId', pdoc.domainId) }}{% endif %} {%- endif -%} -{%- if pdoc.domainId !== handler.domainId -%} - {{ pdoc.domainId }}# -{%- endif -%} -{%- if pdoc.pid -%} - {{ pdoc.pid }} -{%- else -%} - {{ 'P'+pdoc.docId if pdoc.domainId === handler.domainId else pdoc.docId }} -{%- endif -%} -{%- if not small -%}. {{ pdoc.title }}{%- endif -%} +{%- if pdoc.domainId !== handler.domainId -%}{{ pdoc.domainId }}#{%- endif -%} +{{ pdoc.pid or 'P'+pdoc.docId if pdoc.domainId === handler.domainId else pdoc.docId }} +{%- if not small -%}  {{ pdoc.title }}{%- endif -%} {%- if not invalid -%} {%- endif -%} {%- if pdoc.hidden and show_invisible_flag -%} ({{ _('Hidden') }}) {%- endif -%} -{%- if show_tags -%} - {{ render_problem_tags(pdoc, inline=inline) }} -{%- endif -%} +{%- if show_tags -%}{{ render_problem_tags(pdoc, inline=inline) }}{%- endif -%} {% endmacro %} {% macro render_problem_tags(pdoc, show_none_label=false, inline=false) %} @@ -30,11 +22,9 @@ {%- if not inline %}
    {% endif -%} {%- for tag in pdoc['tag'] %}
  • {{ tag }}
  • - {%- endfor %} + {%- endfor -%} {%- if not inline %}
{% endif -%} -{%- else %} - {%- if show_none_label %} - {{ _('(None)') }} - {%- endif %} -{%- endif %} +{%- else -%} + {%- if show_none_label -%}{{ _('(None)') }}{%- endif -%} +{%- endif -%} {% endmacro %} diff --git a/packages/ui-default/templates/components/record.html b/packages/ui-default/templates/components/record.html index c734b914..d8d84417 100644 --- a/packages/ui-default/templates/components/record.html +++ b/packages/ui-default/templates/components/record.html @@ -1,13 +1,13 @@ -{% macro render_status_td(rdoc, rid_key='_id', class='', allDomain = false) %} +{% macro render_status_td(rdoc, rid_key='_id', class='', allDomain=false, short=false) %} {% if rdoc.status == STATUS.STATUS_JUDGING %} diff --git a/packages/ui-default/templates/partials/problem_list.html b/packages/ui-default/templates/partials/problem_list.html index 6f43c10e..a5cdf741 100644 --- a/packages/ui-default/templates/partials/problem_list.html +++ b/packages/ui-default/templates/partials/problem_list.html @@ -15,8 +15,7 @@ {% endif %} - - + @@ -34,8 +33,7 @@ {{ _('Show tags') }} {{ _('Hide tags') }} - {{ _('Tried') }} - {{ _('AC') }} + {{ _('AC') }} / {{ _('Tried') }} {{ _('Difficulty') }} @@ -69,8 +67,7 @@ {% endif %} {{ problem.render_problem_title(pdoc) }} - {{ pdoc.nSubmit }} - {{ pdoc.nAccept }} + {{ pdoc.nAccept }} / {{ pdoc.nSubmit }} {{ pdoc['difficulty'] or lib.difficulty(pdoc.nSubmit, pdoc.nAccept) or _('(None)') }} {%- endfor -%}