@haklex/rich-plugin-image-editor 0.24.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/dist/AnnotationSurface.d.ts +20 -0
- package/dist/AnnotationSurface.d.ts.map +1 -0
- package/dist/ImageEditModal.d.ts +9 -0
- package/dist/ImageEditModal.d.ts.map +1 -0
- package/dist/ImageEditModalPlugin.d.ts +3 -0
- package/dist/ImageEditModalPlugin.d.ts.map +1 -0
- package/dist/ToolOptionsBar.d.ts +14 -0
- package/dist/ToolOptionsBar.d.ts.map +1 -0
- package/dist/annotation.d.ts +10 -0
- package/dist/annotation.d.ts.map +1 -0
- package/dist/build-result.d.ts +2 -0
- package/dist/build-result.d.ts.map +1 -0
- package/dist/crop.d.ts +16 -0
- package/dist/crop.d.ts.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.mjs +740 -0
- package/dist/pipeline.d.ts +16 -0
- package/dist/pipeline.d.ts.map +1 -0
- package/dist/rasterize.d.ts +3 -0
- package/dist/rasterize.d.ts.map +1 -0
- package/dist/rich-plugin-image-editor.css +2 -0
- package/dist/styles.css.d.ts +32 -0
- package/dist/styles.css.d.ts.map +1 -0
- package/dist/useImageEditorState.d.ts +28 -0
- package/dist/useImageEditorState.d.ts.map +1 -0
- package/package.json +55 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { AnnotationState } from '@markerjs/markerjs3';
|
|
2
|
+
import { FC, RefObject } from 'react';
|
|
3
|
+
import { AnnotationTool } from './annotation';
|
|
4
|
+
export interface AnnotationApi {
|
|
5
|
+
redo: () => void;
|
|
6
|
+
undo: () => void;
|
|
7
|
+
}
|
|
8
|
+
export interface AnnotationSurfaceProps {
|
|
9
|
+
apiRef: RefObject<AnnotationApi | null>;
|
|
10
|
+
counterRef: RefObject<number>;
|
|
11
|
+
onDecodeError: () => void;
|
|
12
|
+
onHistoryChange: (canUndo: boolean, canRedo: boolean) => void;
|
|
13
|
+
sourceUrl: string;
|
|
14
|
+
stateRef: RefObject<AnnotationState | null>;
|
|
15
|
+
strokeColor: string;
|
|
16
|
+
strokeWidth: number;
|
|
17
|
+
tool: AnnotationTool;
|
|
18
|
+
}
|
|
19
|
+
export declare const AnnotationSurface: FC<AnnotationSurfaceProps>;
|
|
20
|
+
//# sourceMappingURL=AnnotationSurface.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AnnotationSurface.d.ts","sourceRoot":"","sources":["../src/AnnotationSurface.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAoB,MAAM,qBAAqB,CAAC;AAE7E,OAAO,KAAK,EAAE,EAAE,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAG3C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAInD,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,IAAI,CAAC;IACjB,IAAI,EAAE,MAAM,IAAI,CAAC;CAClB;AAED,MAAM,WAAW,sBAAsB;IACrC,MAAM,EAAE,SAAS,CAAC,aAAa,GAAG,IAAI,CAAC,CAAC;IACxC,UAAU,EAAE,SAAS,CAAC,MAAM,CAAC,CAAC;IAC9B,aAAa,EAAE,MAAM,IAAI,CAAC;IAC1B,eAAe,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,KAAK,IAAI,CAAC;IAC9D,SAAS,EAAE,MAAM,CAAC;IAElB,QAAQ,EAAE,SAAS,CAAC,eAAe,GAAG,IAAI,CAAC,CAAC;IAC5C,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,cAAc,CAAC;CACtB;AAID,eAAO,MAAM,iBAAiB,EAAE,EAAE,CAAC,sBAAsB,CA2LxD,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { FC } from 'react';
|
|
2
|
+
export interface ImageEditModalProps {
|
|
3
|
+
file: File;
|
|
4
|
+
onCancel: () => void;
|
|
5
|
+
onConfirm: (file: File) => void;
|
|
6
|
+
onSkip: () => void;
|
|
7
|
+
}
|
|
8
|
+
export declare const ImageEditModal: FC<ImageEditModalProps>;
|
|
9
|
+
//# sourceMappingURL=ImageEditModal.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ImageEditModal.d.ts","sourceRoot":"","sources":["../src/ImageEditModal.tsx"],"names":[],"mappings":"AAAA,OAAO,qCAAqC,CAAC;AAa7C,OAAO,KAAK,EAAE,EAAE,EAAkB,MAAM,OAAO,CAAC;AA0BhD,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,IAAI,CAAC;IACX,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,SAAS,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,IAAI,CAAC;IAChC,MAAM,EAAE,MAAM,IAAI,CAAC;CACpB;AAED,eAAO,MAAM,cAAc,EAAE,EAAE,CAAC,mBAAmB,CA2LlD,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ImageEditModalPlugin.d.ts","sourceRoot":"","sources":["../src/ImageEditModalPlugin.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC;AAMhC,eAAO,MAAM,oBAAoB,EAAE,EAkDlC,CAAC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { FC } from 'react';
|
|
2
|
+
export interface ToolOptionsBarProps {
|
|
3
|
+
canRedo: boolean;
|
|
4
|
+
canUndo: boolean;
|
|
5
|
+
onRedo: () => void;
|
|
6
|
+
onStrokeColorChange: (color: string) => void;
|
|
7
|
+
onStrokeWidthChange: (width: number) => void;
|
|
8
|
+
onUndo: () => void;
|
|
9
|
+
showStrokeWidth: boolean;
|
|
10
|
+
strokeColor: string;
|
|
11
|
+
strokeWidth: number;
|
|
12
|
+
}
|
|
13
|
+
export declare const ToolOptionsBar: FC<ToolOptionsBarProps>;
|
|
14
|
+
//# sourceMappingURL=ToolOptionsBar.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ToolOptionsBar.d.ts","sourceRoot":"","sources":["../src/ToolOptionsBar.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC;AAKhC,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,MAAM,IAAI,CAAC;IACnB,mBAAmB,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAC7C,mBAAmB,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAC7C,MAAM,EAAE,MAAM,IAAI,CAAC;IACnB,eAAe,EAAE,OAAO,CAAC;IACzB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,eAAO,MAAM,cAAc,EAAE,EAAE,CAAC,mBAAmB,CAoElD,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { AnnotationState } from '@markerjs/markerjs3';
|
|
2
|
+
import { EditorTool } from './useImageEditorState';
|
|
3
|
+
export type AnnotationTool = Exclude<EditorTool, 'crop'>;
|
|
4
|
+
export declare const MARKER_TYPE_BY_TOOL: Record<AnnotationTool, string>;
|
|
5
|
+
export declare function isAnnotationTool(tool: EditorTool): tool is AnnotationTool;
|
|
6
|
+
export declare const SWATCH_COLORS: readonly ["#ef4444", "#f59e0b", "#22c55e", "#3b82f6", "#171717", "#ffffff"];
|
|
7
|
+
export declare const STROKE_WIDTHS: readonly [2, 4, 6];
|
|
8
|
+
export declare const STROKE_TOOLS: ReadonlySet<AnnotationTool>;
|
|
9
|
+
export declare function offsetMarkerState(state: AnnotationState, dx: number, dy: number): AnnotationState;
|
|
10
|
+
//# sourceMappingURL=annotation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"annotation.d.ts","sourceRoot":"","sources":["../src/annotation.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAA2B,MAAM,qBAAqB,CAAC;AAEpF,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AAExD,MAAM,MAAM,cAAc,GAAG,OAAO,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;AAIzD,eAAO,MAAM,mBAAmB,EAAE,MAAM,CAAC,cAAc,EAAE,MAAM,CAQ9D,CAAC;AAEF,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,UAAU,GAAG,IAAI,IAAI,cAAc,CAEzE;AAED,eAAO,MAAM,aAAa,6EAOhB,CAAC;AAEX,eAAO,MAAM,aAAa,oBAAqB,CAAC;AAEhD,eAAO,MAAM,YAAY,EAAE,WAAW,CAAC,cAAc,CAKnD,CAAC;AA0BH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,eAAe,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,eAAe,CAKjG"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"build-result.d.ts","sourceRoot":"","sources":["../src/build-result.ts"],"names":[],"mappings":"AAIA,wBAAsB,WAAW,CAC/B,QAAQ,EAAE,IAAI,EACd,MAAM,CAAC,EAAE,iBAAiB,GAAG,IAAI,GAChC,OAAO,CAAC,IAAI,CAAC,CAWf"}
|
package/dist/crop.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export interface CropRect {
|
|
2
|
+
height: number;
|
|
3
|
+
width: number;
|
|
4
|
+
x: number;
|
|
5
|
+
y: number;
|
|
6
|
+
}
|
|
7
|
+
export declare function clampCropRect(rect: CropRect, imageWidth: number, imageHeight: number): CropRect;
|
|
8
|
+
export declare function isFullImageCrop(rect: CropRect, imageWidth: number, imageHeight: number): boolean;
|
|
9
|
+
export declare function displayedToNatural(rect: CropRect, scaleX: number, scaleY: number): CropRect;
|
|
10
|
+
export declare function pickExportMime(originalType: string): string;
|
|
11
|
+
export declare function loadImage(imageSrc: string): Promise<HTMLImageElement>;
|
|
12
|
+
export declare function extractCrop(imageSrc: string, areaPixels: CropRect): Promise<HTMLCanvasElement>;
|
|
13
|
+
export declare function cropCanvas(source: HTMLCanvasElement, areaPixels: CropRect): HTMLCanvasElement;
|
|
14
|
+
export declare function sourceToCanvas(imageSrc: string): Promise<HTMLCanvasElement>;
|
|
15
|
+
export declare function canvasToObjectUrl(canvas: HTMLCanvasElement): Promise<string>;
|
|
16
|
+
//# sourceMappingURL=crop.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"crop.d.ts","sourceRoot":"","sources":["../src/crop.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,QAAQ;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;CACX;AAID,wBAAgB,aAAa,CAAC,IAAI,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,QAAQ,CAM/F;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAQhG;AAED,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,QAAQ,CAO3F;AAID,wBAAgB,cAAc,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAE3D;AAED,wBAAsB,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAM3E;AAkBD,wBAAsB,WAAW,CAC/B,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,QAAQ,GACnB,OAAO,CAAC,iBAAiB,CAAC,CAG5B;AAED,wBAAgB,UAAU,CAAC,MAAM,EAAE,iBAAiB,EAAE,UAAU,EAAE,QAAQ,GAAG,iBAAiB,CAE7F;AAED,wBAAsB,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAQjF;AAGD,wBAAsB,iBAAiB,CAAC,MAAM,EAAE,iBAAiB,GAAG,OAAO,CAAC,MAAM,CAAC,CAQlF"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,KAAK,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AAC5E,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC"}
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,740 @@
|
|
|
1
|
+
import { Circle, Crop, Hash, ImageOff, MoveUpRight, PaintBucket, Pen, Redo2, Square, Type, Undo2 } from "lucide-react";
|
|
2
|
+
import { useEffect, useRef, useState } from "react";
|
|
3
|
+
import { ReactCrop } from "react-image-crop";
|
|
4
|
+
import { MarkerArea, Renderer, TextMarkerEditor } from "@markerjs/markerjs3";
|
|
5
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
6
|
+
import { useImagePreprocess } from "@haklex/rich-editor/plugins";
|
|
7
|
+
import { presentDialog } from "@haklex/rich-editor-ui";
|
|
8
|
+
import { usePortalTheme } from "@haklex/rich-style-token";
|
|
9
|
+
//#region src/annotation.ts
|
|
10
|
+
var MARKER_TYPE_BY_TOOL = {
|
|
11
|
+
arrow: "ArrowMarker",
|
|
12
|
+
counter: "TextMarker",
|
|
13
|
+
cover: "CoverMarker",
|
|
14
|
+
ellipse: "EllipseFrameMarker",
|
|
15
|
+
pen: "FreehandMarker",
|
|
16
|
+
rect: "FrameMarker",
|
|
17
|
+
text: "TextMarker"
|
|
18
|
+
};
|
|
19
|
+
function isAnnotationTool(tool) {
|
|
20
|
+
return tool !== "crop";
|
|
21
|
+
}
|
|
22
|
+
var SWATCH_COLORS = [
|
|
23
|
+
"#ef4444",
|
|
24
|
+
"#f59e0b",
|
|
25
|
+
"#22c55e",
|
|
26
|
+
"#3b82f6",
|
|
27
|
+
"#171717",
|
|
28
|
+
"#ffffff"
|
|
29
|
+
];
|
|
30
|
+
var STROKE_WIDTHS = [
|
|
31
|
+
2,
|
|
32
|
+
4,
|
|
33
|
+
6
|
|
34
|
+
];
|
|
35
|
+
var STROKE_TOOLS = new Set([
|
|
36
|
+
"arrow",
|
|
37
|
+
"ellipse",
|
|
38
|
+
"pen",
|
|
39
|
+
"rect"
|
|
40
|
+
]);
|
|
41
|
+
function offsetMarker(marker, dx, dy) {
|
|
42
|
+
const next = { ...marker };
|
|
43
|
+
if (typeof next.left === "number") next.left += dx;
|
|
44
|
+
if (typeof next.top === "number") next.top += dy;
|
|
45
|
+
if (typeof next.x1 === "number") next.x1 += dx;
|
|
46
|
+
if (typeof next.y1 === "number") next.y1 += dy;
|
|
47
|
+
if (typeof next.x2 === "number") next.x2 += dx;
|
|
48
|
+
if (typeof next.y2 === "number") next.y2 += dy;
|
|
49
|
+
if (Array.isArray(next.points)) next.points = next.points.map((point) => ({
|
|
50
|
+
x: point.x + dx,
|
|
51
|
+
y: point.y + dy
|
|
52
|
+
}));
|
|
53
|
+
return next;
|
|
54
|
+
}
|
|
55
|
+
function offsetMarkerState(state, dx, dy) {
|
|
56
|
+
return {
|
|
57
|
+
...state,
|
|
58
|
+
markers: state.markers.map((marker) => offsetMarker(marker, dx, dy))
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
//#endregion
|
|
62
|
+
//#region src/styles.css.ts
|
|
63
|
+
var fullscreenPopup = "_1257qq22";
|
|
64
|
+
var root = "_1257qq23";
|
|
65
|
+
var topBar = "_1257qq24";
|
|
66
|
+
var topBarTitle = "_1257qq25";
|
|
67
|
+
var topBarControls = "_1257qq26";
|
|
68
|
+
var toolGroup = "_1257qq27";
|
|
69
|
+
var body = "_1257qq28";
|
|
70
|
+
var toolRail = "_1257qq29";
|
|
71
|
+
var toolButton = "_1257qq2a";
|
|
72
|
+
var toolButtonActive = "_1257qq2b";
|
|
73
|
+
var canvasArea = "_1257qq2d";
|
|
74
|
+
var annotationSurface = "_1257qq2g";
|
|
75
|
+
var optionsBar = "_1257qq2h";
|
|
76
|
+
var swatch = "_1257qq2i";
|
|
77
|
+
var swatchActive = "_1257qq2j";
|
|
78
|
+
var optionButton = "_1257qq2k";
|
|
79
|
+
var optionDivider = "_1257qq2n";
|
|
80
|
+
var errorState = "_1257qq2o";
|
|
81
|
+
var attribution = "_1257qq2p";
|
|
82
|
+
var footer = "_1257qq2q";
|
|
83
|
+
var footerSpacer = "_1257qq2r";
|
|
84
|
+
var ghostButton = "_1257qq2t _1257qq2s";
|
|
85
|
+
var secondaryButton = "_1257qq2v _1257qq2s";
|
|
86
|
+
var primaryButton = "_1257qq2w _1257qq2s";
|
|
87
|
+
//#endregion
|
|
88
|
+
//#region src/AnnotationSurface.tsx
|
|
89
|
+
var TEXT_TOOLS = new Set(["counter", "text"]);
|
|
90
|
+
var AnnotationSurface = ({ apiRef, counterRef, onDecodeError, onHistoryChange, sourceUrl, stateRef, strokeColor, strokeWidth, tool }) => {
|
|
91
|
+
const containerRef = useRef(null);
|
|
92
|
+
const areaRef = useRef(null);
|
|
93
|
+
const syncRef = useRef(() => {});
|
|
94
|
+
const [ready, setReady] = useState(false);
|
|
95
|
+
const settingsRef = useRef({
|
|
96
|
+
strokeColor,
|
|
97
|
+
strokeWidth,
|
|
98
|
+
tool
|
|
99
|
+
});
|
|
100
|
+
settingsRef.current = {
|
|
101
|
+
strokeColor,
|
|
102
|
+
strokeWidth,
|
|
103
|
+
tool
|
|
104
|
+
};
|
|
105
|
+
const callbacksRef = useRef({
|
|
106
|
+
onDecodeError,
|
|
107
|
+
onHistoryChange
|
|
108
|
+
});
|
|
109
|
+
callbacksRef.current = {
|
|
110
|
+
onDecodeError,
|
|
111
|
+
onHistoryChange
|
|
112
|
+
};
|
|
113
|
+
const styleEditorRef = useRef((editor, isNew) => {});
|
|
114
|
+
styleEditorRef.current = (editor, isNew) => {
|
|
115
|
+
const settings = settingsRef.current;
|
|
116
|
+
if (settings.tool === "cover") editor.fillColor = settings.strokeColor;
|
|
117
|
+
else if (editor.is(TextMarkerEditor)) {
|
|
118
|
+
editor.color = settings.strokeColor;
|
|
119
|
+
if (isNew && settings.tool === "counter") editor.marker.text = String(counterRef.current);
|
|
120
|
+
} else {
|
|
121
|
+
editor.strokeColor = settings.strokeColor;
|
|
122
|
+
editor.strokeWidth = settings.strokeWidth;
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
const armRef = useRef(() => {});
|
|
126
|
+
armRef.current = () => {
|
|
127
|
+
const area = areaRef.current;
|
|
128
|
+
if (!area) return;
|
|
129
|
+
const editor = area.createMarker(MARKER_TYPE_BY_TOOL[settingsRef.current.tool]);
|
|
130
|
+
if (editor) styleEditorRef.current(editor, true);
|
|
131
|
+
};
|
|
132
|
+
useEffect(() => {
|
|
133
|
+
const container = containerRef.current;
|
|
134
|
+
if (!container) return;
|
|
135
|
+
let disposed = false;
|
|
136
|
+
const area = new MarkerArea();
|
|
137
|
+
const img = document.createElement("img");
|
|
138
|
+
img.crossOrigin = "anonymous";
|
|
139
|
+
img.src = sourceUrl;
|
|
140
|
+
const sync = () => {
|
|
141
|
+
stateRef.current = area.getState();
|
|
142
|
+
callbacksRef.current.onHistoryChange(area.isUndoPossible, area.isRedoPossible);
|
|
143
|
+
};
|
|
144
|
+
const handleCreate = () => {
|
|
145
|
+
if (settingsRef.current.tool === "counter") counterRef.current += 1;
|
|
146
|
+
sync();
|
|
147
|
+
if (!TEXT_TOOLS.has(settingsRef.current.tool)) armRef.current();
|
|
148
|
+
};
|
|
149
|
+
let pointerActive = false;
|
|
150
|
+
let armPending = false;
|
|
151
|
+
const tryArm = () => {
|
|
152
|
+
if (disposed) return;
|
|
153
|
+
if (area.currentMarkerEditor || area.selectedMarkerEditors.length > 0) return;
|
|
154
|
+
if (pointerActive) {
|
|
155
|
+
armPending = true;
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
armRef.current();
|
|
159
|
+
};
|
|
160
|
+
const handlePointerDown = () => {
|
|
161
|
+
pointerActive = true;
|
|
162
|
+
};
|
|
163
|
+
const handlePointerRelease = () => {
|
|
164
|
+
pointerActive = false;
|
|
165
|
+
if (!armPending) return;
|
|
166
|
+
armPending = false;
|
|
167
|
+
setTimeout(tryArm, 0);
|
|
168
|
+
};
|
|
169
|
+
container.addEventListener("pointerdown", handlePointerDown, true);
|
|
170
|
+
window.addEventListener("pointerup", handlePointerRelease);
|
|
171
|
+
window.addEventListener("pointercancel", handlePointerRelease);
|
|
172
|
+
const handleDeselect = () => {
|
|
173
|
+
if (!TEXT_TOOLS.has(settingsRef.current.tool)) return;
|
|
174
|
+
setTimeout(tryArm, 0);
|
|
175
|
+
};
|
|
176
|
+
img.decode().then(() => {
|
|
177
|
+
if (disposed) return;
|
|
178
|
+
area.targetImage = img;
|
|
179
|
+
area.autoZoomOut = true;
|
|
180
|
+
area.addEventListener("markercreate", handleCreate);
|
|
181
|
+
area.addEventListener("markerchange", sync);
|
|
182
|
+
area.addEventListener("markerdelete", sync);
|
|
183
|
+
area.addEventListener("areastatechange", sync);
|
|
184
|
+
area.addEventListener("markerdeselect", handleDeselect);
|
|
185
|
+
container.append(area);
|
|
186
|
+
areaRef.current = area;
|
|
187
|
+
if (stateRef.current) area.restoreState(stateRef.current, false);
|
|
188
|
+
syncRef.current = sync;
|
|
189
|
+
setReady(true);
|
|
190
|
+
}).catch(() => {
|
|
191
|
+
if (!disposed) callbacksRef.current.onDecodeError();
|
|
192
|
+
});
|
|
193
|
+
return () => {
|
|
194
|
+
disposed = true;
|
|
195
|
+
container.removeEventListener("pointerdown", handlePointerDown, true);
|
|
196
|
+
window.removeEventListener("pointerup", handlePointerRelease);
|
|
197
|
+
window.removeEventListener("pointercancel", handlePointerRelease);
|
|
198
|
+
if (areaRef.current === area) {
|
|
199
|
+
stateRef.current = area.getState();
|
|
200
|
+
areaRef.current = null;
|
|
201
|
+
}
|
|
202
|
+
syncRef.current = () => {};
|
|
203
|
+
callbacksRef.current.onHistoryChange(false, false);
|
|
204
|
+
area.remove();
|
|
205
|
+
setReady(false);
|
|
206
|
+
};
|
|
207
|
+
}, [
|
|
208
|
+
sourceUrl,
|
|
209
|
+
stateRef,
|
|
210
|
+
counterRef
|
|
211
|
+
]);
|
|
212
|
+
useEffect(() => {
|
|
213
|
+
if (!ready) return;
|
|
214
|
+
armRef.current();
|
|
215
|
+
}, [ready, tool]);
|
|
216
|
+
useEffect(() => {
|
|
217
|
+
if (!ready) return;
|
|
218
|
+
const area = areaRef.current;
|
|
219
|
+
if (!area) return;
|
|
220
|
+
for (const editor of area.selectedMarkerEditors) styleEditorRef.current(editor, false);
|
|
221
|
+
syncRef.current();
|
|
222
|
+
armRef.current();
|
|
223
|
+
}, [
|
|
224
|
+
ready,
|
|
225
|
+
strokeColor,
|
|
226
|
+
strokeWidth
|
|
227
|
+
]);
|
|
228
|
+
useEffect(() => {
|
|
229
|
+
apiRef.current = {
|
|
230
|
+
redo: () => {
|
|
231
|
+
const area = areaRef.current;
|
|
232
|
+
if (!area) return;
|
|
233
|
+
area.redo();
|
|
234
|
+
syncRef.current();
|
|
235
|
+
armRef.current();
|
|
236
|
+
},
|
|
237
|
+
undo: () => {
|
|
238
|
+
const area = areaRef.current;
|
|
239
|
+
if (!area) return;
|
|
240
|
+
area.undo();
|
|
241
|
+
syncRef.current();
|
|
242
|
+
armRef.current();
|
|
243
|
+
}
|
|
244
|
+
};
|
|
245
|
+
return () => {
|
|
246
|
+
apiRef.current = null;
|
|
247
|
+
};
|
|
248
|
+
}, [apiRef]);
|
|
249
|
+
return /* @__PURE__ */ jsx("div", {
|
|
250
|
+
className: annotationSurface,
|
|
251
|
+
ref: containerRef
|
|
252
|
+
});
|
|
253
|
+
};
|
|
254
|
+
//#endregion
|
|
255
|
+
//#region src/crop.ts
|
|
256
|
+
var clamp = (value, min, max) => Math.min(Math.max(value, min), max);
|
|
257
|
+
function clampCropRect(rect, imageWidth, imageHeight) {
|
|
258
|
+
const left = clamp(Math.round(rect.x), 0, imageWidth);
|
|
259
|
+
const top = clamp(Math.round(rect.y), 0, imageHeight);
|
|
260
|
+
const right = clamp(Math.round(rect.x + rect.width), left, imageWidth);
|
|
261
|
+
return {
|
|
262
|
+
height: clamp(Math.round(rect.y + rect.height), top, imageHeight) - top,
|
|
263
|
+
width: right - left,
|
|
264
|
+
x: left,
|
|
265
|
+
y: top
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
function isFullImageCrop(rect, imageWidth, imageHeight) {
|
|
269
|
+
const clamped = clampCropRect(rect, imageWidth, imageHeight);
|
|
270
|
+
return clamped.x === 0 && clamped.y === 0 && clamped.width === imageWidth && clamped.height === imageHeight;
|
|
271
|
+
}
|
|
272
|
+
function displayedToNatural(rect, scaleX, scaleY) {
|
|
273
|
+
return {
|
|
274
|
+
height: rect.height * scaleY,
|
|
275
|
+
width: rect.width * scaleX,
|
|
276
|
+
x: rect.x * scaleX,
|
|
277
|
+
y: rect.y * scaleY
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
var CANVAS_ENCODABLE_TYPES = new Set([
|
|
281
|
+
"image/jpeg",
|
|
282
|
+
"image/png",
|
|
283
|
+
"image/webp"
|
|
284
|
+
]);
|
|
285
|
+
function pickExportMime(originalType) {
|
|
286
|
+
return CANVAS_ENCODABLE_TYPES.has(originalType) ? originalType : "image/png";
|
|
287
|
+
}
|
|
288
|
+
async function loadImage(imageSrc) {
|
|
289
|
+
const image = new Image();
|
|
290
|
+
image.crossOrigin = "anonymous";
|
|
291
|
+
image.src = imageSrc;
|
|
292
|
+
await image.decode();
|
|
293
|
+
return image;
|
|
294
|
+
}
|
|
295
|
+
function drawCrop(source, rect) {
|
|
296
|
+
if (rect.width === 0 || rect.height === 0) throw new Error("Crop region is empty");
|
|
297
|
+
const canvas = document.createElement("canvas");
|
|
298
|
+
canvas.width = rect.width;
|
|
299
|
+
canvas.height = rect.height;
|
|
300
|
+
const ctx = canvas.getContext("2d");
|
|
301
|
+
if (!ctx) throw new Error("Canvas 2d context unavailable");
|
|
302
|
+
ctx.drawImage(source, rect.x, rect.y, rect.width, rect.height, 0, 0, rect.width, rect.height);
|
|
303
|
+
return canvas;
|
|
304
|
+
}
|
|
305
|
+
async function extractCrop(imageSrc, areaPixels) {
|
|
306
|
+
const image = await loadImage(imageSrc);
|
|
307
|
+
return drawCrop(image, clampCropRect(areaPixels, image.naturalWidth, image.naturalHeight));
|
|
308
|
+
}
|
|
309
|
+
function cropCanvas(source, areaPixels) {
|
|
310
|
+
return drawCrop(source, clampCropRect(areaPixels, source.width, source.height));
|
|
311
|
+
}
|
|
312
|
+
async function sourceToCanvas(imageSrc) {
|
|
313
|
+
const image = await loadImage(imageSrc);
|
|
314
|
+
return drawCrop(image, {
|
|
315
|
+
height: image.naturalHeight,
|
|
316
|
+
width: image.naturalWidth,
|
|
317
|
+
x: 0,
|
|
318
|
+
y: 0
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
async function canvasToObjectUrl(canvas) {
|
|
322
|
+
const blob = await new Promise((resolve, reject) => {
|
|
323
|
+
canvas.toBlob((result) => result ? resolve(result) : reject(/* @__PURE__ */ new Error("canvas.toBlob returned null")), "image/png");
|
|
324
|
+
});
|
|
325
|
+
return URL.createObjectURL(blob);
|
|
326
|
+
}
|
|
327
|
+
//#endregion
|
|
328
|
+
//#region src/build-result.ts
|
|
329
|
+
async function buildResult(original, canvas) {
|
|
330
|
+
if (!canvas) return original;
|
|
331
|
+
const type = pickExportMime(original.type);
|
|
332
|
+
const blob = await new Promise((resolve, reject) => {
|
|
333
|
+
canvas.toBlob((result) => result ? resolve(result) : reject(/* @__PURE__ */ new Error("canvas.toBlob returned null")), type);
|
|
334
|
+
});
|
|
335
|
+
return new File([blob], original.name, { type: blob.type || type });
|
|
336
|
+
}
|
|
337
|
+
//#endregion
|
|
338
|
+
//#region src/rasterize.ts
|
|
339
|
+
async function rasterizeAnnotations(imageSrc, state) {
|
|
340
|
+
const image = await loadImage(imageSrc);
|
|
341
|
+
const renderer = new Renderer();
|
|
342
|
+
renderer.targetImage = image;
|
|
343
|
+
renderer.naturalSize = true;
|
|
344
|
+
const canvas = document.createElement("canvas");
|
|
345
|
+
await renderer.rasterize(state, canvas);
|
|
346
|
+
return canvas;
|
|
347
|
+
}
|
|
348
|
+
//#endregion
|
|
349
|
+
//#region src/pipeline.ts
|
|
350
|
+
async function applyCropRebase(sourceUrl, rect, markerState) {
|
|
351
|
+
return {
|
|
352
|
+
bitmapUrl: await canvasToObjectUrl(await extractCrop(sourceUrl, rect)),
|
|
353
|
+
markerState: markerState ? {
|
|
354
|
+
...offsetMarkerState(markerState, -rect.x, -rect.y),
|
|
355
|
+
height: rect.height,
|
|
356
|
+
width: rect.width
|
|
357
|
+
} : null
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
async function exportResult({ hasRebasedBitmap, markerState, original, pendingCropRect, sourceUrl }) {
|
|
361
|
+
const hasMarkers = markerState !== null && markerState.markers.length > 0;
|
|
362
|
+
let canvas = null;
|
|
363
|
+
if (hasMarkers && markerState) {
|
|
364
|
+
canvas = await rasterizeAnnotations(sourceUrl, markerState);
|
|
365
|
+
if (pendingCropRect) canvas = cropCanvas(canvas, pendingCropRect);
|
|
366
|
+
} else if (pendingCropRect) canvas = await extractCrop(sourceUrl, pendingCropRect);
|
|
367
|
+
else if (hasRebasedBitmap) canvas = await sourceToCanvas(sourceUrl);
|
|
368
|
+
return buildResult(original, canvas);
|
|
369
|
+
}
|
|
370
|
+
//#endregion
|
|
371
|
+
//#region src/ToolOptionsBar.tsx
|
|
372
|
+
var ToolOptionsBar = ({ canRedo, canUndo, onRedo, onStrokeColorChange, onStrokeWidthChange, onUndo, showStrokeWidth, strokeColor, strokeWidth }) => /* @__PURE__ */ jsxs("div", {
|
|
373
|
+
className: optionsBar,
|
|
374
|
+
children: [
|
|
375
|
+
SWATCH_COLORS.map((color) => /* @__PURE__ */ jsx("button", {
|
|
376
|
+
"aria-label": `Color ${color}`,
|
|
377
|
+
"aria-pressed": strokeColor === color,
|
|
378
|
+
className: strokeColor === color ? `${swatch} ${swatchActive}` : swatch,
|
|
379
|
+
style: { backgroundColor: color },
|
|
380
|
+
title: color,
|
|
381
|
+
type: "button",
|
|
382
|
+
onClick: () => onStrokeColorChange(color)
|
|
383
|
+
}, color)),
|
|
384
|
+
showStrokeWidth && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("div", { className: "_1257qq2n" }), STROKE_WIDTHS.map((width) => /* @__PURE__ */ jsx("button", {
|
|
385
|
+
"aria-label": `Stroke width ${width}`,
|
|
386
|
+
"aria-pressed": strokeWidth === width,
|
|
387
|
+
title: `Stroke width ${width}px`,
|
|
388
|
+
type: "button",
|
|
389
|
+
className: strokeWidth === width ? `_1257qq2k _1257qq2l` : "_1257qq2k",
|
|
390
|
+
onClick: () => onStrokeWidthChange(width),
|
|
391
|
+
children: /* @__PURE__ */ jsx("span", {
|
|
392
|
+
className: "_1257qq2m",
|
|
393
|
+
style: {
|
|
394
|
+
height: width + 4,
|
|
395
|
+
width: width + 4
|
|
396
|
+
}
|
|
397
|
+
})
|
|
398
|
+
}, width))] }),
|
|
399
|
+
/* @__PURE__ */ jsx("div", { className: optionDivider }),
|
|
400
|
+
/* @__PURE__ */ jsx("button", {
|
|
401
|
+
"aria-label": "Undo",
|
|
402
|
+
className: optionButton,
|
|
403
|
+
disabled: !canUndo,
|
|
404
|
+
title: "Undo",
|
|
405
|
+
type: "button",
|
|
406
|
+
onClick: onUndo,
|
|
407
|
+
children: /* @__PURE__ */ jsx(Undo2, { size: 16 })
|
|
408
|
+
}),
|
|
409
|
+
/* @__PURE__ */ jsx("button", {
|
|
410
|
+
"aria-label": "Redo",
|
|
411
|
+
className: optionButton,
|
|
412
|
+
disabled: !canRedo,
|
|
413
|
+
title: "Redo",
|
|
414
|
+
type: "button",
|
|
415
|
+
onClick: onRedo,
|
|
416
|
+
children: /* @__PURE__ */ jsx(Redo2, { size: 16 })
|
|
417
|
+
})
|
|
418
|
+
]
|
|
419
|
+
});
|
|
420
|
+
//#endregion
|
|
421
|
+
//#region src/useImageEditorState.ts
|
|
422
|
+
var FULL_IMAGE_CROP = {
|
|
423
|
+
height: 100,
|
|
424
|
+
unit: "%",
|
|
425
|
+
width: 100,
|
|
426
|
+
x: 0,
|
|
427
|
+
y: 0
|
|
428
|
+
};
|
|
429
|
+
function useImageEditorState(objectUrl) {
|
|
430
|
+
const [activeTool, setActiveTool] = useState("crop");
|
|
431
|
+
const [rebasedBitmapUrl, setRebasedBitmapUrl] = useState(null);
|
|
432
|
+
const [crop, setCrop] = useState(FULL_IMAGE_CROP);
|
|
433
|
+
const [croppedAreaPixels, setCroppedAreaPixels] = useState(null);
|
|
434
|
+
const [naturalSize, setNaturalSize] = useState(null);
|
|
435
|
+
const markerStateRef = useRef(null);
|
|
436
|
+
const rebasedUrlRef = useRef(null);
|
|
437
|
+
rebasedUrlRef.current = rebasedBitmapUrl;
|
|
438
|
+
useEffect(() => () => {
|
|
439
|
+
if (rebasedUrlRef.current) URL.revokeObjectURL(rebasedUrlRef.current);
|
|
440
|
+
}, []);
|
|
441
|
+
const sourceUrl = rebasedBitmapUrl ?? objectUrl;
|
|
442
|
+
const hasPendingCrop = croppedAreaPixels !== null && naturalSize !== null && croppedAreaPixels.width >= 1 && croppedAreaPixels.height >= 1 && !isFullImageCrop(croppedAreaPixels, naturalSize.width, naturalSize.height);
|
|
443
|
+
const resetCrop = () => {
|
|
444
|
+
setCrop(FULL_IMAGE_CROP);
|
|
445
|
+
setCroppedAreaPixels(null);
|
|
446
|
+
};
|
|
447
|
+
const confirmCropAndSwitch = async (tool) => {
|
|
448
|
+
if (!croppedAreaPixels || !naturalSize) return;
|
|
449
|
+
const rect = clampCropRect(croppedAreaPixels, naturalSize.width, naturalSize.height);
|
|
450
|
+
try {
|
|
451
|
+
const rebase = await applyCropRebase(sourceUrl, rect, markerStateRef.current);
|
|
452
|
+
if (rebasedBitmapUrl) URL.revokeObjectURL(rebasedBitmapUrl);
|
|
453
|
+
setRebasedBitmapUrl(rebase.bitmapUrl);
|
|
454
|
+
markerStateRef.current = rebase.markerState;
|
|
455
|
+
resetCrop();
|
|
456
|
+
setNaturalSize({
|
|
457
|
+
height: rect.height,
|
|
458
|
+
width: rect.width
|
|
459
|
+
});
|
|
460
|
+
setActiveTool(tool);
|
|
461
|
+
} catch {}
|
|
462
|
+
};
|
|
463
|
+
return {
|
|
464
|
+
activeTool,
|
|
465
|
+
confirmCropAndSwitch,
|
|
466
|
+
crop,
|
|
467
|
+
croppedAreaPixels,
|
|
468
|
+
hasPendingCrop,
|
|
469
|
+
markerStateRef,
|
|
470
|
+
naturalSize,
|
|
471
|
+
rebasedBitmapUrl,
|
|
472
|
+
resetCrop,
|
|
473
|
+
setActiveTool,
|
|
474
|
+
setCrop,
|
|
475
|
+
setCroppedAreaPixels,
|
|
476
|
+
setNaturalSize,
|
|
477
|
+
sourceUrl
|
|
478
|
+
};
|
|
479
|
+
}
|
|
480
|
+
//#endregion
|
|
481
|
+
//#region src/ImageEditModal.tsx
|
|
482
|
+
var TOOLS = [
|
|
483
|
+
{
|
|
484
|
+
icon: Crop,
|
|
485
|
+
id: "crop",
|
|
486
|
+
label: "Crop"
|
|
487
|
+
},
|
|
488
|
+
{
|
|
489
|
+
icon: MoveUpRight,
|
|
490
|
+
id: "arrow",
|
|
491
|
+
label: "Arrow"
|
|
492
|
+
},
|
|
493
|
+
{
|
|
494
|
+
icon: Pen,
|
|
495
|
+
id: "pen",
|
|
496
|
+
label: "Pen"
|
|
497
|
+
},
|
|
498
|
+
{
|
|
499
|
+
icon: Square,
|
|
500
|
+
id: "rect",
|
|
501
|
+
label: "Rectangle"
|
|
502
|
+
},
|
|
503
|
+
{
|
|
504
|
+
icon: Circle,
|
|
505
|
+
id: "ellipse",
|
|
506
|
+
label: "Ellipse"
|
|
507
|
+
},
|
|
508
|
+
{
|
|
509
|
+
icon: Type,
|
|
510
|
+
id: "text",
|
|
511
|
+
label: "Text"
|
|
512
|
+
},
|
|
513
|
+
{
|
|
514
|
+
icon: Hash,
|
|
515
|
+
id: "counter",
|
|
516
|
+
label: "Counter"
|
|
517
|
+
},
|
|
518
|
+
{
|
|
519
|
+
icon: PaintBucket,
|
|
520
|
+
id: "cover",
|
|
521
|
+
label: "Cover"
|
|
522
|
+
}
|
|
523
|
+
];
|
|
524
|
+
var ImageEditModal = ({ file, onCancel, onConfirm, onSkip }) => {
|
|
525
|
+
const [objectUrl, setObjectUrl] = useState("");
|
|
526
|
+
const [decodeError, setDecodeError] = useState(false);
|
|
527
|
+
const [exporting, setExporting] = useState(false);
|
|
528
|
+
const [strokeColor, setStrokeColor] = useState(SWATCH_COLORS[0]);
|
|
529
|
+
const [strokeWidth, setStrokeWidth] = useState(STROKE_WIDTHS[1]);
|
|
530
|
+
const [history, setHistory] = useState({
|
|
531
|
+
canRedo: false,
|
|
532
|
+
canUndo: false
|
|
533
|
+
});
|
|
534
|
+
const imgRef = useRef(null);
|
|
535
|
+
const counterRef = useRef(1);
|
|
536
|
+
const annotationApiRef = useRef(null);
|
|
537
|
+
const editor = useImageEditorState(objectUrl);
|
|
538
|
+
const { hasPendingCrop, markerStateRef, sourceUrl } = editor;
|
|
539
|
+
useEffect(() => {
|
|
540
|
+
const url = URL.createObjectURL(file);
|
|
541
|
+
setObjectUrl(url);
|
|
542
|
+
return () => URL.revokeObjectURL(url);
|
|
543
|
+
}, [file]);
|
|
544
|
+
const handleImageLoad = (event) => {
|
|
545
|
+
const { naturalHeight, naturalWidth } = event.currentTarget;
|
|
546
|
+
editor.setNaturalSize({
|
|
547
|
+
height: naturalHeight,
|
|
548
|
+
width: naturalWidth
|
|
549
|
+
});
|
|
550
|
+
};
|
|
551
|
+
const handleCropComplete = (pixelCrop) => {
|
|
552
|
+
const img = imgRef.current;
|
|
553
|
+
if (!img || img.width === 0 || img.height === 0) return;
|
|
554
|
+
editor.setCroppedAreaPixels(displayedToNatural(pixelCrop, img.naturalWidth / img.width, img.naturalHeight / img.height));
|
|
555
|
+
};
|
|
556
|
+
const handleToolClick = (tool) => {
|
|
557
|
+
if (tool === editor.activeTool) return;
|
|
558
|
+
if (editor.activeTool === "crop" && hasPendingCrop) {
|
|
559
|
+
editor.confirmCropAndSwitch(tool);
|
|
560
|
+
return;
|
|
561
|
+
}
|
|
562
|
+
editor.setActiveTool(tool);
|
|
563
|
+
};
|
|
564
|
+
const handleConfirm = async () => {
|
|
565
|
+
setExporting(true);
|
|
566
|
+
try {
|
|
567
|
+
const pendingCropRect = editor.activeTool === "crop" && hasPendingCrop ? editor.croppedAreaPixels : null;
|
|
568
|
+
onConfirm(await exportResult({
|
|
569
|
+
hasRebasedBitmap: editor.rebasedBitmapUrl !== null,
|
|
570
|
+
markerState: markerStateRef.current,
|
|
571
|
+
original: file,
|
|
572
|
+
pendingCropRect,
|
|
573
|
+
sourceUrl
|
|
574
|
+
}));
|
|
575
|
+
} catch {
|
|
576
|
+
onSkip();
|
|
577
|
+
} finally {
|
|
578
|
+
setExporting(false);
|
|
579
|
+
}
|
|
580
|
+
};
|
|
581
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
582
|
+
className: root,
|
|
583
|
+
children: [
|
|
584
|
+
/* @__PURE__ */ jsxs("div", {
|
|
585
|
+
className: topBar,
|
|
586
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
587
|
+
className: topBarTitle,
|
|
588
|
+
children: "Edit image"
|
|
589
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
590
|
+
className: topBarControls,
|
|
591
|
+
children: !decodeError && (editor.activeTool === "crop" ? /* @__PURE__ */ jsx("button", {
|
|
592
|
+
className: "_1257qq2u _1257qq2t _1257qq2s",
|
|
593
|
+
disabled: !hasPendingCrop,
|
|
594
|
+
type: "button",
|
|
595
|
+
onClick: editor.resetCrop,
|
|
596
|
+
children: "Reset crop"
|
|
597
|
+
}) : /* @__PURE__ */ jsx(ToolOptionsBar, {
|
|
598
|
+
canRedo: history.canRedo,
|
|
599
|
+
canUndo: history.canUndo,
|
|
600
|
+
showStrokeWidth: STROKE_TOOLS.has(editor.activeTool),
|
|
601
|
+
strokeColor,
|
|
602
|
+
strokeWidth,
|
|
603
|
+
onRedo: () => annotationApiRef.current?.redo(),
|
|
604
|
+
onStrokeColorChange: setStrokeColor,
|
|
605
|
+
onStrokeWidthChange: setStrokeWidth,
|
|
606
|
+
onUndo: () => annotationApiRef.current?.undo()
|
|
607
|
+
}))
|
|
608
|
+
})]
|
|
609
|
+
}),
|
|
610
|
+
/* @__PURE__ */ jsxs("div", {
|
|
611
|
+
className: body,
|
|
612
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
613
|
+
className: toolRail,
|
|
614
|
+
children: TOOLS.map((tool, index) => /* @__PURE__ */ jsxs("span", {
|
|
615
|
+
className: toolGroup,
|
|
616
|
+
children: [/* @__PURE__ */ jsx("button", {
|
|
617
|
+
"aria-label": tool.label,
|
|
618
|
+
"aria-pressed": editor.activeTool === tool.id,
|
|
619
|
+
disabled: decodeError,
|
|
620
|
+
title: tool.label,
|
|
621
|
+
type: "button",
|
|
622
|
+
className: editor.activeTool === tool.id ? `${toolButton} ${toolButtonActive}` : toolButton,
|
|
623
|
+
onClick: () => handleToolClick(tool.id),
|
|
624
|
+
children: /* @__PURE__ */ jsx(tool.icon, { size: 18 })
|
|
625
|
+
}), index === 0 && /* @__PURE__ */ jsx("div", { className: "_1257qq2c" })]
|
|
626
|
+
}, tool.id))
|
|
627
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
628
|
+
className: canvasArea,
|
|
629
|
+
children: [decodeError ? /* @__PURE__ */ jsxs("div", {
|
|
630
|
+
className: errorState,
|
|
631
|
+
children: [/* @__PURE__ */ jsx(ImageOff, { size: 24 }), /* @__PURE__ */ jsx("span", { children: "This image could not be displayed for editing." })]
|
|
632
|
+
}) : sourceUrl && (isAnnotationTool(editor.activeTool) ? /* @__PURE__ */ jsx(AnnotationSurface, {
|
|
633
|
+
apiRef: annotationApiRef,
|
|
634
|
+
counterRef,
|
|
635
|
+
sourceUrl,
|
|
636
|
+
stateRef: markerStateRef,
|
|
637
|
+
strokeColor,
|
|
638
|
+
strokeWidth,
|
|
639
|
+
tool: editor.activeTool,
|
|
640
|
+
onDecodeError: () => setDecodeError(true),
|
|
641
|
+
onHistoryChange: (canUndo, canRedo) => setHistory({
|
|
642
|
+
canRedo,
|
|
643
|
+
canUndo
|
|
644
|
+
})
|
|
645
|
+
}) : /* @__PURE__ */ jsx(ReactCrop, {
|
|
646
|
+
keepSelection: true,
|
|
647
|
+
className: "_1257qq2e",
|
|
648
|
+
crop: editor.crop,
|
|
649
|
+
onChange: (_, percentCrop) => editor.setCrop(percentCrop),
|
|
650
|
+
onComplete: handleCropComplete,
|
|
651
|
+
children: /* @__PURE__ */ jsx("img", {
|
|
652
|
+
alt: file.name,
|
|
653
|
+
className: "_1257qq2f",
|
|
654
|
+
ref: imgRef,
|
|
655
|
+
src: sourceUrl,
|
|
656
|
+
onError: () => setDecodeError(true),
|
|
657
|
+
onLoad: handleImageLoad
|
|
658
|
+
})
|
|
659
|
+
})), /* @__PURE__ */ jsx("a", {
|
|
660
|
+
className: attribution,
|
|
661
|
+
href: "https://markerjs.com",
|
|
662
|
+
rel: "noreferrer",
|
|
663
|
+
target: "_blank",
|
|
664
|
+
children: "marker.js"
|
|
665
|
+
})]
|
|
666
|
+
})]
|
|
667
|
+
}),
|
|
668
|
+
/* @__PURE__ */ jsxs("div", {
|
|
669
|
+
className: footer,
|
|
670
|
+
children: [
|
|
671
|
+
/* @__PURE__ */ jsx("button", {
|
|
672
|
+
className: ghostButton,
|
|
673
|
+
type: "button",
|
|
674
|
+
onClick: onSkip,
|
|
675
|
+
children: "Upload without editing"
|
|
676
|
+
}),
|
|
677
|
+
/* @__PURE__ */ jsx("div", { className: footerSpacer }),
|
|
678
|
+
/* @__PURE__ */ jsx("button", {
|
|
679
|
+
className: secondaryButton,
|
|
680
|
+
type: "button",
|
|
681
|
+
onClick: onCancel,
|
|
682
|
+
children: "Cancel"
|
|
683
|
+
}),
|
|
684
|
+
/* @__PURE__ */ jsx("button", {
|
|
685
|
+
className: primaryButton,
|
|
686
|
+
disabled: decodeError || exporting,
|
|
687
|
+
type: "button",
|
|
688
|
+
onClick: handleConfirm,
|
|
689
|
+
children: "Upload"
|
|
690
|
+
})
|
|
691
|
+
]
|
|
692
|
+
})
|
|
693
|
+
]
|
|
694
|
+
});
|
|
695
|
+
};
|
|
696
|
+
//#endregion
|
|
697
|
+
//#region src/ImageEditModalPlugin.tsx
|
|
698
|
+
var ImageEditModalPlugin = () => {
|
|
699
|
+
const preprocess = useImagePreprocess();
|
|
700
|
+
const portalTheme = usePortalTheme();
|
|
701
|
+
const portalThemeRef = useRef(portalTheme);
|
|
702
|
+
portalThemeRef.current = portalTheme;
|
|
703
|
+
useEffect(() => {
|
|
704
|
+
if (!preprocess) return;
|
|
705
|
+
const fn = (file) => new Promise((resolve) => {
|
|
706
|
+
let settled = false;
|
|
707
|
+
const settle = (result) => {
|
|
708
|
+
if (settled) return;
|
|
709
|
+
settled = true;
|
|
710
|
+
resolve(result);
|
|
711
|
+
};
|
|
712
|
+
const dismiss = presentDialog({
|
|
713
|
+
className: fullscreenPopup,
|
|
714
|
+
content: () => /* @__PURE__ */ jsx(ImageEditModal, {
|
|
715
|
+
file,
|
|
716
|
+
onCancel: () => {
|
|
717
|
+
settle(null);
|
|
718
|
+
dismiss();
|
|
719
|
+
},
|
|
720
|
+
onConfirm: (edited) => {
|
|
721
|
+
settle(edited);
|
|
722
|
+
dismiss();
|
|
723
|
+
},
|
|
724
|
+
onSkip: () => {
|
|
725
|
+
settle("skip");
|
|
726
|
+
dismiss();
|
|
727
|
+
}
|
|
728
|
+
}),
|
|
729
|
+
onClose: () => settle(null),
|
|
730
|
+
portalClassName: portalThemeRef.current.className,
|
|
731
|
+
showCloseButton: false,
|
|
732
|
+
theme: portalThemeRef.current.theme
|
|
733
|
+
});
|
|
734
|
+
});
|
|
735
|
+
return preprocess.register(fn);
|
|
736
|
+
}, [preprocess]);
|
|
737
|
+
return null;
|
|
738
|
+
};
|
|
739
|
+
//#endregion
|
|
740
|
+
export { ImageEditModal, ImageEditModalPlugin };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { AnnotationState } from '@markerjs/markerjs3';
|
|
2
|
+
import { CropRect } from './crop';
|
|
3
|
+
export interface CropRebase {
|
|
4
|
+
bitmapUrl: string;
|
|
5
|
+
markerState: AnnotationState | null;
|
|
6
|
+
}
|
|
7
|
+
export declare function applyCropRebase(sourceUrl: string, rect: CropRect, markerState: AnnotationState | null): Promise<CropRebase>;
|
|
8
|
+
export interface ExportInput {
|
|
9
|
+
hasRebasedBitmap: boolean;
|
|
10
|
+
markerState: AnnotationState | null;
|
|
11
|
+
original: File;
|
|
12
|
+
pendingCropRect: CropRect | null;
|
|
13
|
+
sourceUrl: string;
|
|
14
|
+
}
|
|
15
|
+
export declare function exportResult({ hasRebasedBitmap, markerState, original, pendingCropRect, sourceUrl, }: ExportInput): Promise<File>;
|
|
16
|
+
//# sourceMappingURL=pipeline.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pipeline.d.ts","sourceRoot":"","sources":["../src/pipeline.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAI3D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,QAAQ,CAAC;AAIvC,MAAM,WAAW,UAAU;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,eAAe,GAAG,IAAI,CAAC;CACrC;AAED,wBAAsB,eAAe,CACnC,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,QAAQ,EACd,WAAW,EAAE,eAAe,GAAG,IAAI,GAClC,OAAO,CAAC,UAAU,CAAC,CAarB;AAED,MAAM,WAAW,WAAW;IAC1B,gBAAgB,EAAE,OAAO,CAAC;IAC1B,WAAW,EAAE,eAAe,GAAG,IAAI,CAAC;IACpC,QAAQ,EAAE,IAAI,CAAC;IACf,eAAe,EAAE,QAAQ,GAAG,IAAI,CAAC;IACjC,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,wBAAsB,YAAY,CAAC,EACjC,gBAAgB,EAChB,WAAW,EACX,QAAQ,EACR,eAAe,EACf,SAAS,GACV,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAa7B"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rasterize.d.ts","sourceRoot":"","sources":["../src/rasterize.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAK3D,wBAAsB,oBAAoB,CACxC,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,eAAe,GACrB,OAAO,CAAC,iBAAiB,CAAC,CAQ5B"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
@keyframes marching-ants{0%{background-position:0 0,0 100%,0 0,100% 0}to{background-position:20px 0,-20px 100%,0 -20px,100% 20px}}:root{--rc-drag-handle-size:12px;--rc-drag-handle-mobile-size:24px;--rc-drag-handle-bg-colour:#0003;--rc-drag-bar-size:6px;--rc-border-color:#ffffffb3;--rc-focus-color:#08f}.ReactCrop{cursor:crosshair;max-width:100%;display:inline-block;position:relative}.ReactCrop *,.ReactCrop :before,.ReactCrop :after{box-sizing:border-box}.ReactCrop--disabled,.ReactCrop--locked{cursor:inherit}.ReactCrop__child-wrapper{max-height:inherit;overflow:hidden}.ReactCrop__child-wrapper>img,.ReactCrop__child-wrapper>video{max-width:100%;max-height:inherit;display:block}.ReactCrop:not(.ReactCrop--disabled) .ReactCrop__child-wrapper>img,.ReactCrop:not(.ReactCrop--disabled) .ReactCrop__child-wrapper>video,.ReactCrop:not(.ReactCrop--disabled) .ReactCrop__crop-selection{touch-action:none}.ReactCrop__crop-mask{pointer-events:none;width:calc(100% + .5px);height:calc(100% + .5px);position:absolute;top:0;bottom:0;left:0;right:0}.ReactCrop__crop-selection{cursor:move;position:absolute;top:0;left:0;transform:translateZ(0)}.ReactCrop--disabled .ReactCrop__crop-selection{cursor:inherit}.ReactCrop--circular-crop .ReactCrop__crop-selection{border-radius:50%}.ReactCrop--circular-crop .ReactCrop__crop-selection:after{pointer-events:none;content:"";border:1px solid var(--rc-border-color);opacity:.3;position:absolute;top:-1px;bottom:-1px;left:-1px;right:-1px}.ReactCrop--no-animate .ReactCrop__crop-selection{outline:1px dashed #fff}.ReactCrop__crop-selection:not(.ReactCrop--no-animate .ReactCrop__crop-selection){color:#fff;background-image:linear-gradient(90deg,#fff 50%,#444 50%),linear-gradient(90deg,#fff 50%,#444 50%),linear-gradient(#fff 50%,#444 50%),linear-gradient(#fff 50%,#444 50%);background-position:0 0,0 100%,0 0,100% 0;background-repeat:repeat-x,repeat-x,repeat-y,repeat-y;background-size:10px 1px,10px 1px,1px 10px,1px 10px;animation:1s linear infinite marching-ants}.ReactCrop__crop-selection:focus{outline:2px solid var(--rc-focus-color);outline-offset:-1px}.ReactCrop--invisible-crop .ReactCrop__crop-mask,.ReactCrop--invisible-crop .ReactCrop__crop-selection{display:none}.ReactCrop__rule-of-thirds-vt:before,.ReactCrop__rule-of-thirds-vt:after,.ReactCrop__rule-of-thirds-hz:before,.ReactCrop__rule-of-thirds-hz:after{content:"";background-color:#fff6;display:block;position:absolute}.ReactCrop__rule-of-thirds-vt:before,.ReactCrop__rule-of-thirds-vt:after{width:1px;height:100%}.ReactCrop__rule-of-thirds-vt:before{left:33.3333%}.ReactCrop__rule-of-thirds-vt:after{left:66.6667%}.ReactCrop__rule-of-thirds-hz:before,.ReactCrop__rule-of-thirds-hz:after{width:100%;height:1px}.ReactCrop__rule-of-thirds-hz:before{top:33.3333%}.ReactCrop__rule-of-thirds-hz:after{top:66.6667%}.ReactCrop__drag-handle{width:var(--rc-drag-handle-size);height:var(--rc-drag-handle-size);background-color:var(--rc-drag-handle-bg-colour);border:1px solid var(--rc-border-color);position:absolute}.ReactCrop__drag-handle:focus{background:var(--rc-focus-color)}.ReactCrop .ord-nw{cursor:nw-resize;top:0;left:0;transform:translate(-50%,-50%)}.ReactCrop .ord-n{cursor:n-resize;top:0;left:50%;transform:translate(-50%,-50%)}.ReactCrop .ord-ne{cursor:ne-resize;top:0;right:0;transform:translate(50%,-50%)}.ReactCrop .ord-e{cursor:e-resize;top:50%;right:0;transform:translate(50%,-50%)}.ReactCrop .ord-se{cursor:se-resize;bottom:0;right:0;transform:translate(50%,50%)}.ReactCrop .ord-s{cursor:s-resize;bottom:0;left:50%;transform:translate(-50%,50%)}.ReactCrop .ord-sw{cursor:sw-resize;bottom:0;left:0;transform:translate(-50%,50%)}.ReactCrop .ord-w{cursor:w-resize;top:50%;left:0;transform:translate(-50%,-50%)}.ReactCrop__disabled .ReactCrop__drag-handle{cursor:inherit}.ReactCrop__drag-bar{position:absolute}.ReactCrop__drag-bar.ord-n{width:100%;height:var(--rc-drag-bar-size);top:0;left:0;transform:translateY(-50%)}.ReactCrop__drag-bar.ord-e{width:var(--rc-drag-bar-size);height:100%;top:0;right:0;transform:translate(50%)}.ReactCrop__drag-bar.ord-s{width:100%;height:var(--rc-drag-bar-size);bottom:0;left:0;transform:translateY(50%)}.ReactCrop__drag-bar.ord-w{width:var(--rc-drag-bar-size);height:100%;top:0;left:0;transform:translate(-50%)}.ReactCrop--new-crop .ReactCrop__drag-bar,.ReactCrop--new-crop .ReactCrop__drag-handle,.ReactCrop--fixed-aspect .ReactCrop__drag-bar,.ReactCrop--fixed-aspect .ReactCrop__drag-handle.ord-n,.ReactCrop--fixed-aspect .ReactCrop__drag-handle.ord-e,.ReactCrop--fixed-aspect .ReactCrop__drag-handle.ord-s,.ReactCrop--fixed-aspect .ReactCrop__drag-handle.ord-w{display:none}@media (pointer:coarse){.ReactCrop .ord-n,.ReactCrop .ord-e,.ReactCrop .ord-s,.ReactCrop .ord-w{display:none}.ReactCrop__drag-handle{width:var(--rc-drag-handle-mobile-size);height:var(--rc-drag-handle-mobile-size)}}:root{--rc-text:#000;--rc-text-secondary:#262626;--rc-text-tertiary:#737373;--rc-text-quaternary:#a3a3a3;--rc-bg:#fff;--rc-bg-secondary:#fafafa;--rc-bg-tertiary:#f5f5f5;--rc-fill:#e8e8e8;--rc-fill-secondary:#eee;--rc-fill-tertiary:#f5f5f5;--rc-fill-quaternary:#fafafa;--rc-border:#f5f5f5;--rc-accent:#2563eb;--rc-accent-light:#2563eb20;--rc-link:#2563eb;--rc-code-text:#404040;--rc-code-bg:#f5f5f5;--rc-hr-border:#e5e5e5;--rc-quote-border:#2563eb;--rc-quote-bg:#f5f5f5;--rc-alert-info:#006bb7;--rc-alert-warning:#c50;--rc-alert-tip:#1c0;--rc-alert-caution:#c01;--rc-alert-important:#50c;--rc-max-width:700px;--rc-shadow-top-bar:0 8px 30px #0000001f, 0 2px 8px #0000000f;--rc-shadow-modal:0 10px 15px -3px #0000001a, 0 4px 6px -4px #0000001a;--rc-shadow-menu:0 1px 4px #0000000a, 0 4px 16px #00000014;--rc-space-xs:4px;--rc-space-sm:8px;--rc-space-md:16px;--rc-space-lg:24px;--rc-space-xl:32px;--rc-font-family-sans:"PingFang SC", "Microsoft YaHei", "Segoe UI", Roboto, Helvetica, "noto sans sc", "hiragino sans gb", -apple-system, system-ui, sans-serif, Apple Color Emoji, Segoe UI Emoji, Not Color Emoji;--rc-font-family-serif:"Noto Serif CJK SC", "Source Han Serif SC", "Source Han Serif", "source-han-serif-sc", "Songti SC", STSong, "华文宋体", serif;--rc-font-family-kai:"楷体", KaiTi, STKaiti, "Kaiti SC", "LXGW WenKai", "霞鹜文楷", "Noto Serif CJK SC", serif;--rc-font-mono:"SF Mono", SFMono-Regular, ui-monospace, "DejaVu Sans Mono", Menlo, Consolas, monospace;--rc-font-size-2xs:.625em;--rc-font-size-xs:.75em;--rc-font-size-sm:.8125em;--rc-font-size-md:.875em;--rc-font-size-lg:1.25em;--rc-font-size-base:16px;--rc-font-size-small:14px;--rc-line-height:1.7;--rc-line-height-tight:1.4;--rc-font-family:"PingFang SC", "Microsoft YaHei", "Segoe UI", Roboto, Helvetica, "noto sans sc", "hiragino sans gb", -apple-system, system-ui, sans-serif, Apple Color Emoji, Segoe UI Emoji, Not Color Emoji;--rc-radius-sm:4px;--rc-radius-md:8px;--rc-radius-lg:12px}:root.dark,[data-theme=dark]{--rc-text:#fafafa;--rc-text-secondary:#a3a3a3;--rc-text-tertiary:#737373;--rc-text-quaternary:#525252;--rc-bg:#0a0a0a;--rc-bg-secondary:#171717;--rc-bg-tertiary:#262626;--rc-fill:#2a2a2a;--rc-fill-secondary:#222;--rc-fill-tertiary:#1a1a1a;--rc-fill-quaternary:#141414;--rc-border:#262626;--rc-accent:#60a5fa;--rc-accent-light:#60a5fa20;--rc-link:#60a5fa;--rc-code-text:#d4d4d4;--rc-code-bg:#262626;--rc-hr-border:#262626;--rc-quote-border:#60a5fa;--rc-quote-bg:#262626;--rc-alert-info:#7db9e5;--rc-alert-warning:#da864a;--rc-alert-tip:#54da48;--rc-alert-caution:#e16973;--rc-alert-important:#9966e0;--rc-shadow-top-bar:0 8px 30px #00000073, 0 2px 8px #0000004d;--rc-shadow-modal:0 10px 15px -3px #0006, 0 4px 6px -4px #00000059;--rc-shadow-menu:0 1px 4px #00000040, 0 4px 16px #0006}._1lodjav0{--rc-text:#000;--rc-text-secondary:#262626;--rc-text-tertiary:#737373;--rc-text-quaternary:#a3a3a3;--rc-bg:#fff;--rc-bg-secondary:#fafafa;--rc-bg-tertiary:#f5f5f5;--rc-fill:#e8e8e8;--rc-fill-secondary:#eee;--rc-fill-tertiary:#f5f5f5;--rc-fill-quaternary:#fafafa;--rc-border:#f5f5f5;--rc-accent:#2563eb;--rc-accent-light:#2563eb20;--rc-link:#2563eb;--rc-code-text:#404040;--rc-code-bg:#f5f5f5;--rc-hr-border:#e5e5e5;--rc-quote-border:#2563eb;--rc-quote-bg:#f5f5f5;--rc-alert-info:#006bb7;--rc-alert-warning:#c50;--rc-alert-tip:#1c0;--rc-alert-caution:#c01;--rc-alert-important:#50c;--rc-max-width:700px;--rc-shadow-top-bar:0 8px 30px #0000001f, 0 2px 8px #0000000f;--rc-shadow-modal:0 10px 15px -3px #0000001a, 0 4px 6px -4px #0000001a;--rc-shadow-menu:0 1px 4px #0000000a, 0 4px 16px #00000014;--rc-space-xs:4px;--rc-space-sm:8px;--rc-space-md:16px;--rc-space-lg:24px;--rc-space-xl:32px;--rc-font-family-sans:"PingFang SC", "Microsoft YaHei", "Segoe UI", Roboto, Helvetica, "noto sans sc", "hiragino sans gb", -apple-system, system-ui, sans-serif, Apple Color Emoji, Segoe UI Emoji, Not Color Emoji;--rc-font-family-serif:"Noto Serif CJK SC", "Source Han Serif SC", "Source Han Serif", "source-han-serif-sc", "Songti SC", STSong, "华文宋体", serif;--rc-font-family-kai:"楷体", KaiTi, STKaiti, "Kaiti SC", "LXGW WenKai", "霞鹜文楷", "Noto Serif CJK SC", serif;--rc-font-mono:"SF Mono", SFMono-Regular, ui-monospace, "DejaVu Sans Mono", Menlo, Consolas, monospace;--rc-font-size-2xs:.625em;--rc-font-size-xs:.75em;--rc-font-size-sm:.8125em;--rc-font-size-md:.875em;--rc-font-size-lg:1.25em;--rc-font-size-base:16px;--rc-font-size-small:14px;--rc-line-height:1.7;--rc-line-height-tight:1.4;--rc-font-family:"PingFang SC", "Microsoft YaHei", "Segoe UI", Roboto, Helvetica, "noto sans sc", "hiragino sans gb", -apple-system, system-ui, sans-serif, Apple Color Emoji, Segoe UI Emoji, Not Color Emoji;--rc-radius-sm:4px;--rc-radius-md:8px;--rc-radius-lg:12px}._1lodjav1{--rc-text:#000;--rc-text-secondary:#262626;--rc-text-tertiary:#737373;--rc-text-quaternary:#a3a3a3;--rc-bg:#fff;--rc-bg-secondary:#fafafa;--rc-bg-tertiary:#f5f5f5;--rc-fill:#e8e8e8;--rc-fill-secondary:#eee;--rc-fill-tertiary:#f5f5f5;--rc-fill-quaternary:#fafafa;--rc-border:#f5f5f5;--rc-accent:#2563eb;--rc-accent-light:#2563eb20;--rc-link:#2563eb;--rc-code-text:#404040;--rc-code-bg:#f5f5f5;--rc-hr-border:#e5e5e5;--rc-quote-border:#2563eb;--rc-quote-bg:#f5f5f5;--rc-alert-info:#006bb7;--rc-alert-warning:#c50;--rc-alert-tip:#1c0;--rc-alert-caution:#c01;--rc-alert-important:#50c;--rc-max-width:700px;--rc-shadow-top-bar:0 8px 30px #0000001f, 0 2px 8px #0000000f;--rc-shadow-modal:0 10px 15px -3px #0000001a, 0 4px 6px -4px #0000001a;--rc-shadow-menu:0 1px 4px #0000000a, 0 4px 16px #00000014;--rc-space-xs:4px;--rc-space-sm:8px;--rc-space-md:16px;--rc-space-lg:24px;--rc-space-xl:32px;--rc-font-family-sans:"PingFang SC", "Microsoft YaHei", "Segoe UI", Roboto, Helvetica, "noto sans sc", "hiragino sans gb", -apple-system, system-ui, sans-serif, Apple Color Emoji, Segoe UI Emoji, Not Color Emoji;--rc-font-family-serif:"Noto Serif CJK SC", "Source Han Serif SC", "Source Han Serif", "source-han-serif-sc", "Songti SC", STSong, "华文宋体", serif;--rc-font-family-kai:"楷体", KaiTi, STKaiti, "Kaiti SC", "LXGW WenKai", "霞鹜文楷", "Noto Serif CJK SC", serif;--rc-font-mono:"SF Mono", SFMono-Regular, ui-monospace, "DejaVu Sans Mono", Menlo, Consolas, monospace;--rc-font-size-2xs:.625em;--rc-font-size-xs:.75em;--rc-font-size-sm:.8125em;--rc-font-size-md:.875em;--rc-font-size-lg:1.25em;--rc-font-size-base:16px;--rc-font-size-small:14px;--rc-line-height:1.8;--rc-line-height-tight:1.4;--rc-font-family:"Noto Serif CJK SC", "Source Han Serif SC", "Source Han Serif", "source-han-serif-sc", "Songti SC", STSong, "华文宋体", serif;--rc-radius-sm:4px;--rc-radius-md:8px;--rc-radius-lg:12px}._1lodjav2{--rc-text:#000;--rc-text-secondary:#262626;--rc-text-tertiary:#737373;--rc-text-quaternary:#a3a3a3;--rc-bg:#fff;--rc-bg-secondary:#fafafa;--rc-bg-tertiary:#f5f5f5;--rc-fill:#e8e8e8;--rc-fill-secondary:#eee;--rc-fill-tertiary:#f5f5f5;--rc-fill-quaternary:#fafafa;--rc-border:#f5f5f5;--rc-accent:#2563eb;--rc-accent-light:#2563eb20;--rc-link:#2563eb;--rc-code-text:#404040;--rc-code-bg:#f5f5f5;--rc-hr-border:#e5e5e5;--rc-quote-border:#a3a3a3;--rc-quote-bg:#fafafa;--rc-alert-info:#006bb7;--rc-alert-warning:#c50;--rc-alert-tip:#1c0;--rc-alert-caution:#c01;--rc-alert-important:#50c;--rc-max-width:none;--rc-shadow-top-bar:0 8px 30px #0000001f, 0 2px 8px #0000000f;--rc-shadow-modal:0 10px 15px -3px #0000001a, 0 4px 6px -4px #0000001a;--rc-shadow-menu:0 1px 4px #0000000a, 0 4px 16px #00000014;--rc-space-xs:2px;--rc-space-sm:4px;--rc-space-md:10px;--rc-space-lg:16px;--rc-space-xl:20px;--rc-font-family-sans:"PingFang SC", "Microsoft YaHei", "Segoe UI", Roboto, Helvetica, "noto sans sc", "hiragino sans gb", -apple-system, system-ui, sans-serif, Apple Color Emoji, Segoe UI Emoji, Not Color Emoji;--rc-font-family-serif:"Noto Serif CJK SC", "Source Han Serif SC", "Source Han Serif", "source-han-serif-sc", "Songti SC", STSong, "华文宋体", serif;--rc-font-family-kai:"楷体", KaiTi, STKaiti, "Kaiti SC", "LXGW WenKai", "霞鹜文楷", "Noto Serif CJK SC", serif;--rc-font-mono:"SF Mono", SFMono-Regular, ui-monospace, "DejaVu Sans Mono", Menlo, Consolas, monospace;--rc-font-size-2xs:.625em;--rc-font-size-xs:.75em;--rc-font-size-sm:.8125em;--rc-font-size-md:.875em;--rc-font-size-lg:1.25em;--rc-font-size-base:14px;--rc-font-size-small:12px;--rc-line-height:1.5;--rc-line-height-tight:1.3;--rc-font-family:"PingFang SC", "Microsoft YaHei", "Segoe UI", Roboto, Helvetica, "noto sans sc", "hiragino sans gb", -apple-system, system-ui, sans-serif, Apple Color Emoji, Segoe UI Emoji, Not Color Emoji;--rc-radius-sm:3px;--rc-radius-md:6px;--rc-radius-lg:12px}.dark ._1lodjav0,[data-theme=dark] ._1lodjav0,.dark._1lodjav0,[data-theme=dark]._1lodjav0,.dark ._1lodjav1,[data-theme=dark] ._1lodjav1,.dark._1lodjav1,[data-theme=dark]._1lodjav1,.dark ._1lodjav2,[data-theme=dark] ._1lodjav2,.dark._1lodjav2,[data-theme=dark]._1lodjav2{--rc-text:#fafafa;--rc-text-secondary:#a3a3a3;--rc-text-tertiary:#737373;--rc-text-quaternary:#525252;--rc-bg:#0a0a0a;--rc-bg-secondary:#171717;--rc-bg-tertiary:#262626;--rc-fill:#2a2a2a;--rc-fill-secondary:#222;--rc-fill-tertiary:#1a1a1a;--rc-fill-quaternary:#141414;--rc-border:#262626;--rc-accent:#60a5fa;--rc-accent-light:#60a5fa20;--rc-link:#60a5fa;--rc-code-text:#d4d4d4;--rc-code-bg:#262626;--rc-hr-border:#262626;--rc-quote-border:#60a5fa;--rc-quote-bg:#262626;--rc-alert-info:#7db9e5;--rc-alert-warning:#da864a;--rc-alert-tip:#54da48;--rc-alert-caution:#e16973;--rc-alert-important:#9966e0;--rc-shadow-top-bar:0 8px 30px #00000073, 0 2px 8px #0000004d;--rc-shadow-modal:0 10px 15px -3px #0006, 0 4px 6px -4px #00000059;--rc-shadow-menu:0 1px 4px #00000040, 0 4px 16px #0006}@keyframes _1257qq20{0%{opacity:0}to{opacity:1}}@keyframes _1257qq21{0%{opacity:1}to{opacity:0}}._1257qq22._1257qq22{border-radius:0;flex-direction:column;gap:0;width:100vw;max-width:100vw;height:100vh;max-height:100vh;margin:0;padding:0;display:flex;position:fixed;top:0;bottom:0;left:0;right:0;overflow:hidden;transform:none}._1257qq22._1257qq22[data-open]{animation:.15s ease-out _1257qq20}._1257qq22._1257qq22[data-closed]{animation:.1s ease-in _1257qq21}._1257qq23{background-color:var(--rc-bg);min-height:0;color:var(--rc-text);font-family:var(--rc-font-family-sans);flex-direction:column;flex:1;display:flex}._1257qq24{border-bottom:1px solid var(--rc-border);flex-shrink:0;align-items:center;gap:12px;height:48px;padding:0 16px;display:flex}._1257qq25{font-size:14px;font-weight:600}._1257qq26{flex:1;align-items:center;gap:8px;min-width:0;display:flex}._1257qq27{display:contents}._1257qq28{flex:1;min-height:0;display:flex}._1257qq29{border-right:1px solid var(--rc-border);flex-direction:column;flex-shrink:0;align-items:center;gap:4px;width:56px;padding:12px 0;display:flex}._1257qq2a{border-radius:var(--rc-radius-sm);color:#737373;cursor:pointer;background-color:#0000;border:none;justify-content:center;align-items:center;width:36px;height:36px;display:flex}._1257qq2a:hover:not(:disabled){background-color:var(--rc-fill-secondary);color:var(--rc-text)}._1257qq2a:disabled{color:#a3a3a3;cursor:default;opacity:.55}._1257qq2b{background-color:var(--rc-fill);color:var(--rc-text)}._1257qq2b:hover:not(:disabled){background-color:var(--rc-fill)}._1257qq2c{background-color:var(--rc-border);width:28px;height:1px;margin:6px 0}._1257qq2d{background:repeating-conic-gradient(var(--rc-bg-tertiary) 0% 25%, var(--rc-bg) 0% 50%) 50% / 20px 20px;flex:1;justify-content:center;align-items:center;min-width:0;min-height:0;padding:24px;display:flex;position:relative;overflow:hidden}._1257qq2e{max-width:100%;max-height:100%;box-shadow:var(--rc-shadow-modal)}._1257qq2f{max-width:100%;display:block}._1257qq2e .ReactCrop__child-wrapper>._1257qq2f{max-height:calc(100vh - 152px)}._1257qq2g{width:100%;min-width:0;height:100%;min-height:0;display:flex;position:relative}._1257qq2h{align-items:center;gap:6px;margin-left:auto;display:flex}._1257qq2i{cursor:pointer;border:1px solid #73737359;border-radius:50%;width:18px;height:18px;padding:0}._1257qq2j{box-shadow:0 0 0 2px var(--rc-bg), 0 0 0 4px #737373}._1257qq2k{border-radius:var(--rc-radius-sm);color:#737373;cursor:pointer;background-color:#0000;border:none;justify-content:center;align-items:center;width:26px;height:26px;padding:0;display:flex}._1257qq2k:hover:not(:disabled){background-color:var(--rc-fill-secondary);color:var(--rc-text)}._1257qq2k:disabled{color:#a3a3a3;cursor:default;opacity:.55}._1257qq2l{background-color:var(--rc-fill);color:var(--rc-text)}._1257qq2m{background-color:currentColor;border-radius:50%}._1257qq2n{background-color:var(--rc-border);width:1px;height:18px;margin:0 4px}._1257qq2o{border-radius:var(--rc-radius-md);background-color:var(--rc-bg);color:#737373;box-shadow:var(--rc-shadow-modal);flex-direction:column;align-items:center;gap:8px;padding:24px;font-size:13px;display:flex}._1257qq2p{color:#a3a3a3;font-size:11px;text-decoration:none;position:absolute;bottom:8px;right:10px}._1257qq2p:hover{color:#737373;text-decoration:underline}._1257qq2q{border-top:1px solid var(--rc-border);flex-shrink:0;align-items:center;gap:8px;height:56px;padding:0 16px;display:flex}._1257qq2r{flex:1}._1257qq2s{border-radius:var(--rc-radius-sm);cursor:pointer;justify-content:center;align-items:center;height:32px;padding:0 14px;font-size:13px;font-weight:500;display:inline-flex}._1257qq2s:disabled{opacity:.5;cursor:default}._1257qq2t{color:#737373;background-color:#0000;border:none}._1257qq2t:hover:not(:disabled){background-color:var(--rc-fill-secondary);color:var(--rc-text)}._1257qq2u{height:26px;margin-left:auto;padding:0 10px;font-size:12px}._1257qq2v{border:1px solid var(--rc-border);background-color:var(--rc-bg);color:var(--rc-text)}._1257qq2v:hover:not(:disabled){background-color:var(--rc-fill-secondary)}._1257qq2w{background-color:var(--rc-text);color:var(--rc-bg);border:1px solid #0000}._1257qq2w:hover:not(:disabled){opacity:.85}
|
|
2
|
+
/*$vite$:1*/
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
declare const _fullscreenPopup: string;
|
|
2
|
+
export { _fullscreenPopup as fullscreenPopup };
|
|
3
|
+
export declare const root: string;
|
|
4
|
+
export declare const topBar: string;
|
|
5
|
+
export declare const topBarTitle: string;
|
|
6
|
+
export declare const topBarControls: string;
|
|
7
|
+
export declare const toolGroup: string;
|
|
8
|
+
export declare const body: string;
|
|
9
|
+
export declare const toolRail: string;
|
|
10
|
+
export declare const toolButton: string;
|
|
11
|
+
export declare const toolButtonActive: string;
|
|
12
|
+
export declare const toolDivider: string;
|
|
13
|
+
export declare const canvasArea: string;
|
|
14
|
+
export declare const cropSurface: string;
|
|
15
|
+
export declare const cropImage: string;
|
|
16
|
+
export declare const annotationSurface: string;
|
|
17
|
+
export declare const optionsBar: string;
|
|
18
|
+
export declare const swatch: string;
|
|
19
|
+
export declare const swatchActive: string;
|
|
20
|
+
export declare const optionButton: string;
|
|
21
|
+
export declare const optionButtonActive: string;
|
|
22
|
+
export declare const strokeDot: string;
|
|
23
|
+
export declare const optionDivider: string;
|
|
24
|
+
export declare const errorState: string;
|
|
25
|
+
export declare const attribution: string;
|
|
26
|
+
export declare const footer: string;
|
|
27
|
+
export declare const footerSpacer: string;
|
|
28
|
+
export declare const ghostButton: string;
|
|
29
|
+
export declare const topBarGhostButton: string;
|
|
30
|
+
export declare const secondaryButton: string;
|
|
31
|
+
export declare const primaryButton: string;
|
|
32
|
+
//# sourceMappingURL=styles.css.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"styles.css.d.ts","sourceRoot":"","sources":["../src/styles.css.ts"],"names":[],"mappings":"AAcA,QAAA,MAAM,gBAAgB,QAAY,CAAC;AA2BnC,OAAO,EAAE,gBAAgB,IAAI,eAAe,EAAE,CAAC;AAE/C,eAAO,MAAM,IAAI,QAQf,CAAC;AAEH,eAAO,MAAM,MAAM,QAQjB,CAAC;AAEH,eAAO,MAAM,WAAW,QAGtB,CAAC;AAEH,eAAO,MAAM,cAAc,QAMzB,CAAC;AAEH,eAAO,MAAM,SAAS,QAEpB,CAAC;AAEH,eAAO,MAAM,IAAI,QAIf,CAAC;AAEH,eAAO,MAAM,QAAQ,QASnB,CAAC;AAEH,eAAO,MAAM,UAAU,QAsBrB,CAAC;AAEH,eAAO,MAAM,gBAAgB,QAQ3B,CAAC;AAEH,eAAO,MAAM,WAAW,QAKtB,CAAC;AAEH,eAAO,MAAM,UAAU,QAYrB,CAAC;AAEH,eAAO,MAAM,WAAW,QAItB,CAAC;AAEH,eAAO,MAAM,SAAS,QAGpB,CAAC;AAUH,eAAO,MAAM,iBAAiB,QAO5B,CAAC;AAEH,eAAO,MAAM,UAAU,QAKrB,CAAC;AAEH,eAAO,MAAM,MAAM,QAOjB,CAAC;AAEH,eAAO,MAAM,YAAY,QAEvB,CAAC;AAEH,eAAO,MAAM,YAAY,QAuBvB,CAAC;AAEH,eAAO,MAAM,kBAAkB,QAG7B,CAAC;AAEH,eAAO,MAAM,SAAS,QAGpB,CAAC;AAEH,eAAO,MAAM,aAAa,QAKxB,CAAC;AAEH,eAAO,MAAM,UAAU,QAWrB,CAAC;AAEH,eAAO,MAAM,WAAW,QAatB,CAAC;AAEH,eAAO,MAAM,MAAM,QAQjB,CAAC;AAEH,eAAO,MAAM,YAAY,QAEvB,CAAC;AAoBH,eAAO,MAAM,WAAW,QAatB,CAAC;AAEH,eAAO,MAAM,iBAAiB,QAQ5B,CAAC;AAEH,eAAO,MAAM,eAAe,QAY1B,CAAC;AAEH,eAAO,MAAM,aAAa,QAYxB,CAAC"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { AnnotationState } from '@markerjs/markerjs3';
|
|
2
|
+
import { RefObject } from 'react';
|
|
3
|
+
import { Crop } from 'react-image-crop';
|
|
4
|
+
import { CropRect } from './crop';
|
|
5
|
+
export type EditorTool = 'crop' | 'arrow' | 'pen' | 'rect' | 'ellipse' | 'text' | 'counter' | 'cover';
|
|
6
|
+
export interface ImageNaturalSize {
|
|
7
|
+
height: number;
|
|
8
|
+
width: number;
|
|
9
|
+
}
|
|
10
|
+
export declare const FULL_IMAGE_CROP: Crop;
|
|
11
|
+
export interface ImageEditorState {
|
|
12
|
+
activeTool: EditorTool;
|
|
13
|
+
confirmCropAndSwitch: (tool: EditorTool) => Promise<void>;
|
|
14
|
+
crop: Crop;
|
|
15
|
+
croppedAreaPixels: CropRect | null;
|
|
16
|
+
hasPendingCrop: boolean;
|
|
17
|
+
markerStateRef: RefObject<AnnotationState | null>;
|
|
18
|
+
naturalSize: ImageNaturalSize | null;
|
|
19
|
+
rebasedBitmapUrl: string | null;
|
|
20
|
+
resetCrop: () => void;
|
|
21
|
+
setActiveTool: (tool: EditorTool) => void;
|
|
22
|
+
setCrop: (crop: Crop) => void;
|
|
23
|
+
setCroppedAreaPixels: (area: CropRect | null) => void;
|
|
24
|
+
setNaturalSize: (size: ImageNaturalSize | null) => void;
|
|
25
|
+
sourceUrl: string;
|
|
26
|
+
}
|
|
27
|
+
export declare function useImageEditorState(objectUrl: string): ImageEditorState;
|
|
28
|
+
//# sourceMappingURL=useImageEditorState.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useImageEditorState.d.ts","sourceRoot":"","sources":["../src/useImageEditorState.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAC3D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAEvC,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAE7C,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,QAAQ,CAAC;AAIvC,MAAM,MAAM,UAAU,GAClB,MAAM,GACN,OAAO,GACP,KAAK,GACL,MAAM,GACN,SAAS,GACT,MAAM,GACN,SAAS,GACT,OAAO,CAAC;AAEZ,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;CACf;AAGD,eAAO,MAAM,eAAe,EAAE,IAAyD,CAAC;AAExF,MAAM,WAAW,gBAAgB;IAC/B,UAAU,EAAE,UAAU,CAAC;IAEvB,oBAAoB,EAAE,CAAC,IAAI,EAAE,UAAU,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1D,IAAI,EAAE,IAAI,CAAC;IAEX,iBAAiB,EAAE,QAAQ,GAAG,IAAI,CAAC;IACnC,cAAc,EAAE,OAAO,CAAC;IAExB,cAAc,EAAE,SAAS,CAAC,eAAe,GAAG,IAAI,CAAC,CAAC;IAClD,WAAW,EAAE,gBAAgB,GAAG,IAAI,CAAC;IAErC,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,SAAS,EAAE,MAAM,IAAI,CAAC;IACtB,aAAa,EAAE,CAAC,IAAI,EAAE,UAAU,KAAK,IAAI,CAAC;IAC1C,OAAO,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,IAAI,CAAC;IAC9B,oBAAoB,EAAE,CAAC,IAAI,EAAE,QAAQ,GAAG,IAAI,KAAK,IAAI,CAAC;IACtD,cAAc,EAAE,CAAC,IAAI,EAAE,gBAAgB,GAAG,IAAI,KAAK,IAAI,CAAC;IACxD,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,MAAM,GAAG,gBAAgB,CAgEvE"}
|
package/package.json
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@haklex/rich-plugin-image-editor",
|
|
3
|
+
"version": "0.24.0",
|
|
4
|
+
"description": "Pre-upload image edit modal plugin (crop + annotate)",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "https://github.com/Innei/haklex.git",
|
|
8
|
+
"directory": "packages/rich-plugin-image-editor"
|
|
9
|
+
},
|
|
10
|
+
"license": "MIT",
|
|
11
|
+
"type": "module",
|
|
12
|
+
"exports": {
|
|
13
|
+
".": {
|
|
14
|
+
"import": "./dist/index.mjs",
|
|
15
|
+
"types": "./dist/index.d.ts"
|
|
16
|
+
},
|
|
17
|
+
"./style.css": "./dist/rich-plugin-image-editor.css"
|
|
18
|
+
},
|
|
19
|
+
"main": "./dist/index.mjs",
|
|
20
|
+
"files": [
|
|
21
|
+
"dist"
|
|
22
|
+
],
|
|
23
|
+
"dependencies": {
|
|
24
|
+
"@markerjs/markerjs3": "^3.10.0",
|
|
25
|
+
"react-image-crop": "^11.0.10"
|
|
26
|
+
},
|
|
27
|
+
"devDependencies": {
|
|
28
|
+
"@types/react": "^19.2.15",
|
|
29
|
+
"@types/react-dom": "^19.2.3",
|
|
30
|
+
"@vanilla-extract/css": "^1.20.1",
|
|
31
|
+
"@vanilla-extract/vite-plugin": "^5.2.2",
|
|
32
|
+
"lucide-react": "^1.17.0",
|
|
33
|
+
"react": "19.2.6",
|
|
34
|
+
"react-dom": "19.2.6",
|
|
35
|
+
"typescript": "^5.9.3",
|
|
36
|
+
"unplugin-dts": "^1.0.1",
|
|
37
|
+
"vite": "^8.0.14"
|
|
38
|
+
},
|
|
39
|
+
"peerDependencies": {
|
|
40
|
+
"lucide-react": "^1.0.0",
|
|
41
|
+
"react": ">=19",
|
|
42
|
+
"react-dom": ">=19",
|
|
43
|
+
"@haklex/rich-editor": "0.24.0",
|
|
44
|
+
"@haklex/rich-editor-ui": "0.24.0",
|
|
45
|
+
"@haklex/rich-style-token": "0.24.0"
|
|
46
|
+
},
|
|
47
|
+
"publishConfig": {
|
|
48
|
+
"access": "public"
|
|
49
|
+
},
|
|
50
|
+
"scripts": {
|
|
51
|
+
"build": "vite build",
|
|
52
|
+
"dev:build": "vite build --watch"
|
|
53
|
+
},
|
|
54
|
+
"types": "./dist/index.d.ts"
|
|
55
|
+
}
|