mjpic 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.trae/documents/mjpic-prd.md +111 -0
- package/.trae/documents/mjpic-technical-architecture.md +234 -0
- package/README.md +57 -0
- package/api/app.ts +60 -0
- package/api/cli.ts +61 -0
- package/api/index.ts +19 -0
- package/api/routes/auth.ts +33 -0
- package/api/routes/image.ts +27 -0
- package/api/server.ts +45 -0
- package/dist/cli/app.js +43 -0
- package/dist/cli/cli.js +49 -0
- package/dist/cli/index.js +13 -0
- package/dist/cli/routes/auth.js +28 -0
- package/dist/cli/routes/image.js +21 -0
- package/dist/cli/server.js +38 -0
- package/dist/client/assets/index-BUIYLOn-.js +197 -0
- package/dist/client/assets/index-BoiS81Ei.css +1 -0
- package/dist/client/favicon.svg +4 -0
- package/dist/client/index.html +354 -0
- package/eslint.config.js +28 -0
- package/index.html +24 -0
- package/nodemon.json +10 -0
- package/package.json +68 -0
- package/postcss.config.js +10 -0
- package/public/favicon.svg +4 -0
- package/src/App.tsx +13 -0
- package/src/assets/react.svg +1 -0
- package/src/components/Empty.tsx +8 -0
- package/src/components/dialogs/AspectRatioDialog.tsx +218 -0
- package/src/components/dialogs/SaveDialog.tsx +150 -0
- package/src/components/layout/CanvasArea.tsx +874 -0
- package/src/components/layout/Header.tsx +156 -0
- package/src/components/layout/RightPanel.tsx +886 -0
- package/src/components/layout/Sidebar.tsx +36 -0
- package/src/components/layout/StatusBar.tsx +44 -0
- package/src/hooks/useDebounce.ts +17 -0
- package/src/hooks/useTheme.ts +29 -0
- package/src/i18n/index.ts +26 -0
- package/src/i18n/locales/en.json +56 -0
- package/src/i18n/locales/zh.json +59 -0
- package/src/index.css +14 -0
- package/src/lib/utils.ts +73 -0
- package/src/main.tsx +11 -0
- package/src/pages/Home.tsx +72 -0
- package/src/store/useImageStore.ts +316 -0
- package/src/store/usePresetStore.ts +65 -0
- package/src/store/useUIStore.ts +17 -0
- package/src/vite-env.d.ts +1 -0
- package/tailwind.config.js +13 -0
- package/tmp/guangxi.jpg +0 -0
- package/tsconfig.json +40 -0
- package/tsconfig.server.json +15 -0
- package/vercel.json +12 -0
- package/vite.config.ts +50 -0
- package//345/217/202/350/200/203/345/233/276/347/211/207//346/210/252/345/261/2172026-02-18 16.47.45_/345/233/276/347/211/207/345/260/272/345/257/270/350/260/203/350/212/202_/351/242/204/350/256/276/345/260/272/345/257/270.jpg +0 -0
- package//345/217/202/350/200/203/345/233/276/347/211/207//346/210/252/345/261/2172026-02-18 16.47.51_/345/233/276/347/211/207/345/260/272/345/257/270/350/260/203/350/212/202_/346/211/213/345/267/245/350/276/223/345/205/245/345/260/272/345/257/270.jpg +0 -0
- package//345/217/202/350/200/203/345/233/276/347/211/207//346/210/252/345/261/2172026-02-18 16.54.56_/345/233/276/347/211/207/345/260/272/345/257/270/350/260/203/350/212/202_/346/267/273/345/212/240/345/270/270/347/224/250/345/260/272/345/257/270.jpg +0 -0
- package//345/217/202/350/200/203/345/233/276/347/211/207//346/210/252/345/261/2172026-02-18 16.55.11_/345/233/276/347/211/207/345/260/272/345/257/270/350/260/203/350/212/202_/345/210/240/351/231/244/345/270/270/347/224/250/345/260/272/345/257/270.jpg +0 -0
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
## 1. 产品概述
|
|
2
|
+
|
|
3
|
+
mjpic(敏捷图片)是一个网页版图片处理工具,通过npm包形式分发,用户可通过命令行安装并在本地启动web服务进行图片处理。主要解决用户在不同操作系统上快速进行图片基础编辑的需求,无需安装复杂的桌面软件。
|
|
4
|
+
|
|
5
|
+
目标用户:需要在本地进行简单图片处理的设计师、开发者、普通用户。产品定位为轻量级、跨平台的图片处理解决方案。
|
|
6
|
+
|
|
7
|
+
## 2. 核心功能
|
|
8
|
+
|
|
9
|
+
### 2.1 用户角色
|
|
10
|
+
|
|
11
|
+
本产品为单用户本地应用,无需用户注册和登录系统。
|
|
12
|
+
|
|
13
|
+
### 2.2 功能模块
|
|
14
|
+
|
|
15
|
+
mjpic包含以下核心页面:
|
|
16
|
+
|
|
17
|
+
1. **主编辑页面**:图片显示区域、工具栏、文件操作菜单
|
|
18
|
+
2. **文件选择页面**:本地文件选择器(系统原生)
|
|
19
|
+
|
|
20
|
+
### 2.3 页面详情
|
|
21
|
+
|
|
22
|
+
| 页面名称 | 模块名称 | 功能描述 |
|
|
23
|
+
| ----- | ------- | ----------------------------------------- |
|
|
24
|
+
| 主编辑页面 | 图片显示区 | 加载并显示jpg、png、webp格式图片,支持拖拽打开文件 |
|
|
25
|
+
| 主编辑页面 | 图像增强工具栏 | 自动美化、一键补光、自动白平衡 |
|
|
26
|
+
| 主编辑页面 | 手动调整工具栏 | 亮度、对比度、饱和度、清晰度调节滑块 |
|
|
27
|
+
| 主编辑页面 | 尺寸设置工具 | 预设尺寸(3840px、850px、600px、450px),保持比例自动调节高度 |
|
|
28
|
+
| 主编辑页面 | 裁剪工具 | 预设比例裁剪(1:1、3:2、4:3、9:16、16:9) |
|
|
29
|
+
| 主编辑页面 | 旋转工具 | 左/右旋转、精细旋转(±0.5度)、拉水平线校正 |
|
|
30
|
+
| 主编辑页面 | 边框工具 | 添加简单边框、纹理边框,支持边框宽度和颜色调整 |
|
|
31
|
+
| 主编辑页面 | 基础工具栏 | 放大、缩小、移动、撤销、重做 |
|
|
32
|
+
| 主编辑页面 | 文件菜单 | 打开文件、保存(覆盖原文件)、另存为(选择新路径) |
|
|
33
|
+
| 主编辑页面 | 状态栏 | 显示当前图片尺寸、缩放比例、文件路径 |
|
|
34
|
+
|
|
35
|
+
## 3. 核心流程
|
|
36
|
+
|
|
37
|
+
用户操作流程:
|
|
38
|
+
|
|
39
|
+
1. 命令行启动:用户通过`mjpic`命令启动本地web服务
|
|
40
|
+
2. 打开图片:通过文件选择器或拖拽方式打开本地图片文件
|
|
41
|
+
3. 图像增强:使用一键自动美化、补光、白平衡等智能功能
|
|
42
|
+
4. 手动调整:通过滑块调节亮度、对比度、饱和度、清晰度
|
|
43
|
+
5. 尺寸设置:选择预设尺寸或自定义尺寸,保持比例调整
|
|
44
|
+
6. 裁剪操作:选择预设比例进行精确裁剪
|
|
45
|
+
7. 旋转校正:使用快速旋转或精细旋转,支持拉水平线校正
|
|
46
|
+
8. 边框添加:选择边框样式、宽度和颜色
|
|
47
|
+
9. 保存输出:选择保存覆盖原文件或另存为新文件
|
|
48
|
+
|
|
49
|
+
```mermaid
|
|
50
|
+
graph TD
|
|
51
|
+
A[命令行启动 mjpic] --> B[浏览器打开主编辑页面]
|
|
52
|
+
B --> C[选择或拖拽打开图片]
|
|
53
|
+
C --> D[图片显示在编辑区]
|
|
54
|
+
D --> E{选择编辑功能}
|
|
55
|
+
E --> F[图像增强: 自动美化/补光/白平衡]
|
|
56
|
+
E --> G[手动调整: 亮度/对比度/饱和度/清晰度]
|
|
57
|
+
E --> H[尺寸设置: 预设尺寸/保持比例]
|
|
58
|
+
E --> I[裁剪: 预设比例裁剪]
|
|
59
|
+
E --> J[旋转: 快速/精细/水平校正]
|
|
60
|
+
E --> K[边框: 样式/宽度/颜色]
|
|
61
|
+
F --> L[实时预览效果]
|
|
62
|
+
G --> L
|
|
63
|
+
H --> L
|
|
64
|
+
I --> L
|
|
65
|
+
J --> L
|
|
66
|
+
K --> L
|
|
67
|
+
L --> M[保存或另存为]
|
|
68
|
+
M --> N[覆盖原文件或保存新文件]
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## 4. 用户界面设计
|
|
72
|
+
|
|
73
|
+
### 4.1 设计风格
|
|
74
|
+
|
|
75
|
+
* **主色调**:深灰色(#2D3748)为主背景,蓝色(#3182CE)为强调色
|
|
76
|
+
|
|
77
|
+
* **按钮样式**:扁平化设计,圆角矩形,hover状态有明显颜色变化
|
|
78
|
+
|
|
79
|
+
* **字体**:系统默认字体,主要文字14-16px,标题18-24px
|
|
80
|
+
|
|
81
|
+
* **布局风格**:左侧工具栏+中央图片编辑区+顶部菜单栏的经典布局
|
|
82
|
+
|
|
83
|
+
* **图标风格**:简洁的线性图标,使用开源图标库
|
|
84
|
+
|
|
85
|
+
### 4.2 页面设计概述
|
|
86
|
+
|
|
87
|
+
| 页面名称 | 模块名称 | UI元素 |
|
|
88
|
+
| ----- | ------- | ---------------------------------------------------------------- |
|
|
89
|
+
| 主编辑页面 | 顶部菜单栏 | 文件菜单(打开、保存、另存为)、编辑菜单(撤销、重做)、视图选项,深色背景,高度48px |
|
|
90
|
+
| 主编辑页面 | 左侧工具栏 | 分组图标按钮:增强(魔法棒)、调整(滑块)、尺寸(标尺)、裁剪(虚线框)、旋转(箭头)、边框(方形),宽度60px,图标28px |
|
|
91
|
+
| 主编辑页面 | 右侧调整面板 | 根据选中工具显示对应控制面板:滑块控件、预设按钮、数值输入框,宽度280px |
|
|
92
|
+
| 主编辑页面 | 中央编辑区 | 画布区域显示图片,支持拖拽、缩放操作,背景为棋盘格透明背景,显示网格线辅助线 |
|
|
93
|
+
| 主编辑页面 | 底部状态栏 | 显示图片信息(尺寸、格式、缩放比例、当前操作),高度28px,文字12px |
|
|
94
|
+
| 主编辑页面 | 顶部快捷工具栏 | 常用操作按钮:自动美化、撤销、重做、放大、缩小,高度36px |
|
|
95
|
+
|
|
96
|
+
### 4.3 响应式设计
|
|
97
|
+
|
|
98
|
+
桌面优先设计,主要适配1024x768及以上分辨率。支持窗口大小调整时图片编辑区域自动适配。
|
|
99
|
+
|
|
100
|
+
### 4.4 性能要求
|
|
101
|
+
|
|
102
|
+
* 图片加载:单张图片加载时间不超过2秒(10MB以内文件)
|
|
103
|
+
|
|
104
|
+
* 编辑操作:实时预览,响应时间不超过100ms(基础调整),复杂算法不超过500ms
|
|
105
|
+
|
|
106
|
+
* 内存占用:处理单张图片时内存占用不超过500MB
|
|
107
|
+
|
|
108
|
+
* 批量处理:支持撤销/重做栈深度不少于20步
|
|
109
|
+
|
|
110
|
+
* 算法性能:自动美化等复杂算法采用Web Worker异步处理
|
|
111
|
+
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
## 1. 架构设计
|
|
2
|
+
|
|
3
|
+
```mermaid
|
|
4
|
+
graph TD
|
|
5
|
+
A[用户命令行] --> B[Node.js CLI服务]
|
|
6
|
+
B --> C[静态文件服务器]
|
|
7
|
+
C --> D[React前端应用]
|
|
8
|
+
D --> E[Canvas API]
|
|
9
|
+
E --> F[本地文件系统]
|
|
10
|
+
|
|
11
|
+
subgraph "CLI层"
|
|
12
|
+
B
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
subgraph "服务层"
|
|
16
|
+
C
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
subgraph "前端层"
|
|
20
|
+
D
|
|
21
|
+
E
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
subgraph "数据层"
|
|
25
|
+
F
|
|
26
|
+
end
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## 2. 技术描述
|
|
30
|
+
|
|
31
|
+
* **前端**: React\@18 + TypeScript + Vite + TailwindCSS
|
|
32
|
+
- **Canvas操作库**: konva.js\@9 或 fabric.js\@5(用于复杂图像变换)
|
|
33
|
+
- **图像算法库**:
|
|
34
|
+
- canvas-filters\@1(图像滤镜算法)
|
|
35
|
+
- color-blend\@3(颜色空间转换)
|
|
36
|
+
- histogram-js\@1(直方图分析)
|
|
37
|
+
|
|
38
|
+
* **初始化工具**: vite-init
|
|
39
|
+
|
|
40
|
+
* **后端**: Node.js\@18 + Commander.js(CLI工具)
|
|
41
|
+
|
|
42
|
+
* **图片处理**: Canvas API(浏览器端)+ Sharp(Node.js端,可选)+ 自定义图像算法
|
|
43
|
+
- 图像增强:自动色阶算法、直方图均衡化
|
|
44
|
+
- 一键补光:伽马校正+阴影高光恢复
|
|
45
|
+
- 自动白平衡:灰度世界算法+色温估计
|
|
46
|
+
- 清晰度调节:USM锐化算法(Unsharp Mask)
|
|
47
|
+
- 尺寸调整:Lanczos重采样算法
|
|
48
|
+
- 旋转校正:霍夫变换检测水平线
|
|
49
|
+
|
|
50
|
+
* **包管理**: npm包分发,支持全局安装
|
|
51
|
+
|
|
52
|
+
## 3. 路由定义
|
|
53
|
+
|
|
54
|
+
| 路由 | 用途 |
|
|
55
|
+
| ----- | ---------------- |
|
|
56
|
+
| / | 主编辑页面,图片处理主界面 |
|
|
57
|
+
| /open | 文件选择接口,返回选择的图片文件 |
|
|
58
|
+
| /save | 保存文件接口,处理图片保存请求 |
|
|
59
|
+
|
|
60
|
+
## 4. CLI命令定义
|
|
61
|
+
|
|
62
|
+
### 4.1 核心命令
|
|
63
|
+
|
|
64
|
+
启动服务命令
|
|
65
|
+
|
|
66
|
+
```
|
|
67
|
+
mjpic [options]
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
参数:
|
|
71
|
+
|
|
72
|
+
| 参数名称 | 参数类型 | 是否必需 | 描述 |
|
|
73
|
+
| --------- | ------- | ----- | ---------------- |
|
|
74
|
+
| --port | number | false | 服务端口,默认3000 |
|
|
75
|
+
| --host | string | false | 服务主机,默认localhost |
|
|
76
|
+
| --version | boolean | false | 显示版本号 |
|
|
77
|
+
|
|
78
|
+
## 5. 服务器架构
|
|
79
|
+
|
|
80
|
+
```mermaid
|
|
81
|
+
graph TD
|
|
82
|
+
A[CLI命令解析] --> B[静态文件服务]
|
|
83
|
+
B --> C[API路由处理]
|
|
84
|
+
C --> D[文件I/O操作]
|
|
85
|
+
D --> E[响应返回]
|
|
86
|
+
|
|
87
|
+
subgraph "服务器架构"
|
|
88
|
+
A
|
|
89
|
+
B
|
|
90
|
+
C
|
|
91
|
+
D
|
|
92
|
+
end
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## 6. 数据模型
|
|
96
|
+
|
|
97
|
+
### 6.1 图片处理配置模型
|
|
98
|
+
|
|
99
|
+
```typescript
|
|
100
|
+
interface ImageConfig {
|
|
101
|
+
rotation: number; // 旋转角度(支持0.5度精度)
|
|
102
|
+
scale: number; // 缩放比例
|
|
103
|
+
brightness: number; // 亮度调节(-100到+100)
|
|
104
|
+
contrast: number; // 对比度调节(-100到+100)
|
|
105
|
+
saturation: number; // 饱和度调节(-100到+100)
|
|
106
|
+
sharpness: number; // 清晰度调节(0到100)
|
|
107
|
+
enhancements: {
|
|
108
|
+
autoEnhance: boolean; // 自动美化
|
|
109
|
+
fillLight: boolean; // 一键补光
|
|
110
|
+
autoWhiteBalance: boolean; // 自动白平衡
|
|
111
|
+
};
|
|
112
|
+
resize?: {
|
|
113
|
+
width: number; // 目标宽度
|
|
114
|
+
height?: number; // 目标高度(可选,保持比例时自动计算)
|
|
115
|
+
maintainAspectRatio: boolean; // 保持宽高比
|
|
116
|
+
};
|
|
117
|
+
crop?: {
|
|
118
|
+
x: number;
|
|
119
|
+
y: number;
|
|
120
|
+
width: number;
|
|
121
|
+
height: number;
|
|
122
|
+
aspectRatio?: string; // 预设比例:'1:1' | '3:2' | '4:3' | '9:16' | '16:9'
|
|
123
|
+
};
|
|
124
|
+
border?: {
|
|
125
|
+
width: number;
|
|
126
|
+
color: string;
|
|
127
|
+
style: 'solid' | 'texture';
|
|
128
|
+
};
|
|
129
|
+
straighten?: {
|
|
130
|
+
angle: number; // 水平校正角度
|
|
131
|
+
line?: { // 用户绘制的水平线
|
|
132
|
+
x1: number;
|
|
133
|
+
y1: number;
|
|
134
|
+
x2: number;
|
|
135
|
+
y2: number;
|
|
136
|
+
};
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
interface ProcessedImage {
|
|
141
|
+
originalPath: string;
|
|
142
|
+
currentConfig: ImageConfig;
|
|
143
|
+
previewData: string; // base64预览数据
|
|
144
|
+
}
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### 6.2 支持的文件格式
|
|
148
|
+
|
|
149
|
+
* **输入格式**: JPG/JPEG、PNG、WEBP、BMP、GIF(静态)
|
|
150
|
+
|
|
151
|
+
* **输出格式**: JPG、PNG、WEBP
|
|
152
|
+
|
|
153
|
+
* **最大文件大小**: 50MB
|
|
154
|
+
|
|
155
|
+
* **最大图片尺寸**: 8000x8000像素
|
|
156
|
+
|
|
157
|
+
## 7. 跨平台兼容性
|
|
158
|
+
|
|
159
|
+
### 7.1 支持的操作系统
|
|
160
|
+
|
|
161
|
+
* macOS (Intel x86\_64)
|
|
162
|
+
|
|
163
|
+
* macOS (Apple Silicon)
|
|
164
|
+
|
|
165
|
+
* Windows (x64)
|
|
166
|
+
|
|
167
|
+
* Linux (x64, ARM64)
|
|
168
|
+
|
|
169
|
+
* 银河麒麟 (ARM64架构)
|
|
170
|
+
|
|
171
|
+
### 7.2 Node.js版本要求
|
|
172
|
+
|
|
173
|
+
* 最低版本:Node.js 16.0.0
|
|
174
|
+
|
|
175
|
+
* 推荐版本:Node.js 18.0.0 或更高
|
|
176
|
+
|
|
177
|
+
### 7.3 原生依赖处理
|
|
178
|
+
|
|
179
|
+
使用纯JavaScript实现,避免原生C++扩展,确保跨平台兼容性。图片处理主要依赖:
|
|
180
|
+
|
|
181
|
+
* 前端:Canvas API、原生File API
|
|
182
|
+
|
|
183
|
+
* 后端:fs模块、path模块(Node.js内置)
|
|
184
|
+
|
|
185
|
+
## 8. 性能优化
|
|
186
|
+
|
|
187
|
+
### 8.1 图像处理性能优化
|
|
188
|
+
- **Web Worker**: 复杂图像算法在Web Worker中执行,避免阻塞主线程
|
|
189
|
+
- **Canvas缓存**: 对中间处理结果进行缓存,支持快速撤销/重做
|
|
190
|
+
- **渐进式处理**: 大图片采用分块处理,先显示低分辨率预览
|
|
191
|
+
- **GPU加速**: 使用WebGL进行矩阵运算加速(可选)
|
|
192
|
+
|
|
193
|
+
### 8.2 算法实现细节
|
|
194
|
+
- **自动美化**: 基于直方图均衡化和自动色阶调整
|
|
195
|
+
- **一键补光**: 使用伽马校正结合阴影/高光恢复算法
|
|
196
|
+
- **自动白平衡**: 灰度世界算法配合色温估计
|
|
197
|
+
- **USM锐化**: Unsharp Mask算法,可调节半径、强度、阈值
|
|
198
|
+
- **水平线检测**: 使用霍夫变换检测直线,支持用户交互绘制
|
|
199
|
+
|
|
200
|
+
### 8.3 内存管理
|
|
201
|
+
- **图片分块**: 处理大图片时采用分块策略,控制内存使用
|
|
202
|
+
- **对象池**: 重用Canvas对象和ImageData,减少GC压力
|
|
203
|
+
- **及时释放**: 处理完成后及时释放大对象引用
|
|
204
|
+
|
|
205
|
+
## 9. 构建和打包配置
|
|
206
|
+
|
|
207
|
+
### 8.1 package.json关键配置
|
|
208
|
+
|
|
209
|
+
```json
|
|
210
|
+
{
|
|
211
|
+
"name": "mjpic",
|
|
212
|
+
"version": "1.0.0",
|
|
213
|
+
"description": "敏捷图片 - 跨平台图片处理工具",
|
|
214
|
+
"main": "dist/cli.js",
|
|
215
|
+
"bin": {
|
|
216
|
+
"mjpic": "./dist/cli.js"
|
|
217
|
+
},
|
|
218
|
+
"files": [
|
|
219
|
+
"dist/",
|
|
220
|
+
"assets/"
|
|
221
|
+
],
|
|
222
|
+
"engines": {
|
|
223
|
+
"node": ">=16.0.0"
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
### 8.2 构建流程
|
|
229
|
+
|
|
230
|
+
1. 前端构建:Vite打包React应用为静态文件
|
|
231
|
+
2. CLI构建:TypeScript编译Node.js代码
|
|
232
|
+
3. 资源合并:将静态文件嵌入到CLI包中
|
|
233
|
+
4. 发布:发布到npm registry
|
|
234
|
+
|
package/README.md
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# React + TypeScript + Vite
|
|
2
|
+
|
|
3
|
+
This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
|
|
4
|
+
|
|
5
|
+
Currently, two official plugins are available:
|
|
6
|
+
|
|
7
|
+
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Babel](https://babeljs.io/) for Fast Refresh
|
|
8
|
+
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
|
|
9
|
+
|
|
10
|
+
## Expanding the ESLint configuration
|
|
11
|
+
|
|
12
|
+
If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules:
|
|
13
|
+
|
|
14
|
+
```js
|
|
15
|
+
export default tseslint.config({
|
|
16
|
+
extends: [
|
|
17
|
+
// Remove ...tseslint.configs.recommended and replace with this
|
|
18
|
+
...tseslint.configs.recommendedTypeChecked,
|
|
19
|
+
// Alternatively, use this for stricter rules
|
|
20
|
+
...tseslint.configs.strictTypeChecked,
|
|
21
|
+
// Optionally, add this for stylistic rules
|
|
22
|
+
...tseslint.configs.stylisticTypeChecked,
|
|
23
|
+
],
|
|
24
|
+
languageOptions: {
|
|
25
|
+
// other options...
|
|
26
|
+
parserOptions: {
|
|
27
|
+
project: ['./tsconfig.node.json', './tsconfig.app.json'],
|
|
28
|
+
tsconfigRootDir: import.meta.dirname,
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
})
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules:
|
|
35
|
+
|
|
36
|
+
```js
|
|
37
|
+
// eslint.config.js
|
|
38
|
+
import reactX from 'eslint-plugin-react-x'
|
|
39
|
+
import reactDom from 'eslint-plugin-react-dom'
|
|
40
|
+
|
|
41
|
+
export default tseslint.config({
|
|
42
|
+
extends: [
|
|
43
|
+
// other configs...
|
|
44
|
+
// Enable lint rules for React
|
|
45
|
+
reactX.configs['recommended-typescript'],
|
|
46
|
+
// Enable lint rules for React DOM
|
|
47
|
+
reactDom.configs.recommended,
|
|
48
|
+
],
|
|
49
|
+
languageOptions: {
|
|
50
|
+
// other options...
|
|
51
|
+
parserOptions: {
|
|
52
|
+
project: ['./tsconfig.node.json', './tsconfig.app.json'],
|
|
53
|
+
tsconfigRootDir: import.meta.dirname,
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
})
|
|
57
|
+
```
|
package/api/app.ts
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This is a API server
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import express, {
|
|
6
|
+
type Request,
|
|
7
|
+
type Response,
|
|
8
|
+
type NextFunction,
|
|
9
|
+
} from 'express'
|
|
10
|
+
import cors from 'cors'
|
|
11
|
+
import path from 'path'
|
|
12
|
+
import dotenv from 'dotenv'
|
|
13
|
+
import { fileURLToPath } from 'url'
|
|
14
|
+
import authRoutes from './routes/auth.js'
|
|
15
|
+
import imageRoutes from './routes/image.js'
|
|
16
|
+
|
|
17
|
+
// for esm mode
|
|
18
|
+
const __filename = fileURLToPath(import.meta.url)
|
|
19
|
+
const __dirname = path.dirname(__filename)
|
|
20
|
+
|
|
21
|
+
// load env
|
|
22
|
+
dotenv.config()
|
|
23
|
+
|
|
24
|
+
const app: express.Application = express()
|
|
25
|
+
|
|
26
|
+
app.use(cors())
|
|
27
|
+
app.use(express.json({ limit: '50mb' }))
|
|
28
|
+
app.use(express.urlencoded({ extended: true, limit: '50mb' }))
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* API Routes
|
|
32
|
+
*/
|
|
33
|
+
app.use('/api/auth', authRoutes)
|
|
34
|
+
app.use('/api/image', imageRoutes)
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* health
|
|
38
|
+
*/
|
|
39
|
+
app.use(
|
|
40
|
+
'/api/health',
|
|
41
|
+
(req: Request, res: Response, next: NextFunction): void => {
|
|
42
|
+
res.status(200).json({
|
|
43
|
+
success: true,
|
|
44
|
+
message: 'ok',
|
|
45
|
+
})
|
|
46
|
+
},
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* error handler middleware
|
|
51
|
+
*/
|
|
52
|
+
app.use((error: Error, req: Request, res: Response, next: NextFunction) => {
|
|
53
|
+
res.status(500).json({
|
|
54
|
+
success: false,
|
|
55
|
+
error: 'Server internal error',
|
|
56
|
+
})
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
export default app
|
|
60
|
+
|
package/api/cli.ts
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Command } from 'commander';
|
|
3
|
+
import express from 'express';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
import { fileURLToPath } from 'url';
|
|
6
|
+
import open from 'open';
|
|
7
|
+
import app from './app.js';
|
|
8
|
+
|
|
9
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
10
|
+
const __dirname = path.dirname(__filename);
|
|
11
|
+
|
|
12
|
+
const program = new Command();
|
|
13
|
+
|
|
14
|
+
program
|
|
15
|
+
.name('mjpic')
|
|
16
|
+
.description('Agile Image Processing Tool')
|
|
17
|
+
.version('1.0.0')
|
|
18
|
+
.option('-p, --port <number>', 'server port', '3000')
|
|
19
|
+
.option('--host <string>', 'server host', 'localhost')
|
|
20
|
+
.argument('[file]', 'image file to open')
|
|
21
|
+
.action(async (file, options) => {
|
|
22
|
+
const port = parseInt(options.port, 10);
|
|
23
|
+
const host = options.host;
|
|
24
|
+
|
|
25
|
+
if (file) {
|
|
26
|
+
const absPath = path.resolve(file);
|
|
27
|
+
console.log(`Opening file: ${absPath}`);
|
|
28
|
+
|
|
29
|
+
app.get('/api/current-image', (req, res) => {
|
|
30
|
+
res.json({ path: absPath });
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
app.get('/api/image-content', (req, res) => {
|
|
34
|
+
res.sendFile(absPath);
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Serve static files
|
|
39
|
+
// In production (dist), client is in ../client
|
|
40
|
+
const clientPath = path.join(__dirname, '../client');
|
|
41
|
+
|
|
42
|
+
app.use(express.static(clientPath));
|
|
43
|
+
|
|
44
|
+
// API 404 handler - Ensure API requests don't fall through to index.html
|
|
45
|
+
app.use('/api/*', (req, res) => {
|
|
46
|
+
res.status(404).json({ success: false, error: 'API not found' });
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
// SPA fallback
|
|
50
|
+
app.get('*', (req, res) => {
|
|
51
|
+
res.sendFile(path.join(clientPath, 'index.html'));
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
app.listen(port, host, () => {
|
|
55
|
+
const url = `http://${host}:${port}`;
|
|
56
|
+
console.log(`mjpic is running at ${url}`);
|
|
57
|
+
open(url);
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
program.parse();
|
package/api/index.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vercel deploy entry handler, for serverless deployment, please don't modify this file
|
|
3
|
+
*/
|
|
4
|
+
import type { VercelRequest, VercelResponse } from '@vercel/node';
|
|
5
|
+
import app from './app.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* 404 handler
|
|
9
|
+
*/
|
|
10
|
+
app.use((req, res) => {
|
|
11
|
+
res.status(404).json({
|
|
12
|
+
success: false,
|
|
13
|
+
error: 'API not found',
|
|
14
|
+
});
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
export default function handler(req: VercelRequest, res: VercelResponse) {
|
|
18
|
+
return app(req, res);
|
|
19
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This is a user authentication API route demo.
|
|
3
|
+
* Handle user registration, login, token management, etc.
|
|
4
|
+
*/
|
|
5
|
+
import { Router, type Request, type Response } from 'express'
|
|
6
|
+
|
|
7
|
+
const router = Router()
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* User Login
|
|
11
|
+
* POST /api/auth/register
|
|
12
|
+
*/
|
|
13
|
+
router.post('/register', async (req: Request, res: Response): Promise<void> => {
|
|
14
|
+
// TODO: Implement register logic
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* User Login
|
|
19
|
+
* POST /api/auth/login
|
|
20
|
+
*/
|
|
21
|
+
router.post('/login', async (req: Request, res: Response): Promise<void> => {
|
|
22
|
+
// TODO: Implement login logic
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* User Logout
|
|
27
|
+
* POST /api/auth/logout
|
|
28
|
+
*/
|
|
29
|
+
router.post('/logout', async (req: Request, res: Response): Promise<void> => {
|
|
30
|
+
// TODO: Implement logout logic
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
export default router
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { Router } from 'express';
|
|
2
|
+
import fs from 'fs/promises';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
|
|
5
|
+
const router = Router();
|
|
6
|
+
|
|
7
|
+
router.post('/save', async (req, res) => {
|
|
8
|
+
try {
|
|
9
|
+
const { filePath, data } = req.body;
|
|
10
|
+
if (!filePath || !data) {
|
|
11
|
+
return res.status(400).json({ success: false, error: 'Missing filePath or data' });
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// data is data:image/png;base64,....
|
|
15
|
+
const base64Data = data.replace(/^data:image\/\w+;base64,/, "");
|
|
16
|
+
const buffer = Buffer.from(base64Data, 'base64');
|
|
17
|
+
|
|
18
|
+
await fs.writeFile(filePath, buffer);
|
|
19
|
+
|
|
20
|
+
res.json({ success: true });
|
|
21
|
+
} catch (error) {
|
|
22
|
+
console.error('Save error:', error);
|
|
23
|
+
res.status(500).json({ success: false, error: 'Failed to save file' });
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
export default router;
|
package/api/server.ts
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* local server entry file, for local development
|
|
3
|
+
*/
|
|
4
|
+
import app from './app.js';
|
|
5
|
+
import { type Request, type Response } from 'express';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* 404 handler
|
|
9
|
+
*/
|
|
10
|
+
app.use((req: Request, res: Response) => {
|
|
11
|
+
res.status(404).json({
|
|
12
|
+
success: false,
|
|
13
|
+
error: 'API not found',
|
|
14
|
+
});
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* start server with port
|
|
19
|
+
*/
|
|
20
|
+
const PORT = process.env.PORT || 3002;
|
|
21
|
+
|
|
22
|
+
const server = app.listen(PORT, () => {
|
|
23
|
+
console.log(`Server ready on port ${PORT}`);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* close server
|
|
28
|
+
*/
|
|
29
|
+
process.on('SIGTERM', () => {
|
|
30
|
+
console.log('SIGTERM signal received');
|
|
31
|
+
server.close(() => {
|
|
32
|
+
console.log('Server closed');
|
|
33
|
+
process.exit(0);
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
process.on('SIGINT', () => {
|
|
38
|
+
console.log('SIGINT signal received');
|
|
39
|
+
server.close(() => {
|
|
40
|
+
console.log('Server closed');
|
|
41
|
+
process.exit(0);
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
export default app;
|
package/dist/cli/app.js
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This is a API server
|
|
3
|
+
*/
|
|
4
|
+
import express from 'express';
|
|
5
|
+
import cors from 'cors';
|
|
6
|
+
import path from 'path';
|
|
7
|
+
import dotenv from 'dotenv';
|
|
8
|
+
import { fileURLToPath } from 'url';
|
|
9
|
+
import authRoutes from './routes/auth.js';
|
|
10
|
+
import imageRoutes from './routes/image.js';
|
|
11
|
+
// for esm mode
|
|
12
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
13
|
+
const __dirname = path.dirname(__filename);
|
|
14
|
+
// load env
|
|
15
|
+
dotenv.config();
|
|
16
|
+
const app = express();
|
|
17
|
+
app.use(cors());
|
|
18
|
+
app.use(express.json({ limit: '50mb' }));
|
|
19
|
+
app.use(express.urlencoded({ extended: true, limit: '50mb' }));
|
|
20
|
+
/**
|
|
21
|
+
* API Routes
|
|
22
|
+
*/
|
|
23
|
+
app.use('/api/auth', authRoutes);
|
|
24
|
+
app.use('/api/image', imageRoutes);
|
|
25
|
+
/**
|
|
26
|
+
* health
|
|
27
|
+
*/
|
|
28
|
+
app.use('/api/health', (req, res, next) => {
|
|
29
|
+
res.status(200).json({
|
|
30
|
+
success: true,
|
|
31
|
+
message: 'ok',
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
/**
|
|
35
|
+
* error handler middleware
|
|
36
|
+
*/
|
|
37
|
+
app.use((error, req, res, next) => {
|
|
38
|
+
res.status(500).json({
|
|
39
|
+
success: false,
|
|
40
|
+
error: 'Server internal error',
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
export default app;
|