@electerm/electerm-react 2.3.191 → 2.4.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/client/common/clipboard.js +1 -1
- package/client/common/constants.js +2 -2
- package/client/common/download.jsx +3 -2
- package/client/common/error-handler.jsx +5 -9
- package/client/common/fetch-from-server.js +1 -1
- package/client/common/fetch.jsx +5 -5
- package/client/common/icon-helpers.jsx +16 -0
- package/client/common/parse-json-safe.js +1 -1
- package/client/common/pre.js +0 -7
- package/client/common/sftp.js +1 -1
- package/client/common/terminal-theme.js +1 -1
- package/client/common/transfer.js +2 -2
- package/client/common/upgrade.js +2 -2
- package/client/components/ai/ai-chat.jsx +10 -1
- package/client/components/auth/login.jsx +1 -1
- package/client/components/bg/css-overwrite.jsx +1 -1
- package/client/components/bookmark-form/common/fields.jsx +3 -0
- package/client/components/bookmark-form/common/ssh-agent.jsx +33 -0
- package/client/components/bookmark-form/config/common-fields.js +2 -4
- package/client/components/bookmark-form/config/serial.js +1 -1
- package/client/components/bookmark-form/config/ssh.js +1 -0
- package/client/components/bookmark-form/form-renderer.jsx +3 -2
- package/client/components/common/input-auto-focus.jsx +1 -1
- package/client/components/common/message.jsx +131 -0
- package/client/components/common/message.styl +58 -0
- package/client/components/common/modal.jsx +176 -0
- package/client/components/common/modal.styl +22 -0
- package/client/components/common/notification-with-details.jsx +1 -1
- package/client/components/common/notification.jsx +94 -0
- package/client/components/common/notification.styl +51 -0
- package/client/components/main/connection-hopping-warnning.jsx +1 -3
- package/client/components/main/error-wrapper.jsx +3 -2
- package/client/components/main/main.jsx +4 -11
- package/client/components/main/upgrade.jsx +6 -4
- package/client/components/profile/profile-form-elem.jsx +1 -1
- package/client/components/quick-commands/quick-commands-box.jsx +5 -2
- package/client/components/quick-commands/quick-commands-form-elem.jsx +1 -1
- package/client/components/rdp/rdp-session.jsx +2 -2
- package/client/components/session/session.jsx +4 -9
- package/client/components/setting-panel/deep-link-control.jsx +4 -3
- package/client/components/setting-panel/keyword-input.jsx +60 -0
- package/client/components/setting-panel/keywords-form.jsx +2 -7
- package/client/components/setting-panel/setting-common.jsx +1 -1
- package/client/components/setting-panel/setting-terminal.jsx +1 -1
- package/client/components/setting-panel/tab-settings.jsx +1 -1
- package/client/components/setting-sync/setting-sync-form.jsx +53 -3
- package/client/components/setting-sync/setting-sync.jsx +2 -1
- package/client/components/sftp/owner-list.js +6 -6
- package/client/components/sftp/sftp-entry.jsx +6 -4
- package/client/components/shortcuts/shortcut-editor.jsx +2 -2
- package/client/components/ssh-config/ssh-config-load-notify.jsx +3 -2
- package/client/components/tabs/tab.jsx +1 -1
- package/client/components/tabs/workspace-save-modal.jsx +2 -1
- package/client/components/terminal/attach-addon-custom.js +142 -26
- package/client/components/terminal/command-tracker-addon.js +164 -53
- package/client/components/terminal/highlight-addon.js +84 -43
- package/client/components/terminal/shell.js +138 -0
- package/client/components/terminal/term-search.styl +1 -0
- package/client/components/terminal/terminal-command-dropdown.jsx +3 -0
- package/client/components/terminal/terminal.jsx +166 -104
- package/client/components/theme/theme-form.jsx +2 -1
- package/client/components/tree-list/bookmark-transport.jsx +27 -5
- package/client/components/vnc/vnc-session.jsx +1 -1
- package/client/components/widgets/widget-notification-with-details.jsx +1 -1
- package/client/store/common.js +5 -2
- package/client/store/db-upgrade.js +1 -1
- package/client/store/init-state.js +2 -1
- package/client/store/load-data.js +2 -2
- package/client/store/mcp-handler.js +9 -56
- package/client/store/setting.js +1 -3
- package/client/store/store.js +2 -1
- package/client/store/sync.js +14 -8
- package/client/store/system-menu.js +2 -1
- package/client/store/tab.js +1 -1
- package/client/store/widgets.js +1 -3
- package/package.json +1 -1
- package/client/common/track.js +0 -7
- package/client/components/batch-op/batch-op-entry.jsx +0 -13
|
@@ -145,6 +145,7 @@ export const terminalLocalType = 'local'
|
|
|
145
145
|
export const terminalFtpType = 'ftp'
|
|
146
146
|
export const openedSidebarKey = 'opened-sidebar'
|
|
147
147
|
export const sidebarPinnedKey = 'sidebar-pinned'
|
|
148
|
+
export const pinnedQuickCommandBarKey = 'pinned-quick-command-bar'
|
|
148
149
|
export const leftSidebarWidthKey = 'left-sidebar-width'
|
|
149
150
|
export const rightSidebarWidthKey = 'right-sidebar-width'
|
|
150
151
|
export const addPanelWidthLsKey = 'addPanelWidth'
|
|
@@ -270,8 +271,7 @@ export const instSftpKeys = [
|
|
|
270
271
|
'readFile',
|
|
271
272
|
'writeFile'
|
|
272
273
|
]
|
|
273
|
-
export const
|
|
274
|
-
export const zmodemTransferPackSize = 1024 * 1024 * 2
|
|
274
|
+
export const zmodemTransferPackSize = 1024 * 8
|
|
275
275
|
export const splitMap = {
|
|
276
276
|
c1: 'c1',
|
|
277
277
|
c2: 'c2',
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* simulate download
|
|
3
3
|
*/
|
|
4
|
-
import { notification } from '
|
|
4
|
+
import { notification } from '../components/common/notification'
|
|
5
5
|
import ShowItem from '../components/common/show-item'
|
|
6
6
|
import { chooseSaveDirectory } from './choose-save-folder'
|
|
7
|
+
import { DownloadOutlined } from '@ant-design/icons'
|
|
7
8
|
|
|
8
9
|
function downloadForBrowser (filename, text) {
|
|
9
10
|
const blob = new Blob([text], { type: 'text/plain;charset=utf-8' })
|
|
@@ -32,7 +33,7 @@ export default async function download (filename, text) {
|
|
|
32
33
|
return
|
|
33
34
|
}
|
|
34
35
|
notification.success({
|
|
35
|
-
|
|
36
|
+
message: <DownloadOutlined />,
|
|
36
37
|
description: (
|
|
37
38
|
<ShowItem
|
|
38
39
|
to={filePath}
|
|
@@ -2,25 +2,21 @@
|
|
|
2
2
|
* common error handler
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import { notification } from '
|
|
5
|
+
import { notification } from '../components/common/notification'
|
|
6
6
|
|
|
7
7
|
export default (e) => {
|
|
8
8
|
const { message = 'error', stack } = e
|
|
9
|
-
|
|
10
|
-
const msg = (
|
|
11
|
-
<div className='mw240 elli wordbreak' title={message}>
|
|
12
|
-
{message}
|
|
13
|
-
</div>
|
|
14
|
-
)
|
|
9
|
+
console.error(e)
|
|
15
10
|
const description = (
|
|
16
11
|
<div
|
|
17
|
-
className='
|
|
12
|
+
className='common-err-desc'
|
|
13
|
+
title={stack}
|
|
18
14
|
>
|
|
19
15
|
{stack}
|
|
20
16
|
</div>
|
|
21
17
|
)
|
|
22
18
|
notification.error({
|
|
23
|
-
|
|
19
|
+
message,
|
|
24
20
|
description,
|
|
25
21
|
duration: 55
|
|
26
22
|
})
|
|
@@ -35,7 +35,7 @@ const wsFetch = async (data) => {
|
|
|
35
35
|
return new NewPromise((resolve, reject) => {
|
|
36
36
|
window.et.commonWs.once((arg) => {
|
|
37
37
|
if (arg.error) {
|
|
38
|
-
|
|
38
|
+
console.error('fetch error', arg.error)
|
|
39
39
|
return reject(new Error(arg.error.message))
|
|
40
40
|
}
|
|
41
41
|
resolve(arg.data)
|
package/client/common/fetch.jsx
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// the final fetch wrapper
|
|
2
2
|
import { isString, isFunction } from 'lodash-es'
|
|
3
|
-
import { notification } from '
|
|
3
|
+
import { notification } from '../components/common/notification'
|
|
4
4
|
|
|
5
5
|
function jsonHeader () {
|
|
6
6
|
return {
|
|
@@ -17,7 +17,7 @@ function parseResponse (response) {
|
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
export async function handleErr (res) {
|
|
20
|
-
|
|
20
|
+
console.debug(res)
|
|
21
21
|
let text = res.message || res.statusText
|
|
22
22
|
if (!isString(text)) {
|
|
23
23
|
try {
|
|
@@ -25,12 +25,12 @@ export async function handleErr (res) {
|
|
|
25
25
|
? await res.text()
|
|
26
26
|
: isFunction(res.json) ? await res.json() : ''
|
|
27
27
|
} catch (e) {
|
|
28
|
-
|
|
28
|
+
console.error('fetch response parse fails', e)
|
|
29
29
|
}
|
|
30
30
|
}
|
|
31
|
-
|
|
31
|
+
console.debug(text, 'fetch err info')
|
|
32
32
|
notification.error({
|
|
33
|
-
|
|
33
|
+
message: 'http request error',
|
|
34
34
|
description: (
|
|
35
35
|
<div className='common-err'>
|
|
36
36
|
{text}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import {
|
|
3
|
+
InfoCircleFilled,
|
|
4
|
+
CheckCircleFilled,
|
|
5
|
+
ExclamationCircleFilled,
|
|
6
|
+
CloseCircleFilled
|
|
7
|
+
} from '@ant-design/icons'
|
|
8
|
+
|
|
9
|
+
export const messageIcons = {
|
|
10
|
+
info: <InfoCircleFilled className='msg-icon info' />,
|
|
11
|
+
success: <CheckCircleFilled className='msg-icon success' />,
|
|
12
|
+
warning: <ExclamationCircleFilled className='msg-icon warning' />,
|
|
13
|
+
error: <CloseCircleFilled className='msg-icon error' />
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const getMessageIcon = (type) => messageIcons[type] || messageIcons.info
|
package/client/common/pre.js
CHANGED
|
@@ -38,13 +38,6 @@ function decodeBase64String (base64String) {
|
|
|
38
38
|
return uint8Array
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
-
window.log = {
|
|
42
|
-
debug: (...args) => runSync('debug', ...args),
|
|
43
|
-
log: (...args) => runSync('log', ...args),
|
|
44
|
-
error: (...args) => runSync('error', ...args),
|
|
45
|
-
info: (...args) => runSync('info', ...args)
|
|
46
|
-
}
|
|
47
|
-
|
|
48
41
|
window.pre = {
|
|
49
42
|
requireAuth: runSync('shouldAuth'),
|
|
50
43
|
readClipboard: () => {
|
package/client/common/sftp.js
CHANGED
|
@@ -141,7 +141,7 @@ export const exportTheme = (themeId) => {
|
|
|
141
141
|
const themes = window.store.getSidebarList(settingMap.terminalThemes)
|
|
142
142
|
const theme = themes.find(d => d.id === themeId)
|
|
143
143
|
if (!theme) {
|
|
144
|
-
|
|
144
|
+
console.error('export error', themeId)
|
|
145
145
|
return
|
|
146
146
|
}
|
|
147
147
|
const text = convertThemeToText(theme, true)
|
|
@@ -57,8 +57,8 @@ class Transfer {
|
|
|
57
57
|
th.onDestroy(ws)
|
|
58
58
|
}, 'transfer:end:' + id)
|
|
59
59
|
ws.once((arg) => {
|
|
60
|
-
|
|
61
|
-
|
|
60
|
+
console.debug('sftp transfer error')
|
|
61
|
+
console.debug(arg.error.stack)
|
|
62
62
|
onError(new Error(arg.error.message))
|
|
63
63
|
th.onDestroy(ws)
|
|
64
64
|
}, 'transfer:err:' + id)
|
package/client/common/upgrade.js
CHANGED
|
@@ -49,8 +49,8 @@ class Upgrade {
|
|
|
49
49
|
onEnd(arg)
|
|
50
50
|
}, 'upgrade:end:' + id)
|
|
51
51
|
ws.once((arg) => {
|
|
52
|
-
|
|
53
|
-
|
|
52
|
+
console.debug('upgrade error')
|
|
53
|
+
console.debug(arg.error.stack)
|
|
54
54
|
onError(new Error(arg.error.message))
|
|
55
55
|
}, 'upgrade:err:' + id)
|
|
56
56
|
}
|
|
@@ -185,7 +185,7 @@ export default function AIChat (props) {
|
|
|
185
185
|
<SendOutlined
|
|
186
186
|
onClick={handleSubmit}
|
|
187
187
|
className='mg1l pointer icon-hover send-to-ai-icon'
|
|
188
|
-
title='
|
|
188
|
+
title='Enter to send, Shift+Enter for new line'
|
|
189
189
|
/>
|
|
190
190
|
)
|
|
191
191
|
}
|
|
@@ -207,6 +207,14 @@ export default function AIChat (props) {
|
|
|
207
207
|
return null
|
|
208
208
|
}
|
|
209
209
|
|
|
210
|
+
const handleKeyPress = (e) => {
|
|
211
|
+
if (!e.shiftKey) {
|
|
212
|
+
e.preventDefault()
|
|
213
|
+
handleSubmit()
|
|
214
|
+
}
|
|
215
|
+
// If Shift+Enter, allow default behavior (new line)
|
|
216
|
+
}
|
|
217
|
+
|
|
210
218
|
return (
|
|
211
219
|
<Flex vertical className='ai-chat-container'>
|
|
212
220
|
<Flex className='ai-chat-history' flex='auto'>
|
|
@@ -217,6 +225,7 @@ export default function AIChat (props) {
|
|
|
217
225
|
<TextArea
|
|
218
226
|
value={prompt}
|
|
219
227
|
onChange={handlePromptChange}
|
|
228
|
+
onPressEnter={handleKeyPress}
|
|
220
229
|
placeholder='Enter your prompt here'
|
|
221
230
|
autoSize={{ minRows: 3, maxRows: 10 }}
|
|
222
231
|
disabled={isLoading}
|
|
@@ -2,9 +2,9 @@ import React, { useState, useEffect } from 'react'
|
|
|
2
2
|
import LogoElem from '../common/logo-elem.jsx'
|
|
3
3
|
import store from '../../store'
|
|
4
4
|
import {
|
|
5
|
-
message,
|
|
6
5
|
Spin
|
|
7
6
|
} from 'antd'
|
|
7
|
+
import message from '../common/message'
|
|
8
8
|
import {
|
|
9
9
|
ArrowRightOutlined
|
|
10
10
|
} from '@ant-design/icons'
|
|
@@ -81,7 +81,7 @@ export default class CssOverwrite extends Component {
|
|
|
81
81
|
st = 'text'
|
|
82
82
|
} else if (imagePath && !isWebImg) {
|
|
83
83
|
content = await fs.readFileAsBase64(imagePath)
|
|
84
|
-
.catch(
|
|
84
|
+
.catch(console.error)
|
|
85
85
|
if (content) {
|
|
86
86
|
st = `url(data:image;base64,${content})`
|
|
87
87
|
}
|
|
@@ -10,6 +10,7 @@ import InputAutoFocus from '../../common/input-auto-focus.jsx'
|
|
|
10
10
|
import ProxyField from './proxy.jsx'
|
|
11
11
|
import X11Field from './x11.jsx'
|
|
12
12
|
import SshTunnels from './ssh-tunnels.jsx'
|
|
13
|
+
import SshAgent from './ssh-agent.jsx'
|
|
13
14
|
import ConnectionHopping from './connection-hopping.jsx'
|
|
14
15
|
import TerminalBackgroundField from './terminal-background.jsx'
|
|
15
16
|
import useQuickCmds from './quick-commands.jsx'
|
|
@@ -145,6 +146,8 @@ export function renderFormItem (item, formItemLayout, form, ctxProps, index) {
|
|
|
145
146
|
return <X11Field key={name} form={form} />
|
|
146
147
|
case 'sshTunnels':
|
|
147
148
|
return <SshTunnels key={name} form={form} formData={ctxProps.formData} />
|
|
149
|
+
case 'sshAgent':
|
|
150
|
+
return <SshAgent key={name} />
|
|
148
151
|
case 'connectionHopping':
|
|
149
152
|
return (
|
|
150
153
|
<ConnectionHopping
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { Form, Input, Switch, Space } from 'antd'
|
|
3
|
+
import HelpIcon from '../../common/help-icon'
|
|
4
|
+
import { formItemLayout } from '../../../common/form-layout'
|
|
5
|
+
|
|
6
|
+
const FormItem = Form.Item
|
|
7
|
+
const e = window.translate
|
|
8
|
+
|
|
9
|
+
export default function SshAgent () {
|
|
10
|
+
return (
|
|
11
|
+
<FormItem
|
|
12
|
+
{...formItemLayout}
|
|
13
|
+
label={e('useSshAgent')}
|
|
14
|
+
>
|
|
15
|
+
<Space align='center'>
|
|
16
|
+
<FormItem
|
|
17
|
+
name='useSshAgent'
|
|
18
|
+
valuePropName='checked'
|
|
19
|
+
noStyle
|
|
20
|
+
>
|
|
21
|
+
<Switch />
|
|
22
|
+
</FormItem>
|
|
23
|
+
<FormItem
|
|
24
|
+
name='sshAgent'
|
|
25
|
+
noStyle
|
|
26
|
+
>
|
|
27
|
+
<Input placeholder={e('SSH Agent Path')} />
|
|
28
|
+
</FormItem>
|
|
29
|
+
<HelpIcon link='https://github.com/electerm/electerm/wiki/ssh-agent' />
|
|
30
|
+
</Space>
|
|
31
|
+
</FormItem>
|
|
32
|
+
)
|
|
33
|
+
}
|
|
@@ -272,10 +272,8 @@ export const sshAuthFields = [
|
|
|
272
272
|
{ type: 'sshAuthSelector', name: '__auth__', label: '', formItemName: 'password' },
|
|
273
273
|
commonFields.port,
|
|
274
274
|
{
|
|
275
|
-
type: '
|
|
276
|
-
name: 'useSshAgent'
|
|
277
|
-
label: () => e('useSshAgent'),
|
|
278
|
-
valuePropName: 'checked'
|
|
275
|
+
type: 'sshAgent',
|
|
276
|
+
name: 'useSshAgent'
|
|
279
277
|
},
|
|
280
278
|
commonFields.runScripts,
|
|
281
279
|
commonFields.description,
|
|
@@ -40,7 +40,7 @@ const serialConfig = {
|
|
|
40
40
|
type: 'autocomplete',
|
|
41
41
|
name: 'baudRate',
|
|
42
42
|
label: 'baudRate',
|
|
43
|
-
options: commonBaudRates.map(d => ({ value: d })),
|
|
43
|
+
options: commonBaudRates.map(d => ({ value: d.toString(), label: d.toString() })),
|
|
44
44
|
normalize: (value) => {
|
|
45
45
|
if (value === '' || value == null) {
|
|
46
46
|
return undefined
|
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
* Generic form renderer driven by config (flattened path)
|
|
3
3
|
*/
|
|
4
4
|
import React, { useEffect, useState, useRef } from 'react'
|
|
5
|
-
import { Form, Tabs
|
|
5
|
+
import { Form, Tabs } from 'antd'
|
|
6
|
+
import message from '../common/message'
|
|
6
7
|
import { renderFormItem } from './common/fields'
|
|
7
8
|
import SubmitButtons from './common/submit-buttons'
|
|
8
9
|
import { uniq } from 'lodash-es'
|
|
@@ -260,7 +261,7 @@ export default function FormRenderer ({ config, props }) {
|
|
|
260
261
|
}
|
|
261
262
|
const ips = await window.pre.runGlobalAsync('lookup', value)
|
|
262
263
|
.catch(err => {
|
|
263
|
-
|
|
264
|
+
console.debug(err)
|
|
264
265
|
})
|
|
265
266
|
setIps(ips || [])
|
|
266
267
|
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import React, { useState, useEffect } from 'react'
|
|
2
|
+
import { createRoot } from 'react-dom/client'
|
|
3
|
+
import {
|
|
4
|
+
CloseOutlined
|
|
5
|
+
} from '@ant-design/icons'
|
|
6
|
+
import classnames from 'classnames'
|
|
7
|
+
import generateId from '../../common/uid'
|
|
8
|
+
import { messageIcons } from '../../common/icon-helpers.jsx'
|
|
9
|
+
import './message.styl'
|
|
10
|
+
|
|
11
|
+
let messageContainerRoot = null
|
|
12
|
+
const messageObservers = []
|
|
13
|
+
|
|
14
|
+
const subscribe = (onUpdate) => {
|
|
15
|
+
messageObservers.push(onUpdate)
|
|
16
|
+
return () => {
|
|
17
|
+
const index = messageObservers.indexOf(onUpdate)
|
|
18
|
+
if (index > -1) messageObservers.splice(index, 1)
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const notify = (messages) => {
|
|
23
|
+
messageObservers.forEach(onUpdate => onUpdate([...messages]))
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
let activeMessages = []
|
|
27
|
+
|
|
28
|
+
function MessageItem ({ id, type, content, duration, onRemove, timestamp }) {
|
|
29
|
+
useEffect(() => {
|
|
30
|
+
if (duration !== 0) {
|
|
31
|
+
const timer = setTimeout(onRemove, duration * 1000)
|
|
32
|
+
return () => clearTimeout(timer)
|
|
33
|
+
}
|
|
34
|
+
}, [duration, onRemove, timestamp])
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
<div className={classnames('message-item', type)} id={`message-${id}`}>
|
|
38
|
+
<div className='message-content-wrap'>
|
|
39
|
+
{messageIcons[type]}
|
|
40
|
+
<div className='message-content'>{content}</div>
|
|
41
|
+
<CloseOutlined className='message-close' onClick={onRemove} />
|
|
42
|
+
</div>
|
|
43
|
+
</div>
|
|
44
|
+
)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function MessageContainer () {
|
|
48
|
+
const [messages, setMessages] = useState(activeMessages)
|
|
49
|
+
|
|
50
|
+
useEffect(() => {
|
|
51
|
+
return subscribe(setMessages)
|
|
52
|
+
}, [])
|
|
53
|
+
|
|
54
|
+
const removeMessage = (id, onClose) => {
|
|
55
|
+
activeMessages = activeMessages.filter(m => m.id !== id)
|
|
56
|
+
notify(activeMessages)
|
|
57
|
+
if (typeof onClose === 'function') {
|
|
58
|
+
onClose()
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return (
|
|
63
|
+
<div className='message-container'>
|
|
64
|
+
{messages.map(msg => {
|
|
65
|
+
const { key, ...props } = msg
|
|
66
|
+
return (
|
|
67
|
+
<MessageItem key={msg.id} {...props} onRemove={() => removeMessage(msg.id, msg.onClose)} />
|
|
68
|
+
)
|
|
69
|
+
})}
|
|
70
|
+
</div>
|
|
71
|
+
)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const init = () => {
|
|
75
|
+
if (messageContainerRoot || typeof document === 'undefined') return
|
|
76
|
+
const div = document.createElement('div')
|
|
77
|
+
div.id = 'message-root'
|
|
78
|
+
document.body.appendChild(div)
|
|
79
|
+
messageContainerRoot = createRoot(div)
|
|
80
|
+
messageContainerRoot.render(<MessageContainer />)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const addMessage = (type, content, duration = 3, onClose) => {
|
|
84
|
+
let config = {
|
|
85
|
+
content,
|
|
86
|
+
duration,
|
|
87
|
+
onClose,
|
|
88
|
+
type
|
|
89
|
+
}
|
|
90
|
+
if (typeof content === 'object' && content !== null && !React.isValidElement(content)) {
|
|
91
|
+
config = {
|
|
92
|
+
...config,
|
|
93
|
+
...content,
|
|
94
|
+
type: content.type || type
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
init()
|
|
98
|
+
const id = config.key || generateId()
|
|
99
|
+
const existingIndex = activeMessages.findIndex(m => m.id === id)
|
|
100
|
+
const newMessage = {
|
|
101
|
+
...config,
|
|
102
|
+
id,
|
|
103
|
+
timestamp: Date.now()
|
|
104
|
+
}
|
|
105
|
+
if (existingIndex > -1) {
|
|
106
|
+
activeMessages[existingIndex] = newMessage
|
|
107
|
+
} else {
|
|
108
|
+
activeMessages = [...activeMessages, newMessage]
|
|
109
|
+
}
|
|
110
|
+
notify(activeMessages)
|
|
111
|
+
return {
|
|
112
|
+
id,
|
|
113
|
+
destroy: () => {
|
|
114
|
+
activeMessages = activeMessages.filter(m => m.id !== id)
|
|
115
|
+
notify(activeMessages)
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const message = {
|
|
121
|
+
info: (content, duration) => addMessage('info', content, duration),
|
|
122
|
+
success: (content, duration) => addMessage('success', content, duration),
|
|
123
|
+
warning: (content, duration) => addMessage('warning', content, duration),
|
|
124
|
+
error: (content, duration) => addMessage('error', content, duration),
|
|
125
|
+
destroy: () => {
|
|
126
|
+
activeMessages = []
|
|
127
|
+
notify(activeMessages)
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export default message
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
.message-container
|
|
2
|
+
position fixed
|
|
3
|
+
top 20px
|
|
4
|
+
left 50%
|
|
5
|
+
transform translateX(-50%)
|
|
6
|
+
z-index 10000
|
|
7
|
+
pointer-events none
|
|
8
|
+
|
|
9
|
+
.message-item
|
|
10
|
+
pointer-events all
|
|
11
|
+
margin-bottom 16px
|
|
12
|
+
padding 8px 16px
|
|
13
|
+
border-radius 4px
|
|
14
|
+
background var(--main-lighter)
|
|
15
|
+
color var(--text)
|
|
16
|
+
box-shadow 0 4px 12px rgba(0,0,0,0.15)
|
|
17
|
+
display flex
|
|
18
|
+
align-items center
|
|
19
|
+
animation message-fade-in 0.3s ease
|
|
20
|
+
position relative
|
|
21
|
+
justify-content center
|
|
22
|
+
&:hover
|
|
23
|
+
.message-close
|
|
24
|
+
opacity 1
|
|
25
|
+
|
|
26
|
+
.message-content-wrap
|
|
27
|
+
display flex
|
|
28
|
+
align-items center
|
|
29
|
+
|
|
30
|
+
.msg-icon
|
|
31
|
+
margin-right 8px
|
|
32
|
+
font-size 16px
|
|
33
|
+
&.info
|
|
34
|
+
color var(--info)
|
|
35
|
+
&.success
|
|
36
|
+
color var(--success)
|
|
37
|
+
&.warning
|
|
38
|
+
color var(--warn)
|
|
39
|
+
&.error
|
|
40
|
+
color var(--error)
|
|
41
|
+
|
|
42
|
+
.message-close
|
|
43
|
+
margin-left 8px
|
|
44
|
+
cursor pointer
|
|
45
|
+
font-size 12px
|
|
46
|
+
color var(--text-dark)
|
|
47
|
+
opacity 0
|
|
48
|
+
transition opacity 0.2s
|
|
49
|
+
&:hover
|
|
50
|
+
color var(--text-light)
|
|
51
|
+
|
|
52
|
+
@keyframes message-fade-in
|
|
53
|
+
from
|
|
54
|
+
opacity 0
|
|
55
|
+
transform translateY(-20px)
|
|
56
|
+
to
|
|
57
|
+
opacity 1
|
|
58
|
+
transform translateY(0)
|