index.tsx 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225
  1. /**
  2. * List
  3. * @author: oldj
  4. * @homepage: https://oldj.net
  5. */
  6. import { useModel } from '@@/plugin-model/useModel'
  7. import { Center, useToast } from '@chakra-ui/react'
  8. import { IHostsWriteOptions } from '@main/types'
  9. import ItemIcon from '@renderer/components/ItemIcon'
  10. import { Tree } from '@renderer/components/Tree'
  11. import { actions, agent } from '@renderer/core/agent'
  12. import useOnBroadcast from '@renderer/core/useOnBroadcast'
  13. import { IHostsListObject } from '@root/common/data'
  14. import events from '@root/common/events'
  15. import { findItemById, getNextSelectedItem, setOnStateOfItem } from '@root/common/hostsFn'
  16. import { IFindShowSourceParam } from '@root/common/types'
  17. import clsx from 'clsx'
  18. import React, { useEffect, useState } from 'react'
  19. import { BiChevronRight } from 'react-icons/bi'
  20. import styles from './index.less'
  21. import ListItem from './ListItem'
  22. interface Props {
  23. is_tray?: boolean;
  24. }
  25. const List = (props: Props) => {
  26. const { is_tray } = props
  27. const {
  28. hosts_data,
  29. loadHostsData,
  30. setList,
  31. current_hosts,
  32. setCurrentHosts,
  33. } = useModel('useHostsData')
  34. const { configs } = useModel('useConfigs')
  35. const { lang } = useModel('useI18n')
  36. const [selected_ids, setSelectedIds] = useState<string[]>([current_hosts?.id || '0'])
  37. const [show_list, setShowList] = useState<IHostsListObject[]>([])
  38. const toast = useToast()
  39. useEffect(() => {
  40. if (!is_tray) {
  41. setShowList([{
  42. id: '0',
  43. title: lang.system_hosts,
  44. is_sys: true,
  45. }, ...hosts_data.list])
  46. } else {
  47. setShowList([...hosts_data.list])
  48. }
  49. }, [hosts_data])
  50. const onToggleItem = async (id: string, on: boolean) => {
  51. console.log(`toggle hosts #${id} as ${on ? 'on' : 'off'}`)
  52. const new_list = setOnStateOfItem(hosts_data.list, id, on, configs?.choice_mode ?? 0)
  53. let success = await writeHostsToSystem(new_list)
  54. if (success) {
  55. toast({
  56. status: 'success',
  57. description: lang.success,
  58. isClosable: true,
  59. })
  60. agent.broadcast(events.set_hosts_on_status, id, on)
  61. } else {
  62. agent.broadcast(events.set_hosts_on_status, id, !on)
  63. }
  64. }
  65. const writeHostsToSystem = async (list?: IHostsListObject[], options?: IHostsWriteOptions): Promise<boolean> => {
  66. if (!Array.isArray(list)) {
  67. list = hosts_data.list
  68. }
  69. let content: string = await actions.getContentOfList(list)
  70. const result = await actions.setSystemHosts(content, options)
  71. if (result.success) {
  72. setList(list).catch(e => console.error(e))
  73. // new Notification(lang.success, {
  74. // body: lang.hosts_updated,
  75. // })
  76. if (current_hosts) {
  77. let hosts = findItemById(list, current_hosts.id)
  78. if (hosts) {
  79. agent.broadcast(events.set_hosts_on_status, current_hosts.id, hosts.on)
  80. }
  81. }
  82. } else {
  83. console.log(result)
  84. loadHostsData().catch(e => console.log(e))
  85. let err_desc = lang.fail
  86. // let body: string = lang.no_access_to_hosts
  87. if (result.code === 'no_access') {
  88. if (agent.platform === 'darwin' || agent.platform === 'linux') {
  89. agent.broadcast(events.show_sudo_password_input, list)
  90. }
  91. // } else {
  92. // body = result.message || 'Unknow error!'
  93. err_desc = lang.no_access_to_hosts
  94. }
  95. // new Notification(lang.fail, {
  96. // body,
  97. // })
  98. toast({
  99. status: 'error',
  100. description: err_desc,
  101. isClosable: true,
  102. })
  103. }
  104. agent.broadcast(events.tray_list_updated)
  105. return result.success
  106. }
  107. if (!is_tray) {
  108. useOnBroadcast(events.toggle_item, onToggleItem, [hosts_data])
  109. useOnBroadcast(events.write_hosts_to_system, writeHostsToSystem, [hosts_data])
  110. } else {
  111. useOnBroadcast(events.tray_list_updated, loadHostsData)
  112. }
  113. useOnBroadcast(events.move_to_trashcan, async (ids: string[]) => {
  114. console.log(`move_to_trashcan: #${ids}`)
  115. await actions.moveManyToTrashcan(ids)
  116. await loadHostsData()
  117. if (current_hosts && ids.includes(current_hosts.id)) {
  118. // 选中删除指定节点后的兄弟节点
  119. let next_item = getNextSelectedItem(hosts_data.list, i => ids.includes(i.id))
  120. setCurrentHosts(next_item || null)
  121. setSelectedIds(next_item ? [next_item.id] : [])
  122. }
  123. }, [current_hosts, hosts_data])
  124. useOnBroadcast(events.select_hosts, async (id: string, wait_ms: number = 0) => {
  125. let hosts = findItemById(hosts_data.list, id)
  126. if (!hosts) {
  127. if (wait_ms > 0) {
  128. setTimeout(() => {
  129. agent.broadcast(events.select_hosts, id, wait_ms - 50)
  130. }, 50)
  131. }
  132. return
  133. }
  134. setCurrentHosts(hosts)
  135. setSelectedIds([id])
  136. }, [hosts_data])
  137. useOnBroadcast(events.reload_list, loadHostsData)
  138. useOnBroadcast(events.hosts_content_changed, async (hosts_id: string) => {
  139. let list: IHostsListObject[] = await actions.getList()
  140. let hosts = findItemById(list, hosts_id)
  141. if (!hosts || !hosts.on) return
  142. // 当前 hosts 是开启状态,且内容发生了变化
  143. await writeHostsToSystem(list)
  144. })
  145. useOnBroadcast(events.show_source, async (params: IFindShowSourceParam) => {
  146. agent.broadcast(events.select_hosts, params.item_id)
  147. })
  148. return (
  149. <div className={styles.root}>
  150. {/*<SystemHostsItem/>*/}
  151. <Tree
  152. data={show_list}
  153. selected_ids={selected_ids}
  154. onChange={list => {
  155. setShowList(list)
  156. setList(list).catch(e => console.error(e))
  157. }}
  158. onSelect={(ids: string[]) => {
  159. console.log(ids)
  160. setSelectedIds(ids)
  161. }}
  162. nodeRender={(data) => (
  163. <ListItem key={data.id} data={data} is_tray={is_tray} selected_ids={selected_ids}/>
  164. )}
  165. collapseArrow={<Center w="20px" h="20px"><BiChevronRight/></Center>}
  166. nodeAttr={(item) => {
  167. return {
  168. can_drag: !item.is_sys && !is_tray,
  169. can_drop_before: !item.is_sys,
  170. can_drop_in: item.type === 'folder',
  171. can_drop_after: !item.is_sys,
  172. }
  173. }}
  174. draggingNodeRender={(data) => {
  175. return (
  176. <div className={clsx(styles.for_drag)}>
  177. <span className={clsx(styles.icon, data.type === 'folder' && styles.folder)}>
  178. <ItemIcon
  179. type={data.is_sys ? 'system' : data.type}
  180. is_collapsed={data.is_collapsed}
  181. />
  182. </span>
  183. <span>
  184. {data.title || lang.untitled}
  185. {selected_ids.length > 1 ? (
  186. <span className={styles.items_count}>{selected_ids.length} {lang.items}</span>
  187. ) : null}
  188. </span>
  189. </div>
  190. )
  191. }}
  192. nodeClassName={styles.node}
  193. nodeDropInClassName={styles.node_drop_in}
  194. nodeSelectedClassName={styles.node_selected}
  195. nodeCollapseArrowClassName={styles.arrow}
  196. allowed_multiple_selection={true}
  197. />
  198. </div>
  199. )
  200. }
  201. export default List