@electerm/electerm-react 2.2.0 → 2.3.18

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.
Files changed (50) hide show
  1. package/client/common/constants.js +1 -1
  2. package/client/common/ui-theme.js +25 -12
  3. package/client/components/ai/ai.styl +1 -3
  4. package/client/components/bg/custom-css.jsx +2 -2
  5. package/client/components/bookmark-form/common/serial-path-selector.jsx +1 -1
  6. package/client/components/common/highlight.styl +1 -2
  7. package/client/components/common/input-context-menu.jsx +294 -0
  8. package/client/components/common/opacity.jsx +66 -0
  9. package/client/components/file-transfer/transfer.styl +6 -9
  10. package/client/components/footer/footer.styl +2 -5
  11. package/client/components/layout/layout.styl +2 -3
  12. package/client/components/main/error-wrapper.jsx +3 -3
  13. package/client/components/main/main.jsx +4 -7
  14. package/client/components/main/term-fullscreen.styl +1 -1
  15. package/client/components/main/ui-theme.jsx +46 -6
  16. package/client/components/main/upgrade.styl +4 -6
  17. package/client/components/main/wrapper.styl +5 -27
  18. package/client/components/quick-commands/qm.styl +3 -6
  19. package/client/components/session/session.styl +11 -13
  20. package/client/components/setting-panel/list.styl +5 -5
  21. package/client/components/setting-panel/setting-wrap.styl +1 -6
  22. package/client/components/setting-panel/terminal-bg-config.jsx +3 -0
  23. package/client/components/setting-sync/setting-sync-form.jsx +0 -1
  24. package/client/components/sftp/file-item.jsx +3 -0
  25. package/client/components/sftp/sftp-entry.jsx +50 -9
  26. package/client/components/sftp/sftp.styl +21 -34
  27. package/client/components/sftp/transfer-tag.styl +3 -5
  28. package/client/components/side-panel-r/right-side-panel.styl +7 -9
  29. package/client/components/sidebar/info.styl +1 -14
  30. package/client/components/sidebar/sidebar.styl +16 -18
  31. package/client/components/sys-menu/sys-menu.styl +8 -11
  32. package/client/components/tabs/tabs.styl +34 -35
  33. package/client/components/terminal/term-search.styl +3 -3
  34. package/client/components/terminal/terminal-search-bar.jsx +1 -1
  35. package/client/components/terminal/terminal.jsx +2 -2
  36. package/client/components/terminal/terminal.styl +22 -27
  37. package/client/components/terminal-info/terminal-info.styl +8 -8
  38. package/client/components/theme/terminal-theme-list.styl +2 -2
  39. package/client/components/theme/theme-list-item.jsx +62 -20
  40. package/client/components/tree-list/tree-list.styl +1 -1
  41. package/client/css/basic.styl +6 -12
  42. package/client/css/includes/theme.styl +16 -0
  43. package/client/store/ui-theme.js +0 -35
  44. package/client/views/index.pug +8 -1
  45. package/package.json +1 -1
  46. package/client/components/common/native-input.styl +0 -7
  47. package/client/components/setting-sync/sync.styl +0 -7
  48. package/client/components/terminal/zmodem.styl +0 -14
  49. package/client/css/antd-overwrite.styl +0 -10
  50. package/client/css/includes/theme-default.styl +0 -20
@@ -177,7 +177,7 @@ export const defaultTheme = {
177
177
  brightCyan: '#BCAAFE',
178
178
  brightWhite: '#E6E6E6'
179
179
  },
180
- uiThemeConfig: getUiThemeConfig(window.et.stylus)
180
+ uiThemeConfig: getUiThemeConfig()
181
181
  }
182
182
 
183
183
  export const commonBaudRates = [
@@ -2,19 +2,32 @@
2
2
  * ui theme related
3
3
  */
4
4
 
5
- export function getUiThemeConfig (stylus) {
6
- const reg = /[^\n]+ = [^\n]+\n/g
7
- const arr = stylus.match(reg)
8
- const sep = ' = '
9
- return arr.reduce((p, x) => {
10
- if (!x.includes(sep)) {
11
- return p
12
- }
13
- const [k, v] = x.split(sep)
14
- return {
15
- ...p,
16
- [k.trim()]: v.trim()
5
+ const defaultUiThemeStylus = `
6
+ --main #141314
7
+ --main-dark #000
8
+ --main-light #2E3338
9
+ --text #ddd
10
+ --text-light #fff
11
+ --text-dark #888
12
+ --text-disabled #777
13
+ --primary #08c
14
+ --info #FFD166
15
+ --success #06D6A0
16
+ --error #EF476F
17
+ --warn #E55934
18
+ `
19
+
20
+ export function getUiThemeConfig (conf = defaultUiThemeStylus) {
21
+ const lines = conf.split('\n').filter(line => line.trim())
22
+ return lines.reduce((p, line) => {
23
+ const [k, v] = line.trim().replace('--', '').split(' ')
24
+ if (k && v) {
25
+ return {
26
+ ...p,
27
+ [k]: v
28
+ }
17
29
  }
30
+ return p
18
31
  }, {})
19
32
  }
20
33
 
@@ -1,9 +1,7 @@
1
- @require '../../css/includes/theme-default'
2
1
  .ai-chat-container
3
2
  height 100%
4
3
  display flex
5
4
  flex-direction column
6
- color text
7
5
 
8
6
  .ai-chat-history
9
7
  flex-grow 1
@@ -17,7 +15,7 @@
17
15
 
18
16
  .chat-history-item
19
17
  .code-block
20
- border 1px dashed text
18
+ border 1px dashed var(--main-darker)
21
19
  padding 5px
22
20
  border-radius 3px
23
21
  pre
@@ -14,8 +14,8 @@ export default function CustomCss (props) {
14
14
  const delta = useDelta(customCss)
15
15
 
16
16
  async function applyTheme () {
17
- const stylus = document.getElementById(themeDomId)
18
- stylus.innerHTML = customCss
17
+ const style = document.getElementById(themeDomId)
18
+ style.innerHTML = customCss
19
19
  }
20
20
 
21
21
  useEffect(() => {
@@ -30,7 +30,7 @@ export default function SerialPathSelector ({
30
30
  />
31
31
  </FormItem>
32
32
  <Spin spinning={loaddingSerials}>
33
- <span onClick={store.handleGetSerials}>
33
+ <span onClick={store.handleGetSerials} className='pointer'>
34
34
  <ReloadOutlined /> {e('reload')} serials
35
35
  </span>
36
36
  </Spin>
@@ -1,3 +1,2 @@
1
- @require '../../css/includes/theme-default'
2
1
  .highlight
3
- color error
2
+ color var(--error)
@@ -0,0 +1,294 @@
1
+ /**
2
+ * Standalone Input/TextArea Context Menu Module
3
+ * Automatically adds context menus to all input/textarea elements without code modification
4
+ * Just import this module and render <InputContextMenu /> in your React app to activate
5
+ */
6
+
7
+ import React, { useState, useEffect, useRef } from 'react'
8
+ import { Dropdown } from 'antd'
9
+ import iconsMap from '../sys-menu/icons-map.jsx'
10
+ import { copy, readClipboard, readClipboardAsync } from '../../common/clipboard.js'
11
+
12
+ const e = window.translate
13
+
14
+ /**
15
+ * Check if element is input or textarea
16
+ */
17
+ function isInputElement (element) {
18
+ if (!element) return false
19
+ const tagName = element.tagName.toLowerCase()
20
+ return tagName === 'input' || tagName === 'textarea'
21
+ }
22
+
23
+ /**
24
+ * Get input element state
25
+ */
26
+ function getInputState (element) {
27
+ if (!isInputElement(element)) return null
28
+
29
+ const hasText = element.value && element.value.length > 0
30
+ const hasSelection = element.selectionStart !== element.selectionEnd
31
+ const isReadOnly = element.readOnly || element.disabled
32
+
33
+ let hasClipboard = false
34
+ try {
35
+ const content = readClipboard()
36
+ hasClipboard = content && content.length > 0
37
+ } catch (error) {
38
+ hasClipboard = false
39
+ }
40
+
41
+ return {
42
+ hasText,
43
+ hasSelection,
44
+ hasClipboard,
45
+ isReadOnly
46
+ }
47
+ }
48
+
49
+ /**
50
+ * Handle copy operation
51
+ */
52
+ function handleCopy (element) {
53
+ if (!isInputElement(element)) return
54
+
55
+ const selectedText = element.value.substring(element.selectionStart, element.selectionEnd)
56
+ if (selectedText) {
57
+ copy(selectedText)
58
+ }
59
+ }
60
+
61
+ /**
62
+ * Handle paste operation
63
+ */
64
+ async function handlePaste (element) {
65
+ if (!isInputElement(element) || element.readOnly || element.disabled) return
66
+
67
+ element.focus()
68
+ const clipboardText = await readClipboardAsync()
69
+
70
+ if (clipboardText) {
71
+ insertTextAtCursor(element, clipboardText)
72
+ }
73
+ }
74
+
75
+ /**
76
+ * Insert text at cursor position in input/textarea
77
+ */
78
+ function insertTextAtCursor (element, text) {
79
+ if (!isInputElement(element)) return
80
+
81
+ element.focus()
82
+
83
+ // Try modern approach first - using execCommand
84
+ if (document.execCommand && document.queryCommandSupported('insertText')) {
85
+ try {
86
+ element.setSelectionRange(element.selectionStart, element.selectionEnd)
87
+ const success = document.execCommand('insertText', false, text)
88
+ if (success) {
89
+ triggerInputEvents(element)
90
+ }
91
+ } catch (error) {
92
+ console.log('execCommand insertText failed:', error)
93
+ }
94
+ }
95
+ }
96
+
97
+ /**
98
+ * Trigger input events for React and other frameworks
99
+ */
100
+ function triggerInputEvents (element) {
101
+ const inputEvent = new Event('input', { bubbles: true, cancelable: true })
102
+ element.dispatchEvent(inputEvent)
103
+
104
+ const changeEvent = new Event('change', { bubbles: true, cancelable: true })
105
+ element.dispatchEvent(changeEvent)
106
+
107
+ // Handle React's internal event handlers
108
+ const reactHandlers = Object.keys(element).find(key => key.startsWith('__reactProps') || key.startsWith('__reactEventHandlers'))
109
+ if (reactHandlers) {
110
+ const handler = element[reactHandlers]
111
+ if (handler?.onChange) {
112
+ handler.onChange({ target: element, currentTarget: element })
113
+ }
114
+ }
115
+ }
116
+
117
+ /**
118
+ * Handle cut operation
119
+ */
120
+ function handleCut (element) {
121
+ if (!isInputElement(element) || element.readOnly || element.disabled) return
122
+
123
+ const { selectionStart, selectionEnd } = element
124
+ const selectedText = element.value.substring(selectionStart, selectionEnd)
125
+
126
+ if (selectedText) {
127
+ element.focus()
128
+ element.setSelectionRange(selectionStart, selectionEnd)
129
+
130
+ // Try using execCommand for cut
131
+ if (document.execCommand && document.queryCommandSupported('cut')) {
132
+ try {
133
+ const success = document.execCommand('cut')
134
+ if (success) {
135
+ triggerInputEvents(element)
136
+ }
137
+ } catch (error) {
138
+ console.log('execCommand cut failed:', error)
139
+ }
140
+ }
141
+ }
142
+ }
143
+
144
+ /**
145
+ * Handle select all operation
146
+ */
147
+ function handleSelectAll (element) {
148
+ if (!isInputElement(element)) return
149
+ element.select()
150
+ element.focus()
151
+ }
152
+
153
+ /**
154
+ * Create context menu items based on input state
155
+ */
156
+ function createContextMenuItems (element) {
157
+ const state = getInputState(element)
158
+ if (!state) return []
159
+
160
+ const { hasText, hasSelection, hasClipboard, isReadOnly } = state
161
+
162
+ return [
163
+ {
164
+ key: 'copy',
165
+ icon: 'CopyOutlined',
166
+ label: e('copy'),
167
+ disabled: !hasSelection
168
+ },
169
+ {
170
+ key: 'cut',
171
+ icon: 'FileExcelOutlined',
172
+ label: e('cut'),
173
+ disabled: !hasSelection || isReadOnly
174
+ },
175
+ {
176
+ key: 'paste',
177
+ icon: 'SwitcherOutlined',
178
+ label: e('paste'),
179
+ disabled: !hasClipboard || isReadOnly
180
+ },
181
+ {
182
+ key: 'selectAll',
183
+ icon: 'CheckSquareOutlined',
184
+ label: e('selectall'),
185
+ disabled: !hasText
186
+ }
187
+ ]
188
+ }
189
+
190
+ /**
191
+ * React Component for Input Context Menu
192
+ * Mount this component in your app to enable context menus on all input/textarea elements
193
+ */
194
+ const InputContextMenu = () => {
195
+ const [visible, setVisible] = useState(false)
196
+ const [position, setPosition] = useState({ x: 0, y: 0 })
197
+ const [targetElement, setTargetElement] = useState(null)
198
+ const menuRef = useRef(null)
199
+
200
+ const handleMenuClick = async ({ key }) => {
201
+ // Keep reference to current element before closing menu
202
+ const currentElement = targetElement
203
+ // Close menu first
204
+ setVisible(false)
205
+ setTargetElement(null)
206
+ // Execute action with preserved element reference
207
+ if (currentElement) {
208
+ setTimeout(async () => {
209
+ try {
210
+ switch (key) {
211
+ case 'copy':
212
+ handleCopy(currentElement)
213
+ break
214
+ case 'cut':
215
+ handleCut(currentElement)
216
+ break
217
+ case 'paste':
218
+ await handlePaste(currentElement)
219
+ break
220
+ case 'selectAll':
221
+ handleSelectAll(currentElement)
222
+ break
223
+ default:
224
+ console.warn('Unknown menu action:', key)
225
+ }
226
+ } catch (error) {
227
+ console.error('Menu action failed:', error)
228
+ }
229
+ }, 10) // Small delay to ensure menu is closed
230
+ }
231
+ }
232
+
233
+ useEffect(() => {
234
+ const handleContextMenu = (event) => {
235
+ const target = event.target
236
+ if (isInputElement(target)) {
237
+ event.preventDefault()
238
+ event.stopPropagation()
239
+ setPosition({ x: event.clientX, y: event.clientY })
240
+ setTargetElement(target)
241
+ setVisible(true)
242
+ } else {
243
+ setVisible(false)
244
+ setTargetElement(null)
245
+ }
246
+ }
247
+
248
+ const handleClick = (event) => {
249
+ if (visible && menuRef.current && !menuRef.current.contains(event.target)) {
250
+ setVisible(false)
251
+ setTargetElement(null)
252
+ }
253
+ }
254
+
255
+ document.addEventListener('contextmenu', handleContextMenu, true)
256
+ document.addEventListener('click', handleClick, true)
257
+ return () => {
258
+ document.removeEventListener('contextmenu', handleContextMenu, true)
259
+ document.removeEventListener('click', handleClick, true)
260
+ }
261
+ }, [visible])
262
+ const items = createContextMenuItems(targetElement) || []
263
+ const menuItems = items.map(item => {
264
+ const IconComponent = item.icon && iconsMap[item.icon]
265
+ return {
266
+ ...item,
267
+ icon: IconComponent ? <IconComponent /> : null
268
+ }
269
+ })
270
+ return (
271
+ <div
272
+ ref={menuRef}
273
+ style={{
274
+ position: 'fixed',
275
+ left: position.x,
276
+ top: position.y,
277
+ zIndex: 9999
278
+ }}
279
+ >
280
+ <Dropdown
281
+ menu={{
282
+ items: menuItems,
283
+ onClick: handleMenuClick
284
+ }}
285
+ open={visible}
286
+ placement='bottomLeft'
287
+ >
288
+ <div style={{ width: 1, height: 1 }} />
289
+ </Dropdown>
290
+ </div>
291
+ )
292
+ }
293
+
294
+ export default InputContextMenu
@@ -0,0 +1,66 @@
1
+ import { useEffect } from 'react'
2
+ import { useDelta, useConditionalEffect } from 'react-delta-hooks'
3
+ import eq from 'fast-deep-equal'
4
+
5
+ const opacityDomId = 'opacity-style'
6
+
7
+ /**
8
+ * Opacity component
9
+ * Handles conditional CSS rendering based on opacity setting
10
+ * @param {Object} props
11
+ * @param {number} props.opacity - Opacity value from store.config
12
+ * @returns {null}
13
+ */
14
+ export default function Opacity ({ opacity }) {
15
+ // Default to 1 if opacity is not provided
16
+ const currentOpacity = opacity !== undefined ? opacity : 1
17
+ const delta = useDelta(currentOpacity)
18
+
19
+ function applyOpacity () {
20
+ let styleElement = document.getElementById(opacityDomId)
21
+
22
+ // Create style element if it doesn't exist
23
+ if (!styleElement) {
24
+ styleElement = document.createElement('style')
25
+ styleElement.id = opacityDomId
26
+ document.head.appendChild(styleElement)
27
+ }
28
+
29
+ // Update style content based on opacity value
30
+ if (currentOpacity === 1) {
31
+ styleElement.innerHTML = ''
32
+ window.pre.runGlobalAsync('setBackgroundColor', '#333333')
33
+ } else {
34
+ window.pre.runGlobalAsync('setBackgroundColor', '#33333300')
35
+ styleElement.innerHTML = `
36
+ html {
37
+ background: transparent !important;
38
+ }
39
+ body {
40
+ background: transparent !important;
41
+ }
42
+ #outside-context {
43
+ opacity: ${currentOpacity} !important;
44
+ }
45
+ `
46
+ }
47
+ }
48
+
49
+ useEffect(() => {
50
+ applyOpacity()
51
+
52
+ // Cleanup function
53
+ return () => {
54
+ const styleElement = document.getElementById(opacityDomId)
55
+ if (styleElement) {
56
+ document.head.removeChild(styleElement)
57
+ }
58
+ }
59
+ }, [])
60
+
61
+ useConditionalEffect(() => {
62
+ applyOpacity()
63
+ }, delta && !eq(delta.prev, delta.curr))
64
+
65
+ return null
66
+ }
@@ -1,11 +1,10 @@
1
- @require '../../css/includes/theme-default'
2
1
  .transports-wrap
3
2
  position absolute
4
3
  left 60px
5
4
  top 3px
6
5
  right 60px
7
6
  height 36px
8
- color text
7
+
9
8
  .transports-circle-wrap
10
9
  text-align center
11
10
  position relative
@@ -16,8 +15,8 @@
16
15
  right 0
17
16
  overflow-y scroll
18
17
  z-index 11
19
- background main
20
- box-shadow 0px 0px 3px 3px alpha(main, .5)
18
+ background var(--main)
19
+
21
20
  .transports-wrap
22
21
  .transports-dd
23
22
  display none
@@ -25,7 +24,7 @@
25
24
  display block
26
25
 
27
26
  .transports-title
28
- border-bottom 1px solid primary
27
+ border-bottom 1px solid var(--primary)
29
28
  padding 10px 15px
30
29
  .transports-content
31
30
  max-height 200px
@@ -34,11 +33,9 @@
34
33
  display flex
35
34
  padding 8px 5px
36
35
  &:hover
37
- background main-dark
36
+ background var(--main-darker)
38
37
  .sftp-transport .transfer-control-icon:hover
39
- color primary
40
- .transports-count
41
- color text
38
+ color var(--primary)
42
39
 
43
40
  .flex-child + .flex-child
44
41
  margin-left 5px
@@ -1,13 +1,10 @@
1
- @require '../../css/includes/theme-default'
2
-
3
1
  .main-footer
4
- background main
2
+ background var(--main)
5
3
  height 36px
6
4
  position absolute
7
5
  left 44px
8
6
  right 0
9
7
  bottom 0
10
- color text
11
8
  z-index 200
12
9
  .pinned
13
10
  .main-footer
@@ -52,4 +49,4 @@
52
49
  right 0
53
50
  bottom 0
54
51
  z-index 100
55
- background main
52
+ background var(--main)
@@ -1,8 +1,7 @@
1
- @require '../../css/includes/theme-default'
2
1
  .layout-wrap
3
2
  .layout-item
4
3
  position absolute
5
- background main
4
+ background var(--main)
6
5
  overflow hidden
7
6
  &.drag-over
8
- border 2px solid #08c
7
+ border 2px solid var(--primary)
@@ -20,9 +20,9 @@ const troubleshootContent = {
20
20
  windows: 'path\\to\\electerm.exe'
21
21
  },
22
22
  clearConfig: {
23
- mac: 'rm -rf ~/Library/Application\\ Support/electerm/users/default_user/electerm.data.nedb',
24
- linux: 'rm -rf ~/.config/electerm/users/default_user/electerm.data.nedb',
25
- windows: 'Delete C:\\Users\\your-user-name\\AppData\\Roaming\\electerm\\users\\default_user\\electerm.data.nedb'
23
+ mac: 'rm -rf ~/Library/Application\\ Support/electerm/users/default_user/electerm_data.db && rm -rf ~/Library/Application\\ Support/electerm/users/default_user/electerm.data.nedb',
24
+ linux: 'rm -rf ~/.config/electerm/users/default_user/electerm_data.db && rm -rf ~/.config/electerm/users/default_user/electerm.data.nedb',
25
+ windows: 'Delete C:\\Users\\your-user-name\\AppData\\Roaming\\electerm\\users\\default_user\\electerm_data.db && Delete C:\\Users\\your-user-name\\AppData\\Roaming\\electerm\\users\\default_user\\electerm.data.nedb'
26
26
  },
27
27
  clearData: {
28
28
  mac: 'rm -rf ~/Library/Application\\ Support/electerm*',
@@ -28,6 +28,8 @@ import ConnectionHoppingWarning from './connection-hopping-warnning'
28
28
  import SshConfigLoadNotify from '../ssh-config/ssh-config-load-notify'
29
29
  import LoadSshConfigs from '../ssh-config/load-ssh-configs'
30
30
  import AIChat from '../ai/ai-chat'
31
+ import Opacity from '../common/opacity'
32
+ import InputContextMenu from '../common/input-context-menu'
31
33
  import { pick } from 'lodash-es'
32
34
  import deepCopy from 'json-deep-copy'
33
35
  import './wrapper.styl'
@@ -152,11 +154,6 @@ export default auto(function Index (props) {
152
154
  const themeProps = {
153
155
  themeConfig: store.getUiThemeConfig()
154
156
  }
155
- const outerProps = {
156
- style: {
157
- opacity: config.opacity
158
- }
159
- }
160
157
  const copiedTransfer = deepCopy(fileTransfers)
161
158
  const copiedHistory = deepCopy(transferHistory)
162
159
  const sidebarProps = {
@@ -252,6 +249,7 @@ export default auto(function Index (props) {
252
249
  theme={uiThemeConfig}
253
250
  >
254
251
  <div {...ext1}>
252
+ <InputContextMenu />
255
253
  <ShortcutControl config={config} />
256
254
  <TermFullscreenControl
257
255
  terminalFullScreen={terminalFullScreen}
@@ -260,10 +258,10 @@ export default auto(function Index (props) {
260
258
  {...confsCss}
261
259
  wsInited={wsInited}
262
260
  />
261
+ <Opacity opacity={config.opacity} />
263
262
  <TerminalInteractive />
264
263
  <UiTheme
265
264
  {...themeProps}
266
- buildTheme={store.buildTheme}
267
265
  />
268
266
  <CustomCss customCss={config.customCss} />
269
267
  <TextEditor />
@@ -277,7 +275,6 @@ export default auto(function Index (props) {
277
275
  <BatchOp {...batchOpProps} />
278
276
  <div
279
277
  id='outside-context'
280
- {...outerProps}
281
278
  >
282
279
  <Sidebar {...sidebarProps} />
283
280
  <Layout
@@ -13,7 +13,7 @@
13
13
  top 10px
14
14
  position fixed
15
15
  z-index 100
16
- background rgba(45, 245, 108, 0.8)
16
+ background #2df56c
17
17
  // Hide all sessions first
18
18
  .session-wrap
19
19
  display none
@@ -5,20 +5,60 @@
5
5
  import { useEffect } from 'react'
6
6
  import { useDelta, useConditionalEffect } from 'react-delta-hooks'
7
7
  import eq from 'fast-deep-equal'
8
+ import isColorDark from '../../common/is-color-dark'
8
9
 
9
10
  const themeDomId = 'theme-css'
10
11
 
12
+ function darker (color, amount = 0.1) {
13
+ let usePound = false
14
+
15
+ if (color[0] === '#') {
16
+ color = color.slice(1)
17
+ usePound = true
18
+ }
19
+
20
+ const num = parseInt(color, 16)
21
+
22
+ let r = (num >> 16) - Math.round(255 * amount)
23
+ if (r < 0) r = 0
24
+ let b = ((num >> 8) & 0x00FF) - Math.round(255 * amount)
25
+ if (b < 0) b = 0
26
+ let g = (num & 0x0000FF) - Math.round(255 * amount)
27
+ if (g < 0) g = 0
28
+
29
+ return (usePound ? '#' : '') + (g | (b << 8) | (r << 16)).toString(16)
30
+ }
31
+
32
+ function buildTheme (themeConfig) {
33
+ const keys = Object.keys(themeConfig || {})
34
+ const themeCss = keys.map(key => {
35
+ const val = themeConfig[key]
36
+ if (key === 'primary') {
37
+ const contrast = isColorDark(val) ? '#fff' : '#000'
38
+ return `--${key}-contrast: ${contrast};\n--${key}: ${val};`
39
+ } else if (key === 'main') {
40
+ const darkerMain = darker(val, 0.3)
41
+ const lighterMain = darker(val, -0.3)
42
+ return `--${key}-darker: ${darkerMain};\n--${key}-lighter: ${lighterMain};\n--${key}: ${val};`
43
+ }
44
+ return `--${key}: ${val};`
45
+ }).join('\n')
46
+ if (themeCss) {
47
+ const css = `:root {\n${themeCss}\n}\n`
48
+ return Promise.resolve(css)
49
+ }
50
+ return Promise.resolve('')
51
+ }
52
+
11
53
  export default function UiTheme (props) {
12
- const { themeConfig, buildTheme } = props
54
+ const { themeConfig } = props
13
55
 
14
56
  const delta = useDelta(themeConfig)
15
57
 
16
58
  async function applyTheme () {
17
- const stylus = document.getElementById(themeDomId)
18
- const {
19
- stylusCss
20
- } = await buildTheme(themeConfig)
21
- stylus.innerHTML = stylusCss
59
+ const style = document.getElementById(themeDomId)
60
+ const css = await buildTheme(themeConfig)
61
+ style.innerHTML = css
22
62
  }
23
63
 
24
64
  useEffect(() => {