first commit

This commit is contained in:
编码猿
2024-09-27 01:11:40 +08:00
commit 7ac6f553c6
147 changed files with 10620 additions and 0 deletions

1
src/core/.pydio Normal file
View File

@@ -0,0 +1 @@
42927cff-8842-4b5c-98b9-4ef72d76be57

View File

@@ -0,0 +1 @@
ae18ca61-49b8-41bd-b4e6-8d01591c8336

View File

@@ -0,0 +1,63 @@
import Config from "@config/Index.config";
import Utils from "@utils/Index.utils";
import {Http} from "@net/Http.net";
/**
* @Ipc()
* 实现居中IPC的注册用在controller方法上
* @param IpcParams 传入需要被注册的ipc类
* @constructor
*/
export function Ipc(IpcParams: { new (...args: any[]): {}; }[] ) {
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
/**
* 因为TS没有提供像java那种可以直接提取某个类中所有方法的系统方法
* 这边原本使用 Object.getOwnPropertyNames 能够正常提取类中方法,但是如果类中有被 @Inject() 依赖注入的属性
* 那么这个属性也会被 (new IpcParams())[val](),这样是肯定报错的,所以做下面约定!
* 在IPC 类中,如果在类的属性上使用 @Inject() 注入。那么请在类的属性名前加上 _例如:
* @Inject()
* private readonly _Net!: Http;
*/
IpcParams.forEach((val: { new (): any; } ) => {
Object.getOwnPropertyNames(val.prototype).splice(1)
.filter(f => !f.includes("_"))
.forEach(v => (new val())[v]())
})
}
}
/**
* @CreateApplicationIpc()
* 实现全局IPC的注册
* 能够自动实例化传入的类数组中的所有类,并自动调用类中的所有的方法
* @param IpcParams 类数组,实例:[ classIpc1, classIpc2, classIpc3 ]
* @constructor
*/
export function CreateApplicationIpc(IpcParams: { new (...args: any[]): {}; }[]): any {
return (_constructor: { new (...args: any[]): {}; } ) => {
IpcParams.forEach((val: { new (): any; } ) => {
Object.getOwnPropertyNames(val.prototype).splice(1)
.filter(f => !f.includes("_"))
.forEach(v => (new val())[v]())
})
}
}
/**
* @Render()
* 创建窗口
* @param templateName 要渲染的模板名称
* @constructor
*/
export function Render(templateName?: string) {
return (target: any, propertyKey: string, descriptor: PropertyDescriptor) => {
/**
* 如果被打上注解的类中某个方法是配置文件中的默认启动窗口,那么调用这个方法来创建窗体
* 其他的方法一律不管由js前端渲染进程触发ipc创建
*/
if (propertyKey.includes((Config.StartPage.split("/"))[1])) {
// 如果没有传参数,那么采用方法名来创建窗体!
templateName == undefined ? Utils.startWindows(target,propertyKey) : Utils.startWindows(target,templateName)
}
}
}

View File

@@ -0,0 +1,40 @@
import Config from "@config/Index.config";
import {Http} from "@net/Http.net";
/**
* @GET()
* 用在service类的方法上。
* 如果不传参数将自动根据方法名获取接口请求的地址
* 如果传参了,那么使用用户传入的地址去发送网络请求
*
* 如果 useHandle 为 true 那么方法将获得ajax请求的数据
* 否则 不会将数据给方法
* @param RequestParams
* @constructor
*/
export function GET (RequestParams?: { url?: string, useHandle?: boolean, header?: { [index: string]: any } }) {
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
let url: string = Config.ApiUrl.ApiList[propertyKey],
handle: boolean = false,
header: object = {};
(RequestParams && RequestParams.url)
? url = RequestParams.url
: (RequestParams && RequestParams.useHandle)
? handle = RequestParams.useHandle
: (RequestParams && RequestParams.header)
? header = RequestParams.header
: '';
let OldMethods = descriptor.value;
if (handle) {
descriptor.value = async (data: { [key:string]: any }) =>
await OldMethods.apply(target, [ await (new Http).GET({ url: url, data: data, header: header }) ])
return descriptor
} else {
descriptor.value = async (data: { [key:string]: any }) =>
await (new Http).GET({ url: url, data: data, header: header })
return descriptor
}
}
}

View File

@@ -0,0 +1,37 @@
import 'reflect-metadata';
import IocModel from "@model/Ioc.model";
/**
* 收集类依赖
* @constructor
*/
export function Injectable() {
return (_constructor: { new (...args: any[]): {}; }) => {
if(IocModel.classPool.indexOf(_constructor) !== -1) {
throw new Error('无需重复收集类');
} else {
//注册
IocModel.classPool = [_constructor]
}
}
}
/**
* 将类依赖实例化然后注入到被装饰的属性中
* @constructor
*/
export function Inject() {
return function (target: any, propertyName: string) {
/**
* 使用 reflect-metadata 提供的内置 类型元数据键 design:type 通过反射拿到被装饰属性的类型
* 也就是 类属性要实例化 的 service 类
*/
const propertyType: any = Reflect.getMetadata('design:type', target, propertyName);
if (IocModel.classPool.indexOf(propertyType) == -1) {
throw new Error('被装饰的属性所属的变量类型类,没有被装饰器@Injectable()注入,请检查!');
} else {
// 从存取器的数组中通过下标取出被装饰属性对应的service类然后实例化这个类在放入被装饰的属性中
target[propertyName] = new (IocModel.classPool[IocModel.classPool.indexOf(propertyType)])()
}
}
}

View File

@@ -0,0 +1,16 @@
import Utils from "@utils/Index.utils";
import {ApplicationMenu} from "@interactive/ApplicationMenu.interactive";
export function StartWindow(): any {
return (_constructor: {new(...args:any[]):{}} ) => {
return class extends _constructor {
constructor() {
// 这里只要动态 require 导入类即可,然后类上的装饰器就会运行
Utils.GetController()
// 创建顶部菜单
new ApplicationMenu()
super();
}
}
}
}

1
src/core/config/.pydio Normal file
View File

@@ -0,0 +1 @@
3392576f-5dfc-4270-9335-0fcac5cdbeb3

401
src/core/config/Index.config.ts Executable file
View File

@@ -0,0 +1,401 @@
import { PagePath, PageSize } from "@type/PageConfig.types";
import { TrayConfig } from "@type/Tray.types";
import { ApiUrlConfig } from "@type/ApiConfig";
import {
AboutPanelOptionsOptions,
app,
dialog,
MenuItem,
MenuItemConstructorOptions,
nativeImage,
shell,
TouchBar,
TouchBarConstructorOptions
} from "electron";
import Windows from "@/core/model/Windows.model";
import { join } from "path";
import { JadeOptions } from "jade";
import Utils from "@utils/Index.utils";
import JsonDB from "@utils/db.utils";
import { spawn } from "child_process";
const { TouchBarButton, TouchBarPopover } = TouchBar;
export default class Config {
/**
* 启动页面默认为 Home.controller/Home
* 程序内部根据这个配置自动载入Home.controller.ts文件并自动调用类里面的Home()方法实现窗口创建
* 可以改成其他,例如 PlayVideo.controller/Theme
*/
public static StartPage: string = 'Home.controller/Home';
public static isMac: Boolean = process.platform === 'darwin';
public static isWindows: Boolean = process.platform === 'win32';
public static isLinux: Boolean = process.platform === 'linux';
public static LogsPath: string = "../../../logs/";
public static isShowDockIcon: boolean = JsonDB.findOne("isShowDockIcon", "setting");
// 是否开机自启 默认 false
public static isLoginOpen: boolean = JsonDB.findOne("isOpen", "setting");
public static default = Config.isShowDockIcon ? app.dock.hide() : app.dock.show()
// 升级地址
public static UpdateUrl: string = "https://api.github.com/repos/helpcode/EasyProject/releases/latest";
// ajax 请求地址
public static ApiUrl: ApiUrlConfig = {
BaseUrl: 'https://registry.npmjs.org',
ApiList: {
PlayList: '/list', // 获取视频列表
PalyVideo: '/play', // 获取视频播放地址
VideoComment: '/CommentList', // 获取视频评论
}
};
// jade 模板引擎配置,更多参数自行阅读声明文件
public static jadeCompile0ptions: JadeOptions = {
cache: true,
pretty: true, // 编译输出后是否保留源码格式true 保持
globals: {
css: [
'../../assets/js/lib/index.css',
],
js: [
'../../assets/js/lib/vue.js',
'../../assets/js/lib/vuex.js',
'../../assets/js/lib/vue-router.js',
'../../assets/js/lib/axios.js',
'../../assets/js/lib/index.js',
'../../assets/js/lib/font.js'
]
}
};
// 应用程序的页面地址,地址为打包后的相对路径
public static PagePath: PagePath = {
Welcome: join(__dirname, '../../application/page/Welcome/Welcome.html'),
Home: join(__dirname, '../../application/page/Home/Home.html'),
PlugInfo: join(__dirname, '../../application/page/PlugInfo/PlugInfo.html')
};
// 关于App
public static appAbout: AboutPanelOptionsOptions = {
applicationName: "EasyProject",
copyright: "copyright @bmy",
website: "https://github.com/helpcode",
credits: "统一管理组织凌乱无序的软件项目,带来一站式的快感。"
};
// 所有页面窗体配置
public static PageSize: PageSize = {
Welcome: {
width: 330,
height: 520,
frame: false,
backgroundColor: '#00000000',
titleBarStyle: 'hidden',
transparent: true,
simpleFullscreen: true,
vibrancy: 'menu',
hasShadow: false,
show: false,
webPreferences: {
webSecurity: false,
nodeIntegration: true,
nodeIntegrationInWorker: true,
contextIsolation: false
}
},
Home: {
width: 1290,
height: 810,
minHeight: 630,
minWidth: 954,
frame: false,
backgroundColor: '#00000000',
titleBarStyle: 'hidden',
transparent: true,
simpleFullscreen: true,
hasShadow: true,
show: false,
webPreferences: {
webSecurity: false,
nodeIntegration: true,
nodeIntegrationInWorker: true,
contextIsolation: false
}
},
PlugInfo: {
width: 860,
height: 500,
frame: false,
modal: true,
backgroundColor: '#fff',
titleBarStyle: 'hidden',
transparent: true,
simpleFullscreen: true,
webPreferences: {
webSecurity: false,
nodeIntegration: true,
nodeIntegrationInWorker: true,
contextIsolation: false
}
}
};
// Mac系统顶部全局菜单 右边图标
public static TrayConfig: TrayConfig = {
TopMenuRightImage: join(__dirname, '../../application/assets/img/logoTemplate.png'),
TopMenuRightDropdown: [
{
label: '显示主窗口',
click: (menuItem: any, browserWindow: any, event: any) => {
Windows.CurrentBrowserWindow.show();
}
},
{ type: 'separator' },
{
label: '导入项目',
accelerator: "Cmd+i",
click: async (menuItem: any, browserWindow: any, event: any) => {
let res = await Utils.ImportProject((item) => {
console.log("Tray 导入的项目监听后被修改路径:", item)
// event.reply('DirRemove', item)
Windows.CurrentBrowserWindow.webContents.send('DirRemove', item)
});
if (!Windows.CurrentBrowserWindow.webContents.getURL().includes("Welcome.html")) {
Windows.CurrentBrowserWindow.webContents.send('TouchBarImportProject', res)
}
}
},
{
// TODO 导入 项目 的时候,这边项目列表不更新
// label: '项目列表(功能暂未全实现)',
label: '项目列表',
submenu: (JsonDB.project() as Array<any>).map((v:any,index) => {
return {
label: `${v.name}`,
submenu: [
{
label: '在访达打开',
click: async (menuItem: any, browserWindow: any, event: any) => {
await shell.openPath(v.Fullpath);
}
},
{
label: '在终端打开',
click: async (menuItem: any, browserWindow: any, event: any) => {
spawn ('open', [ '-a', 'Terminal', v.Fullpath ])
}
},
{
label: '在应用打开',
click: async (menuItem: any, browserWindow: any, event: any) => {
Windows.CurrentBrowserWindow.show();
Windows.CurrentBrowserWindow.webContents.send('openInfo', {
item: v, index
})
}
},
]
}
}),
},
{ type: 'separator' },
{
label: "设置...",
accelerator: "Cmd+,",
click: async (menuItem: any, browserWindow: any, event: any) => {
if (!Windows.CurrentBrowserWindow.webContents.getURL().includes("Welcome.html")) {
Windows.CurrentBrowserWindow.show();
Windows.CurrentBrowserWindow.webContents.send('openSetting')
}
}
},
{
label: "开机自启",
type: "checkbox",
checked: Config.isLoginOpen,
click: async (menuItem: any, browserWindow: any, event: any) => {
console.log("menuItem: ", menuItem.checked)
JsonDB.update("isOpen", menuItem.checked, "setting");
}
},
{
label: '隐藏Dock图标',
type: 'checkbox',
checked: Config.isShowDockIcon,
click: async (menuItem: any, browserWindow: any, event: any) => {
JsonDB.update("isShowDockIcon", menuItem.checked, "setting");
menuItem.checked ? app.dock.hide() : app.dock.show()
}
},
{ type: 'separator' },
{
label: `重启应用`,
click: () => {
Utils.killAllTask(() => {
app.relaunch({ args: process.argv.slice(1).concat(['--relaunch']) })
app.exit(0)
})
}
},
{
label: `杀死 ${ app.name }`,
click: () => {
Utils.killAllTask(() => {
app.exit(0)
})
}
}
],
TopMenuRightTips: '测试提醒'
};
// Mac or win 系统顶部全局菜单
public static TemplateMenu: Array<(MenuItemConstructorOptions) | (MenuItem)> = [
{
label: app.name,
submenu: [
{ label: `关于 ${ app.name }`, role: 'about' },
{ type: 'separator' },
{
label: "偏好设置",
accelerator: "Cmd+,",
click: async (menuItem: any, browserWindow: any, event: any) => {
if (!Windows.CurrentBrowserWindow.webContents.getURL().includes("Welcome.html")) {
Windows.CurrentBrowserWindow.show();
Windows.CurrentBrowserWindow.webContents.send('openSetting')
}
}
},
{ type: 'separator' },
{ label: '服务', role: 'services' },
{ type: 'separator' },
{ label: `隐藏 ${ app.name }`, role: 'hide' },
{ label: '隐藏其他', role: 'hideOthers' },
{ label: '隐藏其他2', role: 'unhide' },
{ type: 'separator' },
{ label: `退出${ app.name }`, role: 'quit' }
]
},
{
label: "文件",
submenu: [
{
label: '导入项目',
accelerator: "Cmd+i",
click: async (menuItem: any, browserWindow: any, event: any) => {
let res = await Utils.ImportProject(item => {
Windows.CurrentBrowserWindow.webContents.send('DirRemove', item)
});
if (!Windows.CurrentBrowserWindow.webContents.getURL().includes("Welcome.html")) {
Windows.CurrentBrowserWindow.show();
Windows.CurrentBrowserWindow.webContents.send('TouchBarImportProject', res)
}
}
},
]
},
{
label: '编辑',
submenu: [
{ label: '撤销', role: 'undo' },
{ label: '恢复', role: 'redo' },
{ type: 'separator' },
{ label: '剪切', role: 'cut' },
{ label: '复制', role: 'copy' },
{ label: '粘贴', role: 'paste' },
{ type: 'separator' },
{ label: '粘贴保留样式', role: 'pasteAndMatchStyle' },
{ label: '删除', role: 'delete' },
{ label: '全选', role: 'selectAll' },
{ type: 'separator' },
{ label: '听写', submenu: [ { label: '开始听写', role: 'startSpeaking' }, { label: '停止听写', role: 'stopSpeaking' } ] }
]
},
{
label: '窗口',
submenu: [
{ label: '最小化', role: 'minimize' },
{ label: '最大化', role: 'zoom' },
{ label: '关闭', role: 'close' }
]
},
{
label: '帮助',
role: 'help',
submenu: [
{
label: '联系作者',
click: async () => {
await dialog.showMessageBox({
type: "info",
title: '联系作者',
message: "如果你有任何使用建议或者反馈Bug\n请添加QQ2271608011"
})
}
},
app.isPackaged
? {
label: 'GitHub 主页',
click: async () => {
await shell.openExternal("https://github.com/helpcode")
}
}
: { label: '切换开发人员工具', role: 'toggleDevTools' },
]
}
];
// Mac touchbar 菜单
public static TouchBarConfig: TouchBarConstructorOptions = {
items: [
new TouchBarButton({
label: '导入项目',
iconPosition: "left",
icon: join(__dirname, '../../application/assets/img/+normal@2x.png'),
click: async () => {
let res = await Utils.ImportProject((item) => {
console.log("TouchBar 导入的项目监听后被修改路径:", item)
Windows.CurrentBrowserWindow.webContents.send('DirRemove', item)
});
if (!Windows.CurrentBrowserWindow.webContents.getURL().includes("Welcome.html")) {
Windows.CurrentBrowserWindow.webContents.send('TouchBarImportProject', res)
}
}
}),
new TouchBarPopover({
label: '帮助',
showCloseButton: true,
icon: (nativeImage.createFromPath(join(__dirname, '../../application/assets/img/help.png'))).resize({
width: 16,
height: 16
}),
items: new TouchBar({
items: [
new TouchBarButton({
label: '联系作者',
backgroundColor: '#3a3a3c',
click: async () => {
await dialog.showMessageBox({
type: "info",
title: '联系作者',
message: "如果你有任何使用建议或者反馈Bug\n请添加 QQ2271608011"
})
}
}),
new TouchBarButton({
label: 'Github主页',
backgroundColor: '#3a3a3c',
click: async () => {
await shell.openExternal("https://github.com/helpcode")
}
})
]
})
}),
]
};
}

View File

@@ -0,0 +1 @@
d66ac9d5-fd1e-43c7-9839-9363bad2256d

View File

@@ -0,0 +1,47 @@
import { Render, Ipc } from "@annotation/Creted.annotation";
import { HomeImplService } from "@service/impl/Home.impl.service";
import { Inject } from "@annotation/Ioc.annotation";
import { FileChice } from "@ipc/module/File.ipc";
import {app, Menu, shell, MenuItemConstructorOptions, MenuItem} from "electron";
import { Http } from "@net/Http.net";
import Config from "@config/Index.config";
export class HomeController {
@Inject()
public readonly HomeImplService!: HomeImplService;
@Inject()
public readonly _Http!: Http;
/**
* 启动欢迎页面
* @constructor
*/
@Render()
public async Welcome() {
return {
title: '欢迎'
}
}
/**
* 主页面
* @constructor
*/
@Render()
@Ipc([ FileChice ])
public async Home(params?: { [key:string]:any }) {
return {
title: '首页'
}
}
@Render()
public async PlugInfo(params?: { [ key:string ] :any }) {
console.log("插件详情页面接收到的参数:", params);
return {
title: '插件详情页面'
}
}
}

View File

@@ -0,0 +1 @@
c4c2b685-cd72-4295-bff5-a47af45c77a9

View File

@@ -0,0 +1,16 @@
import {app, Menu, shell, MenuItemConstructorOptions, MenuItem} from "electron";
import Config from "@config/Index.config";
export class ApplicationMenu {
private AppMenu!: Menu;
constructor() {
this.buildFromTemplate();
}
buildFromTemplate(): void {
this.AppMenu = Menu.buildFromTemplate(Config.TemplateMenu);
Menu.setApplicationMenu(this.AppMenu);
}
}

View File

@@ -0,0 +1,26 @@
import { dialog, Notification, OpenDialogOptions} from "electron";
import Windows from "@model/Windows.model"
export class Dialog {
/**
* 错误弹窗
* @param title
* @param content
* @constructor
*/
public static async ErrorBox(title: string, content: string): Promise<void> {
await dialog.showErrorBox(title, content)
}
/**
* 选择文件
* @param title
* @param message
*/
public static async showDialog(params: OpenDialogOptions): Promise<any> {
let SaveFile = await dialog.showOpenDialog(Windows.CurrentBrowserWindow, params);
if(!SaveFile.canceled) {
return SaveFile.filePaths
}
}
}

View File

@@ -0,0 +1,50 @@
import { shell, TouchBar, TouchBarButton as TB, nativeImage } from "electron";
import Windows from "@/core/model/Windows.model";
import Config from "@config/Index.config";
import JsonDB from "@utils/db.utils";
import { join } from "path";
const { TouchBarLabel, TouchBarButton, TouchBarPopover } = TouchBar;
export class Touchbar {
constructor() {
this.createdAllProject()
}
public createdAllProject(): void {
let allProject = JsonDB.project();
let touchBarButtonArr: Array<TB> = [];
// 生成 项目列表 下的项目按钮
(allProject as Array<any>).forEach((v, i) => {
touchBarButtonArr.push(
new TouchBarButton({
label: v.name,
backgroundColor: '#3a3a3c',
click: async () => {
await shell.openPath(v.Fullpath)
}
}),
)
});
// 项目列表 Icon
let projectIcon = nativeImage.createFromPath(join(__dirname, '../../application/assets/img/project.png'));
// @ts-ignore
if (Config.TouchBarConfig.items[1].label == "项目列表") {
// @ts-ignore
Config.TouchBarConfig.items.splice(1, 1)
}
// @ts-ignore
Config.TouchBarConfig.items.splice(1, 0, new TouchBarPopover({
label: '项目列表',
icon: projectIcon.resize({ width: 18, height: 18 }),
showCloseButton: true,
items: new TouchBar({
items: touchBarButtonArr
})
}))
Windows.CurrentBrowserWindow.setTouchBar(new TouchBar(Config.TouchBarConfig))
}
}

View File

@@ -0,0 +1,32 @@
import { nativeImage, NativeImage, Menu, Tray } from "electron";
import Config from "@config/Index.config";
import JsonDB from "@utils/db.utils";
import Windows from "@/core/model/Windows.model";
import Utils from "@utils/Index.utils";
export class TrayInteractive {
private tray!: Tray;
constructor() {
this.tray = new Tray(Config.TrayConfig.TopMenuRightImage);
Windows.tray = this.tray;
this.buildTrayMenu()
}
private buildTrayMenu() {
let contextMenu: Menu = Menu.buildFromTemplate(Config.TrayConfig.TopMenuRightDropdown);
this.tray.setToolTip(Config.TrayConfig.TopMenuRightTips)
Utils.setTrayTitleNums();
this.tray.setContextMenu(contextMenu)
}
/**
* 创建 NativeImage 图片图片会随着Mac系统主题的切换而自定变纯黑或纯白
*/
// private setTemplateImage(): NativeImage {
// let image: NativeImage = nativeImage.createFromPath(Config.TrayConfig.TopMenuRightImage);
// image.setTemplateImage(true)
// return image.resize({ width: 20, height: 20 })
// }
}

View File

1
src/core/ipc/.pydio Normal file
View File

@@ -0,0 +1 @@
f5e29150-31cc-40cb-99ed-6527fc3038a9

67
src/core/ipc/Application.ipc.ts Executable file
View File

@@ -0,0 +1,67 @@
import {ipcMain, shell} from "electron";
import Utils from "@utils/Index.utils";
import Windows from "@model/Windows.model";
import Config from "@config/Index.config";
import { spawn, exec} from "child_process";
export class Application {
CreatedWindow() {
ipcMain.on('openWindow', async (event, arg: any) => {
let ControllerName: string = arg.action.split("/")[0],
MethodsName: string = arg.action.split("/")[1],
data: object = arg.data || {}
// parent false 创建单独的窗体
if (!arg.parent || !arg.hasOwnProperty("parent")) {
// 是否要关闭上一级窗口
if(arg.hasOwnProperty("closeParent") && arg.closeParent) {
Windows.CurrentBrowserWindow.hide()
}
let a = require(`../controller/${ControllerName}.js`);
Utils.startWindows(new a[Utils.toUpperCase(ControllerName)](), MethodsName, data)
event.returnValue = ""
return false
}
// parent true 创建默认依附主窗口的子窗口
if (arg.hasOwnProperty("parent") || arg.parent) {
// @ts-ignore 合并配置参数,注入 parent: 当前默认启动的父类窗口实例
Object.assign(Config.PageSize[MethodsName], { parent: Windows.CurrentBrowserWindow });
// 实例化子窗口类,创建窗口
let a = require(`../controller/${ControllerName}.js`);
Utils.startWindows(new a[Utils.toUpperCase(ControllerName)](), MethodsName, data)
event.returnValue = ""
return false
// 创建默认依附在自定义窗口的子窗口
} else {
console.log("创建默认依附在自定义窗口的子窗口")
}
})
}
/**
* 打开浏览器窗口
*/
shellWindows() {
ipcMain.on("openExternal", async (event, arg:any) => {
await shell.openExternal(arg.url)
event.returnValue = "success"
});
}
/**
* 在文件管理器打开
* 在终端打开mac系统和win
*/
shellOpen() {
ipcMain.on("OpenProject", async (event, arg:any) => {
arg.type == 'file'
? await shell.openPath(arg.path)
: Config.isMac
? spawn ('open', [ '-a', 'Terminal', arg.path ])
: exec(`start cmd.exe /K cd /D ${arg.path}`)
event.returnValue = "success"
});
}
}

View File

@@ -0,0 +1 @@
74566167-ef2f-4b0c-9692-89f4aeef3960

543
src/core/ipc/module/File.ipc.ts Executable file
View File

@@ -0,0 +1,543 @@
import { app, ipcMain, shell } from "electron";
import { Dialog } from "@interactive/Dialog.interactive";
import { Inject } from "@annotation/Ioc.annotation";
import { Http } from "@net/Http.net";
import Utils from "@utils/Index.utils";
import JsonDB from "@utils/db.utils";
import { existsSync, readJsonSync, remove, watch } from "fs-extra";
import { exec, execSync, spawn } from "child_process";
import Config from "@config/Index.config";
import kill from "tree-kill";
import { basename, dirname, resolve } from "path";
import { ProcessUtils } from "@utils/process.utils";
import { Package } from "@utils/package.utils";
import Windows from "@model/Windows.model";
import chokidar, { FSWatcher } from "chokidar";
import { WhichBin } from "@utils/whichBin.utils"
import moment from "moment"
export class FileChice {
@Inject()
private readonly _Net!: Http;
@Inject()
private readonly _Package!: Package;
@Inject()
private readonly _WhichBin!: WhichBin;
// @Inject()
private readonly _process: ProcessUtils = new ProcessUtils();
sendSettingConfig() {
ipcMain.on("checkUpdate", async (event, args) => {
console.log("升级...")
let returnVal: any = {};
let res = await this._Net.GET({
url: Config.UpdateUrl,
custom: undefined,
header: {
'Authorization': 'Basic aGVscGNvZGU6Z2hwXzBEZmx3cDFQY2l6VVRNOFkxTTNBMDZxQlhTZGpvSjRHak4xMw=='
}
});
if (res != undefined) {
// 如果需要更新
if (Utils.compare(res.tag_name, app.getVersion())) {
returnVal.isUpdate = true;
returnVal.detailed = res.body.split("\r\n");
returnVal.version = res.tag_name;
returnVal.title = res.name;
returnVal.down = res.assets[ 0 ].browser_download_url;
returnVal.updatedTime = res.assets[ 0 ].updated_at;
} else {
returnVal.isUpdate = false;
}
event.returnValue = returnVal
} else {
returnVal.isUpdate = false;
}
})
ipcMain.on("selectEnvPath", async (event, args) => {
let DirectoryPath = await Dialog.showDialog({
message: "选择您的Node脚本所在路径",
buttonLabel: '选择',
properties: [ 'openDirectory', 'showHiddenFiles' ]
});
if (DirectoryPath) {
JsonDB.update("envVariable", DirectoryPath[0])
await this._WhichBin.initEnvPath();
Utils.killAllTask(() => {
app.relaunch({ args: process.argv.slice(1).concat(['--relaunch']) })
app.exit(0)
})
}
})
ipcMain.on("getVersion", async (event, args) => {
event.returnValue = { version: app.getVersion() }
})
ipcMain.on("delete_themeList", async (event, args) => {
event.returnValue = JsonDB.remove(args.value, `setting.${ args.keyName }`)
})
ipcMain.on("push_themeList", async (event, args) => {
event.returnValue = JsonDB.saveProject(args.value, `setting.${ args.keyName }`)
})
ipcMain.on("setPackageTypeindex", async (event, args) => {
try {
let res = execSync(`which ${args.packageType}`);
console.log("res: ", res.toString())
JsonDB.update(args.keyName, args.value)
event.returnValue = { status: true, message: null }
} catch (e) {
event.returnValue = { status: false, message: e.message }
}
})
ipcMain.on("getSettingConfig", async (event, args) => {
event.returnValue = JsonDB.project("setting")
})
ipcMain.on("setSettingConfig", async (event, args) => {
JsonDB.update(args.keyName, args.value)
if (args.keyName == "isOpen") {
const appFolder = dirname(process.execPath)
const updateExe = resolve(appFolder, '..', 'EasyProject.app')
const exeName = basename(process.execPath)
app.setLoginItemSettings({
openAtLogin: args.value,
path: updateExe,
args: [
'--processStart', `"${ exeName }"`,
'--process-start-args', `"--hidden"`
]
});
}
// event.returnValue = JsonDB.project("setting")
})
}
getProcess() {
ipcMain.on("getProcess", async (event, args) => {
let res = await this._process.GetAllProcess(args.pid);
event.returnValue = res
})
}
setPackageJson() {
ipcMain.on("setPackageJson", async (event, args) => {
let pkg = JsonDB.findName(args.project);
let packagePath = `${ pkg.Fullpath }/package.json`;
Utils.writePackage(packagePath, args.code)
})
}
getPackageJson() {
ipcMain.on("getPackageJson", async (event, args) => {
let pkg = JsonDB.findName(args.project);
let packagePath = `${ pkg.Fullpath }/package.json`;
try {
event.returnValue = Utils.readPackage(packagePath)
} catch (e) {
event.returnValue = e.message
}
})
}
SaveFile() {
ipcMain.on('SaveFile', async (event, arg) => {
let SaveFilePath = await Dialog.showDialog({
title: "选择路径",
message: "选择下载路径",
buttonLabel: '亲,点我确认选择!',
filters: [
{ name: 'All', extensions: [ '*' ] },
],
properties: [
'openDirectory',
'createDirectory'
]
});
if (SaveFilePath != undefined) {
Utils.DownFile(arg.url, SaveFilePath)
}
})
}
/**
* 从项目列表删除项目
*/
removeListProject() {
// 从项目列表删除项目
ipcMain.on("removeListProject", async (event, args) => {
let { Fullpath } = JsonDB.findName(args.name,"name");
Utils.removeDirWatch(Fullpath);
let a = JsonDB.remove({ name: args.name });
if (a != undefined) {
Utils.updateTouchbarList();
Utils.setTrayTitleNums();
event.returnValue = "success"
} else {
event.returnValue = undefined
}
});
// 从磁盘删除项目
ipcMain.on("removeDistProject", async (event, args) => {
let project = JsonDB.findName(args.name, "name");
Utils.removeDirWatch(project.Fullpath);
try {
// 删除到回收站
await shell.trashItem(resolve(project.Fullpath))
// await remove();
// 从本地数据库删除
JsonDB.remove({ name: args.name });
event.returnValue = "success"
} catch (err) {
event.returnValue = err.message
}
});
}
/**
* 重命名项目
*/
reNameProject() {
ipcMain.on("reNameProject", async (event, args) => {
JsonDB.db.get('projects')
.find({ name: args.oldName })
.assign({ name: args.newName })
.write()
Utils.updateTouchbarList();
event.returnValue = "success"
});
}
changeIcon() {
ipcMain.on("ChangeIcon", async (event, args) => {
JsonDB.db.get('projects')
.find({ name: args.name })
.assign({ FolderIcon: args.newIcon })
.write()
event.returnValue = "success"
})
}
/**
* 导入项目
*/
openDirectory() {
ipcMain.on("openDirectory", async (event, args) => {
let res = await Utils.ImportProject((item) => {
console.log("导入的项目监听后被修改路径:", item)
event.reply('DirRemove', item)
});
event.returnValue = res;
})
}
/**
* 停止命令
* @constructor
*/
StopProject(): void {
ipcMain.on("StopCmd", async (event, args) => {
console.log("停止命令 args: ", args)
let currentTask: any = Utils.findOneTask(args.name, args.ScriptName);
kill(currentTask.Child.pid, (err: Error | undefined) => {
if (err) {
event.returnValue = err.message
} else {
currentTask.Child = null;
currentTask.IsRuning = "idle"
currentTask.RunLogs = ""
event.returnValue = null
}
});
})
}
/**
* 运行命令
* @constructor
*/
RunProject(): void {
ipcMain.on("RunCmd", async (event, args) => {
console.log("运行命令: ", args)
let currentTask: any = Utils.findOneTask(args.name, args.ScriptName);
let cmd = args.ScriptName == 'install'
? `cd ${ currentTask.path } && ${this._Package.install("i")}`
: `cd ${ currentTask.path } && ${this._Package.run(currentTask.ScriptName)}`;
try {
currentTask.Child = spawn(cmd, {
cwd: process.cwd(),
stdio: [ 'inherit', 'pipe', 'pipe' ],
shell: true
});
// 修改运行状态
currentTask.IsRuning = "runing"
currentTask.time = Date.now()
currentTask.Child.stdout.on('data', (data: Buffer) => {
currentTask.RunLogs += data.toString();
if (currentTask.Child != null) {
currentTask.pid = currentTask.Child.pid;
// 保存 你正在运行的 pid
Windows.runPids.add(currentTask.pid);
console.log("运行命令添加pid: ", Windows.runPids)
// 后端返回 ScriptName 为前端助力,让前端的 sendMessage 监听
// 中知道把 log 日志存放到数组的哪个 v.Terminal 中
event.sender.send('sendMessage', {
log: data.toString(),
projectName: args.name,
ScriptName: currentTask.ScriptName,
pid: currentTask.Child.pid
})
}
});
// 命令自动运行完成的时候,向前端发送进程关闭的通知
currentTask.Child.stdout.on('close', (code: Number, signal: string) => {
console.log("5 进程结束")
// 删除已经停止的 pid
Windows.runPids.delete(currentTask.pid);
currentTask.pid = 0;
console.log("停止命令删除pid: ", Windows.runPids)
// 关闭进程
currentTask.IsRuning = "idle"
currentTask.RunLogs = ""
currentTask.Child = null;
currentTask.time = Date.now() - currentTask.time
event.sender.send('close', {
ScriptName: currentTask.ScriptName,
projectName: args.name,
time: moment(currentTask.time).format("mm:ss")
});
});
currentTask.Child.stdout.on('error', (e: any) => {
console.log("------121 运行命令产生错误了------",e)
});
} catch (e) {
console.log("------2 运行命令产生错误了------",e)
}
event.returnValue = ""
})
}
/**
* 获取项目的运行命令
* @constructor
*/
GetTaskList() {
ipcMain.on("GetTaskList", async (event, args) => {
let project = JsonDB.findName(args.name);
let scriptList = (Utils.readPackage(`${ project.Fullpath }/package.json`)).scripts;
let NewSrciptList = []
for (const key in scriptList) {
NewSrciptList.push({
/**
* 进程状态锁
* true 表示命令未运行进程停止时会变为true前端运行按钮可以点击
* false 在你点击停止按钮后,进程还未被完全杀死前,再次点击运行按钮将无反应
* 必须等待进程被完全杀死后,运行按钮才能再次使用
* 如果不加锁,在前端 急速反复点击 运行停止按钮 的时候
* 会导致进程运行后直接闪退Mccos上存在
*/
lock: true,
ScriptName: key, // 命令的名称
IsRuning: "idle", // idle 未运行 runing 运行
RunLogs: '', // 命令运行产生的日志
ScriptShell: scriptList[ key ], // 命令的脚本
Terminal: null,
Child: null,
pid: 0,
time: 0,
})
}
NewSrciptList.unshift({
lock: true,
ScriptName: 'install',
IsRuning: "idle",
RunLogs: '',
ScriptShell: this._Package.install("i"),
Terminal: null,
Child: null,
pid: 0,
time: 0,
});
// 深拷贝去除再次回到【任务模块】的时候Child中保存的子进程无法被ipc发送的问题
let newItem = Utils.DeepCopy(Utils.setTask(project.Fullpath, NewSrciptList))
newItem.forEach((v: any) => v.Child != null ? v.Child = null : '');
event.returnValue = newItem
})
}
/**
* 应用一打开的时候获取项目列表
* @constructor
*/
ProjectList() {
ipcMain.on("ProjectList", async (event, args) => {
let allProject = JsonDB.project();
event.returnValue = allProject.filter((v:any,index: number) => {
// 打开软件的时候,判断本地项目路径是否存在
// 把存在的返回数据给前端,来展示项目列表
if (existsSync(v.Fullpath)) {
Utils.addDirWatch(v.Fullpath, () => {
event.reply('DirRemove', v)
})
return v
} else {
// 路径已经不存在了删除json
console.log("路径已经不存在了删除json")
JsonDB.remove({ name: v.name });
}
});
})
}
/**
* 获取依赖
* @constructor
*/
GetDependentList() {
ipcMain.on("GetDependentList", async (event, args) => {
console.log("获取依赖 ==== args: ", args)
let project = JsonDB.findName(args.name);
let pkg = Utils.readPackage(`${ project.Fullpath }/package.json`);
let ListArr: { title: string, list: { [ key: string ]: any }[] }[] = [];
// 检查项目中是否有 node_modules
if (existsSync(`${project.Fullpath}/node_modules`)) {
// 头像地址https://avatars.dicebear.com/v2/identicon/插件名.svg
// 请求地址https://registry.npmjs.org/插件名
[ pkg.dependencies, pkg.devDependencies ].forEach((v, i) => {
ListArr.push({
title: i == 0 ? '生产环境依赖' : '开发环境依赖',
list: []
})
for (const key in v) {
let otherPath = `${project.Fullpath}/node_modules/${key}/package.json`
if (existsSync(otherPath)) {
const modeulePackage = Utils.readPackage(otherPath);
ListArr[ i ].list.push({
name: key,
logoImg: `https://avatars.dicebear.com/v2/identicon/${ key.replace("/", "-") }.svg`,
currentVersion: v[ key ],
description: modeulePackage.description,
website: modeulePackage.homepage || (modeulePackage.repository && modeulePackage.repository.url) || `https://www.npmjs.com/package/${ key.replace('/', '%2F') }`
})
}
}
});
} else {
event.returnValue = undefined
}
event.returnValue = ListArr
})
}
/**
* 安装依赖
* @constructor
*/
InstallPlug() {
ipcMain.on("installPlug", async (event, args) => {
let pkg = JsonDB.findName(args.project);
let cmd = `cd ${ pkg.Fullpath } && ${this._Package.install("other", args)}`
exec(cmd, (error, stdout, stderr) => {
if (error == null) {
event.reply('reDependentSuccess', 'ok')
} else {
// event.returnValue = stderr
event.reply('reDependentSuccess', {
message: error.message
})
}
})
})
}
/**
* 更新依赖包
*/
updateDependencies() {
ipcMain.on("uDependencies", async (event, args) => {
let cmd, pkg = JsonDB.findName(args.project);
cmd = `cd ${ pkg.Fullpath } && ${this._Package.up(args)}`
exec(cmd, (error, stdout, stderr) => {
if (error == null) {
event.reply('reDependentSuccess', 'ok')
} else {
console.log(error.message)
// event.returnValue = stderr
event.reply('reDependentSuccess', {
message: error.message
})
}
})
})
}
/**
* 删除依赖包
*/
deleteDependencies() {
ipcMain.on("dDependencies", async (event, args) => {
let cmd, pkg = JsonDB.findName(args.project);
cmd = `cd ${ pkg.Fullpath } && ${this._Package.uninstall(args.type)}`;
console.log("删除依赖包: ", cmd)
exec(cmd, (error, stdout, stderr) => {
console.log(stdout)
if (error == null) {
event.reply('reDependentSuccess', 'ok')
} else {
event.reply('reDependentSuccess', {
message: error.message
})
}
})
})
}
/**
* 初始化依赖
*/
installDependent() {
ipcMain.on("reDependent", async (event, args) => {
console.log("args.name: ", args.name)
let pkg = JsonDB.findName(args.name)
let cmd = `cd ${ pkg.Fullpath } && rm -rf node_modules && ${this._Package.install("i")}`
console.log("初始化依赖: ",cmd)
exec(cmd, (error, stdout, stderr) => {
if (error == null) {
event.reply('reDependentSuccess', 'ok')
} else {
// event.returnValue = stderr
event.reply('reDependentSuccess', {
message: error.message
})
}
})
})
}
}

View File

@@ -0,0 +1,12 @@
#!/bin/bash
terminateTree() {
for cpid in $(/usr/bin/pgrep -P $1); do
terminateTree $cpid
done
kill -9 $1 > /dev/null 2>&1
}
for pid in $*; do
terminateTree $pid
done

1
src/core/model/.pydio Normal file
View File

@@ -0,0 +1 @@
0aab50fe-8286-4b21-b0e4-396e502553da

13
src/core/model/Ioc.model.ts Executable file
View File

@@ -0,0 +1,13 @@
class IocModel {
private _classPool: Array<{ new (...args: any[]): {}; }> = [];
get classPool(): Array<{ new(...args: any[]): {} }> {
return this._classPool;
}
set classPool(value: Array<{ new(...args: any[]): {} }>) {
this._classPool = [...value, ...this._classPool]
}
}
export default new IocModel();

86
src/core/model/Windows.model.ts Executable file
View File

@@ -0,0 +1,86 @@
import { WebContents, BrowserWindow, Tray } from "electron";
import chokidar from "chokidar";
import { FSWatcher } from "fs"
class Windows {
private _HomeBrowserWindow!: BrowserWindow;
private _CurrentBrowserWindow!: BrowserWindow;
private _SettingBrowserWindow!: BrowserWindow;
private _CurrentWindowNew!: any;
private _tray!: Tray;
// @ts-ignore
private _HomeBrowserWindowWebContents!: typeof WebContents;
// @ts-ignore
private _SettingBrowserWindowWebContents!: typeof WebContents;
public runPids: Set<number> = new Set();
public watchDirectory: Map<string, FSWatcher> = new Map();
get tray(): Tray {
return this._tray;
}
set tray(value: Tray) {
this._tray = value;
}
get CurrentWindowNew(): any {
return this._CurrentWindowNew;
}
set CurrentWindowNew(value: any) {
this._CurrentWindowNew = value;
}
get CurrentBrowserWindow(): Electron.BrowserWindow {
return this._CurrentBrowserWindow;
}
set CurrentBrowserWindow(value: Electron.BrowserWindow) {
this._CurrentBrowserWindow = value;
}
get HomeBrowserWindow(): BrowserWindow {
return this._HomeBrowserWindow;
}
set HomeBrowserWindow(value: BrowserWindow) {
this.CurrentBrowserWindow = value;
this._HomeBrowserWindow = value;
}
get SettingBrowserWindow(): BrowserWindow {
return this._SettingBrowserWindow;
}
set SettingBrowserWindow(value: BrowserWindow) {
this.CurrentBrowserWindow = value;
this._SettingBrowserWindow = value;
}
// @ts-ignore
get HomeBrowserWindowWebContents(): typeof WebContents {
return this._HomeBrowserWindowWebContents;
}
// @ts-ignore
set HomeBrowserWindowWebContents(value: typeof WebContents) {
this._HomeBrowserWindowWebContents = value;
}
// @ts-ignore
get SettingBrowserWindowWebContents(): typeof WebContents {
return this._SettingBrowserWindowWebContents;
}
// @ts-ignore
set SettingBrowserWindowWebContents(value: typeof WebContents) {
this._SettingBrowserWindowWebContents = value;
}
}
export default new Windows();

1
src/core/net/.pydio Normal file
View File

@@ -0,0 +1 @@
2a9bfddc-1ed2-432c-9d57-20c175ec6af6

94
src/core/net/Http.net.ts Executable file
View File

@@ -0,0 +1,94 @@
import Config from "@config/Index.config";
import {Inject, Injectable } from "@annotation/Ioc.annotation";
import { LogsUtils } from '@utils/logs.utils';
import Axios, { AxiosResponse } from "axios";
import Utils from "@utils/Index.utils";
interface RequestParams {
url: string,
custom?: string,
data?: { [index: string]: any; },
header?: { [index: string]: any }
}
@Injectable()
export class Http {
private ResponseData!: AxiosResponse<any>;
constructor() {
Axios.defaults.baseURL = Utils.CheckAjaxUrl();
}
@Inject()
private LogsUtils!: LogsUtils;
/**
* GET请求
* @param params
* @constructor
*/
public async GET(params: RequestParams): Promise<any> {
if (params.custom !== undefined) {
Axios.defaults.baseURL = params.custom
}
try {
this.ResponseData = await Axios.get(params.url, {
params: params.data,
headers: Object.assign({}, params.header)
});
return this.ResponseData.data;
} catch (e) {
console.log(`GET 请求出错:${e.message}`)
// throw new Error(`GET 请求出错:${e.message}`)
}
}
/**
* POST请求
* @param params
* @constructor
*/
public async POST(params: RequestParams): Promise<any> {
try {
this.ResponseData = await Axios.post(params.url, params.data, {
headers: Object.assign({}, params.header)
})
return this.ResponseData.data;
} catch (e) {
throw new Error(`POST 请求出错:${e.message}`)
}
}
/**
* PUT 请求
* @param params
* @constructor
*/
public async PUT(params: RequestParams): Promise<any> {
try {
this.ResponseData = await Axios.put(params.url, params.data, {
headers: Object.assign({}, params.header)
})
return this.ResponseData.data;
} catch (e) {
throw new Error(`PUT 请求出错:${e.message}`)
}
}
/**
* DELETE 请求
* @param params
*/
public async DELETE(params: RequestParams): Promise<any> {
try {
this.ResponseData = await Axios.delete(params.url, {
params: params.data,
headers: Object.assign({}, params.header)
})
return this.ResponseData.data;
} catch (e) {
throw new Error(`DELETE 请求出错:${e.message}`)
}
}
}

1
src/core/run/.pydio Normal file
View File

@@ -0,0 +1 @@
01b36b6b-4278-4f9f-bef5-3e385aea22fd

13
src/core/run/Init.run.ts Executable file
View File

@@ -0,0 +1,13 @@
import { StartWindow } from "@annotation/Run.annotation";
import { CreateApplicationIpc } from "@annotation/Creted.annotation";
import { Application } from "@ipc/Application.ipc";
import { BaseControllerTypes } from "@type/BaseController.types";
@StartWindow()
@CreateApplicationIpc([ Application ])
export class Run implements BaseControllerTypes {
event(): void {}
ipc(): void {}
monitor(): void {}
ui(): void {}
}

1
src/core/service/.pydio Normal file
View File

@@ -0,0 +1 @@
60b35412-bea5-4464-8911-b1f345541f73

View File

@@ -0,0 +1,3 @@
export interface HomeService {
PlugInfo(params: object): Promise<any>;
}

View File

@@ -0,0 +1 @@
45388eb6-25cb-42d3-96f3-f48c7763b6d1

View File

@@ -0,0 +1,11 @@
import { HomeService } from "@service/Home.service";
import { GET } from "@annotation/Http.annotation";
import { Injectable } from "@annotation/Ioc.annotation";
@Injectable()
export class HomeImplService implements HomeService {
@GET({ useHandle: true })
public async PlugInfo(params: object): Promise<any> {
return params
}
}

1
src/core/types/.pydio Normal file
View File

@@ -0,0 +1 @@
b6ba00f3-28d3-40dd-8c8a-1848fdb6d1f8

6
src/core/types/ApiConfig.ts Executable file
View File

@@ -0,0 +1,6 @@
export interface ApiUrlConfig {
BaseUrl: string,
ApiList: {
[key: string]: any
}
}

View File

@@ -0,0 +1,7 @@
export interface BaseControllerTypes {
// render(): { [ key:string ]: any };
ipc(): void;
ui(): void;
event(): void;
monitor(): void;
}

View File

@@ -0,0 +1,14 @@
import { BrowserWindowConstructorOptions } from "electron";
export interface PagePath {
Welcome?: String,
Home?: String,
PlugInfo?: String
}
export interface PageSize {
// 欢迎页面 窗体的配置
Welcome?: BrowserWindowConstructorOptions,
Home?: BrowserWindowConstructorOptions,
PlugInfo?: BrowserWindowConstructorOptions
}

7
src/core/types/Tray.types.ts Executable file
View File

@@ -0,0 +1,7 @@
import { MenuItem, MenuItemConstructorOptions } from "electron";
export interface TrayConfig {
TopMenuRightImage: string;
TopMenuRightDropdown: Array<(MenuItemConstructorOptions) | (MenuItem)>;
TopMenuRightTips: string;
}

1
src/core/utils/.pydio Normal file
View File

@@ -0,0 +1 @@
e5806741-6f78-4a3d-b60d-f973b650ad7a

465
src/core/utils/Index.utils.ts Executable file
View File

@@ -0,0 +1,465 @@
import {join, parse} from "path";
import Config from "@config/Index.config";
import {BrowserWindow, Notification, NotificationConstructorOptions, app, TouchBar, TouchBarButton as TB} from "electron";
const {TouchBarLabel, TouchBarSpacer, TouchBarPopover, TouchBarButton} = TouchBar;
import {mkdirSync, writeFileSync, existsSync, PathLike} from "fs";
import {renderFile} from "jade";
import Windows from "@model/Windows.model";
import globby from "globby";
import {diff} from 'deep-diff';
import JsonDB from "@utils/db.utils";
import {Touchbar} from "@interactive/Touchbar.interactive";
import {TrayInteractive} from "@interactive/Tray.interactive";
import {ApplicationMenu} from "@interactive/ApplicationMenu.interactive";
import { Dialog } from "@interactive/Dialog.interactive";
import kill from "tree-kill";
import chokidar, { FSWatcher } from "chokidar";
// require('electron-reload')(__dirname, {
// electron: "/Applications/Electron.app/Contents/MacOS/Electron"
// })
export default class Utils {
public static CheckAjaxUrl(): string {
return Config.ApiUrl.BaseUrl
}
/**
* 删除文件监听
* @param path 文件的完整路径
*/
public static async removeDirWatch(path: string) {
// 判断 Map 里面是否有这个文件的监听,有则解除文件监听
if (Windows.watchDirectory.has(path)) {
let watchObj = Windows.watchDirectory.get(path);
await (watchObj as FSWatcher).unwatch(path);
Windows.watchDirectory.delete(path);
}
}
/**
* 增加文件监听
* @param path
*/
public static addDirWatch(path: string, callback: () => void) {
// 用文件名来作为key值是文件夹监听器的返回值
Windows.watchDirectory.set(
path,
chokidar.watch(path, {
persistent: true,
ignoreInitial: false,
followSymlinks: false,
usePolling: true,
interval: 300,
depth: 1,
ignored: 'node_modules/**/*',
cwd: path
}).on("unlinkDir", () => {
// 如果监听到之前磁盘上还存在的文件,现在被修改了,
// 那么判断 这个文件夹现在还存在吗?
// 如果不存在 将被删除文件的下标给前端,让前端能过滤数据 和 自动激活tab
if (!existsSync(path)) {
callback()
}
})
);
}
public static killAllTask(callback: () => void) {
let pidsArr: number[] = Array.from(Windows.runPids)
// 如果有正在运行的命令
if (pidsArr.length != 0) {
// 遍历 干掉你运行过的命令进程
Windows.runPids.forEach(v => {
kill(v)
// 是否已经杀到最后一个进程了,必须等 kill 全部杀完然后
// 延迟一下才能关闭程序,否则关程序速度太快会导致进程杀不完
if (pidsArr[pidsArr.length -1] == v) {
// dialog.showErrorBox("最后一个", v.toString())
setTimeout(() => callback(),400)
}
})
} else {
callback()
}
}
/**
*
* @param curV 新版本号
* @param reqV 当前应用的版本号
*/
public static compare(curV: string, reqV: string): boolean {
if (curV && reqV) {
//将两个版本号拆成数字
var arr1 = curV.split('.'),
arr2 = reqV.split('.');
var minLength = Math.min(arr1.length, arr2.length),
position = 0,
diff = 0;
//依次比较版本号每一位大小,当对比得出结果后跳出循环(后文有简单介绍)
while (position < minLength && ((diff = parseInt(arr1[position]) - parseInt(arr2[position])) == 0)) {
position++;
}
diff = (diff != 0) ? diff : (arr1.length - arr2.length);
//若curV大于reqV则返回true
return diff > 0;
} else {
//输入为空
console.log("版本号不能为空");
return false;
}
}
public static _TaskMap: Map<string, {[key:string]:any}[]> = new Map();
/**
* 存储启动命令但是不能把读到的最新package.json直接赋值到Map中因为这会覆盖掉前端在运行的命令状态
* 所以需要对新读到的package.json和之前保存的进行差异性对比然后不影响前数据的状态情况下修改 Map
* @param key Map kay 数据key名称
* @param val Map value 要存储的数据
*/
public static setTask(key: string, val: any): any {
if (Utils._TaskMap.has(key)) { // 判断这个项目的命令是否已经存储过
for (let list of Utils._TaskMap.values()) {
let diffArr = diff(list, val) // 进行 diff json数据结构的差异对比
if (diffArr != undefined) { // 如果有差异
diffArr.forEach((v: any) => {
switch (v.kind) { // 判断差类型
// A表示package.json发生了改变
case "A":
switch (v.item.kind) { //具体何种改变需要再判断
// 如果为N表示package.json 新增了命令,那么在不影响前面数据状态的情况下
// 合并数据
case "N":
list = [...list, v.item.rhs]
Utils._TaskMap.set(key, list)
break;
// 如果为D表示package.json 删除了命令,那么在不影响前面数据状态的情况下
// 删除数据
case "D":
list = list.filter((s: any, i: number) => i != v.index);
Utils._TaskMap.set(key, list)
break
}
break;
}
});
return Utils._TaskMap.get(key)
// 如果没有差异,直接返回数据
} else {
console.log("没有差异")
return Utils._TaskMap.get(key)
}
}
// 没有存储过,进行存储
} else {
console.log("没有存储过")
Utils._TaskMap.set(key, val)
return Utils._TaskMap.get(key)
}
}
/**
* 查找任务对象
* @param args
*/
public static findOneTask(name: string, ScriptName: string) {
let project = JsonDB.findName(name)
let TaskList = Utils._TaskMap.get(project.Fullpath);
if (TaskList !== undefined) {
let a = TaskList.filter(v => {
return v.ScriptName == ScriptName
});
a[0].path = project.Fullpath;
return a[0]
}
}
/**
* 导入项目 被 前端 和 Touchbar 用到
*/
public static async ImportProject(listener?: (...args: any[]) => void): Promise<any> {
let DirectoryPath = await Dialog.showDialog({
message: "选择您的项目",
buttonLabel: '导入项目',
properties: [ 'openDirectory', 'showHiddenFiles' ]
});
if (DirectoryPath) {
let res = await Utils.GetPathFileList(DirectoryPath[0]);
Utils.addDirWatch(res.Fullpath, () => {
if (listener) {
listener(res)
}
})
Utils.setTrayTitleNums();
Utils.updateTouchbarList();
return res;
} else {
return undefined;
}
}
/**
* 设置顶部托盘上 项目已导入的 数量
*/
public static setTrayTitleNums() {
Windows.tray.setTitle((JsonDB.project() as Array<any>).length.toString())
}
/**
* 更新 touchbar 上的 项目列表
*/
public static updateTouchbarList() {
let touch = new Touchbar();
// @ts-ignore
touch = null;
}
/**
* 数组对象的深拷贝
* @param arr
* @constructor
*/
public static DeepCopy(arr: any[]) {
let newArr: any[] = [];
arr.forEach((v:any) => {
newArr.push(Object.assign({}, v))
});
return newArr
}
/**
* 读取package.json 文件
* @param path package.json 路径
*/
public static readPackage(path: string): { [key: string]: any } {
// 清除 require 的文件缓存
delete require.cache[require.resolve(path)];
return require(path)
}
/**
* 写package.json配置文件
* @param path package.json的路径
* @param code package.json 里面的代码
*/
public static writePackage(path: string, code: string): void {
writeFileSync(path, code)
}
/**
* 获取指定路径下的文件列表
* @param path 路径
*/
public static async globbyFile(path: string): Promise<Array<string>> {
return await globby(['**'], {
cwd: path, gitignore: true, absolute: true,
ignore: ['**/node_modules/**', '**/.git/**', '**/.svn/**'],
});
}
/**
* 返回新版本
* @constructor
*/
public static NewVersion(str: string): string {
let arr: RegExpExecArray | any = /(\d+(\.\d+\.\d)?)/.exec(str);
return arr[0]
}
/**
* 根据传入的路径数组获取改路径下的文件目录结构
* @param Path 路径数组
* @constructor
*/
public static async GetPathFileList(Path: string): Promise<any> {
// 判断用户选中的路径中是否有package.json
let PackageFile: string | string[] = (await Utils.globbyFile(Path))
.filter(v => v.includes("package.json"))
// 如果有表示为前端项目
if (PackageFile.length !== 0) {
// 读取用户选中项目中的 package.json
let PackageObj: { [key: string]: any } = Utils.readPackage(PackageFile[0]);
// 用项目名从数据库中条件查询这个项目
let IsSave: undefined | { [key: string]: any } = JsonDB.isHaveName(PackageObj.name);
// 本地数据库没有存储过用户选中的项目配置
if (IsSave == undefined) {
let NewItem = {...PackageObj};
delete NewItem.scripts;
delete NewItem.devDependencies;
delete NewItem.dependencies;
let selectProject = {
Fullpath: Path,
FolderName: parse(Path).name,
CurrentTabs: 'outline',
FolderIcon: '../../assets/img/GenericFolder.png',
TaskRunList: [], // 前端的启动命令
DependentList: [],// 前端的依赖
...NewItem
}
// 将package.json整个对象存入 json 数据库中json数据库路径为/Users/bmy/.project/db.json
JsonDB.saveProject(selectProject);
return selectProject;
} else {
return undefined
}
// 不是前端项目
} else {
console.log("不是前端项目")
return undefined
}
}
/**
* 返回相对路径
* @param path 路径
* @constructor
*/
public static GetFilePath(path: string): string {
return join(__dirname, path)
}
/**
* 根据配置返回对应的controller类require后的类
* @constructor
*/
public static GetController(): any {
return require(`../controller/${(Config.StartPage.split("/"))[0]}.js`)
}
/**
* Home.controller 拆分为数组然后controller的首字母C大写
* 然后返回 HomeController 的类名
*/
public static toUpperCase(cont: string): string {
let Controller = cont.split(".")
return Controller[0] + Controller[1].charAt(0).toUpperCase() + Controller[1].slice(1);
}
/**
* 系统通知
* @param parmas
* @constructor
*/
public static Notification(parmas: NotificationConstructorOptions): void {
new Notification(parmas).show();
}
/**
* 下载资源
* @param url 需要下载的资源地址
* @param path 下载后需要保存的路径
* @constructor
*/
public static DownFile(url: string, path: PathLike) {
Windows.CurrentBrowserWindow.webContents.downloadURL(url)
Windows.CurrentBrowserWindow.webContents.session.on(
"will-download",
(event: any, item: any, webContents: any) => {
item.setSavePath(`${path}/${item.getFilename()}`);
item.once('done', (event: any, state: any) => {
if (state === 'completed') {
// 下载成功后显示通知
Utils.Notification({
title: '下载完成',
body: `您的视频 ${item.getFilename()} 已成功下载!`,
silent: true,
})
}
})
}
)
}
/**
* 创建文件夹
* @param name 文件夹
* @param html html
*/
public static mkdir(name: string, html: string): void {
let path = join(__dirname, `../../application/page/${name}`);
existsSync(path) ? null : mkdirSync(path)
Utils.mkFile(path, name, html)
}
/**
* 生成文件并写入数据
* @param path 路径
* @param name html文件名
* @param html html数据
*/
public static mkFile(path: string, name: string, html: string): void {
writeFileSync(`${path}/${name}.html`, html)
}
/**
* 创建启动窗口
* @param target
* @param name
*/
public static async startWindows(target: any, name: string, params?: object) {
// 如果项目已经打包过了,则无需再次创建文件
if (!app.isPackaged) {
let data: { [p: string]: any } = await target[name](params),
Dom: string = renderFile(
Utils.GetFilePath(`../../../src/application/page/${name}/${name}.jade`),
Object.assign(Config.jadeCompile0ptions, data),
);
// 在dist/ 创建并生成对应的html文件
Utils.mkdir(name, Dom)
}
try {
// @ts-ignore
Windows.CurrentBrowserWindow = new BrowserWindow(Config.PageSize[name]);
// @ts-ignore
Windows.CurrentBrowserWindow.loadFile(<string>Config.PagePath[name]);
// 当点击关闭按钮
Windows.CurrentBrowserWindow.on('close', (e: any) => {
console.log(" 阻止退出程序,重点...")
// // 阻止退出程序,重点
e.preventDefault();
// 隐藏主程序窗口
Windows.CurrentBrowserWindow.hide()
})
if (name == "Home") {
// 创建顶部托盘
new TrayInteractive()
// 创建 touchbar
new Touchbar()
// 设置关于的信息
app.setAboutPanelOptions(Config.appAbout)
Utils.ShowWindows()
}
Windows.CurrentBrowserWindow.show()
} catch (e) {
throw new Error("创建窗体失败请检查配置文件窗体的html路径是否正确")
}
}
public static ShowWindows() {
let opacity = 0
const time = setInterval(() => {
if (opacity > 1) {
opacity = 1
clearInterval(time)
} else {
Windows.CurrentBrowserWindow.setOpacity(opacity)
opacity = parseFloat((opacity + 0.1).toFixed(1))
if (!Windows.CurrentBrowserWindow.isVisible()) {
Windows.CurrentBrowserWindow.show()
}
}
}, 30)
}
}

114
src/core/utils/db.utils.ts Executable file
View File

@@ -0,0 +1,114 @@
import Lowdb from "lowdb";
import JSONFile from "lowdb/adapters/FileSync"
import { homedir } from "os";
import { resolve } from "path";
import { ensureDirSync } from "fs-extra";
class Db {
public db!: Lowdb.lowdb & {
defaults: (arg: { [key:string]: any }) => any;
get: (arg: string) => any;
unset: (asg: string) => any;
read: () => any;
};
public ConfirPath: string = `${homedir()}/.project`;
constructor () {
this.initDBSystem();
}
public initDBSystem() {
ensureDirSync(this.ConfirPath)
// @ts-ignore
this.db = new Lowdb(new JSONFile(resolve(this.ConfirPath, 'db.json')));
this.db.defaults({
projects: [],
setting: {
envVariable: '/usr/local/bin',
isOpen: false,
isShowDockIcon: false,
selectThemeIndex: 1,
mask: {
open: true,
opacity: 0.4,
blur: 0
},
packageTypeSelect: 0,
packageType: [
"npm", "cnpm", "yarn", "pnpm"
],
list: [
{ system: false, type: 'color', text: '亮色', BgColor: 'rgb(227 224 224 / 80%)', textColor: '#222', activeBgColor: '#c5c2c2' },
{ system: false, type: 'color', text: '暗色', BgColor: 'rgb(35 37 47 / 80%)', textColor: '#c3d1e9', activeBgColor: '#434b5c' },
{ system: false, type: 'color', text: '护眼', BgColor: 'rgb(199 235 202 / 80%)', textColor: '#405840', activeBgColor: '#d4f0d6' },
{ system: false, type: 'color', text: '深邃', BgColor: 'rgb(22 32 47 / 80%)', textColor: '#a0b4c8', activeBgColor: '#1c283b' },
{ system: false, type: 'color', text: '盎然', BgColor: 'rgb(121 149 147 / 80%)', textColor: '#d6e1df', activeBgColor: 'rgb(104 165 160 / 95%)' },
{ system: false, type: 'color', text: '肃穆蓝', BgColor: 'rgb(100 130 192 / 80%)', textColor: '#d6e1df', activeBgColor: 'rgb(106 144 215 / 95%)' },
{ system: false, type: 'color', text: '少女粉', BgColor: 'rgb(175 82 119 / 80%)', textColor: '#d6e1df', activeBgColor: 'rgb(197 93 134 / 95%)' },
{ system: false, type: 'color', text: '基佬紫', BgColor: 'rgb(187 64 171 / 80%)', textColor: '#d6e1df', activeBgColor: 'rgb(209 72 191 / 95%)' },
{ system: false, type: 'color', text: '活力橙', BgColor: 'rgb(203 165 61 / 80%)', textColor: '#d6e1df', activeBgColor: 'rgb(219 178 68 / 95%)' },
{ system: false, type: 'img', text: '少女', BgColor: 'url("https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fi0.hdslb.com%2Fbfs%2Farticle%2Ffbd24661b2186b274a9214dafcbf54c99e4b67e2.jpg&refer=http%3A%2F%2Fi0.hdslb.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1653329712&t=c05effa20c49faa5c73198f63a51c0f0")', textColor: '#d6e1df', activeBgColor: 'rgb(26 28 42 / 77%)' },
{ system: false, type: 'img', text: '地球', BgColor: 'url("https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fi0.hdslb.com%2Fbfs%2Farticle%2F903b941d059186ccacaff616e8258a3f02964216.jpg&refer=http%3A%2F%2Fi0.hdslb.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1653330780&t=21fdce669ec228a2670dc938a2f4c809")', textColor: '#d6e1df', activeBgColor: 'rgb(95 22 10 / 86%)' },
{ system: false, type: 'img', text: '泛舟', BgColor: 'url("https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fimg1.doubanio.com%2Fview%2Frichtext%2Flarge%2Fpublic%2Fp231769778.jpg&refer=http%3A%2F%2Fimg1.doubanio.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1653330978&t=ee3158860a014253d882e78d84139cf7")', textColor: '#d6e1df', activeBgColor: 'rgb(106 103 58 / 88%)' },
{ system: false, type: 'img', text: '汽车', BgColor: 'url("https://img2.baidu.com/it/u=2222928740,2967545615&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=1028")', textColor: '#d6e1df', activeBgColor: 'rgb(12 13 13 / 72%)' },
{ system: false, type: 'img', text: '海绵宝宝', BgColor: 'url("https://c-ssl.duitang.com/uploads/blog/202110/10/20211010134724_b9435.thumb.1000_0.jpeg")', textColor: '#d6e1df', activeBgColor: 'rgb(239 81 79 / 65%)' },
{ system: false, type: 'img', text: '七龙珠', BgColor: 'url("https://c-ssl.duitang.com/uploads/blog/202106/05/20210605202326_66fd6.thumb.1000_0.jpg")', textColor: '#d6e1df', activeBgColor: 'rgb(197 140 3 / 86%)' },
{ system: false, type: 'img', text: '唯美', BgColor: 'url("https://c-ssl.duitang.com/uploads/blog/202110/31/20211031124432_ea58c.thumb.1000_0.jpeg")', textColor: '#d6e1df', activeBgColor: 'rgb(197 140 3 / 86%)' }
]
}
}).write()
}
/**
* 根据项目名查找数据库的指定项目
* @param name
*/
public findName(name: string, key: string = "FolderName" ,table: string = "projects"): { [key: string]: any} {
return this.db.get(table).find({ [ key ]: name }).value()
}
public findOne(key: string = "", table: string = "projects"): any {
return this.db.get(`${table}.${key}`).value()
}
public isHaveName(name: string, table: string = "projects"): { [key: string]: any} {
return this.db.get(table).find({ "name": name }).value()
}
/**
* 获取所有项目
*/
public project(table: string = "projects"): any {
return this.db.get(table).value();
}
public remove(obj: { [key: string]: any}, table: string = "projects"): undefined | { [key: string]: any} {
return this.db.get(table)
.remove(obj)
.write();
}
/**
* 修改表中的数据
* @param key 表下的对象名
* @param value 数值
* @param table 哪张表
*/
public update(key: string, value: any, table: string = "setting"): void {
this.db.read().set(`${table}.${key}`, value).write()
}
/**
* 保存项目至数据库
* @param obj
*/
public saveProject(obj: { [key: string]: any}, table: string = "projects"): void {
this.db.get(table)
.push(obj)
.write()
}
}
export default new Db();

31
src/core/utils/logs.utils.ts Executable file
View File

@@ -0,0 +1,31 @@
import moment from "moment"
import { appendFileSync } from "fs"
import { join } from "path";
import Config from "@config/Index.config";
import { Injectable } from "@annotation/Ioc.annotation";
import os from "os"
@Injectable()
export class LogsUtils {
/**
* 返回日志文件名 application_2020-07-16_21-03-21.log
* @param level 日志类型
* @constructor
*/
public GetTime(level: string = "application"): string {
return `${level}_${moment().format("YYYY-MM-DD-HH")}.log`;
}
/**
* 传入数据生成日志
* @param data 数据
* @param level 日志类型
*/
public logs(data: string, level: string = "application"): void {
appendFileSync(join(
__dirname,
`${Config.LogsPath}/${this.GetTime(level)}`), `${moment().format("YYYY/MM/DD HH:mm:ss")}${data}${os.EOL}${os.EOL}`)
}
}

View File

@@ -0,0 +1,87 @@
import JsonDB from "@utils/db.utils";
import { Injectable } from "@annotation/Ioc.annotation";
@Injectable()
export class Package {
/**
* 获取当前你选择的包管理工具
*/
public getPackageTool(): string {
let allType = JsonDB.findOne("packageType", "setting");
let index = JsonDB.findOne("packageTypeSelect", "setting");
return allType[index];
}
/**
* 初始化 和 安装软件的命令
* @param type
* i 初始化依赖
* other 安装单个插件
*/
install(type: string = "", arg?: any) {
// 如果是npm 和 cnpm
let tool = this.getPackageTool();
if (type == "other") {
// 阶段 true 正式阶段 false 开发阶段
let stage: boolean = arg.status == 0 ? true : false;
if (tool == "npm" || tool == "cnpm") {
return `${tool} i --${stage ? 'save' : 'save-dev'} ${arg.name}`;
} else if (tool == "yarn") {
return `${tool} add ${stage ? '' : '--dev'} ${arg.name}`
} else if (tool == "pnpm") {
return `${tool} add ${stage ? '' : '-D'} ${arg.name}`
}
} else {
return `${tool} install`
}
}
/**
* 删除包
*/
uninstall(arg?: any) {
// 如果是npm 和 cnpm
let tool = this.getPackageTool();
// 阶段 true 正式阶段 false 开发阶段
let stage: boolean = arg.class == 0 ? true : false;
if (tool == "npm" || tool == "cnpm") {
return `${tool} uninstall --${stage ? 'save' : 'save-dev'} ${arg.name}`;
} else if (tool == "yarn") {
return `${tool} remove ${arg.name}`
} else if (tool == "pnpm") {
return `${tool} rm ${stage ? '--save-prod' : '--save-dev'} ${arg.name}`
}
}
/**
* 运行 script 命令
* @param script
*/
run(script: string) {
let tool = this.getPackageTool();
return `${tool} run ${script}`
}
/**
* 更新软件包
* @param script
*/
up(arg?: any) {
let tool = this.getPackageTool();
// 阶段 true 正式阶段 false 开发阶段
let stage: boolean = arg.type == 0 ? true : false;
if (tool == "npm" || tool == "cnpm") {
return `${tool} i --${stage ? 'save' : 'save-dev'} ${arg.name}@latest`;
} else if (tool == "yarn") {
return `${tool} upgrade --latest ${arg.name}`
} else if (tool == "pnpm") {
return `${tool} up --latest ${arg.name}`
}
}
}

View File

@@ -0,0 +1,165 @@
// import { Injectable } from "@annotation/Ioc.annotation";
import { exec } from "child_process";
export interface ProcessItem {
name: string;
cmd: string;
pid: number;
ppid: number;
load: number;
mem: number;
children?: ProcessItem[];
}
// @Injectable()
export class ProcessUtils {
private map = new Map<number, ProcessItem>();
private rootItem!: ProcessItem;
private rootPid!: number;
/**
* 使用ps命令获取所有进程
* @constructor
*/
public GetAllProcess(pid: number) {
this.rootPid = pid;
// @ts-ignore
this.rootItem = {};
return new Promise((resolve, reject) => {
exec('which ps', {}, (err, stdout, stderr) => {
if (err || stderr) {
resolve({ message: 'ps not found', isRunScript: false });
} else {
const ps = stdout.toString().trim();
const args = '-ax -o pid=,ppid=,pcpu=,pmem=,command=';
exec(`${ps} ${args}`, {
maxBuffer: 1000 * 1024, env: {LC_NUMERIC: 'en_US.UTF-8'}
}, (err, stdout, stderr) => {
if (err || (stderr && !stderr.includes('screen size is bogus'))) {
resolve({ message: 'ps is error', isRunScript: false });
} else {
this.parsePsOutput(stdout, this.addToTree);
if (Object.keys(this.rootItem).length == 0) {
resolve({ message: `Root process pid: ${this.rootPid} not found`, isRunScript: false });
} else {
resolve(this.rootItem);
}
}
})
}
})
})
}
private parsePsOutput(stdout: string, addToTree: (pid: number, ppid: number, cmd: string, load: number, mem: number) => void): void {
const PID_CMD = /^\s*([0-9]+)\s+([0-9]+)\s+([0-9]+\.[0-9]+)\s+([0-9]+\.[0-9]+)\s+(.+)$/;
const lines = stdout.toString().split('\n');
for (const line of lines) {
const matches = PID_CMD.exec(line.trim());
if (matches && matches.length === 6) {
this.addToTree(parseInt(matches[1]), parseInt(matches[2]), matches[5], parseFloat(matches[3]), parseFloat(matches[4]));
}
}
}
private addToTree(pid: number, ppid: number, cmd: string, load: number, mem: number): void {
const parent = this.map.get(ppid);
if (pid === this.rootPid || parent) {
const item = {
name: this.findName(cmd),
cmd,
pid,
ppid,
load,
mem
};
this.map.set(pid, item);
if (pid === this.rootPid) {
this.rootItem = item;
}
if (parent) {
if (!parent.children) {
parent.children = [];
}
parent.children.push(item);
if (parent.children.length > 1) {
parent.children = parent.children.sort((a, b) => a.pid - b.pid);
}
}
}
}
private findName(cmd: string): string {
const SHARED_PROCESS_HINT = /--vscode-window-kind=shared-process/;
const ISSUE_REPORTER_HINT = /--vscode-window-kind=issue-reporter/;
const PROCESS_EXPLORER_HINT = /--vscode-window-kind=process-explorer/;
const UTILITY_NETWORK_HINT = /--utility-sub-type=network/;
const WINDOWS_CRASH_REPORTER = /--crashes-directory/;
const WINDOWS_PTY = /\\pipe\\winpty-control/;
const WINDOWS_CONSOLE_HOST = /conhost\.exe/;
const TYPE = /--type=([a-zA-Z-]+)/;
// find windows crash reporter
if (WINDOWS_CRASH_REPORTER.exec(cmd)) {
return 'electron-crash-reporter';
}
// find windows pty process
if (WINDOWS_PTY.exec(cmd)) {
return 'winpty-process';
}
//find windows console host process
if (WINDOWS_CONSOLE_HOST.exec(cmd)) {
return 'console-window-host (Windows internal process)';
}
// find "--type=xxxx"
let matches = TYPE.exec(cmd);
if (matches && matches.length === 2) {
if (matches[1] === 'renderer') {
if (SHARED_PROCESS_HINT.exec(cmd)) {
return 'shared-process';
}
if (ISSUE_REPORTER_HINT.exec(cmd)) {
return 'issue-reporter';
}
if (PROCESS_EXPLORER_HINT.exec(cmd)) {
return 'process-explorer';
}
return `window`;
} else if (matches[1] === 'utility') {
if (UTILITY_NETWORK_HINT.exec(cmd)) {
return 'utility-network-service';
}
}
return matches[1];
}
// find all xxxx.js
const JS = /[a-zA-Z-]+\.js/g;
let result = '';
do {
matches = JS.exec(cmd);
if (matches) {
result += matches + ' ';
}
} while (matches);
if (result) {
if (cmd.indexOf('node ') < 0 && cmd.indexOf('node.exe') < 0) {
return `electron_node ${result}`;
}
}
return cmd;
}
}

View File

@@ -0,0 +1,60 @@
import { dialog } from "electron";
import { join } from "path";
import JsonDB from "@utils/db.utils";
import { Injectable } from "@annotation/Ioc.annotation";
const { execSync } = require("child_process");
@Injectable()
export class WhichBin {
private environment: string[] = [ 'node', 'npm', 'cnpm', 'yarn', 'pnpm' ];
private retult: (string | undefined)[] = [
'./node_modules/.bin',
process.env.PATH
]
private exec(cmd: string): string | boolean {
try {
let res = execSync(`which ${cmd}`);
return res.toString().replace(/\n/g,"");
} catch (e) {
dialog.showErrorBox("eeeee: ", e.message)
return false;
}
}
/**
* 初始nodenpm 等环境变量
*/
public async initEnvPath() {
let path: string = await JsonDB.findOne("envVariable", "setting");
// npm i -g 全局安装依赖的路径
this.retult.push(join(path, "../lib/node_modules"))
// 你选的文件夹,这里面 的 shell 可以被全局调用
this.retult.push(path)
// nodejs 路径
this.retult.push(`${path}/node`);
// npm 路径
this.retult.push(`${path}/npm`);
this.retult.push(`${path}/npx`);
process.env.PATH = this.retult.join(":");
}
public initEnvironment() {
this.environment.forEach(async v => {
let res = this.exec(v)
if (res) {
dialog.showErrorBox("res: ", res.toString())
if ((res as string).includes("/bin/node")) {
// 找到所有存放所有命令的 父级
this.retult.push((res as string).replace("/bin/node", "/bin"))
// 找到存放 npm i -g 安装的路径
this.retult.push((res as string).replace("/bin/node", "/lib/node_modules"))
}
this.retult.push((res as string))
}
});
dialog.showErrorBox("结果结果: ", JSON.stringify(this.retult))
process.env.PATH = this.retult.join(":")
}
}