From 2b22c7e050ec1c3b5d30b5f6a658a58234167a39 Mon Sep 17 00:00:00 2001
From: qier222 <qier222@outlook.com>
Date: Tue, 27 Apr 2021 01:59:09 +0800
Subject: [PATCH] feat(electron): add proxy support

---
 src/electron/ipcMain.js       |  18 ++
 src/store/initLocalStorage.js |   5 +
 src/views/settings.vue        | 317 ++++++++++++++++++++++++----------
 3 files changed, 252 insertions(+), 88 deletions(-)

diff --git a/src/electron/ipcMain.js b/src/electron/ipcMain.js
index 60a778d..c8fa912 100644
--- a/src/electron/ipcMain.js
+++ b/src/electron/ipcMain.js
@@ -103,4 +103,22 @@ export function initIpcMain(win, store) {
       instance: true,
     });
   });
+
+  ipcMain.on('setProxy', (event, config) => {
+    console.log(config);
+    const proxyRules = `${config.protocol}://${config.server}:${config.port}`;
+    win.webContents.session.setProxy(
+      {
+        proxyRules,
+      },
+      () => {
+        console.log('finished setProxy');
+      }
+    );
+  });
+
+  ipcMain.on('removeProxy', (event, arg) => {
+    console.log('removeProxy');
+    win.webContents.session.setProxy({});
+  });
 }
diff --git a/src/store/initLocalStorage.js b/src/store/initLocalStorage.js
index 8d25f67..17a9ed1 100644
--- a/src/store/initLocalStorage.js
+++ b/src/store/initLocalStorage.js
@@ -25,6 +25,11 @@ let localStorage = {
     enableGlobalShortcut: true,
     showLibraryDefault: false,
     enabledPlaylistCategories,
+    proxyConfig: {
+      protocol: 'noProxy',
+      server: '',
+      port: null,
+    },
   },
   data: {
     user: {},
diff --git a/src/views/settings.vue b/src/views/settings.vue
index 1c49b02..0354685 100644
--- a/src/views/settings.vue
+++ b/src/views/settings.vue
@@ -25,7 +25,7 @@
           </button>
         </div>
       </div>
-      <h2>{{ $t('settings.settings') }}</h2>
+
       <div class="item">
         <div class="left">
           <div class="title"> {{ $t('settings.language') }} </div>
@@ -54,6 +54,8 @@
           </select>
         </div>
       </div>
+
+      <h3>音质</h3>
       <div class="item">
         <div class="left">
           <div class="title"> {{ $t('settings.musicQuality.text') }} </div>
@@ -92,6 +94,8 @@
           </select>
         </div>
       </div>
+
+      <h3>缓存</h3>
       <div v-if="isElectron" class="item">
         <div class="left">
           <div class="title">
@@ -143,6 +147,8 @@
           </button>
         </div>
       </div>
+
+      <h3>歌词</h3>
       <div class="item">
         <div class="left">
           <div class="title">{{ $t('settings.showLyricsTranslation') }}</div>
@@ -198,23 +204,8 @@
           </select>
         </div>
       </div>
-      <div v-if="isElectron && !isMac" class="item">
-        <div class="left">
-          <div class="title">{{ $t('settings.minimizeToTray') }}</div>
-        </div>
-        <div class="right">
-          <div class="toggle">
-            <input
-              id="minimize-to-tray"
-              v-model="minimizeToTray"
-              type="checkbox"
-              name="minimize-to-tray"
-            />
-            <label for="minimize-to-tray"></label>
-          </div>
-        </div>
-      </div>
 
+      <h3>第三方</h3>
       <div class="item">
         <div class="left">
           <div class="title">
@@ -232,24 +223,6 @@
           <button v-else @click="lastfmConnect()"> 授权连接 </button>
         </div>
       </div>
-
-      <div class="item">
-        <div class="left">
-          <div class="title"> {{ $t('settings.showLibraryDefault') }}</div>
-        </div>
-        <div class="right">
-          <div class="toggle">
-            <input
-              id="show-library-default"
-              v-model="showLibraryDefault"
-              type="checkbox"
-              name="show-library-default"
-            />
-            <label for="show-library-default"></label>
-          </div>
-        </div>
-      </div>
-
       <div class="item">
         <div class="left">
           <div class="title"
@@ -273,40 +246,75 @@
           </div>
         </div>
       </div>
-
-      <div class="item">
+      <div v-if="isElectron" class="item">
         <div class="left">
           <div class="title">
-            {{ $t('settings.showPlaylistsByAppleMusic') }}</div
+            {{ $t('settings.enableDiscordRichPresence') }}</div
           >
         </div>
         <div class="right">
           <div class="toggle">
             <input
-              id="show-playlists-by-apple-music"
-              v-model="showPlaylistsByAppleMusic"
+              id="enable-discord-rich-presence"
+              v-model="enableDiscordRichPresence"
               type="checkbox"
-              name="show-playlists-by-apple-music"
+              name="enable-discord-rich-presence"
             />
-            <label for="show-playlists-by-apple-music"></label>
+            <label for="enable-discord-rich-presence"></label>
           </div>
         </div>
       </div>
-      <div v-if="isElectron" class="item">
+
+      <h3>其他</h3>
+      <div v-if="isElectron && !isMac" class="item">
+        <div class="left">
+          <div class="title">{{ $t('settings.minimizeToTray') }}</div>
+        </div>
+        <div class="right">
+          <div class="toggle">
+            <input
+              id="minimize-to-tray"
+              v-model="minimizeToTray"
+              type="checkbox"
+              name="minimize-to-tray"
+            />
+            <label for="minimize-to-tray"></label>
+          </div>
+        </div>
+      </div>
+
+      <div class="item">
+        <div class="left">
+          <div class="title"> {{ $t('settings.showLibraryDefault') }}</div>
+        </div>
+        <div class="right">
+          <div class="toggle">
+            <input
+              id="show-library-default"
+              v-model="showLibraryDefault"
+              type="checkbox"
+              name="show-library-default"
+            />
+            <label for="show-library-default"></label>
+          </div>
+        </div>
+      </div>
+
+      <div class="item">
         <div class="left">
           <div class="title">
-            {{ $t('settings.enableDiscordRichPresence') }}</div
+            {{ $t('settings.showPlaylistsByAppleMusic') }}</div
           >
         </div>
         <div class="right">
           <div class="toggle">
             <input
-              id="enable-discord-rich-presence"
-              v-model="enableDiscordRichPresence"
+              id="show-playlists-by-apple-music"
+              v-model="showPlaylistsByAppleMusic"
               type="checkbox"
-              name="enable-discord-rich-presence"
+              name="show-playlists-by-apple-music"
             />
-            <label for="enable-discord-rich-presence"></label>
+            <label for="show-playlists-by-apple-music"></label>
           </div>
         </div>
       </div>
@@ -343,6 +351,40 @@
         </div>
       </div>
 
+      <div v-if="isElectron">
+        <h3>代理</h3>
+        <div class="item">
+          <div class="left">
+            <div class="title"> 代理协议 </div>
+          </div>
+          <div class="right">
+            <select v-model="proxyProtocol">
+              <option value="noProxy"> 关闭代理 </option>
+              <option value="HTTP"> HTTP 代理 </option>
+              <option value="HTTPS"> HTTPS 代理 </option>
+              <option value="SOCKS"> SOCKS 代理 </option>
+            </select>
+          </div>
+        </div>
+        <div id="proxy-form" :class="{ disabled: proxyProtocol === 'noProxy' }">
+          <input
+            v-model="proxyServer"
+            class="text-input"
+            placeholder="服务器地址"
+            :disabled="proxyProtocol === 'noProxy'"
+          /><input
+            v-model="proxyPort"
+            class="text-input"
+            placeholder="端口"
+            type="number"
+            min="1"
+            max="65535"
+            :disabled="proxyProtocol === 'noProxy'"
+          />
+          <button @click="sendProxyConfig">更新代理</button>
+        </div>
+      </div>
+
       <div class="footer">
         <p class="author"
           >MADE BY
@@ -362,6 +404,11 @@ import { changeAppearance, bytesToSize } from '@/utils/common';
 import { countDBSize, clearDB } from '@/utils/db';
 import pkg from '../../package.json';
 
+const electron =
+  process.env.IS_ELECTRON === true ? window.require('electron') : null;
+const ipcRenderer =
+  process.env.IS_ELECTRON === true ? electron.ipcRenderer : null;
+
 export default {
   name: 'Settings',
   data() {
@@ -438,16 +485,17 @@ export default {
     },
     outputDevice: {
       get() {
-        if (this.withoutAudioPrivilege === true) this.getAllOutputDevices();
-        const isValidDevice = this.allOutputDevices.find(
-          device => device.deviceId === this.settings.outputDevice
-        );
-        if (
-          this.settings.outputDevice === undefined ||
-          isValidDevice === undefined
-        )
-          return 'default'; // Default deviceId
-        return this.settings.outputDevice;
+        // if (this.withoutAudioPrivilege === true) this.getAllOutputDevices();
+        // const isValidDevice = this.allOutputDevices.find(
+        //   device => device.deviceId === this.settings.outputDevice
+        // );
+        // if (
+        //   this.settings.outputDevice === undefined ||
+        //   isValidDevice === undefined
+        // )
+        //   return 'default'; // Default deviceId
+        // return this.settings.outputDevice;
+        return 'default'; // Default deviceId
       },
       set(deviceId) {
         if (deviceId === this.settings.outputDevice || deviceId === undefined)
@@ -583,6 +631,48 @@ export default {
         });
       },
     },
+    proxyProtocol: {
+      get() {
+        return this.settings.proxyConfig?.protocol || 'noProxy';
+      },
+      set(value) {
+        let config = this.settings.proxyConfig || {};
+        config.protocol = value;
+        if (value === 'noProxy') {
+          ipcRenderer.send('removeProxy');
+        }
+        this.$store.commit('updateSettings', {
+          key: 'proxyConfig',
+          value: config,
+        });
+      },
+    },
+    proxyServer: {
+      get() {
+        return this.settings.proxyConfig?.server || '';
+      },
+      set(value) {
+        let config = this.settings.proxyConfig || {};
+        config.server = value;
+        this.$store.commit('updateSettings', {
+          key: 'proxyConfig',
+          value: config,
+        });
+      },
+    },
+    proxyPort: {
+      get() {
+        return this.settings.proxyConfig?.port || '';
+      },
+      set(value) {
+        let config = this.settings.proxyConfig || {};
+        config.port = value;
+        this.$store.commit('updateSettings', {
+          key: 'proxyConfig',
+          value: config,
+        });
+      },
+    },
     isLastfmConnected() {
       return this.lastfm.key !== undefined;
     },
@@ -650,6 +740,19 @@ export default {
       localStorage.removeItem('lastfm');
       this.$store.commit('updateLastfm', {});
     },
+    sendProxyConfig() {
+      if (this.proxyProtocol === 'noProxy') return;
+      const config = this.settings.proxyConfig;
+      if (
+        config.server === '' ||
+        !config.port ||
+        config.protocol === 'noProxy'
+      ) {
+        ipcRenderer.send('removeProxy');
+      } else {
+        ipcRenderer.send('setProxy', config);
+      }
+    },
   },
 };
 </script>
@@ -669,6 +772,14 @@ h2 {
   color: var(--color-text);
 }
 
+h3 {
+  margin-top: 48px;
+  padding-bottom: 12px;
+  font-size: 26px;
+  color: var(--color-text);
+  border-bottom: 1px solid rgba(128, 128, 128, 0.18);
+}
+
 .user {
   display: flex;
   align-items: center;
@@ -677,6 +788,7 @@ h2 {
   color: var(--color-text);
   padding: 16px 20px;
   border-radius: 16px;
+  margin-bottom: 48px;
   img.avatar {
     border-radius: 50%;
     height: 64px;
@@ -751,40 +863,69 @@ h2 {
   color: var(--color-text);
 
   .title {
-    font-size: 18px;
-    font-weight: 600;
-    opacity: 0.88;
+    font-size: 16px;
+    font-weight: 500;
+    opacity: 0.68;
   }
+}
 
-  select {
-    min-width: 192px;
-    font-weight: 600;
-    border: none;
-    padding: 8px 12px 8px 12px;
-    border-radius: 8px;
-    color: var(--color-text);
-    background: var(--color-secondary-bg);
-    appearance: none;
-    &:focus {
-      outline: none;
-      color: var(--color-primary);
-      background: var(--color-primary-bg);
-    }
+select {
+  min-width: 192px;
+  font-weight: 600;
+  border: none;
+  padding: 8px 12px 8px 12px;
+  border-radius: 8px;
+  color: var(--color-text);
+  background: var(--color-secondary-bg);
+  appearance: none;
+  &:focus {
+    outline: none;
+    color: var(--color-primary);
+    background: var(--color-primary-bg);
   }
+}
 
-  button {
-    color: var(--color-text);
-    background: var(--color-secondary-bg);
-    padding: 8px 12px 8px 12px;
-    font-weight: 600;
-    border-radius: 8px;
-    transition: 0.2s;
-    &:hover {
-      transform: scale(1.06);
-    }
-    &:active {
-      transform: scale(0.94);
-    }
+button {
+  color: var(--color-text);
+  background: var(--color-secondary-bg);
+  padding: 8px 12px 8px 12px;
+  font-weight: 600;
+  border-radius: 8px;
+  transition: 0.2s;
+  &:hover {
+    transform: scale(1.06);
+  }
+  &:active {
+    transform: scale(0.94);
+  }
+}
+
+input.text-input {
+  background: var(--color-secondary-bg);
+  border: none;
+  margin-right: 22px;
+  padding: 8px 12px 8px 12px;
+  border-radius: 8px;
+  color: var(--color-text);
+  font-weight: 600;
+  font-size: 16px;
+}
+input::-webkit-outer-spin-button,
+input::-webkit-inner-spin-button {
+  -webkit-appearance: none;
+}
+input[type='number'] {
+  -moz-appearance: textfield;
+}
+
+#proxy-form {
+  display: flex;
+  align-items: center;
+}
+#proxy-form.disabled {
+  opacity: 0.47;
+  button:hover {
+    transform: unset;
   }
 }