瀏覽代碼

supports importing data from url.

oldj 4 年之前
父節點
當前提交
4ce2dd9180

+ 2 - 0
src/common/i18n/languages/en.ts

@@ -65,6 +65,8 @@ export default {
   hours: 'hours',
   import: 'Import',
   import_done: 'The import is complete.',
+  import_fail: 'Import failed!',
+  import_from_url: 'Import from URL',
   is_latest_version_inform: 'Great, you are running the latest version!',
   language: 'Language',
   last_refresh: 'Last refresh: ',

+ 2 - 0
src/common/i18n/languages/zh.ts

@@ -67,6 +67,8 @@ const lang: LanguageDict = {
   hours: '小时',
   import: '导入',
   import_done: '导入已完成。',
+  import_fail: '导入失败!',
+  import_from_url: '从 URL 导入',
   is_latest_version_inform: '太棒了,你正在运行的是最新版本!',
   language: '语言',
   last_refresh: '最后刷新:',

+ 3 - 2
src/main/actions/index.ts

@@ -43,9 +43,10 @@ export { default as showItemInFolder } from './showItemInFolder'
 export { default as updateTrayTitle } from './updateTrayTitle'
 export { default as checkUpdate } from './checkUpdate'
 export { default as closeMainWindow } from './closeMainWindow'
-export { default as exportData } from './export'
-export { default as importData } from './import'
 export { default as quit } from './quit'
 
 export { default as migrateCheck } from './migrate/checkIfMigration'
 export { default as migrateData } from './migrate/migrateData'
+export { default as exportData } from './migrate/export'
+export { default as importData } from './migrate/import'
+export { default as importDataFromUrl } from './migrate/importFromUrl'

+ 0 - 0
src/main/actions/export.ts → src/main/actions/migrate/export.ts


+ 0 - 0
src/main/actions/import.ts → src/main/actions/migrate/import.ts


+ 66 - 0
src/main/actions/migrate/importFromUrl.ts

@@ -0,0 +1,66 @@
+/**
+ * importFromUrl
+ * @author: oldj
+ * @homepage: https://oldj.net
+ */
+
+import importV3Data from '@main/actions/migrate/importV3Data'
+import { swhdb } from '@main/data'
+import { GET } from '@main/libs/request'
+
+export default async (url: string): Promise<boolean | null | string> => {
+  console.log(`import from url: ${url}`)
+  let res
+  try {
+    res = await GET(url)
+  } catch (e) {
+    console.error(e)
+    return e.message
+  }
+
+  // console.log(res)
+  if (res.status !== 200) {
+    return `error_${res.status}`
+  }
+
+  let data: any
+  if (typeof res.data === 'string') {
+    try {
+      data = JSON.parse(res.data)
+    } catch (e) {
+      console.error(e)
+      return 'parse_error'
+    }
+  } else {
+    data = res.data
+  }
+
+  if (typeof data !== 'object' || !data.version || !Array.isArray(data.version)) {
+    return 'invalid_data'
+  }
+
+  let { version } = data
+  if (version[0] === 3) {
+    // import v3 data
+    try {
+      await importV3Data(data)
+    } catch (e) {
+      console.error(e)
+      return 'invalid_v3_data'
+    }
+
+    return true
+  }
+
+  if (version[0] > 4) {
+    return 'new_version'
+  }
+
+  if (!data.data || typeof data.data !== 'object') {
+    return 'invalid_data_key'
+  }
+
+  await swhdb.loadJSON(data.data)
+
+  return true
+}

+ 126 - 113
src/renderer/components/TopBar/ConfigMenu.tsx

@@ -6,19 +6,20 @@
 
 import { useModel } from '@@/plugin-model/useModel'
 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'
-import React from 'react'
+import React, { useState } from 'react'
 import {
   BiCog,
   BiExit,
+  BiExport,
   BiHomeCircle,
+  BiImport,
   BiInfoCircle,
   BiMessageDetail,
   BiRefresh,
   BiSliderAlt,
-  BiExport,
-  BiImport,
 } from 'react-icons/bi'
 
 interface Props {
@@ -28,128 +29,140 @@ interface Props {
 const ConfigMenu = (props: Props) => {
   const { lang } = useModel('useI18n')
   const { loadHostsData, setCurrentHosts } = useModel('useHostsData')
+  const [show_import_from_url, setShowImportFromUrl] = useState(false)
   const toast = useToast()
 
   return (
-    <Menu>
-      <MenuButton
-        as={Button}
-        variant="ghost"
-        width="35px"
-      >
-        <BiCog/>
-      </MenuButton>
-      <MenuList borderColor="var(--swh-border-color-0)">
-        <MenuItem
-          icon={<BiInfoCircle/>}
-          onClick={() => agent.broadcast('show_about')}
+    <>
+      <Menu>
+        <MenuButton
+          as={Button}
+          variant="ghost"
+          width="35px"
         >
-          {lang.about}
-        </MenuItem>
+          <BiCog/>
+        </MenuButton>
+        <MenuList borderColor="var(--swh-border-color-0)">
+          <MenuItem
+            icon={<BiInfoCircle/>}
+            onClick={() => agent.broadcast('show_about')}
+          >
+            {lang.about}
+          </MenuItem>
 
-        <MenuDivider/>
+          <MenuDivider/>
 
-        <MenuItem
-          icon={<BiRefresh/>}
-          onClick={async () => {
-            let r = await actions.checkUpdate()
-            if (r === false) {
-              toast({
-                description: lang.is_latest_version_inform,
-                status: 'info',
-                duration: 3000,
-                isClosable: true,
-              })
-            }
-          }}
-        >
-          {lang.check_update}
-        </MenuItem>
-        <MenuItem
-          icon={<BiMessageDetail/>}
-          onClick={() => actions.openUrl(feedback_url)}
-        >
-          {lang.feedback}
-        </MenuItem>
-        <MenuItem
-          icon={<BiHomeCircle/>}
-          onClick={() => actions.openUrl(homepage_url)}
-        >
-          {lang.homepage}
-        </MenuItem>
+          <MenuItem
+            icon={<BiRefresh/>}
+            onClick={async () => {
+              let r = await actions.checkUpdate()
+              if (r === false) {
+                toast({
+                  description: lang.is_latest_version_inform,
+                  status: 'info',
+                  duration: 3000,
+                  isClosable: true,
+                })
+              }
+            }}
+          >
+            {lang.check_update}
+          </MenuItem>
+          <MenuItem
+            icon={<BiMessageDetail/>}
+            onClick={() => actions.openUrl(feedback_url)}
+          >
+            {lang.feedback}
+          </MenuItem>
+          <MenuItem
+            icon={<BiHomeCircle/>}
+            onClick={() => actions.openUrl(homepage_url)}
+          >
+            {lang.homepage}
+          </MenuItem>
 
-        <MenuDivider/>
+          <MenuDivider/>
 
-        <MenuItem
-          icon={<BiExport/>}
-          onClick={async () => {
-            let r = await actions.exportData()
-            if (r === null) {
-              return
-            } else if (r === false) {
-              toast({
-                status: 'error',
-                description: lang.fail,
-                isClosable: true,
-              })
-            } else {
-              toast({
-                status: 'success',
-                description: lang.export_done,
-                isClosable: true,
-              })
-            }
-          }}
-        >
-          {lang.export}
-        </MenuItem>
-        <MenuItem
-          icon={<BiImport/>}
-          onClick={async () => {
-            let r = await actions.importData()
-            if (r === null) {
-              return
-            } else if (r === true) {
-              toast({
-                status: 'success',
-                description: lang.import_done,
-                isClosable: true,
-              })
-              await loadHostsData()
-              setCurrentHosts(null)
-            } else {
-              let description = lang.fail
-              if (typeof r === 'string') {
-                description += ` [${r}]`
+          <MenuItem
+            icon={<BiExport/>}
+            onClick={async () => {
+              let r = await actions.exportData()
+              if (r === null) {
+                return
+              } else if (r === false) {
+                toast({
+                  status: 'error',
+                  description: lang.import_fail,
+                  isClosable: true,
+                })
+              } else {
+                toast({
+                  status: 'success',
+                  description: lang.export_done,
+                  isClosable: true,
+                })
               }
+            }}
+          >
+            {lang.export}
+          </MenuItem>
+          <MenuItem
+            icon={<BiImport/>}
+            onClick={async () => {
+              let r = await actions.importData()
+              if (r === null) {
+                return
+              } else if (r === true) {
+                toast({
+                  status: 'success',
+                  description: lang.import_done,
+                  isClosable: true,
+                })
+                await loadHostsData()
+                setCurrentHosts(null)
+              } else {
+                let description = lang.import_fail
+                if (typeof r === 'string') {
+                  description += ` [${r}]`
+                }
 
-              toast({
-                status: 'error',
-                description,
-                isClosable: true,
-              })
-            }
-          }}
-        >
-          {lang.import}
-        </MenuItem>
+                toast({
+                  status: 'error',
+                  description,
+                  isClosable: true,
+                })
+              }
+            }}
+          >
+            {lang.import}
+          </MenuItem>
+          <MenuItem
+            icon={<BiImport/>}
+            onClick={async () => {
+              setShowImportFromUrl(true)
+            }}
+          >
+            {lang.import_from_url}
+          </MenuItem>
 
-        <MenuDivider/>
+          <MenuDivider/>
 
-        <MenuItem
-          icon={<BiSliderAlt/>}
-          onClick={() => agent.broadcast('show_preferences')}
-        >
-          {lang.preferences}
-        </MenuItem>
-        <MenuItem
-          icon={<BiExit/>}
-          onClick={() => actions.quit()}
-        >
-          {lang.quit}
-        </MenuItem>
-      </MenuList>
-    </Menu>
+          <MenuItem
+            icon={<BiSliderAlt/>}
+            onClick={() => agent.broadcast('show_preferences')}
+          >
+            {lang.preferences}
+          </MenuItem>
+          <MenuItem
+            icon={<BiExit/>}
+            onClick={() => actions.quit()}
+          >
+            {lang.quit}
+          </MenuItem>
+        </MenuList>
+      </Menu>
+      <ImportFromUrl is_show={show_import_from_url} setIsShow={setShowImportFromUrl}/>
+    </>
   )
 }
 

+ 8 - 0
src/renderer/components/TopBar/ImportFromUrl.less

@@ -0,0 +1,8 @@
+@import "../../styles/common";
+
+.root {
+}
+
+.label {
+  margin: 10px 0 20px 0;
+}

+ 128 - 0
src/renderer/components/TopBar/ImportFromUrl.tsx

@@ -0,0 +1,128 @@
+/**
+ * SudoPasswordInput
+ * @author: oldj
+ * @homepage: https://oldj.net
+ */
+
+import { useModel } from '@@/plugin-model/useModel'
+import {
+  Button,
+  Input,
+  Modal,
+  ModalBody,
+  ModalCloseButton,
+  ModalContent,
+  ModalFooter,
+  ModalOverlay,
+  ToastId,
+  useToast,
+} from '@chakra-ui/react'
+import { actions } from '@renderer/core/agent'
+import React, { useRef, useState } from 'react'
+import styles from './ImportFromUrl.less'
+
+interface Props {
+  is_show: boolean;
+  setIsShow: (show: boolean) => void;
+}
+
+const ImportFromUrl = (props: Props) => {
+  const { is_show, setIsShow } = props
+  const { lang } = useModel('useI18n')
+  const { loadHostsData, setCurrentHosts } = useModel('useHostsData')
+  const [url, setUrl] = useState('')
+  const ipt_ref = React.useRef<HTMLInputElement>(null)
+  const toast = useToast()
+  const toast_ref = useRef<ToastId>()
+
+  const onCancel = () => {
+    setIsShow(false)
+    setUrl('')
+  }
+
+  const onOk = async () => {
+    setIsShow(false)
+    console.log(`url: ${url}`)
+    toast_ref.current = toast({
+      description: 'loading...',
+      duration: null,
+      isClosable: true,
+    })
+
+    let t0 = (new Date()).getTime()
+
+    if (url) {
+      let r = await actions.importDataFromUrl(url)
+      console.log(r)
+
+      if (r === true) {
+        // import success
+        toast({
+          status: 'success',
+          description: lang.import_done,
+          isClosable: true,
+        })
+        await loadHostsData()
+        setCurrentHosts(null)
+
+      } else {
+        let description = lang.import_fail
+        if (typeof r === 'string') {
+          description += ` [${r}]`
+        }
+
+        toast({
+          status: 'error',
+          description,
+          isClosable: true,
+        })
+      }
+    }
+
+    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/>
+      <ModalContent>
+        <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)}
+            autoFocus={true}
+            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
+            colorScheme="blue"
+            onClick={onOk}
+            isDisabled={!url || !url.match(/^https?:\/\/\w+/i)}
+          >{lang.btn_ok}</Button>
+        </ModalFooter>
+      </ModalContent>
+    </Modal>
+  )
+}
+
+export default ImportFromUrl