@electerm/electerm-react 3.15.46 → 3.15.58

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.
@@ -46,116 +46,121 @@ async function callBackendAIchatWithTools (messages, config) {
46
46
  }
47
47
 
48
48
  export async function runAgentLoop (chatEntry, config, abortRef, setIsStreaming) {
49
- const messages = [
50
- { role: 'system', content: buildAgentSystemPrompt(config) },
51
- { role: 'user', content: chatEntry.prompt }
52
- ]
53
- const toolCallsLog = []
54
- let accumulatedContent = ''
55
-
56
- setIsStreaming(true)
57
- updateChatEntry(chatEntry, {
58
- toolCalls: [],
59
- response: ''
60
- })
61
-
62
- for (let iteration = 0; iteration < MAX_ITERATIONS; iteration++) {
63
- if (abortRef && abortRef.current) {
64
- setIsStreaming(false)
65
- updateChatEntry(chatEntry, {
66
- response: accumulatedContent + '\n\n*(Agent stopped by user)*'
67
- })
68
- return
69
- }
70
-
71
- const result = await callBackendAIchatWithTools(messages, config)
49
+ window.store.agentRunning = true
50
+ try {
51
+ const messages = [
52
+ { role: 'system', content: buildAgentSystemPrompt(config) },
53
+ { role: 'user', content: chatEntry.prompt }
54
+ ]
55
+ const toolCallsLog = []
56
+ let accumulatedContent = ''
57
+
58
+ setIsStreaming(true)
59
+ updateChatEntry(chatEntry, {
60
+ toolCalls: [],
61
+ response: ''
62
+ })
63
+
64
+ for (let iteration = 0; iteration < MAX_ITERATIONS; iteration++) {
65
+ if (abortRef && abortRef.current) {
66
+ setIsStreaming(false)
67
+ updateChatEntry(chatEntry, {
68
+ response: accumulatedContent + '\n\n*(Agent stopped by user)*'
69
+ })
70
+ return
71
+ }
72
72
 
73
- if (result.error) {
74
- setIsStreaming(false)
75
- updateChatEntry(chatEntry, {
76
- response: accumulatedContent + `\n\n**Error:** ${result.error}`
77
- })
78
- return
79
- }
73
+ const result = await callBackendAIchatWithTools(messages, config)
80
74
 
81
- const assistantMessage = result.message
82
- if (!assistantMessage) {
83
- setIsStreaming(false)
84
- updateChatEntry(chatEntry, {
85
- response: accumulatedContent || 'No response from AI.'
86
- })
87
- return
88
- }
75
+ if (result.error) {
76
+ setIsStreaming(false)
77
+ updateChatEntry(chatEntry, {
78
+ response: accumulatedContent + `\n\n**Error:** ${result.error}`
79
+ })
80
+ return
81
+ }
89
82
 
90
- messages.push(assistantMessage)
83
+ const assistantMessage = result.message
84
+ if (!assistantMessage) {
85
+ setIsStreaming(false)
86
+ updateChatEntry(chatEntry, {
87
+ response: accumulatedContent || 'No response from AI.'
88
+ })
89
+ return
90
+ }
91
91
 
92
- if (assistantMessage.content) {
93
- accumulatedContent += (accumulatedContent ? '\n\n' : '') + assistantMessage.content
94
- updateChatEntry(chatEntry, {
95
- response: accumulatedContent
96
- })
97
- }
92
+ messages.push(assistantMessage)
98
93
 
99
- if (!assistantMessage.tool_calls || assistantMessage.tool_calls.length === 0) {
100
- setIsStreaming(false)
101
- updateChatEntry(chatEntry, {
102
- response: accumulatedContent
103
- })
104
- return
105
- }
94
+ if (assistantMessage.content) {
95
+ accumulatedContent += (accumulatedContent ? '\n\n' : '') + assistantMessage.content
96
+ updateChatEntry(chatEntry, {
97
+ response: accumulatedContent
98
+ })
99
+ }
106
100
 
107
- for (const toolCall of assistantMessage.tool_calls) {
108
- if (abortRef && abortRef.current) {
101
+ if (!assistantMessage.tool_calls || assistantMessage.tool_calls.length === 0) {
109
102
  setIsStreaming(false)
110
103
  updateChatEntry(chatEntry, {
111
- response: accumulatedContent + '\n\n*(Agent stopped by user)*'
104
+ response: accumulatedContent
112
105
  })
113
106
  return
114
107
  }
115
108
 
116
- let args
117
- try {
118
- args = JSON.parse(toolCall.function.arguments)
119
- } catch {
120
- args = {}
121
- }
109
+ for (const toolCall of assistantMessage.tool_calls) {
110
+ if (abortRef && abortRef.current) {
111
+ setIsStreaming(false)
112
+ updateChatEntry(chatEntry, {
113
+ response: accumulatedContent + '\n\n*(Agent stopped by user)*'
114
+ })
115
+ return
116
+ }
117
+
118
+ let args
119
+ try {
120
+ args = JSON.parse(toolCall.function.arguments)
121
+ } catch {
122
+ args = {}
123
+ }
124
+
125
+ const toolEntry = {
126
+ id: toolCall.id,
127
+ name: toolCall.function.name,
128
+ args,
129
+ status: 'running',
130
+ result: null
131
+ }
132
+ toolCallsLog.push(toolEntry)
133
+ updateChatEntry(chatEntry, {
134
+ toolCalls: [...toolCallsLog]
135
+ })
122
136
 
123
- const toolEntry = {
124
- id: toolCall.id,
125
- name: toolCall.function.name,
126
- args,
127
- status: 'running',
128
- result: null
129
- }
130
- toolCallsLog.push(toolEntry)
131
- updateChatEntry(chatEntry, {
132
- toolCalls: [...toolCallsLog]
133
- })
134
-
135
- let toolResult
136
- try {
137
- toolResult = await executeToolCall(toolCall.function.name, args)
138
- toolEntry.status = 'completed'
139
- toolEntry.result = toolResult
140
- } catch (err) {
141
- toolEntry.status = 'error'
142
- toolEntry.result = err.message
143
- }
137
+ let toolResult
138
+ try {
139
+ toolResult = await executeToolCall(toolCall.function.name, args)
140
+ toolEntry.status = 'completed'
141
+ toolEntry.result = toolResult
142
+ } catch (err) {
143
+ toolEntry.status = 'error'
144
+ toolEntry.result = err.message
145
+ }
144
146
 
145
- updateChatEntry(chatEntry, {
146
- toolCalls: [...toolCallsLog]
147
- })
147
+ updateChatEntry(chatEntry, {
148
+ toolCalls: [...toolCallsLog]
149
+ })
148
150
 
149
- messages.push({
150
- role: 'tool',
151
- tool_call_id: toolCall.id,
152
- content: toolEntry.result
153
- })
151
+ messages.push({
152
+ role: 'tool',
153
+ tool_call_id: toolCall.id,
154
+ content: toolEntry.result
155
+ })
156
+ }
154
157
  }
155
- }
156
158
 
157
- setIsStreaming(false)
158
- updateChatEntry(chatEntry, {
159
- response: accumulatedContent + '\n\n*(Agent reached maximum iterations)*'
160
- })
159
+ setIsStreaming(false)
160
+ updateChatEntry(chatEntry, {
161
+ response: accumulatedContent + '\n\n*(Agent reached maximum iterations)*'
162
+ })
163
+ } finally {
164
+ window.store.agentRunning = false
165
+ }
161
166
  }
@@ -173,7 +173,6 @@ export default function AIChatHistoryItem ({ item }) {
173
173
  {showOutput ? <CaretDownOutlined /> : <CaretRightOutlined />}
174
174
  </span>
175
175
  <span>{prompt}</span>
176
- {renderStopButton()}
177
176
  </div>
178
177
  ),
179
178
  type: 'info'
@@ -244,6 +243,7 @@ export default function AIChatHistoryItem ({ item }) {
244
243
  </div>
245
244
  {renderToolCalls()}
246
245
  {showOutput && <AIOutput item={item} />}
246
+ {renderStopButton()}
247
247
  </div>
248
248
  )
249
249
  }
@@ -25,6 +25,7 @@ export default function AIChat (props) {
25
25
  const [prompt, setPrompt] = useState('')
26
26
  const [mode, setMode] = useState(() => getItem(aiChatModeLsKey) || 'ask')
27
27
  const isAgent = mode === 'agent'
28
+ const submitDisabled = isAgent && props.agentRunning
28
29
 
29
30
  function handlePromptChange (e) {
30
31
  setPrompt(e.target.value)
@@ -103,6 +104,14 @@ export default function AIChat (props) {
103
104
  }
104
105
 
105
106
  function renderSendIcon () {
107
+ if (submitDisabled) {
108
+ return (
109
+ <SendOutlined
110
+ className='mg1l send-to-ai-icon disabled'
111
+ title='Agent is running, please wait'
112
+ />
113
+ )
114
+ }
106
115
  return (
107
116
  <SendOutlined
108
117
  onClick={handleSubmit}
@@ -132,7 +141,9 @@ export default function AIChat (props) {
132
141
  const handleKeyPress = (e) => {
133
142
  if (!e.shiftKey) {
134
143
  e.preventDefault()
135
- handleSubmit()
144
+ if (!submitDisabled) {
145
+ handleSubmit()
146
+ }
136
147
  }
137
148
  }
138
149
 
@@ -1,3 +1,4 @@
1
+ import { useRef, useEffect } from 'react'
1
2
  import ReactMarkdown from 'react-markdown'
2
3
  import { copy } from '../../common/clipboard'
3
4
  import Link from '../common/external-link'
@@ -8,12 +9,20 @@ import getBrand from './get-brand'
8
9
  const e = window.translate
9
10
 
10
11
  export default function AIOutput ({ item }) {
12
+ const outputRef = useRef(null)
11
13
  const {
12
14
  response,
13
15
  baseURLAI,
14
16
  nameAI,
15
17
  modelAI
16
18
  } = item
19
+
20
+ useEffect(() => {
21
+ if (outputRef.current) {
22
+ outputRef.current.scrollTop = outputRef.current.scrollHeight
23
+ }
24
+ }, [response])
25
+
17
26
  if (!response) {
18
27
  return null
19
28
  }
@@ -103,9 +112,11 @@ export default function AIOutput ({ item }) {
103
112
  }
104
113
 
105
114
  return (
106
- <div className='pd1'>
107
- {renderBrand()}
108
- <ReactMarkdown {...mdProps} />
115
+ <div className='ai-stream-output' ref={outputRef}>
116
+ <div className='pd1'>
117
+ {renderBrand()}
118
+ <ReactMarkdown {...mdProps} />
119
+ </div>
109
120
  </div>
110
121
  )
111
122
  }
@@ -14,6 +14,12 @@
14
14
  overflow-x hidden
15
15
 
16
16
  .chat-history-item
17
+ position relative
18
+ > .ai-stop-icon-square
19
+ position absolute
20
+ bottom 8px
21
+ right 8px
22
+ z-index 10
17
23
  .code-block
18
24
  border 1px dashed var(--main-darker)
19
25
  padding 5px
@@ -26,6 +32,10 @@
26
32
  // .code-block-actions
27
33
  // display block
28
34
 
35
+ .ai-stream-output
36
+ max-height 400px
37
+ overflow-y auto
38
+
29
39
  .ai-chat-input
30
40
  position relative
31
41
  margin-top 10px
@@ -53,6 +63,10 @@
53
63
  white-space pre-wrap
54
64
  word-break break-all
55
65
 
66
+ .send-to-ai-icon.disabled
67
+ opacity 0.4
68
+ cursor not-allowed
69
+
56
70
  .clear-ai-icon
57
71
  position relative
58
72
  &::after
@@ -232,7 +232,8 @@ export default auto(function Index (props) {
232
232
  tabs: store.getTabs(),
233
233
  activeTabId: store.activeTabId,
234
234
  showAIConfig: store.showAIConfig,
235
- rightPanelTab
235
+ rightPanelTab,
236
+ agentRunning: store.agentRunning
236
237
  }
237
238
  const cmdSuggestionsProps = {
238
239
  suggestions: store.terminalCommandSuggestions
@@ -11,7 +11,6 @@ import {
11
11
  settingMap,
12
12
  modals
13
13
  } from '../../common/constants'
14
-
15
14
  const TabBookmarks = lazy(() => import('./tab-bookmarks'))
16
15
  const TabQuickCommands = lazy(() => import('./tab-quick-commands'))
17
16
  const TabSettings = lazy(() => import('./tab-settings'))
@@ -1,7 +1,8 @@
1
1
  import { syncTypes } from '../../common/constants'
2
2
  import { useState } from 'react'
3
- import { LoadingOutlined, ReloadOutlined } from '@ant-design/icons'
3
+ import { LoadingOutlined, ReloadOutlined, DiffOutlined } from '@ant-design/icons'
4
4
  import dayjs from 'dayjs'
5
+ import SyncDataCompare from './sync-data-compare'
5
6
 
6
7
  const e = window.translate
7
8
 
@@ -9,6 +10,7 @@ export default function ServerDataStatus (props) {
9
10
  const { store } = window
10
11
  const { type, status } = props
11
12
  const [loading, setLoading] = useState(false)
13
+ const [showCompare, setShowCompare] = useState(false)
12
14
  const token = store.getSyncToken(type)
13
15
  const gistId = store.getSyncGistId(type)
14
16
  const canSync = token && (gistId || type === 'custom' || type === 'cloud' || type === syncTypes.webdav)
@@ -20,6 +22,10 @@ export default function ServerDataStatus (props) {
20
22
  setLoading(false)
21
23
  }
22
24
 
25
+ function handleCompare () {
26
+ setShowCompare(!showCompare)
27
+ }
28
+
23
29
  function renderReloadButton () {
24
30
  if (loading) {
25
31
  return (
@@ -27,10 +33,19 @@ export default function ServerDataStatus (props) {
27
33
  )
28
34
  }
29
35
  return (
30
- <ReloadOutlined
31
- className='pointer mg1l hover-black'
32
- onClick={handleReload}
33
- />
36
+ <span>
37
+ <ReloadOutlined
38
+ className='pointer mg1r hover-black'
39
+ onClick={handleReload}
40
+ />
41
+ <span
42
+ className='pointer mg2l hover-black'
43
+ onClick={handleCompare}
44
+ >
45
+ <DiffOutlined className='mg1r' />
46
+ {e('compare') || 'compare'}
47
+ </span>
48
+ </span>
34
49
  )
35
50
  }
36
51
 
@@ -59,14 +74,17 @@ export default function ServerDataStatus (props) {
59
74
  } = status
60
75
 
61
76
  return (
62
- <p>
63
- <span className='mg1r'>{e('syncServerDataStatus')}:</span>
64
- <b className='mg1r'>{dayjs(lastSyncTime).format('YYYY-MM-DD HH:mm:ss')}</b>
65
- <span className='mg1r'>{e('from')}:</span>
66
- <b className='mg1r'>{deviceName}</b>
67
- <b className='mg1r'>(v{electermVersion})</b>
68
- {renderReloadButton()}
69
- </p>
77
+ <div>
78
+ <p>
79
+ <span className='mg1r'>{e('syncServerDataStatus')}:</span>
80
+ <b className='mg1r'>{dayjs(lastSyncTime).format('YYYY-MM-DD HH:mm:ss')}</b>
81
+ <span className='mg1r'>{e('from')}:</span>
82
+ <b className='mg1r'>{deviceName}</b>
83
+ <b className='mg1r'>(v{electermVersion})</b>
84
+ {renderReloadButton()}
85
+ </p>
86
+ {showCompare && <SyncDataCompare syncType={type} />}
87
+ </div>
70
88
  )
71
89
  }
72
90
 
@@ -0,0 +1,92 @@
1
+ /**
2
+ * Sync data comparison component
3
+ * Shows simple diff suggestions
4
+ */
5
+
6
+ import { useState, useEffect } from 'react'
7
+ import { Spin } from 'antd'
8
+
9
+ const e = window.translate
10
+
11
+ export default function SyncDataCompare (props) {
12
+ const { store } = window
13
+ const { syncType } = props
14
+ const [loading, setLoading] = useState(false)
15
+ const [comparison, setComparison] = useState(null)
16
+
17
+ useEffect(() => {
18
+ loadComparison()
19
+ }, [syncType])
20
+
21
+ async function loadComparison () {
22
+ setLoading(true)
23
+ try {
24
+ const result = await store.previewServerDataWithCompare(syncType)
25
+ setComparison(result)
26
+ } catch (err) {
27
+ console.error('Failed to load comparison:', err)
28
+ }
29
+ setLoading(false)
30
+ }
31
+
32
+ if (!comparison) {
33
+ return null
34
+ }
35
+
36
+ const { comparison: comp } = comparison
37
+
38
+ // Filter only items with differences
39
+ const diffs = comp.filter(item => item.onlyLocal > 0 || item.onlyServer > 0)
40
+
41
+ if (diffs.length === 0) {
42
+ return (
43
+ <p className='mg1t sync-diff-text'>
44
+ {e('dataInSync') || 'Data in sync'}
45
+ </p>
46
+ )
47
+ }
48
+
49
+ const nameMap = {
50
+ bookmarks: e('bookmarks') || 'Bookmarks',
51
+ bookmarkGroups: 'Bookmark Groups',
52
+ terminalThemes: e('terminalThemes') || 'Terminal Themes',
53
+ quickCommands: e('quickCommands') || 'Quick Commands',
54
+ profiles: e('profiles') || 'Profiles',
55
+ addressBookmarks: e('addressBookmarks') || 'Address Bookmarks',
56
+ workspaces: e('workspaces') || 'Workspaces'
57
+ }
58
+
59
+ const lines = diffs.map(item => {
60
+ const displayName = nameMap[item.name] || item.name
61
+ const localCount = item.localCount
62
+ const serverCount = item.serverCount
63
+ const diff = serverCount - localCount
64
+ let action = ''
65
+ if (diff > 0) {
66
+ action = e('download') || 'download'
67
+ } else if (diff < 0) {
68
+ action = e('upload') || 'upload'
69
+ }
70
+ return {
71
+ text: `${e('remote') || 'remote'}: ${serverCount} ${displayName}, ${e('local') || 'local'}: ${localCount} ${displayName}`,
72
+ action
73
+ }
74
+ })
75
+
76
+ return (
77
+ <div className='sync-data-compare mg1t mg2b'>
78
+ <Spin spinning={loading}>
79
+ <div className='sync-diff-text'>
80
+ {lines.map((line, i) => (
81
+ <p key={i} className='mg0'>
82
+ {line.text}
83
+ {line.action && (
84
+ <span className='sync-suggest-action'> {'->'} {line.action} ?</span>
85
+ )}
86
+ </p>
87
+ ))}
88
+ </div>
89
+ </Spin>
90
+ </div>
91
+ )
92
+ }
@@ -90,7 +90,7 @@ export default function TermInteractiveUI ({
90
90
  type='primary'
91
91
  onClick={onConfirm}
92
92
  >
93
- {opts.options.submitText || e('submit')}
93
+ {opts.options.submitText || e('save')}
94
94
  </Button>
95
95
  <Button
96
96
  className='mg1l'
@@ -40,4 +40,9 @@ a
40
40
  color var(--text-dark)
41
41
 
42
42
  .cap
43
- text-transform capitalize
43
+ text-transform capitalize
44
+
45
+ .sync-data-compare
46
+ .sync-suggest-action
47
+ color var(--success)
48
+ font-weight 500
@@ -87,6 +87,7 @@ export default () => {
87
87
  // batch input selected tab ids
88
88
  _batchInputSelectedTabIds: new Set(),
89
89
  aiChatHistory: [],
90
+ agentRunning: false,
90
91
 
91
92
  // sftp
92
93
  fileOperation: fileOperationsMap.cp, // cp or mv
@@ -24,6 +24,9 @@ export default Store => {
24
24
  const current = !store.pinned
25
25
  ls.setItem(sidebarPinnedKey, current + '')
26
26
  store.pinned = current
27
+ if (!current) {
28
+ store.setOpenedSideBar('')
29
+ }
27
30
  }
28
31
 
29
32
  Store.prototype.handleSidebarPanelTab = function (tab) {
@@ -234,7 +234,7 @@ export default (Store) => {
234
234
  const status = statusContent ? parseJsonSafe(statusContent) : undefined
235
235
  store.syncServerStatus[type] = status
236
236
  }
237
- return
237
+ return gist
238
238
  }
239
239
 
240
240
  const gist = await fetchData(
@@ -245,6 +245,103 @@ export default (Store) => {
245
245
  store.getSyncProxy(type)
246
246
  )
247
247
  updateSyncServerStatusFromGist(store, gist, type)
248
+ return gist
249
+ }
250
+
251
+ Store.prototype.previewServerDataWithCompare = async function (type) {
252
+ const { store } = window
253
+ const token = store.getSyncToken(type)
254
+ const gistId = store.getSyncGistId(type)
255
+ const pass = store.getSyncPassword(type)
256
+ const { names } = store.getDataSyncNames()
257
+
258
+ // Get server data
259
+ let serverGist
260
+ if (type === syncTypes.webdav) {
261
+ serverGist = await fetchData(
262
+ type,
263
+ 'download',
264
+ [],
265
+ token,
266
+ store.getSyncProxy(type)
267
+ )
268
+ } else {
269
+ serverGist = await fetchData(
270
+ type,
271
+ 'getOne',
272
+ [gistId],
273
+ token,
274
+ store.getSyncProxy(type)
275
+ )
276
+ }
277
+
278
+ // Update status
279
+ if (type === syncTypes.webdav) {
280
+ if (serverGist && serverGist.files) {
281
+ const statusContent = get(serverGist, 'files["electerm-status.json"].content')
282
+ const status = statusContent ? parseJsonSafe(statusContent) : undefined
283
+ store.syncServerStatus[type] = status
284
+ }
285
+ } else {
286
+ updateSyncServerStatusFromGist(store, serverGist, type)
287
+ }
288
+
289
+ // Compare data
290
+ const comparison = []
291
+ const localData = {}
292
+ const serverData = {}
293
+
294
+ for (const n of names) {
295
+ // Get local data
296
+ const localItems = store.getItems(n)
297
+ localData[n] = localItems
298
+
299
+ // Get server data
300
+ let serverStr
301
+ if (type === syncTypes.webdav) {
302
+ serverStr = get(serverGist, `files["${n}.json"].content`)
303
+ } else {
304
+ serverStr = get(serverGist, `files["${n}.json"].content`)
305
+ }
306
+
307
+ let serverItems = []
308
+ if (serverStr) {
309
+ try {
310
+ if (!isJSON(serverStr)) {
311
+ serverStr = await window.pre.runGlobalAsync('decryptAsync', serverStr, pass)
312
+ }
313
+ serverItems = JSON.parse(serverStr)
314
+ } catch (e) {
315
+ console.error(`Failed to parse server data for ${n}:`, e)
316
+ }
317
+ }
318
+ serverData[n] = serverItems
319
+
320
+ // Find unique items
321
+ const localIds = new Set(localItems.map(item => item.id))
322
+ const serverIds = new Set(serverItems.map(item => item.id))
323
+
324
+ const onlyLocal = localItems.filter(item => !serverIds.has(item.id))
325
+ const onlyServer = serverItems.filter(item => !localIds.has(item.id))
326
+ const common = localItems.filter(item => serverIds.has(item.id))
327
+
328
+ comparison.push({
329
+ name: n,
330
+ localCount: localItems.length,
331
+ serverCount: serverItems.length,
332
+ onlyLocal: onlyLocal.length,
333
+ onlyServer: onlyServer.length,
334
+ common: common.length,
335
+ localItems: onlyLocal,
336
+ serverItems: onlyServer
337
+ })
338
+ }
339
+
340
+ return {
341
+ localData,
342
+ serverData,
343
+ comparison
344
+ }
248
345
  }
249
346
 
250
347
  Store.prototype.uploadSettingAction = async function (type) {
@@ -21,51 +21,45 @@ import dataCompare from '../common/data-compare'
21
21
 
22
22
  export default store => {
23
23
  for (const name of dbNamesForWatch) {
24
- window[`watch${name}Running`] = false
25
24
  window[`watch${name}`] = autoRun(async () => {
26
25
  const n = store.getItems(name)
27
- if (window.migrating || window[`watch${name}Running`]) {
26
+ if (window.migrating) {
28
27
  return
29
28
  }
30
- window[`watch${name}Running`] = true
31
- try {
32
- const old = refsStatic.get('oldState-' + name)
33
- const { updated, added, removed } = dataCompare(
34
- old,
35
- n
29
+ const old = refsStatic.get('oldState-' + name)
30
+ const { updated, added, removed } = dataCompare(
31
+ old,
32
+ n
33
+ )
34
+ await Promise.all([
35
+ ...removed.map(item => remove(name, item.id)),
36
+ ...updated.map(item => update(item.id, item, name, false)),
37
+ added.length ? insert(name, added) : Promise.resolve()
38
+ ])
39
+ const newOrder = (n || []).map(d => d.id)
40
+ await update(
41
+ `${name}:order`,
42
+ newOrder
43
+ )
44
+ refsStatic.add('oldState-' + name, deepCopy(n) || [])
45
+ if (name === 'bookmarks') {
46
+ store.bookmarksMap = new Map(
47
+ n.map(d => [d.id, d])
36
48
  )
37
- await Promise.all([
38
- ...removed.map(item => remove(name, item.id)),
39
- ...updated.map(item => update(item.id, item, name, false)),
40
- added.length ? insert(name, added) : Promise.resolve()
41
- ])
42
- const newOrder = (n || []).map(d => d.id)
43
- await update(
44
- `${name}:order`,
45
- newOrder
46
- )
47
- refsStatic.add('oldState-' + name, deepCopy(n) || [])
48
- if (name === 'bookmarks') {
49
- store.bookmarksMap = new Map(
50
- n.map(d => [d.id, d])
51
- )
52
- }
53
- await store.updateLastDataUpdateTime()
54
- if (dbNamesForSync.includes(name)) {
55
- const syncSetting = store.config.syncSetting || {}
56
- const { autoSync, autoSyncInterval, autoSyncDirection } = syncSetting
57
- if (autoSync && autoSyncInterval === 0) {
58
- if (autoSyncDirection === 'download') {
59
- await store.downloadSettingAll()
60
- } else {
61
- await store.uploadSettingAll()
62
- }
49
+ }
50
+ await store.updateLastDataUpdateTime()
51
+ if (dbNamesForSync.includes(name)) {
52
+ const syncSetting = store.config.syncSetting || {}
53
+ const { autoSync, autoSyncInterval, autoSyncDirection } = syncSetting
54
+ if (autoSync && autoSyncInterval === 0) {
55
+ if (autoSyncDirection === 'download') {
56
+ await store.downloadSettingAll()
57
+ } else {
58
+ await store.uploadSettingAll()
63
59
  }
64
60
  }
65
- return store[name]
66
- } finally {
67
- window[`watch${name}Running`] = false
68
61
  }
62
+ return store[name]
69
63
  })
70
64
  window[`watch${name}`].start()
71
65
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@electerm/electerm-react",
3
- "version": "3.15.46",
3
+ "version": "3.15.58",
4
4
  "description": "react components src for electerm",
5
5
  "main": "./client/components/main/main.jsx",
6
6
  "license": "MIT",