Compare commits
17 Commits
d726f9f781
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 8b54d44c6e | |||
| b7d4d9e8a1 | |||
| e505c03952 | |||
| 5fcbc3d826 | |||
| 9eb44cac9c | |||
| 4730f7c948 | |||
| 00b5ed9a79 | |||
| aa16a81d8c | |||
| 615dd44129 | |||
| 6002ac1418 | |||
| cabb2f65da | |||
| 4a87702694 | |||
| b9d6f8b049 | |||
| 7a7e58b3ee | |||
| e568a2dfaa | |||
| c714f554ac | |||
| 1bf08a3698 |
1
.idea/dictionaries/project.xml
generated
1
.idea/dictionaries/project.xml
generated
@@ -1,6 +1,7 @@
|
||||
<component name="ProjectDictionaryState">
|
||||
<dictionary name="project">
|
||||
<words>
|
||||
<w>globalstorage</w>
|
||||
<w>scrollarea</w>
|
||||
</words>
|
||||
</dictionary>
|
||||
|
||||
11
.idea/inspectionProfiles/Project_Default.xml
generated
11
.idea/inspectionProfiles/Project_Default.xml
generated
@@ -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
6
.idea/jsLinters/eslint.xml
generated
Normal 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
21
LICENSE.txt
Normal 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
25
clone_wiki.sh
Normal 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
279
package-lock.json
generated
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
93
src/main/code-launchpad/code-launchpad.ts
Normal file
93
src/main/code-launchpad/code-launchpad.ts
Normal 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
|
||||
}
|
||||
178
src/main/code-launchpad/ide-projects.ts
Normal file
178
src/main/code-launchpad/ide-projects.ts
Normal 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
|
||||
}
|
||||
@@ -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> {
|
||||
// 构建数据结构的辅助函数
|
||||
@@ -58,11 +53,11 @@ async function checkJetBrainsIDEs(): Promise<checkIDEsResultDto> {
|
||||
}
|
||||
}
|
||||
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
|
||||
}
|
||||
29
src/main/code-launchpad/project-git.ts
Normal file
29
src/main/code-launchpad/project-git.ts
Normal 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
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
})
|
||||
|
||||
11
src/my-type/dataFormatter.ts
Normal file
11
src/my-type/dataFormatter.ts
Normal 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
|
||||
})
|
||||
}
|
||||
@@ -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
22
src/my-type/ide-projects.d.ts
vendored
Normal 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
|
||||
}
|
||||
32
src/my-type/jetbrains-ide-options-recentProjects.d.ts
vendored
Normal file
32
src/my-type/jetbrains-ide-options-recentProjects.d.ts
vendored
Normal 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'
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
10
src/my-type/settings.d.ts
vendored
10
src/my-type/settings.d.ts
vendored
@@ -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>>
|
||||
|
||||
9
src/my-type/vscode-globalstorage-json.d.ts
vendored
Normal file
9
src/my-type/vscode-globalstorage-json.d.ts
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
export interface VSCodeGlobalStorageJson {
|
||||
profileAssociations: {
|
||||
/** 此键值对中项目越靠后,时间越新,我估计是的。<br/>
|
||||
* key:远程开发链接为 `vscode-remote://`,本地文件(目录)为 `file:///`。<br/>
|
||||
* value:难说到底有没有用,`__default__profile__`。
|
||||
*/
|
||||
workspaces: Record<string, string>
|
||||
}
|
||||
}
|
||||
19
src/preload/index.d.ts
vendored
19
src/preload/index.d.ts
vendored
@@ -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>
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
10
src/renderer/components.d.ts
vendored
10
src/renderer/components.d.ts
vendored
@@ -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']
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
// 滚动区域
|
||||
|
||||
@@ -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>
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
130
src/renderer/src/components-code-launchpad/ProjectCard.vue
Normal file
130
src/renderer/src/components-code-launchpad/ProjectCard.vue
Normal 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>
|
||||
54
src/renderer/src/components-code-launchpad/ProjectGit.vue
Normal file
54
src/renderer/src/components-code-launchpad/ProjectGit.vue
Normal 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>
|
||||
@@ -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>
|
||||
104
src/renderer/src/components/CoderBaseCard.vue
Normal file
104
src/renderer/src/components/CoderBaseCard.vue
Normal 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>
|
||||
@@ -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>
|
||||
|
||||
30
src/renderer/src/components/CoderXml.vue
Normal file
30
src/renderer/src/components/CoderXml.vue
Normal 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>
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
70
src/renderer/src/components/NavigatorBar.vue
Normal file
70
src/renderer/src/components/NavigatorBar.vue
Normal 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>
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ const IDESearchMethodOptions = [
|
||||
>
|
||||
</template>
|
||||
<template #default>
|
||||
<DetectedIDECardList :ides="ides" :versions />
|
||||
<DetectedIDECardList :ides :versions />
|
||||
</template>
|
||||
<template #action>
|
||||
<n-p
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 }
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user