Skip to content

Commit 3996f6f

Browse files
ui: drop blueprintjs 6 (#1073)
1 parent 4011432 commit 3996f6f

27 files changed

Lines changed: 760 additions & 648 deletions

packages/common/subtask.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { parseMemoryMB, parseTimeMS } from '@hydrooj/utils/lib/common';
1+
import { parseMemoryMB, parseTimeMS, sortFiles } from '@hydrooj/utils/lib/common';
22

33
export function convertIniConfig(ini: string) {
44
const f = ini.split('\n');
@@ -118,13 +118,16 @@ export function readSubtasksFromFiles(files: string[], config) {
118118
id: sid,
119119
};
120120
} else if (!subtask[sid].cases) subtask[sid].cases = [c];
121-
else subtask[sid].cases.push(c);
121+
else if (!subtask[sid].cases.find((i) => i.input === c.input && i.output === c.output)) {
122+
subtask[sid].cases.push(c);
123+
}
122124
break;
123125
}
124126
}
125127
if (match) break;
126128
}
127129
}
130+
for (const id in subtask) subtask[id].cases = sortFiles(subtask[id].cases, 'input');
128131
return Object.values(subtask);
129132
}
130133

packages/components/frontend/autocomplete/autocomplete.scss

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -75,12 +75,6 @@
7575
white-space: nowrap;
7676
text-overflow: ellipsis;
7777
}
78-
79-
&>span.bp6-icon>svg {
80-
font-size: 12px;
81-
cursor: pointer;
82-
padding: 4px;
83-
}
8478
}
8579

8680
.autocomplete-list {
@@ -104,26 +98,14 @@
10498
flex-grow: 1;
10599
}
106100

107-
&>span.bp6-icon>svg {
108-
color: transparent;
109-
}
110-
111101
&[aria-selected='true'] {
112102
background-color: #fafafa;
113103
font-weight: 600;
114-
115-
&>span.bp6-icon>svg {
116-
color: #1890ff;
117-
}
118104
}
119105

120106
&[data-focus='true'] {
121107
background-color: #e6f7ff;
122108
cursor: pointer;
123-
124-
&>span.bp6-icon>svg {
125-
color: currentColor;
126-
}
127109
}
128110
}
129111
}

packages/ui-default/components/datepicker/datepicker.styl

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
opacity .1s ease-in-out,
1717
max-height 0s,
1818
border-width 0s
19-
box-shadow: $data-picker-shadow
2019

2120
.picker__list
2221
padding: 0

packages/ui-default/components/discussion/history.page.tsx

Lines changed: 29 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
/* eslint-disable react-refresh/only-export-components */
2-
import { Popover } from '@blueprintjs/core';
2+
import { MantineProvider, Popover } from '@mantine/core';
33
import React from 'react';
44
import ReactDOM from 'react-dom/client';
5-
import { QueryClient, QueryClientProvider, useQuery } from 'react-query';
65
import TimeAgo from 'timeago-react';
76
import { InfoDialog } from 'vj/components/dialog';
87
import { AutoloadPage } from 'vj/misc/Page';
@@ -22,22 +21,31 @@ async function historyDialog(payload, time, uid) {
2221
}).open();
2322
}
2423

25-
const queryClient = new QueryClient();
26-
2724
function History({ payload }) {
2825
const [load, setLoad] = React.useState(false);
29-
const { isLoading, isError, data } = useQuery(['history', payload], async () => {
30-
const { history } = await request.get(`${payload}?all=1`);
31-
return history;
32-
}, { enabled: !!load });
33-
return (
34-
<Popover
35-
usePortal
36-
interactionKind="hover"
37-
position="bottom"
38-
onOpening={() => setLoad(true)}
39-
content={<ol className="menu">
40-
{(isLoading || isError) && (
26+
const [data, setData] = React.useState([]);
27+
const [isLoading, setLoading] = React.useState(true);
28+
const [error, setError] = React.useState('');
29+
30+
React.useEffect(() => {
31+
if (!load) return;
32+
setLoading(true);
33+
request.get(`${payload}?all=1`).then(({ history }) => {
34+
setData(history);
35+
}).catch((e) => {
36+
setError(e.message);
37+
}).finally(() => {
38+
setLoading(false);
39+
});
40+
}, [load]);
41+
42+
return <Popover onOpen={() => setLoad(true)}>
43+
<Popover.Target>
44+
<span>{i18n('Edited')} <span className="icon icon-expand_more"></span></span>
45+
</Popover.Target>
46+
<Popover.Dropdown>
47+
<ol className="menu">
48+
{(isLoading || error) && (
4149
<li className="menu__item">
4250
<a className="menu__link">
4351
{isLoading ? i18n('Loading...') : i18n('Loading failed.')}
@@ -53,21 +61,19 @@ function History({ payload }) {
5361
</a>
5462
</li>
5563
))}
56-
</ol>}
57-
>
58-
<span>{i18n('Edited')} <span className="icon icon-expand_more"></span></span>
59-
</Popover>
60-
);
64+
</ol>
65+
</Popover.Dropdown>
66+
</Popover>;
6167
}
6268

6369
const page = new AutoloadPage('discussionHistoryPage', () => {
6470
$('[data-discussion-history]').each((i, e) => {
6571
const url = $(e).data('raw-url');
6672
if (!url) return;
6773
ReactDOM.createRoot(e).render(
68-
<QueryClientProvider client={queryClient}>
74+
<MantineProvider>
6975
<History payload={url} />
70-
</QueryClientProvider>,
76+
</MantineProvider>,
7177
);
7278
});
7379
});

packages/ui-default/components/discussion/reaction.page.tsx

Lines changed: 22 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/* eslint-disable react-refresh/only-export-components */
22
import 'jquery.easing';
33

4-
import { Popover } from '@blueprintjs/core';
4+
import { MantineProvider, Popover } from '@mantine/core';
55
import $ from 'jquery';
66
import { chunk } from 'lodash';
77
import * as React from 'react';
@@ -36,21 +36,30 @@ function Reaction({ payload, ele }) {
3636
const emojiList: string[] = (UiContext.emojiList || '👍 👎 😄 😕 ❤️ 🤔 🤣 🌿 🍋 🕊️ 👀 🤡').split(' ');
3737
const elesPerRow = getRow(Math.sqrt(emojiList.length));
3838
const [focus, updateFocus] = React.useState(false);
39-
const [finish, updateFinish] = React.useState(false);
40-
if (finish) setTimeout(() => updateFinish(false), 1000);
39+
const [open, updateOpen] = React.useState(false);
40+
const [trigger, updateTrigger] = React.useState(false);
4141
return (
42-
<Popover
43-
usePortal
44-
interactionKind="hover"
45-
isOpen={finish ? false : (focus ? true : undefined)}
46-
content={<div>
42+
<Popover opened={trigger || open || focus}>
43+
<Popover.Target>
44+
<span
45+
className="icon icon-emoji"
46+
onMouseEnter={() => updateTrigger(true)}
47+
onMouseLeave={() => {
48+
setTimeout(() => updateTrigger(false), 300);
49+
}}
50+
/>
51+
</Popover.Target>
52+
<Popover.Dropdown onMouseEnter={() => updateOpen(true)} onMouseLeave={() => updateOpen(false)}>
4753
{chunk(emojiList, elesPerRow).map((line, i) => (
4854
<div className="row" key={+i} style={{ paddingBottom: 4, paddingTop: 4 }}>
4955
{line.map((emoji) => (
5056
<div
5157
key={emoji}
5258
className={`medium-${12 / elesPerRow} small-${12 / elesPerRow} columns popover-reaction-item`}
53-
onClick={() => handleEmojiClick(payload, emoji, ele).then(() => updateFinish(true))}
59+
onClick={() => handleEmojiClick(payload, emoji, ele).then(() => {
60+
updateOpen(false);
61+
updateTrigger(false);
62+
})}
5463
>
5564
{emoji}
5665
</div>
@@ -62,19 +71,17 @@ function Reaction({ payload, ele }) {
6271
<input name="emojiSuggest" onFocus={() => updateFocus(true)} onBlur={() => updateFocus(false)}></input>
6372
</div>
6473
</div>
65-
</div>}
66-
>
67-
<span className="icon icon-emoji"></span>
74+
</Popover.Dropdown>
6875
</Popover>
6976
);
7077
}
7178

7279
const reactionPage = new AutoloadPage('reactionPage', () => {
7380
const canUseReaction = $('[data-op="react"]').length > 0;
7481
$('[data-op="react"]').each((i, e) => {
75-
ReactDOM.createRoot(e).render(
76-
<Reaction payload={$(e).data('form')} ele={$(`.reactions[data-${$(e).data('form').nodeType}='${$(e).data('form').id}']`)} />,
77-
);
82+
ReactDOM.createRoot(e).render(<MantineProvider>
83+
<Reaction payload={$(e).data('form')} ele={$(`.reactions[data-${$(e).data('form').nodeType}='${$(e).data('form').id}']`)} />
84+
</MantineProvider>);
7885
});
7986
$(document).on('click', '.reaction', async (e) => {
8087
if (!canUseReaction) {

packages/ui-default/components/notification/index.ts

Lines changed: 68 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,43 @@
1-
import { Intent, OverlayToaster, Position } from '@blueprintjs/core';
1+
import { createTheme, MantineProvider, Notification as MantineNotification } from '@mantine/core';
2+
import { Notifications, notifications } from '@mantine/notifications';
23
import $ from 'jquery';
4+
import React from 'react';
35
import { tpl, zIndexManager } from 'vj/utils/base';
46

5-
const ToasterContainer = document.createElement('div');
6-
ToasterContainer.style.position = 'fixed';
7-
ToasterContainer.style.bottom = '0px';
8-
ToasterContainer.style.width = '100%';
9-
ToasterContainer.style.zIndex = '9999';
10-
document.body.append(ToasterContainer);
7+
const colorWhite = {
8+
color: 'var(--mantine-color-white)',
9+
} as const;
1110

12-
const AppToaster = OverlayToaster.create(
13-
{ position: Position.BOTTOM_LEFT, usePortal: false },
14-
{ container: ToasterContainer },
11+
const theme = createTheme({
12+
components: {
13+
Notification: MantineNotification.extend({
14+
classNames: {
15+
closeButton: 'mantine-notifications-close-button',
16+
},
17+
styles: {
18+
root: {
19+
backgroundColor: 'var(--notification-color, var(--mantine-primary-color-filled))',
20+
paddingInlineStart: 'var(--mantine-spacing-xs)',
21+
},
22+
title: {
23+
...colorWhite,
24+
fontSize: 'var(--mantine-font-size-md)',
25+
},
26+
icon: {
27+
fontSize: '24px',
28+
marginInlineEnd: 'var(--mantine-spacing-xs)',
29+
},
30+
description: colorWhite,
31+
closeButton: colorWhite,
32+
},
33+
}),
34+
},
35+
});
36+
37+
document.body.append(tpl(
38+
React.createElement(MantineProvider, { theme },
39+
React.createElement(Notifications, { position: 'bottom-left', zIndex: 99999 }),
40+
), true),
1541
);
1642

1743
interface NotificationOptions {
@@ -66,20 +92,44 @@ export default class Notification {
6692
setTimeout(() => this.$n.remove(), 200);
6793
}
6894

69-
static async success(message: string, duration?: number) {
70-
return (await AppToaster).show({ message, timeout: duration, intent: Intent.SUCCESS });
95+
static success(message: string, duration?: number) {
96+
return notifications.show({
97+
title: message,
98+
color: '#238551',
99+
message: '',
100+
icon: React.createElement('i', { className: 'icon icon-check' }),
101+
autoClose: duration,
102+
});
71103
}
72104

73-
static async info(message: string, duration?: number) {
74-
return (await AppToaster).show({ message, timeout: duration, intent: Intent.PRIMARY });
105+
static info(message: string, duration?: number) {
106+
return notifications.show({
107+
title: message,
108+
color: '#2d72d2',
109+
message: '',
110+
icon: React.createElement('i', { className: 'icon icon-info--circle' }),
111+
autoClose: duration,
112+
});
75113
}
76114

77-
static async warn(message: string, duration?: number) {
78-
return (await AppToaster).show({ message, timeout: duration, intent: Intent.WARNING });
115+
static warn(message: string, duration?: number) {
116+
return notifications.show({
117+
title: message,
118+
color: '#fbb360',
119+
message: '',
120+
icon: React.createElement('i', { className: 'icon icon-warning' }),
121+
autoClose: duration,
122+
});
79123
}
80124

81-
static async error(message: string, duration?: number) {
82-
return (await AppToaster).show({ message, timeout: duration, intent: Intent.DANGER });
125+
static error(message: string, duration?: number) {
126+
return notifications.show({
127+
title: message,
128+
color: '#cd4246',
129+
message: '',
130+
icon: React.createElement('i', { className: 'icon icon-close--circle' }),
131+
autoClose: duration,
132+
});
83133
}
84134
}
85135

packages/ui-default/components/notification/notification.page.styl

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,4 +77,8 @@
7777
.notification {
7878
display: none !important
7979
}
80+
}
81+
82+
.mantine-notifications-close-button:hover {
83+
background-color: rgba(0, 0, 0, 0.1) !important;
8084
}

packages/ui-default/components/problem/tag.page.styl

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,41 @@
1919
margin-left: rem(3px)
2020
margin-bottom: rem(5px)
2121
line-height: 30px !important
22+
display: inline-flex
23+
flex-direction: row
24+
align-items: center
25+
border: none
26+
border-radius: 2px
27+
box-shadow: none
28+
font-size: 14px
29+
line-height: 18px
30+
max-width: 100%
31+
min-height: 30px
32+
min-width: 30px
33+
padding: 6px 8px
34+
position: relative
35+
background-color: rgba(143, 153, 168, 0.15)
36+
color: #1c2127
2237
&:hover
2338
text-decoration: none
2439
> span
2540
display: flex
2641
align-items: center
42+
> *
43+
flex-grow: 0
44+
flex-shrink: 0
45+
margin-right: 8px
46+
> :last-child
47+
margin-right: 0
48+
&:focus
49+
outline: rgba(33, 93, 176, 0.752) solid 2px
50+
outline-offset: 0
51+
-moz-outline-radius: 6px
52+
&.interactive
53+
cursor: pointer
54+
&:hover
55+
background: rgba(143, 153, 168, 0.3)
56+
color: #111418
2757

2858
.problem__tag-link
2959
display: inline-block

0 commit comments

Comments
 (0)