Compare commits

...

3 Commits

13 changed files with 280 additions and 12 deletions

91
package-lock.json generated
View File

@@ -16,7 +16,9 @@
"highlight.js": "^11.11.1", "highlight.js": "^11.11.1",
"load-json-file": "^7.0.1", "load-json-file": "^7.0.1",
"make-dir": "^5.1.0", "make-dir": "^5.1.0",
"markdown-it": "^14.1.1",
"pinia": "^3.0.4", "pinia": "^3.0.4",
"simple-git": "^3.33.0",
"vue-router": "^4.6.4", "vue-router": "^4.6.4",
"write-json-file": "^7.0.0" "write-json-file": "^7.0.0"
}, },
@@ -1763,6 +1765,21 @@
"dev": true, "dev": true,
"license": "Apache-2.0" "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": { "node_modules/@malept/cross-spawn-promise": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmmirror.com/@malept/cross-spawn-promise/-/cross-spawn-promise-2.0.0.tgz", "resolved": "https://registry.npmmirror.com/@malept/cross-spawn-promise/-/cross-spawn-promise-2.0.0.tgz",
@@ -6834,6 +6851,15 @@
"immediate": "~3.0.5" "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": { "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",
@@ -6994,6 +7020,35 @@
"node": "^18.17.0 || >=20.5.0" "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": { "node_modules/matcher": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmmirror.com/matcher/-/matcher-3.0.0.tgz", "resolved": "https://registry.npmmirror.com/matcher/-/matcher-3.0.0.tgz",
@@ -7024,6 +7079,12 @@
"dev": true, "dev": true,
"license": "CC0-1.0" "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": { "node_modules/mime": {
"version": "2.6.0", "version": "2.6.0",
"resolved": "https://registry.npmmirror.com/mime/-/mime-2.6.0.tgz", "resolved": "https://registry.npmmirror.com/mime/-/mime-2.6.0.tgz",
@@ -8071,6 +8132,15 @@
"node": ">=6" "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": { "node_modules/quansync": {
"version": "0.2.11", "version": "0.2.11",
"resolved": "https://registry.npmmirror.com/quansync/-/quansync-0.2.11.tgz", "resolved": "https://registry.npmmirror.com/quansync/-/quansync-0.2.11.tgz",
@@ -8826,6 +8896,21 @@
"dev": true, "dev": true,
"license": "ISC" "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": { "node_modules/simple-update-notifier": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmmirror.com/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", "resolved": "https://registry.npmmirror.com/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz",
@@ -9453,6 +9538,12 @@
"typescript": ">=4.8.4 <6.0.0" "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": { "node_modules/ufo": {
"version": "1.6.3", "version": "1.6.3",
"resolved": "https://registry.npmmirror.com/ufo/-/ufo-1.6.3.tgz", "resolved": "https://registry.npmmirror.com/ufo/-/ufo-1.6.3.tgz",

View File

@@ -29,7 +29,9 @@
"highlight.js": "^11.11.1", "highlight.js": "^11.11.1",
"load-json-file": "^7.0.1", "load-json-file": "^7.0.1",
"make-dir": "^5.1.0", "make-dir": "^5.1.0",
"markdown-it": "^14.1.1",
"pinia": "^3.0.4", "pinia": "^3.0.4",
"simple-git": "^3.33.0",
"vue-router": "^4.6.4", "vue-router": "^4.6.4",
"write-json-file": "^7.0.0" "write-json-file": "^7.0.0"
}, },

View File

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

View File

@@ -13,6 +13,7 @@ import { getJetBrainsProjects, getVscodeProjects, openProject } from './code-lau
import { createCodeLaunchpadTray, createCodeLaunchpadWindow } from './code-launchpad/code-launchpad' import { createCodeLaunchpadTray, createCodeLaunchpadWindow } from './code-launchpad/code-launchpad'
import type { IDECode } from '@my-type/settings' import type { IDECode } from '@my-type/settings'
import installExtension, { VUEJS_DEVTOOLS } from 'electron-devtools-installer' import installExtension, { VUEJS_DEVTOOLS } from 'electron-devtools-installer'
import { getProjectGitInfo } from './code-launchpad/project-git'
let mainWindow: BrowserWindow | null = null let mainWindow: BrowserWindow | null = null
// @ts-ignore 保存引用,禁用报错 // @ts-ignore 保存引用,禁用报错
@@ -148,6 +149,10 @@ app.whenReady().then(async () => {
ipcMain.handle('window:closeWindow', () => { ipcMain.handle('window:closeWindow', () => {
mainWindow?.hide() mainWindow?.hide()
}) })
ipcMain.handle('window:exit', () => {
global.isQuiting = true
app.quit()
})
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)
@@ -157,6 +162,9 @@ app.whenReady().then(async () => {
ipcMain.handle('codeLaunchpad:openProject', (_, ide: IDECode, path: string) => { ipcMain.handle('codeLaunchpad:openProject', (_, ide: IDECode, path: string) => {
return openProject(ide, path) return openProject(ide, path)
}) })
ipcMain.handle('codeLaunchpad:getProjectGitInfo', (_, path: string) => {
return getProjectGitInfo(path)
})
await checkIDEs() await checkIDEs()
await checkIDEsVersion() await checkIDEsVersion()

View File

@@ -10,3 +10,13 @@ export interface IdeProjectDto {
} }
export type IdeProjectsDto = IdeProjectDto[] export type IdeProjectsDto = IdeProjectDto[]
export interface ProjectGitDto {
current: string | null
tracking: string | null
created: number
deleted: number
modified: number
renamed: number
staged: number
}

View File

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

View File

@@ -13,7 +13,8 @@ const api = {
_closeCodeLaunchpad: () => ipcRenderer.invoke('tools:closeCodeLaunchpad'), _closeCodeLaunchpad: () => ipcRenderer.invoke('tools:closeCodeLaunchpad'),
_minimize: () => ipcRenderer.invoke('window:minimize'), _minimize: () => ipcRenderer.invoke('window:minimize'),
_maximize: () => ipcRenderer.invoke('window:maximize'), _maximize: () => ipcRenderer.invoke('window:maximize'),
_closeWindow: () => ipcRenderer.invoke('window:closeWindow') _closeWindow: () => ipcRenderer.invoke('window:closeWindow'),
_exit: () => ipcRenderer.invoke('window:exit')
} }
const codeLaunchpadApi = { const codeLaunchpadApi = {
@@ -24,7 +25,8 @@ const codeLaunchpadApi = {
_getVSCodeProjects: () => ipcRenderer.invoke('codeLaunchpad:getVSCodeProjects'), _getVSCodeProjects: () => ipcRenderer.invoke('codeLaunchpad:getVSCodeProjects'),
_getJetBrainsProjects: () => ipcRenderer.invoke('codeLaunchpad:getJetBrainsProjects'), _getJetBrainsProjects: () => ipcRenderer.invoke('codeLaunchpad:getJetBrainsProjects'),
_openProject: (ide: string, path: string) => _openProject: (ide: string, path: string) =>
ipcRenderer.invoke('codeLaunchpad:openProject', ide, path) ipcRenderer.invoke('codeLaunchpad:openProject', ide, path),
_getProjectGitInfo: (path: string) => ipcRenderer.invoke('codeLaunchpad:getProjectGitInfo', path)
} }
// Use `contextBridge` APIs to expose Electron APIs to // Use `contextBridge` APIs to expose Electron APIs to

View File

@@ -33,6 +33,7 @@ declare module 'vue' {
NH2: typeof import('naive-ui')['NH2'] NH2: typeof import('naive-ui')['NH2']
NH3: typeof import('naive-ui')['NH3'] NH3: typeof import('naive-ui')['NH3']
NH4: typeof import('naive-ui')['NH4'] NH4: typeof import('naive-ui')['NH4']
NH5: typeof import('naive-ui')['NH5']
NIcon: typeof import('naive-ui')['NIcon'] NIcon: typeof import('naive-ui')['NIcon']
NInput: typeof import('naive-ui')['NInput'] NInput: typeof import('naive-ui')['NInput']
NInputNumber: typeof import('naive-ui')['NInputNumber'] NInputNumber: typeof import('naive-ui')['NInputNumber']

View File

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

View File

@@ -5,6 +5,7 @@ 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' import { formatTimestamp } from '@my-type/dataFormatter'
import ProjectGit from '@renderer/components-code-launchpad/ProjectGit.vue'
const message = useMessage() const message = useMessage()
@@ -85,6 +86,7 @@ async function openWithIDE(ide: string): Promise<void> {
<n-p v-if="project.timestamp !== 0" class="no-margin-bottom" type="default"> <n-p v-if="project.timestamp !== 0" class="no-margin-bottom" type="default">
上次打开于 {{ formatTimestamp(project.timestamp) }} 上次打开于 {{ formatTimestamp(project.timestamp) }}
</n-p> </n-p>
<ProjectGit :path="project.path" />
<n-flex> <n-flex>
<n-button-group> <n-button-group>
<n-button <n-button

View File

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

View File

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

View File

@@ -27,8 +27,33 @@ const menuOptions: MenuOption[] = [
function handleUpdateValue(key: string): void { function handleUpdateValue(key: string): void {
router.push(key) router.push(key)
} }
function exit(): void {
window.api._exit()
}
</script> </script>
<template> <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> </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>