Browse Source

Merge branch 'feature/append-mode' into develop

oldj 3 years ago
parent
commit
6666bb4e59

+ 2 - 0
src/common/default_configs.ts

@@ -1,6 +1,7 @@
 import { LocaleName } from '@root/common/i18n'
 import { FolderModeType } from './data.d'
 
+export type WriteModeType = null | 'overwrite' | 'append'
 export type ThemeType = 'light' | 'dark' | 'system'
 export type ProtocolType = 'http' | 'https'
 export type DefaultLocaleType = LocaleName | undefined
@@ -11,6 +12,7 @@ const configs = {
   left_panel_width: 270,
 
   // preferences
+  write_mode: null as WriteModeType,
   history_limit: 50,
   locale: undefined as DefaultLocaleType,
   theme: 'light' as ThemeType,

+ 1 - 0
src/common/events.ts

@@ -23,6 +23,7 @@ export default {
   show_about: 'show_about',
   show_history: 'show_history',
   show_preferences: 'show_preferences',
+  show_set_write_mode: 'show_set_write_mode',
   show_source: 'show_source',
   show_sudo_password_input: 'show_sudo_password_input',
   system_hosts_updated: 'system_hosts_updated',

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

@@ -5,11 +5,13 @@
 
 export default {
   _app_name: 'SwitchHosts',
+  _key: 'en',
   _name: 'English',
   about: 'About',
   acknowledgement: 'Acknowledgement',
   advanced: 'Advanced',
   all: 'All',
+  append: 'Append',
   auto_refresh: 'Auto refresh',
   btn_cancel: 'Cancel',
   btn_ok: 'OK',
@@ -108,6 +110,7 @@ export default {
   next: 'Next',
   no_access_to_hosts: 'No permission to write to the Hosts file.',
   no_record: 'No record',
+  overwrite: 'Overwrite',
   password: 'Password',
   paste: 'Paste',
   port: 'Port',
@@ -175,6 +178,12 @@ export default {
   where_is_my_data: 'Where is my data stored?',
   where_is_my_hosts: 'Where is my hosts file?',
   window: 'Window',
+  write_mode: 'Write mode',
+  write_mode_append_help:
+    'Append the new records to the end of the system hosts file.',
+  write_mode_overwrite_help:
+    'Overwrite the system hosts file with the new records.',
+  write_mode_set: 'Set the write mode',
   your_data_is: 'Your data files are stored in:',
   your_hosts_file_is: 'Your hosts file is located at:',
   zoom: 'Zoom',

+ 9 - 0
src/common/i18n/languages/fr.ts

@@ -5,11 +5,13 @@
 
 export default {
   _app_name: 'SwitchHosts',
+  _key: 'fr',
   _name: 'Français',
   about: 'À propos',
   acknowledgement: 'Remerciements',
   advanced: 'Avancé',
   all: 'Tout',
+  append: 'Ajouter',
   auto_refresh: 'Rafraîchissement automatique',
   btn_cancel: 'Annuler',
   btn_ok: 'OK',
@@ -108,6 +110,7 @@ export default {
   next: 'Suivant',
   no_access_to_hosts: 'Aucune autorisation pour écrire dans le fichier hosts.',
   no_record: 'Aucun enregistrement',
+  overwrite: 'Écraser',
   password: 'Mot de passe',
   paste: 'Coller',
   port: 'Port',
@@ -176,6 +179,12 @@ export default {
   where_is_my_data: 'Où sont stockées mes données ?',
   where_is_my_hosts: 'Où est mon fichier hosts ?',
   window: 'Fenêtre',
+  write_mode: "Mode d'écriture",
+  write_mode_append_help:
+    "Ajoutez les nouveaux enregistrements à la fin du fichier d'hôtes système.",
+  write_mode_overwrite_help:
+    "Écrasez le fichier d'hôtes système avec les nouveaux enregistrements.",
+  write_mode_set: "Définir le mode d'écriture",
   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',

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

@@ -7,11 +7,13 @@ import { LanguageDict } from '@root/common/types'
 
 const lang: LanguageDict = {
   _app_name: 'SwitchHosts',
+  _key: 'zh',
   _name: '中文',
   about: '关于',
   acknowledgement: '特别致谢',
   advanced: '高级',
   all: '全部',
+  append: '追加',
   auto_refresh: '自动刷新',
   btn_cancel: '取消',
   btn_ok: '确定',
@@ -56,6 +58,7 @@ const lang: LanguageDict = {
   help: 'Help',
   hide: '隐藏',
   hide_at_launch: '启动时隐藏',
+  hide_dock_icon: '隐藏任务栏(Dock)图标',
   hide_history: '隐藏历史记录',
   hide_others: '隐藏其他',
   homepage: '主页',
@@ -92,7 +95,6 @@ const lang: LanguageDict = {
   migrate_confirm:
     'SwitchHosts v4.0 使用了新的数据存储格式,是否迁移旧数据为新格式?',
   migrate_data: '迁移数据',
-  hide_dock_icon: '隐藏任务栏(Dock)图标',
   minimize: '最小化',
   minute: '分钟',
   minutes: '分钟',
@@ -106,6 +108,7 @@ const lang: LanguageDict = {
   next: '下一个',
   no_access_to_hosts: '没有写入 Hosts 文件的权限。',
   no_record: '没有记录',
+  overwrite: '覆盖',
   password: '密码',
   paste: '粘贴',
   port: '端口',
@@ -171,6 +174,10 @@ const lang: LanguageDict = {
   where_is_my_data: '我的数据存储在哪里?',
   where_is_my_hosts: '我的 hosts 文件在哪里?',
   window: 'Window',
+  write_mode: '写入模式',
+  write_mode_append_help: '新记录将追加到现有系统 hosts 文件末尾。',
+  write_mode_overwrite_help: '新记录将覆盖现有系统 hosts 文件。',
+  write_mode_set: '设置写入模式',
   your_data_is: '你的数据在:',
   your_hosts_file_is: '你的 hosts 文件在:',
   zoom: '缩放',

+ 24 - 1
src/main/actions/hosts/setSystemHosts.ts

@@ -33,6 +33,8 @@ interface IWriteResult {
   new_content?: string
 }
 
+const CONTENT_START = '# --- SWITCHHOSTS_CONTENT_START ---'
+
 let sudo_pswd: string = ''
 
 const checkAccess = async (fn: string): Promise<boolean> => {
@@ -160,7 +162,7 @@ const write = async (
 
   try {
     await fs.promises.writeFile(sys_hosts_path, content, 'utf-8')
-  } catch (e) {
+  } catch (e: any) {
     console.error(e)
     let code = 'fail'
     if (e.code === 'EPERM' || e.message.include('operation not permitted')) {
@@ -177,10 +179,31 @@ const write = async (
   return { success: true, old_content, new_content: content }
 }
 
+const makeAppendContent = async (content: string): Promise<string> => {
+  const sys_hosts_path = await getPathOfSystemHosts()
+  const old_content = await fs.promises.readFile(sys_hosts_path, 'utf-8')
+
+  let index = old_content.indexOf(CONTENT_START)
+  let new_content =
+    index > -1 ? old_content.substring(0, index).trimEnd() : old_content
+
+  if (!content) {
+    return new_content + '\n'
+  }
+
+  return `${new_content}\n\n${CONTENT_START}\n\n${content}`
+}
+
 const setSystemHosts = async (
   content: string,
   options?: IHostsWriteOptions,
 ): Promise<IWriteResult> => {
+  let write_mode = await configGet('write_mode')
+  console.log(`write_mode: ${write_mode}`)
+  if (write_mode === 'append') {
+    content = await makeAppendContent(content)
+  }
+
   let result = await write(content, options)
   let { success, old_content } = result
 

+ 8 - 1
src/renderer/components/List/index.tsx

@@ -57,7 +57,14 @@ const List = (props: Props) => {
   }, [hosts_data])
 
   const onToggleItem = async (id: string, on: boolean) => {
+    console.log(`writeMode: ${configs?.write_mode}`)
     console.log(`toggle hosts #${id} as ${on ? 'on' : 'off'}`)
+
+    if (!configs?.write_mode) {
+      agent.broadcast(events.show_set_write_mode, { id, on })
+      return
+    }
+
     const new_list = setOnStateOfItem(
       hosts_data.list,
       id,
@@ -134,7 +141,7 @@ const List = (props: Props) => {
   }
 
   if (!is_tray) {
-    useOnBroadcast(events.toggle_item, onToggleItem, [hosts_data])
+    useOnBroadcast(events.toggle_item, onToggleItem, [hosts_data, configs])
     useOnBroadcast(events.write_hosts_to_system, writeHostsToSystem, [
       hosts_data,
     ])

+ 40 - 6
src/renderer/components/Pref/General.tsx

@@ -16,7 +16,7 @@ import {
   RadioGroup,
   Select,
   VStack,
-  Stack
+  Stack,
 } from '@chakra-ui/react'
 import { agent } from '@renderer/core/agent'
 import { http_api_port } from '@root/common/constants'
@@ -67,8 +67,38 @@ const General = (props: IProps) => {
         </HStack>
       </FormControl>
 
+      <FormControl>
+        <HStack alignItems={'flex-start'}>
+          <FormLabel w={label_width}>{lang.write_mode}</FormLabel>
+          <VStack align="left">
+            <RadioGroup
+              value={data.write_mode || ''}
+              onChange={(v) =>
+                onChange({
+                  write_mode: v as ConfigsType['write_mode'],
+                })
+              }
+            >
+              <HStack spacing={10}>
+                <Radio value="append">
+                  <Box>{lang.append}</Box>
+                </Radio>
+                <Radio value="overwrite">
+                  <Box>{lang.overwrite}</Box>
+                </Radio>
+              </HStack>
+            </RadioGroup>
+            <FormHelperText maxW={'350px'}>
+              {data.write_mode === 'append' && lang.write_mode_append_help}
+              {data.write_mode === 'overwrite' &&
+                lang.write_mode_overwrite_help}
+            </FormHelperText>
+          </VStack>
+        </HStack>
+      </FormControl>
+
       <FormControl pb={6}>
-        <HStack>
+        <HStack alignItems={'flex-start'}>
           <FormLabel w={label_width}>{lang.choice_mode}</FormLabel>
           <VStack align="left">
             <RadioGroup
@@ -90,7 +120,9 @@ const General = (props: IProps) => {
                 </Radio>
               </HStack>
             </RadioGroup>
-            <FormHelperText>{lang.choice_mode_desc}</FormHelperText>
+            <FormHelperText maxW={'350px'}>
+              {lang.choice_mode_desc}
+            </FormHelperText>
           </VStack>
         </HStack>
       </FormControl>
@@ -163,9 +195,11 @@ const General = (props: IProps) => {
           </FormHelperText>
           <Stack pl={6} mt={1} spacing={1}>
             <Checkbox
-                isDisabled={!data.http_api_on}
-                isChecked={data.http_api_only_local}
-                onChange={(e) => onChange({ http_api_only_local: e.target.checked })}
+              isDisabled={!data.http_api_on}
+              isChecked={data.http_api_only_local}
+              onChange={(e) =>
+                onChange({ http_api_only_local: e.target.checked })
+              }
             >
               {lang.http_api_only_local}
             </Checkbox>

+ 9 - 0
src/renderer/components/Pref/styles.less

@@ -4,3 +4,12 @@
   text-decoration: underline;
   color: inherit;
 }
+
+:global {
+  label {
+    span.chakra-radio__control {
+      width: 16px;
+      height: 16px;
+    }
+  }
+}

+ 8 - 0
src/renderer/components/SetWriteMode.less

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

+ 104 - 0
src/renderer/components/SetWriteMode.tsx

@@ -0,0 +1,104 @@
+/**
+ * @author: oldj
+ * @homepage: https://oldj.net
+ */
+
+import { useModel } from '@@/plugin-model/useModel'
+import {
+  Box,
+  Button,
+  HStack,
+  Modal,
+  ModalBody,
+  ModalCloseButton,
+  ModalContent,
+  ModalFooter,
+  ModalOverlay,
+  Radio,
+  RadioGroup,
+} from '@chakra-ui/react'
+import { agent } from '@renderer/core/agent'
+import useOnBroadcast from '@renderer/core/useOnBroadcast'
+import events from '@root/common/events'
+import React, { useState } from 'react'
+import styles from './SetWriteMode.less'
+import { WriteModeType } from '@root/common/default_configs'
+
+interface Props {}
+
+interface IPendingData {
+  id: string
+  on: boolean
+}
+
+const SetWriteMode = (props: Props) => {
+  const { updateConfigs } = useModel('useConfigs')
+  const { lang } = useModel('useI18n')
+  const [is_show, setIsShow] = useState(false)
+  const ipt_ref = React.useRef<HTMLInputElement>(null)
+  const [write_mode, setWriteMode] = useState<WriteModeType>(null)
+  const [pending_data, setPendingData] = useState<IPendingData | undefined>(
+    undefined,
+  )
+
+  const onCancel = () => {
+    setIsShow(false)
+  }
+
+  const onOk = async () => {
+    await updateConfigs({ write_mode })
+    setIsShow(false)
+
+    if (pending_data && pending_data.id) {
+      agent.broadcast(events.toggle_item, pending_data.id, pending_data.on)
+    }
+  }
+
+  useOnBroadcast(
+    events.show_set_write_mode,
+    (data?: IPendingData) => {
+      setIsShow(true)
+      setPendingData(data)
+      agent.broadcast(events.active_main_window)
+    },
+    [],
+  )
+
+  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.write_mode_set}</div>
+          <RadioGroup
+            value={write_mode || undefined}
+            onChange={(v) => setWriteMode(v as WriteModeType)}
+          >
+            <HStack spacing={10}>
+              <Radio value={'append'}>追加</Radio>
+              <Radio value={'overwrite'}>覆盖</Radio>
+            </HStack>
+          </RadioGroup>
+
+          <Box h={8} mt={4} opacity={0.5}>
+            {write_mode === 'append' && lang.write_mode_append_help}
+            {write_mode === 'overwrite' && lang.write_mode_overwrite_help}
+          </Box>
+        </ModalBody>
+        <ModalFooter>
+          <Button variant="outline" onClick={onCancel} mr={3}>
+            {lang.btn_cancel}
+          </Button>
+          <Button colorScheme="blue" onClick={onOk}>
+            {lang.btn_ok}
+          </Button>
+        </ModalFooter>
+      </ModalContent>
+    </Modal>
+  )
+}
+
+export default SetWriteMode

+ 2 - 0
src/renderer/pages/index.tsx

@@ -16,6 +16,7 @@ import clsx from 'clsx'
 import React, { useEffect, useState } from 'react'
 import TopBar from '../components/TopBar'
 import styles from './index.less'
+import SetWriteMode from '@renderer/components/SetWriteMode'
 
 export default () => {
   const [loading, setLoading] = useState(true)
@@ -135,6 +136,7 @@ export default () => {
 
       <EditHostsInfo />
       <SudoPasswordInput />
+      <SetWriteMode />
       <PreferencePanel />
       <History />
       <About />

+ 1 - 1
src/version.json

@@ -1 +1 @@
-[4, 0, 4, 6075]
+[4, 1, 0, 6076]