From c21c08c370ea60ee3e4b555ec950b7431ecf84a6 Mon Sep 17 00:00:00 2001 From: muwoo <2424880409@qq.com> Date: Fri, 15 Sep 2023 16:17:52 +0800 Subject: [PATCH] =?UTF-8?q?:sparkles:=20=E6=94=AF=E6=8C=81=E6=9C=AC?= =?UTF-8?q?=E5=9C=B0=E5=90=AF=E5=8A=A8=EF=BC=8C=E4=BF=AE=E6=94=B9mac=20?= =?UTF-8?q?=E4=B8=8B=E8=8E=B7=E5=8F=96=20APP=20icon=20=E7=9A=84=E6=96=B9?= =?UTF-8?q?=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- feature/public/package.json | 10 +- feature/public/preload.js | 23 ++++ feature/src/languages/langs/en-US.ts | 3 + feature/src/languages/langs/zh-CN.ts | 3 + feature/src/views/settings/index.vue | 9 ++ feature/src/views/settings/local-start.vue | 71 ++++++++++++ package.json | 1 + public/feature/package.json | 10 +- public/feature/preload.js | 23 ++++ src/common/utils/getWindowHeight.ts | 12 +- src/core/app-search/darwin.ts | 91 ++++++--------- src/core/app-search/get-mac-app/app2png.ts | 109 ++++++++++++++++++ src/core/app-search/get-mac-app/index.ts | 4 +- src/main/common/api.ts | 16 +++ src/renderer/App.vue | 35 ++++-- src/renderer/assets/var.less | 2 +- src/renderer/components/result.vue | 57 +++++++-- src/renderer/components/search.vue | 4 +- src/renderer/main.ts | 4 + .../plugins-manager/clipboardWatch.ts | 18 ++- src/renderer/plugins-manager/index.ts | 44 ++++++- src/renderer/plugins-manager/options.ts | 55 +++++---- .../plugins-manager/pluginClickEvent.ts | 11 +- src/renderer/shims-vue.d.ts | 2 + 24 files changed, 493 insertions(+), 124 deletions(-) create mode 100644 feature/src/views/settings/local-start.vue create mode 100644 src/core/app-search/get-mac-app/app2png.ts diff --git a/feature/public/package.json b/feature/public/package.json index dfd9c7c..70f91a8 100644 --- a/feature/public/package.json +++ b/feature/public/package.json @@ -1,7 +1,7 @@ { "name": "rubick-system-feature", - "pluginName": "rubick 系统菜单", - "description": "rubick 系统菜单", + "pluginName": "系统菜单", + "description": "系统菜单", "main": "index.html", "logo": "https://pic1.zhimg.com/80/v2-c09780808301668a82e6646cb42f0806_720w.png", "version": "0.0.0", @@ -10,19 +10,19 @@ "features": [ { "code": "market", - "explain": "rubick 插件市场", + "explain": "插件市场", "cmds":[ "插件市场" ] },{ "code": "installed", - "explain": "rubick 已安装插件", + "explain": "已安装插件", "cmds":[ "已安装插件" ] },{ "code": "settings", - "explain": "rubick 偏好设置", + "explain": "偏好设置", "cmds":[ "偏好设置" ] diff --git a/feature/public/preload.js b/feature/public/preload.js index 896da73..33e0b2b 100644 --- a/feature/public/preload.js +++ b/feature/public/preload.js @@ -1,4 +1,21 @@ const remote = require('@electron/remote'); +const { ipcRenderer } = require('electron'); + +const ipcSendSync = (type, data) => { + const returnValue = ipcRenderer.sendSync('msg-trigger', { + type, + data, + }); + if (returnValue instanceof Error) throw returnValue; + return returnValue; +}; + +const ipcSend = (type, data) => { + ipcRenderer.send('msg-trigger', { + type, + data, + }); +}; window.market = { getLocalPlugins() { @@ -13,4 +30,10 @@ window.market = { refreshPlugin(plugin) { return remote.getGlobal('LOCAL_PLUGINS').refreshPlugin(plugin); }, + addLocalStartPlugin(plugin) { + ipcSend('addLocalStartPlugin', { plugin }); + }, + removeLocalStartPlugin(plugin) { + ipcSend('removeLocalStartPlugin', { plugin }); + }, }; diff --git a/feature/src/languages/langs/en-US.ts b/feature/src/languages/langs/en-US.ts index b897399..9e8c239 100644 --- a/feature/src/languages/langs/en-US.ts +++ b/feature/src/languages/langs/en-US.ts @@ -96,6 +96,9 @@ export default { accessToken: 'access token', placeholder: 'required for private network gitlab warehouse', }, + localstart: { + title: 'Local Start', + }, }, dev: { title: 'Developer', diff --git a/feature/src/languages/langs/zh-CN.ts b/feature/src/languages/langs/zh-CN.ts index 82c238e..374b54e 100644 --- a/feature/src/languages/langs/zh-CN.ts +++ b/feature/src/languages/langs/zh-CN.ts @@ -94,6 +94,9 @@ export default { accessToken: 'access token', placeholder: '内网gitlab仓库必填', }, + localstart: { + title: '本地启动', + }, }, dev: { title: '开发者', diff --git a/feature/src/views/settings/index.vue b/feature/src/views/settings/index.vue index 5e02ca4..73beddd 100644 --- a/feature/src/views/settings/index.vue +++ b/feature/src/views/settings/index.vue @@ -14,6 +14,12 @@ {{ $t('feature.settings.basic.title') }} + + + + + {{ $t('feature.settings.localstart.title') }} + @@ -220,6 +226,7 @@ + @@ -232,6 +239,7 @@ import { MinusCircleOutlined, PlusCircleOutlined, UserOutlined, + FolderOpenOutlined, } from '@ant-design/icons-vue'; import debounce from 'lodash.debounce'; import { ref, reactive, watch, toRefs, computed } from 'vue'; @@ -239,6 +247,7 @@ import keycodes from './keycode'; import Localhost from './localhost.vue'; import SuperPanel from './super-panel.vue'; import UserInfo from './user-info'; +import LocalStart from './local-start'; import { useI18n } from 'vue-i18n'; import localConfig from '@/confOp'; diff --git a/feature/src/views/settings/local-start.vue b/feature/src/views/settings/local-start.vue new file mode 100644 index 0000000..382d584 --- /dev/null +++ b/feature/src/views/settings/local-start.vue @@ -0,0 +1,71 @@ + + + + + + + + remove(item)">移除 + + + + {{item.name}} + + + + + + + + + + + + + + \ No newline at end of file diff --git a/package.json b/package.json index 2879025..34cf924 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,7 @@ "lodash.throttle": "^4.1.1", "node-key-sender": "^1.0.11", "pouchdb": "^7.2.2", + "simple-plist": "0.2.1", "vue": "^3.0.0", "vue-router": "^4.0.0-0", "vuex": "^4.0.0-0", diff --git a/public/feature/package.json b/public/feature/package.json index dfd9c7c..70f91a8 100644 --- a/public/feature/package.json +++ b/public/feature/package.json @@ -1,7 +1,7 @@ { "name": "rubick-system-feature", - "pluginName": "rubick 系统菜单", - "description": "rubick 系统菜单", + "pluginName": "系统菜单", + "description": "系统菜单", "main": "index.html", "logo": "https://pic1.zhimg.com/80/v2-c09780808301668a82e6646cb42f0806_720w.png", "version": "0.0.0", @@ -10,19 +10,19 @@ "features": [ { "code": "market", - "explain": "rubick 插件市场", + "explain": "插件市场", "cmds":[ "插件市场" ] },{ "code": "installed", - "explain": "rubick 已安装插件", + "explain": "已安装插件", "cmds":[ "已安装插件" ] },{ "code": "settings", - "explain": "rubick 偏好设置", + "explain": "偏好设置", "cmds":[ "偏好设置" ] diff --git a/public/feature/preload.js b/public/feature/preload.js index 896da73..33e0b2b 100644 --- a/public/feature/preload.js +++ b/public/feature/preload.js @@ -1,4 +1,21 @@ const remote = require('@electron/remote'); +const { ipcRenderer } = require('electron'); + +const ipcSendSync = (type, data) => { + const returnValue = ipcRenderer.sendSync('msg-trigger', { + type, + data, + }); + if (returnValue instanceof Error) throw returnValue; + return returnValue; +}; + +const ipcSend = (type, data) => { + ipcRenderer.send('msg-trigger', { + type, + data, + }); +}; window.market = { getLocalPlugins() { @@ -13,4 +30,10 @@ window.market = { refreshPlugin(plugin) { return remote.getGlobal('LOCAL_PLUGINS').refreshPlugin(plugin); }, + addLocalStartPlugin(plugin) { + ipcSend('addLocalStartPlugin', { plugin }); + }, + removeLocalStartPlugin(plugin) { + ipcSend('removeLocalStartPlugin', { plugin }); + }, }; diff --git a/src/common/utils/getWindowHeight.ts b/src/common/utils/getWindowHeight.ts index d701175..e0a5ca3 100644 --- a/src/common/utils/getWindowHeight.ts +++ b/src/common/utils/getWindowHeight.ts @@ -1,12 +1,14 @@ const WINDOW_MAX_HEIGHT = 600; const WINDOW_MIN_HEIGHT = 60; const PRE_ITEM_HEIGHT = 60; +const HISTORY_HEIGHT = 80; -export default (searchList: Array): number => { - if (!searchList) return WINDOW_MAX_HEIGHT; - if (!searchList.length) return WINDOW_MIN_HEIGHT; - return searchList.length * PRE_ITEM_HEIGHT + WINDOW_MIN_HEIGHT + 5 > +export default (searchList: Array, historyList): number => { + const defaultHeight = historyList.length ? HISTORY_HEIGHT : 0; + if (!searchList) return WINDOW_MAX_HEIGHT + defaultHeight; + if (!searchList.length) return WINDOW_MIN_HEIGHT + defaultHeight; + return searchList.length * PRE_ITEM_HEIGHT + WINDOW_MIN_HEIGHT > WINDOW_MAX_HEIGHT ? WINDOW_MAX_HEIGHT - : searchList.length * PRE_ITEM_HEIGHT + WINDOW_MIN_HEIGHT + 5; + : searchList.length * PRE_ITEM_HEIGHT + WINDOW_MIN_HEIGHT; }; diff --git a/src/core/app-search/darwin.ts b/src/core/app-search/darwin.ts index 60b89ac..3c4352e 100644 --- a/src/core/app-search/darwin.ts +++ b/src/core/app-search/darwin.ts @@ -16,16 +16,7 @@ if (!exists) { const isZhRegex = /[\u4e00-\u9fa5]/; -async function getAppIcon( - appPath: string, - nativeImage: { - createThumbnailFromPath: ( - iconPath: string, - size: { width: number; height: number } - ) => { toDataURL: () => string }; - }, - name: string -) { +async function getAppIcon(appPath: string, nativeImage: any, name: string) { try { const iconpath = path.join(icondir, `${name}.png`); const iconnone = path.join(icondir, `${name}.none`); @@ -33,52 +24,40 @@ async function getAppIcon( const existsnone = fs.existsSync(iconnone); if (exists) return true; if (existsnone) return false; - const appName: string = appPath.split('/').pop() || ''; - const extname: string = path.extname(appName); - const appSubStr: string = appName.split(extname)[0]; - const path1 = path.join(appPath, `/Contents/Resources/App.icns`); - const path2 = path.join(appPath, `/Contents/Resources/AppIcon.icns`); - const path3 = path.join(appPath, `/Contents/Resources/${appSubStr}.icns`); - const path4 = path.join( - appPath, - `/Contents/Resources/${appSubStr.replace(' ', '')}.icns` - ); - let iconPath: string = path1; - if (fs.existsSync(path1)) { - iconPath = path1; - } else if (fs.existsSync(path2)) { - iconPath = path2; - } else if (fs.existsSync(path3)) { - iconPath = path3; - } else if (fs.existsSync(path4)) { - iconPath = path4; - } else { - // 性能最低的方式 - const resourceList = fs.readdirSync( - path.join(appPath, `/Contents/Resources`) - ); - const iconName = resourceList.filter( - (file) => path.extname(file) === '.icns' - )[0]; - if (!iconName) { - fs.writeFileSync(iconnone, ''); - return false; - } - iconPath = path.join(appPath, `/Contents/Resources/${iconName}`); - } - const img = await nativeImage.createThumbnailFromPath(iconPath, { - width: 64, - height: 64, - }); - - const base64Data = img.toDataURL().replace(/^data:.+;base64,/, '"'); - - const result = Buffer.from(base64Data, 'base64'); - - fs.writeFile(iconpath, result, 'base64', () => { - // todo - }); - + // const appName: string = appPath.split('/').pop() || ''; + // const extname: string = path.extname(appName); + // const appSubStr: string = appName.split(extname)[0]; + // const path1 = path.join(appPath, `/Contents/Resources/App.icns`); + // const path2 = path.join(appPath, `/Contents/Resources/AppIcon.icns`); + // const path3 = path.join(appPath, `/Contents/Resources/${appSubStr}.icns`); + // const path4 = path.join( + // appPath, + // `/Contents/Resources/${appSubStr.replace(' ', '')}.icns` + // ); + // let iconPath: string = path1; + // if (fs.existsSync(path1)) { + // iconPath = path1; + // } else if (fs.existsSync(path2)) { + // iconPath = path2; + // } else if (fs.existsSync(path3)) { + // iconPath = path3; + // } else if (fs.existsSync(path4)) { + // iconPath = path4; + // } else { + // // 性能最低的方式 + // const resourceList = fs.readdirSync( + // path.join(appPath, `/Contents/Resources`) + // ); + // const iconName = resourceList.filter( + // (file) => path.extname(file) === '.icns' + // )[0]; + // if (!iconName) { + // fs.writeFileSync(iconnone, ''); + // return false; + // } + // iconPath = path.join(appPath, `/Contents/Resources/${iconName}`); + // } + await getMacApps.app2png(appPath, iconpath); return true; } catch (e) { return false; diff --git a/src/core/app-search/get-mac-app/app2png.ts b/src/core/app-search/get-mac-app/app2png.ts new file mode 100644 index 0000000..6211715 --- /dev/null +++ b/src/core/app-search/get-mac-app/app2png.ts @@ -0,0 +1,109 @@ +import path from 'path'; +import fs from 'fs'; +import { exec } from 'child_process'; + +// eslint-disable-next-line @typescript-eslint/no-var-requires +const plist = require('simple-plist'); + +const getIconFile = (appFileInput) => { + return new Promise((resolve, reject) => { + const plistPath = path.join(appFileInput, 'Contents', 'Info.plist'); + plist.readFile(plistPath, (err, data) => { + if (err || !data.CFBundleIconFile) { + return resolve( + '/System/Library/CoreServices/CoreTypes.bundle/Contents/Resources/GenericApplicationIcon.icns' + ); + } + const iconFile = path.join( + appFileInput, + 'Contents', + 'Resources', + data.CFBundleIconFile + ); + const iconFiles = [iconFile, iconFile + '.icns', iconFile + '.tiff']; + const existedIcon = iconFiles.find((iconFile) => { + return fs.existsSync(iconFile); + }); + resolve( + existedIcon || + '/System/Library/CoreServices/CoreTypes.bundle/Contents/Resources/GenericApplicationIcon.icns' + ); + }); + }); +}; + +// const sortIcons = (icons) => { +// const aWins = -1; +// const bWins = 1; +// const catWins = 0; +// return icons.sort((a, b) => { +// const aSize = parseInt(a.match(/(\d+)x\1/)[1], 10); +// const bSize = parseInt(b.match(/(\d+)x\1/)[1], 10); +// if (aSize === bSize) { +// if (a.indexOf('@2x') > -1) return aWins; +// if (b.indexOf('@2x') > -1) return bWins; +// return catWins; +// } +// if (aSize > bSize) return aWins; +// if (aSize < bSize) return bWins; +// return catWins; +// }); +// }; + +// const icnsToPng = (iconFile, pngFileOutput) => { +// const outputDir = pngFileOutput.split('.')[0] + '.iconset' +// return new Promise((resolve, reject) => { +// exec(`iconutil --convert iconset '${iconFile}' --output '${outputDir}'`, (error) => { +// if (error) return reject(error) +// fs.readdir(outputDir, (error, files) => { +// if (error) { +// return resolve(tiffToPng(iconFile, pngFileOutput)) +// } +// const realIcons = files.map((file) => { +// return path.join(outputDir, file) +// }) +// const biggestIcon = sortIcons(realIcons).find(Boolean) +// fs.rename(biggestIcon, pngFileOutput, (error) => { +// error ? reject(error) : resolve(realIcons.filter((file) => { +// return file !== biggestIcon +// })) +// }) +// }) +// }) +// }).then((files) => { +// // Cleanup temp icons +// return Promise.all(files.map((file) => { +// return new Promise((resolve, reject) => { +// fs.unlink(file, (error) => { +// error ? reject(error) : resolve() +// }) +// }) +// })) +// }).then(() => { +// // Cleanup temp directory +// return new Promise((resolve, reject) => { +// fs.rmdir(outputDir, (error) => { +// error ? reject(error) : resolve() +// }) +// }) +// }) +// } + +const tiffToPng = (iconFile, pngFileOutput) => { + return new Promise((resolve, reject) => { + exec( + `sips -s format png '${iconFile}' --out '${pngFileOutput}' --resampleHeightWidth 64 64`, + (error) => { + error ? reject(error) : resolve(null); + } + ); + }); +}; + +const app2png = (appFileInput, pngFileOutput) => { + return getIconFile(appFileInput).then((iconFile) => { + return tiffToPng(iconFile, pngFileOutput); + }); +}; + +export default app2png; diff --git a/src/core/app-search/get-mac-app/index.ts b/src/core/app-search/get-mac-app/index.ts index d6afc01..8015622 100644 --- a/src/core/app-search/get-mac-app/index.ts +++ b/src/core/app-search/get-mac-app/index.ts @@ -1,4 +1,5 @@ -import getApps from "./getApps"; +import getApps from './getApps'; +import app2png from './app2png'; export default { getApps: () => { @@ -7,4 +8,5 @@ export default { isInstalled: (appName) => { return new Promise((resolve, reject) => getApps(resolve, reject, appName)); }, + app2png, }; diff --git a/src/main/common/api.ts b/src/main/common/api.ts index 3266338..6b49e84 100644 --- a/src/main/common/api.ts +++ b/src/main/common/api.ts @@ -319,6 +319,22 @@ class API extends DBInstance { ks.sendKeys(keys); } } + + public addLocalStartPlugin({ data: { plugin } }, window) { + window.webContents.executeJavaScript( + `window.addLocalStartPlugin(${JSON.stringify({ + plugin, + })})` + ); + } + + public removeLocalStartPlugin({ data: { plugin } }, window) { + window.webContents.executeJavaScript( + `window.removeLocalStartPlugin(${JSON.stringify({ + plugin, + })})` + ); + } } export default new API(); diff --git a/src/renderer/App.vue b/src/renderer/App.vue index 5d72f51..c7c5110 100644 --- a/src/renderer/App.vue +++ b/src/renderer/App.vue @@ -12,6 +12,7 @@ :searchValue="searchValue" :placeholder="placeholder" :pluginLoading="pluginLoading" + :pluginHistory="pluginHistory" :clipboardFile="clipboardFile || []" @choosePlugin="choosePlugin" @focus="searchFocus" @@ -20,6 +21,7 @@ @readClipboardContent="readClipboardContent" /> { +watch([options, pluginHistory], () => { currentSelect.value = 0; if (currentPlugin.value.name) return; nextTick(() => { ipcRenderer.sendSync('msg-trigger', { type: 'setExpendHeight', - data: getWindowHeight(options.value), + data: getWindowHeight(options.value, pluginHistory.value), }); }); }); const changeIndex = (index) => { - if (!options.value.length) return; + if (!options.value.length) { + if (!pluginHistory.value.length) return; + if ( + currentSelect.value + index > pluginHistory.value.length - 1 || + currentSelect.value + index < 0 + ) { + currentSelect.value = 0; + return; + } + currentSelect.value = currentSelect.value + index; + return; + } if ( currentSelect.value + index > options.value.length - 1 || currentSelect.value + index < 0 - ) + ) { + currentSelect.value = 0; return; + } currentSelect.value = currentSelect.value + index; }; @@ -101,14 +116,20 @@ const openMenu = (ext) => { feature: menuPluginInfo.value.features[0], cmd: '插件市场', ext, + click: () => openMenu(ext), }); }; window.rubick.openMenu = openMenu; const choosePlugin = () => { - const currentChoose = options.value[currentSelect.value]; - currentChoose.click(); + if (options.value.length) { + const currentChoose = options.value[currentSelect.value]; + currentChoose.click(); + } else { + const currentChoose = pluginHistory.value[currentSelect.value]; + currentChoose.click(); + } }; const clearSearchValue = () => { diff --git a/src/renderer/assets/var.less b/src/renderer/assets/var.less index 9435b0d..5df67a0 100644 --- a/src/renderer/assets/var.less +++ b/src/renderer/assets/var.less @@ -5,7 +5,7 @@ // 背景色 --color-body-bg: #fff; --color-menu-bg: #f3efef; - --color-list-hover: #e2e2e2; + --color-list-hover: #ebeee8; --color-input-hover: #fff; // 边框 --color-border-light: #f0f0f0; diff --git a/src/renderer/components/result.vue b/src/renderer/components/result.vue index 535724d..ab572fc 100644 --- a/src/renderer/components/result.vue +++ b/src/renderer/components/result.vue @@ -1,14 +1,24 @@ - + + + item.click()" + :class="currentSelect === index ? 'active history-item' : 'history-item'" + :span="3" + v-for="(item, index) in pluginHistory" + :key="index" + > + + {{item.pluginName || item._name || item.name}} + + + + item.click()" @@ -52,6 +62,7 @@ const props = defineProps({ default: 0, }, currentPlugin: {}, + pluginHistory: (() => [])(), clipboardFile: (() => [])(), }); @@ -91,15 +102,45 @@ const sort = (options) => {