Compare commits

...

5 Commits

Author SHA1 Message Date
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
20 changed files with 560 additions and 171 deletions

View File

@@ -1,6 +1,17 @@
<component name="InspectionProjectProfileManager"> <component name="InspectionProjectProfileManager">
<profile version="1.0"> <profile version="1.0">
<option name="myName" value="Project Default" /> <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="Eslint" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="PyPackageRequirementsInspection" enabled="true" level="WARNING" enabled_by_default="true"> <inspection_tool class="PyPackageRequirementsInspection" enabled="true" level="WARNING" enabled_by_default="true">
<option name="ignoredPackages"> <option name="ignoredPackages">

21
LICENSE.txt Normal file
View File

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

125
package-lock.json generated
View File

@@ -36,6 +36,7 @@
"@vitejs/plugin-vue": "^6.0.2", "@vitejs/plugin-vue": "^6.0.2",
"electron": "^39.2.6", "electron": "^39.2.6",
"electron-builder": "^26.0.12", "electron-builder": "^26.0.12",
"electron-devtools-installer": "^4.0.0",
"electron-vite": "^5.0.0", "electron-vite": "^5.0.0",
"eslint": "^9.39.1", "eslint": "^9.39.1",
"eslint-plugin-vue": "^10.6.2", "eslint-plugin-vue": "^10.6.2",
@@ -4272,8 +4273,7 @@
"resolved": "https://registry.npmmirror.com/core-util-is/-/core-util-is-1.0.2.tgz", "resolved": "https://registry.npmmirror.com/core-util-is/-/core-util-is-1.0.2.tgz",
"integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==", "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT"
"optional": true
}, },
"node_modules/crc": { "node_modules/crc": {
"version": "3.8.0", "version": "3.8.0",
@@ -4977,6 +4977,16 @@
"node": ">= 10.0.0" "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": { "node_modules/electron-publish": {
"version": "26.8.1", "version": "26.8.1",
"resolved": "https://registry.npmmirror.com/electron-publish/-/electron-publish-26.8.1.tgz", "resolved": "https://registry.npmmirror.com/electron-publish/-/electron-publish-26.8.1.tgz",
@@ -6435,6 +6445,13 @@
"node": ">= 4" "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": { "node_modules/immutable": {
"version": "5.1.5", "version": "5.1.5",
"resolved": "https://registry.npmmirror.com/immutable/-/immutable-5.1.5.tgz", "resolved": "https://registry.npmmirror.com/immutable/-/immutable-5.1.5.tgz",
@@ -6577,6 +6594,13 @@
"url": "https://github.com/sponsors/mesqueeb" "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": { "node_modules/isbinaryfile": {
"version": "5.0.7", "version": "5.0.7",
"resolved": "https://registry.npmmirror.com/isbinaryfile/-/isbinaryfile-5.0.7.tgz", "resolved": "https://registry.npmmirror.com/isbinaryfile/-/isbinaryfile-5.0.7.tgz",
@@ -6725,6 +6749,52 @@
"graceful-fs": "^4.1.6" "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": { "node_modules/keyv": {
"version": "4.5.4", "version": "4.5.4",
"resolved": "https://registry.npmmirror.com/keyv/-/keyv-4.5.4.tgz", "resolved": "https://registry.npmmirror.com/keyv/-/keyv-4.5.4.tgz",
@@ -6754,6 +6824,16 @@
"node": ">= 0.8.0" "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/load-json-file": { "node_modules/load-json-file": {
"version": "7.0.1", "version": "7.0.1",
"resolved": "https://registry.npmmirror.com/load-json-file/-/load-json-file-7.0.1.tgz", "resolved": "https://registry.npmmirror.com/load-json-file/-/load-json-file-7.0.1.tgz",
@@ -7190,7 +7270,6 @@
"integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"minimist": "^1.2.6" "minimist": "^1.2.6"
}, },
@@ -7603,6 +7682,13 @@
"dev": true, "dev": true,
"license": "BlueOak-1.0.0" "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": { "node_modules/parent-module": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmmirror.com/parent-module/-/parent-module-1.0.1.tgz", "resolved": "https://registry.npmmirror.com/parent-module/-/parent-module-1.0.1.tgz",
@@ -7923,6 +8009,13 @@
"node": "^18.17.0 || >=20.5.0" "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": { "node_modules/progress": {
"version": "2.0.3", "version": "2.0.3",
"resolved": "https://registry.npmmirror.com/progress/-/progress-2.0.3.tgz", "resolved": "https://registry.npmmirror.com/progress/-/progress-2.0.3.tgz",
@@ -8696,6 +8789,13 @@
"url": "https://github.com/sponsors/sindresorhus" "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": { "node_modules/shebang-command": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmmirror.com/shebang-command/-/shebang-command-2.0.0.tgz", "resolved": "https://registry.npmmirror.com/shebang-command/-/shebang-command-2.0.0.tgz",
@@ -9497,6 +9597,18 @@
"url": "https://paulmillr.com/funding/" "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": { "node_modules/update-browserslist-db": {
"version": "1.2.3", "version": "1.2.3",
"resolved": "https://registry.npmmirror.com/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", "resolved": "https://registry.npmmirror.com/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz",
@@ -10449,6 +10561,13 @@
"node": ">=10" "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": { "node_modules/yallist": {
"version": "3.1.1", "version": "3.1.1",
"resolved": "https://registry.npmmirror.com/yallist/-/yallist-3.1.1.tgz", "resolved": "https://registry.npmmirror.com/yallist/-/yallist-3.1.1.tgz",

View File

@@ -49,6 +49,7 @@
"@vitejs/plugin-vue": "^6.0.2", "@vitejs/plugin-vue": "^6.0.2",
"electron": "^39.2.6", "electron": "^39.2.6",
"electron-builder": "^26.0.12", "electron-builder": "^26.0.12",
"electron-devtools-installer": "^4.0.0",
"electron-vite": "^5.0.0", "electron-vite": "^5.0.0",
"eslint": "^9.39.1", "eslint": "^9.39.1",
"eslint-plugin-vue": "^10.6.2", "eslint-plugin-vue": "^10.6.2",

View File

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

View File

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

View File

@@ -6,18 +6,16 @@ import type {
IDECode IDECode
} from '@my-type/settings' } from '@my-type/settings'
import { execSync } from 'node:child_process' import { execSync } from 'node:child_process'
import { BrowserWindow, screen, shell, Tray } from 'electron'
import { is } from '@electron-toolkit/utils'
import path from 'path' import path from 'path'
import { loadJsonFile } from 'load-json-file' import { loadJsonFile } from 'load-json-file'
import os from 'os' import os from 'os'
import { import {
JetBrainsIDEDisplayNameEnum as JIN, JetBrainsIDEDisplayNameEnum as JIN,
JetBrainsProductCode, JetBrainsProductCode,
JetBrainsStateDto JetBrainsStateDto,
toProductDisplayName
} from '@my-type/jetbrains-state-tools' } from '@my-type/jetbrains-state-tools'
import { settingsManager } from '../settings' import { settingsManager } from '../settings'
import { codeLaunchpadIcon } from '../resources'
import { isNodeError } from '@my-type/node-error' import { isNodeError } from '@my-type/node-error'
import { JetBrainsDataProductDto } from '@my-type/jetbrains-data-products' import { JetBrainsDataProductDto } from '@my-type/jetbrains-data-products'
@@ -140,20 +138,20 @@ async function checkVSCodeVersion(): Promise<checkIDEVersionDto> {
*/ */
export async function checkJetBrainsIDEsVersion(): Promise<checkIDEsVersionDto> { export async function checkJetBrainsIDEsVersion(): Promise<checkIDEsVersionDto> {
// 构建数据结构的辅助函数 // 构建数据结构的辅助函数
const _ = (display: string, code: JetBrainsProductCode): checkIDEVersionDto => { const _ = (code: JetBrainsProductCode): checkIDEVersionDto => {
return { return {
code, code,
display, display: toProductDisplayName(code) as string,
install: 'unknown', install: 'unknown',
latest: 'unknown' latest: 'unknown'
} }
} }
const result: checkIDEsVersionDto = { const result: checkIDEsVersionDto = {
PY: _('pycharm', 'PY'), PY: _('PY'),
CL: _('clion', 'CL'), CL: _('CL'),
WS: _('webstorm', 'WS'), WS: _('WS'),
PS: _('phpstorm', 'PS'), PS: _('PS'),
IU: _('idea', 'IU') IU: _('IU')
} }
// 尝试从 JBTState.json 获取已安装的 JetBrains IDEs 的版本 // 尝试从 JBTState.json 获取已安装的 JetBrains IDEs 的版本
@@ -233,91 +231,3 @@ export async function checkIDEsVersion(): Promise<checkIDEsVersionDto> {
global.installedIDEsVersion = result global.installedIDEsVersion = result
return 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

@@ -4,14 +4,15 @@ import { saveSettingsToLocal, updateSettingsFromLocal } from './settings'
import { import {
checkIDEs, checkIDEs,
checkIDEsVersion, checkIDEsVersion,
createCodeLaunchpadTray,
createCodeLaunchpadWindow,
getIDEs, getIDEs,
getIDEsVersion getIDEsVersion
} from './code-launchpad/ide-versions-check' } from './code-launchpad/ide-versions-check'
import { fanToolsIcon } from './resources' import { fanToolsIcon } from './resources'
import path from 'path' import path from 'path'
import { getJetBrainsProjects, getVscodeProjects, openProject } from './code-launchpad/ide-projects' 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'
let mainWindow: BrowserWindow | null = null let mainWindow: BrowserWindow | null = null
// @ts-ignore 保存引用,禁用报错 // @ts-ignore 保存引用,禁用报错
@@ -31,6 +32,7 @@ function createWindow(): BrowserWindow {
show: false, show: false,
autoHideMenuBar: true, autoHideMenuBar: true,
backgroundColor: '#1f1f1f', backgroundColor: '#1f1f1f',
frame: false,
icon: fanToolsIcon, icon: fanToolsIcon,
webPreferences: { webPreferences: {
preload: path.join(__dirname, '../preload/index.mjs'), preload: path.join(__dirname, '../preload/index.mjs'),
@@ -97,6 +99,14 @@ app.whenReady().then(async () => {
// Set app user model id for windows // Set app user model id for windows
electronApp.setAppUserModelId('com.electron') 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 // Default open or close DevTools by F12 in development
// and ignore CommandOrControl + R in production. // and ignore CommandOrControl + R in production.
// see https://github.com/alex8088/electron-toolkit/tree/master/packages/utils // see https://github.com/alex8088/electron-toolkit/tree/master/packages/utils
@@ -125,13 +135,26 @@ app.whenReady().then(async () => {
} }
return false 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('codeLaunchpad:getIDEs', getIDEs) ipcMain.handle('codeLaunchpad:getIDEs', getIDEs)
ipcMain.handle('codeLaunchpad:checkIDEs', checkIDEs) ipcMain.handle('codeLaunchpad:checkIDEs', checkIDEs)
ipcMain.handle('codeLaunchpad:getIDEsVersion', getIDEsVersion) ipcMain.handle('codeLaunchpad:getIDEsVersion', getIDEsVersion)
ipcMain.handle('codeLaunchpad:checkIDEsVersion', checkIDEsVersion) ipcMain.handle('codeLaunchpad:checkIDEsVersion', checkIDEsVersion)
ipcMain.handle('codeLaunchpad:getVSCodeProjects', getVscodeProjects) ipcMain.handle('codeLaunchpad:getVSCodeProjects', getVscodeProjects)
ipcMain.handle('codeLaunchpad:getJetBrainsProjects', getJetBrainsProjects) ipcMain.handle('codeLaunchpad:getJetBrainsProjects', getJetBrainsProjects)
ipcMain.handle('codeLaunchpad:openProject', (_, ide: string, path: string) => { ipcMain.handle('codeLaunchpad:openProject', (_, ide: IDECode, path: string) => {
return openProject(ide, path) return openProject(ide, path)
}) })

View File

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

View File

@@ -5,6 +5,7 @@ type Ide = JetBrainsProductCode | 'VSC'
export interface IdeProjectDto { export interface IdeProjectDto {
name: string name: string
path: string path: string
timestamp: number
ide: Ide[] ide: Ide[]
} }

View File

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

View File

@@ -13,6 +13,9 @@ declare global {
_updateSettings: () => Promise<settingsDto> _updateSettings: () => Promise<settingsDto>
_openCodeLaunchpad: () => Promise<boolean> _openCodeLaunchpad: () => Promise<boolean>
_closeCodeLaunchpad: () => Promise<boolean> _closeCodeLaunchpad: () => Promise<boolean>
_minimize: () => Promise<void>
_maximize: () => Promise<void>
_closeWindow: () => Promise<void>
} }
codeLaunchpad: { codeLaunchpad: {
_getIDEs: () => Promise<checkIDEsResultDto> _getIDEs: () => Promise<checkIDEsResultDto>

View File

@@ -1,3 +1,5 @@
// noinspection JSUnusedGlobalSymbols
import { contextBridge, ipcRenderer } from 'electron' import { contextBridge, ipcRenderer } from 'electron'
import { electronAPI } from '@electron-toolkit/preload' import { electronAPI } from '@electron-toolkit/preload'
import { settingsDto } from '@my-type/settings' import { settingsDto } from '@my-type/settings'
@@ -8,7 +10,10 @@ const api = {
_saveSettings: (settings: settingsDto) => ipcRenderer.invoke('settings:save', settings), _saveSettings: (settings: settingsDto) => ipcRenderer.invoke('settings:save', settings),
_updateSettings: () => ipcRenderer.invoke('settings:update'), _updateSettings: () => ipcRenderer.invoke('settings:update'),
_openCodeLaunchpad: () => ipcRenderer.invoke('tools:openCodeLaunchpad'), _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')
} }
const codeLaunchpadApi = { const codeLaunchpadApi = {

View File

@@ -17,6 +17,7 @@ declare module 'vue' {
DetectedIDECard: typeof import('./src/components/DetectedIDECard.vue')['default'] DetectedIDECard: typeof import('./src/components/DetectedIDECard.vue')['default']
DetectedIDECardList: typeof import('./src/components/DetectedIDECardList.vue')['default'] DetectedIDECardList: typeof import('./src/components/DetectedIDECardList.vue')['default']
NAlert: typeof import('naive-ui')['NAlert'] NAlert: typeof import('naive-ui')['NAlert']
NavigatorBar: typeof import('./src/components/NavigatorBar.vue')['default']
NButton: typeof import('naive-ui')['NButton'] NButton: typeof import('naive-ui')['NButton']
NButtonGroup: typeof import('naive-ui')['NButtonGroup'] NButtonGroup: typeof import('naive-ui')['NButtonGroup']
NCard: typeof import('naive-ui')['NCard'] NCard: typeof import('naive-ui')['NCard']
@@ -37,6 +38,7 @@ declare module 'vue' {
NInputNumber: typeof import('naive-ui')['NInputNumber'] NInputNumber: typeof import('naive-ui')['NInputNumber']
NMenu: typeof import('naive-ui')['NMenu'] NMenu: typeof import('naive-ui')['NMenu']
NMessageProvider: typeof import('naive-ui')['NMessageProvider'] NMessageProvider: typeof import('naive-ui')['NMessageProvider']
NModal: typeof import('naive-ui')['NModal']
NP: typeof import('naive-ui')['NP'] NP: typeof import('naive-ui')['NP']
NSelect: typeof import('naive-ui')['NSelect'] NSelect: typeof import('naive-ui')['NSelect']
NSpace: typeof import('naive-ui')['NSpace'] NSpace: typeof import('naive-ui')['NSpace']

View File

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

View File

@@ -10,8 +10,24 @@ body {
overflow: hidden; 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 { .no-margin-bottom {
margin-bottom: 0; margin-bottom: 0 !important;
} }
// 滚动区域 // 滚动区域

View File

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

View File

@@ -0,0 +1,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

@@ -2,11 +2,12 @@
import ProjectCard from '@renderer/components-code-launchpad/ProjectCard.vue' import ProjectCard from '@renderer/components-code-launchpad/ProjectCard.vue'
import { useProjects } from '@renderer/stores' import { useProjects } from '@renderer/stores'
import { computed, onMounted, ref } from 'vue' import { computed, onMounted, ref } from 'vue'
import { RefreshOutline as RefreshIcon } from '@vicons/ionicons5' import { AlertOutline as AlertIcon, RefreshOutline as RefreshIcon } from '@vicons/ionicons5'
const projects = useProjects() const projects = useProjects()
const ide = ref<'VSCode' | 'JetBrains'>('VSCode') const ide = ref<'VSCode' | 'JetBrains'>('VSCode')
const showModal = ref(false)
// 此 switch-case 结构已经触及所有情况 // 此 switch-case 结构已经触及所有情况
// eslint-disable-next-line vue/return-in-computed-property // eslint-disable-next-line vue/return-in-computed-property
@@ -15,7 +16,7 @@ const reverseProjects = computed(() => {
case 'VSCode': case 'VSCode':
return projects.vscodeProjects.toReversed() return projects.vscodeProjects.toReversed()
case 'JetBrains': case 'JetBrains':
return projects.jetBrainsProjects.toReversed() return projects.jetBrainsProjects.toSorted((a, b) => (a.timestamp > b.timestamp ? -1 : 1))
} }
}) })
@@ -32,10 +33,12 @@ onMounted(() => {
<template> <template>
<n-flex> <n-flex>
<n-button-group class="ide-button-group"> <n-button-group class="ide-button-group">
<n-button type="primary" secondary round @click="() => (ide = 'VSCode')">VS Code</n-button> <n-button type="primary" secondary round @click="() => (ide = 'VSCode')">
<n-button type="primary" secondary round @click="() => (ide = 'JetBrains')" <n-text :strong="ide === 'VSCode'" type="success">VS Code</n-text>
>JetBrains</n-button </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-group>
<n-button <n-button
type="primary" type="primary"
@@ -52,12 +55,38 @@ onMounted(() => {
<RefreshIcon /> <RefreshIcon />
</n-icon> </n-icon>
</n-button> </n-button>
<n-button type="default" secondary circle @click="() => (showModal = true)">
<n-icon>
<AlertIcon />
</n-icon>
</n-button>
</n-flex> </n-flex>
<n-flex size="small" vertical> <n-flex size="small" vertical>
<div v-for="project of reverseProjects" :key="project.path" class="project-card"> <div v-for="project of reverseProjects" :key="project.path" class="project-card">
<ProjectCard :project /> <ProjectCard :project />
</div> </div>
</n-flex> </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> </template>
<style scoped> <style scoped>

View File

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