浏览代码

Add prettier formatter; update dependencies.

oldj 4 年之前
父节点
当前提交
d4989e27cb
共有 100 个文件被更改,包括 2117 次插入1590 次删除
  1. 10 0
      .babelrc
  2. 2 0
      .prettierignore
  3. 19 0
      .prettierrc.json
  4. 443 307
      package-lock.json
  5. 42 41
      package.json
  6. 43 43
      src/common/data.d.ts
  7. 39 18
      src/common/hostsFn.ts
  8. 7 5
      src/common/i18n/index.ts
  9. 16 8
      src/common/i18n/languages/en.ts
  10. 172 164
      src/common/i18n/languages/fr.ts
  11. 8 4
      src/common/i18n/languages/zh.ts
  12. 17 10
      src/common/normalize.ts
  13. 23 23
      src/common/types.d.ts
  14. 1 1
      src/common/utils/wait.ts
  15. 2 2
      src/main/actions/cmd/deleteHistory.ts
  16. 15 12
      src/main/actions/cmd/tryToRun.ts
  17. 1 1
      src/main/actions/config/get.ts
  18. 4 1
      src/main/actions/config/set.ts
  19. 1 2
      src/main/actions/config/update.ts
  20. 1 1
      src/main/actions/find/addHistory.ts
  21. 1 1
      src/main/actions/find/addReplaceHistory.ts
  22. 7 4
      src/main/actions/find/findBy.ts
  23. 12 1
      src/main/actions/find/findPositionsInContent.ts
  24. 1 1
      src/main/actions/find/getHistory.ts
  25. 1 1
      src/main/actions/find/getReplaceHistory.ts
  26. 3 3
      src/main/actions/find/setHistory.ts
  27. 8 3
      src/main/actions/find/splitContent.ts
  28. 5 3
      src/main/actions/getBasicData.ts
  29. 1 1
      src/main/actions/hosts/deleteHistory.ts
  30. 16 10
      src/main/actions/hosts/getContent.ts
  31. 3 3
      src/main/actions/hosts/getPathOfSystemHostsPath.ts
  32. 1 1
      src/main/actions/hosts/refresh.ts
  33. 4 2
      src/main/actions/hosts/setContent.ts
  34. 65 44
      src/main/actions/hosts/setSystemHosts.ts
  35. 1 1
      src/main/actions/list/getContentOfList.ts
  36. 1 1
      src/main/actions/list/moveItemToTrashcan.ts
  37. 2 1
      src/main/actions/migrate/checkIfMigration.ts
  38. 9 8
      src/main/actions/migrate/export.ts
  39. 6 4
      src/main/actions/migrate/import.ts
  40. 5 1
      src/main/actions/migrate/importFromUrl.ts
  41. 1 1
      src/main/actions/ping.ts
  42. 4 4
      src/main/actions/trashcan/clear.ts
  43. 7 5
      src/main/actions/trashcan/deleteItem.ts
  44. 6 4
      src/main/actions/trashcan/restoreItem.ts
  45. 2 2
      src/main/actions/updateTrayTitle.ts
  46. 4 3
      src/main/core/message.ts
  47. 1 1
      src/main/core/popupMenu.ts
  48. 1 5
      src/main/data/index.ts
  49. 12 8
      src/main/http/api/list.ts
  50. 10 3
      src/main/http/index.ts
  51. 7 11
      src/main/libs/cron.ts
  52. 8 4
      src/main/libs/request.ts
  53. 6 4
      src/main/libs/safePSWD.ts
  54. 3 3
      src/main/libs/tracer.ts
  55. 3 5
      src/main/main.ts
  56. 4 3
      src/main/preload.ts
  57. 15 15
      src/main/types.d.ts
  58. 2 5
      src/main/ui/find.ts
  59. 24 18
      src/main/ui/menu.ts
  60. 37 31
      src/main/ui/tray/index.ts
  61. 2 5
      src/main/ui/tray/window.ts
  62. 2 2
      src/renderer/app.tsx
  63. 14 11
      src/renderer/components/About/AboutContent.tsx
  64. 2 2
      src/renderer/components/About/index.tsx
  65. 7 6
      src/renderer/components/BrowserLink.tsx
  66. 88 70
      src/renderer/components/EditHostsInfo.tsx
  67. 86 49
      src/renderer/components/Editor/HostsEditor.tsx
  68. 2 2
      src/renderer/components/Editor/cm_hl.ts
  69. 53 45
      src/renderer/components/History.tsx
  70. 8 6
      src/renderer/components/HostsViewer.tsx
  71. 8 10
      src/renderer/components/ItemIcon.tsx
  72. 2 2
      src/renderer/components/Lang.tsx
  73. 4 4
      src/renderer/components/LeftPanel/SystemHostsItem.tsx
  74. 21 15
      src/renderer/components/LeftPanel/Trashcan.tsx
  75. 11 12
      src/renderer/components/LeftPanel/TrashcanItem.tsx
  76. 5 8
      src/renderer/components/LeftPanel/index.tsx
  77. 26 14
      src/renderer/components/List/ListItem.tsx
  78. 95 52
      src/renderer/components/List/index.tsx
  79. 2 6
      src/renderer/components/Loading.tsx
  80. 19 12
      src/renderer/components/MainPanel/index.tsx
  81. 11 11
      src/renderer/components/Pref/Advanced.tsx
  82. 8 8
      src/renderer/components/Pref/Commands.tsx
  83. 17 13
      src/renderer/components/Pref/CommandsHistory.tsx
  84. 37 25
      src/renderer/components/Pref/General.tsx
  85. 8 8
      src/renderer/components/Pref/Proxy.tsx
  86. 10 15
      src/renderer/components/Pref/index.tsx
  87. 7 13
      src/renderer/components/StatusBar.tsx
  88. 25 23
      src/renderer/components/SudoPasswordInput.tsx
  89. 12 8
      src/renderer/components/SwitchButton.tsx
  90. 34 30
      src/renderer/components/TopBar/ConfigMenu.tsx
  91. 23 21
      src/renderer/components/TopBar/ImportFromUrl.tsx
  92. 39 32
      src/renderer/components/TopBar/index.tsx
  93. 39 33
      src/renderer/components/Transfer.tsx
  94. 86 76
      src/renderer/components/Tree/Node.tsx
  95. 43 23
      src/renderer/components/Tree/Tree.tsx
  96. 69 35
      src/renderer/components/Tree/fn.ts
  97. 4 5
      src/renderer/core/PopupMenu.ts
  98. 7 4
      src/renderer/core/agent.ts
  99. 5 1
      src/renderer/core/useOnBroadcast.ts
  100. 1 1
      src/renderer/models/useConfigs.ts

+ 10 - 0
.babelrc

@@ -0,0 +1,10 @@
+{
+  "plugins": [
+    [
+      "@babel/plugin-proposal-private-methods",
+      {
+        "loose": true
+      }
+    ]
+  ]
+}

+ 2 - 0
.prettierignore

@@ -0,0 +1,2 @@
+src/renderer/.umi
+src/renderer/.umi-production

+ 19 - 0
.prettierrc.json

@@ -0,0 +1,19 @@
+{
+  "arrowParens": "always",
+  "bracketSpacing": true,
+  "embeddedLanguageFormatting": "auto",
+  "endOfLine": "lf",
+  "htmlWhitespaceSensitivity": "css",
+  "insertPragma": false,
+  "jsxBracketSameLine": false,
+  "jsxSingleQuote": false,
+  "printWidth": 80,
+  "proseWrap": "preserve",
+  "quoteProps": "as-needed",
+  "requirePragma": false,
+  "semi": false,
+  "singleQuote": true,
+  "tabWidth": 2,
+  "trailingComma": "all",
+  "useTabs": false
+}

文件差异内容过多而无法显示
+ 443 - 307
package-lock.json


+ 42 - 41
package.json

@@ -18,72 +18,73 @@
   "dependencies": {
     "axios": "^0.21.1",
     "compare-versions": "^3.6.0",
-    "dayjs": "^1.10.5",
+    "dayjs": "^1.10.6",
     "electron-window-state": "^5.0.3",
     "express": "^4.17.1",
     "lodash": "^4.17.21",
     "md5": "^2.3.0",
     "md5-file": "^5.0.0",
     "mkdirp": "^1.0.4",
-    "potdb": "^2.0.1",
+    "potdb": "^2.0.4",
     "uuid": "^8.3.2"
   },
   "devDependencies": {
-    "@babel/plugin-proposal-class-properties": "^7.13.0",
-    "@babel/plugin-proposal-decorators": "^7.14.2",
-    "@babel/preset-env": "^7.14.4",
-    "@babel/preset-typescript": "^7.13.0",
-    "@chakra-ui/icons": "^1.0.13",
-    "@chakra-ui/react": "^1.6.3",
-    "@emotion/react": "^11.4.0",
+    "@babel/plugin-proposal-class-properties": "^7.14.5",
+    "@babel/plugin-proposal-decorators": "^7.14.5",
+    "@babel/preset-env": "^7.15.0",
+    "@babel/preset-typescript": "^7.15.0",
+    "@chakra-ui/icons": "^1.0.15",
+    "@chakra-ui/react": "^1.6.6",
+    "@emotion/react": "^11.4.1",
     "@emotion/styled": "^11.3.0",
-    "@types/assert": "^1.5.4",
-    "@types/codemirror": "^5.60.0",
-    "@types/express": "^4.17.12",
-    "@types/lodash": "^4.14.170",
-    "@types/md5": "^2.3.0",
-    "@types/mkdirp": "^1.0.1",
-    "@types/mocha": "^8.2.2",
+    "@types/assert": "^1.5.5",
+    "@types/codemirror": "^5.60.2",
+    "@types/express": "^4.17.13",
+    "@types/lodash": "^4.14.172",
+    "@types/md5": "^2.3.1",
+    "@types/mkdirp": "^1.0.2",
+    "@types/mocha": "^9.0.0",
     "@types/node": "^15.6.1",
-    "@types/react": "^17.0.8",
-    "@types/react-dom": "^17.0.5",
-    "@types/react-virtualized-auto-sizer": "^1.0.0",
-    "@types/react-window": "^1.8.3",
-    "@types/uuid": "^8.3.0",
+    "@types/react": "^17.0.19",
+    "@types/react-dom": "^17.0.9",
+    "@types/react-virtualized-auto-sizer": "^1.0.1",
+    "@types/react-window": "^1.8.5",
+    "@types/uuid": "^8.3.1",
     "@umijs/preset-react": "1.x",
-    "@umijs/test": "^3.4.23",
-    "ahooks": "^2.10.4",
+    "@umijs/test": "^3.5.17",
+    "ahooks": "^2.10.9",
     "babel-loader": "^8.2.2",
     "clsx": "^1.1.1",
-    "codemirror": "^5.61.1",
-    "concurrently": "^6.2.0",
-    "copy-webpack-plugin": "^9.0.0",
+    "codemirror": "^5.62.3",
+    "concurrently": "^6.2.1",
+    "copy-webpack-plugin": "^9.0.1",
     "cross-env": "^7.0.3",
     "dotenv": "^10.0.0",
-    "electron": "^13.0.1",
-    "electron-builder": "^22.10.5",
-    "electron-notarize": "^1.0.0",
+    "electron": "^13.2.1",
+    "electron-builder": "^22.11.7",
+    "electron-notarize": "^1.1.0",
     "espower-typescript": "^10.0.0",
-    "execa": "^5.0.0",
-    "fork-ts-checker-webpack-plugin": "^6.2.10",
+    "execa": "^5.1.1",
+    "fork-ts-checker-webpack-plugin": "^6.3.2",
     "framer-motion": "^4.1.17",
     "fs-extra": "^10.0.0",
-    "mocha": "^8.4.0",
+    "mocha": "^9.1.0",
     "power-assert": "^1.6.1",
+    "prettier": "^2.3.2",
     "pretty-bytes": "^5.6.0",
-    "react": "^16.8.6",
-    "react-dom": "^16.8.6",
+    "react": "^17.0.2",
+    "react-dom": "^17.0.2",
     "react-icons": "^4.2.0",
-    "react-virtualized-auto-sizer": "^1.0.5",
+    "react-virtualized-auto-sizer": "^1.0.6",
     "react-window": "^1.8.6",
     "smooth-scroll-into-view-if-needed": "^1.1.32",
-    "ts-node": "^10.0.0",
+    "ts-node": "^10.2.1",
     "tsconfig-paths-webpack-plugin": "^3.5.1",
-    "typescript": "^4.3.2",
-    "umi": "^3.4.23",
-    "webpack": "^5.38.1",
-    "webpack-cli": "^4.7.0",
-    "webpack-merge": "^5.7.3",
+    "typescript": "^4.3.5",
+    "umi": "^3.5.17",
+    "webpack": "^5.51.1",
+    "webpack-cli": "^4.8.0",
+    "webpack-merge": "^5.8.0",
     "yorkie": "^2.0.0"
   },
   "build": {

+ 43 - 43
src/common/data.d.ts

@@ -1,81 +1,81 @@
 import { ITreeNodeData } from '@renderer/components/Tree/Node'
 
-export type HostsType = 'local' | 'remote' | 'group' | 'folder';
+export type HostsType = 'local' | 'remote' | 'group' | 'folder'
 export type FolderModeType = 0 | 1 | 2 // 0: 默认; 1: 单选; 2: 多选
 
 export interface IHostsListObject {
-  id: string;
-  title?: string;
-  on?: boolean;
-  type?: HostsType;
+  id: string
+  title?: string
+  on?: boolean
+  type?: HostsType
 
   // remote
-  url?: string;
-  last_refresh?: string;
-  last_refresh_ms?: number;
-  refresh_interval?: number; // 单位:秒
+  url?: string
+  last_refresh?: string
+  last_refresh_ms?: number
+  refresh_interval?: number // 单位:秒
 
   // group
-  include?: string[];
+  include?: string[]
 
   // folder
-  folder_mode?: FolderModeType;
-  folder_open?: boolean;
-  children?: IHostsListObject[];
+  folder_mode?: FolderModeType
+  folder_open?: boolean
+  children?: IHostsListObject[]
 
-  is_sys?: boolean;
+  is_sys?: boolean
 
-  [key: string]: any;
+  [key: string]: any
 }
 
 export interface IHostsContentObject {
-  id: string;
-  content: string;
+  id: string
+  content: string
 
-  [key: string]: any;
+  [key: string]: any
 }
 
 export interface ITrashcanObject {
-  data: IHostsListObject;
-  add_time_ms: number;
-  parent_id: string | null;
+  data: IHostsListObject
+  add_time_ms: number
+  parent_id: string | null
 }
 
 export interface ITrashcanListObject extends ITrashcanObject, ITreeNodeData {
-  id: string;
-  children?: ITrashcanListObject[];
-  is_root?: boolean;
-  type?: HostsType | 'trashcan';
+  id: string
+  children?: ITrashcanListObject[]
+  is_root?: boolean
+  type?: HostsType | 'trashcan'
 
-  [key: string]: any;
+  [key: string]: any
 }
 
 export interface IHostsHistoryObject {
-  id: string;
-  content: string;
-  add_time_ms: number;
-  label?: string;
+  id: string
+  content: string
+  add_time_ms: number
+  label?: string
 }
 
-export type VersionType = [ number, number, number, number ]
+export type VersionType = [number, number, number, number]
 
 export interface IHostsBasicData {
-  list: IHostsListObject[];
-  trashcan: ITrashcanObject[];
-  version: VersionType;
+  list: IHostsListObject[]
+  trashcan: ITrashcanObject[]
+  version: VersionType
 }
 
 export interface IOperationResult {
-  success: boolean;
-  message?: string;
-  data?: any;
-  code?: string | number;
+  success: boolean
+  message?: string
+  data?: any
+  code?: string | number
 }
 
 export interface ICommandRunResult {
-  _id?: string;
-  success: boolean;
-  stdout: string;
-  stderr: string;
-  add_time_ms: number;
+  _id?: string
+  success: boolean
+  stdout: string
+  stderr: string
+  add_time_ms: number
 }

+ 39 - 18
src/common/hostsFn.ts

@@ -3,20 +3,24 @@
  * @homepage: https://oldj.net
  */
 
-import { FolderModeType, IHostsBasicData, IHostsListObject } from '@root/common/data'
+import {
+  FolderModeType,
+  IHostsBasicData,
+  IHostsListObject,
+} from '@root/common/data'
 import lodash from 'lodash'
 
 type PartHostsObjectType = Partial<IHostsListObject> & { id: string }
 
-type Predicate = (obj: IHostsListObject) => boolean;
+type Predicate = (obj: IHostsListObject) => boolean
 
 export const flatten = (list: IHostsListObject[]): IHostsListObject[] => {
   let new_list: IHostsListObject[] = []
 
-  list.map(item => {
+  list.map((item) => {
     new_list.push(item)
     if (item.children) {
-      new_list = [ ...new_list, ...flatten(item.children) ]
+      new_list = [...new_list, ...flatten(item.children)]
     }
   })
 
@@ -26,7 +30,7 @@ export const flatten = (list: IHostsListObject[]): IHostsListObject[] => {
 export const cleanHostsList = (data: IHostsBasicData): IHostsBasicData => {
   let list = flatten(data.list)
 
-  list.map(item => {
+  list.map((item) => {
     if (item.type === 'folder' && !Array.isArray(item.children)) {
       item.children = [] as IHostsListObject[]
     }
@@ -43,11 +47,17 @@ export const cleanHostsList = (data: IHostsBasicData): IHostsBasicData => {
   return data
 }
 
-export const findItemById = (list: IHostsListObject[], id: string): IHostsListObject | undefined => {
-  return flatten(list).find(item => item.id === id)
+export const findItemById = (
+  list: IHostsListObject[],
+  id: string,
+): IHostsListObject | undefined => {
+  return flatten(list).find((item) => item.id === id)
 }
 
-export const updateOneItem = (list: IHostsListObject[], item: PartHostsObjectType): IHostsListObject[] => {
+export const updateOneItem = (
+  list: IHostsListObject[],
+  item: PartHostsObjectType,
+): IHostsListObject[] => {
   let new_list: IHostsListObject[] = lodash.cloneDeep(list)
 
   let i = findItemById(new_list, item.id)
@@ -59,10 +69,15 @@ export const updateOneItem = (list: IHostsListObject[], item: PartHostsObjectTyp
 }
 
 const isInTopLevel = (list: IHostsListObject[], id: string): boolean => {
-  return list.findIndex(i => i.id === id) > -1
+  return list.findIndex((i) => i.id === id) > -1
 }
 
-export const setOnStateOfItem = (list: IHostsListObject[], id: string, on: boolean, default_choice_mode: FolderModeType = 0): IHostsListObject[] => {
+export const setOnStateOfItem = (
+  list: IHostsListObject[],
+  id: string,
+  on: boolean,
+  default_choice_mode: FolderModeType = 0,
+): IHostsListObject[] => {
   let new_list: IHostsListObject[] = lodash.cloneDeep(list)
 
   let item = findItemById(new_list, id)
@@ -74,7 +89,7 @@ export const setOnStateOfItem = (list: IHostsListObject[], id: string, on: boole
 
   if (isInTopLevel(list, id)) {
     if (default_choice_mode === 1) {
-      new_list.map(item => {
+      new_list.map((item) => {
         if (item.id !== id) {
           item.on = false
         }
@@ -86,7 +101,7 @@ export const setOnStateOfItem = (list: IHostsListObject[], id: string, on: boole
       let folder_mode = parent.folder_mode || default_choice_mode
       if (folder_mode === 1 && parent.children) {
         // 单选模式
-        parent.children.map(item => {
+        parent.children.map((item) => {
           if (item.id !== id) {
             item.on = false
           }
@@ -99,13 +114,13 @@ export const setOnStateOfItem = (list: IHostsListObject[], id: string, on: boole
 }
 
 export const deleteItemById = (list: IHostsListObject[], id: string) => {
-  let idx = list.findIndex(item => item.id === id)
+  let idx = list.findIndex((item) => item.id === id)
   if (idx >= 0) {
     list.splice(idx, 1)
     return
   }
 
-  list.map(item => deleteItemById(item.children || [], id))
+  list.map((item) => deleteItemById(item.children || [], id))
 }
 
 // export const getNextSelectedItem = (list: IHostsListObject[], id: string): IHostsListObject | undefined => {
@@ -115,7 +130,10 @@ export const deleteItemById = (list: IHostsListObject[], id: string) => {
 //   return flat[idx + 1] || flat[idx - 1]
 // }
 
-export const getNextSelectedItem = (tree: IHostsListObject[], predicate: Predicate): IHostsListObject | undefined => {
+export const getNextSelectedItem = (
+  tree: IHostsListObject[],
+  predicate: Predicate,
+): IHostsListObject | undefined => {
   let flat = flatten(tree)
   let idx_1 = -1
   let idx_2 = -1
@@ -132,15 +150,18 @@ export const getNextSelectedItem = (tree: IHostsListObject[], predicate: Predica
   return flat[idx_2 + 1] || flat[idx_1 - 1]
 }
 
-export const getParentOfItem = (list: IHostsListObject[], item_id: string): IHostsListObject | undefined => {
-  if (list.find(i => i.id === item_id)) {
+export const getParentOfItem = (
+  list: IHostsListObject[],
+  item_id: string,
+): IHostsListObject | undefined => {
+  if (list.find((i) => i.id === item_id)) {
     // is in the top level
     return
   }
 
   let flat = flatten(list)
   for (let p of flat) {
-    if (p.children && p.children.find(i => i.id === item_id)) {
+    if (p.children && p.children.find((i) => i.id === item_id)) {
       return p
     }
   }

+ 7 - 5
src/common/i18n/index.ts

@@ -28,11 +28,14 @@ export class I18N {
 
     const _this = this
 
-    this.lang = new Proxy({}, {
-      get(obj, key: LanguageKey) {
-        return _this.trans(key)
+    this.lang = new Proxy(
+      {},
+      {
+        get(obj, key: LanguageKey) {
+          return _this.trans(key)
+        },
       },
-    }) as LanguageDict
+    ) as LanguageDict
   }
 
   trans(key: LanguageKey, words?: string[]) {
@@ -54,4 +57,3 @@ export class I18N {
     return s
   }
 }
-

+ 16 - 8
src/common/i18n/languages/en.ts

@@ -16,7 +16,8 @@ export default {
   check_update: 'Check update',
   choice_mode: 'Choice mode',
   choice_mode_default: 'Default',
-  choice_mode_desc: 'Only valid for the topmost item, each folder can set its own choice mode.',
+  choice_mode_desc:
+    'Only valid for the topmost item, each folder can set its own choice mode.',
   choice_mode_multiple: 'Multiple',
   choice_mode_single: 'Single',
   choices: 'Choices',
@@ -26,7 +27,8 @@ export default {
   close: 'Close',
   colon: ': ',
   commands: 'Commands',
-  commands_help: 'The following system commands will be executed when Hosts applied:',
+  commands_help:
+    'The following system commands will be executed when Hosts applied:',
   commands_title: 'Command after a hosts be applied',
   comment_current_line: 'Comment current line',
   content: 'Content',
@@ -68,7 +70,8 @@ export default {
   hour: 'hour',
   hours: 'hours',
   http_api_on: 'HTTP API on',
-  http_api_on_desc: 'Runs on port {0}, can be used by third-party software such as Alfred to switch hosts.',
+  http_api_on_desc:
+    'Runs on port {0}, can be used by third-party software such as Alfred to switch hosts.',
   ignore_case: 'Ignore case',
   import: 'Import',
   import_done: 'The import is complete.',
@@ -86,7 +89,8 @@ export default {
   loading: 'Loading...',
   local: 'Local',
   match: 'Match',
-  migrate_confirm: 'SwitchHosts v4.0 uses a new data storage format, do you want to migrate old data to the new format?',
+  migrate_confirm:
+    'SwitchHosts v4.0 uses a new data storage format, do you want to migrate old data to the new format?',
   migrate_data: 'Migrate data',
   minimize: 'Minimize',
   minute: 'minute',
@@ -115,7 +119,8 @@ export default {
   reload: 'Reload',
   remote: 'Remote',
   remove_duplicate_records: 'Remove duplicate records',
-  remove_duplicate_records_desc: 'If a domain points to multiple IPs, only the first one will take effect, and the following ones will be converted into comments.',
+  remove_duplicate_records_desc:
+    'If a domain points to multiple IPs, only the first one will take effect, and the following ones will be converted into comments.',
   replace: 'Replace',
   replace_all: 'Replace all',
   replace_history: 'Replace history',
@@ -131,8 +136,10 @@ export default {
   sudo_prompt_title: 'Input your sudo password',
   system_hosts: 'System Hosts',
   system_hosts_history: 'History versions of the System Hosts',
-  system_hosts_history_delete_confirm: 'Are you sure you want to delete this item?',
-  system_hosts_history_help: 'If the total number of historical records exceeds this limit, the oldest record will be deleted.',
+  system_hosts_history_delete_confirm:
+    'Are you sure you want to delete this item?',
+  system_hosts_history_help:
+    'If the total number of historical records exceeds this limit, the oldest record will be deleted.',
   system_hosts_history_limit: 'Maximum number of records: ',
   test: 'Test',
   theme: 'Theme',
@@ -153,7 +160,8 @@ export default {
   untitled: 'Untitled',
   url_placeholder: 'http:// or https:// or file://',
   usage_data_agree: 'Yes, submit anonymized usage data',
-  usage_data_help: 'Would you like to help us improve SwitchHosts by periodically submitting anonymous usage data?',
+  usage_data_help:
+    'Would you like to help us improve SwitchHosts by periodically submitting anonymous usage data?',
   usage_data_title: 'Make SwitchHosts better!',
   use_proxy: 'Use proxy',
   view: 'View',

+ 172 - 164
src/common/i18n/languages/fr.ts

@@ -3,167 +3,175 @@
  * @homepage: https://oldj.net
  */
 
- export default {
-    _app_name: 'SwitchHosts',
-    _name: 'Français',
-    about: 'À propos',
-    acknowledgement: 'Remerciements',
-    advanced: 'Avancé',
-    all: 'Tout',
-    auto_refresh: 'Rafraîchissement automatique',
-    btn_cancel: 'Annuler',
-    btn_ok: 'OK',
-    check_update: 'Vérifier les mises à jour',
-    choice_mode: 'Choice mode',
-    choice_mode_default: 'Défaut',
-    choice_mode_desc: 'Uniquement valable pour l\'élément le plus haut, chaque dossier peut définir son propre mode.',
-    choice_mode_multiple: 'Multiple',
-    choice_mode_single: 'Seul',
-    choices: 'Choix',
-    chosen: 'Choisi',
-    clear_history: 'Effacer l\'historique',
-    click_to_open: 'Cliquer pour ouvrir',
-    close: 'Fermer',
-    colon: ' : ',
-    commands: 'Commandes',
-    commands_help: 'Les commandes systèmes suivantes seront exécutées quand l\'hosts sera activé :',
-    commands_title: 'Commandes une fois qu\'un hosts est activé',
-    comment_current_line: 'Commenter cette ligne',
-    content: 'Contenu',
-    copy: 'Copier',
-    cut: 'Couper',
-    day: 'jour',
-    days: 'jours',
-    delete: 'Supprimer',
-    download: 'Télécharger',
-    edit: 'Éditer',
-    export: 'Exporter',
-    export_done: 'L\'export est terminé.',
-    fail: 'Échec !',
-    feedback: 'Laisser un commentaire',
-    file: 'Fichier',
-    find: 'Rechercher',
-    find_all: 'Rechercher tout',
-    find_and_replace: 'Rechercher et remplacer',
-    find_history: 'Historique des recherches',
-    folder: 'Dossier',
-    front: 'Front',
-    general: 'Général',
-    group: 'Groupe',
-    help: 'Aide',
-    hide: 'Cacher',
-    hide_at_launch: 'Cacher au lancement',
-    hide_dock_icon: 'Cacher l\'icone dans le Dock',
-    hide_history: 'Cacher l\'historique',
-    hide_others: 'Cacher les autres',
-    homepage: 'Page d\'accueil',
-    host: 'Host',
-    hosts_add: 'Ajouter un nouvel hosts',
-    hosts_delete: 'Supprimer cet hosts',
-    hosts_delete_confirm: 'Êtes-vous sûr de vouloir supprimer cet hosts?',
-    hosts_edit: 'Éditer l\'hosts',
-    hosts_title: 'Titre de l\'hosts',
-    hosts_type: 'Type d\'hosts',
-    hosts_updated: 'Le fichier hosts a été mis à jour',
-    hour: 'heure',
-    hours: 'heures',
-    http_api_on: 'Activer HTTP API',
-    http_api_on_desc: 'Actif sur le port {0}, peut être utilisé par un logiciel tier comme Alfred pour changer d\'hosts',
-    ignore_case: 'Ignorer la casse',
-    import: 'Importer',
-    import_done: 'L\'importation est terminée',
-    import_fail: 'Échec de l\'importation !',
-    import_from_url: 'Importer à partir d\'une URL',
-    is_latest_version_inform: 'Super, vous avez la dernière version !',
-    item_found: '{0} élément trouvé.',
-    items: 'éléments',
-    items_found: '{0} éléments trouvés.',
-    language: 'Langage',
-    last_refresh: 'Dernier rafraîchissement : ',
-    latest_version_desc: 'La dernière version est : {0}',
-    line: 'ligne',
-    lines: 'lignes',
-    loading: 'Chargement...',
-    local: 'Local',
-    match: 'Correspondance',
-    migrate_confirm: 'SwitchHosts v4.0 utilise un nouveau format de stockage des données, voulez-vous migrer les anciennes données dans ce nouveau format ?',
-    migrate_data: 'Migrer les données',
-    minimize: 'Réduire',
-    minute: 'minute',
-    minutes: 'minutes',
-    move_items_to_trashcan: 'Déplacer {0} éléments dans la corbeille',
-    move_to_trashcan: 'Déplacer dans la corbeille',
-    need_to_relaunch: 'Besoin de redémarrer',
-    never: 'Jamais',
-    new: 'Nouveau',
-    new_version_found: 'Nouvelle version trouvée',
-    next: 'Suivant',
-    no_access_to_hosts: 'Aucune autorisation pour écrire dans le fichier hosts.',
-    no_record: 'Aucun enregistrement',
-    password: 'Mot de passe',
-    paste: 'Coller',
-    port: 'Port',
-    preferences: 'Préférences',
-    previous: 'Précédent',
-    protocol: 'Protocol',
-    proxy: 'Proxy',
-    quit: 'Quitter',
-    read_only: 'Lecture seule',
-    redo: 'Rétablir',
-    refresh: 'Rafraîchir',
-    regexp: 'Expression régulière',
-    reload: 'Recharger',
-    remote: 'Distant',
-    remove_duplicate_records: 'Supprimer les enregistrements doublons',
-    remove_duplicate_records_desc: 'Si un domaine pointe sur plusieurs IPs, seulement la première sera prise en compte, et les autres seront converties en commentaires.',
-    replace: 'Remplacer',
-    replace_all: 'Tout remplacer',
-    replace_history: 'Remplacer l\'historique',
-    reset_zoom: 'Réinitialiser le zoom',
-    search: 'Rechercher',
-    select_all: 'Tout sélectionner',
-    selected: 'Sélectionné',
-    show_history: 'Afficher l\'historique',
-    show_main_window: 'Afficher la fenêtre principale',
-    show_title_on_tray: 'Afficher le titre dans la barre des menus',
-    source_code: 'Code source',
-    success: 'Succès !',
-    sudo_prompt_title: 'Entrez votre mot de passe sudo',
-    system_hosts: 'Hosts du système',
-    system_hosts_history: 'Historique des versions hosts du système',
-    system_hosts_history_delete_confirm: 'Êtes-vous sûr de vouloir supprimer cet élément ?',
-    system_hosts_history_help: 'Si le nombre total d\'enregistrements dépasse cette limite, l\'enregistrement le plus ancien sera supprimé.',
-    system_hosts_history_limit: 'Nombre max. d\'enregistrements : ',
-    test: 'Test',
-    theme: 'Thème',
-    theme_dark: 'Sombre',
-    theme_light: 'Clair',
-    title: 'Titre',
-    to_show_source: 'Double-cliquez pour afficher le code source',
-    toggle_developer_tools: 'Afficher/Cacher le Developer Tools',
-    toggle_dock_icon: 'Afficher/Cacher l\'icone dans le Dock',
-    toggle_full_screen: 'Activer/Désactiver le plein écran',
-    trashcan: 'Corbeille',
-    trashcan_clear: 'Vider la corbeille',
-    trashcan_clear_confirm: 'Êtes-vous sûr de vouloir vider la corbeille ?',
-    trashcan_delete_confirm: 'Voulez-vous supprimer définitivement cet élément ?',
-    trashcan_restore: 'Restaurer',
-    undo: 'Annuler',
-    unhide: 'Démasquer',
-    untitled: 'Sans titre',
-    url_placeholder: 'http:// ou https:// ou file://',
-    usage_data_agree: 'Oui, soumettre de manière anonyme mes données d\'utilisation',
-    usage_data_help: 'Voulez-vous nous aider à améliorer SwitchHosts en soumettant périodiquement vos données d\'utilisation de manière anonyme ?',
-    usage_data_title: 'Rendez SwitchHosts meilleur !',
-    use_proxy: 'Utiliser un proxy',
-    view: 'Vue',
-    where_is_my_data: 'Où sont stockées mes données ?',
-    where_is_my_hosts: 'Où est mon fichier hosts ?',
-    window: 'Fenêtre',
-    your_data_is: 'Les fichiers contenant vos données sont stockés ici :',
-    your_hosts_file_is: 'Votre fichier hosts est situé ici :',
-    zoom: 'Zoom',
-    zoom_in: 'Zoommer',
-    zoom_out: 'Dézoommer',
-  }
-  
+export default {
+  _app_name: 'SwitchHosts',
+  _name: 'Français',
+  about: 'À propos',
+  acknowledgement: 'Remerciements',
+  advanced: 'Avancé',
+  all: 'Tout',
+  auto_refresh: 'Rafraîchissement automatique',
+  btn_cancel: 'Annuler',
+  btn_ok: 'OK',
+  check_update: 'Vérifier les mises à jour',
+  choice_mode: 'Choice mode',
+  choice_mode_default: 'Défaut',
+  choice_mode_desc:
+    "Uniquement valable pour l'élément le plus haut, chaque dossier peut définir son propre mode.",
+  choice_mode_multiple: 'Multiple',
+  choice_mode_single: 'Seul',
+  choices: 'Choix',
+  chosen: 'Choisi',
+  clear_history: "Effacer l'historique",
+  click_to_open: 'Cliquer pour ouvrir',
+  close: 'Fermer',
+  colon: ' : ',
+  commands: 'Commandes',
+  commands_help:
+    "Les commandes systèmes suivantes seront exécutées quand l'hosts sera activé :",
+  commands_title: "Commandes une fois qu'un hosts est activé",
+  comment_current_line: 'Commenter cette ligne',
+  content: 'Contenu',
+  copy: 'Copier',
+  cut: 'Couper',
+  day: 'jour',
+  days: 'jours',
+  delete: 'Supprimer',
+  download: 'Télécharger',
+  edit: 'Éditer',
+  export: 'Exporter',
+  export_done: "L'export est terminé.",
+  fail: 'Échec !',
+  feedback: 'Laisser un commentaire',
+  file: 'Fichier',
+  find: 'Rechercher',
+  find_all: 'Rechercher tout',
+  find_and_replace: 'Rechercher et remplacer',
+  find_history: 'Historique des recherches',
+  folder: 'Dossier',
+  front: 'Front',
+  general: 'Général',
+  group: 'Groupe',
+  help: 'Aide',
+  hide: 'Cacher',
+  hide_at_launch: 'Cacher au lancement',
+  hide_dock_icon: "Cacher l'icone dans le Dock",
+  hide_history: "Cacher l'historique",
+  hide_others: 'Cacher les autres',
+  homepage: "Page d'accueil",
+  host: 'Host',
+  hosts_add: 'Ajouter un nouvel hosts',
+  hosts_delete: 'Supprimer cet hosts',
+  hosts_delete_confirm: 'Êtes-vous sûr de vouloir supprimer cet hosts?',
+  hosts_edit: "Éditer l'hosts",
+  hosts_title: "Titre de l'hosts",
+  hosts_type: "Type d'hosts",
+  hosts_updated: 'Le fichier hosts a été mis à jour',
+  hour: 'heure',
+  hours: 'heures',
+  http_api_on: 'Activer HTTP API',
+  http_api_on_desc:
+    "Actif sur le port {0}, peut être utilisé par un logiciel tier comme Alfred pour changer d'hosts",
+  ignore_case: 'Ignorer la casse',
+  import: 'Importer',
+  import_done: "L'importation est terminée",
+  import_fail: "Échec de l'importation !",
+  import_from_url: "Importer à partir d'une URL",
+  is_latest_version_inform: 'Super, vous avez la dernière version !',
+  item_found: '{0} élément trouvé.',
+  items: 'éléments',
+  items_found: '{0} éléments trouvés.',
+  language: 'Langage',
+  last_refresh: 'Dernier rafraîchissement : ',
+  latest_version_desc: 'La dernière version est : {0}',
+  line: 'ligne',
+  lines: 'lignes',
+  loading: 'Chargement...',
+  local: 'Local',
+  match: 'Correspondance',
+  migrate_confirm:
+    'SwitchHosts v4.0 utilise un nouveau format de stockage des données, voulez-vous migrer les anciennes données dans ce nouveau format ?',
+  migrate_data: 'Migrer les données',
+  minimize: 'Réduire',
+  minute: 'minute',
+  minutes: 'minutes',
+  move_items_to_trashcan: 'Déplacer {0} éléments dans la corbeille',
+  move_to_trashcan: 'Déplacer dans la corbeille',
+  need_to_relaunch: 'Besoin de redémarrer',
+  never: 'Jamais',
+  new: 'Nouveau',
+  new_version_found: 'Nouvelle version trouvée',
+  next: 'Suivant',
+  no_access_to_hosts: 'Aucune autorisation pour écrire dans le fichier hosts.',
+  no_record: 'Aucun enregistrement',
+  password: 'Mot de passe',
+  paste: 'Coller',
+  port: 'Port',
+  preferences: 'Préférences',
+  previous: 'Précédent',
+  protocol: 'Protocol',
+  proxy: 'Proxy',
+  quit: 'Quitter',
+  read_only: 'Lecture seule',
+  redo: 'Rétablir',
+  refresh: 'Rafraîchir',
+  regexp: 'Expression régulière',
+  reload: 'Recharger',
+  remote: 'Distant',
+  remove_duplicate_records: 'Supprimer les enregistrements doublons',
+  remove_duplicate_records_desc:
+    'Si un domaine pointe sur plusieurs IPs, seulement la première sera prise en compte, et les autres seront converties en commentaires.',
+  replace: 'Remplacer',
+  replace_all: 'Tout remplacer',
+  replace_history: "Remplacer l'historique",
+  reset_zoom: 'Réinitialiser le zoom',
+  search: 'Rechercher',
+  select_all: 'Tout sélectionner',
+  selected: 'Sélectionné',
+  show_history: "Afficher l'historique",
+  show_main_window: 'Afficher la fenêtre principale',
+  show_title_on_tray: 'Afficher le titre dans la barre des menus',
+  source_code: 'Code source',
+  success: 'Succès !',
+  sudo_prompt_title: 'Entrez votre mot de passe sudo',
+  system_hosts: 'Hosts du système',
+  system_hosts_history: 'Historique des versions hosts du système',
+  system_hosts_history_delete_confirm:
+    'Êtes-vous sûr de vouloir supprimer cet élément ?',
+  system_hosts_history_help:
+    "Si le nombre total d'enregistrements dépasse cette limite, l'enregistrement le plus ancien sera supprimé.",
+  system_hosts_history_limit: "Nombre max. d'enregistrements : ",
+  test: 'Test',
+  theme: 'Thème',
+  theme_dark: 'Sombre',
+  theme_light: 'Clair',
+  title: 'Titre',
+  to_show_source: 'Double-cliquez pour afficher le code source',
+  toggle_developer_tools: 'Afficher/Cacher le Developer Tools',
+  toggle_dock_icon: "Afficher/Cacher l'icone dans le Dock",
+  toggle_full_screen: 'Activer/Désactiver le plein écran',
+  trashcan: 'Corbeille',
+  trashcan_clear: 'Vider la corbeille',
+  trashcan_clear_confirm: 'Êtes-vous sûr de vouloir vider la corbeille ?',
+  trashcan_delete_confirm: 'Voulez-vous supprimer définitivement cet élément ?',
+  trashcan_restore: 'Restaurer',
+  undo: 'Annuler',
+  unhide: 'Démasquer',
+  untitled: 'Sans titre',
+  url_placeholder: 'http:// ou https:// ou file://',
+  usage_data_agree:
+    "Oui, soumettre de manière anonyme mes données d'utilisation",
+  usage_data_help:
+    "Voulez-vous nous aider à améliorer SwitchHosts en soumettant périodiquement vos données d'utilisation de manière anonyme ?",
+  usage_data_title: 'Rendez SwitchHosts meilleur !',
+  use_proxy: 'Utiliser un proxy',
+  view: 'Vue',
+  where_is_my_data: 'Où sont stockées mes données ?',
+  where_is_my_hosts: 'Où est mon fichier hosts ?',
+  window: 'Fenêtre',
+  your_data_is: 'Les fichiers contenant vos données sont stockés ici :',
+  your_hosts_file_is: 'Votre fichier hosts est situé ici :',
+  zoom: 'Zoom',
+  zoom_in: 'Zoommer',
+  zoom_out: 'Dézoommer',
+}

+ 8 - 4
src/common/i18n/languages/zh.ts

@@ -87,7 +87,8 @@ const lang: LanguageDict = {
   loading: '加载中...',
   local: '本地',
   match: '匹配',
-  migrate_confirm: 'SwitchHosts v4.0 使用了新的数据存储格式,是否迁移旧数据为新格式?',
+  migrate_confirm:
+    'SwitchHosts v4.0 使用了新的数据存储格式,是否迁移旧数据为新格式?',
   migrate_data: '迁移数据',
   hide_dock_icon: '隐藏任务栏(Dock)图标',
   minimize: '最小化',
@@ -117,7 +118,8 @@ const lang: LanguageDict = {
   reload: '重载',
   remote: '远程',
   remove_duplicate_records: '移除重复的记录',
-  remove_duplicate_records_desc: '如果一个域名指向多个 IP,只有第一条会生效,后面的将被转为注释。',
+  remove_duplicate_records_desc:
+    '如果一个域名指向多个 IP,只有第一条会生效,后面的将被转为注释。',
   replace: '替换',
   replace_all: '替换所有',
   replace_history: '替换历史',
@@ -134,7 +136,8 @@ const lang: LanguageDict = {
   system_hosts: '系统 Hosts',
   system_hosts_history: '系统 Hosts 历史版本',
   system_hosts_history_delete_confirm: '确实要删除该项记录吗?',
-  system_hosts_history_help: '如果历史记录的总数超过这个限制,最老的记录将被删除。',
+  system_hosts_history_help:
+    '如果历史记录的总数超过这个限制,最老的记录将被删除。',
   system_hosts_history_limit: '最大记录数:',
   test: '测试',
   theme: '主题',
@@ -155,7 +158,8 @@ const lang: LanguageDict = {
   untitled: '未命名',
   url_placeholder: 'http:// 或 https:// 或 file://',
   usage_data_agree: '好的,发送匿名的使用数据',
-  usage_data_help: '您愿意发送匿名的使用数据来帮助我们改进 SwitchHosts 吗?数据中不会包含任何隐私信息。',
+  usage_data_help:
+    '您愿意发送匿名的使用数据来帮助我们改进 SwitchHosts 吗?数据中不会包含任何隐私信息。',
   usage_data_title: '帮助改进 SwitchHosts',
   use_proxy: '使用代理',
   view: '视图',

+ 17 - 10
src/common/normalize.ts

@@ -13,13 +13,13 @@ const default_options = {
 export type INormalizeOptions = Partial<typeof default_options>
 
 interface IHostsLineObj {
-  ip: string;
-  domains: string[];
-  comment: string;
+  ip: string
+  domains: string[]
+  comment: string
 }
 
 interface IDomainsIPMap {
-  [domain: string]: string;
+  [domain: string]: string
 }
 
 export const parseLine = (line: string): IHostsLineObj => {
@@ -44,7 +44,7 @@ const removeDuplicateRecords = (content: string): string => {
   let lines = content.split('\n')
   let new_lines: string[] = []
 
-  lines.map(line => {
+  lines.map((line) => {
     let { ip, domains, comment } = parseLine(line)
 
     if (!ip || domains.length === 0) {
@@ -56,7 +56,7 @@ const removeDuplicateRecords = (content: string): string => {
 
     let new_domains: string[] = []
     let duplicate_domains: string[] = []
-    domains.map(domain => {
+    domains.map((domain) => {
       const domain_v = `${domain}_${ipv}`
       if (domain_v in domain_ip_map) {
         duplicate_domains.push(domain)
@@ -70,16 +70,23 @@ const removeDuplicateRecords = (content: string): string => {
       new_lines.push(formatLine({ ip, domains: new_domains, comment }))
     }
     if (duplicate_domains.length > 0) {
-      new_lines.push(formatLine({
-        comment: 'invalid hosts (repeated): ' + formatLine({ ip, domains: duplicate_domains }),
-      }))
+      new_lines.push(
+        formatLine({
+          comment:
+            'invalid hosts (repeated): ' +
+            formatLine({ ip, domains: duplicate_domains }),
+        }),
+      )
     }
   })
 
   return new_lines.join(os.EOL)
 }
 
-export default (hosts_content: string, options: INormalizeOptions = {}): string => {
+export default (
+  hosts_content: string,
+  options: INormalizeOptions = {},
+): string => {
   // 在这儿执行去重等等操作
   if (options.remove_duplicate_records) {
     hosts_content = removeDuplicateRecords(hosts_content)

+ 23 - 23
src/common/types.d.ts

@@ -14,42 +14,42 @@ export type LanguageKey = keyof LanguageDict
 export interface IMenuItemOption extends MenuItemConstructorOptions {
   // 参见:https://www.electronjs.org/docs/api/menu-item
 
-  _click_evt?: string;
+  _click_evt?: string
 }
 
 export interface IPopupMenuOption {
-  menu_id: string;
-  items: IMenuItemOption[];
+  menu_id: string
+  items: IMenuItemOption[]
 }
 
 export interface IFindPosition {
-  start: number;
-  end: number;
-  line: number;
-  line_pos: number;
-  end_line: number;
-  end_line_pos: number;
-  before: string;
-  match: string;
-  after: string;
+  start: number
+  end: number
+  line: number
+  line_pos: number
+  end_line: number
+  end_line_pos: number
+  before: string
+  match: string
+  after: string
 }
 
 export interface IFindSpliter {
-  before: string;
-  match: string;
-  after: string;
-  replace?: string;
+  before: string
+  match: string
+  after: string
+  replace?: string
 }
 
 export interface IFindItem {
-  item_id: string;
-  item_title: string;
-  item_type: HostsType;
-  positions: IFindPosition[];
-  spliters: IFindSpliter[];
+  item_id: string
+  item_title: string
+  item_type: HostsType
+  positions: IFindPosition[]
+  spliters: IFindSpliter[]
 }
 
 export type IFindShowSourceParam = IFindPosition & {
-  item_id: string;
-  [key: string]: any;
+  item_id: string
+  [key: string]: any
 }

+ 1 - 1
src/common/utils/wait.ts

@@ -3,4 +3,4 @@
  * @homepage: https://oldj.net
  */
 
-export default (ms: number) => new Promise(resolve => setTimeout(resolve, ms))
+export default (ms: number) => new Promise((resolve) => setTimeout(resolve, ms))

+ 2 - 2
src/main/actions/cmd/deleteHistory.ts

@@ -5,6 +5,6 @@
 
 import { cfgdb } from '@main/data'
 
-export default async (_id: string)=> {
-  return await cfgdb.collection.cmd_history.delete(i => i._id === _id)
+export default async (_id: string) => {
+  return await cfgdb.collection.cmd_history.delete((i) => i._id === _id)
 }

+ 15 - 12
src/main/actions/cmd/tryToRun.ts

@@ -8,19 +8,20 @@ import { cfgdb } from '@main/data'
 import { ICommandRunResult } from '@root/common/data'
 import { exec } from 'child_process'
 
-const run = (cmd: string): Promise<ICommandRunResult> => new Promise(resolve => {
-  exec(cmd, (error, stdout, stderr) => {
-    // command output is in stdout
-    let success: boolean = !error
-
-    resolve({
-      success,
-      stdout,
-      stderr,
-      add_time_ms: (new Date()).getTime(),
+const run = (cmd: string): Promise<ICommandRunResult> =>
+  new Promise((resolve) => {
+    exec(cmd, (error, stdout, stderr) => {
+      // command output is in stdout
+      let success: boolean = !error
+
+      resolve({
+        success,
+        stdout,
+        stderr,
+        add_time_ms: new Date().getTime(),
+      })
     })
   })
-})
 
 export default async () => {
   let cmd = await cfgdb.dict.cfg.get('cmd_after_hosts_apply')
@@ -40,7 +41,9 @@ export default async () => {
   if (all.length > max_records) {
     let n = all.length - max_records
     for (let i = 0; i < n; i++) {
-      await cfgdb.collection.cmd_history.delete(item => item._id === all[i]._id)
+      await cfgdb.collection.cmd_history.delete(
+        (item) => item._id === all[i]._id,
+      )
     }
   }
 

+ 1 - 1
src/main/actions/config/get.ts

@@ -7,5 +7,5 @@ import { cfgdb } from '@main/data'
 import default_configs, { ConfigsType } from '@root/common/default_configs'
 
 export default async <K extends keyof ConfigsType>(key: K) => {
-  return await cfgdb.dict.cfg.get(key, default_configs[key]) as ConfigsType[K]
+  return (await cfgdb.dict.cfg.get(key, default_configs[key])) as ConfigsType[K]
 }

+ 4 - 1
src/main/actions/config/set.ts

@@ -6,7 +6,10 @@
 import { cfgdb } from '@main/data'
 import { ConfigsType } from '@root/common/default_configs'
 
-export default async <K extends keyof ConfigsType>(key: K, value: ConfigsType[K]) => {
+export default async <K extends keyof ConfigsType>(
+  key: K,
+  value: ConfigsType[K],
+) => {
   console.log(`config:store.set [${key}]: ${value}`)
   await cfgdb.dict.cfg.set(key, value)
 }

+ 1 - 2
src/main/actions/config/update.ts

@@ -32,8 +32,7 @@ export default async (data: Partial<ConfigsType>) => {
     if (data.hide_dock_icon) {
       app.dock.hide()
     } else {
-      app.dock.show()
-        .catch(e => console.error(e))
+      app.dock.show().catch((e) => console.error(e))
     }
   }
 }

+ 1 - 1
src/main/actions/find/addHistory.ts

@@ -12,7 +12,7 @@ export default async (data: IFindHistoryData) => {
   let history_all = await getHistory()
 
   // remove old
-  history_all = history_all.filter(i => i.value !== data.value)
+  history_all = history_all.filter((i) => i.value !== data.value)
 
   // insert new
   history_all.push(data)

+ 1 - 1
src/main/actions/find/addReplaceHistory.ts

@@ -12,7 +12,7 @@ export default async (value: string) => {
   let history_all = await getReplaceHistory()
 
   // remove old
-  history_all = history_all.filter(v => v !== value)
+  history_all = history_all.filter((v) => v !== value)
 
   // insert new
   history_all.push(value)

+ 7 - 4
src/main/actions/find/findBy.ts

@@ -11,11 +11,14 @@ import findInContent from 'src/main/actions/find/findPositionsInContent'
 import { getList } from '../index'
 
 export interface IFindOptions {
-  is_regexp: boolean;
-  is_ignore_case: boolean;
+  is_regexp: boolean
+  is_ignore_case: boolean
 }
 
-export default async (keyword: string, options: IFindOptions): Promise<IFindItem[]> => {
+export default async (
+  keyword: string,
+  options: IFindOptions,
+): Promise<IFindItem[]> => {
   console.log(keyword)
   let result_items: IFindItem[] = []
 
@@ -26,7 +29,7 @@ export default async (keyword: string, options: IFindOptions): Promise<IFindItem
   if (options.is_regexp) {
     exp = new RegExp(keyword, options.is_ignore_case ? 'ig' : 'g')
   } else {
-    let kw = keyword.replace(/([.^$([?*+])/ig, '\\$1')
+    let kw = keyword.replace(/([.^$([?*+])/gi, '\\$1')
     exp = new RegExp(kw, options.is_ignore_case ? 'ig' : 'g')
   }
 

+ 12 - 1
src/main/actions/find/findPositionsInContent.ts

@@ -5,7 +5,18 @@
 
 import { IFindPosition } from '@root/common/types'
 
-type MatchResult = Pick<IFindPosition, 'start' | 'end' | 'before' | 'match' | 'after' | 'line' | 'line_pos' | 'end_line' | 'end_line_pos'>
+type MatchResult = Pick<
+  IFindPosition,
+  | 'start'
+  | 'end'
+  | 'before'
+  | 'match'
+  | 'after'
+  | 'line'
+  | 'line_pos'
+  | 'end_line'
+  | 'end_line_pos'
+>
 
 export default (content: string, exp: RegExp): MatchResult[] => {
   let result_items: MatchResult[] = []

+ 1 - 1
src/main/actions/find/getHistory.ts

@@ -7,5 +7,5 @@ import { IFindHistoryData } from '@main/actions/find/setHistory'
 import { cfgdb } from '@main/data'
 
 export default async (): Promise<IFindHistoryData[]> => {
-  return await cfgdb.list.find_history.all() as IFindHistoryData[]
+  return (await cfgdb.list.find_history.all()) as IFindHistoryData[]
 }

+ 1 - 1
src/main/actions/find/getReplaceHistory.ts

@@ -6,5 +6,5 @@
 import { cfgdb } from '@main/data'
 
 export default async (): Promise<string[]> => {
-  return await cfgdb.list.replace_history.all() as string[]
+  return (await cfgdb.list.replace_history.all()) as string[]
 }

+ 3 - 3
src/main/actions/find/setHistory.ts

@@ -6,9 +6,9 @@
 import { cfgdb } from '@main/data'
 
 export interface IFindHistoryData {
-  value: string;
-  is_regexp: boolean;
-  is_ignore_case: boolean;
+  value: string
+  is_regexp: boolean
+  is_ignore_case: boolean
 }
 
 export default async (data: IFindHistoryData[]) => {

+ 8 - 3
src/main/actions/find/splitContent.ts

@@ -6,10 +6,13 @@
 import { IFindPosition, IFindSpliter } from '@root/common/types'
 
 type MatchResult = Pick<IFindPosition, 'start' | 'end' | 'match'> & {
-  [key: string]: any;
+  [key: string]: any
 }
 
-export default (content: string, find_results: MatchResult[]): IFindSpliter[] => {
+export default (
+  content: string,
+  find_results: MatchResult[],
+): IFindSpliter[] => {
   let spliters: IFindSpliter[] = []
 
   let last_end = 0
@@ -24,7 +27,9 @@ export default (content: string, find_results: MatchResult[]): IFindSpliter[] =>
     }
 
     let spliter: IFindSpliter = {
-      before, after, match,
+      before,
+      after,
+      match,
     }
 
     spliters.push(spliter)

+ 5 - 3
src/main/actions/getBasicData.ts

@@ -16,7 +16,7 @@ import version from '@root/version.json'
 
 const normalizeList = (list: IHostsListObject[]): IHostsListObject[] => {
   let flat = flatten(list)
-  flat.map(item => {
+  flat.map((item) => {
     if (!item.id) {
       item.id = uuid4()
     }
@@ -25,8 +25,10 @@ const normalizeList = (list: IHostsListObject[]): IHostsListObject[] => {
   return list
 }
 
-const normalizeTrashcan = (list: ITrashcanListObject[]): ITrashcanListObject[] => {
-  list.map(item => {
+const normalizeTrashcan = (
+  list: ITrashcanListObject[],
+): ITrashcanListObject[] => {
+  list.map((item) => {
     if (!item.id) {
       item.id = uuid4()
     }

+ 1 - 1
src/main/actions/hosts/deleteHistory.ts

@@ -8,5 +8,5 @@ import { swhdb } from '@main/data'
 
 export default async (id: string) => {
   console.log('delete history #' + id)
-  await swhdb.collection.history.delete(item => item.id === id)
+  await swhdb.collection.history.delete((item) => item.id === id)
 }

+ 16 - 10
src/main/actions/hosts/getContent.ts

@@ -9,7 +9,9 @@ import { IHostsContentObject } from '@root/common/data'
 import { findItemById, flatten } from '@root/common/hostsFn'
 
 const getContentById = async (id: string) => {
-  let hosts_content = await swhdb.collection.hosts.find<IHostsContentObject>(i => i.id === id)
+  let hosts_content = await swhdb.collection.hosts.find<IHostsContentObject>(
+    (i) => i.id === id,
+  )
   return hosts_content?.content || ''
 }
 
@@ -29,19 +31,23 @@ const getContentOfHosts = async (id: string): Promise<string> => {
   if (type === 'folder') {
     const items = flatten(hosts.children || [])
 
-    let a = await Promise.all(items.map(async (item) => {
-      return `# file: ${item.title}\n` + (await getContentOfHosts(item.id))
-    }))
+    let a = await Promise.all(
+      items.map(async (item) => {
+        return `# file: ${item.title}\n` + (await getContentOfHosts(item.id))
+      }),
+    )
     return a.join('\n\n')
   }
 
   if (type === 'group') {
-    let a = await Promise.all((hosts.include || []).map(async (id) => {
-      let item = findItemById(list, id)
-      if (!item) return ''
-
-      return `# file: ${item.title}\n` + (await getContentOfHosts(id))
-    }))
+    let a = await Promise.all(
+      (hosts.include || []).map(async (id) => {
+        let item = findItemById(list, id)
+        if (!item) return ''
+
+        return `# file: ${item.title}\n` + (await getContentOfHosts(id))
+      }),
+    )
     return a.join('\n\n')
   }
 

+ 3 - 3
src/main/actions/hosts/getPathOfSystemHostsPath.ts

@@ -5,7 +5,7 @@
 
 export default async (): Promise<string> => {
   // Windows 系统有可能不安装在 C 盘
-  return process.platform === 'win32' ?
-    `${process.env.windir || 'C:\\WINDOWS'}\\system32\\drivers\\etc\\hosts` :
-    '/etc/hosts'
+  return process.platform === 'win32'
+    ? `${process.env.windir || 'C:\\WINDOWS'}\\system32\\drivers\\etc\\hosts`
+    : '/etc/hosts'
 }

+ 1 - 1
src/main/actions/hosts/refresh.ts

@@ -56,7 +56,7 @@ export default async (hosts_id: string): Promise<IOperationResult> => {
   }
 
   hosts.last_refresh = dayjs().format('YYYY-MM-DD HH:mm:ss')
-  hosts.last_refresh_ms = (new Date()).getTime()
+  hosts.last_refresh_ms = new Date().getTime()
 
   await setList(list)
 

+ 4 - 2
src/main/actions/hosts/setContent.ts

@@ -7,10 +7,12 @@ import { swhdb } from '@main/data'
 import { IHostsContentObject } from '@root/common/data'
 
 export default async (id: string, content: string) => {
-  let d = await swhdb.collection.hosts.find<IHostsContentObject>(i => i.id === id)
+  let d = await swhdb.collection.hosts.find<IHostsContentObject>(
+    (i) => i.id === id,
+  )
   if (!d || !d._id) {
     await swhdb.collection.hosts.insert({ id, content })
   } else {
-    await swhdb.collection.hosts.update(i => i._id === d?._id, { content })
+    await swhdb.collection.hosts.update((i) => i._id === d?._id, { content })
   }
 }

+ 65 - 44
src/main/actions/hosts/setSystemHosts.ts

@@ -3,7 +3,12 @@
  * @homepage: https://oldj.net
  */
 
-import { configGet, deleteHistory, getHistoryList, updateTrayTitle } from '@main/actions'
+import {
+  configGet,
+  deleteHistory,
+  getHistoryList,
+  updateTrayTitle,
+} from '@main/actions'
 import tryToRun from '@main/actions/cmd/tryToRun'
 import { broadcast } from '@main/core/agent'
 import { swhdb } from '@main/data'
@@ -21,11 +26,11 @@ import { v4 as uuid4 } from 'uuid'
 import getPathOfSystemHosts from './getPathOfSystemHostsPath'
 
 interface IWriteResult {
-  success: boolean;
-  code?: string;
-  message?: string;
-  old_content?: string;
-  new_content?: string;
+  success: boolean
+  code?: string
+  message?: string
+  old_content?: string
+  new_content?: string
 }
 
 let sudo_pswd: string = ''
@@ -44,7 +49,7 @@ const addHistory = async (content: string) => {
   await swhdb.collection.history.insert({
     id: uuid4(),
     content,
-    add_time_ms: (new Date()).getTime(),
+    add_time_ms: new Date().getTime(),
   })
 
   let history_limit = await configGet('history_limit')
@@ -61,49 +66,59 @@ const addHistory = async (content: string) => {
   }
 }
 
-const writeWithSudo = (sys_hosts_path: string, content: string): Promise<IWriteResult> => new Promise((resolve) => {
-  let tmp_fn = path.join(os.tmpdir(), `swh_${(new Date()).getTime()}_${Math.random()}.txt`)
-  fs.writeFileSync(tmp_fn, content, 'utf-8')
-
-  let cmd = [
-    `echo '${sudo_pswd}' | sudo -S chmod 777 ${sys_hosts_path}`
-    , `cat "${tmp_fn}" > ${sys_hosts_path}`
-    , `echo '${sudo_pswd}' | sudo -S chmod 644 ${sys_hosts_path}`,
-    // , 'rm -rf ' + tmp_fn
-  ].join(' && ')
-
-  exec(cmd, function (error, stdout, stderr) {
-    // command output is in stdout
-    console.log('stdout', stdout)
-    console.log('stderr', stderr)
-
-    if (fs.existsSync(tmp_fn)) {
-      fs.unlinkSync(tmp_fn)
-    }
+const writeWithSudo = (
+  sys_hosts_path: string,
+  content: string,
+): Promise<IWriteResult> =>
+  new Promise((resolve) => {
+    let tmp_fn = path.join(
+      os.tmpdir(),
+      `swh_${new Date().getTime()}_${Math.random()}.txt`,
+    )
+    fs.writeFileSync(tmp_fn, content, 'utf-8')
+
+    let cmd = [
+      `echo '${sudo_pswd}' | sudo -S chmod 777 ${sys_hosts_path}`,
+      `cat "${tmp_fn}" > ${sys_hosts_path}`,
+      `echo '${sudo_pswd}' | sudo -S chmod 644 ${sys_hosts_path}`,
+      // , 'rm -rf ' + tmp_fn
+    ].join(' && ')
+
+    exec(cmd, function (error, stdout, stderr) {
+      // command output is in stdout
+      console.log('stdout', stdout)
+      console.log('stderr', stderr)
+
+      if (fs.existsSync(tmp_fn)) {
+        fs.unlinkSync(tmp_fn)
+      }
 
-    let result: IWriteResult
+      let result: IWriteResult
 
-    if (!error) {
-      console.log('success.')
+      if (!error) {
+        console.log('success.')
 
-      result = {
-        success: true,
-      }
-    } else {
-      console.log('fail!')
-      sudo_pswd = ''
+        result = {
+          success: true,
+        }
+      } else {
+        console.log('fail!')
+        sudo_pswd = ''
 
-      result = {
-        success: false,
-        message: stderr,
+        result = {
+          success: false,
+          message: stderr,
+        }
       }
-    }
 
-    resolve(result)
+      resolve(result)
+    })
   })
-})
 
-const write = async (content: string, options?: IHostsWriteOptions): Promise<IWriteResult> => {
+const write = async (
+  content: string,
+  options?: IHostsWriteOptions,
+): Promise<IWriteResult> => {
   const sys_hosts_path = await getPathOfSystemHosts()
   const fn_md5 = await md5File(sys_hosts_path)
   const content_md5 = md5(content)
@@ -162,14 +177,20 @@ const write = async (content: string, options?: IHostsWriteOptions): Promise<IWr
   return { success: true, old_content, new_content: content }
 }
 
-const setSystemHosts = async (content: string, options?: IHostsWriteOptions): Promise<IWriteResult> => {
+const setSystemHosts = async (
+  content: string,
+  options?: IHostsWriteOptions,
+): Promise<IWriteResult> => {
   let result = await write(content, options)
   let { success, old_content } = result
 
   if (success) {
     if (typeof old_content === 'string') {
       let histories = await getHistoryList()
-      if (histories.length === 0 || histories[histories.length - 1].content !== old_content) {
+      if (
+        histories.length === 0 ||
+        histories[histories.length - 1].content !== old_content
+      ) {
         await addHistory(old_content)
       }
     }

+ 1 - 1
src/main/actions/list/getContentOfList.ts

@@ -10,7 +10,7 @@ import normalize, { INormalizeOptions } from '@root/common/normalize'
 
 const getContentOfList = async (list: IHostsListObject[]): Promise<string> => {
   const content_list: string[] = []
-  const flat = flatten(list).filter(item => item.on)
+  const flat = flatten(list).filter((item) => item.on)
 
   for (let hosts of flat) {
     let c = await getHostsContent(hosts.id)

+ 1 - 1
src/main/actions/list/moveItemToTrashcan.ts

@@ -29,7 +29,7 @@ export default async (id: string) => {
       ...node,
       on: false,
     },
-    add_time_ms: (new Date()).getTime(),
+    add_time_ms: new Date().getTime(),
     parent_id: hostsFn.getParentOfItem(list, id)?.id || null,
   }
 

+ 2 - 1
src/main/actions/migrate/checkIfMigration.ts

@@ -14,7 +14,8 @@ export default async (): Promise<boolean> => {
   let dir = getDataFolder()
   let old_data_file = path.join(dir, 'data.json')
   let new_data_dir = path.join(dir, 'data')
-  let has_new_data = isDir(new_data_dir) && isDir(path.join(new_data_dir, 'collection'))
+  let has_new_data =
+    isDir(new_data_dir) && isDir(path.join(new_data_dir, 'collection'))
 
   return fs.existsSync(old_data_file) && !has_new_data
 }

+ 9 - 8
src/main/actions/migrate/export.ts

@@ -17,10 +17,7 @@ export default async (): Promise<string | null | false> => {
   let result = await dialog.showSaveDialog({
     title: lang.import,
     defaultPath: path.join(global.last_path || '', 'swh_data.json'),
-    properties: [
-      'createDirectory',
-      'showOverwriteConfirmation',
-    ],
+    properties: ['createDirectory', 'showOverwriteConfirmation'],
   })
 
   if (result.canceled || !result.filePath) {
@@ -31,10 +28,14 @@ export default async (): Promise<string | null | false> => {
 
   let data = await swhdb.toJSON()
   try {
-    await fs.writeFile(target_dir, JSON.stringify({
-      data,
-      version,
-    }), 'utf-8')
+    await fs.writeFile(
+      target_dir,
+      JSON.stringify({
+        data,
+        version,
+      }),
+      'utf-8',
+    )
   } catch (e) {
     console.error(e)
     return false

+ 6 - 4
src/main/actions/migrate/import.ts

@@ -20,9 +20,7 @@ export default async (): Promise<boolean | null | string> => {
       { name: 'JSON', extensions: ['json'] },
       { name: 'All Files', extensions: ['*'] },
     ],
-    properties: [
-      'openFile',
-    ],
+    properties: ['openFile'],
   })
 
   if (result.canceled) {
@@ -41,7 +39,11 @@ export default async (): Promise<boolean | null | string> => {
     return 'parse_error'
   }
 
-  if (typeof data !== 'object' || !data.version || !Array.isArray(data.version)) {
+  if (
+    typeof data !== 'object' ||
+    !data.version ||
+    !Array.isArray(data.version)
+  ) {
     return 'invalid_data'
   }
 

+ 5 - 1
src/main/actions/migrate/importFromUrl.ts

@@ -35,7 +35,11 @@ export default async (url: string): Promise<boolean | null | string> => {
     data = res.data
   }
 
-  if (typeof data !== 'object' || !data.version || !Array.isArray(data.version)) {
+  if (
+    typeof data !== 'object' ||
+    !data.version ||
+    !Array.isArray(data.version)
+  ) {
     return 'invalid_data'
   }
 

+ 1 - 1
src/main/actions/ping.ts

@@ -4,7 +4,7 @@
  * @homepage: https://oldj.net
  */
 
-const wait = (ms: number) => new Promise(resolve => setTimeout(resolve, ms))
+const wait = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms))
 
 export default async (ms: number = 1000): Promise<string> => {
   await wait(ms)

+ 4 - 4
src/main/actions/trashcan/clear.ts

@@ -10,13 +10,13 @@ export default async () => {
   let trashcan_items = await swhdb.list.trashcan.all()
 
   let ids: string[] = []
-  trashcan_items.map(i => {
+  trashcan_items.map((i) => {
     ids.push(i.data.id)
-    flatten(i.data.children || []).map(i => ids.push(i.id))
+    flatten(i.data.children || []).map((i) => ids.push(i.id))
   })
 
-  await swhdb.collection.hosts.delete(i => ids.includes(i.id))
-  await swhdb.list.tree.delete(i => ids.includes(i.id))
+  await swhdb.collection.hosts.delete((i) => ids.includes(i.id))
+  await swhdb.list.tree.delete((i) => ids.includes(i.id))
   await swhdb.list.trashcan.remove()
 
   return true

+ 7 - 5
src/main/actions/trashcan/deleteItem.ts

@@ -10,7 +10,9 @@ import { flatten } from '@root/common/hostsFn'
 export default async (id: string): Promise<boolean> => {
   // Permanently delete the specified item with id.
 
-  let trashcan_item: ITrashcanListObject = await swhdb.list.trashcan.find(i => i.data.id === id)
+  let trashcan_item: ITrashcanListObject = await swhdb.list.trashcan.find(
+    (i) => i.data.id === id,
+  )
 
   if (!trashcan_item) {
     console.log(`can't find trashcan_item with id #${id}.`)
@@ -18,11 +20,11 @@ export default async (id: string): Promise<boolean> => {
   }
 
   let ids: string[] = [id]
-  flatten(trashcan_item.data.children || []).map(i => ids.push(i.id))
+  flatten(trashcan_item.data.children || []).map((i) => ids.push(i.id))
 
-  await swhdb.collection.hosts.delete(i => ids.includes(i.id))
-  await swhdb.list.tree.delete(i => i.id === id)
-  await swhdb.list.trashcan.delete(i => i.data.id === id)
+  await swhdb.collection.hosts.delete((i) => ids.includes(i.id))
+  await swhdb.list.tree.delete((i) => i.id === id)
+  await swhdb.list.trashcan.delete((i) => i.data.id === id)
 
   return true
 }

+ 6 - 4
src/main/actions/trashcan/restoreItem.ts

@@ -9,7 +9,9 @@ import { getNodeById } from '@renderer/components/Tree/fn'
 import { ITrashcanListObject } from '@root/common/data'
 
 export default async (id: string): Promise<boolean> => {
-  let trashcan_item: ITrashcanListObject = await swhdb.list.trashcan.find(i => i.data.id === id)
+  let trashcan_item: ITrashcanListObject = await swhdb.list.trashcan.find(
+    (i) => i.data.id === id,
+  )
 
   if (!trashcan_item) {
     console.log(`can't find trashcan_item with id #${id}.`)
@@ -26,7 +28,7 @@ export default async (id: string): Promise<boolean> => {
   let { parent_id } = trashcan_item
 
   if (!parent_id) {
-    await setList([ ...list, hosts ])
+    await setList([...list, hosts])
   } else {
     let parent_hosts = getNodeById(list, parent_id)
     if (!parent_hosts) {
@@ -34,11 +36,11 @@ export default async (id: string): Promise<boolean> => {
       return false
     }
 
-    parent_hosts.children = [ ...(parent_hosts.children || []), hosts ]
+    parent_hosts.children = [...(parent_hosts.children || []), hosts]
     await setList(list)
   }
 
-  await swhdb.list.trashcan.delete(i => i.data.id === id)
+  await swhdb.list.trashcan.delete((i) => i.data.id === id)
 
   return true
 }

+ 2 - 2
src/main/actions/updateTrayTitle.ts

@@ -23,8 +23,8 @@ export default async (show?: boolean, title?: string) => {
 
   if (!title) {
     let list = await getList()
-    let on_items = flatten(list).filter(i => i.on)
-    title = on_items.map(i => i.title).join(',')
+    let on_items = flatten(list).filter((i) => i.on)
+    title = on_items.map((i) => i.title).join(',')
     if (title.length > 20) {
       title = title.substring(0, 17) + '...'
     }

+ 4 - 3
src/main/core/message.ts

@@ -28,13 +28,14 @@ ipcMain.on('x_unreg', (e, d) => {
         delete registered_clients[k]
       }
     }
-
   } else if (name) {
     delete registered_clients[name]
-
   } else {
     for (let k in registered_clients) {
-      if (registered_clients.hasOwnProperty(k) && registered_clients[k] === e.sender) {
+      if (
+        registered_clients.hasOwnProperty(k) &&
+        registered_clients[k] === e.sender
+      ) {
         delete registered_clients[k]
         break
       }

+ 1 - 1
src/main/core/popupMenu.ts

@@ -12,7 +12,7 @@ ipcMain.on('x_popup_menu', (e, options: IPopupMenuOption) => {
   // console.log(options)
   const menu = new Menu()
 
-  options.items.map(opt => {
+  options.items.map((opt) => {
     if (typeof opt._click_evt === 'string') {
       let evt: string = opt._click_evt
       opt.click = () => {

+ 1 - 5
src/main/data/index.ts

@@ -40,8 +40,4 @@ if (!global.localdb) {
   localdb = global.localdb
 }
 
-export {
-  swhdb,
-  cfgdb,
-  localdb,
-}
+export { swhdb, cfgdb, localdb }

+ 12 - 8
src/main/http/api/list.ts

@@ -14,20 +14,24 @@ const list = async (req: Request, res: Response) => {
   try {
     list = await getList()
   } catch (e) {
-    res.end(JSON.stringify({
-      success: false,
-      message: e.message,
-    }))
+    res.end(
+      JSON.stringify({
+        success: false,
+        message: e.message,
+      }),
+    )
 
     return
   }
 
   list = flatten(list)
 
-  res.end(JSON.stringify({
-    success: true,
-    data: list,
-  }))
+  res.end(
+    JSON.stringify({
+      success: true,
+      data: list,
+    }),
+  )
 }
 
 export default list

+ 10 - 3
src/main/http/index.ts

@@ -12,7 +12,12 @@ import api_router from './api/index'
 const app = express()
 
 app.use((req, res, next) => {
-  console.log(`> "${(new Date()).toString()}"`, req.method, req.originalUrl, `"${req.headers['user-agent']}"`)
+  console.log(
+    `> "${new Date().toString()}"`,
+    req.method,
+    req.originalUrl,
+    `"${req.headers['user-agent']}"`,
+  )
   next()
 })
 
@@ -21,7 +26,7 @@ app.get('/', (req, res) => {
 })
 
 app.get('/remote-test', (req, res) => {
-  res.send(`# remote-test\n# ${(new Date()).toString()}`)
+  res.send(`# remote-test\n# ${new Date().toString()}`)
 })
 
 app.use('/api', api_router)
@@ -31,7 +36,9 @@ let server: Server
 export const start = (): boolean => {
   try {
     server = app.listen(http_api_port, '127.0.0.1', function () {
-      console.log(`SwitchHosts HTTP server is listening on port ${http_api_port}!`)
+      console.log(
+        `SwitchHosts HTTP server is listening on port ${http_api_port}!`,
+      )
       console.log(`-> http://127.0.0.1:${http_api_port}`)
     })
   } catch (e) {

+ 7 - 11
src/main/libs/cron.ts

@@ -21,7 +21,7 @@ const isNeedRefresh = (hosts: IHostsListObject): boolean => {
 
   if (!last_refresh_ms) return true
 
-  let ts = (new Date()).getTime()
+  let ts = new Date().getTime()
   if ((ts - last_refresh_ms) / 1000 >= refresh_interval) {
     return true
   }
@@ -33,8 +33,7 @@ const isNeedRefresh = (hosts: IHostsListObject): boolean => {
 const checkRefresh = async () => {
   // console.log('check refresh...')
   let list = await getList()
-  let remote_hosts = flatten(list)
-    .filter(h => h.type === 'remote')
+  let remote_hosts = flatten(list).filter((h) => h.type === 'remote')
 
   for (let hosts of remote_hosts) {
     if (isNeedRefresh(hosts)) {
@@ -50,22 +49,19 @@ const checkRefresh = async () => {
 }
 
 const checkServer = async () => {
-  let ts = (new Date()).getTime()
-  if (!ts_last_server_check || (ts - ts_last_server_check) > 3600 * 1000) {
+  let ts = new Date().getTime()
+  if (!ts_last_server_check || ts - ts_last_server_check > 3600 * 1000) {
     await checkUpdate()
     ts_last_server_check = ts
   }
 }
 
 const check = async () => {
-  checkRefresh()
-    .catch(e => console.error(e))
+  checkRefresh().catch((e) => console.error(e))
 
-  checkServer()
-    .catch(e => console.error(e))
+  checkServer().catch((e) => console.error(e))
 
-  global.tracer.emit()
-    .catch(e => console.error(e))
+  global.tracer.emit().catch((e) => console.error(e))
 }
 
 export const start = () => {

+ 8 - 4
src/main/libs/request.ts

@@ -10,15 +10,19 @@ import querystring from 'querystring'
 import version from '@root/version.json'
 
 interface IParams {
-  [key: string]: string | string[] | number;
+  [key: string]: string | string[] | number
 }
 
 interface IRequestOptions {
-  timeout?: number;
-  headers?: { [key: string]: string | string[] },
+  timeout?: number
+  headers?: { [key: string]: string | string[] }
 }
 
-export const GET = async (url: string, params: IParams | null = null, options: IRequestOptions = {}) => {
+export const GET = async (
+  url: string,
+  params: IParams | null = null,
+  options: IRequestOptions = {},
+) => {
   let s = ''
   if (params) {
     s = querystring.stringify(params)

+ 6 - 4
src/main/libs/safePSWD.ts

@@ -5,8 +5,10 @@
  */
 
 export default (pswd: string): string => {
-  return pswd
-    .replace(/\\/g, '\\\\')
-    //.replace(/'/g, "\\''")
-    .replace(/'/g, '\\x27')
+  return (
+    pswd
+      .replace(/\\/g, '\\\\')
+      //.replace(/'/g, "\\''")
+      .replace(/'/g, '\\x27')
+  )
 }

+ 3 - 3
src/main/libs/tracer.ts

@@ -5,15 +5,15 @@ import { server_url } from '@root/common/constants'
 class Tracer {
   data: string[]
 
-  constructor () {
+  constructor() {
     this.data = []
   }
 
-  add (action: string) {
+  add(action: string) {
     this.data.push(action)
   }
 
-  async emit () {
+  async emit() {
     if (this.data.length === 0) return
 
     let send_usage_data = await configGet('send_usage_data')

+ 3 - 5
src/main/main.ts

@@ -59,8 +59,7 @@ const createWindow = async () => {
   if (hide_dock_icon) {
     app.dock && app.dock.hide()
   } else {
-    app.dock && app.dock.show()
-      .catch(e => console.error(e))
+    app.dock && app.dock.show().catch((e) => console.error(e))
   }
 
   console.log('isDev: ', isDev())
@@ -70,8 +69,7 @@ const createWindow = async () => {
 
   makeMainMenu(configs.locale)
 
-  win.loadURL(getIndex())
-    .catch(e => console.error(e))
+  win.loadURL(getIndex()).catch((e) => console.error(e))
 
   if (isDev()) {
     // Open DevTools, see https://github.com/electron/electron/issues/12438 for why we wait for dom-ready
@@ -160,6 +158,6 @@ app.on('window-all-closed', () => {
   }
 })
 
-app.on('before-quit', () => global.is_will_quit = true)
+app.on('before-quit', () => (global.is_will_quit = true))
 app.on('activate', onActive)
 message.on('active_main_window', onActive)

+ 4 - 3
src/main/preload.ts

@@ -11,7 +11,7 @@ import { EventEmitter } from 'events'
 
 declare global {
   interface Window {
-    _agent: typeof _agent;
+    _agent: typeof _agent
   }
 }
 
@@ -22,7 +22,7 @@ const ee = new EventEmitter()
 let x_get_idx = 0
 
 const callAction = (action: keyof Actions, ...params: any[]) => {
-  const callback = [ '_cb', (new Date()).getTime(), x_get_idx++ ].join('_')
+  const callback = ['_cb', new Date().getTime(), x_get_idx++].join('_')
 
   return new Promise((resolve, reject) => {
     ipcRenderer.send('x_action', {
@@ -87,7 +87,8 @@ const _agent = {
   off,
   popupMenu,
   platform: process.platform,
-  darkModeToggle: (theme?: 'dark' | 'light' | 'system') => ipcRenderer.invoke(`dark-mode:${theme ?? 'toggle'}`),
+  darkModeToggle: (theme?: 'dark' | 'light' | 'system') =>
+    ipcRenderer.invoke(`dark-mode:${theme ?? 'toggle'}`),
 }
 
 contextBridge.exposeInMainWorld('_agent', _agent)

+ 15 - 15
src/main/types.d.ts

@@ -12,29 +12,29 @@ import * as actions from './actions'
 export type Actions = typeof actions
 
 export interface ActionData {
-  action: keyof Actions;
-  data?: any;
-  callback: string;
+  action: keyof Actions
+  data?: any
+  callback: string
 }
 
 export interface IHostsWriteOptions {
-  sudo_pswd?: string;
+  sudo_pswd?: string
 }
 
 declare global {
   namespace NodeJS {
     interface Global {
-      db_dir?: string;
-      swhdb: SwhDb;
-      cfgdb: SwhDb;
-      localdb: SwhDb;
-      ua: string; // user agent
-      session_id: string; // A random value, refreshed every time the app starts, used to identify different startup sessions.
-      main_win: BrowserWindow;
-      find_win?: BrowserWindow | null;
-      last_path?: string; // the last path opened by SwitchHosts
-      tracer: Tracer;
-      is_will_quit?: boolean;
+      db_dir?: string
+      swhdb: SwhDb
+      cfgdb: SwhDb
+      localdb: SwhDb
+      ua: string // user agent
+      session_id: string // A random value, refreshed every time the app starts, used to identify different startup sessions.
+      main_win: BrowserWindow
+      find_win?: BrowserWindow | null
+      last_path?: string // the last path opened by SwitchHosts
+      tracer: Tracer
+      is_will_quit?: boolean
     }
   }
 }

+ 2 - 5
src/main/ui/find.ts

@@ -38,8 +38,7 @@ const makeWindow = () => {
   //   visibleOnFullScreen: true,
   // })
 
-  win.loadURL(`${getIndex()}#/find`)
-    .catch(e => console.error(e))
+  win.loadURL(`${getIndex()}#/find`).catch((e) => console.error(e))
 
   // win.on('blur', () => win?.hide())
 
@@ -66,6 +65,4 @@ const makeWindow = () => {
   return win
 }
 
-export {
-  makeWindow,
-}
+export { makeWindow }

+ 24 - 18
src/main/ui/menu.ts

@@ -5,7 +5,13 @@
 
 import { findShow } from '@main/actions'
 import events from '@root/common/events'
-import { BrowserWindow, Menu, MenuItem, MenuItemConstructorOptions, shell } from 'electron'
+import {
+  BrowserWindow,
+  Menu,
+  MenuItem,
+  MenuItemConstructorOptions,
+  shell,
+} from 'electron'
 import { I18N, LocaleName } from '@root/common/i18n'
 import { homepage_url, feedback_url } from '@root/common/constants'
 import { broadcast } from '@main/core/agent'
@@ -92,14 +98,14 @@ export const makeMainMenu = (locale: LocaleName = 'en') => {
         {
           label: lang.comment_current_line,
           accelerator: 'CommandOrControl+/',
-          click () {
+          click() {
             broadcast(events.toggle_comment)
           },
         },
         {
           label: lang.find_and_replace,
           accelerator: 'CommandOrControl+F',
-          click () {
+          click() {
             findShow()
           },
         },
@@ -111,16 +117,15 @@ export const makeMainMenu = (locale: LocaleName = 'en') => {
         {
           label: lang.reload,
           accelerator: 'CmdOrCtrl+R',
-          click (item: MenuItem, focusedWindow: BrowserWindow | undefined) {
+          click(item: MenuItem, focusedWindow: BrowserWindow | undefined) {
             if (focusedWindow) focusedWindow.reload()
           },
         },
         {
-          label: lang.toggle_developer_tools,// 'Toggle Developer Tools',
-          accelerator: process.platform === 'darwin'
-            ? 'Alt+Command+I'
-            : 'Ctrl+Shift+I',
-          click (item: MenuItem, focusedWindow: BrowserWindow | undefined) {
+          label: lang.toggle_developer_tools, // 'Toggle Developer Tools',
+          accelerator:
+            process.platform === 'darwin' ? 'Alt+Command+I' : 'Ctrl+Shift+I',
+          click(item: MenuItem, focusedWindow: BrowserWindow | undefined) {
             if (focusedWindow) focusedWindow.webContents.toggleDevTools()
           },
         },
@@ -177,16 +182,14 @@ export const makeMainMenu = (locale: LocaleName = 'en') => {
         // },
         {
           label: lang.feedback,
-          click () {
-            shell.openExternal(feedback_url)
-              .catch(e => console.log(e))
+          click() {
+            shell.openExternal(feedback_url).catch((e) => console.log(e))
           },
         },
         {
           label: lang.homepage,
-          click () {
-            shell.openExternal(homepage_url)
-              .catch(e => console.log(e))
+          click() {
+            shell.openExternal(homepage_url).catch((e) => console.log(e))
           },
         },
       ],
@@ -234,7 +237,8 @@ export const makeMainMenu = (locale: LocaleName = 'en') => {
         {
           role: 'quit',
           label: lang.quit,
-        }],
+        },
+      ],
     })
     // Edit menu.
     /*template[2].submenu.push(
@@ -278,7 +282,8 @@ export const makeMainMenu = (locale: LocaleName = 'en') => {
       // },
     ]
   } else if (os === 'win32' || os === 'linux') {
-    let submenu = (template[0] && template[0].submenu) as MenuItemConstructorOptions[]
+    let submenu = (template[0] &&
+      template[0].submenu) as MenuItemConstructorOptions[]
 
     if (submenu) {
       submenu.unshift({
@@ -303,7 +308,8 @@ export const makeMainMenu = (locale: LocaleName = 'en') => {
     }
 
     // VIEW
-    submenu = (template[2] && template[2].submenu) as MenuItemConstructorOptions[]
+    submenu = (template[2] &&
+      template[2].submenu) as MenuItemConstructorOptions[]
     submenu.splice(0, 4)
   }
 

+ 37 - 31
src/main/ui/tray/index.ts

@@ -10,7 +10,14 @@ import { makeWindow } from '@main/ui/tray/window'
 import events from '@root/common/events'
 import { I18N } from '@root/common/i18n'
 import version from '@root/version.json'
-import { app, BrowserWindow, Menu, MenuItemConstructorOptions, screen, Tray } from 'electron'
+import {
+  app,
+  BrowserWindow,
+  Menu,
+  MenuItemConstructorOptions,
+  screen,
+  Tray,
+} from 'electron'
 import * as path from 'path'
 
 let tray: Tray
@@ -25,8 +32,7 @@ const makeTray = async () => {
   tray = new Tray(path.join(__dirname, 'assets', icon))
   win = makeWindow()
 
-  updateTrayTitle()
-    .catch(e => console.error(e))
+  updateTrayTitle().catch((e) => console.error(e))
 
   tray.setToolTip('SwitchHosts')
 
@@ -61,7 +67,7 @@ const makeTray = async () => {
       {
         label: lang._app_name,
         toolTip: lang.show_main_window,
-        click () {
+        click() {
           broadcast(events.active_main_window)
         },
       },
@@ -69,23 +75,24 @@ const makeTray = async () => {
         label: `v${ver}`,
         enabled: false,
       },
-      ...(app.dock ? <MenuItemConstructorOptions[]>[
-        { type: 'separator' },
-        {
-          label: lang.toggle_dock_icon,
-          async click () {
-            let hide_dock_icon = await configGet('hide_dock_icon')
-            hide_dock_icon = !hide_dock_icon
-            await configSet('hide_dock_icon', hide_dock_icon)
-            if (hide_dock_icon) {
-              app.dock.hide()
-            } else {
-              app.dock.show()
-                .catch(e => console.error(e))
-            }
-          },
-        },
-      ] : []),
+      ...(app.dock
+        ? <MenuItemConstructorOptions[]>[
+            { type: 'separator' },
+            {
+              label: lang.toggle_dock_icon,
+              async click() {
+                let hide_dock_icon = await configGet('hide_dock_icon')
+                hide_dock_icon = !hide_dock_icon
+                await configSet('hide_dock_icon', hide_dock_icon)
+                if (hide_dock_icon) {
+                  app.dock.hide()
+                } else {
+                  app.dock.show().catch((e) => console.error(e))
+                }
+              },
+            },
+          ]
+        : []),
       { type: 'separator' },
       {
         label: lang.quit,
@@ -123,7 +130,8 @@ const getPosition = () => {
   }
 
   if (x < 0) x = 0
-  if (x + window_bounds.width > screen_bounds.width) x = screen_bounds.width - window_bounds.width
+  if (x + window_bounds.width > screen_bounds.width)
+    x = screen_bounds.width - window_bounds.width
 
   x = Math.round(x)
   y = Math.round(y)
@@ -138,13 +146,11 @@ const show = () => {
   // win.focus()
 }
 
-app && app.whenReady().then(() => {
-  if (!tray) {
-    makeTray()
-  }
-})
+app &&
+  app.whenReady().then(() => {
+    if (!tray) {
+      makeTray()
+    }
+  })
 
-export {
-  tray,
-  makeTray,
-}
+export { tray, makeTray }

+ 2 - 5
src/main/ui/tray/window.ts

@@ -36,8 +36,7 @@ const makeWindow = () => {
     visibleOnFullScreen: true,
   })
 
-  win.loadURL(`${getIndex()}#/tray`)
-    .catch(e => console.error(e))
+  win.loadURL(`${getIndex()}#/tray`).catch((e) => console.error(e))
 
   win.on('blur', () => win?.hide())
 
@@ -60,6 +59,4 @@ const makeWindow = () => {
   return win
 }
 
-export {
-  makeWindow,
-}
+export { makeWindow }

+ 2 - 2
src/renderer/app.tsx

@@ -8,11 +8,11 @@ import { ChakraProvider, ColorModeScript } from '@chakra-ui/react'
 import React from 'react'
 import theme from './theme'
 
-export function rootContainer (container: React.ReactElement) {
+export function rootContainer(container: React.ReactElement) {
   // return React.createElement(ChakraProvider, null, container)
   return (
     <ChakraProvider>
-      <ColorModeScript initialColorMode={theme.config.initialColorMode}/>
+      <ColorModeScript initialColorMode={theme.config.initialColorMode} />
       {container}
     </ChakraProvider>
   )

+ 14 - 11
src/renderer/components/About/AboutContent.tsx

@@ -14,9 +14,7 @@ import version from '@root/version.json'
 import React from 'react'
 import styles from './AboutContent.less'
 
-interface Props {
-
-}
+interface Props {}
 
 const AboutContent = (props: Props) => {
   const { lang } = useModel('useI18n')
@@ -26,22 +24,27 @@ const AboutContent = (props: Props) => {
     <div className={styles.root}>
       <VStack spacing={1}>
         <Box pt={8} pb={3}>
-          <Image
-            className={styles.logo}
-            src={logo}
-          />
+          <Image className={styles.logo} src={logo} />
+        </Box>
+        <Box fontWeight="bold" fontSize="16px">
+          {lang._app_name}
+        </Box>
+        <Box fontSize="80%" opacity={0.5}>
+          v{version_str}
         </Box>
-        <Box fontWeight="bold" fontSize="16px">{lang._app_name}</Box>
-        <Box fontSize="80%" opacity={0.5}>v{version_str}</Box>
         <HStack>
           <Link href={homepage_url}>{lang.homepage}</Link>
           <Link href={source_url}>{lang.source_code}</Link>
         </HStack>
 
-        <Box pt={8} fontWeight="bold">{lang.acknowledgement}</Box>
+        <Box pt={8} fontWeight="bold">
+          {lang.acknowledgement}
+        </Box>
         <Box className={styles.names}>
           {acknowledgements.map((o, idx) => (
-            <Link key={idx} href={o.link}>{o.name}</Link>
+            <Link key={idx} href={o.link}>
+              {o.name}
+            </Link>
           ))}
         </Box>
       </VStack>

+ 2 - 2
src/renderer/components/About/index.tsx

@@ -29,10 +29,10 @@ const About = () => {
 
   return (
     <Modal isOpen={is_open} onClose={onClose}>
-      <ModalOverlay/>
+      <ModalOverlay />
       <ModalContent>
         <ModalBody className={styles.root}>
-          <AboutContent/>
+          <AboutContent />
         </ModalBody>
 
         <ModalFooter className={styles.footer}>

+ 7 - 6
src/renderer/components/BrowserLink.tsx

@@ -9,10 +9,10 @@ import events from '@root/common/events'
 import React from 'react'
 
 interface Props {
-  href: string;
-  children: React.ReactElement | string;
+  href: string
+  children: React.ReactElement | string
 
-  [key: string]: any;
+  [key: string]: any
 }
 
 const BrowserLink = (props: Props) => {
@@ -21,12 +21,13 @@ const BrowserLink = (props: Props) => {
   const onClick = (e: React.MouseEvent) => {
     e.preventDefault()
     agent.broadcast(events.browser_link, href)
-    actions.openUrl(href)
-      .catch(e => console.error(e))
+    actions.openUrl(href).catch((e) => console.error(e))
   }
 
   return (
-    <a href={href} onClick={onClick}>{props.children}</a>
+    <a href={href} onClick={onClick}>
+      {props.children}
+    </a>
   )
 }
 

+ 88 - 70
src/renderer/components/EditHostsInfo.tsx

@@ -41,11 +41,12 @@ import styles from './EditHostsInfo.less'
 
 const EditHostsInfo = () => {
   const { lang } = useModel('useI18n')
-  const [ hosts, setHosts ] = useState<IHostsListObject | null>(null)
-  const { hosts_data, setList, current_hosts, setCurrentHosts } = useModel('useHostsData')
-  const [ is_show, setIsShow ] = useState(false)
-  const [ is_add, setIsAdd ] = useState(true)
-  const [ is_refreshing, setIsRefreshing ] = useState(false)
+  const [hosts, setHosts] = useState<IHostsListObject | null>(null)
+  const { hosts_data, setList, current_hosts, setCurrentHosts } =
+    useModel('useHostsData')
+  const [is_show, setIsShow] = useState(false)
+  const [is_add, setIsAdd] = useState(true)
+  const [is_refreshing, setIsRefreshing] = useState(false)
   const ipt_title_ref = useRef<HTMLInputElement>(null)
 
   const toast = useToast()
@@ -58,8 +59,8 @@ const EditHostsInfo = () => {
   const onSave = async () => {
     let data: Omit<IHostsListObject, 'id'> & { id?: string } = { ...hosts }
 
-    const keys_to_trim = [ 'title', 'url' ]
-    keys_to_trim.map(k => {
+    const keys_to_trim = ['title', 'url']
+    keys_to_trim.map((k) => {
       if (data[k]) {
         data[k] = data[k].trim()
       }
@@ -68,31 +69,31 @@ const EditHostsInfo = () => {
     if (is_add) {
       // add
       let h: IHostsListObject = {
-        ...(data),
+        ...data,
         id: uuidv4(),
       }
-      let list: IHostsListObject[] = [ ...hosts_data.list, h ]
+      let list: IHostsListObject[] = [...hosts_data.list, h]
       await setList(list)
       agent.broadcast(events.select_hosts, h.id, 1000)
-
     } else if (data && data.id) {
       // edit
-      let h: IHostsListObject | undefined = hostsFn.findItemById(hosts_data.list, data.id)
+      let h: IHostsListObject | undefined = hostsFn.findItemById(
+        hosts_data.list,
+        data.id,
+      )
       if (h) {
         Object.assign(h, data)
-        await setList([ ...hosts_data.list ])
+        await setList([...hosts_data.list])
 
         if (data.id === current_hosts?.id) {
           setCurrentHosts(h)
         }
-
       } else {
         // can not find by id
         setIsAdd(true)
         setTimeout(onSave, 300)
         return
       }
-
     } else {
       // unknow error
       alert('unknow error!')
@@ -118,11 +119,15 @@ const EditHostsInfo = () => {
     setIsShow(true)
   })
 
-  useOnBroadcast(events.hosts_refreshed, (_hosts: IHostsListObject) => {
-    if (hosts && hosts.id === _hosts.id) {
-      onUpdate(lodash.pick(_hosts, [ 'last_refresh', 'last_refresh_ms' ]))
-    }
-  }, [ hosts ])
+  useOnBroadcast(
+    events.hosts_refreshed,
+    (_hosts: IHostsListObject) => {
+      if (hosts && hosts.id === _hosts.id) {
+        onUpdate(lodash.pick(_hosts, ['last_refresh', 'last_refresh_ms']))
+      }
+    },
+    [hosts],
+  )
 
   const forRemote = (): React.ReactElement => {
     return (
@@ -131,9 +136,9 @@ const EditHostsInfo = () => {
           <FormLabel>URL</FormLabel>
           <Input
             value={hosts?.url || ''}
-            onChange={e => onUpdate({ url: e.target.value })}
+            onChange={(e) => onUpdate({ url: e.target.value })}
             placeholder={lang.url_placeholder}
-            onKeyDown={e => e.key === 'Enter' && onSave()}
+            onKeyDown={(e) => e.key === 'Enter' && onSave()}
           />
         </FormControl>
 
@@ -142,7 +147,9 @@ const EditHostsInfo = () => {
           <div>
             <Select
               value={hosts?.refresh_interval || 0}
-              onChange={e => onUpdate({ refresh_interval: parseInt(e.target.value) || 0 })}
+              onChange={(e) =>
+                onUpdate({ refresh_interval: parseInt(e.target.value) || 0 })
+              }
               style={{ minWidth: 120 }}
             >
               <option value={0}>{lang.never}</option>
@@ -156,7 +163,10 @@ const EditHostsInfo = () => {
           </div>
           {is_add ? null : (
             <FormHelperText className={styles.refresh_info}>
-              <span>{lang.last_refresh}{hosts?.last_refresh || 'N/A'}</span>
+              <span>
+                {lang.last_refresh}
+                {hosts?.last_refresh || 'N/A'}
+              </span>
               <Button
                 size="small"
                 variant="ghost"
@@ -165,8 +175,9 @@ const EditHostsInfo = () => {
                   if (!hosts) return
 
                   setIsRefreshing(true)
-                  actions.refreshHosts(hosts.id)
-                    .then(r => {
+                  actions
+                    .refreshHosts(hosts.id)
+                    .then((r) => {
                       console.log(r)
                       if (!r.success) {
                         toast({
@@ -187,7 +198,7 @@ const EditHostsInfo = () => {
                         last_refresh_ms: r.data.last_refresh_ms,
                       })
                     })
-                    .catch(e => {
+                    .catch((e) => {
                       console.log(e)
                       toast({
                         status: 'error',
@@ -197,7 +208,9 @@ const EditHostsInfo = () => {
                     })
                     .finally(() => setIsRefreshing(false))
                 }}
-              >{lang.refresh}</Button>
+              >
+                {lang.refresh}
+              </Button>
             </FormHelperText>
           )}
         </FormControl>
@@ -208,7 +221,7 @@ const EditHostsInfo = () => {
   const renderTransferItem = (item: IHostsListObject): React.ReactElement => {
     return (
       <HStack>
-        <ItemIcon type={item.type}/>
+        <ItemIcon type={item.type} />
         <span style={{ marginLeft: 4 }}>{item.title || lang.untitled}</span>
       </HStack>
     )
@@ -218,8 +231,10 @@ const EditHostsInfo = () => {
     const list = hostsFn.flatten(hosts_data.list)
 
     let source_list: IHostsListObject[] = list
-      .filter(item => !item.type || item.type === 'local' || item.type === 'remote')
-      .map(item => {
+      .filter(
+        (item) => !item.type || item.type === 'local' || item.type === 'remote',
+      )
+      .map((item) => {
         let o = { ...item }
         o.key = o.id
         return o
@@ -248,45 +263,50 @@ const EditHostsInfo = () => {
         <FormLabel>{lang.choice_mode}</FormLabel>
         <RadioGroup
           value={(hosts?.folder_mode || 0).toString()}
-          onChange={(v: string) => onUpdate({ folder_mode: (parseInt(v) || 0) as FolderModeType })}
+          onChange={(v: string) =>
+            onUpdate({ folder_mode: (parseInt(v) || 0) as FolderModeType })
+          }
         >
           <HStack spacing={3}>
             <Radio value="0">{lang.choice_mode_default}</Radio>
             <Radio value="1">{lang.choice_mode_single}</Radio>
             <Radio value="2">{lang.choice_mode_multiple}</Radio>
           </HStack>
-        </RadioGroup
-        >
+        </RadioGroup>
       </FormControl>
     )
   }
 
-  const types: HostsType[] = [ 'local', 'remote', 'group', 'folder' ]
+  const types: HostsType[] = ['local', 'remote', 'group', 'folder']
 
   const footer_buttons = (
     <Grid templateColumns="1fr 1fr" style={{ width: '100%' }}>
       <Box>
-        {
-          is_add ? null : (
-            <Button
-              leftIcon={<BiTrash/>}
-              mr={3}
-              variant="outline"
-              disabled={!hosts}
-              colorScheme="pink"
-              onClick={() => {
-                if (hosts) {
-                  agent.broadcast(events.move_to_trashcan, [hosts.id])
-                  onCancel()
-                }
-              }}
-            >{lang.move_to_trashcan}</Button>
-          )
-        }
+        {is_add ? null : (
+          <Button
+            leftIcon={<BiTrash />}
+            mr={3}
+            variant="outline"
+            disabled={!hosts}
+            colorScheme="pink"
+            onClick={() => {
+              if (hosts) {
+                agent.broadcast(events.move_to_trashcan, [hosts.id])
+                onCancel()
+              }
+            }}
+          >
+            {lang.move_to_trashcan}
+          </Button>
+        )}
       </Box>
       <Box style={{ textAlign: 'right' }}>
-        <Button onClick={onCancel} variant="outline" mr={3}>{lang.btn_cancel}</Button>
-        <Button onClick={onSave} colorScheme="blue">{lang.btn_ok}</Button>
+        <Button onClick={onCancel} variant="outline" mr={3}>
+          {lang.btn_cancel}
+        </Button>
+        <Button onClick={onSave} colorScheme="blue">
+          {lang.btn_ok}
+        </Button>
       </Box>
     </Grid>
   )
@@ -298,11 +318,13 @@ const EditHostsInfo = () => {
       onClose={onCancel}
       size="lg"
     >
-      <DrawerOverlay/>
+      <DrawerOverlay />
       <DrawerContent>
         <DrawerHeader>
           <HStack>
-            <Box mr={1}><BiEdit/></Box>
+            <Box mr={1}>
+              <BiEdit />
+            </Box>
             <Box>{is_add ? lang.hosts_add : lang.hosts_edit}</Box>
           </HStack>
         </DrawerHeader>
@@ -314,16 +336,14 @@ const EditHostsInfo = () => {
               value={hosts?.type || 'local'}
             >
               <Stack direction="row" spacing={6}>
-                {
-                  types.map(type => (
-                    <Radio value={type} key={type} isDisabled={!is_add}>
-                      <HStack spacing="4px">
-                        <ItemIcon type={type}/>
-                        <span>{lang[type]}</span>
-                      </HStack>
-                    </Radio>
-                  ))
-                }
+                {types.map((type) => (
+                  <Radio value={type} key={type} isDisabled={!is_add}>
+                    <HStack spacing="4px">
+                      <ItemIcon type={type} />
+                      <span>{lang[type]}</span>
+                    </HStack>
+                  </Radio>
+                ))}
               </Stack>
             </RadioGroup>
           </FormControl>
@@ -334,8 +354,8 @@ const EditHostsInfo = () => {
               ref={ipt_title_ref}
               value={hosts?.title || ''}
               maxLength={50}
-              onChange={e => onUpdate({ title: e.target.value })}
-              onKeyDown={e => e.key === 'Enter' && onSave()}
+              onChange={(e) => onUpdate({ title: e.target.value })}
+              onKeyDown={(e) => e.key === 'Enter' && onSave()}
             />
           </FormControl>
 
@@ -344,9 +364,7 @@ const EditHostsInfo = () => {
           {hosts?.type === 'folder' ? forFolder() : null}
         </DrawerBody>
 
-        <DrawerFooter>
-          {footer_buttons}
-        </DrawerFooter>
+        <DrawerFooter>{footer_buttons}</DrawerFooter>
       </DrawerContent>
     </Drawer>
   )

+ 86 - 49
src/renderer/components/Editor/HostsEditor.tsx

@@ -27,9 +27,9 @@ modeHosts()
 
 interface Props {
   hosts: {
-    id: string;
-    content?: string;
-  };
+    id: string
+    content?: string
+  }
 }
 
 const HostsEditor = (props: Props) => {
@@ -38,14 +38,20 @@ const HostsEditor = (props: Props) => {
   const [hosts_id, setHostsId] = useState(hosts.id)
   const [content, setContent] = useState(hosts.content || '')
   const [is_read_only, setIsReadOnly] = useState(true)
-  const [cm_editor, setCMEditor] = useState<CodeMirror.EditorFromTextArea | null>(null)
+  const [cm_editor, setCMEditor] =
+    useState<CodeMirror.EditorFromTextArea | null>(null)
   const el_ref = useRef<HTMLTextAreaElement>(null)
-  const [find_params, setFindParams] = useState<IFindShowSourceParam | null>(null)
+  const [find_params, setFindParams] = useState<IFindShowSourceParam | null>(
+    null,
+  )
 
   const loadContent = async () => {
     if (!cm_editor) return
 
-    let content = hosts.id === '0' ? await actions.getSystemHosts() : await actions.getHostsContent(hosts.id)
+    let content =
+      hosts.id === '0'
+        ? await actions.getSystemHosts()
+        : await actions.getHostsContent(hosts.id)
     setContent(content)
     cm_editor.setValue(content)
     cm_editor.setOption('readOnly', isReadOnly(hosts))
@@ -65,9 +71,10 @@ const HostsEditor = (props: Props) => {
   }, [hosts])
 
   const toSave = lodash.debounce((id: string, content: string) => {
-    actions.setHostsContent(id, content)
+    actions
+      .setHostsContent(id, content)
       .then(() => agent.broadcast(events.hosts_content_changed, id))
-      .catch(e => console.error(e))
+      .catch((e) => console.error(e))
   }, 1000)
 
   const onChange = (content: string) => {
@@ -94,7 +101,8 @@ const HostsEditor = (props: Props) => {
       new_line = '# ' + line
     }
 
-    cm_editor.getDoc()
+    cm_editor
+      .getDoc()
       .replaceRange(
         new_line,
         { line: info.line, ch: 0 },
@@ -130,42 +138,67 @@ const HostsEditor = (props: Props) => {
     }
   }, [hosts, find_params])
 
-  useOnBroadcast(events.editor_content_change, (new_content: string) => {
-    if (new_content === content) return
-    onChange(new_content)
-  }, [hosts, hosts_id, content])
-
-  useOnBroadcast(events.editor_gutter_click, onGutterClick, [cm_editor, is_read_only])
-
-  useOnBroadcast(events.hosts_refreshed, (h: IHostsListObject) => {
-    if (hosts.id !== '0' && h.id !== hosts.id) return
-    loadContent()
-  }, [hosts, hosts_data, cm_editor])
+  useOnBroadcast(
+    events.editor_content_change,
+    (new_content: string) => {
+      if (new_content === content) return
+      onChange(new_content)
+    },
+    [hosts, hosts_id, content],
+  )
 
-  useOnBroadcast(events.hosts_refreshed_by_id, (id: string) => {
-    if (hosts.id !== '0' && id !== hosts.id) return
-    loadContent()
-  }, [hosts, hosts_data, cm_editor])
+  useOnBroadcast(events.editor_gutter_click, onGutterClick, [
+    cm_editor,
+    is_read_only,
+  ])
 
-  useOnBroadcast(events.toggle_comment, toggleComment, [cm_editor, is_read_only])
+  useOnBroadcast(
+    events.hosts_refreshed,
+    (h: IHostsListObject) => {
+      if (hosts.id !== '0' && h.id !== hosts.id) return
+      loadContent()
+    },
+    [hosts, hosts_data, cm_editor],
+  )
 
-  useOnBroadcast(events.set_hosts_on_status, () => {
-    if (hosts.id === '0') {
+  useOnBroadcast(
+    events.hosts_refreshed_by_id,
+    (id: string) => {
+      if (hosts.id !== '0' && id !== hosts.id) return
       loadContent()
-    }
-  }, [hosts, cm_editor])
+    },
+    [hosts, hosts_data, cm_editor],
+  )
+
+  useOnBroadcast(events.toggle_comment, toggleComment, [
+    cm_editor,
+    is_read_only,
+  ])
+
+  useOnBroadcast(
+    events.set_hosts_on_status,
+    () => {
+      if (hosts.id === '0') {
+        loadContent()
+      }
+    },
+    [hosts, cm_editor],
+  )
 
   const setSelection = async (params: IFindShowSourceParam) => {
     if (!cm_editor) return
     let doc = cm_editor.getDoc()
 
-    doc.setSelection({
-      line: params.line - 1,
-      ch: params.line_pos,
-    }, {
-      line: params.end_line - 1,
-      ch: params.end_line_pos,
-    })
+    doc.setSelection(
+      {
+        line: params.line - 1,
+        ch: params.line_pos,
+      },
+      {
+        line: params.end_line - 1,
+        ch: params.end_line_pos,
+      },
+    )
 
     // console.log(doc.getSelection())
     await wait(200)
@@ -175,19 +208,23 @@ const HostsEditor = (props: Props) => {
     cm_editor.focus()
   }
 
-  useOnBroadcast(events.show_source, async (params: IFindShowSourceParam) => {
-    if (!cm_editor) return
-
-    if (params.item_id !== hosts.id) {
-      setFindParams(params)
-      setTimeout(() => {
-        setFindParams(null)
-      }, 3000)
-      return
-    }
-
-    setSelection(params)
-  }, [hosts, cm_editor])
+  useOnBroadcast(
+    events.show_source,
+    async (params: IFindShowSourceParam) => {
+      if (!cm_editor) return
+
+      if (params.item_id !== hosts.id) {
+        setFindParams(params)
+        setTimeout(() => {
+          setFindParams(null)
+        }, 3000)
+        return
+      }
+
+      setSelection(params)
+    },
+    [hosts, cm_editor],
+  )
 
   return (
     <div className={styles.root}>

+ 2 - 2
src/renderer/components/Editor/cm_hl.ts

@@ -4,7 +4,7 @@ import CodeMirror from 'codemirror'
 
 export default function () {
   CodeMirror.defineMode('hosts', function () {
-    function tokenBase (stream: CodeMirror.StringStream) {
+    function tokenBase(stream: CodeMirror.StringStream) {
       if (stream.eatSpace()) return null
 
       let sol = stream.sol()
@@ -28,7 +28,7 @@ export default function () {
       return null
     }
 
-    function tokenize (stream: CodeMirror.StringStream, state: any) {
+    function tokenize(stream: CodeMirror.StringStream, state: any) {
       return (state.tokens[0] || tokenBase)(stream, state)
     }
 

+ 53 - 45
src/renderer/components/History.tsx

@@ -38,9 +38,9 @@ import { BiDetail, BiHelpCircle, BiHistory, BiTrash } from 'react-icons/bi'
 import styles from './History.less'
 
 interface IHistoryProps {
-  list: IHostsHistoryObject[];
-  selected_item: IHostsHistoryObject | undefined;
-  setSelectedItem: (item: IHostsHistoryObject) => void;
+  list: IHostsHistoryObject[]
+  selected_item: IHostsHistoryObject | undefined
+  setSelectedItem: (item: IHostsHistoryObject) => void
 }
 
 const HistoryList = (props: IHistoryProps): React.ReactElement => {
@@ -49,23 +49,23 @@ const HistoryList = (props: IHistoryProps): React.ReactElement => {
 
   if (list.length === 0) {
     return (
-      <Center h="100%" opacity={0.5} fontSize="lg">{lang.no_record}</Center>
+      <Center h="100%" opacity={0.5} fontSize="lg">
+        {lang.no_record}
+      </Center>
     )
   }
 
   return (
     <Flex h="100%" minHeight="300px">
-      <Box
-        flex={1} mr={3}
-        borderWidth="1px" borderRadius="md"
-      >
-        <HostsViewer
-          content={selected_item ? selected_item.content : ''}
-        />
+      <Box flex={1} mr={3} borderWidth="1px" borderRadius="md">
+        <HostsViewer content={selected_item ? selected_item.content : ''} />
       </Box>
       <List
-        w="200px" h="100%" overflow="auto"
-        borderWidth="1px" borderRadius="md"
+        w="200px"
+        h="100%"
+        overflow="auto"
+        borderWidth="1px"
+        borderRadius="md"
       >
         {list.map((item) => (
           <ListItem
@@ -77,9 +77,13 @@ const HistoryList = (props: IHistoryProps): React.ReactElement => {
             className={clsx(item.id === selected_item?.id && styles.selected)}
           >
             <HStack>
-              <Box><BiDetail/></Box>
+              <Box>
+                <BiDetail />
+              </Box>
               <VStack align="left" spacing={0}>
-                <Box>{dayjs(item.add_time_ms).format('YYYY-MM-DD HH:mm:ss')}</Box>
+                <Box>
+                  {dayjs(item.add_time_ms).format('YYYY-MM-DD HH:mm:ss')}
+                </Box>
                 <HStack lineHeight="14px" fontSize="9px" opacity={0.6}>
                   <Box>{item.content.split('\n').length} lines</Box>
                   <Box>{prettyBytes(item.content.length)}</Box>
@@ -96,12 +100,7 @@ const HistoryList = (props: IHistoryProps): React.ReactElement => {
 const Loading = (): React.ReactElement => {
   return (
     <Center h="300px">
-      <Spinner
-        speed="1s"
-        emptyColor="gray.200"
-        size="lg"
-        mr={3}
-      />
+      <Spinner speed="1s" emptyColor="gray.200" size="lg" mr={3} />
       <Box>Loading...</Box>
     </Center>
   )
@@ -109,10 +108,10 @@ const Loading = (): React.ReactElement => {
 
 const History = () => {
   const { configs, updateConfigs } = useModel('useConfigs')
-  const [ is_open, setIsOpen ] = useState(false)
-  const [ is_loading, setIsLoading ] = useState(false)
-  const [ list, setList ] = useState<IHostsHistoryObject[]>([])
-  const [ selected_item, setSelectedItem ] = useState<IHostsHistoryObject>()
+  const [is_open, setIsOpen] = useState(false)
+  const [is_loading, setIsLoading] = useState(false)
+  const [list, setList] = useState<IHostsHistoryObject[]>([])
+  const [selected_item, setSelectedItem] = useState<IHostsHistoryObject>()
   // const btn_close = useRef(null)
 
   const { lang } = useModel('useI18n')
@@ -141,7 +140,7 @@ const History = () => {
       return
     }
 
-    let idx = list.findIndex(i => i.id === id)
+    let idx = list.findIndex((i) => i.id === id)
     await actions.deleteHistory(id)
     setSelectedItem(undefined)
     let list2 = await loadData()
@@ -163,7 +162,7 @@ const History = () => {
     loadData()
   })
 
-  let history_limit_values: number[] = [ 10, 50, 100, 500 ]
+  let history_limit_values: number[] = [10, 50, 100, 500]
   if (configs && !history_limit_values.includes(configs.history_limit)) {
     history_limit_values.push(configs.history_limit)
     history_limit_values.sort()
@@ -177,25 +176,27 @@ const History = () => {
       onClose={onClose}
       // initialFocusRef={btn_close}
     >
-      <DrawerOverlay/>
+      <DrawerOverlay />
       <DrawerContent>
         {/*<DrawerCloseButton/>*/}
         <DrawerHeader>
           <HStack>
-            <Box mr={1}><BiHistory/></Box>
+            <Box mr={1}>
+              <BiHistory />
+            </Box>
             <Box>{lang.system_hosts_history}</Box>
           </HStack>
         </DrawerHeader>
         <DrawerBody>
-          {
-            is_loading ?
-              <Loading/> :
-              <HistoryList
-                list={list}
-                selected_item={selected_item}
-                setSelectedItem={setSelectedItem}
-              />
-          }
+          {is_loading ? (
+            <Loading />
+          ) : (
+            <HistoryList
+              list={list}
+              selected_item={selected_item}
+              setSelectedItem={setSelectedItem}
+            />
+          )}
         </DrawerBody>
         <DrawerFooter>
           <Flex width="100%" align="center">
@@ -203,19 +204,26 @@ const History = () => {
             <Box>
               <Select
                 value={configs?.history_limit}
-                onChange={e => updateHistoryLimit(parseInt(e.target.value))}
+                onChange={(e) => updateHistoryLimit(parseInt(e.target.value))}
               >
-                {history_limit_values.map(v => (
-                  <option key={v} value={v}>{v}</option>
+                {history_limit_values.map((v) => (
+                  <option key={v} value={v}>
+                    {v}
+                  </option>
                 ))}
               </Select>
             </Box>
-            <Tooltip label={lang.system_hosts_history_help} aria-label="A tooltip">
-              <Box ml={3}><BiHelpCircle/></Box>
+            <Tooltip
+              label={lang.system_hosts_history_help}
+              aria-label="A tooltip"
+            >
+              <Box ml={3}>
+                <BiHelpCircle />
+              </Box>
             </Tooltip>
-            <Spacer/>
+            <Spacer />
             <Button
-              leftIcon={<BiTrash/>}
+              leftIcon={<BiTrash />}
               variant="outline"
               mr={3}
               colorScheme="pink"

+ 8 - 6
src/renderer/components/HostsViewer.tsx

@@ -9,7 +9,7 @@ import React from 'react'
 import styles from './HostsViewer.less'
 
 interface Props {
-  content: string;
+  content: string
 }
 
 const HostsViewer = (props: Props) => {
@@ -17,19 +17,21 @@ const HostsViewer = (props: Props) => {
   const lines = content.split('\n')
 
   const Line = (p: { line: string }) => {
-    return (
-      <div className={styles.line}>{p.line}</div>
-    )
+    return <div className={styles.line}>{p.line}</div>
   }
 
   return (
     <div className={styles.root}>
       <div className={styles.content}>
         {lines.map((line, idx) => (
-          <Line line={line} key={idx}/>
+          <Line line={line} key={idx} />
         ))}
       </div>
-      <StatusBar line_count={lines.length} bytes={content.length} read_only={true}/>
+      <StatusBar
+        line_count={lines.length}
+        bytes={content.length}
+        read_only={true}
+      />
     </div>
   )
 }

+ 8 - 10
src/renderer/components/ItemIcon.tsx

@@ -16,8 +16,8 @@ import {
 } from 'react-icons/bi'
 
 interface Props {
-  type?: string;
-  is_collapsed?: boolean;
+  type?: string
+  is_collapsed?: boolean
 }
 
 const ItemIcon = (props: Props) => {
@@ -25,19 +25,17 @@ const ItemIcon = (props: Props) => {
 
   switch (type) {
     case 'folder':
-      return is_collapsed ?
-        <BiFolder/> :
-        <BiFolderOpen/>
+      return is_collapsed ? <BiFolder /> : <BiFolderOpen />
     case 'remote':
-      return <BiGlobe/>
+      return <BiGlobe />
     case 'group':
-      return <BiLayer/>
+      return <BiLayer />
     case 'system':
-      return <BiDesktop/>
+      return <BiDesktop />
     case 'trashcan':
-      return <BiTrash/>
+      return <BiTrash />
     default:
-      return <BiFile/>
+      return <BiFile />
   }
 }
 

+ 2 - 2
src/renderer/components/Lang.tsx

@@ -9,8 +9,8 @@ import { LocaleName } from '@root/common/i18n'
 import React from 'react'
 
 interface Props {
-  locale: LocaleName;
-  children: string | React.ReactElement | React.ReactElement[];
+  locale: LocaleName
+  children: string | React.ReactElement | React.ReactElement[]
 }
 
 const Lang = (props: Props): React.ReactElement | null => {

+ 4 - 4
src/renderer/components/LeftPanel/SystemHostsItem.tsx

@@ -10,9 +10,7 @@ import clsx from 'clsx'
 import React from 'react'
 import styles from './SystemHostsItem.less'
 
-interface Props {
-
-}
+interface Props {}
 
 const SystemHostsItem = (props: Props) => {
   const { i18n } = useModel('useI18n')
@@ -29,7 +27,9 @@ const SystemHostsItem = (props: Props) => {
       className={clsx(styles.root, is_selected && styles.selected)}
       onClick={showSystemHosts}
     >
-      <span className={styles.icon}><ItemIcon type="system"/></span>
+      <span className={styles.icon}>
+        <ItemIcon type="system" />
+      </span>
       <span>{i18n.lang.system_hosts}</span>
     </div>
   )

+ 21 - 15
src/renderer/components/LeftPanel/Trashcan.tsx

@@ -14,13 +14,12 @@ import React, { useEffect, useState } from 'react'
 import { BiChevronRight } from 'react-icons/bi'
 import styles from './Trashcan.less'
 
-interface Props {
-
-}
+interface Props {}
 
 const Trashcan = (props: Props) => {
   const { lang } = useModel('useI18n')
-  const { hosts_data, current_hosts, setCurrentHosts } = useModel('useHostsData')
+  const { hosts_data, current_hosts, setCurrentHosts } =
+    useModel('useHostsData')
   const [trash_list, setTrashList] = useState<ITrashcanListObject[]>([])
   const [is_collapsed, setIsCollapsed] = useState(true)
 
@@ -43,13 +42,14 @@ const Trashcan = (props: Props) => {
 
     let list: ITrashcanListObject[] = [root]
 
-    hosts_data.trashcan.map(i => {
-      root.children && root.children.push({
-        ...i,
-        id: i.data.id,
-        can_drag: false,
-        type: i.data.type,
-      })
+    hosts_data.trashcan.map((i) => {
+      root.children &&
+        root.children.push({
+          ...i,
+          id: i.data.id,
+          can_drag: false,
+          type: i.data.type,
+        })
     })
 
     setTrashList(list)
@@ -57,7 +57,7 @@ const Trashcan = (props: Props) => {
 
   const onSelect = (ids: string[]) => {
     let id = ids[0]
-    let item = hosts_data.trashcan.find(i => i.data.id === id)
+    let item = hosts_data.trashcan.find((i) => i.data.id === id)
     if (!item) return
     setCurrentHosts(item.data)
   }
@@ -66,14 +66,20 @@ const Trashcan = (props: Props) => {
     <div className={styles.root}>
       <Tree
         data={trash_list}
-        nodeRender={(item) => <TrashcanItem data={item as ITrashcanListObject}/>}
-        collapseArrow={<Center w="20px" h="20px"><BiChevronRight/></Center>}
+        nodeRender={(item) => (
+          <TrashcanItem data={item as ITrashcanListObject} />
+        )}
+        collapseArrow={
+          <Center w="20px" h="20px">
+            <BiChevronRight />
+          </Center>
+        }
         nodeClassName={list_styles.node}
         nodeSelectedClassName={list_styles.node_selected}
         nodeCollapseArrowClassName={list_styles.arrow}
         onSelect={onSelect}
         selected_ids={current_hosts ? [current_hosts.id] : []}
-        onChange={list => setIsCollapsed(!!list[0]?.is_collapsed)}
+        onChange={(list) => setIsCollapsed(!!list[0]?.is_collapsed)}
       />
     </div>
   )

+ 11 - 12
src/renderer/components/LeftPanel/TrashcanItem.tsx

@@ -15,7 +15,7 @@ import React from 'react'
 import styles from './TrashcanItem.less'
 
 interface Props {
-  data: ITrashcanListObject;
+  data: ITrashcanListObject
 }
 
 const TrashcanItem = (props: Props) => {
@@ -33,9 +33,10 @@ const TrashcanItem = (props: Props) => {
       enabled: hosts_data.trashcan.length > 0,
       click() {
         if (confirm(lang.trashcan_clear_confirm)) {
-          actions.clearTrashcan()
+          actions
+            .clearTrashcan()
             .then(loadHostsData)
-            .catch(e => console.error(e))
+            .catch((e) => console.error(e))
         }
       },
     },
@@ -45,10 +46,9 @@ const TrashcanItem = (props: Props) => {
     {
       label: lang.trashcan_restore,
       click() {
-        actions.restoreItemFromTrashcan(data.id)
-          .then(success => {
-            success && loadHostsData()
-          })
+        actions.restoreItemFromTrashcan(data.id).then((success) => {
+          success && loadHostsData()
+        })
       },
     },
     {
@@ -58,10 +58,9 @@ const TrashcanItem = (props: Props) => {
       label: lang.hosts_delete,
       click() {
         if (confirm(lang.trashcan_delete_confirm)) {
-          actions.deleteItemFromTrashcan(data.id)
-            .then(success => {
-              success && loadHostsData()
-            })
+          actions.deleteItemFromTrashcan(data.id).then((success) => {
+            success && loadHostsData()
+          })
         }
       },
     },
@@ -83,7 +82,7 @@ const TrashcanItem = (props: Props) => {
     >
       <div className={styles.title} onClick={onSelect}>
         <span className={list_item_styles.icon}>
-          <ItemIcon type={data.type} is_collapsed={true}/>
+          <ItemIcon type={data.type} is_collapsed={true} />
         </span>
 
         {data.data.title || lang.untitled}

+ 5 - 8
src/renderer/components/LeftPanel/index.tsx

@@ -14,7 +14,7 @@ import React from 'react'
 import styles from './index.less'
 
 interface Props {
-  width: number;
+  width: number
 }
 
 const Index = (props: Props) => {
@@ -24,19 +24,16 @@ const Index = (props: Props) => {
   const menu = new PopupMenu([
     {
       label: lang.hosts_add,
-      click () {
+      click() {
         agent.broadcast(events.add_new)
       },
     },
   ])
 
   return (
-    <div
-      className={styles.list}
-      onContextMenu={() => menu.show()}
-    >
-      <List/>
-      {hosts_data.trashcan.length > 0 ? <Trashcan/> : null}
+    <div className={styles.list} onContextMenu={() => menu.show()}>
+      <List />
+      {hosts_data.trashcan.length > 0 ? <Trashcan /> : null}
     </div>
   )
 }

+ 26 - 14
src/renderer/components/List/ListItem.tsx

@@ -20,23 +20,24 @@ import styles from './ListItem.less'
 import events from '@root/common/events'
 
 interface Props {
-  data: IHostsListObject;
-  selected_ids: string[];
-  is_tray?: boolean;
+  data: IHostsListObject
+  selected_ids: string[]
+  is_tray?: boolean
 }
 
 const ListItem = (props: Props) => {
   const { data, is_tray, selected_ids } = props
   const { lang, i18n } = useModel('useI18n')
-  const { hosts_data, setList, current_hosts, setCurrentHosts } = useModel('useHostsData')
-  const [ is_collapsed, setIsCollapsed ] = useState(!!data.is_collapsed)
-  const [ is_on, setIsOn ] = useState(data.on)
+  const { hosts_data, setList, current_hosts, setCurrentHosts } =
+    useModel('useHostsData')
+  const [is_collapsed, setIsCollapsed] = useState(!!data.is_collapsed)
+  const [is_on, setIsOn] = useState(data.on)
   const el = useRef<HTMLDivElement>(null)
   // const [item_height, setItemHeight] = useState(0)
 
   useEffect(() => {
     setIsOn(data.on)
-  }, [ data ])
+  }, [data])
 
   useEffect(() => {
     const is_selected = data.id === current_hosts?.id
@@ -48,8 +49,7 @@ const ListItem = (props: Props) => {
         scrollMode: 'if-needed',
       })
     }
-
-  }, [ data, current_hosts, el ])
+  }, [data, current_hosts, el])
 
   const onSelect = () => {
     setCurrentHosts(data.is_sys ? null : data)
@@ -60,8 +60,12 @@ const ListItem = (props: Props) => {
 
     let _is_collapsed = !is_collapsed
     setIsCollapsed(_is_collapsed)
-    setList(updateOneItem(hosts_data.list, { id: data.id, is_collapsed: _is_collapsed }))
-      .catch(e => console.error(e))
+    setList(
+      updateOneItem(hosts_data.list, {
+        id: data.id,
+        is_collapsed: _is_collapsed,
+      }),
+    ).catch((e) => console.error(e))
   }
 
   const toggleOn = (on?: boolean) => {
@@ -102,7 +106,12 @@ const ListItem = (props: Props) => {
             type: 'separator',
           },
           {
-            label: deal_count === 1 ? lang.move_to_trashcan : i18n.trans('move_items_to_trashcan', [deal_count.toLocaleString()]),
+            label:
+              deal_count === 1
+                ? lang.move_to_trashcan
+                : i18n.trans('move_items_to_trashcan', [
+                    deal_count.toLocaleString(),
+                  ]),
             click() {
               let ids = deal_count === 1 ? [data.id] : selected_ids
               agent.broadcast(events.move_to_trashcan, ids)
@@ -127,7 +136,10 @@ const ListItem = (props: Props) => {
           className={clsx(styles.icon, is_folder && styles.folder)}
           onClick={toggleIsCollapsed}
         >
-          <ItemIcon type={data.is_sys ? 'system' : data.type} is_collapsed={data.is_collapsed}/>
+          <ItemIcon
+            type={data.is_sys ? 'system' : data.type}
+            is_collapsed={data.is_collapsed}
+          />
         </span>
         {data.title || lang.untitled}
       </div>
@@ -144,7 +156,7 @@ const ListItem = (props: Props) => {
                 />
               </Center>
             </div>
-            <SwitchButton on={!!is_on} onChange={(on) => toggleOn(on)}/>
+            <SwitchButton on={!!is_on} onChange={(on) => toggleOn(on)} />
           </>
         )}
       </div>

+ 95 - 52
src/renderer/components/List/index.tsx

@@ -13,7 +13,11 @@ import { actions, agent } from '@renderer/core/agent'
 import useOnBroadcast from '@renderer/core/useOnBroadcast'
 import { IHostsListObject } from '@root/common/data'
 import events from '@root/common/events'
-import { findItemById, getNextSelectedItem, setOnStateOfItem } from '@root/common/hostsFn'
+import {
+  findItemById,
+  getNextSelectedItem,
+  setOnStateOfItem,
+} from '@root/common/hostsFn'
 import { IFindShowSourceParam } from '@root/common/types'
 import clsx from 'clsx'
 import React, { useEffect, useState } from 'react'
@@ -22,31 +26,31 @@ import styles from './index.less'
 import ListItem from './ListItem'
 
 interface Props {
-  is_tray?: boolean;
+  is_tray?: boolean
 }
 
 const List = (props: Props) => {
   const { is_tray } = props
-  const {
-    hosts_data,
-    loadHostsData,
-    setList,
-    current_hosts,
-    setCurrentHosts,
-  } = useModel('useHostsData')
+  const { hosts_data, loadHostsData, setList, current_hosts, setCurrentHosts } =
+    useModel('useHostsData')
   const { configs } = useModel('useConfigs')
   const { lang } = useModel('useI18n')
-  const [selected_ids, setSelectedIds] = useState<string[]>([current_hosts?.id || '0'])
+  const [selected_ids, setSelectedIds] = useState<string[]>([
+    current_hosts?.id || '0',
+  ])
   const [show_list, setShowList] = useState<IHostsListObject[]>([])
   const toast = useToast()
 
   useEffect(() => {
     if (!is_tray) {
-      setShowList([{
-        id: '0',
-        title: lang.system_hosts,
-        is_sys: true,
-      }, ...hosts_data.list])
+      setShowList([
+        {
+          id: '0',
+          title: lang.system_hosts,
+          is_sys: true,
+        },
+        ...hosts_data.list,
+      ])
     } else {
       setShowList([...hosts_data.list])
     }
@@ -54,7 +58,12 @@ const List = (props: Props) => {
 
   const onToggleItem = async (id: string, on: boolean) => {
     console.log(`toggle hosts #${id} as ${on ? 'on' : 'off'}`)
-    const new_list = setOnStateOfItem(hosts_data.list, id, on, configs?.choice_mode ?? 0)
+    const new_list = setOnStateOfItem(
+      hosts_data.list,
+      id,
+      on,
+      configs?.choice_mode ?? 0,
+    )
     let success = await writeHostsToSystem(new_list)
     if (success) {
       toast({
@@ -68,7 +77,10 @@ const List = (props: Props) => {
     }
   }
 
-  const writeHostsToSystem = async (list?: IHostsListObject[], options?: IHostsWriteOptions): Promise<boolean> => {
+  const writeHostsToSystem = async (
+    list?: IHostsListObject[],
+    options?: IHostsWriteOptions,
+  ): Promise<boolean> => {
     if (!Array.isArray(list)) {
       list = hosts_data.list
     }
@@ -76,7 +88,7 @@ const List = (props: Props) => {
     let content: string = await actions.getContentOfList(list)
     const result = await actions.setSystemHosts(content, options)
     if (result.success) {
-      setList(list).catch(e => console.error(e))
+      setList(list).catch((e) => console.error(e))
       // new Notification(lang.success, {
       //   body: lang.hosts_updated,
       // })
@@ -84,13 +96,16 @@ const List = (props: Props) => {
       if (current_hosts) {
         let hosts = findItemById(list, current_hosts.id)
         if (hosts) {
-          agent.broadcast(events.set_hosts_on_status, current_hosts.id, hosts.on)
+          agent.broadcast(
+            events.set_hosts_on_status,
+            current_hosts.id,
+            hosts.on,
+          )
         }
       }
-
     } else {
       console.log(result)
-      loadHostsData().catch(e => console.log(e))
+      loadHostsData().catch((e) => console.log(e))
       let err_desc = lang.fail
 
       // let body: string = lang.no_access_to_hosts
@@ -120,38 +135,50 @@ const List = (props: Props) => {
 
   if (!is_tray) {
     useOnBroadcast(events.toggle_item, onToggleItem, [hosts_data])
-    useOnBroadcast(events.write_hosts_to_system, writeHostsToSystem, [hosts_data])
+    useOnBroadcast(events.write_hosts_to_system, writeHostsToSystem, [
+      hosts_data,
+    ])
   } else {
     useOnBroadcast(events.tray_list_updated, loadHostsData)
   }
 
-  useOnBroadcast(events.move_to_trashcan, async (ids: string[]) => {
-    console.log(`move_to_trashcan: #${ids}`)
-    await actions.moveManyToTrashcan(ids)
-    await loadHostsData()
+  useOnBroadcast(
+    events.move_to_trashcan,
+    async (ids: string[]) => {
+      console.log(`move_to_trashcan: #${ids}`)
+      await actions.moveManyToTrashcan(ids)
+      await loadHostsData()
 
-    if (current_hosts && ids.includes(current_hosts.id)) {
-      // 选中删除指定节点后的兄弟节点
-      let next_item = getNextSelectedItem(hosts_data.list, i => ids.includes(i.id))
-      setCurrentHosts(next_item || null)
-      setSelectedIds(next_item ? [next_item.id] : [])
-    }
-  }, [current_hosts, hosts_data])
-
-  useOnBroadcast(events.select_hosts, async (id: string, wait_ms: number = 0) => {
-    let hosts = findItemById(hosts_data.list, id)
-    if (!hosts) {
-      if (wait_ms > 0) {
-        setTimeout(() => {
-          agent.broadcast(events.select_hosts, id, wait_ms - 50)
-        }, 50)
+      if (current_hosts && ids.includes(current_hosts.id)) {
+        // 选中删除指定节点后的兄弟节点
+        let next_item = getNextSelectedItem(hosts_data.list, (i) =>
+          ids.includes(i.id),
+        )
+        setCurrentHosts(next_item || null)
+        setSelectedIds(next_item ? [next_item.id] : [])
       }
-      return
-    }
+    },
+    [current_hosts, hosts_data],
+  )
 
-    setCurrentHosts(hosts)
-    setSelectedIds([id])
-  }, [hosts_data])
+  useOnBroadcast(
+    events.select_hosts,
+    async (id: string, wait_ms: number = 0) => {
+      let hosts = findItemById(hosts_data.list, id)
+      if (!hosts) {
+        if (wait_ms > 0) {
+          setTimeout(() => {
+            agent.broadcast(events.select_hosts, id, wait_ms - 50)
+          }, 50)
+        }
+        return
+      }
+
+      setCurrentHosts(hosts)
+      setSelectedIds([id])
+    },
+    [hosts_data],
+  )
 
   useOnBroadcast(events.reload_list, loadHostsData)
 
@@ -174,18 +201,27 @@ const List = (props: Props) => {
       <Tree
         data={show_list}
         selected_ids={selected_ids}
-        onChange={list => {
+        onChange={(list) => {
           setShowList(list)
-          setList(list).catch(e => console.error(e))
+          setList(list).catch((e) => console.error(e))
         }}
         onSelect={(ids: string[]) => {
           console.log(ids)
           setSelectedIds(ids)
         }}
         nodeRender={(data) => (
-          <ListItem key={data.id} data={data} is_tray={is_tray} selected_ids={selected_ids}/>
+          <ListItem
+            key={data.id}
+            data={data}
+            is_tray={is_tray}
+            selected_ids={selected_ids}
+          />
         )}
-        collapseArrow={<Center w="20px" h="20px"><BiChevronRight/></Center>}
+        collapseArrow={
+          <Center w="20px" h="20px">
+            <BiChevronRight />
+          </Center>
+        }
         nodeAttr={(item) => {
           return {
             can_drag: !item.is_sys && !is_tray,
@@ -197,7 +233,12 @@ const List = (props: Props) => {
         draggingNodeRender={(data) => {
           return (
             <div className={clsx(styles.for_drag)}>
-              <span className={clsx(styles.icon, data.type === 'folder' && styles.folder)}>
+              <span
+                className={clsx(
+                  styles.icon,
+                  data.type === 'folder' && styles.folder,
+                )}
+              >
                 <ItemIcon
                   type={data.is_sys ? 'system' : data.type}
                   is_collapsed={data.is_collapsed}
@@ -206,7 +247,9 @@ const List = (props: Props) => {
               <span>
                 {data.title || lang.untitled}
                 {selected_ids.length > 1 ? (
-                  <span className={styles.items_count}>{selected_ids.length} {lang.items}</span>
+                  <span className={styles.items_count}>
+                    {selected_ids.length} {lang.items}
+                  </span>
                 ) : null}
               </span>
             </div>

+ 2 - 6
src/renderer/components/Loading.tsx

@@ -8,16 +8,12 @@ import { useModel } from '@@/plugin-model/useModel'
 import React from 'react'
 import styles from './Loading.less'
 
-interface Props {
-
-}
+interface Props {}
 
 const Loading = (props: Props) => {
   const { i18n } = useModel('useI18n')
 
-  return (
-    <div className={styles.root}>{i18n.lang.loading}</div>
-  )
+  return <div className={styles.root}>{i18n.lang.loading}</div>
 }
 
 export default Loading

+ 19 - 12
src/renderer/components/MainPanel/index.tsx

@@ -12,8 +12,7 @@ import events from '@root/common/events'
 import React, { useEffect, useState } from 'react'
 import styles from './index.less'
 
-interface Props {
-}
+interface Props {}
 
 const MainPanel = (props: Props) => {
   const { current_hosts } = useModel('useHostsData')
@@ -21,22 +20,30 @@ const MainPanel = (props: Props) => {
 
   useEffect(() => {
     if (!current_hosts) {
-      actions.getSystemHosts().then(value => setSystemHostsContent(value))
+      actions.getSystemHosts().then((value) => setSystemHostsContent(value))
     }
   }, [current_hosts])
 
-  useOnBroadcast(events.system_hosts_updated, () => {
-    if (!current_hosts) {
-      actions.getSystemHosts().then(value => setSystemHostsContent(value))
-    }
-  }, [current_hosts])
+  useOnBroadcast(
+    events.system_hosts_updated,
+    () => {
+      if (!current_hosts) {
+        actions.getSystemHosts().then((value) => setSystemHostsContent(value))
+      }
+    },
+    [current_hosts],
+  )
 
   return (
     <div className={styles.root}>
-      <HostsEditor hosts={current_hosts || {
-        id: '0',
-        content: system_hosts_content,
-      }}/>
+      <HostsEditor
+        hosts={
+          current_hosts || {
+            id: '0',
+            content: system_hosts_content,
+          }
+        }
+      />
     </div>
   )
 }

+ 11 - 11
src/renderer/components/Pref/Advanced.tsx

@@ -20,8 +20,8 @@ import React, { useEffect, useState } from 'react'
 import styles from './styles.less'
 
 interface IProps {
-  data: ConfigsType;
-  onChange: (kv: Partial<ConfigsType>) => void;
+  data: ConfigsType
+  onChange: (kv: Partial<ConfigsType>) => void
 }
 
 const PathLink = (props: { link: string }) => {
@@ -48,14 +48,14 @@ const PathLink = (props: { link: string }) => {
 const Advanced = (props: IProps) => {
   const { data, onChange } = props
   const { lang } = useModel('useI18n')
-  const [ hosts_path, setHostsPath ] = useState('')
-  const [ data_path, setDataPath ] = useState('')
+  const [hosts_path, setHostsPath] = useState('')
+  const [data_path, setDataPath] = useState('')
 
   useEffect(() => {
-    actions.getPathOfSystemHosts()
-      .then(hosts_path => setHostsPath(hosts_path))
-    actions.getDataFolder()
-      .then(data_path => setDataPath(data_path))
+    actions
+      .getPathOfSystemHosts()
+      .then((hosts_path) => setHostsPath(hosts_path))
+    actions.getDataFolder().then((data_path) => setDataPath(data_path))
   }, [])
 
   return (
@@ -65,7 +65,7 @@ const Advanced = (props: IProps) => {
         <FormHelperText mb={2}>{lang.usage_data_help}</FormHelperText>
         <Checkbox
           isChecked={data.send_usage_data}
-          onChange={e => onChange({ send_usage_data: e.target.checked })}
+          onChange={(e) => onChange({ send_usage_data: e.target.checked })}
         >
           {lang.usage_data_agree}
         </Checkbox>
@@ -74,13 +74,13 @@ const Advanced = (props: IProps) => {
       <FormControl>
         <FormLabel>{lang.where_is_my_hosts}</FormLabel>
         <FormHelperText mb={2}>{lang.your_hosts_file_is}</FormHelperText>
-        <PathLink link={hosts_path}/>
+        <PathLink link={hosts_path} />
       </FormControl>
 
       <FormControl>
         <FormLabel>{lang.where_is_my_data}</FormLabel>
         <FormHelperText mb={2}>{lang.your_data_is}</FormHelperText>
-        <PathLink link={data_path}/>
+        <PathLink link={data_path} />
       </FormControl>
     </VStack>
   )

+ 8 - 8
src/renderer/components/Pref/Commands.tsx

@@ -19,14 +19,14 @@ import { ConfigsType } from '@root/common/default_configs'
 import React, { useState } from 'react'
 
 interface IProps {
-  data: ConfigsType;
-  onChange: (kv: Partial<ConfigsType>) => void;
+  data: ConfigsType
+  onChange: (kv: Partial<ConfigsType>) => void
 }
 
 const Commands = (props: IProps) => {
   const { data, onChange } = props
   const { lang } = useModel('useI18n')
-  const [ show_history, setShowHistory ] = useState(false)
+  const [show_history, setShowHistory] = useState(false)
 
   const toggleShowHistory = () => {
     setShowHistory(!show_history)
@@ -41,18 +41,18 @@ const Commands = (props: IProps) => {
           minHeight="200px"
           placeholder={'# echo "ok!"'}
           value={data.cmd_after_hosts_apply}
-          onChange={e => onChange({ cmd_after_hosts_apply: e.target.value })}
+          onChange={(e) => onChange({ cmd_after_hosts_apply: e.target.value })}
         />
       </FormControl>
 
       <Box>
-        <Button variant="link" onClick={toggleShowHistory}>{
-          show_history ? lang.hide_history : lang.show_history
-        }</Button>
+        <Button variant="link" onClick={toggleShowHistory}>
+          {show_history ? lang.hide_history : lang.show_history}
+        </Button>
       </Box>
 
       <Box w="100%">
-        <CommandsHistory is_show={show_history}/>
+        <CommandsHistory is_show={show_history} />
       </Box>
     </VStack>
   )

+ 17 - 13
src/renderer/components/Pref/CommandsHistory.tsx

@@ -25,12 +25,12 @@ import React, { useEffect, useState } from 'react'
 import { BiTrash } from 'react-icons/bi'
 
 interface Props {
-  is_show: boolean;
+  is_show: boolean
 }
 
 const CommandsHistory = (props: Props) => {
   const { is_show } = props
-  const [ list, setList ] = useState<ICommandRunResult[]>([])
+  const [list, setList] = useState<ICommandRunResult[]>([])
   const { lang } = useModel('useI18n')
 
   const loadData = async () => {
@@ -41,7 +41,7 @@ const CommandsHistory = (props: Props) => {
 
   const deleteOneRecord = async (_id: string) => {
     await actions.cmdDeleteHistory(_id)
-    setList(list.filter(i => i._id !== _id))
+    setList(list.filter((i) => i._id !== _id))
   }
 
   const clearAll = async () => {
@@ -53,16 +53,14 @@ const CommandsHistory = (props: Props) => {
     if (is_show) {
       loadData()
     }
-  }, [ is_show ])
+  }, [is_show])
 
   if (!is_show) {
     return null
   }
 
   if (list.length === 0) {
-    return (
-      <Center h="100px">{lang.no_record}</Center>
-    )
+    return <Center h="100px">{lang.no_record}</Center>
   }
 
   return (
@@ -75,7 +73,7 @@ const CommandsHistory = (props: Props) => {
             w="100%"
             // alignItems="top"
           >
-            <AlertIcon/>
+            <AlertIcon />
             <Box flex="1">
               <AlertTitle d="block">
                 <HStack>
@@ -83,10 +81,10 @@ const CommandsHistory = (props: Props) => {
                   <span style={{ fontWeight: 'normal' }}>
                     {dayjs(item.add_time_ms).format('YYYY-MM-DD HH:mm:ss')}
                   </span>
-                  <Spacer/>
+                  <Spacer />
                   <IconButton
                     aria-label="delete"
-                    icon={<BiTrash/>}
+                    icon={<BiTrash />}
                     size="sm"
                     variant="ghost"
                     onClick={() => item._id && deleteOneRecord(item._id)}
@@ -96,7 +94,9 @@ const CommandsHistory = (props: Props) => {
               <AlertDescription d="block" spacing={3}>
                 {item.stdout ? (
                   <>
-                    <Box><strong>stdout:</strong></Box>
+                    <Box>
+                      <strong>stdout:</strong>
+                    </Box>
                     <Box>
                       <pre>{item.stdout}</pre>
                     </Box>
@@ -104,7 +104,9 @@ const CommandsHistory = (props: Props) => {
                 ) : null}
                 {item.stderr ? (
                   <>
-                    <Box><strong>stderr:</strong></Box>
+                    <Box>
+                      <strong>stderr:</strong>
+                    </Box>
                     <Box>
                       <pre>{item.stderr}</pre>
                     </Box>
@@ -117,7 +119,9 @@ const CommandsHistory = (props: Props) => {
       })}
 
       <Box pt={10}>
-        <Button onClick={clearAll} variant="link">{lang.clear_history}</Button>
+        <Button onClick={clearAll} variant="link">
+          {lang.clear_history}
+        </Button>
       </Box>
     </VStack>
   )

+ 37 - 25
src/renderer/components/Pref/General.tsx

@@ -24,8 +24,8 @@ import { LocaleName } from '@root/common/i18n'
 import React from 'react'
 
 interface IProps {
-  data: ConfigsType;
-  onChange: (kv: Partial<ConfigsType>) => void;
+  data: ConfigsType
+  onChange: (kv: Partial<ConfigsType>) => void
 }
 
 const General = (props: IProps) => {
@@ -43,7 +43,7 @@ const General = (props: IProps) => {
           <Select
             w="200px"
             value={data.locale}
-            onChange={e => onChange({ locale: e.target.value as LocaleName })}
+            onChange={(e) => onChange({ locale: e.target.value as LocaleName })}
           >
             <option value="zh">简体中文</option>
             <option value="en">English</option>
@@ -58,7 +58,7 @@ const General = (props: IProps) => {
           <Select
             w="200px"
             value={data.theme}
-            onChange={e => onChange({ theme: e.target.value as ThemeType })}
+            onChange={(e) => onChange({ theme: e.target.value as ThemeType })}
           >
             <option value="light">{lang.theme_light}</option>
             <option value="dark">{lang.theme_dark}</option>
@@ -72,7 +72,13 @@ const General = (props: IProps) => {
           <VStack align="left">
             <RadioGroup
               value={data.choice_mode.toString()}
-              onChange={v => onChange({ choice_mode: parseInt(v.toString()) as ConfigsType['choice_mode'] })}
+              onChange={(v) =>
+                onChange({
+                  choice_mode: parseInt(
+                    v.toString(),
+                  ) as ConfigsType['choice_mode'],
+                })
+              }
             >
               <HStack spacing={10}>
                 <Radio value="1">
@@ -88,26 +94,26 @@ const General = (props: IProps) => {
         </HStack>
       </FormControl>
 
-      {
-        platform === 'darwin' ? (
-          <FormControl>
-            <HStack>
-              <Checkbox
-                isChecked={data.show_title_on_tray}
-                onChange={e => onChange({ show_title_on_tray: e.target.checked })}
-              >
-                {lang.show_title_on_tray}
-              </Checkbox>
-            </HStack>
-          </FormControl>
-        ) : null
-      }
+      {platform === 'darwin' ? (
+        <FormControl>
+          <HStack>
+            <Checkbox
+              isChecked={data.show_title_on_tray}
+              onChange={(e) =>
+                onChange({ show_title_on_tray: e.target.checked })
+              }
+            >
+              {lang.show_title_on_tray}
+            </Checkbox>
+          </HStack>
+        </FormControl>
+      ) : null}
 
       <FormControl>
         <HStack>
           <Checkbox
             isChecked={data.hide_at_launch}
-            onChange={e => onChange({ hide_at_launch: e.target.checked })}
+            onChange={(e) => onChange({ hide_at_launch: e.target.checked })}
           >
             {lang.hide_at_launch}
           </Checkbox>
@@ -119,7 +125,7 @@ const General = (props: IProps) => {
           <HStack>
             <Checkbox
               isChecked={data.hide_dock_icon}
-              onChange={e => onChange({ hide_dock_icon: e.target.checked })}
+              onChange={(e) => onChange({ hide_dock_icon: e.target.checked })}
             >
               {lang.hide_dock_icon}
             </Checkbox>
@@ -131,11 +137,15 @@ const General = (props: IProps) => {
         <VStack align="left">
           <Checkbox
             isChecked={data.remove_duplicate_records}
-            onChange={e => onChange({ remove_duplicate_records: e.target.checked })}
+            onChange={(e) =>
+              onChange({ remove_duplicate_records: e.target.checked })
+            }
           >
             {lang.remove_duplicate_records}
           </Checkbox>
-          <FormHelperText pl="20px">{lang.remove_duplicate_records_desc}</FormHelperText>
+          <FormHelperText pl="20px">
+            {lang.remove_duplicate_records_desc}
+          </FormHelperText>
         </VStack>
       </FormControl>
 
@@ -143,11 +153,13 @@ const General = (props: IProps) => {
         <VStack align="left">
           <Checkbox
             isChecked={data.http_api_on}
-            onChange={e => onChange({ http_api_on: e.target.checked })}
+            onChange={(e) => onChange({ http_api_on: e.target.checked })}
           >
             {lang.http_api_on}
           </Checkbox>
-          <FormHelperText pl="20px">{i18n.trans('http_api_on_desc', [http_api_port.toString()])}</FormHelperText>
+          <FormHelperText pl="20px">
+            {i18n.trans('http_api_on_desc', [http_api_port.toString()])}
+          </FormHelperText>
         </VStack>
       </FormControl>
     </VStack>

+ 8 - 8
src/renderer/components/Pref/Proxy.tsx

@@ -20,8 +20,8 @@ import { ConfigsType, ProtocolType } from '@root/common/default_configs'
 import React, { useState } from 'react'
 
 interface IProps {
-  data: ConfigsType;
-  onChange: (kv: Partial<ConfigsType>) => void;
+  data: ConfigsType
+  onChange: (kv: Partial<ConfigsType>) => void
 }
 
 const General = (props: IProps) => {
@@ -33,12 +33,11 @@ const General = (props: IProps) => {
 
   return (
     <VStack spacing={4}>
-
       <FormControl>
         <HStack>
           <Checkbox
             isChecked={data.use_proxy}
-            onChange={e => {
+            onChange={(e) => {
               let is_use = e.target.checked
               setIsUse(is_use)
               onChange({ use_proxy: is_use })
@@ -56,7 +55,9 @@ const General = (props: IProps) => {
             w="200px"
             isDisabled={!is_use}
             value={data.proxy_protocol}
-            onChange={e => onChange({ proxy_protocol: e.target.value as ProtocolType })}
+            onChange={(e) =>
+              onChange({ proxy_protocol: e.target.value as ProtocolType })
+            }
           >
             <option value="http">HTTP</option>
             <option value="https">HTTPS</option>
@@ -71,7 +72,7 @@ const General = (props: IProps) => {
             w="200px"
             isDisabled={!is_use}
             value={data.proxy_host}
-            onChange={e => onChange({ proxy_host: e.target.value })}
+            onChange={(e) => onChange({ proxy_host: e.target.value })}
           />
         </HStack>
       </FormControl>
@@ -85,11 +86,10 @@ const General = (props: IProps) => {
             value={data.proxy_port || ''}
             onChange={(_, vn) => onChange({ proxy_port: vn })}
           >
-            <NumberInputField/>
+            <NumberInputField />
           </NumberInput>
         </HStack>
       </FormControl>
-
     </VStack>
   )
 }

+ 10 - 15
src/renderer/components/Pref/index.tsx

@@ -33,9 +33,7 @@ import Advanced from './Advanced'
 import Commands from './Commands'
 import General from './General'
 
-interface Props {
-
-}
+interface Props {}
 
 const PreferencePanel = (props: Props) => {
   const [is_open, setIsOpen] = useState(false)
@@ -80,17 +78,14 @@ const PreferencePanel = (props: Props) => {
   }
 
   return (
-    <Drawer
-      size="lg"
-      isOpen={is_open}
-      placement="right"
-      onClose={onClose}
-    >
-      <DrawerOverlay/>
+    <Drawer size="lg" isOpen={is_open} placement="right" onClose={onClose}>
+      <DrawerOverlay />
       <DrawerContent>
         <DrawerHeader>
           <HStack>
-            <Box mr={1}><BiSliderAlt/></Box>
+            <Box mr={1}>
+              <BiSliderAlt />
+            </Box>
             <Box>{lang.preferences}</Box>
           </HStack>
         </DrawerHeader>
@@ -106,16 +101,16 @@ const PreferencePanel = (props: Props) => {
 
             <TabPanels>
               <TabPanel>
-                <General data={data} onChange={onUpdate}/>
+                <General data={data} onChange={onUpdate} />
               </TabPanel>
               <TabPanel>
-                <Commands data={data} onChange={onUpdate}/>
+                <Commands data={data} onChange={onUpdate} />
               </TabPanel>
               <TabPanel>
-                <Proxy data={data} onChange={onUpdate}/>
+                <Proxy data={data} onChange={onUpdate} />
               </TabPanel>
               <TabPanel>
-                <Advanced data={data} onChange={onUpdate}/>
+                <Advanced data={data} onChange={onUpdate} />
               </TabPanel>
             </TabPanels>
           </Tabs>

+ 7 - 13
src/renderer/components/StatusBar.tsx

@@ -11,21 +11,17 @@ import prettyBytes from 'pretty-bytes'
 import styles from './StatusBar.less'
 
 interface Props {
-  line_count: number;
-  bytes: number;
-  read_only?: boolean;
+  line_count: number
+  bytes: number
+  read_only?: boolean
 }
 
 const StatusBar = (props: Props) => {
-  const {line_count, bytes, read_only} = props
+  const { line_count, bytes, read_only } = props
   const { i18n } = useModel('useI18n')
 
   return (
-    <Flex
-      className={styles.root}
-      px="10px"
-      userSelect="none"
-    >
+    <Flex className={styles.root} px="10px" userSelect="none">
       <HStack spacing={4}>
         <Box>
           {line_count} {line_count > 1 ? i18n.lang.lines : i18n.lang.line}
@@ -33,10 +29,8 @@ const StatusBar = (props: Props) => {
         <Box>{prettyBytes(bytes)}</Box>
         <Box>{read_only ? i18n.lang.read_only : ''}</Box>
       </HStack>
-      <Spacer/>
-      <Box>
-        {/* right */}
-      </Box>
+      <Spacer />
+      <Box>{/* right */}</Box>
     </Flex>
   )
 }

+ 25 - 23
src/renderer/components/SudoPasswordInput.tsx

@@ -22,15 +22,13 @@ import events from '@root/common/events'
 import React, { useState } from 'react'
 import styles from './SudoPasswordInput.less'
 
-interface Props {
-
-}
+interface Props {}
 
 const SudoPasswordInput = (props: Props) => {
   const { lang } = useModel('useI18n')
-  const [ is_show, setIsShow ] = useState(false)
-  const [ pswd, setPswd ] = useState('')
-  const [ tmp_list, setTmpList ] = useState<IHostsListObject[] | undefined>()
+  const [is_show, setIsShow] = useState(false)
+  const [pswd, setPswd] = useState('')
+  const [tmp_list, setTmpList] = useState<IHostsListObject[] | undefined>()
   const ipt_ref = React.useRef<HTMLInputElement>(null)
 
   const onCancel = () => {
@@ -44,40 +42,44 @@ const SudoPasswordInput = (props: Props) => {
     agent.broadcast(events.write_hosts_to_system, tmp_list, { sudo_pswd: pswd })
   }
 
-  useOnBroadcast(events.show_sudo_password_input, (tmp_list?: IHostsListObject[]) => {
-    setTmpList(tmp_list)
-    setIsShow(true)
-    // console.log(tmp_list)
-    agent.broadcast(events.active_main_window)
-  }, [ tmp_list ])
+  useOnBroadcast(
+    events.show_sudo_password_input,
+    (tmp_list?: IHostsListObject[]) => {
+      setTmpList(tmp_list)
+      setIsShow(true)
+      // console.log(tmp_list)
+      agent.broadcast(events.active_main_window)
+    },
+    [tmp_list],
+  )
 
   if (!is_show) return null
 
   return (
-    <Modal
-      initialFocusRef={ipt_ref}
-      isOpen={is_show}
-      onClose={onCancel}
-    >
-      <ModalOverlay/>
+    <Modal initialFocusRef={ipt_ref} isOpen={is_show} onClose={onCancel}>
+      <ModalOverlay />
       <ModalContent>
-        <ModalCloseButton/>
+        <ModalCloseButton />
         <ModalBody pb={6}>
           <div className={styles.label}>{lang.sudo_prompt_title}</div>
           <Input
             ref={ipt_ref}
             type="password"
             value={pswd}
-            onChange={e => setPswd(e.target.value)}
+            onChange={(e) => setPswd(e.target.value)}
             autoFocus={true}
-            onKeyDown={e => {
+            onKeyDown={(e) => {
               if (e.key === 'Enter') onOk()
             }}
           />
         </ModalBody>
         <ModalFooter>
-          <Button variant="outline" onClick={onCancel} mr={3}>{lang.btn_cancel}</Button>
-          <Button colorScheme="blue" onClick={onOk}>{lang.btn_ok}</Button>
+          <Button variant="outline" onClick={onCancel} mr={3}>
+            {lang.btn_cancel}
+          </Button>
+          <Button colorScheme="blue" onClick={onOk}>
+            {lang.btn_ok}
+          </Button>
         </ModalFooter>
       </ModalContent>
     </Modal>

+ 12 - 8
src/renderer/components/SwitchButton.tsx

@@ -9,15 +9,15 @@ import React, { useEffect, useState } from 'react'
 import styles from './SwitchButton.less'
 
 interface Props {
-  on: boolean;
-  onChange?: (on: boolean) => void;
-  disabled?: boolean;
+  on: boolean
+  onChange?: (on: boolean) => void
+  disabled?: boolean
 }
 
 const SwitchButton = (props: Props) => {
   const { on, onChange, disabled } = props
-  const [ is_on, setIsOn ] = useState(on)
-  const [ is_disabled, setIsDisabled ] = useState(disabled)
+  const [is_on, setIsOn] = useState(on)
+  const [is_disabled, setIsDisabled] = useState(disabled)
 
   const onClick = () => {
     if (disabled) return
@@ -32,14 +32,18 @@ const SwitchButton = (props: Props) => {
   useEffect(() => {
     setIsOn(on)
     setIsDisabled(disabled)
-  }, [ on, disabled ])
+  }, [on, disabled])
 
   return (
     <div
-      className={clsx(styles.root, is_on && styles.on, is_disabled && styles.disabled)}
+      className={clsx(
+        styles.root,
+        is_on && styles.on,
+        is_disabled && styles.disabled,
+      )}
       onClick={onClick}
     >
-      <div className={styles.handler}/>
+      <div className={styles.handler} />
     </div>
   )
 }

+ 34 - 30
src/renderer/components/TopBar/ConfigMenu.tsx

@@ -5,7 +5,15 @@
  */
 
 import { useModel } from '@@/plugin-model/useModel'
-import { Button, Menu, MenuButton, MenuDivider, MenuItem, MenuList, useToast } from '@chakra-ui/react'
+import {
+  Button,
+  Menu,
+  MenuButton,
+  MenuDivider,
+  MenuItem,
+  MenuList,
+  useToast,
+} from '@chakra-ui/react'
 import ImportFromUrl from '@renderer/components/TopBar/ImportFromUrl'
 import { actions, agent } from '@renderer/core/agent'
 import { feedback_url, homepage_url } from '@root/common/constants'
@@ -25,9 +33,7 @@ import {
 } from 'react-icons/bi'
 import styles from './ConfigMenu.less'
 
-interface Props {
-
-}
+interface Props {}
 
 const ConfigMenu = (props: Props) => {
   const { lang } = useModel('useI18n')
@@ -38,26 +44,24 @@ const ConfigMenu = (props: Props) => {
   return (
     <>
       <Menu>
-        <MenuButton
-          as={Button}
-          variant="ghost"
-          width="35px"
-          px="10.5px"
-        >
-          <BiCog/>
+        <MenuButton as={Button} variant="ghost" width="35px" px="10.5px">
+          <BiCog />
         </MenuButton>
-        <MenuList borderColor="var(--swh-border-color-0)" className={styles.menu_list}>
+        <MenuList
+          borderColor="var(--swh-border-color-0)"
+          className={styles.menu_list}
+        >
           <MenuItem
-            icon={<BiInfoCircle/>}
+            icon={<BiInfoCircle />}
             onClick={() => agent.broadcast(events.show_about)}
           >
             {lang.about}
           </MenuItem>
 
-          <MenuDivider/>
+          <MenuDivider />
 
           <MenuItem
-            icon={<BiRefresh/>}
+            icon={<BiRefresh />}
             onClick={async () => {
               let r = await actions.checkUpdate()
               if (r === false) {
@@ -73,22 +77,22 @@ const ConfigMenu = (props: Props) => {
             {lang.check_update}
           </MenuItem>
           <MenuItem
-            icon={<BiMessageDetail/>}
+            icon={<BiMessageDetail />}
             onClick={() => actions.openUrl(feedback_url)}
           >
             {lang.feedback}
           </MenuItem>
           <MenuItem
-            icon={<BiHomeCircle/>}
+            icon={<BiHomeCircle />}
             onClick={() => actions.openUrl(homepage_url)}
           >
             {lang.homepage}
           </MenuItem>
 
-          <MenuDivider/>
+          <MenuDivider />
 
           <MenuItem
-            icon={<BiExport/>}
+            icon={<BiExport />}
             onClick={async () => {
               let r = await actions.exportData()
               if (r === null) {
@@ -111,7 +115,7 @@ const ConfigMenu = (props: Props) => {
             {lang.export}
           </MenuItem>
           <MenuItem
-            icon={<BiImport/>}
+            icon={<BiImport />}
             onClick={async () => {
               let r = await actions.importData()
               if (r === null) {
@@ -141,7 +145,7 @@ const ConfigMenu = (props: Props) => {
             {lang.import}
           </MenuItem>
           <MenuItem
-            icon={<BiImport/>}
+            icon={<BiImport />}
             onClick={async () => {
               setShowImportFromUrl(true)
             }}
@@ -149,32 +153,32 @@ const ConfigMenu = (props: Props) => {
             {lang.import_from_url}
           </MenuItem>
 
-          <MenuDivider/>
+          <MenuDivider />
 
           <MenuItem
-            icon={<BiSliderAlt/>}
+            icon={<BiSliderAlt />}
             onClick={() => agent.broadcast(events.show_preferences)}
           >
             {lang.preferences}
           </MenuItem>
           <MenuItem
-            icon={<BiWrench/>}
+            icon={<BiWrench />}
             onClick={() => actions.cmdToggleDevTools()}
           >
             {lang.toggle_developer_tools}
           </MenuItem>
 
-          <MenuDivider/>
+          <MenuDivider />
 
-          <MenuItem
-            icon={<BiExit/>}
-            onClick={() => actions.quit()}
-          >
+          <MenuItem icon={<BiExit />} onClick={() => actions.quit()}>
             {lang.quit}
           </MenuItem>
         </MenuList>
       </Menu>
-      <ImportFromUrl is_show={show_import_from_url} setIsShow={setShowImportFromUrl}/>
+      <ImportFromUrl
+        is_show={show_import_from_url}
+        setIsShow={setShowImportFromUrl}
+      />
     </>
   )
 }

+ 23 - 21
src/renderer/components/TopBar/ImportFromUrl.tsx

@@ -22,8 +22,8 @@ import React, { useRef, useState } from 'react'
 import styles from './ImportFromUrl.less'
 
 interface Props {
-  is_show: boolean;
-  setIsShow: (show: boolean) => void;
+  is_show: boolean
+  setIsShow: (show: boolean) => void
 }
 
 const ImportFromUrl = (props: Props) => {
@@ -49,7 +49,7 @@ const ImportFromUrl = (props: Props) => {
       isClosable: true,
     })
 
-    let t0 = (new Date()).getTime()
+    let t0 = new Date().getTime()
 
     if (url) {
       let r = await actions.importDataFromUrl(url)
@@ -64,7 +64,6 @@ const ImportFromUrl = (props: Props) => {
         })
         await loadHostsData()
         setCurrentHosts(null)
-
       } else {
         let description = lang.import_fail
         if (typeof r === 'string') {
@@ -79,46 +78,49 @@ const ImportFromUrl = (props: Props) => {
       }
     }
 
-    let t1 = (new Date()).getTime()
-    setTimeout(() => {
-      if (toast_ref.current) {
-        toast.close(toast_ref.current)
-      }
-    }, t1 - t0 > 1000 ? 0 : 1000)
+    let t1 = new Date().getTime()
+    setTimeout(
+      () => {
+        if (toast_ref.current) {
+          toast.close(toast_ref.current)
+        }
+      },
+      t1 - t0 > 1000 ? 0 : 1000,
+    )
     setUrl('')
   }
 
   if (!is_show) return null
 
   return (
-    <Modal
-      initialFocusRef={ipt_ref}
-      isOpen={is_show}
-      onClose={onCancel}
-    >
-      <ModalOverlay/>
+    <Modal initialFocusRef={ipt_ref} isOpen={is_show} onClose={onCancel}>
+      <ModalOverlay />
       <ModalContent>
-        <ModalCloseButton/>
+        <ModalCloseButton />
         <ModalBody pb={6}>
           <div className={styles.label}>{lang.import_from_url}</div>
           <Input
             ref={ipt_ref}
             value={url}
-            onChange={e => setUrl(e.target.value)}
+            onChange={(e) => setUrl(e.target.value)}
             autoFocus={true}
-            onKeyDown={e => {
+            onKeyDown={(e) => {
               if (e.key === 'Enter') onOk()
             }}
             placeholder={'http:// or https://'}
           />
         </ModalBody>
         <ModalFooter>
-          <Button variant="outline" onClick={onCancel} mr={3}>{lang.btn_cancel}</Button>
+          <Button variant="outline" onClick={onCancel} mr={3}>
+            {lang.btn_cancel}
+          </Button>
           <Button
             colorScheme="blue"
             onClick={onOk}
             isDisabled={!url || !url.match(/^https?:\/\/\w+/i)}
-          >{lang.btn_ok}</Button>
+          >
+            {lang.btn_ok}
+          </Button>
         </ModalFooter>
       </ModalContent>
     </Modal>

+ 39 - 32
src/renderer/components/TopBar/index.tsx

@@ -17,16 +17,18 @@ import { BiHistory, BiPlus, BiSidebar, BiX } from 'react-icons/bi'
 import styles from './index.less'
 
 interface IProps {
-  show_left_panel: boolean;
+  show_left_panel: boolean
 }
 
 export default (props: IProps) => {
   const { show_left_panel } = props
   const { lang } = useModel('useI18n')
-  const { isHostsInTrashcan, current_hosts, isReadOnly } = useModel('useHostsData')
+  const { isHostsInTrashcan, current_hosts, isReadOnly } =
+    useModel('useHostsData')
   const [is_on, setIsOn] = useState(!!current_hosts?.on)
 
-  const show_toggle_switch = !show_left_panel && current_hosts && !isHostsInTrashcan(current_hosts.id)
+  const show_toggle_switch =
+    !show_left_panel && current_hosts && !isHostsInTrashcan(current_hosts.id)
   const show_history = !current_hosts
   const show_close_button = agent.platform !== 'darwin'
 
@@ -34,18 +36,22 @@ export default (props: IProps) => {
     setIsOn(!!current_hosts?.on)
   }, [current_hosts])
 
-  useOnBroadcast(events.set_hosts_on_status, (id: string, on: boolean) => {
-    if (current_hosts && current_hosts.id === id) {
-      setIsOn(on)
-    }
-  }, [current_hosts])
+  useOnBroadcast(
+    events.set_hosts_on_status,
+    (id: string, on: boolean) => {
+      if (current_hosts && current_hosts.id === id) {
+        setIsOn(on)
+      }
+    },
+    [current_hosts],
+  )
 
   return (
     <div className={styles.root}>
       <Flex align="center" className={styles.left}>
         <IconButton
           aria-label="Toggle sidebar"
-          icon={<BiSidebar/>}
+          icon={<BiSidebar />}
           onClick={() => {
             agent.broadcast(events.toggle_left_pannel, !show_left_panel)
           }}
@@ -54,7 +60,7 @@ export default (props: IProps) => {
         />
         <IconButton
           aria-label="Add"
-          icon={<BiPlus/>}
+          icon={<BiPlus />}
           onClick={() => agent.broadcast(events.add_new)}
           variant="ghost"
         />
@@ -64,21 +70,22 @@ export default (props: IProps) => {
         <HStack className={styles.title}>
           {current_hosts ? (
             <>
-                <span className={styles.hosts_icon}>
-                  <ItemIcon type={current_hosts.type} is_collapsed={!current_hosts.folder_open}/>
-                </span>
+              <span className={styles.hosts_icon}>
+                <ItemIcon
+                  type={current_hosts.type}
+                  is_collapsed={!current_hosts.folder_open}
+                />
+              </span>
               <span className={styles.hosts_title}>
-                  {current_hosts.title || lang.untitled}
-                </span>
+                {current_hosts.title || lang.untitled}
+              </span>
             </>
           ) : (
             <>
-                <span className={styles.hosts_icon}>
-                  <ItemIcon type="system"/>
-                </span>
-              <span className={styles.hosts_title}>
-                  {lang.system_hosts}
-                </span>
+              <span className={styles.hosts_icon}>
+                <ItemIcon type="system" />
+              </span>
+              <span className={styles.hosts_title}>{lang.system_hosts}</span>
             </>
           )}
 
@@ -88,34 +95,34 @@ export default (props: IProps) => {
         </HStack>
       </Box>
 
-      <Flex
-        align="center"
-        justifyContent="flex-end"
-        className={styles.right}
-      >
+      <Flex align="center" justifyContent="flex-end" className={styles.right}>
         {show_toggle_switch ? (
           <Box mr={3}>
-            <SwitchButton on={is_on} onChange={on => {
-              current_hosts && agent.broadcast(events.toggle_item, current_hosts.id, on)
-            }}/>
+            <SwitchButton
+              on={is_on}
+              onChange={(on) => {
+                current_hosts &&
+                  agent.broadcast(events.toggle_item, current_hosts.id, on)
+              }}
+            />
           </Box>
         ) : null}
         {show_history ? (
           <IconButton
             aria-label="Show history"
-            icon={<BiHistory/>}
+            icon={<BiHistory />}
             variant="ghost"
             onClick={() => agent.broadcast(events.show_history)}
           />
         ) : null}
 
-        <ConfigMenu/>
+        <ConfigMenu />
 
         {show_close_button ? (
           <IconButton
             aria-label="Close window"
             fontSize="20px"
-            icon={<BiX/>}
+            icon={<BiX />}
             variant="ghost"
             onClick={() => actions.closeMainWindow()}
           />

+ 39 - 33
src/renderer/components/Transfer.tsx

@@ -14,22 +14,22 @@ import styles from './Transfer.less'
 type IdType = string
 
 interface ITransferSourceObject {
-  id: IdType;
+  id: IdType
 
-  [key: string]: any;
+  [key: string]: any
 }
 
 interface IListProps {
-  data: ITransferSourceObject[];
-  selected_keys: IdType[];
-  setSelectedKeys: (ids: IdType[]) => void;
+  data: ITransferSourceObject[]
+  selected_keys: IdType[]
+  setSelectedKeys: (ids: IdType[]) => void
 }
 
 interface Props {
-  dataSource: ITransferSourceObject[];
-  targetKeys: IdType[];
-  render?: (obj: ITransferSourceObject) => React.ReactElement;
-  onChange?: (next_target_keys: IdType[]) => void;
+  dataSource: ITransferSourceObject[]
+  targetKeys: IdType[]
+  render?: (obj: ITransferSourceObject) => React.ReactElement
+  onChange?: (next_target_keys: IdType[]) => void
 }
 
 const Transfer = (props: Props) => {
@@ -44,15 +44,15 @@ const Transfer = (props: Props) => {
 
     const toggleSelect = (id: IdType) => {
       setSelectedKeys(
-        selected_keys.includes(id) ?
-          selected_keys.filter(i => i != id) :
-          [...selected_keys, id],
+        selected_keys.includes(id)
+          ? selected_keys.filter((i) => i != id)
+          : [...selected_keys, id],
       )
     }
 
     return (
       <div className={styles.list}>
-        {data.map(item => {
+        {data.map((item) => {
           if (!item || !item.id) return null
           const is_selected = selected_keys.includes(item.id)
 
@@ -64,11 +64,7 @@ const Transfer = (props: Props) => {
               py={1}
               onClick={() => toggleSelect(item.id)}
             >
-              {
-                render ?
-                  render(item) :
-                  item.title || item.id
-              }
+              {render ? render(item) : item.title || item.id}
             </Box>
           )
         })}
@@ -84,7 +80,7 @@ const Transfer = (props: Props) => {
   }
 
   const moveRightToLeft = () => {
-    let result = right_keys.filter(i => !right_selectd_keys.includes(i))
+    let result = right_keys.filter((i) => !right_selectd_keys.includes(i))
     setRightKeys(result)
     setRightSelectedKeys([])
     onChange && onChange(result)
@@ -95,14 +91,17 @@ const Transfer = (props: Props) => {
       <Grid templateColumns="minmax(0, 1fr) 40px minmax(0, 1fr)" gap={1}>
         <Box borderWidth="1px" borderRadius="md">
           <Box className={styles.title} borderBottomWidth={1} px={3} py={1}>
-            {lang.all} <span>({
-            left_selectd_keys.length === 0
-              ? dataSource.length
-              : `${left_selectd_keys.length}/${dataSource.length}`
-          })</span>
+            {lang.all}{' '}
+            <span>
+              (
+              {left_selectd_keys.length === 0
+                ? dataSource.length
+                : `${left_selectd_keys.length}/${dataSource.length}`}
+              )
+            </span>
           </Box>
           <List
-            data={dataSource.filter(i => !right_keys.includes(i.id))}
+            data={dataSource.filter((i) => !right_keys.includes(i.id))}
             selected_keys={left_selectd_keys}
             setSelectedKeys={setLeftSelectedKeys}
           />
@@ -113,7 +112,7 @@ const Transfer = (props: Props) => {
               size="sm"
               variant="outline"
               aria-label="Move to right"
-              icon={<ArrowForwardIcon/>}
+              icon={<ArrowForwardIcon />}
               isDisabled={left_selectd_keys.length === 0}
               onClick={moveLeftToRight}
             />
@@ -121,7 +120,7 @@ const Transfer = (props: Props) => {
               size="sm"
               variant="outline"
               aria-label="Move to left"
-              icon={<ArrowBackIcon/>}
+              icon={<ArrowBackIcon />}
               isDisabled={right_selectd_keys.length === 0}
               onClick={moveRightToLeft}
             />
@@ -129,14 +128,21 @@ const Transfer = (props: Props) => {
         </Center>
         <Box borderWidth="1px" borderRadius="md">
           <Box className={styles.title} borderBottomWidth={1} px={3} py={1}>
-            {lang.selected} <span>({
-            right_selectd_keys.length === 0
-              ? right_keys.length
-              : `${right_selectd_keys.length}/${right_keys.length}`
-          })</span>
+            {lang.selected}{' '}
+            <span>
+              (
+              {right_selectd_keys.length === 0
+                ? right_keys.length
+                : `${right_selectd_keys.length}/${right_keys.length}`}
+              )
+            </span>
           </Box>
           <List
-            data={right_keys.map(id => dataSource.find(i => i.id === id)) as ITransferSourceObject[]}
+            data={
+              right_keys.map((id) =>
+                dataSource.find((i) => i.id === id),
+              ) as ITransferSourceObject[]
+            }
             selected_keys={right_selectd_keys}
             setSelectedKeys={setRightSelectedKeys}
           />

+ 86 - 76
src/renderer/components/Tree/Node.tsx

@@ -13,54 +13,60 @@ import { DropWhereType, MultipleSelectType, NodeIdType } from './Tree'
 
 declare global {
   interface Window {
-    _t_dragover_id?: string;
-    _t_dragover_ts: number;
+    _t_dragover_id?: string
+    _t_dragover_ts: number
   }
 }
 
 export type NodeUpdate = (data: Partial<ITreeNodeData>) => void
 
 export interface ITreeNodeData {
-  id: NodeIdType;
-  title?: string;
-  can_select?: boolean; // 是否可以被选中,默认为 true
-  can_drag?: boolean; // 是否可以拖动,默认为 true
-  can_drop_before?: boolean; // 是否可以接受 drop before,默认为 true
-  can_drop_in?: boolean; // 是否可以接受 drop in,默认为 true
-  can_drop_after?: boolean; // 是否可以接受 drop after,默认为 true
-  is_collapsed?: boolean;
-  children?: ITreeNodeData[];
-
-  [key: string]: any;
+  id: NodeIdType
+  title?: string
+  can_select?: boolean // 是否可以被选中,默认为 true
+  can_drag?: boolean // 是否可以拖动,默认为 true
+  can_drop_before?: boolean // 是否可以接受 drop before,默认为 true
+  can_drop_in?: boolean // 是否可以接受 drop in,默认为 true
+  can_drop_after?: boolean // 是否可以接受 drop after,默认为 true
+  is_collapsed?: boolean
+  children?: ITreeNodeData[]
+
+  [key: string]: any
 }
 
 interface INodeProps {
-  tree: ITreeNodeData[];
-  data: ITreeNodeData;
-  nodeClassName?: string;
-  nodeDropInClassName?: string;
-  nodeSelectedClassName?: string;
-  nodeCollapseArrowClassName?: string;
-  drag_source_id: NodeIdType | null;
-  drop_target_id: NodeIdType | null;
-  drag_target_where: DropWhereType | null;
-  onDragStart: (id: NodeIdType) => void;
-  onDragEnd: () => void;
-  setDropTargetId: (id: NodeIdType | null) => void;
-  setDropWhere: (where: DropWhereType | null) => void;
-  selected_ids: NodeIdType[];
-  onSelect: (id: NodeIdType, multiple_type?: MultipleSelectType) => void;
-  level: number;
-  is_dragging: boolean;
-  render?: (data: ITreeNodeData, update: NodeUpdate) => React.ReactElement | null;
-  draggingNodeRender?: (data: ITreeNodeData, source_ids: string[]) => React.ReactElement;
-  collapseArrow?: string | React.ReactElement;
-  onChange: (id: NodeIdType, data: Partial<ITreeNodeData>) => void;
-  indent_px?: number;
-  nodeAttr?: (node: ITreeNodeData) => Partial<ITreeNodeData>;
-  has_no_child: boolean;
-  no_child_no_indent?: boolean;
-  allowed_multiple_selection?: boolean;
+  tree: ITreeNodeData[]
+  data: ITreeNodeData
+  nodeClassName?: string
+  nodeDropInClassName?: string
+  nodeSelectedClassName?: string
+  nodeCollapseArrowClassName?: string
+  drag_source_id: NodeIdType | null
+  drop_target_id: NodeIdType | null
+  drag_target_where: DropWhereType | null
+  onDragStart: (id: NodeIdType) => void
+  onDragEnd: () => void
+  setDropTargetId: (id: NodeIdType | null) => void
+  setDropWhere: (where: DropWhereType | null) => void
+  selected_ids: NodeIdType[]
+  onSelect: (id: NodeIdType, multiple_type?: MultipleSelectType) => void
+  level: number
+  is_dragging: boolean
+  render?: (
+    data: ITreeNodeData,
+    update: NodeUpdate,
+  ) => React.ReactElement | null
+  draggingNodeRender?: (
+    data: ITreeNodeData,
+    source_ids: string[],
+  ) => React.ReactElement
+  collapseArrow?: string | React.ReactElement
+  onChange: (id: NodeIdType, data: Partial<ITreeNodeData>) => void
+  indent_px?: number
+  nodeAttr?: (node: ITreeNodeData) => Partial<ITreeNodeData>
+  has_no_child: boolean
+  no_child_no_indent?: boolean
+  allowed_multiple_selection?: boolean
 }
 
 const Node = (props: INodeProps) => {
@@ -137,15 +143,15 @@ const Node = (props: INodeProps) => {
 
     setDropTargetId(data.id)
 
-    let now = (new Date()).getTime()
+    let now = new Date().getTime()
     if (window._t_dragover_id !== data.id) {
       window._t_dragover_id = data.id
       window._t_dragover_ts = now
     }
     if (
-      data.children?.length
-      && data.is_collapsed
-      && now - window._t_dragover_ts > 1000
+      data.children?.length &&
+      data.is_collapsed &&
+      now - window._t_dragover_ts > 1000
     ) {
       props.onChange(data.id, { is_collapsed: false })
     }
@@ -202,7 +208,9 @@ const Node = (props: INodeProps) => {
   const is_drag_source = drag_source_id === data.id
   const is_drop_target = drop_target_id === data.id
   const is_selected = selected_ids.includes(data.id)
-  const is_parent_is_drag_source = drag_source_id ? isChildOf(props.tree, data.id, drag_source_id) : false
+  const is_parent_is_drag_source = drag_source_id
+    ? isChildOf(props.tree, data.id, drag_source_id)
+    : false
   const has_children = Array.isArray(data.children) && data.children.length > 0
 
   return (
@@ -213,8 +221,12 @@ const Node = (props: INodeProps) => {
           styles.node,
           is_dragging && styles.is_dragging,
           (is_drag_source || is_parent_is_drag_source) && styles.is_source,
-          is_drop_target && drag_target_where === 'before' && styles.drop_before,
-          is_drop_target && drag_target_where === 'in' && (props.nodeDropInClassName || styles.drop_in),
+          is_drop_target &&
+            drag_target_where === 'before' &&
+            styles.drop_before,
+          is_drop_target &&
+            drag_target_where === 'in' &&
+            (props.nodeDropInClassName || styles.drop_in),
           is_drop_target && drag_target_where === 'after' && styles.drop_after,
           is_selected && (props.nodeSelectedClassName || styles.selected),
           nodeClassName,
@@ -244,12 +256,16 @@ const Node = (props: INodeProps) => {
           paddingLeft: level * (indent_px || 20) + 4,
         }}
       >
-        <div className={clsx(
-          styles.content,
-          props.has_no_child && props.no_child_no_indent && styles.no_children,
-        )}>
-          <div className={styles.ln_header} data-role="tree-node-header">{
-            has_children ?
+        <div
+          className={clsx(
+            styles.content,
+            props.has_no_child &&
+              props.no_child_no_indent &&
+              styles.no_children,
+          )}
+        >
+          <div className={styles.ln_header} data-role="tree-node-header">
+            {has_children ? (
               <div
                 className={clsx(
                   styles.arrow,
@@ -262,45 +278,39 @@ const Node = (props: INodeProps) => {
                 }}
               >
                 {props.collapseArrow ? props.collapseArrow : '>'}
-              </div> :
-              null
-          }</div>
+              </div>
+            ) : null}
+          </div>
           <div className={styles.ln_body} data-role="tree-node-body">
-            {
-              render ? render(data, onUpdate) : (data.title || `node#${data.id}`)
-            }
+            {render ? render(data, onUpdate) : data.title || `node#${data.id}`}
           </div>
         </div>
       </div>
       {draggingNodeRender && (
         <div ref={el_dragging} className={styles.for_dragging}>
-          {
-            draggingNodeRender(data, selected_ids.includes(data.id) ? selected_ids : [data.id])
-          }
+          {draggingNodeRender(
+            data,
+            selected_ids.includes(data.id) ? selected_ids : [data.id],
+          )}
         </div>
       )}
       {has_children && data.children && !data.is_collapsed
         ? data.children.map((node) => (
-          <Node
-            {...props}
-            key={node.id}
-            data={node}
-            level={level + 1}
-          />
-        ))
+            <Node {...props} key={node.id} data={node} level={level + 1} />
+          ))
         : null}
     </>
   )
 }
 
-function diff<T> (a: T[], b: T[]): T[] {
+function diff<T>(a: T[], b: T[]): T[] {
   return [
-    ...a.filter(i => !b.includes(i)),
-    ...b.filter(i => !a.includes(i)),
+    ...a.filter((i) => !b.includes(i)),
+    ...b.filter((i) => !a.includes(i)),
   ]
 }
 
-function isEqual (prevProps: INodeProps, nextProps: INodeProps): boolean {
+function isEqual(prevProps: INodeProps, nextProps: INodeProps): boolean {
   let { data, selected_ids, allowed_multiple_selection } = nextProps
 
   if (!lodash.isEqual(prevProps.data, data)) {
@@ -330,10 +340,10 @@ function isEqual (prevProps: INodeProps, nextProps: INodeProps): boolean {
 
   let { drag_source_id, drop_target_id } = nextProps
   if (
-    isSelfOrChild(data, drag_source_id)
-    || isSelfOrChild(data, drop_target_id)
-    || isSelfOrChild(data, prevProps.drag_source_id)
-    || isSelfOrChild(data, prevProps.drop_target_id)
+    isSelfOrChild(data, drag_source_id) ||
+    isSelfOrChild(data, drop_target_id) ||
+    isSelfOrChild(data, prevProps.drag_source_id) ||
+    isSelfOrChild(data, prevProps.drop_target_id)
   ) {
     return false
   }

+ 43 - 23
src/renderer/components/Tree/Tree.tsx

@@ -7,31 +7,43 @@
 import clsx from 'clsx'
 import lodash from 'lodash'
 import React, { useEffect, useState } from 'react'
-import { canBeSelected, flatten, getNodeById, selectTo, treeMoveNode } from './fn'
+import {
+  canBeSelected,
+  flatten,
+  getNodeById,
+  selectTo,
+  treeMoveNode,
+} from './fn'
 import Node, { ITreeNodeData, NodeUpdate } from './Node'
 import styles from './style.less'
 
-export type NodeIdType = string;
-export type DropWhereType = 'before' | 'in' | 'after';
+export type NodeIdType = string
+export type DropWhereType = 'before' | 'in' | 'after'
 export type MultipleSelectType = 0 | 1 | 2
 
 interface ITreeProps {
-  data: ITreeNodeData[];
-  className?: string;
-  nodeClassName?: string;
-  nodeSelectedClassName?: string;
-  nodeDropInClassName?: string;
-  nodeCollapseArrowClassName?: string;
-  nodeRender?: (node: ITreeNodeData, update: NodeUpdate) => React.ReactElement | null;
-  nodeAttr?: (node: ITreeNodeData) => Partial<ITreeNodeData>;
-  draggingNodeRender?: (node: ITreeNodeData, source_ids: string[]) => React.ReactElement;
-  collapseArrow?: string | React.ReactElement;
-  onChange?: (tree: ITreeNodeData[]) => void;
-  indent_px?: number;
-  selected_ids: NodeIdType[];
-  onSelect?: (ids: NodeIdType[]) => void;
-  no_child_no_indent?: boolean;
-  allowed_multiple_selection?: boolean;
+  data: ITreeNodeData[]
+  className?: string
+  nodeClassName?: string
+  nodeSelectedClassName?: string
+  nodeDropInClassName?: string
+  nodeCollapseArrowClassName?: string
+  nodeRender?: (
+    node: ITreeNodeData,
+    update: NodeUpdate,
+  ) => React.ReactElement | null
+  nodeAttr?: (node: ITreeNodeData) => Partial<ITreeNodeData>
+  draggingNodeRender?: (
+    node: ITreeNodeData,
+    source_ids: string[],
+  ) => React.ReactElement
+  collapseArrow?: string | React.ReactElement
+  onChange?: (tree: ITreeNodeData[]) => void
+  indent_px?: number
+  selected_ids: NodeIdType[]
+  onSelect?: (ids: NodeIdType[]) => void
+  no_child_no_indent?: boolean
+  allowed_multiple_selection?: boolean
 }
 
 const Tree = (props: ITreeProps) => {
@@ -40,7 +52,9 @@ const Tree = (props: ITreeProps) => {
   const [is_dragging, setIsDragging] = useState(false)
   const [drag_source_id, setDragSourceId] = useState<NodeIdType | null>(null)
   const [drop_target_id, setDropTargetId] = useState<NodeIdType | null>(null)
-  const [selected_ids, setSelectedIds] = useState<NodeIdType[]>(props.selected_ids || [])
+  const [selected_ids, setSelectedIds] = useState<NodeIdType[]>(
+    props.selected_ids || [],
+  )
   const [drop_where, setDropWhere] = useState<DropWhereType | null>(null)
 
   useEffect(() => {
@@ -48,7 +62,10 @@ const Tree = (props: ITreeProps) => {
   }, [data])
 
   useEffect(() => {
-    if (props.selected_ids && props.selected_ids.join(',') !== selected_ids.join(',')) {
+    if (
+      props.selected_ids &&
+      props.selected_ids.join(',') !== selected_ids.join(',')
+    ) {
       setSelectedIds(props.selected_ids)
     }
   }, [props.selected_ids])
@@ -101,7 +118,10 @@ const Tree = (props: ITreeProps) => {
     onTreeChange(tree2)
   }
 
-  const onSelectOne = (id: NodeIdType, multiple_type: MultipleSelectType = 0) => {
+  const onSelectOne = (
+    id: NodeIdType,
+    multiple_type: MultipleSelectType = 0,
+  ) => {
     // console.log('multiple_type:', multiple_type, 'ids:', selected_ids, 'id:', id)
     const { onSelect } = props
     let new_selected_ids: NodeIdType[] = []
@@ -118,7 +138,7 @@ const Tree = (props: ITreeProps) => {
         return
       }
       if (selected_ids.includes(id)) {
-        new_selected_ids = selected_ids.filter(i => i !== id)
+        new_selected_ids = selected_ids.filter((i) => i !== id)
       } else {
         new_selected_ids = [...selected_ids, id]
       }

+ 69 - 35
src/renderer/components/Tree/fn.ts

@@ -3,30 +3,30 @@ import { ITreeNodeData } from './Node'
 import { DropWhereType, NodeIdType } from './Tree'
 
 interface IObj {
-  [key: string]: any;
+  [key: string]: any
 }
 
-export type KeyMapType = [string, string];
+export type KeyMapType = [string, string]
 
-export function flatten (tree_list: ITreeNodeData[]): ITreeNodeData[] {
+export function flatten(tree_list: ITreeNodeData[]): ITreeNodeData[] {
   let arr: any[] = []
 
   Array.isArray(tree_list) &&
-  tree_list.map((item) => {
-    if (!item) return
+    tree_list.map((item) => {
+      if (!item) return
 
-    arr.push(item)
+      arr.push(item)
 
-    if (Array.isArray(item.children)) {
-      let a2 = flatten(item.children)
-      arr = arr.concat(a2)
-    }
-  })
+      if (Array.isArray(item.children)) {
+        let a2 = flatten(item.children)
+        arr = arr.concat(a2)
+      }
+    })
 
   return arr
 }
 
-export function getParentList (
+export function getParentList(
   tree_list: ITreeNodeData[],
   id: NodeIdType,
 ): ITreeNodeData[] {
@@ -63,7 +63,7 @@ export const treeMoveNode = (
 
   let source_nodes: ITreeNodeData[] = []
   while (true) {
-    let idx = source_parent_list.findIndex(i => source_ids.includes(i.id))
+    let idx = source_parent_list.findIndex((i) => source_ids.includes(i.id))
     if (idx === -1) break
     let node = source_parent_list.splice(idx, 1)[0]
     source_nodes.push(node)
@@ -91,29 +91,43 @@ export const treeMoveNode = (
   return tree_list
 }
 
-export function getNodeById (tree_list: ITreeNodeData[], id: NodeIdType): ITreeNodeData | undefined {
-  return flatten(tree_list).find(i => i.id === id)
+export function getNodeById(
+  tree_list: ITreeNodeData[],
+  id: NodeIdType,
+): ITreeNodeData | undefined {
+  return flatten(tree_list).find((i) => i.id === id)
 }
 
 /**
  * a is child of b
  */
-export function isChildOf (tree_list: ITreeNodeData[], a_id: NodeIdType, b_id: NodeIdType): boolean {
+export function isChildOf(
+  tree_list: ITreeNodeData[],
+  a_id: NodeIdType,
+  b_id: NodeIdType,
+): boolean {
   if (a_id === b_id) return false
 
   let target_node = getNodeById(tree_list, b_id)
   if (!target_node || !Array.isArray(target_node.children)) return false
 
-  return flatten(target_node.children).findIndex(i => i.id === a_id) > -1
+  return flatten(target_node.children).findIndex((i) => i.id === a_id) > -1
 }
 
-export function isSelfOrChild (item: ITreeNodeData, id: NodeIdType | null): boolean {
+export function isSelfOrChild(
+  item: ITreeNodeData,
+  id: NodeIdType | null,
+): boolean {
   if (!id) return false
   if (item.id === id) return true
-  return flatten(item.children || []).findIndex(i => i.id === id) > -1
+  return flatten(item.children || []).findIndex((i) => i.id === id) > -1
 }
 
-export function objKeyMap (obj: IObj, key_maps: KeyMapType[], reversed: boolean = false): IObj {
+export function objKeyMap(
+  obj: IObj,
+  key_maps: KeyMapType[],
+  reversed: boolean = false,
+): IObj {
   if (reversed) {
     key_maps = keyMapReverse(key_maps)
   }
@@ -121,8 +135,8 @@ export function objKeyMap (obj: IObj, key_maps: KeyMapType[], reversed: boolean
   let keys = Object.keys(obj)
   let new_obj: IObj = {}
 
-  keys.map(key => {
-    let map = key_maps.find(i => i[0] === key)
+  keys.map((key) => {
+    let map = key_maps.find((i) => i[0] === key)
     let value = obj[key]
 
     if (Array.isArray(value)) {
@@ -141,54 +155,74 @@ export function objKeyMap (obj: IObj, key_maps: KeyMapType[], reversed: boolean
   return new_obj
 }
 
-export function treeKeyMap (tree_list: IObj[], key_maps: KeyMapType[], reversed: boolean = false): any[] {
+export function treeKeyMap(
+  tree_list: IObj[],
+  key_maps: KeyMapType[],
+  reversed: boolean = false,
+): any[] {
   if (reversed) {
     key_maps = keyMapReverse(key_maps)
   }
 
-  return tree_list.map(item => objKeyMap(item, key_maps))
+  return tree_list.map((item) => objKeyMap(item, key_maps))
 }
 
-export function keyMapReverse (key_maps: KeyMapType[]): KeyMapType[] {
+export function keyMapReverse(key_maps: KeyMapType[]): KeyMapType[] {
   return key_maps.map(([a, b]) => [b, a])
 }
 
-export function isParent (tree_list: ITreeNodeData[], item: ITreeNodeData, id: string): boolean {
+export function isParent(
+  tree_list: ITreeNodeData[],
+  item: ITreeNodeData,
+  id: string,
+): boolean {
   let parents = getParentList(tree_list, item.id)
-  return parents.findIndex(i => i.id === id) > -1
+  return parents.findIndex((i) => i.id === id) > -1
 }
 
-export function canBeSelected (tree_list: ITreeNodeData[], selected_ids: NodeIdType[], new_id: NodeIdType): boolean {
+export function canBeSelected(
+  tree_list: ITreeNodeData[],
+  selected_ids: NodeIdType[],
+  new_id: NodeIdType,
+): boolean {
   let id_one = selected_ids[0]
   if (!id_one) return true
 
   if (
-    tree_list.findIndex(i => i.id === id_one) > -1 &&
-    tree_list.findIndex(i => i.id === new_id) > -1
+    tree_list.findIndex((i) => i.id === id_one) > -1 &&
+    tree_list.findIndex((i) => i.id === new_id) > -1
   ) {
     return true
   }
 
   let flat = flatten(tree_list)
-  let parent = flat.find(i => i.children && i.children.findIndex(j => j.id === id_one) > -1)
+  let parent = flat.find(
+    (i) => i.children && i.children.findIndex((j) => j.id === id_one) > -1,
+  )
   if (!parent || !parent.children) {
     return false
   }
 
-  return parent.children.findIndex(i => i.id === new_id) > -1
+  return parent.children.findIndex((i) => i.id === new_id) > -1
 }
 
-export function selectTo (tree_list: ITreeNodeData[], selected_ids: NodeIdType[], new_id: NodeIdType): NodeIdType[] {
+export function selectTo(
+  tree_list: ITreeNodeData[],
+  selected_ids: NodeIdType[],
+  new_id: NodeIdType,
+): NodeIdType[] {
   if (!canBeSelected(tree_list, selected_ids, new_id)) {
     return selected_ids
   }
 
   let list: ITreeNodeData[]
-  if (tree_list.findIndex(i => i.id === new_id) > -1) {
+  if (tree_list.findIndex((i) => i.id === new_id) > -1) {
     list = tree_list
   } else {
     let flat = flatten(tree_list)
-    let parent = flat.find(i => i.children && i.children.findIndex(j => j.id === new_id) > -1)
+    let parent = flat.find(
+      (i) => i.children && i.children.findIndex((j) => j.id === new_id) > -1,
+    )
     if (!parent || !parent.children) {
       return selected_ids
     }

+ 4 - 5
src/renderer/core/PopupMenu.ts

@@ -9,7 +9,7 @@ import { IMenuItemOption } from '@root/common/types'
 
 let _idx: number = 0
 
-type OffFunction = () => void;
+type OffFunction = () => void
 
 export class PopupMenu {
   private _id: string
@@ -25,7 +25,7 @@ export class PopupMenu {
     // console.log('show')
     this.onHide()
 
-    let items = this._items.map(i => {
+    let items = this._items.map((i) => {
       let d = { ...i }
 
       if (typeof d.click === 'function') {
@@ -44,12 +44,11 @@ export class PopupMenu {
       menu_id: this._id,
       items,
     })
-
     ;((offs: OffFunction[]) => {
       agent.once(`popup_menu_close:${this._id}`, () => {
         // console.log(`on popup_menu_close:${this._id}`)
         setTimeout(() => {
-          offs.map(o => o())
+          offs.map((o) => o())
         }, 100)
       })
     })(this._offs)
@@ -57,7 +56,7 @@ export class PopupMenu {
 
   private onHide() {
     // console.log('hide...')
-    this._offs.map(o => o())
+    this._offs.map((o) => o())
     this._offs = []
   }
 }

+ 7 - 4
src/renderer/core/agent.ts

@@ -6,10 +6,13 @@
 
 import { Actions } from '@main/types'
 
-export const actions: Actions = new Proxy({}, {
-  get(obj, key: keyof Actions) {
-    return (...params: any[]) => window._agent.call(key, ...params)
+export const actions: Actions = new Proxy(
+  {},
+  {
+    get(obj, key: keyof Actions) {
+      return (...params: any[]) => window._agent.call(key, ...params)
+    },
   },
-}) as Actions
+) as Actions
 
 export const agent = window._agent

+ 5 - 1
src/renderer/core/useOnBroadcast.ts

@@ -2,7 +2,11 @@ import { EventHandler } from '@main/preload'
 import { agent } from '@renderer/core/agent'
 import { useEffect } from 'react'
 
-const useOnBroadcast = (event: string, handler: EventHandler, deps: any[] = []) => {
+const useOnBroadcast = (
+  event: string,
+  handler: EventHandler,
+  deps: any[] = [],
+) => {
   // agent.on will return an off function for clean up
   useEffect(() => agent.on(event, handler), deps)
 }

+ 1 - 1
src/renderer/models/useConfigs.ts

@@ -9,7 +9,7 @@ import { ConfigsType } from '@root/common/default_configs'
 import { useEffect, useState } from 'react'
 
 export default function useConfigs() {
-  const [ configs, setConfigs ] = useState<ConfigsType | null>(null)
+  const [configs, setConfigs] = useState<ConfigsType | null>(null)
 
   const loadConfigs = async () => {
     setConfigs(await actions.configAll())

部分文件因为文件数量过多而无法显示