hazo_images 1.9.0 → 1.11.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/CHANGE_LOG.md +56 -0
- package/dist/ui/index.d.ts +5 -1
- package/dist/ui/index.js +133 -64
- package/package.json +11 -13
package/CHANGE_LOG.md
CHANGED
|
@@ -1,5 +1,61 @@
|
|
|
1
1
|
# hazo_images Change Log
|
|
2
2
|
|
|
3
|
+
## 1.11.0 — 2026-09-19
|
|
4
|
+
|
|
5
|
+
### Changed
|
|
6
|
+
|
|
7
|
+
Migrated this package's i18n bridge onto the canonical `hazo_core/i18n`
|
|
8
|
+
(`createPackageT`/`resolvePackageLocale`), deleting the local
|
|
9
|
+
`src/lib/create_package_t.ts` copy. Same shape (flat, literal-dotted
|
|
10
|
+
`src/locales/en.json` keys, `{{var}}` mustache interpolation) — this is an
|
|
11
|
+
internal wiring change, not a behavior change for consumers, and the four
|
|
12
|
+
UI components (`ImageUploader`, `ImageViewer`, `ImageEditorDialog`,
|
|
13
|
+
`ImageAnnotator`) now each call `createPackageT('hazo_images', en)` from
|
|
14
|
+
`hazo_core/i18n` instead of the deleted local file. `hazo_i18n` peer range
|
|
15
|
+
bumped `^0.3.0` → `^0.5.0` (still optional). `hazo_core` peer/devDep bumped
|
|
16
|
+
`^2.3.0` → `^2.4.0` — `hazo_core/i18n` doesn't exist before 2.4.0, so the
|
|
17
|
+
old range was unsatisfiable for this migration.
|
|
18
|
+
|
|
19
|
+
Also newly exports `t`, `createPackageT`, `resolvePackageLocale`, and the
|
|
20
|
+
`TFunc` type from `hazo_images/ui` — previously the bridge wasn't reachable
|
|
21
|
+
from any public entry point, which blocked writing an autotest against the
|
|
22
|
+
real catalog. Consumers that want this package's own translator (e.g. to
|
|
23
|
+
extend/override strings) can now import it directly instead of re-deriving
|
|
24
|
+
it from `src/locales/en.json`.
|
|
25
|
+
|
|
26
|
+
### Fixed
|
|
27
|
+
|
|
28
|
+
- `ImageEditorDialog`'s color-input hex-value `aria-label` concatenated a raw
|
|
29
|
+
English string (`` `${label} hex value` ``) instead of going through `t()`.
|
|
30
|
+
Extracted into a new catalog key, `editor.hex_value_label` (`"{{label}}
|
|
31
|
+
hex value"`), and wired through `t('editor.hex_value_label', { label })`.
|
|
32
|
+
|
|
33
|
+
### Added
|
|
34
|
+
|
|
35
|
+
- `test-app` `/autotest` page: new `hazo_images_i18n` scenario group
|
|
36
|
+
(catalog loads, all keys resolve, interpolation, coverage threshold).
|
|
37
|
+
|
|
38
|
+
## 1.10.0 — 2026-08-04
|
|
39
|
+
|
|
40
|
+
### Fixed
|
|
41
|
+
|
|
42
|
+
Moved one package from optional `peerDependencies` to `dependencies`. It is
|
|
43
|
+
imported statically at the top level of this package's own `dist/`, but was
|
|
44
|
+
declared as an *optional* peer — and npm never auto-installs an optional peer,
|
|
45
|
+
under any `legacy-peer-deps` setting. The result was a package that could not
|
|
46
|
+
run as shipped: a consumer installed it, imported it, and the build failed with
|
|
47
|
+
module-not-found on a package it had never heard of and had no reason to
|
|
48
|
+
declare. It is an implementation detail of this package, so this package now
|
|
49
|
+
brings it.
|
|
50
|
+
|
|
51
|
+
- react-konva@^18.2.16 || ^19.2.5
|
|
52
|
+
|
|
53
|
+
Dropped the `optional` flag on `react` in `peerDependenciesMeta`. It is
|
|
54
|
+
imported statically and this package cannot render without it, so marking
|
|
55
|
+
it optional understated a hard requirement. It stays a peer — the host controls the
|
|
56
|
+
version and a single instance must be shared.
|
|
57
|
+
|
|
58
|
+
|
|
3
59
|
## 1.9.0 — 2026-07-21
|
|
4
60
|
|
|
5
61
|
### Added
|
package/dist/ui/index.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import * as hazo_core_i18n from 'hazo_core/i18n';
|
|
2
|
+
export { TFunc, createPackageT, resolvePackageLocale } from 'hazo_core/i18n';
|
|
1
3
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
4
|
import * as React from 'react';
|
|
3
5
|
|
|
@@ -406,4 +408,6 @@ declare function cn(...parts: Array<string | false | null | undefined>): string;
|
|
|
406
408
|
*/
|
|
407
409
|
declare function canDecodeImage(file: File): Promise<boolean>;
|
|
408
410
|
|
|
409
|
-
|
|
411
|
+
declare const t: hazo_core_i18n.TFunc;
|
|
412
|
+
|
|
413
|
+
export { type AnnotatorShape, type AnnotatorTool, type ArrowShape, type CircleShape, DEFAULT_IMAGE_EDIT_CONTROLS, ImageAnnotator, type ImageAnnotatorProps, type ImageEditControls, type ImageEditTab, ImageEditorDialog, type ImageEditorDialogProps, type ImageProcessOptions, type ImageProcessResult, type ImageProcessThumbnail, ImageUploader, type ImageUploaderProps, ImageViewer, type ImageViewerProps, type InputOption, type PenShape, type ProcessFn, type ResizeBox, type ResizeHandle, ResizeHandleOverlay, type ResizeHandleOverlayProps, type TabElement, type ValidationError, type ValidationResult, addShape, canDecodeImage, clearShapes, cn, controlValuesToOptions, defaultControlValues, t, undoShape, validate_image_edit_controls };
|
package/dist/ui/index.js
CHANGED
|
@@ -1,3 +1,67 @@
|
|
|
1
|
+
// src/locales/en.json
|
|
2
|
+
var en_default = {
|
|
3
|
+
"uploader.default_label": "Drop an image here, or click to browse",
|
|
4
|
+
"uploader.replace_hint": "Click or drag to replace",
|
|
5
|
+
"uploader.browse": "Browse files",
|
|
6
|
+
"viewer.before": "Before",
|
|
7
|
+
"viewer.after": "After",
|
|
8
|
+
"annotator.toolbar_aria": "Annotation tools",
|
|
9
|
+
"annotator.tool.pen.label": "Pen",
|
|
10
|
+
"annotator.tool.pen.hint": "Freehand pen \u2014 draw by dragging",
|
|
11
|
+
"annotator.tool.arrow.label": "Arrow",
|
|
12
|
+
"annotator.tool.arrow.hint": "Arrow \u2014 drag from tail to head",
|
|
13
|
+
"annotator.tool.circle.label": "Circle",
|
|
14
|
+
"annotator.tool.circle.hint": "Circle \u2014 drag to size an ellipse",
|
|
15
|
+
"annotator.tool.bubble.label": "Speech bubble",
|
|
16
|
+
"annotator.tool.bubble.hint": "Speech bubble \u2014 drag to size, then type",
|
|
17
|
+
"annotator.tool.text.label": "Text",
|
|
18
|
+
"annotator.tool.text.hint": "Text \u2014 click to place, then type",
|
|
19
|
+
"annotator.color": "Color",
|
|
20
|
+
"annotator.color_swatch_aria": "Color {{color}}",
|
|
21
|
+
"annotator.custom_color": "Custom",
|
|
22
|
+
"annotator.stroke_width": "Stroke width",
|
|
23
|
+
"annotator.size": "Size",
|
|
24
|
+
"annotator.width_px": "{{width}}px",
|
|
25
|
+
"annotator.undo": "Undo",
|
|
26
|
+
"annotator.redo": "Redo",
|
|
27
|
+
"annotator.clear": "Clear",
|
|
28
|
+
"annotator.clear_all_tooltip": "Clear all",
|
|
29
|
+
"annotator.save_tooltip": "Flatten image + markup to PNG",
|
|
30
|
+
"annotator.save": "Save",
|
|
31
|
+
"annotator.loading": "Loading image\u2026",
|
|
32
|
+
"annotator.text_placeholder": "Type\u2026",
|
|
33
|
+
"editor.title": "Edit image",
|
|
34
|
+
"editor.trigger": "Edit image",
|
|
35
|
+
"editor.save": "Save",
|
|
36
|
+
"editor.cancel": "Cancel",
|
|
37
|
+
"editor.processing": "Processing\u2026",
|
|
38
|
+
"editor.reset": "Reset",
|
|
39
|
+
"editor.crop": "Crop image",
|
|
40
|
+
"editor.crop_done": "Done",
|
|
41
|
+
"editor.color_clear": "Clear",
|
|
42
|
+
"editor.hex_value_default": "Hex value",
|
|
43
|
+
"editor.hex_value_label": "{{label}} hex value",
|
|
44
|
+
"editor.crop_drag_hint": "Drag the box to move \xB7 drag a handle to resize",
|
|
45
|
+
"editor.preview_alt": "Edited preview",
|
|
46
|
+
"editor.original_alt": "Original image",
|
|
47
|
+
"editor.preview_badge": "Preview",
|
|
48
|
+
"editor.original_badge": "Original",
|
|
49
|
+
"editor.no_image": "No image",
|
|
50
|
+
"editor.shape.rectangle": "Rectangle",
|
|
51
|
+
"editor.shape.square": "Square",
|
|
52
|
+
"editor.shape.circle": "Circle",
|
|
53
|
+
"editor.crop_section_label": "Crop",
|
|
54
|
+
"editor.crop_clear": "Clear",
|
|
55
|
+
"editor.shape_label": "Shape",
|
|
56
|
+
"editor.crop_draw_hint": "Click \u201C{{cropLabel}}\u201D to draw a crop region on the image.",
|
|
57
|
+
"editor.saved": "Saved: {{path}}",
|
|
58
|
+
"editor.crop_original_alt": "Crop original",
|
|
59
|
+
"editor.network_error": "Network error"
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
// src/ui/index.ts
|
|
63
|
+
import { createPackageT as createPackageT5 } from "hazo_core/i18n";
|
|
64
|
+
|
|
1
65
|
// src/ui/image-uploader.tsx
|
|
2
66
|
import * as React from "react";
|
|
3
67
|
import { Button } from "hazo_ui";
|
|
@@ -8,12 +72,14 @@ function cn(...parts) {
|
|
|
8
72
|
}
|
|
9
73
|
|
|
10
74
|
// src/ui/image-uploader.tsx
|
|
75
|
+
import { createPackageT } from "hazo_core/i18n";
|
|
11
76
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
77
|
+
var t = createPackageT("hazo_images", en_default);
|
|
12
78
|
function ImageUploader({
|
|
13
79
|
onSelect,
|
|
14
80
|
accept = "image/*",
|
|
15
81
|
className,
|
|
16
|
-
label = "
|
|
82
|
+
label = t("uploader.default_label"),
|
|
17
83
|
disabled = false
|
|
18
84
|
}) {
|
|
19
85
|
const inputRef = React.useRef(null);
|
|
@@ -96,7 +162,7 @@ function ImageUploader({
|
|
|
96
162
|
),
|
|
97
163
|
selectedFilename ? /* @__PURE__ */ jsxs("div", { className: "text-center", children: [
|
|
98
164
|
/* @__PURE__ */ jsx("p", { className: "text-sm font-medium text-gray-700", children: selectedFilename }),
|
|
99
|
-
/* @__PURE__ */ jsx("p", { className: "text-xs text-gray-500 mt-1", children: "
|
|
165
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs text-gray-500 mt-1", children: t("uploader.replace_hint") })
|
|
100
166
|
] }) : /* @__PURE__ */ jsx("p", { className: "text-sm text-gray-600 text-center", children: label }),
|
|
101
167
|
/* @__PURE__ */ jsx(
|
|
102
168
|
Button,
|
|
@@ -109,7 +175,7 @@ function ImageUploader({
|
|
|
109
175
|
e.stopPropagation();
|
|
110
176
|
handleClick();
|
|
111
177
|
},
|
|
112
|
-
children: "
|
|
178
|
+
children: t("uploader.browse")
|
|
113
179
|
}
|
|
114
180
|
)
|
|
115
181
|
]
|
|
@@ -120,7 +186,9 @@ function ImageUploader({
|
|
|
120
186
|
// src/ui/image-viewer.tsx
|
|
121
187
|
import * as React2 from "react";
|
|
122
188
|
import { Card, CardContent, CardHeader, CardTitle } from "hazo_ui";
|
|
189
|
+
import { createPackageT as createPackageT2 } from "hazo_core/i18n";
|
|
123
190
|
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
191
|
+
var t2 = createPackageT2("hazo_images", en_default);
|
|
124
192
|
function ZoomableImage({
|
|
125
193
|
src,
|
|
126
194
|
alt,
|
|
@@ -150,8 +218,8 @@ function ImageViewer({
|
|
|
150
218
|
before,
|
|
151
219
|
after,
|
|
152
220
|
className,
|
|
153
|
-
beforeLabel = "
|
|
154
|
-
afterLabel = "
|
|
221
|
+
beforeLabel = t2("viewer.before"),
|
|
222
|
+
afterLabel = t2("viewer.after"),
|
|
155
223
|
zoomable = true
|
|
156
224
|
}) {
|
|
157
225
|
if (!before && !after) return null;
|
|
@@ -201,6 +269,7 @@ import {
|
|
|
201
269
|
TabsTrigger,
|
|
202
270
|
TabsContent
|
|
203
271
|
} from "hazo_ui";
|
|
272
|
+
import { createPackageT as createPackageT3 } from "hazo_core/i18n";
|
|
204
273
|
|
|
205
274
|
// src/ui/resize-handle-overlay.tsx
|
|
206
275
|
import * as React3 from "react";
|
|
@@ -727,6 +796,7 @@ function controlValuesToOptions(values, controls) {
|
|
|
727
796
|
|
|
728
797
|
// src/ui/image-editor-dialog.tsx
|
|
729
798
|
import { Fragment, jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
799
|
+
var t3 = createPackageT3("hazo_images", en_default);
|
|
730
800
|
var MIN_BOX = 16;
|
|
731
801
|
var CSS_RENDERED_KEYS = [
|
|
732
802
|
"rotate",
|
|
@@ -826,10 +896,10 @@ function ControlInput({ element, value, onChange }) {
|
|
|
826
896
|
placeholder: "#ff0000",
|
|
827
897
|
onChange: (e) => onChange(name, e.target.value),
|
|
828
898
|
className: "w-28",
|
|
829
|
-
"aria-label": label ?
|
|
899
|
+
"aria-label": label ? t3("editor.hex_value_label", { label }) : t3("editor.hex_value_default")
|
|
830
900
|
}
|
|
831
901
|
),
|
|
832
|
-
value.trim() !== "" && /* @__PURE__ */ jsx4(Button2, { type: "button", variant: "ghost", size: "sm", onClick: () => onChange(name, ""), children: "
|
|
902
|
+
value.trim() !== "" && /* @__PURE__ */ jsx4(Button2, { type: "button", variant: "ghost", size: "sm", onClick: () => onChange(name, ""), children: t3("editor.color_clear") })
|
|
833
903
|
] });
|
|
834
904
|
}
|
|
835
905
|
if (input_type === "textbox") {
|
|
@@ -933,7 +1003,7 @@ function CropBox({ src, crop, shape, onCropChange }) {
|
|
|
933
1003
|
{
|
|
934
1004
|
ref: imgRef,
|
|
935
1005
|
src,
|
|
936
|
-
alt: "
|
|
1006
|
+
alt: t3("editor.crop_original_alt"),
|
|
937
1007
|
className: "block h-auto w-auto max-h-[62vh] max-w-full object-contain",
|
|
938
1008
|
draggable: false,
|
|
939
1009
|
onLoad: measure
|
|
@@ -1035,7 +1105,7 @@ function ImageEditorDialog({
|
|
|
1035
1105
|
onSave?.(res);
|
|
1036
1106
|
}
|
|
1037
1107
|
} catch (err) {
|
|
1038
|
-
const msg = err instanceof Error ? err.message : "
|
|
1108
|
+
const msg = err instanceof Error ? err.message : t3("editor.network_error");
|
|
1039
1109
|
setError(msg);
|
|
1040
1110
|
setResult({ ok: false, error: msg, errorType: "NetworkError" });
|
|
1041
1111
|
} finally {
|
|
@@ -1073,13 +1143,13 @@ function ImageEditorDialog({
|
|
|
1073
1143
|
function handleOpenChange(nextOpen) {
|
|
1074
1144
|
onOpenChange?.(nextOpen);
|
|
1075
1145
|
}
|
|
1076
|
-
const resolvedTitle = labels?.title ?? "
|
|
1077
|
-
const resolvedSaveLabel = labels?.save ?? "
|
|
1078
|
-
const resolvedCancelLabel = labels?.cancel ?? "
|
|
1079
|
-
const resolvedProcessingLabel = labels?.processing ?? "
|
|
1080
|
-
const resolvedResetLabel = labels?.reset ?? "
|
|
1081
|
-
const resolvedCropLabel = labels?.crop ?? "
|
|
1082
|
-
const resolvedCropDoneLabel = labels?.cropDone ?? "
|
|
1146
|
+
const resolvedTitle = labels?.title ?? t3("editor.title");
|
|
1147
|
+
const resolvedSaveLabel = labels?.save ?? t3("editor.save");
|
|
1148
|
+
const resolvedCancelLabel = labels?.cancel ?? t3("editor.cancel");
|
|
1149
|
+
const resolvedProcessingLabel = labels?.processing ?? t3("editor.processing");
|
|
1150
|
+
const resolvedResetLabel = labels?.reset ?? t3("editor.reset");
|
|
1151
|
+
const resolvedCropLabel = labels?.crop ?? t3("editor.crop");
|
|
1152
|
+
const resolvedCropDoneLabel = labels?.cropDone ?? t3("editor.crop_done");
|
|
1083
1153
|
const inCropMode = cropMode && Boolean(beforeUrl);
|
|
1084
1154
|
const previewSrc = result?.ok ? result.preview : void 0;
|
|
1085
1155
|
const stageSrc = previewSrc ?? beforeUrl ?? void 0;
|
|
@@ -1104,13 +1174,13 @@ function ImageEditorDialog({
|
|
|
1104
1174
|
const stage = /* @__PURE__ */ jsxs3("div", { className: "relative flex min-h-[52vh] flex-1 items-center justify-center overflow-hidden rounded-lg bg-gray-900 p-4 lg:min-h-[64vh] lg:max-h-[72vh]", children: [
|
|
1105
1175
|
inCropMode && beforeUrl ? /* @__PURE__ */ jsxs3("div", { className: "flex max-h-full flex-col items-center gap-2", children: [
|
|
1106
1176
|
/* @__PURE__ */ jsx4(CropBox, { src: beforeUrl, crop, shape: cropShape, onCropChange: setCrop }),
|
|
1107
|
-
/* @__PURE__ */ jsx4("span", { className: "text-xs text-gray-400", children: "
|
|
1177
|
+
/* @__PURE__ */ jsx4("span", { className: "text-xs text-gray-400", children: t3("editor.crop_drag_hint") })
|
|
1108
1178
|
] }) : stageSrc ? /* @__PURE__ */ jsxs3("div", { className: "relative inline-block", style: { transform: geometryTransform }, children: [
|
|
1109
1179
|
/* @__PURE__ */ jsx4(
|
|
1110
1180
|
"img",
|
|
1111
1181
|
{
|
|
1112
1182
|
src: stageSrc,
|
|
1113
|
-
alt: stageIsPreview ? "
|
|
1183
|
+
alt: stageIsPreview ? t3("editor.preview_alt") : t3("editor.original_alt"),
|
|
1114
1184
|
className: "block max-h-[68vh] max-w-full object-contain",
|
|
1115
1185
|
style: { filter: cssFilter },
|
|
1116
1186
|
draggable: false
|
|
@@ -1123,14 +1193,14 @@ function ImageEditorDialog({
|
|
|
1123
1193
|
style: { backgroundColor: tintColor, mixBlendMode: "color" }
|
|
1124
1194
|
}
|
|
1125
1195
|
)
|
|
1126
|
-
] }) : /* @__PURE__ */ jsx4("span", { className: "text-sm text-gray-500", children: "
|
|
1127
|
-
!inCropMode && stageSrc && /* @__PURE__ */ jsx4("span", { className: "absolute left-3 top-3 rounded-full bg-black/60 px-2.5 py-1 text-xs font-medium text-gray-200", children: stageIsPreview ? "
|
|
1196
|
+
] }) : /* @__PURE__ */ jsx4("span", { className: "text-sm text-gray-500", children: t3("editor.no_image") }),
|
|
1197
|
+
!inCropMode && stageSrc && /* @__PURE__ */ jsx4("span", { className: "absolute left-3 top-3 rounded-full bg-black/60 px-2.5 py-1 text-xs font-medium text-gray-200", children: stageIsPreview ? t3("editor.preview_badge") : t3("editor.original_badge") }),
|
|
1128
1198
|
isProcessing && /* @__PURE__ */ jsx4("span", { className: "absolute right-3 top-3 rounded-full bg-black/60 px-2.5 py-1 text-xs font-medium text-gray-200", children: resolvedProcessingLabel })
|
|
1129
1199
|
] });
|
|
1130
1200
|
const shapeOptions = [
|
|
1131
|
-
{ value: "rect", label: "
|
|
1132
|
-
{ value: "square", label: "
|
|
1133
|
-
{ value: "circle", label: "
|
|
1201
|
+
{ value: "rect", label: t3("editor.shape.rectangle") },
|
|
1202
|
+
{ value: "square", label: t3("editor.shape.square") },
|
|
1203
|
+
{ value: "circle", label: t3("editor.shape.circle") }
|
|
1134
1204
|
];
|
|
1135
1205
|
const rail = /* @__PURE__ */ jsxs3("div", { className: "flex w-full flex-col gap-4 px-1 lg:w-80 lg:shrink-0 lg:max-h-[72vh] lg:overflow-y-auto", children: [
|
|
1136
1206
|
error && /* @__PURE__ */ jsx4("div", { className: "rounded-md bg-red-50 border border-red-200 px-4 py-2 text-sm text-red-700", children: error }),
|
|
@@ -1150,7 +1220,7 @@ function ImageEditorDialog({
|
|
|
1150
1220
|
] }, el.name)),
|
|
1151
1221
|
tab.tab_name === "Geometry" && beforeUrl && /* @__PURE__ */ jsxs3("div", { className: "flex flex-col gap-3 mt-1 border-t border-gray-100 pt-3", children: [
|
|
1152
1222
|
/* @__PURE__ */ jsxs3("div", { className: "flex items-center justify-between", children: [
|
|
1153
|
-
/* @__PURE__ */ jsx4("span", { className: "text-sm font-medium text-gray-700", children: "
|
|
1223
|
+
/* @__PURE__ */ jsx4("span", { className: "text-sm font-medium text-gray-700", children: t3("editor.crop_section_label") }),
|
|
1154
1224
|
/* @__PURE__ */ jsxs3("div", { className: "flex items-center gap-1", children: [
|
|
1155
1225
|
crop && /* @__PURE__ */ jsx4(
|
|
1156
1226
|
Button2,
|
|
@@ -1162,7 +1232,7 @@ function ImageEditorDialog({
|
|
|
1162
1232
|
setCrop(null);
|
|
1163
1233
|
setCropMode(false);
|
|
1164
1234
|
},
|
|
1165
|
-
children: "
|
|
1235
|
+
children: t3("editor.crop_clear")
|
|
1166
1236
|
}
|
|
1167
1237
|
),
|
|
1168
1238
|
/* @__PURE__ */ jsx4(
|
|
@@ -1178,7 +1248,7 @@ function ImageEditorDialog({
|
|
|
1178
1248
|
] })
|
|
1179
1249
|
] }),
|
|
1180
1250
|
cropMode && /* @__PURE__ */ jsxs3("div", { className: "flex flex-col gap-1.5", children: [
|
|
1181
|
-
/* @__PURE__ */ jsx4(Label, { className: "text-sm text-gray-700", children: "
|
|
1251
|
+
/* @__PURE__ */ jsx4(Label, { className: "text-sm text-gray-700", children: t3("editor.shape_label") }),
|
|
1182
1252
|
/* @__PURE__ */ jsx4("div", { className: "flex gap-1", children: shapeOptions.map((opt) => /* @__PURE__ */ jsx4(
|
|
1183
1253
|
Button2,
|
|
1184
1254
|
{
|
|
@@ -1203,18 +1273,11 @@ function ImageEditorDialog({
|
|
|
1203
1273
|
"\xD7",
|
|
1204
1274
|
crop.height,
|
|
1205
1275
|
"px"
|
|
1206
|
-
] }) : /* @__PURE__ */
|
|
1207
|
-
"Click \u201C",
|
|
1208
|
-
resolvedCropLabel,
|
|
1209
|
-
"\u201D to draw a crop region on the image."
|
|
1210
|
-
] })
|
|
1276
|
+
] }) : /* @__PURE__ */ jsx4("p", { className: "text-xs text-gray-500", children: t3("editor.crop_draw_hint", { cropLabel: resolvedCropLabel }) })
|
|
1211
1277
|
] })
|
|
1212
1278
|
] }) }, tab.tab_name))
|
|
1213
1279
|
] }),
|
|
1214
|
-
result?.ok && result.saved && /* @__PURE__ */
|
|
1215
|
-
"Saved: ",
|
|
1216
|
-
result.saved.path ?? result.saved.url
|
|
1217
|
-
] }),
|
|
1280
|
+
result?.ok && result.saved && /* @__PURE__ */ jsx4("p", { className: "text-xs text-green-700 bg-green-50 rounded p-2 break-all", children: t3("editor.saved", { path: result.saved.path ?? result.saved.url ?? "" }) }),
|
|
1218
1281
|
result?.saveError && /* @__PURE__ */ jsx4("p", { className: "text-xs text-red-600", children: result.saveError })
|
|
1219
1282
|
] });
|
|
1220
1283
|
const dialogBody = /* @__PURE__ */ jsxs3(Fragment, { children: [
|
|
@@ -1267,7 +1330,7 @@ function ImageEditorDialog({
|
|
|
1267
1330
|
return /* @__PURE__ */ jsx4(Dialog, { open, onOpenChange: handleOpenChange, children: /* @__PURE__ */ jsx4(DialogContent, { className: cn("max-w-5xl", className), children: dialogBody }) });
|
|
1268
1331
|
}
|
|
1269
1332
|
return /* @__PURE__ */ jsxs3(Dialog, { onOpenChange: handleOpenChange, children: [
|
|
1270
|
-
/* @__PURE__ */ jsx4(DialogTrigger, { asChild: true, children: trigger ?? /* @__PURE__ */ jsx4(Button2, { type: "button", children: "
|
|
1333
|
+
/* @__PURE__ */ jsx4(DialogTrigger, { asChild: true, children: trigger ?? /* @__PURE__ */ jsx4(Button2, { type: "button", children: t3("editor.trigger") }) }),
|
|
1271
1334
|
/* @__PURE__ */ jsx4(DialogContent, { className: cn("max-w-5xl", className), children: dialogBody })
|
|
1272
1335
|
] });
|
|
1273
1336
|
}
|
|
@@ -1336,7 +1399,9 @@ function redo(shapes, redoStack) {
|
|
|
1336
1399
|
}
|
|
1337
1400
|
|
|
1338
1401
|
// src/ui/image-annotator.tsx
|
|
1402
|
+
import { createPackageT as createPackageT4 } from "hazo_core/i18n";
|
|
1339
1403
|
import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
1404
|
+
var t4 = createPackageT4("hazo_images", en_default);
|
|
1340
1405
|
var TOOL_IDS = ["pen", "arrow", "circle", "bubble", "text"];
|
|
1341
1406
|
var DEFAULT_STROKE_COLOR = "#ef4444";
|
|
1342
1407
|
var DEFAULT_STROKE_WIDTH = 4;
|
|
@@ -1355,11 +1420,11 @@ var PRESET_COLORS = [
|
|
|
1355
1420
|
var BUBBLE_PADDING = 10;
|
|
1356
1421
|
var fontSizeFor = (strokeWidth) => Math.max(12, Math.round(strokeWidth * 4));
|
|
1357
1422
|
var TOOL_META = {
|
|
1358
|
-
pen: { Icon: Pencil, label: "
|
|
1359
|
-
arrow: { Icon: MoveUpRight, label: "
|
|
1360
|
-
circle: { Icon: CircleIcon, label: "
|
|
1361
|
-
bubble: { Icon: MessageSquare, label: "
|
|
1362
|
-
text: { Icon: TypeIcon, label: "
|
|
1423
|
+
pen: { Icon: Pencil, label: t4("annotator.tool.pen.label"), hint: t4("annotator.tool.pen.hint") },
|
|
1424
|
+
arrow: { Icon: MoveUpRight, label: t4("annotator.tool.arrow.label"), hint: t4("annotator.tool.arrow.hint") },
|
|
1425
|
+
circle: { Icon: CircleIcon, label: t4("annotator.tool.circle.label"), hint: t4("annotator.tool.circle.hint") },
|
|
1426
|
+
bubble: { Icon: MessageSquare, label: t4("annotator.tool.bubble.label"), hint: t4("annotator.tool.bubble.hint") },
|
|
1427
|
+
text: { Icon: TypeIcon, label: t4("annotator.tool.text.label"), hint: t4("annotator.tool.text.hint") }
|
|
1363
1428
|
};
|
|
1364
1429
|
function bubbleTailPoints(width, height) {
|
|
1365
1430
|
const baseX = Math.min(28, width * 0.5);
|
|
@@ -1672,12 +1737,12 @@ function ImageAnnotator({
|
|
|
1672
1737
|
});
|
|
1673
1738
|
}
|
|
1674
1739
|
const resolvedLabels = {
|
|
1675
|
-
save: labels?.save ?? "
|
|
1676
|
-
loading: labels?.loading ?? "
|
|
1740
|
+
save: labels?.save ?? t4("annotator.save"),
|
|
1741
|
+
loading: labels?.loading ?? t4("annotator.loading")
|
|
1677
1742
|
};
|
|
1678
1743
|
const iconBtn = "h-10 w-10";
|
|
1679
1744
|
return /* @__PURE__ */ jsx5(TooltipProvider, { delayDuration: 200, children: /* @__PURE__ */ jsxs4("div", { className: cn("flex flex-col gap-3", className), children: [
|
|
1680
|
-
/* @__PURE__ */ jsxs4("div", { className: "flex flex-wrap items-center gap-1.5", role: "toolbar", "aria-label": "
|
|
1745
|
+
/* @__PURE__ */ jsxs4("div", { className: "flex flex-wrap items-center gap-1.5", role: "toolbar", "aria-label": t4("annotator.toolbar_aria"), children: [
|
|
1681
1746
|
TOOL_IDS.map((id) => {
|
|
1682
1747
|
const { Icon, label, hint } = TOOL_META[id];
|
|
1683
1748
|
return /* @__PURE__ */ jsxs4(Tooltip, { children: [
|
|
@@ -1700,13 +1765,13 @@ function ImageAnnotator({
|
|
|
1700
1765
|
/* @__PURE__ */ jsx5("span", { className: "mx-1 h-6 w-px self-center bg-gray-300", "aria-hidden": "true" }),
|
|
1701
1766
|
/* @__PURE__ */ jsxs4(Tooltip, { children: [
|
|
1702
1767
|
/* @__PURE__ */ jsx5(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxs4(Popover, { children: [
|
|
1703
|
-
/* @__PURE__ */ jsx5(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsx5(Button3, { type: "button", size: "icon", variant: "outline", className: iconBtn, "aria-label": "
|
|
1768
|
+
/* @__PURE__ */ jsx5(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsx5(Button3, { type: "button", size: "icon", variant: "outline", className: iconBtn, "aria-label": t4("annotator.color"), children: /* @__PURE__ */ jsx5(Palette, { size: 18, style: { color } }) }) }),
|
|
1704
1769
|
/* @__PURE__ */ jsx5(PopoverContent, { className: "w-auto p-3", children: /* @__PURE__ */ jsxs4("div", { className: "flex flex-col gap-3", children: [
|
|
1705
1770
|
/* @__PURE__ */ jsx5("div", { className: "grid grid-cols-4 gap-2", children: PRESET_COLORS.map((c) => /* @__PURE__ */ jsx5(
|
|
1706
1771
|
"button",
|
|
1707
1772
|
{
|
|
1708
1773
|
type: "button",
|
|
1709
|
-
"aria-label":
|
|
1774
|
+
"aria-label": t4("annotator.color_swatch_aria", { color: c }),
|
|
1710
1775
|
onClick: () => setColor(c),
|
|
1711
1776
|
className: cn(
|
|
1712
1777
|
"h-7 w-7 rounded-full border border-gray-300",
|
|
@@ -1717,7 +1782,7 @@ function ImageAnnotator({
|
|
|
1717
1782
|
c
|
|
1718
1783
|
)) }),
|
|
1719
1784
|
/* @__PURE__ */ jsxs4("label", { className: "flex items-center gap-2 text-sm", children: [
|
|
1720
|
-
/* @__PURE__ */ jsx5("span", { className: "text-gray-600", children: "
|
|
1785
|
+
/* @__PURE__ */ jsx5("span", { className: "text-gray-600", children: t4("annotator.custom_color") }),
|
|
1721
1786
|
/* @__PURE__ */ jsx5(
|
|
1722
1787
|
"input",
|
|
1723
1788
|
{
|
|
@@ -1730,18 +1795,15 @@ function ImageAnnotator({
|
|
|
1730
1795
|
] })
|
|
1731
1796
|
] }) })
|
|
1732
1797
|
] }) }),
|
|
1733
|
-
/* @__PURE__ */ jsx5(TooltipContent, { children: "
|
|
1798
|
+
/* @__PURE__ */ jsx5(TooltipContent, { children: t4("annotator.color") })
|
|
1734
1799
|
] }),
|
|
1735
1800
|
/* @__PURE__ */ jsxs4(Tooltip, { children: [
|
|
1736
1801
|
/* @__PURE__ */ jsx5(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxs4(Popover, { children: [
|
|
1737
|
-
/* @__PURE__ */ jsx5(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsx5(Button3, { type: "button", size: "icon", variant: "outline", className: iconBtn, "aria-label": "
|
|
1802
|
+
/* @__PURE__ */ jsx5(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsx5(Button3, { type: "button", size: "icon", variant: "outline", className: iconBtn, "aria-label": t4("annotator.stroke_width"), children: /* @__PURE__ */ jsx5(SlidersHorizontal, { size: 18 }) }) }),
|
|
1738
1803
|
/* @__PURE__ */ jsx5(PopoverContent, { className: "w-56 p-4", children: /* @__PURE__ */ jsxs4("div", { className: "flex flex-col gap-3", children: [
|
|
1739
1804
|
/* @__PURE__ */ jsxs4("div", { className: "flex items-center justify-between text-sm text-gray-600", children: [
|
|
1740
|
-
/* @__PURE__ */ jsx5("span", { children: "
|
|
1741
|
-
/* @__PURE__ */
|
|
1742
|
-
width,
|
|
1743
|
-
"px"
|
|
1744
|
-
] })
|
|
1805
|
+
/* @__PURE__ */ jsx5("span", { children: t4("annotator.size") }),
|
|
1806
|
+
/* @__PURE__ */ jsx5("span", { className: "font-medium text-gray-900", children: t4("annotator.width_px", { width: String(width) }) })
|
|
1745
1807
|
] }),
|
|
1746
1808
|
/* @__PURE__ */ jsx5(
|
|
1747
1809
|
Slider2,
|
|
@@ -1751,7 +1813,7 @@ function ImageAnnotator({
|
|
|
1751
1813
|
step: 1,
|
|
1752
1814
|
value: [width],
|
|
1753
1815
|
onValueChange: ([v]) => setWidth(v),
|
|
1754
|
-
"aria-label": "
|
|
1816
|
+
"aria-label": t4("annotator.stroke_width")
|
|
1755
1817
|
}
|
|
1756
1818
|
),
|
|
1757
1819
|
/* @__PURE__ */ jsx5("div", { className: "flex items-center justify-center", children: /* @__PURE__ */ jsx5(
|
|
@@ -1763,7 +1825,7 @@ function ImageAnnotator({
|
|
|
1763
1825
|
) })
|
|
1764
1826
|
] }) })
|
|
1765
1827
|
] }) }),
|
|
1766
|
-
/* @__PURE__ */ jsx5(TooltipContent, { children: "
|
|
1828
|
+
/* @__PURE__ */ jsx5(TooltipContent, { children: t4("annotator.stroke_width") })
|
|
1767
1829
|
] }),
|
|
1768
1830
|
/* @__PURE__ */ jsx5("span", { className: "mx-1 h-6 w-px self-center bg-gray-300", "aria-hidden": "true" }),
|
|
1769
1831
|
/* @__PURE__ */ jsxs4(Tooltip, { children: [
|
|
@@ -1774,13 +1836,13 @@ function ImageAnnotator({
|
|
|
1774
1836
|
size: "icon",
|
|
1775
1837
|
variant: "outline",
|
|
1776
1838
|
className: iconBtn,
|
|
1777
|
-
"aria-label": "
|
|
1839
|
+
"aria-label": t4("annotator.undo"),
|
|
1778
1840
|
disabled: shapes.length === 0 && !editing,
|
|
1779
1841
|
onClick: handleUndo,
|
|
1780
1842
|
children: /* @__PURE__ */ jsx5(Undo2, { size: 18 })
|
|
1781
1843
|
}
|
|
1782
1844
|
) }),
|
|
1783
|
-
/* @__PURE__ */ jsx5(TooltipContent, { children: "
|
|
1845
|
+
/* @__PURE__ */ jsx5(TooltipContent, { children: t4("annotator.undo") })
|
|
1784
1846
|
] }),
|
|
1785
1847
|
/* @__PURE__ */ jsxs4(Tooltip, { children: [
|
|
1786
1848
|
/* @__PURE__ */ jsx5(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx5(
|
|
@@ -1790,13 +1852,13 @@ function ImageAnnotator({
|
|
|
1790
1852
|
size: "icon",
|
|
1791
1853
|
variant: "outline",
|
|
1792
1854
|
className: iconBtn,
|
|
1793
|
-
"aria-label": "
|
|
1855
|
+
"aria-label": t4("annotator.redo"),
|
|
1794
1856
|
disabled: redoStack.length === 0,
|
|
1795
1857
|
onClick: handleRedo,
|
|
1796
1858
|
children: /* @__PURE__ */ jsx5(Redo2, { size: 18 })
|
|
1797
1859
|
}
|
|
1798
1860
|
) }),
|
|
1799
|
-
/* @__PURE__ */ jsx5(TooltipContent, { children: "
|
|
1861
|
+
/* @__PURE__ */ jsx5(TooltipContent, { children: t4("annotator.redo") })
|
|
1800
1862
|
] }),
|
|
1801
1863
|
/* @__PURE__ */ jsxs4(Tooltip, { children: [
|
|
1802
1864
|
/* @__PURE__ */ jsx5(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx5(
|
|
@@ -1806,13 +1868,13 @@ function ImageAnnotator({
|
|
|
1806
1868
|
size: "icon",
|
|
1807
1869
|
variant: "outline",
|
|
1808
1870
|
className: iconBtn,
|
|
1809
|
-
"aria-label": "
|
|
1871
|
+
"aria-label": t4("annotator.clear"),
|
|
1810
1872
|
disabled: shapes.length === 0 && !editing,
|
|
1811
1873
|
onClick: handleClear,
|
|
1812
1874
|
children: /* @__PURE__ */ jsx5(Trash2, { size: 18 })
|
|
1813
1875
|
}
|
|
1814
1876
|
) }),
|
|
1815
|
-
/* @__PURE__ */ jsx5(TooltipContent, { children: "
|
|
1877
|
+
/* @__PURE__ */ jsx5(TooltipContent, { children: t4("annotator.clear_all_tooltip") })
|
|
1816
1878
|
] }),
|
|
1817
1879
|
onSave && /* @__PURE__ */ jsxs4(Tooltip, { children: [
|
|
1818
1880
|
/* @__PURE__ */ jsx5(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxs4(
|
|
@@ -1828,7 +1890,7 @@ function ImageAnnotator({
|
|
|
1828
1890
|
]
|
|
1829
1891
|
}
|
|
1830
1892
|
) }),
|
|
1831
|
-
/* @__PURE__ */ jsx5(TooltipContent, { children: "
|
|
1893
|
+
/* @__PURE__ */ jsx5(TooltipContent, { children: t4("annotator.save_tooltip") })
|
|
1832
1894
|
] })
|
|
1833
1895
|
] }),
|
|
1834
1896
|
/* @__PURE__ */ jsx5(
|
|
@@ -1877,7 +1939,7 @@ function ImageAnnotator({
|
|
|
1877
1939
|
cancelEditing();
|
|
1878
1940
|
}
|
|
1879
1941
|
},
|
|
1880
|
-
placeholder: "
|
|
1942
|
+
placeholder: t4("annotator.text_placeholder"),
|
|
1881
1943
|
className: "absolute resize-none rounded border border-dashed border-gray-400 bg-white/90 p-1 leading-tight shadow-sm outline-none",
|
|
1882
1944
|
style: {
|
|
1883
1945
|
left: editing.x + (editing.kind === "bubble" ? BUBBLE_PADDING : 0),
|
|
@@ -1911,6 +1973,10 @@ function canDecodeImage(file) {
|
|
|
1911
1973
|
img.src = url;
|
|
1912
1974
|
});
|
|
1913
1975
|
}
|
|
1976
|
+
|
|
1977
|
+
// src/ui/index.ts
|
|
1978
|
+
import { createPackageT as createPackageT6, resolvePackageLocale } from "hazo_core/i18n";
|
|
1979
|
+
var t5 = createPackageT5("hazo_images", en_default);
|
|
1914
1980
|
export {
|
|
1915
1981
|
DEFAULT_IMAGE_EDIT_CONTROLS,
|
|
1916
1982
|
ImageAnnotator,
|
|
@@ -1923,7 +1989,10 @@ export {
|
|
|
1923
1989
|
clearShapes,
|
|
1924
1990
|
cn,
|
|
1925
1991
|
controlValuesToOptions,
|
|
1992
|
+
createPackageT6 as createPackageT,
|
|
1926
1993
|
defaultControlValues,
|
|
1994
|
+
resolvePackageLocale,
|
|
1995
|
+
t5 as t,
|
|
1927
1996
|
undoShape,
|
|
1928
1997
|
validate_image_edit_controls
|
|
1929
1998
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hazo_images",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.11.0",
|
|
4
4
|
"description": "Image processing pipeline for the hazo ecosystem — Sharp wrapper, thumbnail generation, EXIF handling, and hazo_files integration helper",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -48,24 +48,28 @@
|
|
|
48
48
|
},
|
|
49
49
|
"dependencies": {
|
|
50
50
|
"ini": "^4.1.0",
|
|
51
|
+
"react-konva": "^18.2.16 || ^19.2.5",
|
|
51
52
|
"zod": "^4.1.12"
|
|
52
53
|
},
|
|
53
54
|
"peerDependencies": {
|
|
54
|
-
"hazo_core": "^
|
|
55
|
-
"hazo_files": "^3.
|
|
56
|
-
"
|
|
57
|
-
"
|
|
55
|
+
"hazo_core": "^2.4.0",
|
|
56
|
+
"hazo_files": "^3.4.0",
|
|
57
|
+
"hazo_i18n": "^0.5.0",
|
|
58
|
+
"hazo_theme": "^1.4.0",
|
|
59
|
+
"hazo_ui": "^6.12.0",
|
|
58
60
|
"konva": "^10.3.0",
|
|
59
61
|
"lucide-react": "^0.553.0",
|
|
60
62
|
"react": "^18.0.0 || ^19.0.0",
|
|
61
63
|
"react-dom": "^18.0.0 || ^19.0.0",
|
|
62
|
-
"react-konva": "^18.2.16 || ^19.2.5",
|
|
63
64
|
"sharp": "^0.33.0"
|
|
64
65
|
},
|
|
65
66
|
"peerDependenciesMeta": {
|
|
66
67
|
"hazo_files": {
|
|
67
68
|
"optional": true
|
|
68
69
|
},
|
|
70
|
+
"hazo_i18n": {
|
|
71
|
+
"optional": true
|
|
72
|
+
},
|
|
69
73
|
"lucide-react": {
|
|
70
74
|
"optional": true
|
|
71
75
|
},
|
|
@@ -75,15 +79,9 @@
|
|
|
75
79
|
"konva": {
|
|
76
80
|
"optional": true
|
|
77
81
|
},
|
|
78
|
-
"react": {
|
|
79
|
-
"optional": true
|
|
80
|
-
},
|
|
81
82
|
"react-dom": {
|
|
82
83
|
"optional": true
|
|
83
84
|
},
|
|
84
|
-
"react-konva": {
|
|
85
|
-
"optional": true
|
|
86
|
-
},
|
|
87
85
|
"sharp": {
|
|
88
86
|
"optional": true
|
|
89
87
|
},
|
|
@@ -95,7 +93,7 @@
|
|
|
95
93
|
"@types/jest": "^30.0.0",
|
|
96
94
|
"@types/node": "^22.10.0",
|
|
97
95
|
"@types/react": "^18.3.3",
|
|
98
|
-
"hazo_core": "^
|
|
96
|
+
"hazo_core": "^2.4.0",
|
|
99
97
|
"jest": "^30.2.0",
|
|
100
98
|
"jest-environment-node": "^30.2.0",
|
|
101
99
|
"konva": "^10.3.0",
|