first commit
This commit is contained in:
1
src/core/.pydio
Normal file
1
src/core/.pydio
Normal file
@@ -0,0 +1 @@
|
||||
42927cff-8842-4b5c-98b9-4ef72d76be57
|
||||
1
src/core/annotation/.pydio
Normal file
1
src/core/annotation/.pydio
Normal file
@@ -0,0 +1 @@
|
||||
ae18ca61-49b8-41bd-b4e6-8d01591c8336
|
||||
63
src/core/annotation/Creted.annotation.ts
Executable file
63
src/core/annotation/Creted.annotation.ts
Executable 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
40
src/core/annotation/Http.annotation.ts
Executable file
40
src/core/annotation/Http.annotation.ts
Executable 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
|
||||
}
|
||||
}
|
||||
}
|
||||
37
src/core/annotation/Ioc.annotation.ts
Executable file
37
src/core/annotation/Ioc.annotation.ts
Executable 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)])()
|
||||
}
|
||||
}
|
||||
}
|
||||
16
src/core/annotation/Run.annotation.ts
Executable file
16
src/core/annotation/Run.annotation.ts
Executable 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
1
src/core/config/.pydio
Normal file
@@ -0,0 +1 @@
|
||||
3392576f-5dfc-4270-9335-0fcac5cdbeb3
|
||||
401
src/core/config/Index.config.ts
Executable file
401
src/core/config/Index.config.ts
Executable 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请添加QQ:2271608011"
|
||||
})
|
||||
}
|
||||
},
|
||||
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请添加 QQ:2271608011"
|
||||
})
|
||||
}
|
||||
}),
|
||||
new TouchBarButton({
|
||||
label: 'Github主页',
|
||||
backgroundColor: '#3a3a3c',
|
||||
click: async () => {
|
||||
await shell.openExternal("https://github.com/helpcode")
|
||||
}
|
||||
})
|
||||
]
|
||||
})
|
||||
}),
|
||||
]
|
||||
};
|
||||
}
|
||||
1
src/core/controller/.pydio
Normal file
1
src/core/controller/.pydio
Normal file
@@ -0,0 +1 @@
|
||||
d66ac9d5-fd1e-43c7-9839-9363bad2256d
|
||||
47
src/core/controller/Home.controller.ts
Executable file
47
src/core/controller/Home.controller.ts
Executable 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: '插件详情页面'
|
||||
}
|
||||
}
|
||||
}
|
||||
1
src/core/interactive/.pydio
Normal file
1
src/core/interactive/.pydio
Normal file
@@ -0,0 +1 @@
|
||||
c4c2b685-cd72-4295-bff5-a47af45c77a9
|
||||
16
src/core/interactive/ApplicationMenu.interactive.ts
Executable file
16
src/core/interactive/ApplicationMenu.interactive.ts
Executable 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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
26
src/core/interactive/Dialog.interactive.ts
Executable file
26
src/core/interactive/Dialog.interactive.ts
Executable 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
|
||||
}
|
||||
}
|
||||
}
|
||||
0
src/core/interactive/ProgressBar.interactive.ts
Executable file
0
src/core/interactive/ProgressBar.interactive.ts
Executable file
50
src/core/interactive/Touchbar.interactive.ts
Executable file
50
src/core/interactive/Touchbar.interactive.ts
Executable 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))
|
||||
}
|
||||
}
|
||||
32
src/core/interactive/Tray.interactive.ts
Executable file
32
src/core/interactive/Tray.interactive.ts
Executable 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 })
|
||||
// }
|
||||
}
|
||||
0
src/core/interactive/dock.interactive.ts
Executable file
0
src/core/interactive/dock.interactive.ts
Executable file
1
src/core/ipc/.pydio
Normal file
1
src/core/ipc/.pydio
Normal file
@@ -0,0 +1 @@
|
||||
f5e29150-31cc-40cb-99ed-6527fc3038a9
|
||||
67
src/core/ipc/Application.ipc.ts
Executable file
67
src/core/ipc/Application.ipc.ts
Executable 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"
|
||||
});
|
||||
}
|
||||
}
|
||||
1
src/core/ipc/module/.pydio
Normal file
1
src/core/ipc/module/.pydio
Normal file
@@ -0,0 +1 @@
|
||||
74566167-ef2f-4b0c-9692-89f4aeef3960
|
||||
543
src/core/ipc/module/File.ipc.ts
Executable file
543
src/core/ipc/module/File.ipc.ts
Executable 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
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
12
src/core/ipc/module/terminate.sh
Executable file
12
src/core/ipc/module/terminate.sh
Executable 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
1
src/core/model/.pydio
Normal file
@@ -0,0 +1 @@
|
||||
0aab50fe-8286-4b21-b0e4-396e502553da
|
||||
13
src/core/model/Ioc.model.ts
Executable file
13
src/core/model/Ioc.model.ts
Executable 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
86
src/core/model/Windows.model.ts
Executable 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
1
src/core/net/.pydio
Normal file
@@ -0,0 +1 @@
|
||||
2a9bfddc-1ed2-432c-9d57-20c175ec6af6
|
||||
94
src/core/net/Http.net.ts
Executable file
94
src/core/net/Http.net.ts
Executable 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
1
src/core/run/.pydio
Normal file
@@ -0,0 +1 @@
|
||||
01b36b6b-4278-4f9f-bef5-3e385aea22fd
|
||||
13
src/core/run/Init.run.ts
Executable file
13
src/core/run/Init.run.ts
Executable 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
1
src/core/service/.pydio
Normal file
@@ -0,0 +1 @@
|
||||
60b35412-bea5-4464-8911-b1f345541f73
|
||||
3
src/core/service/Home.service.ts
Executable file
3
src/core/service/Home.service.ts
Executable file
@@ -0,0 +1,3 @@
|
||||
export interface HomeService {
|
||||
PlugInfo(params: object): Promise<any>;
|
||||
}
|
||||
1
src/core/service/impl/.pydio
Normal file
1
src/core/service/impl/.pydio
Normal file
@@ -0,0 +1 @@
|
||||
45388eb6-25cb-42d3-96f3-f48c7763b6d1
|
||||
11
src/core/service/impl/Home.impl.service.ts
Executable file
11
src/core/service/impl/Home.impl.service.ts
Executable 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
1
src/core/types/.pydio
Normal file
@@ -0,0 +1 @@
|
||||
b6ba00f3-28d3-40dd-8c8a-1848fdb6d1f8
|
||||
6
src/core/types/ApiConfig.ts
Executable file
6
src/core/types/ApiConfig.ts
Executable file
@@ -0,0 +1,6 @@
|
||||
export interface ApiUrlConfig {
|
||||
BaseUrl: string,
|
||||
ApiList: {
|
||||
[key: string]: any
|
||||
}
|
||||
}
|
||||
7
src/core/types/BaseController.types.ts
Executable file
7
src/core/types/BaseController.types.ts
Executable file
@@ -0,0 +1,7 @@
|
||||
export interface BaseControllerTypes {
|
||||
// render(): { [ key:string ]: any };
|
||||
ipc(): void;
|
||||
ui(): void;
|
||||
event(): void;
|
||||
monitor(): void;
|
||||
}
|
||||
14
src/core/types/PageConfig.types.ts
Executable file
14
src/core/types/PageConfig.types.ts
Executable 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
7
src/core/types/Tray.types.ts
Executable 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
1
src/core/utils/.pydio
Normal file
@@ -0,0 +1 @@
|
||||
e5806741-6f78-4a3d-b60d-f973b650ad7a
|
||||
465
src/core/utils/Index.utils.ts
Executable file
465
src/core/utils/Index.utils.ts
Executable 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
114
src/core/utils/db.utils.ts
Executable 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
31
src/core/utils/logs.utils.ts
Executable 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}`)
|
||||
}
|
||||
}
|
||||
|
||||
87
src/core/utils/package.utils.ts
Normal file
87
src/core/utils/package.utils.ts
Normal 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}`
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
165
src/core/utils/process.utils.ts
Normal file
165
src/core/utils/process.utils.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
60
src/core/utils/whichBin.utils.ts
Normal file
60
src/core/utils/whichBin.utils.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始node,npm 等环境变量
|
||||
*/
|
||||
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(":")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user