Compare commits

...

9 Commits

26 changed files with 864 additions and 182 deletions

View File

@@ -1,6 +1,17 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="CssUnknownProperty" enabled="true" level="WARNING" enabled_by_default="true">
<option name="myCustomPropertiesEnabled" value="true" />
<option name="myIgnoreVendorSpecificProperties" value="false" />
<option name="myCustomPropertiesList">
<value>
<list size="1">
<item index="0" class="java.lang.String" itemvalue="app-region" />
</list>
</value>
</option>
</inspection_tool>
<inspection_tool class="Eslint" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="PyPackageRequirementsInspection" enabled="true" level="WARNING" enabled_by_default="true">
<option name="ignoredPackages">

21
LICENSE.txt Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) [year] [fullname]
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

25
clone_wiki.sh Normal file
View File

@@ -0,0 +1,25 @@
#!/bin/bash
# 克隆 FanTools Wiki 仓库到指定目录
WIKI_URL="https://gitea.mangofanfan.cn/MangoFanFanw/FanTools.wiki.git"
TARGET_DIR="/home/fanfan/FanTools.wiki"
echo "开始克隆 FanTools Wiki 仓库..."
echo "仓库地址: $WIKI_URL"
echo "目标目录: $TARGET_DIR"
# 创建目标目录(如果不存在)
mkdir -p "$TARGET_DIR"
# 克隆仓库
if [ -d "$TARGET_DIR/.git" ]; then
echo "目录已存在 git 仓库,执行拉取更新..."
cd "$TARGET_DIR"
git pull origin master
else
echo "开始克隆..."
git clone "$WIKI_URL" "$TARGET_DIR"
fi
echo "克隆完成!"
echo "Wiki 内容已保存到: $TARGET_DIR"

216
package-lock.json generated
View File

@@ -16,7 +16,9 @@
"highlight.js": "^11.11.1",
"load-json-file": "^7.0.1",
"make-dir": "^5.1.0",
"markdown-it": "^14.1.1",
"pinia": "^3.0.4",
"simple-git": "^3.33.0",
"vue-router": "^4.6.4",
"write-json-file": "^7.0.0"
},
@@ -36,6 +38,7 @@
"@vitejs/plugin-vue": "^6.0.2",
"electron": "^39.2.6",
"electron-builder": "^26.0.12",
"electron-devtools-installer": "^4.0.0",
"electron-vite": "^5.0.0",
"eslint": "^9.39.1",
"eslint-plugin-vue": "^10.6.2",
@@ -1762,6 +1765,21 @@
"dev": true,
"license": "Apache-2.0"
},
"node_modules/@kwsites/file-exists": {
"version": "1.1.1",
"resolved": "https://registry.npmmirror.com/@kwsites/file-exists/-/file-exists-1.1.1.tgz",
"integrity": "sha512-m9/5YGR18lIwxSFDwfE3oA7bWuq9kdau6ugN4H2rJeyhFQZcG9AgSHkQtSD15a8WvTgfz9aikZMrKPHvbpqFiw==",
"license": "MIT",
"dependencies": {
"debug": "^4.1.1"
}
},
"node_modules/@kwsites/promise-deferred": {
"version": "1.1.1",
"resolved": "https://registry.npmmirror.com/@kwsites/promise-deferred/-/promise-deferred-1.1.1.tgz",
"integrity": "sha512-GaHYm+c0O9MjZRu0ongGBRbinu8gVAMd2UZjji6jVmqKtZluZnptXGWhz1E8j8D2HJ3f/yMxKAUC0b+57wncIw==",
"license": "MIT"
},
"node_modules/@malept/cross-spawn-promise": {
"version": "2.0.0",
"resolved": "https://registry.npmmirror.com/@malept/cross-spawn-promise/-/cross-spawn-promise-2.0.0.tgz",
@@ -4272,8 +4290,7 @@
"resolved": "https://registry.npmmirror.com/core-util-is/-/core-util-is-1.0.2.tgz",
"integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==",
"dev": true,
"license": "MIT",
"optional": true
"license": "MIT"
},
"node_modules/crc": {
"version": "3.8.0",
@@ -4977,6 +4994,16 @@
"node": ">= 10.0.0"
}
},
"node_modules/electron-devtools-installer": {
"version": "4.0.0",
"resolved": "https://registry.npmmirror.com/electron-devtools-installer/-/electron-devtools-installer-4.0.0.tgz",
"integrity": "sha512-9Tntu/jtfSn0n6N/ZI6IdvRqXpDyLQiDuuIbsBI+dL+1Ef7C8J2JwByw58P3TJiNeuqyV3ZkphpNWuZK5iSY2w==",
"dev": true,
"license": "MIT",
"dependencies": {
"unzip-crx-3": "^0.2.0"
}
},
"node_modules/electron-publish": {
"version": "26.8.1",
"resolved": "https://registry.npmmirror.com/electron-publish/-/electron-publish-26.8.1.tgz",
@@ -6435,6 +6462,13 @@
"node": ">= 4"
}
},
"node_modules/immediate": {
"version": "3.0.6",
"resolved": "https://registry.npmmirror.com/immediate/-/immediate-3.0.6.tgz",
"integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==",
"dev": true,
"license": "MIT"
},
"node_modules/immutable": {
"version": "5.1.5",
"resolved": "https://registry.npmmirror.com/immutable/-/immutable-5.1.5.tgz",
@@ -6577,6 +6611,13 @@
"url": "https://github.com/sponsors/mesqueeb"
}
},
"node_modules/isarray": {
"version": "1.0.0",
"resolved": "https://registry.npmmirror.com/isarray/-/isarray-1.0.0.tgz",
"integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==",
"dev": true,
"license": "MIT"
},
"node_modules/isbinaryfile": {
"version": "5.0.7",
"resolved": "https://registry.npmmirror.com/isbinaryfile/-/isbinaryfile-5.0.7.tgz",
@@ -6725,6 +6766,52 @@
"graceful-fs": "^4.1.6"
}
},
"node_modules/jszip": {
"version": "3.10.1",
"resolved": "https://registry.npmmirror.com/jszip/-/jszip-3.10.1.tgz",
"integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==",
"dev": true,
"license": "(MIT OR GPL-3.0-or-later)",
"dependencies": {
"lie": "~3.3.0",
"pako": "~1.0.2",
"readable-stream": "~2.3.6",
"setimmediate": "^1.0.5"
}
},
"node_modules/jszip/node_modules/readable-stream": {
"version": "2.3.8",
"resolved": "https://registry.npmmirror.com/readable-stream/-/readable-stream-2.3.8.tgz",
"integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==",
"dev": true,
"license": "MIT",
"dependencies": {
"core-util-is": "~1.0.0",
"inherits": "~2.0.3",
"isarray": "~1.0.0",
"process-nextick-args": "~2.0.0",
"safe-buffer": "~5.1.1",
"string_decoder": "~1.1.1",
"util-deprecate": "~1.0.1"
}
},
"node_modules/jszip/node_modules/safe-buffer": {
"version": "5.1.2",
"resolved": "https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.1.2.tgz",
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
"dev": true,
"license": "MIT"
},
"node_modules/jszip/node_modules/string_decoder": {
"version": "1.1.1",
"resolved": "https://registry.npmmirror.com/string_decoder/-/string_decoder-1.1.1.tgz",
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
"dev": true,
"license": "MIT",
"dependencies": {
"safe-buffer": "~5.1.0"
}
},
"node_modules/keyv": {
"version": "4.5.4",
"resolved": "https://registry.npmmirror.com/keyv/-/keyv-4.5.4.tgz",
@@ -6754,6 +6841,25 @@
"node": ">= 0.8.0"
}
},
"node_modules/lie": {
"version": "3.3.0",
"resolved": "https://registry.npmmirror.com/lie/-/lie-3.3.0.tgz",
"integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"immediate": "~3.0.5"
}
},
"node_modules/linkify-it": {
"version": "5.0.0",
"resolved": "https://registry.npmmirror.com/linkify-it/-/linkify-it-5.0.0.tgz",
"integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==",
"license": "MIT",
"dependencies": {
"uc.micro": "^2.0.0"
}
},
"node_modules/load-json-file": {
"version": "7.0.1",
"resolved": "https://registry.npmmirror.com/load-json-file/-/load-json-file-7.0.1.tgz",
@@ -6914,6 +7020,35 @@
"node": "^18.17.0 || >=20.5.0"
}
},
"node_modules/markdown-it": {
"version": "14.1.1",
"resolved": "https://registry.npmmirror.com/markdown-it/-/markdown-it-14.1.1.tgz",
"integrity": "sha512-BuU2qnTti9YKgK5N+IeMubp14ZUKUUw7yeJbkjtosvHiP0AZ5c8IAgEMk79D0eC8F23r4Ac/q8cAIFdm2FtyoA==",
"license": "MIT",
"dependencies": {
"argparse": "^2.0.1",
"entities": "^4.4.0",
"linkify-it": "^5.0.0",
"mdurl": "^2.0.0",
"punycode.js": "^2.3.1",
"uc.micro": "^2.1.0"
},
"bin": {
"markdown-it": "bin/markdown-it.mjs"
}
},
"node_modules/markdown-it/node_modules/entities": {
"version": "4.5.0",
"resolved": "https://registry.npmmirror.com/entities/-/entities-4.5.0.tgz",
"integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
"license": "BSD-2-Clause",
"engines": {
"node": ">=0.12"
},
"funding": {
"url": "https://github.com/fb55/entities?sponsor=1"
}
},
"node_modules/matcher": {
"version": "3.0.0",
"resolved": "https://registry.npmmirror.com/matcher/-/matcher-3.0.0.tgz",
@@ -6944,6 +7079,12 @@
"dev": true,
"license": "CC0-1.0"
},
"node_modules/mdurl": {
"version": "2.0.0",
"resolved": "https://registry.npmmirror.com/mdurl/-/mdurl-2.0.0.tgz",
"integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==",
"license": "MIT"
},
"node_modules/mime": {
"version": "2.6.0",
"resolved": "https://registry.npmmirror.com/mime/-/mime-2.6.0.tgz",
@@ -7190,7 +7331,6 @@
"integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"minimist": "^1.2.6"
},
@@ -7603,6 +7743,13 @@
"dev": true,
"license": "BlueOak-1.0.0"
},
"node_modules/pako": {
"version": "1.0.11",
"resolved": "https://registry.npmmirror.com/pako/-/pako-1.0.11.tgz",
"integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==",
"dev": true,
"license": "(MIT AND Zlib)"
},
"node_modules/parent-module": {
"version": "1.0.1",
"resolved": "https://registry.npmmirror.com/parent-module/-/parent-module-1.0.1.tgz",
@@ -7923,6 +8070,13 @@
"node": "^18.17.0 || >=20.5.0"
}
},
"node_modules/process-nextick-args": {
"version": "2.0.1",
"resolved": "https://registry.npmmirror.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
"integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==",
"dev": true,
"license": "MIT"
},
"node_modules/progress": {
"version": "2.0.3",
"resolved": "https://registry.npmmirror.com/progress/-/progress-2.0.3.tgz",
@@ -7978,6 +8132,15 @@
"node": ">=6"
}
},
"node_modules/punycode.js": {
"version": "2.3.1",
"resolved": "https://registry.npmmirror.com/punycode.js/-/punycode.js-2.3.1.tgz",
"integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==",
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/quansync": {
"version": "0.2.11",
"resolved": "https://registry.npmmirror.com/quansync/-/quansync-0.2.11.tgz",
@@ -8696,6 +8859,13 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/setimmediate": {
"version": "1.0.5",
"resolved": "https://registry.npmmirror.com/setimmediate/-/setimmediate-1.0.5.tgz",
"integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==",
"dev": true,
"license": "MIT"
},
"node_modules/shebang-command": {
"version": "2.0.0",
"resolved": "https://registry.npmmirror.com/shebang-command/-/shebang-command-2.0.0.tgz",
@@ -8726,6 +8896,21 @@
"dev": true,
"license": "ISC"
},
"node_modules/simple-git": {
"version": "3.33.0",
"resolved": "https://registry.npmmirror.com/simple-git/-/simple-git-3.33.0.tgz",
"integrity": "sha512-D4V/tGC2sjsoNhoMybKyGoE+v8A60hRawKQ1iFRA1zwuDgGZCBJ4ByOzZ5J8joBbi4Oam0qiPH+GhzmSBwbJng==",
"license": "MIT",
"dependencies": {
"@kwsites/file-exists": "^1.1.1",
"@kwsites/promise-deferred": "^1.1.1",
"debug": "^4.4.0"
},
"funding": {
"type": "github",
"url": "https://github.com/steveukx/git-js?sponsor=1"
}
},
"node_modules/simple-update-notifier": {
"version": "2.0.0",
"resolved": "https://registry.npmmirror.com/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz",
@@ -9353,6 +9538,12 @@
"typescript": ">=4.8.4 <6.0.0"
}
},
"node_modules/uc.micro": {
"version": "2.1.0",
"resolved": "https://registry.npmmirror.com/uc.micro/-/uc.micro-2.1.0.tgz",
"integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==",
"license": "MIT"
},
"node_modules/ufo": {
"version": "1.6.3",
"resolved": "https://registry.npmmirror.com/ufo/-/ufo-1.6.3.tgz",
@@ -9497,6 +9688,18 @@
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/unzip-crx-3": {
"version": "0.2.0",
"resolved": "https://registry.npmmirror.com/unzip-crx-3/-/unzip-crx-3-0.2.0.tgz",
"integrity": "sha512-0+JiUq/z7faJ6oifVB5nSwt589v1KCduqIJupNVDoWSXZtWDmjDGO3RAEOvwJ07w90aoXoP4enKsR7ecMrJtWQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"jszip": "^3.1.0",
"mkdirp": "^0.5.1",
"yaku": "^0.16.6"
}
},
"node_modules/update-browserslist-db": {
"version": "1.2.3",
"resolved": "https://registry.npmmirror.com/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz",
@@ -10449,6 +10652,13 @@
"node": ">=10"
}
},
"node_modules/yaku": {
"version": "0.16.7",
"resolved": "https://registry.npmmirror.com/yaku/-/yaku-0.16.7.tgz",
"integrity": "sha512-Syu3IB3rZvKvYk7yTiyl1bo/jiEFaaStrgv1V2TIJTqYPStSMQVO8EQjg/z+DRzLq/4LIIharNT3iH1hylEIRw==",
"dev": true,
"license": "MIT"
},
"node_modules/yallist": {
"version": "3.1.1",
"resolved": "https://registry.npmmirror.com/yallist/-/yallist-3.1.1.tgz",

View File

@@ -29,7 +29,9 @@
"highlight.js": "^11.11.1",
"load-json-file": "^7.0.1",
"make-dir": "^5.1.0",
"markdown-it": "^14.1.1",
"pinia": "^3.0.4",
"simple-git": "^3.33.0",
"vue-router": "^4.6.4",
"write-json-file": "^7.0.0"
},
@@ -49,6 +51,7 @@
"@vitejs/plugin-vue": "^6.0.2",
"electron": "^39.2.6",
"electron-builder": "^26.0.12",
"electron-devtools-installer": "^4.0.0",
"electron-vite": "^5.0.0",
"eslint": "^9.39.1",
"eslint-plugin-vue": "^10.6.2",

View File

@@ -0,0 +1,93 @@
import { BrowserWindow, screen, shell, Tray } from 'electron'
import { settingsManager } from '../settings'
import { codeLaunchpadIcon } from '../resources'
import path from 'path'
import { is } from '@electron-toolkit/utils'
/**
* 创建代码启动台窗口。
* @return 布尔值,表明创建是否成功
*/
export function createCodeLaunchpadWindow(closeOnBlur: boolean): boolean {
// 不允许重复创建
if (global.codeLaunchpadWindow !== null) {
return false
}
const windowWidth = settingsManager._settings?.codeLaunchpadWidth
? settingsManager._settings?.codeLaunchpadWidth
: 460
const windowHeight = settingsManager._settings?.codeLaunchpadHeight
? settingsManager._settings?.codeLaunchpadHeight
: 760
const position = settingsManager._settings?.codeLaunchpadPosition
? settingsManager._settings.codeLaunchpadPosition
: 'left top'
const codeLaunchpadWindow = new BrowserWindow({
width: windowWidth,
height: windowHeight,
x:
position === 'left top' || position === 'left bottom'
? 0
: screen.getPrimaryDisplay().workArea.width - windowWidth,
y:
position === 'left top' || position === 'right top'
? 0
: screen.getPrimaryDisplay().workArea.height - windowHeight,
frame: false,
show: false,
autoHideMenuBar: true,
// 代码启动台需要置顶
alwaysOnTop: true,
resizable: false,
backgroundColor: '#1f1f1f',
icon: codeLaunchpadIcon,
webPreferences: {
preload: path.join(__dirname, '../preload/index.mjs'),
sandbox: false
}
})
codeLaunchpadWindow.on('ready-to-show', () => {
codeLaunchpadWindow.show()
})
// 如有必要,失去焦点时自动关闭
if (closeOnBlur) {
codeLaunchpadWindow.on('blur', () => {
codeLaunchpadWindow.close()
})
}
// 关闭代码启动台时
codeLaunchpadWindow.on('close', () => {
global.codeLaunchpadWindow = null
})
codeLaunchpadWindow.webContents.setWindowOpenHandler((details) => {
shell.openExternal(details.url)
return { action: 'deny' }
})
// 开发和生产环境的各自设置
if (is.dev && process.env['ELECTRON_RENDERER_URL']) {
codeLaunchpadWindow.loadURL(process.env['ELECTRON_RENDERER_URL'] + '#/codeLaunchpad/IDEs')
} else {
codeLaunchpadWindow.loadFile(path.join(__dirname, '../renderer/index.html'), {
hash: '/codeLaunchpad/IDEs'
})
}
global.codeLaunchpadWindow = codeLaunchpadWindow
return true
}
export function createCodeLaunchpadTray(): Tray {
const tray = new Tray(codeLaunchpadIcon)
tray.on('click', () => createCodeLaunchpadWindow(true))
return tray
}

View File

@@ -6,11 +6,7 @@ import { loadJsonFile } from 'load-json-file'
import { XMLParser } from 'fast-xml-parser'
import { VSCodeGlobalStorageJson } from '@my-type/vscode-globalstorage-json'
import { JetBrainsIdeOptionsRecentProjects } from '@my-type/jetbrains-ide-options-recentProjects'
import {
JetBrainsIDEDisplayNameEnum,
JetBrainsProductCode,
toProductCode
} from '@my-type/jetbrains-state-tools'
import { JetBrainsIDEDisplayNameEnum, JetBrainsProductCode, toProductCode } from '@my-type/jetbrains-state-tools'
import { checkIDEsResultDto, IDECode } from '@my-type/settings'
import { spawn } from 'node:child_process'
@@ -43,7 +39,7 @@ export async function getVscodeProjects(): Promise<IdeProjectsDto> {
// 还有一种协议名是 vscode-remote:// 将会保留,以作为 VSCode Remote 的标识
const path = decodeURIComponent(workspace[0]).replace('file:///', '')
const name = <string>path.split('/').at(-1)
result.push({ name, path, ide: ['VSC'] })
result.push({ name, path, timestamp: 0, ide: ['VSC'] })
}
return result
}
@@ -89,17 +85,42 @@ export async function getJetBrainsProjects(): Promise<IdeProjectsDto> {
await fs.readFile(rpp, 'utf-8')
)
for (const datum of data.application.component.option) {
// 检索正确的 xml 路径
if (datum['@_name'] !== 'additionalInfo') continue
for (const entry of datum.map.entry) {
const name = <string>entry.value.RecentProjectMetaInfo['@_frameTitle'].split(' ').at(0)
const path = entry['@_key'].replace('$USER_HOME$', os.homedir())
// 项目的上次打开时间 projectOpenTimestamp 是一个时间戳,这里提供默认值 0 表示来自 1970 年的上古项目
let timestamp = 0
for (const option of entry.value.RecentProjectMetaInfo.option) {
if (option['@_name'] === 'projectOpenTimestamp') {
timestamp = Number(option['@_value'])
}
}
// 认为包含此(安装/设置?)目录的是未保存的编辑,例如 light-edit 模式
// 正常来说不会在这样的目录下保存项目的……吧?而且俺寻思 IDE 用这种变量的话也不像是人为刻意保存到此目录。
if (path.includes('$APPLICATION_CONFIG_DIR$/')) continue
result.push({
name,
path,
ide: [ide]
})
// 去重。
let pass = false
for (const resultElement of result) {
// 如果路径已存在,即此项目被用其他 JetBrains IDE 打开过
if (resultElement.path === path) {
// 如果 IDE 与该项目已有的工作 IDE 不同,就把这个 IDE 加进列表里去
if (!resultElement.ide.includes(ide)) {
resultElement.ide.push(ide)
}
// 如果 IDE 也重复了就忽略,然后直接
pass = true
}
}
if (!pass) {
result.push({
name,
path,
timestamp,
ide: [ide]
})
}
}
}
} catch {

View File

@@ -6,18 +6,16 @@ import type {
IDECode
} from '@my-type/settings'
import { execSync } from 'node:child_process'
import { BrowserWindow, screen, shell, Tray } from 'electron'
import { is } from '@electron-toolkit/utils'
import path from 'path'
import { loadJsonFile } from 'load-json-file'
import os from 'os'
import {
JetBrainsIDEDisplayNameEnum as JIN,
JetBrainsProductCode,
JetBrainsStateDto
JetBrainsStateDto,
toProductDisplayName
} from '@my-type/jetbrains-state-tools'
import { settingsManager } from '../settings'
import { codeLaunchpadIcon } from '../resources'
import { isNodeError } from '@my-type/node-error'
import { JetBrainsDataProductDto } from '@my-type/jetbrains-data-products'
@@ -140,20 +138,20 @@ async function checkVSCodeVersion(): Promise<checkIDEVersionDto> {
*/
export async function checkJetBrainsIDEsVersion(): Promise<checkIDEsVersionDto> {
// 构建数据结构的辅助函数
const _ = (display: string, code: JetBrainsProductCode): checkIDEVersionDto => {
const _ = (code: JetBrainsProductCode): checkIDEVersionDto => {
return {
code,
display,
display: toProductDisplayName(code) as string,
install: 'unknown',
latest: 'unknown'
}
}
const result: checkIDEsVersionDto = {
PY: _('pycharm', 'PY'),
CL: _('clion', 'CL'),
WS: _('webstorm', 'WS'),
PS: _('phpstorm', 'PS'),
IU: _('idea', 'IU')
PY: _('PY'),
CL: _('CL'),
WS: _('WS'),
PS: _('PS'),
IU: _('IU')
}
// 尝试从 JBTState.json 获取已安装的 JetBrains IDEs 的版本
@@ -233,91 +231,3 @@ export async function checkIDEsVersion(): Promise<checkIDEsVersionDto> {
global.installedIDEsVersion = result
return result
}
/**
* 创建代码启动台窗口。
* @return 布尔值,表明创建是否成功
*/
export function createCodeLaunchpadWindow(closeOnBlur: boolean): boolean {
// 不允许重复创建
if (global.codeLaunchpadWindow !== null) {
return false
}
const windowWidth = settingsManager._settings?.codeLaunchpadWidth
? settingsManager._settings?.codeLaunchpadWidth
: 460
const windowHeight = settingsManager._settings?.codeLaunchpadHeight
? settingsManager._settings?.codeLaunchpadHeight
: 760
const position = settingsManager._settings?.codeLaunchpadPosition
? settingsManager._settings.codeLaunchpadPosition
: 'left top'
const codeLaunchpadWindow = new BrowserWindow({
width: windowWidth,
height: windowHeight,
x:
position === 'left top' || position === 'left bottom'
? 0
: screen.getPrimaryDisplay().workArea.width - windowWidth,
y:
position === 'left top' || position === 'right top'
? 0
: screen.getPrimaryDisplay().workArea.height - windowHeight,
frame: false,
show: false,
autoHideMenuBar: true,
// 代码启动台需要置顶
alwaysOnTop: true,
resizable: false,
backgroundColor: '#1f1f1f',
icon: codeLaunchpadIcon,
webPreferences: {
preload: path.join(__dirname, '../preload/index.mjs'),
sandbox: false
}
})
codeLaunchpadWindow.on('ready-to-show', () => {
codeLaunchpadWindow.show()
})
// 如有必要,失去焦点时自动关闭
if (closeOnBlur) {
codeLaunchpadWindow.on('blur', () => {
codeLaunchpadWindow.close()
})
}
// 关闭代码启动台时
codeLaunchpadWindow.on('close', () => {
global.codeLaunchpadWindow = null
})
codeLaunchpadWindow.webContents.setWindowOpenHandler((details) => {
shell.openExternal(details.url)
return { action: 'deny' }
})
// 开发和生产环境的各自设置
if (is.dev && process.env['ELECTRON_RENDERER_URL']) {
codeLaunchpadWindow.loadURL(process.env['ELECTRON_RENDERER_URL'] + '#/codeLaunchpad/IDEs')
} else {
codeLaunchpadWindow.loadFile(path.join(__dirname, '../renderer/index.html'), {
hash: '/codeLaunchpad/IDEs'
})
}
global.codeLaunchpadWindow = codeLaunchpadWindow
return true
}
export function createCodeLaunchpadTray(): Tray {
const tray = new Tray(codeLaunchpadIcon)
tray.on('click', () => createCodeLaunchpadWindow(true))
return tray
}

View File

@@ -0,0 +1,29 @@
import { ProjectGitDto } from '@my-type/ide-projects'
import simpleGit from 'simple-git'
/**
* 获取指定项目的 git 数据(如有)
* @param path 项目路径,应该是 {@link IdeProjectDto.path}
*/
export async function getProjectGitInfo(path: string): Promise<ProjectGitDto | null> {
try {
const git = simpleGit(path)
if (!(await git.checkIsRepo())) {
console.log(`路径 ${path} 的项目不存在 git 仓库。`)
return null
}
const status = await git.status()
return {
current: status.current,
tracking: status.tracking,
created: status.created.length,
deleted: status.deleted.length,
modified: status.modified.length,
renamed: status.renamed.length,
staged: status.staged.length
}
} catch (error) {
console.error(error)
return null
}
}

View File

@@ -4,14 +4,16 @@ import { saveSettingsToLocal, updateSettingsFromLocal } from './settings'
import {
checkIDEs,
checkIDEsVersion,
createCodeLaunchpadTray,
createCodeLaunchpadWindow,
getIDEs,
getIDEsVersion
} from './code-launchpad/ide-versions-check'
import { fanToolsIcon } from './resources'
import path from 'path'
import { getJetBrainsProjects, getVscodeProjects, openProject } from './code-launchpad/ide-projects'
import { createCodeLaunchpadTray, createCodeLaunchpadWindow } from './code-launchpad/code-launchpad'
import type { IDECode } from '@my-type/settings'
import installExtension, { VUEJS_DEVTOOLS } from 'electron-devtools-installer'
import { getProjectGitInfo } from './code-launchpad/project-git'
let mainWindow: BrowserWindow | null = null
// @ts-ignore 保存引用,禁用报错
@@ -31,6 +33,7 @@ function createWindow(): BrowserWindow {
show: false,
autoHideMenuBar: true,
backgroundColor: '#1f1f1f',
frame: false,
icon: fanToolsIcon,
webPreferences: {
preload: path.join(__dirname, '../preload/index.mjs'),
@@ -97,6 +100,14 @@ app.whenReady().then(async () => {
// Set app user model id for windows
electronApp.setAppUserModelId('com.electron')
// 安装 DevTools 插件
if (is.dev) {
// 安装Vue DevTools
installExtension(VUEJS_DEVTOOLS)
.then((name) => console.log(`已安装扩展: ${name}`))
.catch((err) => console.log('安装失败:', err))
}
// Default open or close DevTools by F12 in development
// and ignore CommandOrControl + R in production.
// see https://github.com/alex8088/electron-toolkit/tree/master/packages/utils
@@ -125,15 +136,35 @@ app.whenReady().then(async () => {
}
return false
})
ipcMain.handle('window:minimize', () => {
mainWindow?.minimize()
})
ipcMain.handle('window:maximize', () => {
if (mainWindow?.isMaximized()) {
mainWindow?.unmaximize()
} else {
mainWindow?.maximize()
}
})
ipcMain.handle('window:closeWindow', () => {
mainWindow?.hide()
})
ipcMain.handle('window:exit', () => {
global.isQuiting = true
app.quit()
})
ipcMain.handle('codeLaunchpad:getIDEs', getIDEs)
ipcMain.handle('codeLaunchpad:checkIDEs', checkIDEs)
ipcMain.handle('codeLaunchpad:getIDEsVersion', getIDEsVersion)
ipcMain.handle('codeLaunchpad:checkIDEsVersion', checkIDEsVersion)
ipcMain.handle('codeLaunchpad:getVSCodeProjects', getVscodeProjects)
ipcMain.handle('codeLaunchpad:getJetBrainsProjects', getJetBrainsProjects)
ipcMain.handle('codeLaunchpad:openProject', (_, ide: string, path: string) => {
ipcMain.handle('codeLaunchpad:openProject', (_, ide: IDECode, path: string) => {
return openProject(ide, path)
})
ipcMain.handle('codeLaunchpad:getProjectGitInfo', (_, path: string) => {
return getProjectGitInfo(path)
})
await checkIDEs()
await checkIDEsVersion()

View File

@@ -0,0 +1,11 @@
export const formatTimestamp = (timestamp: number, locale = 'zh-CN'): string => {
return new Date(timestamp).toLocaleString(locale, {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
hour12: false
})
}

View File

@@ -5,7 +5,18 @@ type Ide = JetBrainsProductCode | 'VSC'
export interface IdeProjectDto {
name: string
path: string
timestamp: number
ide: Ide[]
}
export type IdeProjectsDto = IdeProjectDto[]
export interface ProjectGitDto {
current: string | null
tracking: string | null
created: number
deleted: number
modified: number
renamed: number
staged: number
}

View File

@@ -1,23 +1,29 @@
// 这些数据结构是由 XMLParser 解析成的 Javascript 对象,解析设置允许属性。
// 不完整,因为我也看不懂很多东西是干嘛用的,写完整了也用不到,所以把用到的定义一下就酱了
export interface RecentProjectMetaInfo {
option: []
frame: object
'@_frameTitle': string
}
export interface JetBrainsIdeOptionsRecentProjects {
application: {
component: {
option: [
{
'@_name': 'activationTimestamp'
'@_value': string
},
{
map: {
entry: {
value: { RecentProjectMetaInfo: RecentProjectMetaInfo }
value: {
RecentProjectMetaInfo: {
// option 中还有很多东西,这里只有我们需要的
option: [{ '@_name': 'projectOpenTimestamp'; '@_value': string }]
frame: object
'@_frameTitle': string
}
}
'@_key': string
}[]
}
'@_name': 'additionalInfo'
}
]
'@_name': 'RecentProjectsManager'

View File

@@ -1,19 +1,23 @@
import { ElectronAPI } from '@electron-toolkit/preload'
import { settingsDto, checkIDEsResultDto } from '@my-type/settings'
import { ElectronAPI } from "@electron-toolkit/preload";
import { settingsDto, checkIDEsResultDto } from "@my-type/settings";
import { checkIDEsVersionDto } from "../my-type/settings";
import { IdeProjectsDto } from "../my-type/ide-projects";
import { GitProjectDto, IdeProjectsDto } from "../my-type/ide-projects";
// 此处只有签名
declare global {
interface Window {
electron: ElectronAPI
electron: ElectronAPI;
api: {
_saveSettings: (settings: settingsDto) => Promise<boolean>
_updateSettings: () => Promise<settingsDto>
_openCodeLaunchpad: () => Promise<boolean>
_closeCodeLaunchpad: () => Promise<boolean>
}
_minimize: () => Promise<void>
_maximize: () => Promise<void>
_closeWindow: () => Promise<void>
_exit: () => Promise<void>
};
codeLaunchpad: {
_getIDEs: () => Promise<checkIDEsResultDto>
_checkIDEs: () => Promise<checkIDEsResultDto>
@@ -22,6 +26,7 @@ declare global {
_getVSCodeProjects: () => Promise<IdeProjectsDto>
_getJetBrainsProjects: () => Promise<IdeProjectsDto>
_openProject: (ide: string, path: string) => Promise<boolean>
}
_getProjectGitInfo: (path: string) => Promise<GitProjectDto | null>
};
}
}

View File

@@ -1,3 +1,5 @@
// noinspection JSUnusedGlobalSymbols
import { contextBridge, ipcRenderer } from 'electron'
import { electronAPI } from '@electron-toolkit/preload'
import { settingsDto } from '@my-type/settings'
@@ -8,7 +10,11 @@ const api = {
_saveSettings: (settings: settingsDto) => ipcRenderer.invoke('settings:save', settings),
_updateSettings: () => ipcRenderer.invoke('settings:update'),
_openCodeLaunchpad: () => ipcRenderer.invoke('tools:openCodeLaunchpad'),
_closeCodeLaunchpad: () => ipcRenderer.invoke('tools:closeCodeLaunchpad')
_closeCodeLaunchpad: () => ipcRenderer.invoke('tools:closeCodeLaunchpad'),
_minimize: () => ipcRenderer.invoke('window:minimize'),
_maximize: () => ipcRenderer.invoke('window:maximize'),
_closeWindow: () => ipcRenderer.invoke('window:closeWindow'),
_exit: () => ipcRenderer.invoke('window:exit')
}
const codeLaunchpadApi = {
@@ -19,7 +25,8 @@ const codeLaunchpadApi = {
_getVSCodeProjects: () => ipcRenderer.invoke('codeLaunchpad:getVSCodeProjects'),
_getJetBrainsProjects: () => ipcRenderer.invoke('codeLaunchpad:getJetBrainsProjects'),
_openProject: (ide: string, path: string) =>
ipcRenderer.invoke('codeLaunchpad:openProject', ide, path)
ipcRenderer.invoke('codeLaunchpad:openProject', ide, path),
_getProjectGitInfo: (path: string) => ipcRenderer.invoke('codeLaunchpad:getProjectGitInfo', path)
}
// Use `contextBridge` APIs to expose Electron APIs to

View File

@@ -17,6 +17,7 @@ declare module 'vue' {
DetectedIDECard: typeof import('./src/components/DetectedIDECard.vue')['default']
DetectedIDECardList: typeof import('./src/components/DetectedIDECardList.vue')['default']
NAlert: typeof import('naive-ui')['NAlert']
NavigatorBar: typeof import('./src/components/NavigatorBar.vue')['default']
NButton: typeof import('naive-ui')['NButton']
NButtonGroup: typeof import('naive-ui')['NButtonGroup']
NCard: typeof import('naive-ui')['NCard']
@@ -32,11 +33,13 @@ declare module 'vue' {
NH2: typeof import('naive-ui')['NH2']
NH3: typeof import('naive-ui')['NH3']
NH4: typeof import('naive-ui')['NH4']
NH5: typeof import('naive-ui')['NH5']
NIcon: typeof import('naive-ui')['NIcon']
NInput: typeof import('naive-ui')['NInput']
NInputNumber: typeof import('naive-ui')['NInputNumber']
NMenu: typeof import('naive-ui')['NMenu']
NMessageProvider: typeof import('naive-ui')['NMessageProvider']
NModal: typeof import('naive-ui')['NModal']
NP: typeof import('naive-ui')['NP']
NSelect: typeof import('naive-ui')['NSelect']
NSpace: typeof import('naive-ui')['NSpace']

View File

@@ -1,39 +1,57 @@
<script lang="ts" setup>
import SidebarRouter from '@renderer/components/SidebarRouter.vue'
import SaveSettingsButton from '@renderer/components/SaveSettingsButton.vue'
import NavigatorBar from '@renderer/components/NavigatorBar.vue'
</script>
<template>
<div class="everything-container">
<div class="sidebar-router-container scrollarea">
<SidebarRouter />
<div class="main-app-container">
<div class="navigator-bar">
<NavigatorBar />
</div>
<div class="content-container scrollarea">
<n-message-provider placement="bottom">
<RouterView />
<SaveSettingsButton />
</n-message-provider>
<div class="everything-container">
<div class="sidebar-router-container scrollarea">
<SidebarRouter />
</div>
<div class="content-container scrollarea">
<n-message-provider placement="bottom">
<RouterView />
<SaveSettingsButton />
</n-message-provider>
</div>
</div>
</div>
</template>
<style lang="scss">
div.everything-container {
div.main-app-container {
display: flex;
flex-direction: row;
overflow: hidden;
flex-direction: column;
width: 100vw;
height: 100vh;
overflow: hidden;
div.sidebar-router-container {
flex: 0;
min-width: 200px;
div.navigator-bar {
padding: 8px;
-webkit-app-region: drag;
}
div.content-container {
div.everything-container {
flex: 1;
padding: 20px 10px 20px 0;
overflow-x: hidden;
display: flex;
flex-direction: row;
overflow: hidden;
div.sidebar-router-container {
flex: 0;
min-width: 200px;
}
div.content-container {
flex: 1;
padding: 20px 10px 20px 0;
overflow-x: hidden;
}
}
}
</style>

View File

@@ -10,8 +10,24 @@ body {
overflow: hidden;
}
.no-padding {
padding: 0 !important;
}
.small-padding {
padding: 10px !important;
}
.no-padding-top {
padding-top: 0 !important;
}
.no-margin-top {
margin-top: 0 !important;
}
.no-margin-bottom {
margin-bottom: 0;
margin-bottom: 0 !important;
}
// 滚动区域

View File

@@ -13,11 +13,14 @@ onMounted(() => {})
</script>
<template>
<n-card>
<n-card
content-class="small-padding no-padding-top"
header-class="small-padding no-padding-bottom"
>
<template #header>
<div class="ide-card-header">
<n-icon :component="icon" size="34" />
<n-h2>{{ ideInfo.display }}</n-h2>
<n-h3>{{ ideInfo.display }}</n-h3>
</div>
</template>
<template #header-extra>
@@ -39,7 +42,7 @@ div.ide-card-header {
display: flex;
flex-direction: row;
h2.n-h2 {
h3.n-h3 {
margin: 0 12px;
}
}

View File

@@ -4,6 +4,8 @@ import { EllipsisHorizontalSharp as EllipsisIcon } from '@vicons/ionicons5'
import type { IdeProjectDto } from '@my-type/ide-projects'
import { toProductDisplayName } from '@my-type/jetbrains-state-tools'
import { useMessage } from 'naive-ui'
import { formatTimestamp } from '@my-type/dataFormatter'
import ProjectGit from '@renderer/components-code-launchpad/ProjectGit.vue'
const message = useMessage()
@@ -67,11 +69,11 @@ async function openWithIDE(ide: string): Promise<void> {
</script>
<template>
<n-card>
<n-card content-class="no-padding" footer-class="no-padding-top small-padding">
<template #default>
<div @click="() => (showDetail = true)">
<div class="click-area" @click="() => (showDetail = true)">
<n-flex justify="left">
<n-h4>{{ project.name }}</n-h4>
<n-h4 class="no-margin-bottom">{{ project.name }}</n-h4>
<n-tag v-for="tag in ideTags" :key="tag" round :type="tag === 'WSL' ? 'primary' : 'info'">
{{ tag }}
</n-tag>
@@ -80,29 +82,49 @@ async function openWithIDE(ide: string): Promise<void> {
</div>
</template>
<template v-if="showDetail" #footer>
<n-flex>
<n-button-group>
<n-button
v-for="ide in project.ide"
:key="ide"
round
type="info"
@click="() => openWithIDE(ide)"
>
{{ ide === 'VSC' ? 'VS Code' : toProductDisplayName(ide) }} 打开
</n-button>
<n-dropdown trigger="click" :options @select="(key) => openWithIDE(key as string)">
<n-button type="info" circle>
<n-icon>
<EllipsisIcon />
</n-icon>
<n-flex vertical>
<n-p v-if="project.timestamp !== 0" class="no-margin-bottom" type="default">
上次打开于 {{ formatTimestamp(project.timestamp) }}
</n-p>
<ProjectGit :path="project.path" />
<n-flex>
<n-button-group>
<n-button
v-for="ide in project.ide"
:key="ide"
round
type="info"
size="small"
@click="() => openWithIDE(ide)"
>
{{ ide === 'VSC' ? 'VS Code' : toProductDisplayName(ide) }} 打开
</n-button>
</n-dropdown>
</n-button-group>
<n-button round secondary type="primary" @click="() => (showDetail = false)">收起</n-button>
<n-dropdown trigger="click" :options @select="(key) => openWithIDE(key as string)">
<n-button type="info" circle size="small">
<n-icon>
<EllipsisIcon />
</n-icon>
</n-button>
</n-dropdown>
</n-button-group>
<n-button round secondary type="primary" size="small" @click="() => (showDetail = false)">
收起
</n-button>
</n-flex>
</n-flex>
</template>
</n-card>
</template>
<style scoped></style>
<style scoped>
div.click-area {
padding: 10px;
border: 1px solid transparent;
border-radius: 4px;
transition: border-color 0.3s;
}
div.click-area:hover {
border: 1px solid #74c072;
}
</style>

View File

@@ -0,0 +1,54 @@
<script setup lang="ts">
import { onMounted, ref } from 'vue'
import { CreateOutline as CreatedIcon } from '@vicons/ionicons5'
import {
ChangeHistoryOutlined as ModifiedIcon,
DeleteOutlined as DeletedIcon,
DriveFileRenameOutlineSharp as RenamedIcon
} from '@vicons/material'
import { ProjectGitDto } from '@my-type/ide-projects'
import ProjectGitIconWidget from '@renderer/components-code-launchpad/ProjectGitIconWidget.vue'
const info = ref<ProjectGitDto | null>(null)
const loading = ref(true)
const props = defineProps<{ path: string }>()
onMounted(async () => {
info.value = await window.codeLaunchpad._getProjectGitInfo(props.path)
loading.value = false
})
</script>
<template>
<n-card content-class="small-padding">
<n-p v-if="loading">正在查找 git 仓库</n-p>
<n-flex v-else-if="info !== null" vertical size="large">
<n-flex>
<n-tag round size="large" type="success">
{{ info.current }}
</n-tag>
<n-tag round size="large" type="info">
{{ info.tracking ? info.tracking : '无远程' }}
</n-tag>
</n-flex>
<n-flex justify="end">
<ProjectGitIconWidget name="新建" :count="info.created" color="#63e2b7">
<CreatedIcon />
</ProjectGitIconWidget>
<ProjectGitIconWidget name="更名" :count="info.renamed" color="#bbb935">
<RenamedIcon />
</ProjectGitIconWidget>
<ProjectGitIconWidget name="修改" :count="info.modified" color="#8acbec">
<ModifiedIcon />
</ProjectGitIconWidget>
<ProjectGitIconWidget name="删除" :count="info.deleted" color="#e38686">
<DeletedIcon />
</ProjectGitIconWidget>
</n-flex>
</n-flex>
<n-p v-else>未在项目中发现 git 仓库</n-p>
</n-card>
</template>
<style scoped></style>

View File

@@ -0,0 +1,39 @@
<script setup lang="ts">
import { computed } from 'vue'
const props = defineProps<{
name: string
count: number
color: string
}>()
const finalColor = computed(() => {
return props.count === 0 ? '#989898' : props.color
})
</script>
<template>
<div class="icon-widget">
<n-icon size="large">
<slot />
</n-icon>
<n-text style="font-size: 11px">{{ name }}</n-text>
<n-text strong style="font-size: 16px">{{ count }}</n-text>
</div>
</template>
<style scoped lang="scss">
div.icon-widget {
display: flex;
flex-direction: row;
align-items: center;
gap: 6px;
border: 1px solid v-bind(finalColor);
border-radius: 4px;
padding: 3px 6px;
* {
color: v-bind(finalColor);
}
}
</style>

View File

@@ -0,0 +1,70 @@
<script setup lang="ts">
import { CloseOutline as CloseIcon } from '@vicons/ionicons5'
import { CropSquareOutlined as FullScreenIcon } from '@vicons/material'
import { ArrowMinimize20Filled as HideIcon } from '@vicons/fluent'
function minimize(): void {
window.api._minimize()
}
function maximize(): void {
window.api._maximize()
}
function closeWindow(): void {
window.api._closeWindow()
}
</script>
<template>
<div class="title-bar">
<div class="title">
<n-text strong type="info">芒果工具箱 FanTools</n-text>
</div>
<div class="button-group">
<n-flex justify="right" align="center" class="no-drag">
<n-button circle type="default" size="small" @click="minimize()">
<n-icon size="large">
<HideIcon />
</n-icon>
</n-button>
<n-button circle type="warning" size="small" @click="maximize()">
<n-icon size="large">
<FullScreenIcon />
</n-icon>
</n-button>
<n-button circle type="error" size="small" @click="closeWindow()">
<n-icon size="large">
<CloseIcon />
</n-icon>
</n-button>
</n-flex>
</div>
</div>
</template>
<style scoped lang="scss">
.no-drag {
-webkit-app-region: no-drag;
}
div.title-bar {
display: flex;
flex-direction: row;
div.title {
flex: 1;
text-align: center;
align-content: center;
font-size: 16px;
background-color: rgb(57 57 57 / 0.4);
border: 1px solid #8cb0bc;
border-radius: 4px;
}
div.button-group {
flex: 0;
min-width: 120px;
}
}
</style>

View File

@@ -27,8 +27,33 @@ const menuOptions: MenuOption[] = [
function handleUpdateValue(key: string): void {
router.push(key)
}
function exit(): void {
window.api._exit()
}
</script>
<template>
<n-menu v-model:value="activeKey" :options="menuOptions" @update:value="handleUpdateValue" />
<div class="sidebar-container">
<n-menu v-model:value="activeKey" :options="menuOptions" @update:value="handleUpdateValue" />
<div class="exit-button-container">
<n-button class="exit-button" @click="exit()">退出工具箱</n-button>
</div>
</div>
</template>
<style scoped>
div.sidebar-container {
display: flex;
flex-direction: column;
height: 100%;
}
div.exit-button-container {
margin: auto 8px 8px 8px;
}
.exit-button {
width: 100%;
}
</style>

View File

@@ -2,11 +2,12 @@
import ProjectCard from '@renderer/components-code-launchpad/ProjectCard.vue'
import { useProjects } from '@renderer/stores'
import { computed, onMounted, ref } from 'vue'
import { RefreshOutline as RefreshIcon } from '@vicons/ionicons5'
import { AlertOutline as AlertIcon, RefreshOutline as RefreshIcon } from '@vicons/ionicons5'
const projects = useProjects()
const ide = ref<'VSCode' | 'JetBrains'>('VSCode')
const showModal = ref(false)
// 此 switch-case 结构已经触及所有情况
// eslint-disable-next-line vue/return-in-computed-property
@@ -15,7 +16,7 @@ const reverseProjects = computed(() => {
case 'VSCode':
return projects.vscodeProjects.toReversed()
case 'JetBrains':
return projects.jetBrainsProjects.toReversed()
return projects.jetBrainsProjects.toSorted((a, b) => (a.timestamp > b.timestamp ? -1 : 1))
}
})
@@ -32,10 +33,12 @@ onMounted(() => {
<template>
<n-flex>
<n-button-group class="ide-button-group">
<n-button type="primary" secondary round @click="() => (ide = 'VSCode')">VS Code</n-button>
<n-button type="primary" secondary round @click="() => (ide = 'JetBrains')"
>JetBrains</n-button
>
<n-button type="primary" secondary round @click="() => (ide = 'VSCode')">
<n-text :strong="ide === 'VSCode'" type="success">VS Code</n-text>
</n-button>
<n-button type="primary" secondary round @click="() => (ide = 'JetBrains')">
<n-text :strong="ide === 'JetBrains'" type="success">JetBrains</n-text>
</n-button>
</n-button-group>
<n-button
type="primary"
@@ -52,12 +55,38 @@ onMounted(() => {
<RefreshIcon />
</n-icon>
</n-button>
<n-button type="default" secondary circle @click="() => (showModal = true)">
<n-icon>
<AlertIcon />
</n-icon>
</n-button>
</n-flex>
<n-flex size="small" vertical>
<div v-for="project of reverseProjects" :key="project.path" class="project-card">
<ProjectCard :project />
</div>
</n-flex>
<n-modal v-model:show="showModal" preset="card" title="管理项目?">
<n-p>
代码启动台仅在此罗列找到的项目
<n-text type="warning" strong>您无法在这里管理删除某个项目</n-text>
</n-p>
<n-p>
你可以在这里选择用其他 IDE 打开一个项目但是并非所有 IDE 都支持某些特殊 URI 的项目 例如
<n-code inline>vscode-remote://</n-code>
协议是
<n-text strong type="info">VS Code 远程项目</n-text>
的协议你无法使用 JetBrains IDEs 打开此协议的项目哪怕它们可能运行在
<n-text type="success" strong>WSL</n-text>
</n-p>
<template #footer>
<a href="https://gitea.mangofanfan.cn/MangoFanFanw/FanTools/wiki" target="_blank">
<n-button secondary type="primary"> FanTools.wiki 中查看</n-button>
</a>
</template>
</n-modal>
</template>
<style scoped>

View File

@@ -3,6 +3,7 @@
<template>
<n-h1 prefix="bar"><n-text type="success">FanTools - 芒果工具箱</n-text></n-h1>
<n-h2>主页</n-h2>
<n-h3>简介</n-h3>
<n-p
>FanTools或者芒果工具箱是一个使用 Electron 技术开发的 Windows
桌面应用程序提供一些工具功能从名称上也能看得出来</n-p
@@ -12,4 +13,12 @@
>窗口右下角的浮动按钮是<n-text strong type="success">保存</n-text
>设置需要保存之后才能生效部分设置还需要重启工具箱来生效如果你改动了设置记得保存</n-p
>
<n-h3>包含工具</n-h3>
<n-p>左侧边栏中列出了所有工具</n-p>
<n-p>
对于未来的更多工具开发计划不会考虑添加已经由
<n-text strong type="info">PowerToys</n-text>
或其他知名同类软件实现的功能
</n-p>
</template>