Compare commits

...

19 Commits

Author SHA1 Message Date
8b54d44c6e 添加 wiki 克隆脚本 2026-04-01 14:08:19 +00:00
b7d4d9e8a1 读取 git 仓库数据(如有)并显示 2026-03-30 23:27:50 +08:00
e505c03952 主窗口边栏底部添加退出按钮 2026-03-30 21:33:51 +08:00
5fcbc3d826 减小 IDECard 卡片尺寸 2026-03-30 16:45:23 +08:00
9eb44cac9c Vue devtools 2026-03-29 23:31:17 +08:00
4730f7c948 实现 JB 项目时间戳、排序。窗口无边框。 2026-03-29 23:31:03 +08:00
00b5ed9a79 移动代码 2026-03-29 16:40:49 +08:00
aa16a81d8c MIT LICENSE.txt 2026-03-29 14:41:40 +08:00
615dd44129 程序的主页要怎么写? 2026-03-29 14:38:04 +08:00
6002ac1418 完成启动项目功能、排查一些灵异问题 2026-03-29 00:26:49 +08:00
cabb2f65da bug fix 2026-03-28 18:38:24 +08:00
4a87702694 在 CodeLaunchpad 里整一个关闭按钮 2026-03-27 23:29:30 +08:00
b9d6f8b049 更新 WebStorm 工作区配置 2026-03-27 22:50:28 +08:00
7a7e58b3ee 代码启动台窗口实现项目列表 2026-03-27 22:50:05 +08:00
e568a2dfaa 主进程实现对 VSCode 和 JetBrains 项目的基本查找 2026-03-27 22:49:27 +08:00
c714f554ac 增加 xml <=> 编解码工具 2026-03-27 13:01:21 +08:00
1bf08a3698 移动并重命名 code-launchpad.ts 2026-03-26 13:21:17 +08:00
d726f9f781 文档/百科 2026-03-26 12:55:54 +08:00
31de2dbae5 奇怪的调用错误 2026-03-26 11:18:56 +08:00
46 changed files with 1570 additions and 270 deletions

View File

@@ -1,6 +1,7 @@
<component name="ProjectDictionaryState">
<dictionary name="project">
<words>
<w>globalstorage</w>
<w>scrollarea</w>
</words>
</dictionary>

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">

6
.idea/jsLinters/eslint.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="EslintConfiguration">
<option name="fix-on-save" value="true" />
</component>
</project>

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.

View File

@@ -6,6 +6,12 @@
这应该是芒果工具箱的第三个(?)迭代了~
## 文档/百科
我可能想不起来及时更新 README.md但是我一定会记得及时更新**文档**,也就是**百科**~
百科入口位于本仓库的一个标签页中,找找看吧!
## 推荐 IDE
创建项目的模板默认推荐使用 Visual Studio Code但是我还是更习惯 WebStorm。

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"

279
package-lock.json generated
View File

@@ -12,10 +12,13 @@
"@electron-toolkit/preload": "^3.0.2",
"@electron-toolkit/utils": "^4.0.0",
"electron-updater": "^6.3.9",
"fast-xml-parser": "^5.5.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"
},
@@ -35,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",
@@ -1761,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",
@@ -4271,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",
@@ -4976,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",
@@ -5708,6 +5736,41 @@
"dev": true,
"license": "MIT"
},
"node_modules/fast-xml-builder": {
"version": "1.1.4",
"resolved": "https://registry.npmmirror.com/fast-xml-builder/-/fast-xml-builder-1.1.4.tgz",
"integrity": "sha512-f2jhpN4Eccy0/Uz9csxh3Nu6q4ErKxf0XIsasomfOihuSUa3/xw6w8dnOtCDgEItQFJG8KyXPzQXzcODDrrbOg==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/NaturalIntelligence"
}
],
"license": "MIT",
"dependencies": {
"path-expression-matcher": "^1.1.3"
}
},
"node_modules/fast-xml-parser": {
"version": "5.5.9",
"resolved": "https://registry.npmmirror.com/fast-xml-parser/-/fast-xml-parser-5.5.9.tgz",
"integrity": "sha512-jldvxr1MC6rtiZKgrFnDSvT8xuH+eJqxqOBThUVjYrxssYTo1avZLGql5l0a0BAERR01CadYzZ83kVEkbyDg+g==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/NaturalIntelligence"
}
],
"license": "MIT",
"dependencies": {
"fast-xml-builder": "^1.1.4",
"path-expression-matcher": "^1.2.0",
"strnum": "^2.2.2"
},
"bin": {
"fxparser": "src/cli/cli.js"
}
},
"node_modules/fd-slicer": {
"version": "1.1.0",
"resolved": "https://registry.npmmirror.com/fd-slicer/-/fd-slicer-1.1.0.tgz",
@@ -6399,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",
@@ -6541,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",
@@ -6689,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",
@@ -6718,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",
@@ -6878,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",
@@ -6908,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",
@@ -7154,7 +7331,6 @@
"integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"minimist": "^1.2.6"
},
@@ -7567,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",
@@ -7597,6 +7780,21 @@
"node": ">=8"
}
},
"node_modules/path-expression-matcher": {
"version": "1.2.0",
"resolved": "https://registry.npmmirror.com/path-expression-matcher/-/path-expression-matcher-1.2.0.tgz",
"integrity": "sha512-DwmPWeFn+tq7TiyJ2CxezCAirXjFxvaiD03npak3cRjlP9+OjTmSy1EpIrEbh+l6JgUundniloMLDQ/6VTdhLQ==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/NaturalIntelligence"
}
],
"license": "MIT",
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/path-is-absolute": {
"version": "1.0.1",
"resolved": "https://registry.npmmirror.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
@@ -7872,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",
@@ -7927,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",
@@ -8645,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",
@@ -8675,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",
@@ -8923,6 +9159,18 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/strnum": {
"version": "2.2.2",
"resolved": "https://registry.npmmirror.com/strnum/-/strnum-2.2.2.tgz",
"integrity": "sha512-DnR90I+jtXNSTXWdwrEy9FakW7UX+qUZg28gj5fk2vxxl7uS/3bpI4fjFYVmdK9etptYBPNkpahuQnEwhwECqA==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/NaturalIntelligence"
}
],
"license": "MIT"
},
"node_modules/sumchecker": {
"version": "3.0.1",
"resolved": "https://registry.npmmirror.com/sumchecker/-/sumchecker-3.0.1.tgz",
@@ -9290,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",
@@ -9434,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",
@@ -10386,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

@@ -25,10 +25,13 @@
"@electron-toolkit/preload": "^3.0.2",
"@electron-toolkit/utils": "^4.0.0",
"electron-updater": "^6.3.9",
"fast-xml-parser": "^5.5.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"
},
@@ -48,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

@@ -0,0 +1,178 @@
import path from 'path'
import fs from 'fs/promises'
import os from 'os'
import type { IdeProjectsDto } from '@my-type/ide-projects'
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 { checkIDEsResultDto, IDECode } from '@my-type/settings'
import { spawn } from 'node:child_process'
const xmlParser = new XMLParser({ ignoreAttributes: false })
// VSCode 用来保存打开过的工作区的文件路径
const VSCODE_GLOBALSTORAGE_PATH = path.join(
os.homedir(),
'AppData/Roaming/Code/User/globalStorage/storage.json'
)
// JetBrains IDEs 的默认数据保存目录
// 每个 IDE 的每个版本都在该目录中拥有一个子目录
const JETBRAINS_IDES_DATA_PATH = path.join(os.homedir(), 'AppData/Roaming/JetBrains')
/**
* 从 VSCode 的 GlobalStorage 中读取所有打开过的工作区,整理后返回。
*/
export async function getVscodeProjects(): Promise<IdeProjectsDto> {
console.log('查找 VSCode 项目中')
const result: IdeProjectsDto = []
const data: VSCodeGlobalStorageJson = await loadJsonFile(VSCODE_GLOBALSTORAGE_PATH)
for (const workspace of Object.entries(data.profileAssociations.workspaces)) {
// VSCode 存储的工作区的一个示例:
// "vscode-remote://wsl%2Bubuntu-24.04/home/mango/pythonTest123": "__default__profile__"
// 目前不清楚值的具体含义,但显然值对我们没有帮助。
// 所以,将 path (项目路径)设置为键,然后取路径的最后一层目录为名称,构建数据并返回。
// JetBrains IDEs 的终端调用不支持 file:/// 命令,因此在此将协议名 file:/// 去除,方便用 JetBrains IDEs 打开它们。
// 还有一种协议名是 vscode-remote:// 将会保留,以作为 VSCode Remote 的标识
const path = decodeURIComponent(workspace[0]).replace('file:///', '')
const name = <string>path.split('/').at(-1)
result.push({ name, path, timestamp: 0, ide: ['VSC'] })
}
return result
}
export async function getJetBrainsProjects(): Promise<IdeProjectsDto> {
console.log('查找 JetBrains IDEs 项目中')
const result: IdeProjectsDto = []
// 意外的是JetBrains Toolbox 并不会自己保存 JetBrains IDEs 打开过的项目的历史记录,哪怕是在 Toolbox 中打开的。
// 据 AI 总结,工具箱的项目列表系读取已安装的所有 JetBrains IDE 的项目历史,并综合列出的。
// 所以,我们也要这么做。
const items = await fs.readdir(JETBRAINS_IDES_DATA_PATH, { withFileTypes: true })
const subDirs: string[] = []
// 只要目录,不要文件
for (const item of items) {
if (item.isDirectory()) {
subDirs.push(item.name)
}
}
// 创建工具函数
const _ = (subDir: string): JetBrainsProductCode | null => {
// 获取枚举成员的变量名称
for (const ide in JetBrainsIDEDisplayNameEnum) {
if (subDir.toLowerCase().includes(ide)) {
// 查找与之对应的产品代码并返回
// 由于已经做过判定,所以可得返回结果非空
return <JetBrainsProductCode>toProductCode(ide)
}
}
return null
}
for (const subDir of subDirs) {
// 如果目录不受支持,直接排除
// 不受支持的表现之一就是目录名称中不包含工具箱支持的 IDE 的名称
const ide = _(subDir)
if (ide === null) continue
// 从目录中尝试读取 options/recentProjects.xml
const rpp = path.join(JETBRAINS_IDES_DATA_PATH, subDir, 'options/recentProjects.xml')
try {
// 从 xml 中解析数据
const data: JetBrainsIdeOptionsRecentProjects = xmlParser.parse(
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
// 去重。
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 {
// 忽略
}
}
return result
}
/**
* 结合 {@link getVscodeProjects} 和 {@link getJetBrainsProjects} 的返回结果,
* 剔除重复项后,获取项目列表。
*/
export async function getProjects(): Promise<IdeProjectsDto> {
const result: IdeProjectsDto = []
return result
}
/**
* 使用指定 IDE 打开指定项目
* @param ide IDE 代码
* @param path 项目路径
*/
export function openProject(ide: IDECode, path: string): boolean {
if (global.installedIDEs === null) return false
// 考虑到安全性问题,这里限制 IDE 代码,只有提供合法的 IDE 代码才能执行命令打开项目。
for (const ideInfo of Object.values(global.installedIDEs as checkIDEsResultDto)) {
if (ideInfo !== null) {
if (ide === ideInfo.code) {
try {
const params: string[] = []
// 如果是使用 VSCode 打开远程项目WSL
if (ide === 'VSC' && path.startsWith('vscode-remote://')) {
// 拼接正确命令
params.push('--folder-uri', path)
}
// TODO 处理更多边界情况
else {
params.push(path)
}
// 避免阻塞当前进程
console.log(`打开项目:尝试执行:${ideInfo.command} ${params.join(' ')}`)
spawn(ideInfo.command, params, { detached: true, stdio: 'ignore', shell: true })
return true
} catch (error) {
console.error(`使用 IDE ${ide} 打开 ${path} 时出现错误。`)
console.error(error)
return false
}
}
}
}
console.log(`打开项目:提供的 IDE 代号不合法:${ide} ${path}`)
return false
}

View File

@@ -2,21 +2,20 @@ import type {
checkIDEResultDto,
checkIDEsResultDto,
checkIDEsVersionDto,
checkIDEVersionDto
checkIDEVersionDto,
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 { settingsManager } from '../settings'
import { isNodeError } from '@my-type/node-error'
import { JetBrainsDataProductDto } from '@my-type/jetbrains-data-products'
@@ -31,11 +30,7 @@ const JETBRAINS_TOOLBOX_STATE_JSON_PATH = path.join(
* @param command IDE `code`
* @param code JetBrains IDE IDE
*/
function checkIDE(
display: string,
command: string,
code: JetBrainsProductCode | '' = ''
): checkIDEResultDto | null {
function checkIDE(display: string, command: string, code: IDECode): checkIDEResultDto | null {
try {
const paths = execSync(`where.exe ${command}`).toString().split('\n').slice(0, -1)
return { code, display: display, command: command, paths: paths }
@@ -45,7 +40,7 @@ function checkIDE(
}
/**
* JerBrains IDEs
* JetBrains IDEs
*/
async function checkJetBrainsIDEs(): Promise<checkIDEsResultDto> {
// 构建数据结构的辅助函数
@@ -53,16 +48,16 @@ async function checkJetBrainsIDEs(): Promise<checkIDEsResultDto> {
return {
code,
command,
display: JIN[code],
display: JIN[command],
paths: []
}
}
const result: checkIDEsResultDto = {
pycharm: _('pycharm', 'PY'),
clion: _('clion', 'CL'),
webstorm: _('webstorm', 'WS'),
phpstorm: _('phpstorm', 'PS'),
idea: _('idea', 'IU')
PY: _('pycharm', 'PY'),
CL: _('clion', 'CL'),
WS: _('webstorm', 'WS'),
PS: _('phpstorm', 'PS'),
IU: _('idea', 'IU')
}
// 优先从 JBTState.json 查找
if (settingsManager._settings?.codeLaunchpadIDESearchPolicy.includes('JBTState.json')) {
@@ -115,7 +110,7 @@ export async function getIDEs(): Promise<checkIDEsResultDto> {
export async function checkIDEs(): Promise<checkIDEsResultDto> {
console.log('在系统中查找已安装的 IDE ...')
const vscodeIDEs = {
vscode: checkIDE('Visual Studio Code', 'code')
VSC: checkIDE('Visual Studio Code', 'code', 'VSC')
}
global.installedIDEs = { ...vscodeIDEs, ...(await checkJetBrainsIDEs()) }
return global.installedIDEs
@@ -135,7 +130,7 @@ async function checkVSCodeVersion(): Promise<checkIDEVersionDto> {
console.error('获取 VSCode 版本列表时出现错误。错误提供如下。')
console.error(error)
}
return { code: '', install, latest, display: 'Visual Studio Code' }
return { code: 'VSC', install, latest, display: 'Visual Studio Code' }
}
/**
@@ -143,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 = {
pycharm: _('pycharm', 'PY'),
clion: _('clion', 'CL'),
webstorm: _('webstorm', 'WS'),
phpstorm: _('phpstorm', 'PS'),
idea: _('idea', 'IU')
PY: _('PY'),
CL: _('CL'),
WS: _('WS'),
PS: _('PS'),
IU: _('IU')
}
// 尝试从 JBTState.json 获取已安装的 JetBrains IDEs 的版本
@@ -227,8 +222,8 @@ export async function checkIDEsVersion(): Promise<checkIDEsVersionDto> {
let result: checkIDEsVersionDto = {}
for (const ide in await getIDEs()) {
switch (ide) {
case 'vscode':
result['vscode'] = await checkVSCodeVersion()
case 'VSC':
result['VSC'] = await checkVSCodeVersion()
break
}
}
@@ -236,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,16 +4,23 @@ import { saveSettingsToLocal, updateSettingsFromLocal } from './settings'
import {
checkIDEs,
checkIDEsVersion,
createCodeLaunchpadTray,
createCodeLaunchpadWindow,
getIDEs,
getIDEsVersion
} from './code-launchpad'
} 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 保存引用,禁用报错
// eslint-disable-next-line @typescript-eslint/no-unused-vars
let mainTray: Tray | null = null
// @ts-ignore 保存引用,禁用报错
// eslint-disable-next-line @typescript-eslint/no-unused-vars
let codeLaunchpadTray: Tray | null = null
/**
@@ -26,6 +33,7 @@ function createWindow(): BrowserWindow {
show: false,
autoHideMenuBar: true,
backgroundColor: '#1f1f1f',
frame: false,
icon: fanToolsIcon,
webPreferences: {
preload: path.join(__dirname, '../preload/index.mjs'),
@@ -88,10 +96,18 @@ function createTray(): Tray {
// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.whenReady().then(() => {
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
@@ -120,11 +136,36 @@ app.whenReady().then(() => {
}
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: IDECode, path: string) => {
return openProject(ide, path)
})
ipcMain.handle('codeLaunchpad:getProjectGitInfo', (_, path: string) => {
return getProjectGitInfo(path)
})
checkIDEs()
checkIDEsVersion()
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

@@ -12,6 +12,7 @@ import PyCharmIcon from '@renderer/assets/PyCharm_icon.svg?component'
import WebStormIcon from '@renderer/assets/WebStorm_icon.svg?component'
import { FunctionalComponent } from 'vue'
import { IDECode } from '@my-type/settings'
function _(
icon: FunctionalComponent,
@@ -20,11 +21,11 @@ function _(
return { icon, description }
}
export const ideIcons = {
vscode: _(VSCodeIcon, '全能的代码编辑器'),
clion: _(CLionIcon, '强大的 C 和 C++ IDE'),
idea: _(IDEAIcon, '强大的 Java IDE'),
phpstorm: _(PhpStormIcon, '强大的 PHP IDE'),
pycharm: _(PyCharmIcon, '强大的 Python IDE'),
webstorm: _(WebStormIcon, '强大的 Web IDE')
export const ideIcons: Record<IDECode, { icon: FunctionalComponent; description: string }> = {
VSC: _(VSCodeIcon, '全能的代码编辑器'),
CL: _(CLionIcon, '强大的 C 和 C++ IDE'),
IU: _(IDEAIcon, '强大的 Java IDE'),
PS: _(PhpStormIcon, '强大的 PHP IDE'),
PY: _(PyCharmIcon, '强大的 Python IDE'),
WS: _(WebStormIcon, '强大的 Web IDE')
}

22
src/my-type/ide-projects.d.ts vendored Normal file
View File

@@ -0,0 +1,22 @@
import { JetBrainsProductCode } from '@my-type/jetbrains-state-tools'
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

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

View File

@@ -25,3 +25,21 @@ export enum JetBrainsIDEDisplayNameEnum {
phpstorm = 'PhpStorm',
webstorm = 'WebStorm'
}
export function toProductCode(anything: string): JetBrainsProductCode | null {
if ([JetBrainsIDEDisplayNameEnum.pycharm, 'pycharm'].includes(anything)) return 'PY'
if ([JetBrainsIDEDisplayNameEnum.idea, 'idea'].includes(anything)) return 'IU'
if ([JetBrainsIDEDisplayNameEnum.clion, 'clion'].includes(anything)) return 'CL'
if ([JetBrainsIDEDisplayNameEnum.phpstorm, 'phpstorm'].includes(anything)) return 'PS'
if ([JetBrainsIDEDisplayNameEnum.webstorm, 'webstorm'].includes(anything)) return 'WS'
return null
}
export function toProductDisplayName(anything: string): string | null {
if (['PY', 'pycharm'].includes(anything)) return JetBrainsIDEDisplayNameEnum.pycharm
if (['IU', 'idea'].includes(anything)) return JetBrainsIDEDisplayNameEnum.idea
if (['CL', 'clion'].includes(anything)) return JetBrainsIDEDisplayNameEnum.clion
if (['PS', 'phpstorm'].includes(anything)) return JetBrainsIDEDisplayNameEnum.phpstorm
if (['WS', 'webstorm'].includes(anything)) return JetBrainsIDEDisplayNameEnum.webstorm
return null
}

View File

@@ -18,22 +18,24 @@ export interface settingsDto {
codeLaunchpadIDESearchPolicy: ideSearchPolicy[]
}
export type IDECode = JetBrainsProductCode | 'VSC'
export interface checkIDEResultDto {
// 仅针对 JetBrains 系列 IDEs 的产品代码,对于其他 IDE 统一为空字符串
code: JetBrainsProductCode | ''
code: IDECode
display: string
command: string
paths: string[]
}
export type checkIDEsResultDto = Record<string, checkIDEResultDto | null>
export type checkIDEsResultDto = Partial<Record<IDECode, checkIDEResultDto | null>>
export interface checkIDEVersionDto {
// 仅针对 JetBrains 系列 IDEs 的产品代码,对于其他 IDE 统一为空字符串
code: JetBrainsProductCode | ''
code: IDECode
display: string
install: string
latest: string
}
export type checkIDEsVersionDto = Record<string, checkIDEVersionDto | null>
export type checkIDEsVersionDto = Partial<Record<IDECode, checkIDEVersionDto | null>>

View File

@@ -0,0 +1,9 @@
export interface VSCodeGlobalStorageJson {
profileAssociations: {
/** 此键值对中项目越靠后,时间越新,我估计是的。<br/>
* key远程开发链接为 `vscode-remote://`,本地文件(目录)为 `file:///`。<br/>
* value难说到底有没有用`__default__profile__`。
*/
workspaces: Record<string, string>
}
}

View File

@@ -1,23 +1,32 @@
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 { 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>
_getIDEsVersion: () => Promise<checkIDEsVersionDto>
_checkIDEsVersion: () => Promise<checkIDEsVersionDto>
}
_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,14 +10,23 @@ 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 = {
_getIDEs: () => ipcRenderer.invoke('codeLaunchpad:getIDEs'),
_checkIDEs: () => ipcRenderer.invoke('codeLaunchpad:checkIDEs'),
_getIDEsVersion: () => ipcRenderer.invoke('codeLaunchpad:getIDEsVersion'),
_checkIDEsVersion: () => ipcRenderer.invoke('codeLaunchpad:checkIDEsVersion')
_checkIDEsVersion: () => ipcRenderer.invoke('codeLaunchpad:checkIDEsVersion'),
_getVSCodeProjects: () => ipcRenderer.invoke('codeLaunchpad:getVSCodeProjects'),
_getJetBrainsProjects: () => ipcRenderer.invoke('codeLaunchpad:getJetBrainsProjects'),
_openProject: (ide: string, path: string) =>
ipcRenderer.invoke('codeLaunchpad:openProject', ide, path),
_getProjectGitInfo: (path: string) => ipcRenderer.invoke('codeLaunchpad:getProjectGitInfo', path)
}
// Use `contextBridge` APIs to expose Electron APIs to

View File

@@ -11,16 +11,20 @@ export {}
/* prettier-ignore */
declare module 'vue' {
export interface GlobalComponents {
CoderBaseCard: typeof import('./src/components/CoderBaseCard.vue')['default']
CoderJson: typeof import('./src/components/CoderJson.vue')['default']
CoderXml: typeof import('./src/components/CoderXml.vue')['default']
DetectedIDECard: typeof import('./src/components/DetectedIDECard.vue')['default']
DetectedIDECardList: typeof import('./src/components/DetectedIDECardList.vue')['default']
DetectedIDEVersionCard: typeof import('./src/components/DetectedIDEVersionCard.vue')['default']
DetectedIDEVersionCardList: typeof import('./src/components/DetectedIDEVersionCardList.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']
NCode: typeof import('naive-ui')['NCode']
NConfigProvider: typeof import('naive-ui')['NConfigProvider']
NDropdown: typeof import('naive-ui')['NDropdown']
NEllipsis: typeof import('naive-ui')['NEllipsis']
NEmpty: typeof import('naive-ui')['NEmpty']
NFlex: typeof import('naive-ui')['NFlex']
NFloatButton: typeof import('naive-ui')['NFloatButton']
@@ -29,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

@@ -3,9 +3,11 @@ import { darkTheme, dateZhCN, zhCN } from 'naive-ui'
import hljs from 'highlight.js'
import javascript from 'highlight.js/lib/languages/javascript'
import json from 'highlight.js/lib/languages/json'
import xml from 'highlight.js/lib/languages/xml'
hljs.registerLanguage('javascript', javascript)
hljs.registerLanguage('json', json)
hljs.registerLanguage('xml', xml)
</script>
<template>

View File

@@ -1,6 +1,7 @@
<script lang="ts" setup>
import { ref } from 'vue'
import { useRouter } from 'vue-router'
import CodeLaunchpadButton from '@renderer/components-code-launchpad/CodeLaunchpadButton.vue'
const activeKey = ref('/codeLaunchpad/IDEs')
const router = useRouter()
@@ -22,25 +23,35 @@ function handleUpdateValue(key: string): void {
</script>
<template>
<div class="codeLaunchpad-container">
<n-menu
v-model:value="activeKey"
:options="codeLaunchpadMenuOptions"
mode="horizontal"
@update:value="handleUpdateValue"
/>
<div class="scrollarea">
<router-view />
<n-message-provider placement="bottom">
<div class="codeLaunchpad-container">
<n-menu
v-model:value="activeKey"
class="codeLaunchpad-menu"
:options="codeLaunchpadMenuOptions"
mode="horizontal"
@update:value="handleUpdateValue"
/>
<div class="scrollarea">
<router-view />
</div>
<CodeLaunchpadButton />
</div>
</div>
</n-message-provider>
</template>
<style lang="scss" scoped>
div.codeLaunchpad-container {
display: flex;
flex: 0 1;
flex-direction: column;
overflow: hidden;
width: 100vw;
height: 100vh;
.codeLaunchpad-menu {
height: 40px;
min-height: 40px;
}
}
</style>

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

@@ -0,0 +1,29 @@
<script setup lang="ts">
import { Close as CloseIcon } from '@vicons/ionicons5'
import { Settings16Filled as SettingsIcon } from '@vicons/fluent'
function close(): void {
window.api._closeCodeLaunchpad()
}
function openSettings(): void {
// TODO
}
</script>
<template>
<n-float-button-group :right="16" :top="16" shape="circle">
<n-float-button type="default" @click="close">
<n-icon>
<CloseIcon />
</n-icon>
</n-float-button>
<n-float-button type="default" @click="openSettings">
<n-icon>
<SettingsIcon />
</n-icon>
</n-float-button>
</n-float-button-group>
</template>
<style scoped></style>

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

@@ -0,0 +1,130 @@
<script setup lang="ts">
import { computed, ref } from 'vue'
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()
// 用指定 IDE 打开的 Dropdown
const options = [
{
label: 'VS Code',
key: 'VSC'
},
{
label: 'PyCharm',
key: 'PY'
},
{
label: 'IntelliJ IDEA',
key: 'IU'
},
{
label: 'CLion',
key: 'CL'
},
{
label: 'WebStorm',
key: 'WS'
},
{
label: 'PhpStorm',
key: 'PS'
}
]
const props = defineProps<{
project: IdeProjectDto
}>()
const showDetail = ref(false)
// 计算属性,返回一个将显示在该卡片上的项目标签列表
const ideTags = computed(() => {
const tags: string[] = []
// 如果项目路径拥有 vscode-remote:// 协议,即是 VSCode Remote 项目
if (props.project.path.startsWith('vscode-remote://')) tags.push('VS Code 远程')
// 项目使用的 IDE
for (const ide of props.project.ide) {
// 不重复添加 VSCode
if (ide === 'VSC' && tags.length === 0) tags.push('VS Code')
if (ide !== 'VSC') tags.push(toProductDisplayName(ide) as string)
}
// 如果项目路径包含 wsl+ 或 wsl.,则认为是在 Windows Subsystem of Linux 中开发的项目
if (props.project.path.includes('wsl+') || props.project.path.includes('wsl.')) tags.push('WSL')
return tags
})
async function openWithIDE(ide: string): Promise<void> {
if ((await window.codeLaunchpad._openProject(ide, props.project.path)) === true) {
message.success('项目已打开。')
} else {
message.error('项目打开失败?')
}
}
</script>
<template>
<n-card content-class="no-padding" footer-class="no-padding-top small-padding">
<template #default>
<div class="click-area" @click="() => (showDetail = true)">
<n-flex justify="left">
<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>
</n-flex>
<n-ellipsis>{{ project.path }}</n-ellipsis>
</div>
</template>
<template v-if="showDetail" #footer>
<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 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>
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,104 @@
<script lang="ts" setup>
import { computed, onMounted, ref } from 'vue'
import { useMessage } from 'naive-ui'
const message = useMessage()
const props = defineProps<{
cardTitle: string
defaultInput: string
// 人类友好格式的代码语言
codeLanguage1: 'json' | 'xml'
// 机器友好格式的代码语言
codeLanguage2: 'json' | 'xml'
// 转为人类友好格式
parser1: (input: string) => string
// 转为机器友好格式
parser2: (input: string) => string
// 转为人类友好格式别名
buttonText1?: string
// 转为机器友好格式别名
buttonText2?: string
}>()
const codingHumanFriendly = ref(true)
const inputValue = ref(props.defaultInput)
const displayValue = ref('')
const errorTip = ref('')
function generate(humanFriendly: boolean, showSuccessMessage: boolean = true): void {
try {
displayValue.value = humanFriendly
? props.parser1(inputValue.value)
: props.parser2(inputValue.value)
errorTip.value = ''
// 页面挂载时不显示成功提示
if (showSuccessMessage) {
message.success('没抱错,所以应该成功辽~')
}
} catch (error) {
if (error instanceof SyntaxError) {
message.error(error.name)
displayValue.value = ''
errorTip.value = error.name + ': ' + error.message
} else {
// TODO 还有什么可能的错误?
throw error
}
}
}
const codeLanguage = computed(() =>
codingHumanFriendly.value ? props.codeLanguage1 : props.codeLanguage2
)
onMounted(() => {
generate(true, false)
})
</script>
<template>
<n-card class="default-card" :title="cardTitle">
<template #header-extra>
<n-flex align="center" justify="center">
<n-switch v-model:value="codingHumanFriendly" size="large">
<template #checked> {{ buttonText1 ? buttonText1 : '转换为人类友好格式' }} </template>
<template #unchecked> {{ buttonText2 ? buttonText2 : '转换为机器友好格式' }} </template>
</n-switch>
<n-button secondary type="primary" @click="() => generate(codingHumanFriendly)"
>生成</n-button
>
<n-button
secondary
type="warning"
@click="
() => {
inputValue = defaultInput
codingHumanFriendly = true
generate(true)
}
"
>重置</n-button
>
<n-button secondary type="info">复制结果</n-button>
</n-flex>
</template>
<template #default>
<n-flex vertical>
<n-input v-model:value="inputValue" spellcheck="false" type="textarea" />
<div class="scrollarea code-area">
<n-code :code="displayValue" :language="codeLanguage" />
</div>
<n-alert v-if="errorTip !== ''" type="error">
{{ errorTip }}
</n-alert>
</n-flex>
</template>
</n-card>
</template>
<style scoped>
div.code-area {
max-height: 600px;
}
</style>

View File

@@ -1,77 +1,16 @@
<script lang="ts" setup>
import { onMounted, ref } from 'vue'
import { useMessage } from 'naive-ui'
const message = useMessage()
const defaultInput =
'{"name":"Code Space","author":1,"url":"https://code.mangofanfan.cn","users":[{"name":"MangoFanFanw","age":18,"male":true}]}'
const codingHumanFriendly = ref(true)
const inputValue = ref(defaultInput)
const displayValue = ref('')
const errorTip = ref('')
function generate(humanFriendly: boolean): void {
try {
displayValue.value = JSON.stringify(JSON.parse(inputValue.value), null, humanFriendly ? 4 : 0)
errorTip.value = ''
} catch (error) {
if (error instanceof SyntaxError) {
message.error('JSON 解析错误,请复制完整并检查是否合法。')
displayValue.value = ''
errorTip.value = error.message
}
}
}
onMounted(() => {
generate(true)
})
import CoderBaseCard from '@renderer/components/CoderBaseCard.vue'
</script>
<template>
<n-card class="default-card" title="json 格式化/压缩">
<template #header-extra>
<n-flex align="center" justify="center">
<n-switch v-model:value="codingHumanFriendly" size="large">
<template #checked> 转换为人类友好格式 </template>
<template #unchecked> 转换为机器友好格式 </template>
</n-switch>
<n-button secondary type="primary" @click="() => generate(codingHumanFriendly)"
>生成</n-button
>
<n-button
secondary
type="warning"
@click="
() => {
inputValue = defaultInput
codingHumanFriendly = true
generate(true)
}
"
>重置</n-button
>
<n-button secondary type="info">复制结果</n-button>
</n-flex>
</template>
<template #default>
<n-flex vertical>
<n-input v-model:value="inputValue" spellcheck="false" type="textarea" />
<div class="scrollarea code-area">
<n-code :code="displayValue" language="json" />
</div>
<n-alert v-if="errorTip !== ''" type="error">
{{ errorTip }}
</n-alert>
</n-flex>
</template>
</n-card>
<CoderBaseCard
card-title="json 格式化/压缩"
default-input='{"name":"Code Space","author":1,"url":"https://code.mangofanfan.cn","users":[{"name":"MangoFanFanw","age":18,"male":true}]}'
code-language1="json"
code-language2="json"
:parser1="(code: string) => JSON.stringify(JSON.parse(code), null, 4)"
:parser2="(code: string) => JSON.stringify(JSON.parse(code), null, 0)"
/>
</template>
<style scoped>
div.code-area {
max-height: 600px;
}
</style>
<style scoped></style>

View File

@@ -0,0 +1,30 @@
<script setup lang="ts">
import CoderBaseCard from '@renderer/components/CoderBaseCard.vue'
import { XMLBuilder, XMLParser } from 'fast-xml-parser'
const xmlParser = new XMLParser({ ignoreAttributes: false })
const xmlBuilder = new XMLBuilder({ format: true, indentBy: ' ', ignoreAttributes: false })
function parser1(code: string): string {
return JSON.stringify(xmlParser.parse(code), null, 4)
}
function parser2(code: string): string {
return xmlBuilder.build(JSON.parse(code))
}
</script>
<template>
<CoderBaseCard
card-title="xml <=> json"
default-input='<?xml version="1.0" encoding="UTF-8"?><bookstore><book category="cooking"><title lang="en">Everyday Italian</title><author>Giada De Laurentiis</author><year>2005</year><price>30.00</price></book><book category="children"><title lang="en">Harry Potter</title><author>J K. Rowling</author><year>2005</year><price>29.99</price></book></bookstore>'
code-language1="json"
code-language2="xml"
:parser1
:parser2
button-text1="xml => json"
button-text2="json => xml"
/>
</template>
<style scoped></style>

View File

@@ -3,14 +3,14 @@ import { checkIDEResultDto, checkIDEVersionDto } from '@my-type/settings'
import { ideIcons } from '@my-type/ide-icons'
defineProps<{
info: checkIDEResultDto | null
version: checkIDEVersionDto | null
info: checkIDEResultDto | null | undefined
version: checkIDEVersionDto | null | undefined
name: string
}>()
</script>
<template>
<n-card v-if="info !== null">
<n-card v-if="info !== null && info !== undefined">
<template #header>
<n-flex justify="left">
<n-icon :component="ideIcons[name]['icon']" size="28"></n-icon>

View File

@@ -2,7 +2,7 @@
import { checkIDEsResultDto, checkIDEsVersionDto } from '@my-type/settings'
import DetectedIDECard from '@renderer/components/DetectedIDECard.vue'
const props = defineProps<{
defineProps<{
ides: checkIDEsResultDto | null
versions: checkIDEsVersionDto
}>()
@@ -12,7 +12,7 @@ const props = defineProps<{
<div class="scrollarea detected-ide-card-list">
<n-flex v-if="ides !== null" vertical>
<DetectedIDECard
v-for="(info, name) in props.ides"
v-for="(info, name) in ides"
:key="name"
:info
:name

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

@@ -1,5 +1,101 @@
<script setup lang="ts"></script>
<script setup lang="ts">
import ProjectCard from '@renderer/components-code-launchpad/ProjectCard.vue'
import { useProjects } from '@renderer/stores'
import { computed, onMounted, ref } from 'vue'
import { AlertOutline as AlertIcon, RefreshOutline as RefreshIcon } from '@vicons/ionicons5'
<template></template>
const projects = useProjects()
<style scoped></style>
const ide = ref<'VSCode' | 'JetBrains'>('VSCode')
const showModal = ref(false)
// 此 switch-case 结构已经触及所有情况
// eslint-disable-next-line vue/return-in-computed-property
const reverseProjects = computed(() => {
switch (ide.value) {
case 'VSCode':
return projects.vscodeProjects.toReversed()
case 'JetBrains':
return projects.jetBrainsProjects.toSorted((a, b) => (a.timestamp > b.timestamp ? -1 : 1))
}
})
onMounted(() => {
if (projects.vscodeProjects.length === 0) {
projects.getVSCodeProjects()
}
if (projects.jetBrainsProjects.length === 0) {
projects.getJetBrainsProjects()
}
})
</script>
<template>
<n-flex>
<n-button-group class="ide-button-group">
<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"
secondary
circle
@click="
() => {
projects.getVSCodeProjects()
projects.getJetBrainsProjects()
}
"
>
<n-icon>
<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>
.ide-button-group {
margin-bottom: 8px;
margin-left: 10px;
}
.project-card {
margin: 0 4px;
}
</style>

View File

@@ -7,9 +7,9 @@ import CodeLaunchpadPageDisplay from '@renderer/pages/CodeLaunchpadPageDisplay.v
const IDEs = useIDEs()
onMounted(() => {
IDEs.getIDEs()
IDEs.getVersions()
onMounted(async () => {
await IDEs.getIDEs()
await IDEs.getVersions()
})
</script>

View File

@@ -39,7 +39,7 @@ const IDESearchMethodOptions = [
>
</template>
<template #default>
<DetectedIDECardList :ides="ides" :versions />
<DetectedIDECardList :ides :versions />
</template>
<template #action>
<n-p

View File

@@ -31,14 +31,6 @@ async function openCodeLaunchpad(): Promise<void> {
message.error('啊哦?出问题了,请检查。')
}
}
async function closeCodeLaunchpad(): Promise<void> {
if (await window.api._closeCodeLaunchpad()) {
message.success('如你所愿,代码启动台已关闭。')
} else {
message.error('代码启动台真的打开了吗?')
}
}
</script>
<template>
@@ -51,7 +43,6 @@ async function closeCodeLaunchpad(): Promise<void> {
>
<n-space>
<n-button @click="openCodeLaunchpad">我现在就要打开代码启动台</n-button>
<n-button @click="closeCodeLaunchpad">关闭已打开的代码启动台</n-button>
</n-space>
</setting-card>
<n-p>使用上面的按钮打开的代码启动台只能通过上面的按钮再将其关闭方便你测试效果</n-p>

View File

@@ -1,5 +1,6 @@
<script lang="ts" setup>
import CoderJson from '@renderer/components/CoderJson.vue'
import CoderXml from '@renderer/components/CoderXml.vue'
</script>
<template>
@@ -10,6 +11,7 @@ import CoderJson from '@renderer/components/CoderJson.vue'
</n-alert>
<CoderJson />
<CoderXml />
</template>
<style scoped></style>

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>

View File

@@ -7,6 +7,7 @@ import type {
keyboardShortcut,
screenPosition
} from '@my-type/settings'
import { IdeProjectDto } from '@my-type/ide-projects'
export const useSettings = defineStore('settings', () => {
const isStayInTray = ref(false)
@@ -101,3 +102,18 @@ export const useIDEs = defineStore('IDEs', () => {
return { ides, versions, getIDEs, checkIDEs, getVersions, checkVersions }
})
export const useProjects = defineStore('projects', () => {
const vscodeProjects = ref<IdeProjectDto[]>([])
const jetBrainsProjects = ref<IdeProjectDto[]>([])
async function getVSCodeProjects(): Promise<void> {
vscodeProjects.value = await window.codeLaunchpad._getVSCodeProjects()
}
async function getJetBrainsProjects(): Promise<void> {
jetBrainsProjects.value = await window.codeLaunchpad._getJetBrainsProjects()
}
return { vscodeProjects, jetBrainsProjects, getVSCodeProjects, getJetBrainsProjects }
})