diff --git a/packages/common/subtask.ts b/packages/common/subtask.ts index 6a1fa5747..f571d7a80 100644 --- a/packages/common/subtask.ts +++ b/packages/common/subtask.ts @@ -1,4 +1,4 @@ -import { parseMemoryMB, parseTimeMS } from '@hydrooj/utils/lib/common'; +import { parseMemoryMB, parseTimeMS, sortFiles } from '@hydrooj/utils/lib/common'; export function convertIniConfig(ini: string) { const f = ini.split('\n'); @@ -118,13 +118,16 @@ export function readSubtasksFromFiles(files: string[], config) { id: sid, }; } else if (!subtask[sid].cases) subtask[sid].cases = [c]; - else subtask[sid].cases.push(c); + else if (!subtask[sid].cases.find((i) => i.input === c.input && i.output === c.output)) { + subtask[sid].cases.push(c); + } break; } } if (match) break; } } + for (const id in subtask) subtask[id].cases = sortFiles(subtask[id].cases, 'input'); return Object.values(subtask); } diff --git a/packages/components/frontend/autocomplete/autocomplete.scss b/packages/components/frontend/autocomplete/autocomplete.scss index 8383271ff..3f391a380 100644 --- a/packages/components/frontend/autocomplete/autocomplete.scss +++ b/packages/components/frontend/autocomplete/autocomplete.scss @@ -75,12 +75,6 @@ white-space: nowrap; text-overflow: ellipsis; } - - &>span.bp6-icon>svg { - font-size: 12px; - cursor: pointer; - padding: 4px; - } } .autocomplete-list { @@ -104,26 +98,14 @@ flex-grow: 1; } - &>span.bp6-icon>svg { - color: transparent; - } - &[aria-selected='true'] { background-color: #fafafa; font-weight: 600; - - &>span.bp6-icon>svg { - color: #1890ff; - } } &[data-focus='true'] { background-color: #e6f7ff; cursor: pointer; - - &>span.bp6-icon>svg { - color: currentColor; - } } } } \ No newline at end of file diff --git a/packages/ui-default/components/datepicker/datepicker.styl b/packages/ui-default/components/datepicker/datepicker.styl index 16e60db05..68e155e70 100644 --- a/packages/ui-default/components/datepicker/datepicker.styl +++ b/packages/ui-default/components/datepicker/datepicker.styl @@ -16,7 +16,6 @@ opacity .1s ease-in-out, max-height 0s, border-width 0s - box-shadow: $data-picker-shadow .picker__list padding: 0 diff --git a/packages/ui-default/components/discussion/history.page.tsx b/packages/ui-default/components/discussion/history.page.tsx index b71c6a917..76829e04e 100644 --- a/packages/ui-default/components/discussion/history.page.tsx +++ b/packages/ui-default/components/discussion/history.page.tsx @@ -1,8 +1,7 @@ /* eslint-disable react-refresh/only-export-components */ -import { Popover } from '@blueprintjs/core'; +import { MantineProvider, Popover } from '@mantine/core'; import React from 'react'; import ReactDOM from 'react-dom/client'; -import { QueryClient, QueryClientProvider, useQuery } from 'react-query'; import TimeAgo from 'timeago-react'; import { InfoDialog } from 'vj/components/dialog'; import { AutoloadPage } from 'vj/misc/Page'; @@ -22,22 +21,31 @@ async function historyDialog(payload, time, uid) { }).open(); } -const queryClient = new QueryClient(); - function History({ payload }) { const [load, setLoad] = React.useState(false); - const { isLoading, isError, data } = useQuery(['history', payload], async () => { - const { history } = await request.get(`${payload}?all=1`); - return history; - }, { enabled: !!load }); - return ( - setLoad(true)} - content={
    - {(isLoading || isError) && ( + const [data, setData] = React.useState([]); + const [isLoading, setLoading] = React.useState(true); + const [error, setError] = React.useState(''); + + React.useEffect(() => { + if (!load) return; + setLoading(true); + request.get(`${payload}?all=1`).then(({ history }) => { + setData(history); + }).catch((e) => { + setError(e.message); + }).finally(() => { + setLoading(false); + }); + }, [load]); + + return setLoad(true)}> + + {i18n('Edited')} + + +
      + {(isLoading || error) && (
    1. {isLoading ? i18n('Loading...') : i18n('Loading failed.')} @@ -53,11 +61,9 @@ function History({ payload }) {
    2. ))} -
    } - > - {i18n('Edited')} -
    - ); +
+ +
; } const page = new AutoloadPage('discussionHistoryPage', () => { @@ -65,9 +71,9 @@ const page = new AutoloadPage('discussionHistoryPage', () => { const url = $(e).data('raw-url'); if (!url) return; ReactDOM.createRoot(e).render( - + - , + , ); }); }); diff --git a/packages/ui-default/components/discussion/reaction.page.tsx b/packages/ui-default/components/discussion/reaction.page.tsx index eb30a09e7..c5a41dda2 100644 --- a/packages/ui-default/components/discussion/reaction.page.tsx +++ b/packages/ui-default/components/discussion/reaction.page.tsx @@ -1,7 +1,7 @@ /* eslint-disable react-refresh/only-export-components */ import 'jquery.easing'; -import { Popover } from '@blueprintjs/core'; +import { MantineProvider, Popover } from '@mantine/core'; import $ from 'jquery'; import { chunk } from 'lodash'; import * as React from 'react'; @@ -36,21 +36,30 @@ function Reaction({ payload, ele }) { const emojiList: string[] = (UiContext.emojiList || '👍 👎 😄 😕 ❤️ 🤔 🤣 🌿 🍋 🕊️ 👀 🤡').split(' '); const elesPerRow = getRow(Math.sqrt(emojiList.length)); const [focus, updateFocus] = React.useState(false); - const [finish, updateFinish] = React.useState(false); - if (finish) setTimeout(() => updateFinish(false), 1000); + const [open, updateOpen] = React.useState(false); + const [trigger, updateTrigger] = React.useState(false); return ( - + + + updateTrigger(true)} + onMouseLeave={() => { + setTimeout(() => updateTrigger(false), 300); + }} + /> + + updateOpen(true)} onMouseLeave={() => updateOpen(false)}> {chunk(emojiList, elesPerRow).map((line, i) => (
{line.map((emoji) => (
handleEmojiClick(payload, emoji, ele).then(() => updateFinish(true))} + onClick={() => handleEmojiClick(payload, emoji, ele).then(() => { + updateOpen(false); + updateTrigger(false); + })} > {emoji}
@@ -62,9 +71,7 @@ function Reaction({ payload, ele }) { updateFocus(true)} onBlur={() => updateFocus(false)}>
- } - > - +
); } @@ -72,9 +79,9 @@ function Reaction({ payload, ele }) { const reactionPage = new AutoloadPage('reactionPage', () => { const canUseReaction = $('[data-op="react"]').length > 0; $('[data-op="react"]').each((i, e) => { - ReactDOM.createRoot(e).render( - , - ); + ReactDOM.createRoot(e).render( + + ); }); $(document).on('click', '.reaction', async (e) => { if (!canUseReaction) { diff --git a/packages/ui-default/components/notification/index.ts b/packages/ui-default/components/notification/index.ts index a9e878023..5f91e54cb 100644 --- a/packages/ui-default/components/notification/index.ts +++ b/packages/ui-default/components/notification/index.ts @@ -1,17 +1,43 @@ -import { Intent, OverlayToaster, Position } from '@blueprintjs/core'; +import { createTheme, MantineProvider, Notification as MantineNotification } from '@mantine/core'; +import { Notifications, notifications } from '@mantine/notifications'; import $ from 'jquery'; +import React from 'react'; import { tpl, zIndexManager } from 'vj/utils/base'; -const ToasterContainer = document.createElement('div'); -ToasterContainer.style.position = 'fixed'; -ToasterContainer.style.bottom = '0px'; -ToasterContainer.style.width = '100%'; -ToasterContainer.style.zIndex = '9999'; -document.body.append(ToasterContainer); +const colorWhite = { + color: 'var(--mantine-color-white)', +} as const; -const AppToaster = OverlayToaster.create( - { position: Position.BOTTOM_LEFT, usePortal: false }, - { container: ToasterContainer }, +const theme = createTheme({ + components: { + Notification: MantineNotification.extend({ + classNames: { + closeButton: 'mantine-notifications-close-button', + }, + styles: { + root: { + backgroundColor: 'var(--notification-color, var(--mantine-primary-color-filled))', + paddingInlineStart: 'var(--mantine-spacing-xs)', + }, + title: { + ...colorWhite, + fontSize: 'var(--mantine-font-size-md)', + }, + icon: { + fontSize: '24px', + marginInlineEnd: 'var(--mantine-spacing-xs)', + }, + description: colorWhite, + closeButton: colorWhite, + }, + }), + }, +}); + +document.body.append(tpl( + React.createElement(MantineProvider, { theme }, + React.createElement(Notifications, { position: 'bottom-left', zIndex: 99999 }), + ), true), ); interface NotificationOptions { @@ -66,20 +92,44 @@ export default class Notification { setTimeout(() => this.$n.remove(), 200); } - static async success(message: string, duration?: number) { - return (await AppToaster).show({ message, timeout: duration, intent: Intent.SUCCESS }); + static success(message: string, duration?: number) { + return notifications.show({ + title: message, + color: '#238551', + message: '', + icon: React.createElement('i', { className: 'icon icon-check' }), + autoClose: duration, + }); } - static async info(message: string, duration?: number) { - return (await AppToaster).show({ message, timeout: duration, intent: Intent.PRIMARY }); + static info(message: string, duration?: number) { + return notifications.show({ + title: message, + color: '#2d72d2', + message: '', + icon: React.createElement('i', { className: 'icon icon-info--circle' }), + autoClose: duration, + }); } - static async warn(message: string, duration?: number) { - return (await AppToaster).show({ message, timeout: duration, intent: Intent.WARNING }); + static warn(message: string, duration?: number) { + return notifications.show({ + title: message, + color: '#fbb360', + message: '', + icon: React.createElement('i', { className: 'icon icon-warning' }), + autoClose: duration, + }); } - static async error(message: string, duration?: number) { - return (await AppToaster).show({ message, timeout: duration, intent: Intent.DANGER }); + static error(message: string, duration?: number) { + return notifications.show({ + title: message, + color: '#cd4246', + message: '', + icon: React.createElement('i', { className: 'icon icon-close--circle' }), + autoClose: duration, + }); } } diff --git a/packages/ui-default/components/notification/notification.page.styl b/packages/ui-default/components/notification/notification.page.styl index c7e76e7ca..6be09a441 100644 --- a/packages/ui-default/components/notification/notification.page.styl +++ b/packages/ui-default/components/notification/notification.page.styl @@ -77,4 +77,8 @@ .notification { display: none !important } +} + +.mantine-notifications-close-button:hover { + background-color: rgba(0, 0, 0, 0.1) !important; } \ No newline at end of file diff --git a/packages/ui-default/components/problem/tag.page.styl b/packages/ui-default/components/problem/tag.page.styl index 2f12b1a15..67e88686c 100644 --- a/packages/ui-default/components/problem/tag.page.styl +++ b/packages/ui-default/components/problem/tag.page.styl @@ -19,11 +19,41 @@ margin-left: rem(3px) margin-bottom: rem(5px) line-height: 30px !important + display: inline-flex + flex-direction: row + align-items: center + border: none + border-radius: 2px + box-shadow: none + font-size: 14px + line-height: 18px + max-width: 100% + min-height: 30px + min-width: 30px + padding: 6px 8px + position: relative + background-color: rgba(143, 153, 168, 0.15) + color: #1c2127 &:hover text-decoration: none > span display: flex align-items: center + > * + flex-grow: 0 + flex-shrink: 0 + margin-right: 8px + > :last-child + margin-right: 0 + &:focus + outline: rgba(33, 93, 176, 0.752) solid 2px + outline-offset: 0 + -moz-outline-radius: 6px + &.interactive + cursor: pointer + &:hover + background: rgba(143, 153, 168, 0.3) + color: #111418 .problem__tag-link display: inline-block diff --git a/packages/ui-default/components/problemconfig/ProblemConfigForm.tsx b/packages/ui-default/components/problemconfig/ProblemConfigForm.tsx index fc92d4469..cc9e2516c 100644 --- a/packages/ui-default/components/problemconfig/ProblemConfigForm.tsx +++ b/packages/ui-default/components/problemconfig/ProblemConfigForm.tsx @@ -1,6 +1,4 @@ -import { - Card, InputGroup, Tag, -} from '@blueprintjs/core'; +import { Card, Text, TextInput } from '@mantine/core'; import { isEqual } from 'lodash'; import React from 'react'; import { useDispatch, useSelector } from 'react-redux'; @@ -16,16 +14,18 @@ function FileIOConfig() { const dispatch = useDispatch(); return ( - +
- .in/.out} + .in/.out} + rightSectionWidth={100} + rightSectionPointerEvents="none" value={filename || ''} onChange={(ev) => { dispatch({ type: 'problemconfig/updateFileIO', filename: ev.currentTarget.value }); }} - fill + style={{ width: '100%' }} />
@@ -41,7 +41,7 @@ function ExtraFilesConfig() { const dispatch = useDispatch(); return ( - +
- +
- {subtaskId !== -1 &&
setExpand((e) => !e)}> -   - {i18n('Subtask {0}', subtaskId)} - ev.stopPropagation()}> - - {i18n('Are you sure you want to delete this subtask?')} -
- -
-
} +
+ {subtaskId !== -1 && ( +
setExpand((e) => !e)}> + + {expand ? : } + + {i18n('Subtask {0}', subtaskId)} +
} -
    - {subtaskId !== -1 && } + + +
+ )} +
+ {subtaskId !== -1 && expand && ( + + )} {expand ? - : setExpand(false)} - icon="layers" - label={<> {cases.length} testcases.} - path={[0]} - />} + :
+ {cases.length} testcases. +
} {!cases.length && ( -
  • -
    - - {subtaskId === -1 - ? i18n('No testcase here') - : i18n('Drag and drop testcases here:')} -
    -
  • +
    + {subtaskId === -1 + ? i18n('No testcase here') + : i18n('Drag and drop testcases here:')} +
    )} - - +
    +
    ); } @@ -110,7 +101,7 @@ export function SubtaskConfigTree() { const ids = useSelector((s: RootState) => Object.values(s.config?.subtasks || []).map((i) => i.id), isEqual); const dispatch = useDispatch(); const store = useStore(); - function autoConfigure() { + const autoConfigure = React.useCallback(() => { const state = store.getState(); const subtasks = readSubtasksFromFiles(state.testdata.map((t) => t.name), state.config); const cases = subtasks.reduce((a, b) => a.concat(b.cases), []); @@ -122,29 +113,34 @@ export function SubtaskConfigTree() { type: 'problemconfig/delTestcases', cases, }); - } + }, [dispatch, store]); + const rootNodes = React.useMemo(() => [ + { id: 'auto', label: i18n('Auto configure'), type: 'action' as const }, + { id: 'global', label: i18n('Global settings'), type: 'global' as const }, + ...ids.map((id) => ({ id: `sub-${id}`, label: i18n('Subtask {0}', id), type: 'subtask' as const, subtaskId: id })), + { id: 'add', label: i18n('Add new subtask'), type: 'add' as const }, + ], [ids]); + return ( -
    -
      -
    • -
      -   - {i18n('Auto configure')} -
      -
    • - - {ids.map((id) => )} -
    • dispatch({ type: 'problemconfig/addSubtask' })} - > -
      -   - {i18n('Add new subtask')} -
      -
    • -
    -
    + { + const n: any = node; + if (n.type === 'action') { + return
    + {i18n('Auto configure')} +
    ; + } + if (n.type === 'global') return ; + if (n.type === 'add') { + return
    dispatch({ type: 'problemconfig/addSubtask' })}> + {i18n('Add new subtask')} +
    ; + } + return ; + }} + /> ); } @@ -156,11 +152,9 @@ export function ProblemConfigTree() {
    -
    -
      - - -
    +
    + +
    diff --git a/packages/ui-default/components/problemconfig/ProblemType.tsx b/packages/ui-default/components/problemconfig/ProblemType.tsx index 240b04a76..bbf69575d 100644 --- a/packages/ui-default/components/problemconfig/ProblemType.tsx +++ b/packages/ui-default/components/problemconfig/ProblemType.tsx @@ -1,6 +1,4 @@ -import { - Card, Classes, Switch, Tab, Tabs, TabsExpander, -} from '@blueprintjs/core'; +import { Card, SegmentedControl, Switch } from '@mantine/core'; import React, { useEffect } from 'react'; import { useDispatch, useSelector } from 'react-redux'; import { i18n } from 'vj/utils'; @@ -26,158 +24,141 @@ export default function ProblemType() { }, [checker]); return ( - - - {i18n('Problem Type')} - - +
    +
    {i18n('Problem Type')}
    + +
    + + {Type === 'default' && ( + <> +
    +
    {i18n('CheckerType')}
    + { dispatch({ type: 'CONFIG_FORM_UPDATE', key: 'checker_type', value }); if (value === 'testlib' && !category) setCategory('custom'); }} - renderActiveTabPanelOnly - > - {i18n('CheckerType')} - - - - { - dispatch({ type: 'CONFIG_FORM_UPDATE', key: 'checker_type', value: checkerType === 'strict' ? 'default' : 'strict' }); - }} - /> - -
    - )} - /> - - - - - {category === 'preset' - ? - - - : } - - )} - /> - - - - - - - )} - /> -
    + data={[ + { label: i18n('default'), value: 'default' }, + { label: 'testlib', value: 'testlib' }, + { label: 'other', value: 'other' }, + ]} + /> + + + {((['strict', 'default'].includes(checkerType) || !checkerType)) && ( + { + dispatch({ type: 'CONFIG_FORM_UPDATE', key: 'checker_type', value: checkerType === 'strict' ? 'default' : 'strict' }); + }} + /> )} - /> - - - - )} - /> - - - - dispatch(({ type: 'CONFIG_FORM_UPDATE', key: 'num_processes', value: +ev.currentTarget.value }))} - className="textbox" - /> - - - )} - /> - - - { - dispatch({ type: 'CONFIG_FORM_UPDATE', key: 'subType', value: subType === 'multi' ? 'single' : 'multi' }); + + dispatch(({ type: 'CONFIG_FORM_UPDATE', key: 'filename', value: ev.currentTarget.value }))} - className="textbox" - /> + className="select" + > + + + + {category === 'preset' + ? + + + : } )} - /> - {i18n('Unsupported configure this type of problem. Please refer to the documentation.')}

    )} - /> - + + {(!['strict', 'default'].includes(checkerType) && checkerType && checkerType !== 'testlib') && ( +
    + + + + +
    + )} + + )} + + {Type === 'interactive' && ( +
    + +
    + )} + + {Type === 'communication' && ( +
    + + + dispatch(({ type: 'CONFIG_FORM_UPDATE', key: 'num_processes', value: +ev.currentTarget.value }))} + className="textbox" + /> + +
    + )} + + {Type === 'submit_answer' && ( +
    + + { + dispatch({ type: 'CONFIG_FORM_UPDATE', key: 'subType', value: subType === 'multi' ? 'single' : 'multi' }); + }} + /> + + + dispatch(({ type: 'CONFIG_FORM_UPDATE', key: 'filename', value: ev.currentTarget.value }))} + className="textbox" + /> + +
    + )} + + {Type === 'objective' && ( +

    {i18n('Unsupported configure this type of problem. Please refer to the documentation.')}

    + )}
    ); diff --git a/packages/ui-default/components/problemconfig/index.tsx b/packages/ui-default/components/problemconfig/index.tsx index f95338e9c..ec5e29d37 100644 --- a/packages/ui-default/components/problemconfig/index.tsx +++ b/packages/ui-default/components/problemconfig/index.tsx @@ -1,8 +1,9 @@ -import { - Button, Dialog, DialogBody, DialogFooter, Tab, Tabs, -} from '@blueprintjs/core'; +import { Button, Tabs } from '@mantine/core'; +import { Allotment } from 'allotment'; +import { ContextMenuProvider } from 'mantine-contextmenu'; import React from 'react'; -import { useSelector } from 'react-redux'; +import { useSelector, useStore } from 'react-redux'; +import { confirm } from 'vj/components/dialog/index'; import { i18n } from 'vj/utils'; import ProblemConfigEditor from './ProblemConfigEditor'; import ProblemConfigForm from './ProblemConfigForm'; @@ -15,40 +16,74 @@ interface Props { export default function ProblemConfig(props: Props) { const [selected, setSelected] = React.useState('basic'); - const [open, setOpen] = React.useState(false); + const [container, setContainer] = React.useState(null); + const [tabs, setTabs] = React.useState(null); + const [editor, setEditor] = React.useState(null); + const store = useStore(); const valid = useSelector((state: RootState) => state.config.__valid); const errors = useSelector((state: RootState) => state.config.__errors, (a, b) => JSON.stringify(a) === JSON.stringify(b)); - React.useEffect(() => { - if (valid) setOpen(false); - }, [valid]); - return (<> -
    -
    - -
    -
    - (t !== 'errors' && setSelected(t.toString()))} selectedTabId={valid ? selected : 'errors'}> - } /> - } /> - {errors.map((i) => (
    {i}
    ))}
    } - /> - -
    + const handleSave = React.useCallback(async () => { + if (valid) props.onSave(); + else if (await confirm(i18n('Errors detected in the config. Confirm save?'))) props.onSave(); + }, [valid, props.onSave]); + + React.useEffect(() => { + if (!container) return () => { }; + container.style.height ||= '600px'; + const callback = () => { + setTimeout(() => { + const target = Math.max(container.clientHeight, tabs?.clientHeight || 0); + container.style.height = `${target}px`; + if (editor) editor.layout(); + }, 50); + }; + const dispose = store.subscribe(callback); + window.addEventListener('resize', callback); + return () => { + dispose(); + window.removeEventListener('resize', callback); + }; + }, [container, tabs, store]); + + return +
    + + +
    + +
    +
    + + (t !== 'errors' && t && setSelected(t.toString()))} + > +
    + + {i18n('Basic')} + {i18n('Subtasks')} + {errors.length ? `Errors(${errors.length})` : 'No Errors'} + + +
    + + + + + + + +
    {errors.map((i) => (
    {i}
    ))}
    +
    +
    +
    +
    - setOpen(false)}> - -

    {i18n('Errors detected in the config. Confirm save?')}

    -
    - - - - } /> -
    - - ); +
    ; } diff --git a/packages/ui-default/components/problemconfig/reducer/config.ts b/packages/ui-default/components/problemconfig/reducer/config.ts index b054ea561..b3efc9c73 100644 --- a/packages/ui-default/components/problemconfig/reducer/config.ts +++ b/packages/ui-default/components/problemconfig/reducer/config.ts @@ -138,17 +138,21 @@ export default function reducer(state = { case 'problemconfig/moveTestcases': { const testcases = action.payload.cases; const subtasks = cloneDeep(state.subtasks); + const targetCases = action.payload.target === -1 + ? state.__cases + : (subtasks.find((s) => s.id === action.payload.target)?.cases || []); + const toAdd = testcases.filter((tc) => !targetCases.find((j) => j.input === tc.input && j.output === tc.output)); const __cases = action.payload.source === -1 ? state.__cases.filter((i) => !testcases.find((j) => i.input === j.input && i.output === j.output)) : action.payload.target === -1 - ? sortFiles([...state.__cases, ...testcases], 'input') + ? sortFiles([...state.__cases, ...toAdd], 'input') : state.__cases; for (const key in subtasks) { const subtask = subtasks[key]; if (subtask.id === action.payload.source) { subtask.cases = subtask.cases.filter((i) => !testcases.find((j) => i.input === j.input && i.output === j.output)); } else if (subtask.id === action.payload.target) { - subtask.cases = sortFiles([...subtask.cases, ...testcases], 'input'); + subtask.cases = sortFiles([...subtask.cases, ...toAdd], 'input'); } } return { ...state, subtasks, __cases }; @@ -158,8 +162,10 @@ export default function reducer(state = { return { ...state, __cases: state.__cases.filter((i) => !testcases.find((j) => i.input === j.input && i.output === j.output)) }; } case 'problemconfig/deleteSubtask': { + const currentCases = state.subtasks.find((i) => i.id === action.id)?.cases || []; const subtasks = state.subtasks.filter((i) => i.id !== action.id); - return { ...state, subtasks }; + const add = currentCases.filter((i) => !subtasks.find((j) => j.cases.find((k) => k.input === i.input && k.output === i.output))); + return { ...state, subtasks, __cases: sortFiles([...state.__cases, ...add], 'input') }; } default: return state; diff --git a/packages/ui-default/components/problemconfig/tree/AddTestcase.tsx b/packages/ui-default/components/problemconfig/tree/AddTestcase.tsx index 4dfb5aa74..7a2b6a53f 100644 --- a/packages/ui-default/components/problemconfig/tree/AddTestcase.tsx +++ b/packages/ui-default/components/problemconfig/tree/AddTestcase.tsx @@ -1,7 +1,5 @@ import { readSubtasksFromFiles } from '@hydrooj/common'; -import { - Button, Classes, Dialog, DialogBody, DialogFooter, Icon, -} from '@blueprintjs/core'; +import { Button, Modal, Text } from '@mantine/core'; import React, { useEffect, useRef } from 'react'; import { useSelector, useStore } from 'react-redux'; import { i18n } from 'vj/utils'; @@ -83,14 +81,14 @@ export function AddTestcase() { } return (<> -
  • -
    -   - {i18n('Auto detect')} -
    -
  • -
  • + {i18n('Auto detect')} + +
    { setInput(''); setOutput(''); @@ -99,47 +97,41 @@ export function AddTestcase() { setOpen(true); }} > -
    -   - {i18n('Add testcase')} -
    -
  • - setOpen(false)}> - -
    -
    - setInput(e)} - placeholder={i18n('Input')} - value={input || ''} - /> -
    -
    - setOutput(e)} - placeholder={i18n('Output')} - value={input || ''} - /> -
    + {i18n('Add testcase')} +
    + setOpen(false)} + title={i18n('Add testcase')} + styles={{ body: { overflow: 'visible' }, content: { overflow: 'visible' } }} + > +
    +
    + setInput(e)} + placeholder={i18n('Input')} + value={input || ''} + />
    - - } - /> -
    +
    + setOutput(e)} + placeholder={i18n('Output')} + value={output || ''} + /> +
    + +
    + +
    + ); } diff --git a/packages/ui-default/components/problemconfig/tree/SubtaskSettings.tsx b/packages/ui-default/components/problemconfig/tree/SubtaskSettings.tsx index c2c095404..db132d60d 100644 --- a/packages/ui-default/components/problemconfig/tree/SubtaskSettings.tsx +++ b/packages/ui-default/components/problemconfig/tree/SubtaskSettings.tsx @@ -1,10 +1,6 @@ import { CustomSelectAutoComplete } from '@hydrooj/components'; import { parseMemoryMB, parseTimeMS } from '@hydrooj/utils/lib/common'; -import { - Button, Classes, ControlGroup, - Dialog, DialogBody, DialogFooter, - Icon, InputGroup, Tag, -} from '@blueprintjs/core'; +import { Button, Group, Modal, Text, TextInput } from '@mantine/core'; import { isEqual } from 'lodash'; import React, { useEffect } from 'react'; import { useDispatch, useSelector } from 'react-redux'; @@ -61,83 +57,71 @@ export function SubtaskSettings(props: SubtaskSettingsProps) { } return (<> - setOpen(false)}> - - - } - rightElement={ms} - onChange={(ev) => setTime(ev.currentTarget.value ? `${ev.currentTarget.value}ms` : '')} - placeholder={parseTimeMS(props.time, false).toString() || '1000'} - value={ctime ? parseTimeMS(ctime, false).toString() || '' : ''} - /> - } - rightElement={MB} - onChange={(ev) => setMemory(ev.currentTarget.value ? `${ev.currentTarget.value}MB` : '')} - placeholder={parseMemoryMB(props.memory, false).toString() || '256'} - value={cmemory ? parseMemoryMB(cmemory, false).toString() || '' : ''} - /> - } - onChange={(ev) => setScore(+ev.target.value || 0)} - placeholder="Score" - type="number" - value={cscore.toString()} - /> - - - } /> - - setDepsOpen(false)}> - - ({ _id: i, name: `${i18n('Subtask {0}', i)}` }))} - setSelectItems={cdeps.split(',').map((i) => i.trim()).filter((i) => +i).map((i) => +i)} - onChange={(items) => setDeps(items)} - placeholder="dependencies" - multi + setOpen(false)} title={i18n('Set time and memory limits')}> + + } + rightSection={ms} + rightSectionWidth={60} + onChange={(ev) => setTime(ev.currentTarget.value ? `${ev.currentTarget.value}ms` : '')} + placeholder={parseTimeMS(props.time, false).toString() || '1000'} + value={ctime ? parseTimeMS(ctime, false).toString() || '' : ''} /> - - } /> - -
  • setOpen(true)}> -
    - - -    - {time || props.time || '1s'} - -    - {memory || props.memory || '256m'} - - {' '} - {score || 0} + } + rightSection={MB} + rightSectionWidth={60} + onChange={(ev) => setMemory(ev.currentTarget.value ? `${ev.currentTarget.value}MB` : '')} + placeholder={parseMemoryMB(props.memory, false).toString() || '256'} + value={cmemory ? parseMemoryMB(cmemory, false).toString() || '' : ''} + /> + } + onChange={(ev) => setScore(+ev.target.value || 0)} + placeholder="Score" + type="number" + value={cscore.toString()} + /> + +
    +
    -
  • -
  • setDepsOpen(true)}> -
    - - -    - {i18n('Dependencies')}: {deps.length ? deps.join(', ') : i18n('(None)')} + + setDepsOpen(false)} title={i18n('Set dependencies')}> + ({ _id: i, name: `${i18n('Subtask {0}', i)}` }))} + setSelectItems={cdeps.split(',').map((i) => i.trim()).filter((i) => +i).map((i) => +i)} + onChange={(items) => setDeps(items)} + placeholder="dependencies" + multi + /> +
    +
    -
  • -
  • -
    - - -    - {i18n('Scoring method')} - - - -
    -
  • + +
    setOpen(true)}> + + {time || props.time || '1s'}   + + {memory || props.memory || '256m'}   + + {score || 0} +
    +
    setDepsOpen(true)}> + + {i18n('Dependencies')}: {deps.length ? deps.join(', ') : i18n('(None)')} +
    +
    + + {i18n('Scoring method')} + + + +
    ); } @@ -163,36 +147,34 @@ export function GlobalSettings() { setOpen(false); } return (<> - setOpen(false)}> - - - } - rightElement={ms} - onChange={(ev) => setTime(ev.currentTarget.value ? `${ev.currentTarget.value}ms` : '')} - placeholder="1000" - value={ctime ? parseTimeMS(ctime, false).toString() || '' : ''} - /> - } - rightElement={MB} - onChange={(ev) => setMemory(ev.currentTarget.value ? `${ev.currentTarget.value}MB` : '')} - placeholder="256" - value={cmemory ? parseMemoryMB(cmemory, false).toString() || '' : ''} - /> - - - } /> - -
  • setOpen(true)}> -
    - -    - {time || '1s'} - - {' '} - {memory || '256MB'} + setOpen(false)} title={i18n('Set time and memory limits')}> + + } + rightSection={ms} + rightSectionWidth={60} + onChange={(ev) => setTime(ev.currentTarget.value ? `${ev.currentTarget.value}ms` : '')} + placeholder="1000" + value={ctime ? parseTimeMS(ctime, false).toString() || '' : ''} + /> + } + rightSection={MB} + rightSectionWidth={60} + onChange={(ev) => setMemory(ev.currentTarget.value ? `${ev.currentTarget.value}MB` : '')} + placeholder="256" + value={cmemory ? parseMemoryMB(cmemory, false).toString() || '' : ''} + /> + +
    +
    -
  • + +
    setOpen(true)}> + + {time || '1s'} + + {memory || '256MB'} +
    ); } diff --git a/packages/ui-default/components/problemconfig/tree/Testcase.tsx b/packages/ui-default/components/problemconfig/tree/Testcase.tsx index 64b45104b..3c849a59c 100644 --- a/packages/ui-default/components/problemconfig/tree/Testcase.tsx +++ b/packages/ui-default/components/problemconfig/tree/Testcase.tsx @@ -1,10 +1,10 @@ import { TestCaseConfig } from 'hydrooj'; -import { - ContextMenu, Menu, MenuItem, TreeNode, -} from '@blueprintjs/core'; +import { Text } from '@mantine/core'; import { omit } from 'lodash'; +import { useContextMenu } from 'mantine-contextmenu'; import React from 'react'; import { useDrag } from 'react-dnd'; +import { useDispatch } from 'react-redux'; import { i18n } from 'vj/utils'; interface TestcaseNodeProps { @@ -20,37 +20,35 @@ interface TestcaseNodeProps { export function TestcaseNode(props: TestcaseNodeProps) { const { - c, selected, onClick, subtaskIds, subtaskId, + c, selected, onClick, subtaskId, } = props; + let display = `${c.input} / ${c.output}`; + const minlength = Math.min(c.input.length, c.output.length); + for (let i = minlength; i >= 0; i--) { + const prefix = c.input.slice(0, i); + if (c.input.startsWith(prefix) && c.output.startsWith(prefix)) { + display = `${prefix}(${c.input.substring(i)}/${c.output.substring(i)})`; + break; + } + } return ( - - - {subtaskIds.filter((i) => i !== subtaskId).map((i) => ( - - ))} - {subtaskIds.length <= 1 && ( - - )} - - - } + style={{ + paddingLeft: 22, + display: 'flex', + alignItems: 'center', + cursor: 'pointer', + background: selected ? 'var(--mantine-color-blue-light, #ebf3ff)' : 'transparent', + borderRadius: 4, + fontFamily: 'var(--mantine-font-family-monospace)', + }} > -  {c.input} / {c.output}} - path={[0]} - /> - + {display} + ); } @@ -61,8 +59,11 @@ interface TestcaseGroupProps extends Omit { export function TestcaseGroup(props: TestcaseGroupProps) { const { - cases, subtaskId, onClick, index, + cases, subtaskId, subtaskIds, onClick, index, } = props; + const dispatch = useDispatch(); + const { showContextMenu } = useContextMenu(); + const moveTargets = subtaskIds.filter((i) => i !== subtaskId); const [collected, drag] = useDrag(() => ({ type: 'cases', collect: (monitor) => ({ @@ -71,7 +72,25 @@ export function TestcaseGroup(props: TestcaseGroupProps) { canDrag: props.selected, item: { cases, subtaskId }, }), [JSON.stringify(cases), subtaskId]); - return
    + return
    ({ + key: `move-${id}`, + icon: , + title: `${i18n('Move to')} ${i18n('Subtask {0}', id)}`, + onClick: () => dispatch({ + type: 'problemconfig/moveTestcases', + payload: { target: id, source: subtaskId, cases }, + }), + })) + : [{ key: 'no-target', title: i18n('No target available'), onClick: () => { }, disabled: true }], + )} + style={{ opacity: collected.isDragging ? 0.5 : 1 }} + > {cases.map((c, id) => ( ))} diff --git a/packages/ui-default/components/upload.tsx b/packages/ui-default/components/upload.tsx index ce943ac80..34d2f67ec 100644 --- a/packages/ui-default/components/upload.tsx +++ b/packages/ui-default/components/upload.tsx @@ -1,4 +1,4 @@ -import { Classes } from '@blueprintjs/core'; +import { MantineProvider, Progress } from '@mantine/core'; import $ from 'jquery'; import React from 'react'; import { Dialog } from 'vj/components/dialog/index'; @@ -19,36 +19,40 @@ interface UploadOptions { filenameCallback?: (file: File) => string; } export default async function uploadFiles(endpoint = '', files: File[] | FileList = [], options: UploadOptions = {}) { - const dialog = new Dialog({ - $body: $(tpl(<> + let fileLabel = ''; + let uploadLabel = ''; + let fileProgress = 0; + let uploadProgress = 0; + let render = () => { }; + + function ProgressDialog() { + const [, setRender] = React.useState(0); + React.useEffect(() => { + render = () => setRender((r) => r + 1); + }, []); + return
    -
    -
    -
    + >{uploadLabel}
    +
    -
    -
    -
    - )), + >{fileLabel}
    + + ; + } + + const dialog = new Dialog({ + $body: $(tpl(, true)), }); 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]; @@ -58,20 +62,22 @@ export default async function uploadFiles(endpoint = '', files: File[] | FileLis if (options.type) data.append('type', options.type); data.append('operation', 'upload_file'); await request.postFile(endpoint, data, { - xhr() { + xhr() { // eslint-disable-line 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); + uploadLabel = `[${+i + 1}/${files.length}] ${file.name} `; + uploadProgress = Math.round((+i + 1) / files.length * 100); + fileLabel = i18n('Uploading... ({0}%)', 0); + fileProgress = 0; + render(); }); 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}% `); + if (percentComplete === 100) fileLabel = i18n('Processing...'); + else fileLabel = i18n('Uploading... ({0}%)', percentComplete); + fileProgress = percentComplete; + render(); } }, false); return xhr; diff --git a/packages/ui-default/package.json b/packages/ui-default/package.json index c2e18a676..b83b76431 100644 --- a/packages/ui-default/package.json +++ b/packages/ui-default/package.json @@ -11,7 +11,6 @@ "cli": false }, "devDependencies": { - "@blueprintjs/core": "^6.3.2", "@fontsource/dm-mono": "^5.2.7", "@fontsource/fira-code": "^5.2.7", "@fontsource/inconsolata": "^5.2.8", @@ -21,8 +20,11 @@ "@fontsource/source-code-pro": "^5.2.7", "@fontsource/ubuntu-mono": "^5.2.8", "@hydrooj/utils": "workspace:^", + "@mantine/core": "^8.3.6", + "@mantine/hooks": "^8.3.6", + "@mantine/notifications": "^8.3.6", "@sentry/browser": "^10.22.0", - "@sentry/webpack-plugin": "^4.5.0", + "@sentry/webpack-plugin": "^4.6.0", "@simplewebauthn/browser": "13.2.2", "@svgr/webpack": "^8.1.0", "@types/html-to-text": "^9.0.4", @@ -34,8 +36,8 @@ "@types/nunjucks": "^3.2.6", "@types/pickadate": "^3.5.35", "@types/qrcode": "^1.5.6", - "@types/react": "^18.3.26", - "@types/react-dom": "^18.3.7", + "@types/react": "^19.2.2", + "@types/react-dom": "^19.2.2", "@types/redux-logger": "^3.0.13", "@types/uuid": "^11.0.0", "@types/webpack-env": "^1.18.8", @@ -65,6 +67,7 @@ "jquery-scroll-lock": "^3.1.3", "jquery.easing": "^1.4.1", "jquery.transit": "^0.9.12", + "mantine-contextmenu": "^8.2.0", "matchmedia-polyfill": "^0.3.2", "md-editor-rt": "^6.1.0", "mini-css-extract-plugin": "^2.9.4", @@ -80,17 +83,17 @@ "p-queue": "^9.0.0", "pickadate": "^3.6.4", "postcss-loader": "7.3.4", + "postcss-preset-mantine": "^1.18.0", "prism-themes": "^1.9.0", "prismjs": "^1.30.0", "prop-types": "^15.8.1", "qface": "^1.4.1", "qrcode": "^1.5.4", - "react": "^18.3.1", + "react": "^19.2.0", "react-colorful": "^5.6.1", "react-dnd": "^16.0.1", "react-dnd-html5-backend": "^16.0.1", - "react-dom": "^18.3.1", - "react-query": "^3.39.3", + "react-dom": "^19.2.0", "react-redux": "^9.2.0", "reconnecting-websocket": "^4.4.0", "redux": "^5.0.1", @@ -98,7 +101,7 @@ "redux-promise-middleware": "^6.2.0", "redux-thunk": "^3.1.0", "rupture": "^0.7.1", - "sass": "^1.93.2", + "sass": "^1.93.3", "sass-loader": "^16.0.6", "schemastery": "^3.17.1", "shorty.js": "^1.0.1", @@ -117,7 +120,7 @@ "webpack": "^5.102.1", "webpack-bundle-analyzer": "^4.10.2", "webpack-dev-server": "^5.2.2", - "webpack-manifest-plugin": "^5.0.1", + "webpack-manifest-plugin": "^6.0.1", "webpackbar": "^7.0.0" }, "dependencies": { diff --git a/packages/ui-default/pages/problem_config.page.styl b/packages/ui-default/pages/problem_config.page.styl index 15d4d7b50..6ebbe0384 100644 --- a/packages/ui-default/pages/problem_config.page.styl +++ b/packages/ui-default/pages/problem_config.page.styl @@ -1,50 +1,14 @@ .page--problem_config + .main + .row + max-width: 100vw !important; + .textbox,.select margin: 0 - - .bp6-tab-panel - margin-top: 5px - - .bp6-dialog - font-family: var(--code-font-family) - background: #fff - - .autocomplete-container - font-size: 0.875rem - color: #666 - - .bp6-dialog-footer-fixed - border-top: 0 - - .button - margin: 0 input::placeholder color: rgba(95,107,124,.6)!important - .bp6-input-group - input - appearance: none - display: block - width: 100% - font-size: 0.875rem - height: 2.1875rem - line-height: 1.2 - padding: 0.3125rem; - border: 1px solid #ddd - color: inherit - border-radius: 0 - outline: 2px solid transparent - transition: outline-color 0.2s, border-color 0.2s - transition-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1) - box-shadow: none - - .bp6-tag.bp6-minimal - background: transparent - - .bp6-input-action - pointer-events: none - .problem-config-form font-family: var(--code-font-family) @@ -55,4 +19,3 @@ .hover-row.status-bar display: none - \ No newline at end of file diff --git a/packages/ui-default/pages/problem_config.page.tsx b/packages/ui-default/pages/problem_config.page.tsx index 93f750478..ba8ab02d2 100644 --- a/packages/ui-default/pages/problem_config.page.tsx +++ b/packages/ui-default/pages/problem_config.page.tsx @@ -1,4 +1,5 @@ import { normalizeSubtasks, readSubtasksFromFiles, SubtaskType } from '@hydrooj/common'; +import { MantineProvider } from '@mantine/core'; import $ from 'jquery'; import yaml from 'js-yaml'; import React from 'react'; @@ -114,6 +115,7 @@ const page = new NamedPage('problem_config', () => { type: 'CONFIG_LOAD', payload: request.get(''), }); + let autoConfigTriggered = false; const unsubscribe = store.subscribe(() => { // TODO set yaml schema const state = store.getState(); @@ -129,7 +131,10 @@ const page = new NamedPage('problem_config', () => { delete state.config.cases; delete state.config.score; } - if (state.config.subtasks?.length) return; + if (state.config.subtasks?.length || autoConfigTriggered) { + autoConfigTriggered = true; + return; + } const testdata = (state.testdata || []).map((i) => i.name); unsubscribe(); const subtasks = readSubtasksFromFiles(testdata, state.config); @@ -138,11 +143,11 @@ const page = new NamedPage('problem_config', () => { subtasks: normalizeSubtasks(subtasks, (i) => i, state.config.time, state.config.memory, true), }); }); - createRoot(document.getElementById('ProblemConfig')!).render( + createRoot(document.getElementById('ProblemConfig')!).render( uploadConfig(store.getState().config)} /> - , - ); + + ); } mountComponent(); diff --git a/packages/ui-default/pages/problem_main.page.tsx b/packages/ui-default/pages/problem_main.page.tsx index 0fe3bdeeb..26cb83ecd 100644 --- a/packages/ui-default/pages/problem_main.page.tsx +++ b/packages/ui-default/pages/problem_main.page.tsx @@ -373,7 +373,7 @@ function ProblemSelectionDisplay(props) { // eslint-disable-line }, [pids]); return (<> - setDialogOpen(true)}> + setDialogOpen(true)}> {' '}{i18n('{0} problem(s) selected', pids.length)} diff --git a/packages/ui-default/postcss.config.js b/packages/ui-default/postcss.config.js index a47ef4f95..835dc13ec 100644 --- a/packages/ui-default/postcss.config.js +++ b/packages/ui-default/postcss.config.js @@ -1,5 +1,15 @@ module.exports = { plugins: { autoprefixer: {}, + 'postcss-preset-mantine': {}, + 'postcss-simple-vars': { + variables: { + 'mantine-breakpoint-xs': '36em', + 'mantine-breakpoint-sm': '48em', + 'mantine-breakpoint-md': '62em', + 'mantine-breakpoint-lg': '75em', + 'mantine-breakpoint-xl': '88em', + }, + }, }, }; diff --git a/packages/ui-default/templates/contest_detail.html b/packages/ui-default/templates/contest_detail.html index db28e1cc3..5a022e348 100644 --- a/packages/ui-default/templates/contest_detail.html +++ b/packages/ui-default/templates/contest_detail.html @@ -12,14 +12,14 @@

    {{ tdoc.title }}

    - {{ _(model.contest.statusText(tdoc, tsdoc)) }} - {{ _(model.contest.RULES[tdoc.rule].TEXT) }} - {{ _('Start at') }}: {{ contest.render_time(tsdoc.startAt or tdoc.beginAt) }} - {{ contest.render_duration(tdoc) }} {{ _('hour(s)') }} - {{ _('Host') }}: {{ user.render_inline(udict[tdoc.owner], badge=false) }} - {{ tdoc['attend']|default(0) }} + {{ _(model.contest.statusText(tdoc, tsdoc)) }} + {{ _(model.contest.RULES[tdoc.rule].TEXT) }} + {{ _('Start at') }}: {{ contest.render_time(tsdoc.startAt or tdoc.beginAt) }} + {{ contest.render_duration(tdoc) }} {{ _('hour(s)') }} + {{ _('Host') }}: {{ user.render_inline(udict[tdoc.owner], badge=false) }} + {{ tdoc['attend']|default(0) }} {% if tsdoc.attend %} - {{ _('Attended') }} + {{ _('Attended') }} {% endif %}
    diff --git a/packages/ui-default/templates/problem_detail.html b/packages/ui-default/templates/problem_detail.html index 79ae3e5b8..d68c08486 100644 --- a/packages/ui-default/templates/problem_detail.html +++ b/packages/ui-default/templates/problem_detail.html @@ -90,26 +90,26 @@
      {% if not tdoc %} - {{ _('ID') }}: {{ pdoc.docId }} + {{ _('ID') }}: {{ pdoc.docId }} {% endif %} - {{ _('problem_type.' + pdoc.config.type).format(pdoc.config.subType) }} + {{ _('problem_type.' + pdoc.config.type).format(pdoc.config.subType) }} {% if pdoc.config.type == 'default' and pdoc.config.subType %} - {{ _('File IO: {0}').format(pdoc.config.subType) }} + {{ _('File IO: {0}').format(pdoc.config.subType) }} {% endif %} {% if pdoc.config.type != 'objective' and pdoc.config.type != 'submit_answer' %} - {{ pdoc.config.timeMax if pdoc.config.timeMax == pdoc.config.timeMin else pdoc.config.timeMin+'~'+pdoc.config.timeMax }}ms - {{ pdoc.config.memoryMax if pdoc.config.memoryMax == pdoc.config.memoryMin else pdoc.config.memoryMin+'~'+pdoc.config.memoryMax }}MiB + {{ pdoc.config.timeMax if pdoc.config.timeMax == pdoc.config.timeMin else pdoc.config.timeMin+'~'+pdoc.config.timeMax }}ms + {{ pdoc.config.memoryMax if pdoc.config.memoryMax == pdoc.config.memoryMin else pdoc.config.memoryMin+'~'+pdoc.config.memoryMax }}MiB {% endif %} {% if not tdoc %} - {{ _('Tried') }}: {{ pdoc.nSubmit }} - {{ _('Accepted') }}: {{ pdoc.nAccept if ((not tdoc) or showAccept) else '?' }} - {{ _('Difficulty') }}: {{ pdoc['difficulty'] or lib.difficulty(pdoc.nSubmit, pdoc.nAccept) or _('(None)') }} - {{ _('Uploaded By') }}: {{ user.render_inline(udoc, badge=false) }} + {{ _('Tried') }}: {{ pdoc.nSubmit }} + {{ _('Accepted') }}: {{ pdoc.nAccept if ((not tdoc) or showAccept) else '?' }} + {{ _('Difficulty') }}: {{ pdoc['difficulty'] or lib.difficulty(pdoc.nSubmit, pdoc.nAccept) or _('(None)') }} + {{ _('Uploaded By') }}: {{ user.render_inline(udoc, badge=false) }} {% endif %} {% if pdoc.tag.length %} - {{ _('Tags') + '>' }} + {{ _('Tags') + '>' }} {%- for tag in pdoc.tag -%} - {{ tag }} + {{ tag }} {%- endfor -%} {% endif %}
    diff --git a/packages/ui-default/theme/bp6-compat.css b/packages/ui-default/theme/bp6-compat.css new file mode 100644 index 000000000..727d69a2a --- /dev/null +++ b/packages/ui-default/theme/bp6-compat.css @@ -0,0 +1,48 @@ +.bp6-progress-bar { + background: rgba(95, 107, 124, 0.2); + border-radius: 40px; + display: block; + height: 8px; + overflow: hidden; + position: relative; + width: 100%; +} + +.bp6-progress-bar .bp6-progress-meter { + background: linear-gradient(-45deg, rgba(255, 255, 255, 0.2) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.2) 50%, rgba(255, 255, 255, 0.2) 75%, transparent 75%); + background-color: rgba(95, 107, 124, 0.8); + background-size: 30px 30px; + border-radius: 40px; + height: 100%; + position: absolute; + transition: width 200ms cubic-bezier(0.4, 1, 0.75, 0.9); + width: 100%; +} + +.bp6-progress-bar.bp6-no-stripes .bp6-progress-meter { + background-image: none; +} + +.bp6-dark .bp6-progress-bar { + background: rgba(17, 20, 24, 0.5); +} + +.bp6-dark .bp6-progress-bar .bp6-progress-meter { + background-color: #8f99a8; +} + +.bp6-progress-bar.bp6-intent-primary .bp6-progress-meter { + background-color: #2d72d2; +} + +.bp6-progress-bar.bp6-intent-success .bp6-progress-meter { + background-color: #238551; +} + +.bp6-progress-bar.bp6-intent-warning .bp6-progress-meter { + background-color: #c87619; +} + +.bp6-progress-bar.bp6-intent-danger .bp6-progress-meter { + background-color: #cd4246; +} \ No newline at end of file diff --git a/packages/ui-default/theme/dark.styl b/packages/ui-default/theme/dark.styl index 5ef2fcd74..54f127dd0 100644 --- a/packages/ui-default/theme/dark.styl +++ b/packages/ui-default/theme/dark.styl @@ -212,10 +212,6 @@ $hover-background-color = #424242 .menu__link.highlight background: rgb(40, 11, 20) - .bp6-tag.bp6-minimal:not([class*="bp6-intent-"]) - background-color: rgba(87, 100, 117, .2) - color: #d9dfe7 - .typo table thead th background: #1e1e1e background-color: rgb(30, 30, 30) @@ -391,18 +387,6 @@ $hover-background-color = #424242 background-color: $main-background-color !important border-color: #3d3d3d !important - .bp6-tab - color: #aaa - - &[aria-disabled=true] - color: #555 - - .bp6-card,.bp6-dialog,.bp6-dialog-header,.bp6-dialog-footer - background-color: #1f1f1f !important - - .bp6-input-group input - border-color: #3d3d3d!important - &.hasjs .collapsed #status:not([data-status="7"]) .compiler-text-mask background-image: linear-gradient(to bottom, transparent, #000000 25px) diff --git a/packages/ui-default/theme/default.js b/packages/ui-default/theme/default.js index 1ce9815df..6e5c17597 100644 --- a/packages/ui-default/theme/default.js +++ b/packages/ui-default/theme/default.js @@ -4,7 +4,9 @@ import 'pickadate/lib/themes/classic.css'; import 'pickadate/lib/themes/classic.date.css'; import 'pickadate/lib/themes/classic.time.css'; import 'katex/dist/katex.min.css'; -import '@blueprintjs/core/lib/css/blueprint.css'; +import '@mantine/core/styles.css'; +import '@mantine/notifications/styles.css'; +import 'mantine-contextmenu/styles.css'; import '@fontsource/fira-code'; import '@fontsource/source-code-pro'; import '@fontsource/roboto-mono'; @@ -26,6 +28,7 @@ import 'vj/misc/nothing.styl'; import 'vj/components/editor/cmeditor.styl'; import 'vj/components/datepicker/datepicker.styl'; import './dark.styl'; +import './bp6-compat.css'; // load all page stylesheets const pageStyleReq = require.context('../', true, /\.page\.styl$/i);