@merchi/product-editor 0.1.9
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/.turbo/turbo-build.log +1 -0
- package/README.md +174 -0
- package/dist/components/AlignMenu.d.ts +3 -0
- package/dist/components/AlignMenu.js +61 -0
- package/dist/components/FloatingToolbar.d.ts +4 -0
- package/dist/components/FloatingToolbar.js +97 -0
- package/dist/components/FormatButtons.d.ts +2 -0
- package/dist/components/FormatButtons.js +22 -0
- package/dist/components/ImageNavButton.d.ts +7 -0
- package/dist/components/ImageNavButton.js +14 -0
- package/dist/components/ImageZoomModal.d.ts +12 -0
- package/dist/components/ImageZoomModal.js +129 -0
- package/dist/components/LayerPanel.d.ts +4 -0
- package/dist/components/LayerPanel.js +223 -0
- package/dist/components/LoadingOverlay.d.ts +9 -0
- package/dist/components/LoadingOverlay.js +17 -0
- package/dist/components/ProductEditor.d.ts +4 -0
- package/dist/components/ProductEditor.js +67 -0
- package/dist/components/ProductImageGallery.d.ts +6 -0
- package/dist/components/ProductImageGallery.js +148 -0
- package/dist/components/ProductPreviews.d.ts +7 -0
- package/dist/components/ProductPreviews.js +281 -0
- package/dist/components/TextToolbar.d.ts +4 -0
- package/dist/components/TextToolbar.js +117 -0
- package/dist/components/Toolbar.d.ts +3 -0
- package/dist/components/Toolbar.js +47 -0
- package/dist/config/colorConfig.d.ts +1 -0
- package/dist/config/colorConfig.js +10 -0
- package/dist/config/fontConfig.d.ts +5 -0
- package/dist/config/fontConfig.js +21 -0
- package/dist/context/ProductEditorContext.d.ts +60 -0
- package/dist/context/ProductEditorContext.js +743 -0
- package/dist/hooks/useSwipeNavigate.d.ts +11 -0
- package/dist/hooks/useSwipeNavigate.js +35 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +30 -0
- package/dist/styles/BottomPreviewDisplay.css +65 -0
- package/dist/styles/FloatingToolbar.css +41 -0
- package/dist/styles/ImageGallery.css +147 -0
- package/dist/styles/ImageNavButton.css +35 -0
- package/dist/styles/ImageZoomModal.css +101 -0
- package/dist/styles/LayerPanel.css +179 -0
- package/dist/styles/LoadingOverlay.css +48 -0
- package/dist/styles/ProductEditor.css +192 -0
- package/dist/styles/TextToolbar.css +327 -0
- package/dist/styles/Toolbar.css +125 -0
- package/dist/styles/index.css +5 -0
- package/dist/styles/textToolbarSelectStyles.d.ts +3 -0
- package/dist/styles/textToolbarSelectStyles.js +30 -0
- package/dist/types.d.ts +228 -0
- package/dist/types.js +17 -0
- package/dist/utils/canvasAddText.d.ts +11 -0
- package/dist/utils/canvasAddText.js +66 -0
- package/dist/utils/canvasUtils.d.ts +37 -0
- package/dist/utils/canvasUtils.js +342 -0
- package/dist/utils/deviceUtils.d.ts +5 -0
- package/dist/utils/deviceUtils.js +33 -0
- package/dist/utils/draftTemplateUtils.d.ts +23 -0
- package/dist/utils/draftTemplateUtils.js +66 -0
- package/dist/utils/grid.d.ts +32 -0
- package/dist/utils/grid.js +151 -0
- package/dist/utils/imageUtils.d.ts +12 -0
- package/dist/utils/imageUtils.js +178 -0
- package/dist/utils/job.d.ts +40 -0
- package/dist/utils/job.js +115 -0
- package/dist/utils/keyboard.d.ts +1 -0
- package/dist/utils/keyboard.js +26 -0
- package/dist/utils/previewUtils.d.ts +9 -0
- package/dist/utils/previewUtils.js +39 -0
- package/dist/utils/psdConverter.d.ts +40 -0
- package/dist/utils/psdConverter.js +516 -0
- package/dist/utils/psdRenderUtils.d.ts +12 -0
- package/dist/utils/psdRenderUtils.js +465 -0
- package/dist/utils/renderUtils.d.ts +2 -0
- package/dist/utils/renderUtils.js +85 -0
- package/package.json +66 -0
- package/src/components/AlignMenu.tsx +49 -0
- package/src/components/FloatingToolbar.tsx +83 -0
- package/src/components/FormatButtons.tsx +31 -0
- package/src/components/ImageNavButton.tsx +23 -0
- package/src/components/ImageZoomModal.tsx +163 -0
- package/src/components/LayerPanel.tsx +256 -0
- package/src/components/LoadingOverlay.tsx +27 -0
- package/src/components/ProductEditor.tsx +104 -0
- package/src/components/ProductImageGallery.tsx +181 -0
- package/src/components/ProductPreviews.tsx +243 -0
- package/src/components/TextToolbar.tsx +164 -0
- package/src/components/Toolbar.tsx +86 -0
- package/src/config/colorConfig.ts +7 -0
- package/src/config/fontConfig.ts +23 -0
- package/src/context/ProductEditorContext.tsx +829 -0
- package/src/hooks/useSwipeNavigate.ts +40 -0
- package/src/index.ts +6 -0
- package/src/styles/BottomPreviewDisplay.css +65 -0
- package/src/styles/FloatingToolbar.css +41 -0
- package/src/styles/ImageGallery.css +147 -0
- package/src/styles/ImageNavButton.css +35 -0
- package/src/styles/ImageZoomModal.css +101 -0
- package/src/styles/LayerPanel.css +179 -0
- package/src/styles/LoadingOverlay.css +48 -0
- package/src/styles/ProductEditor.css +192 -0
- package/src/styles/TextToolbar.css +327 -0
- package/src/styles/Toolbar.css +125 -0
- package/src/styles/index.css +5 -0
- package/src/styles/textToolbarSelectStyles.ts +60 -0
- package/src/types/ag-psd/index.d.ts +1 -0
- package/src/types/fabric.d.ts +14 -0
- package/src/types/lodash/index.d.ts +1 -0
- package/src/types/psd.d.ts +53 -0
- package/src/types.ts +247 -0
- package/src/utils/canvasAddText.ts +72 -0
- package/src/utils/canvasUtils.ts +406 -0
- package/src/utils/deviceUtils.ts +31 -0
- package/src/utils/draftTemplateUtils.ts +70 -0
- package/src/utils/grid.ts +154 -0
- package/src/utils/imageUtils.ts +218 -0
- package/src/utils/job.ts +128 -0
- package/src/utils/keyboard.ts +28 -0
- package/src/utils/previewUtils.ts +43 -0
- package/src/utils/psdConverter.ts +595 -0
- package/src/utils/psdRenderUtils.ts +453 -0
- package/src/utils/renderUtils.ts +44 -0
- package/tsconfig.json +17 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
$ tsc && copyfiles -u 2 src/styles/*.css dist/styles/
|
package/README.md
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
# Merchi Product Editor
|
|
2
|
+
|
|
3
|
+
A React-based product editor component that allows users to edit product templates using Fabric.js. This library enables customization of product designs through variations, template switching, and canvas manipulation.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Multiple Template Support**: Select and switch between different draft templates
|
|
8
|
+
- **Variation Management**: Update canvas objects based on product variations
|
|
9
|
+
- **Responsive Design**: Works on both desktop and mobile devices
|
|
10
|
+
- **Canvas Customization**: Grid toggle, image upload
|
|
11
|
+
- **Context-based Architecture**: Uses React Context API for state management
|
|
12
|
+
- **Type-safe**: Written in TypeScript with proper type definitions
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npm install @merchi/product-editor
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Usage
|
|
21
|
+
|
|
22
|
+
### Basic Example
|
|
23
|
+
|
|
24
|
+
```jsx
|
|
25
|
+
import { ProductEditor } from '@merchi/product-editor';
|
|
26
|
+
|
|
27
|
+
function App() {
|
|
28
|
+
const product = {
|
|
29
|
+
id: 1,
|
|
30
|
+
name: "Sample Product",
|
|
31
|
+
draftTemplates: [
|
|
32
|
+
{
|
|
33
|
+
id: 1,
|
|
34
|
+
name: "Template 1",
|
|
35
|
+
file: {
|
|
36
|
+
viewUrl: "https://example.com/template1.jpg"
|
|
37
|
+
},
|
|
38
|
+
width: 800,
|
|
39
|
+
height: 600
|
|
40
|
+
}
|
|
41
|
+
]
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const variations = [
|
|
45
|
+
{
|
|
46
|
+
variationField: {
|
|
47
|
+
id: 1,
|
|
48
|
+
fieldType: "TEXT_INPUT"
|
|
49
|
+
},
|
|
50
|
+
value: "Sample Text"
|
|
51
|
+
}
|
|
52
|
+
];
|
|
53
|
+
|
|
54
|
+
const handleSave = () => {
|
|
55
|
+
// Handle save action
|
|
56
|
+
console.log('Saved product design');
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const handleCancel = () => {
|
|
60
|
+
// Handle cancel action
|
|
61
|
+
console.log('Editing cancelled');
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
return (
|
|
65
|
+
<ProductEditor
|
|
66
|
+
product={product}
|
|
67
|
+
job={{id: 1}}
|
|
68
|
+
width={800}
|
|
69
|
+
height={600}
|
|
70
|
+
onSave={handleSave}
|
|
71
|
+
onCancel={handleCancel}
|
|
72
|
+
variations={variations}
|
|
73
|
+
groupVariations={[]}
|
|
74
|
+
/>
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Using the Context API
|
|
80
|
+
|
|
81
|
+
You can access the editor's context in child components:
|
|
82
|
+
|
|
83
|
+
```jsx
|
|
84
|
+
import { useProductEditor } from '@merchi/product-editor';
|
|
85
|
+
|
|
86
|
+
function CustomButton() {
|
|
87
|
+
const { handleSave, updateCanvasFromVariations } = useProductEditor();
|
|
88
|
+
|
|
89
|
+
const handleUpdate = () => {
|
|
90
|
+
const newVariations = [/* updated variations */];
|
|
91
|
+
updateCanvasFromVariations(newVariations);
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
return (
|
|
95
|
+
<button onClick={handleUpdate}>Update Design</button>
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## Props
|
|
101
|
+
|
|
102
|
+
| Prop | Type | Required | Default | Description |
|
|
103
|
+
|------|------|----------|---------|-------------|
|
|
104
|
+
| product | Product | Yes | - | Merchi Product entity containing draft templates |
|
|
105
|
+
| job | Job | Yes | - | Job information |
|
|
106
|
+
| width | number | No | 800 | Width of the editor canvas |
|
|
107
|
+
| height | number | No | 600 | Height of the editor canvas |
|
|
108
|
+
| onSave | () => void | Yes | - | Callback function when saving the edited image |
|
|
109
|
+
| onCancel | () => void | Yes | - | Callback function when canceling the edit |
|
|
110
|
+
| variations | Variation[] | Yes | [] | Array of variations to apply to the templates |
|
|
111
|
+
| groupVariations | Variation[] | Yes | [] | Array of group variations to apply |
|
|
112
|
+
| fontOptions | FontOption[] | No | Default list | Custom fonts for text toolbar |
|
|
113
|
+
| colorPalette | (string \| null)[][] | No | Default palette | Custom color palette for text toolbar |
|
|
114
|
+
|
|
115
|
+
## Context API
|
|
116
|
+
|
|
117
|
+
The `useProductEditor` hook provides access to the following properties and methods:
|
|
118
|
+
|
|
119
|
+
| Property/Method | Type | Description |
|
|
120
|
+
|----------------|------|-------------|
|
|
121
|
+
| canvas | fabric.Canvas | The Fabric.js canvas instance |
|
|
122
|
+
| draftTemplates | Array | Available draft templates with associated variation objects |
|
|
123
|
+
| selectedTemplate | number | ID of the currently selected template |
|
|
124
|
+
| handleTemplateChange | Function | Method to switch between templates |
|
|
125
|
+
| showGrid | boolean | Whether the grid is currently displayed |
|
|
126
|
+
| setShowGrid | Function | Toggle grid visibility |
|
|
127
|
+
| showPreview | boolean | Whether the preview area is displayed |
|
|
128
|
+
| togglePreview | Function | Toggle preview area visibility |
|
|
129
|
+
| canvasObjects | Map | Map of canvas objects indexed by variation field ID |
|
|
130
|
+
| updateCanvasFromVariations | Function | Update canvas objects based on changed variations |
|
|
131
|
+
|
|
132
|
+
## ImageGallery Component
|
|
133
|
+
|
|
134
|
+
The `ImageGallery` component provides a simple way to display images in your product editor when other images fail to load.
|
|
135
|
+
|
|
136
|
+
```jsx
|
|
137
|
+
import { ImageGallery } from '@merchi/product-editor';
|
|
138
|
+
|
|
139
|
+
function Example() {
|
|
140
|
+
return (
|
|
141
|
+
<ImageGallery
|
|
142
|
+
fallbackImageUrl="https://example.com/default-image.jpg"
|
|
143
|
+
/>
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### Props for ImageGallery
|
|
149
|
+
|
|
150
|
+
| Prop | Type | Required | Default | Description |
|
|
151
|
+
|------|------|----------|---------|-------------|
|
|
152
|
+
| fallbackImageUrl | string | No | null | URL to an image that will be displayed when the primary image fails to load |
|
|
153
|
+
|
|
154
|
+
The ImageGallery component is designed to be simple and focused on providing fallback functionality for image loading errors.
|
|
155
|
+
|
|
156
|
+
## Development
|
|
157
|
+
|
|
158
|
+
```bash
|
|
159
|
+
# Install dependencies
|
|
160
|
+
npm install
|
|
161
|
+
|
|
162
|
+
# Start development server
|
|
163
|
+
npm start
|
|
164
|
+
|
|
165
|
+
# Build the library
|
|
166
|
+
npm run build
|
|
167
|
+
|
|
168
|
+
# Run tests
|
|
169
|
+
npm test
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
## License
|
|
173
|
+
|
|
174
|
+
MIT
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
+
if (mod && mod.__esModule) return mod;
|
|
20
|
+
var result = {};
|
|
21
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
+
__setModuleDefault(result, mod);
|
|
23
|
+
return result;
|
|
24
|
+
};
|
|
25
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
|
+
exports.AlignMenu = void 0;
|
|
27
|
+
var react_1 = __importStar(require("react"));
|
|
28
|
+
var grommet_icons_1 = require("grommet-icons");
|
|
29
|
+
var ProductEditorContext_1 = require("../context/ProductEditorContext");
|
|
30
|
+
require("../styles/TextToolbar.css");
|
|
31
|
+
var AlignMenu = function () {
|
|
32
|
+
var _a = (0, ProductEditorContext_1.useProductEditor)(), selectedTextObject = _a.selectedTextObject, updateSelectedText = _a.updateSelectedText;
|
|
33
|
+
var _b = (0, react_1.useState)(false), showMenu = _b[0], setShowMenu = _b[1];
|
|
34
|
+
var ref = (0, react_1.useRef)(null);
|
|
35
|
+
(0, react_1.useEffect)(function () {
|
|
36
|
+
var handleClickOutside = function (e) {
|
|
37
|
+
if (ref.current && !ref.current.contains(e.target)) {
|
|
38
|
+
setShowMenu(false);
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
document.addEventListener('mousedown', handleClickOutside);
|
|
42
|
+
return function () { return document.removeEventListener('mousedown', handleClickOutside); };
|
|
43
|
+
}, []);
|
|
44
|
+
if (!selectedTextObject || !updateSelectedText)
|
|
45
|
+
return null;
|
|
46
|
+
var handleAlign = function (align) {
|
|
47
|
+
updateSelectedText({ textAlign: align });
|
|
48
|
+
setShowMenu(false);
|
|
49
|
+
};
|
|
50
|
+
return (react_1.default.createElement("div", { className: "align-group", ref: ref },
|
|
51
|
+
react_1.default.createElement("button", { type: "button", className: "toolbar-button align-trigger", onClick: function () { return setShowMenu(function (prev) { return !prev; }); }, title: "Text Align" }, selectedTextObject.textAlign === 'center' ? react_1.default.createElement(grommet_icons_1.TextAlignCenter, null) :
|
|
52
|
+
selectedTextObject.textAlign === 'right' ? react_1.default.createElement(grommet_icons_1.TextAlignRight, null) : react_1.default.createElement(grommet_icons_1.TextAlignLeft, null)),
|
|
53
|
+
showMenu && (react_1.default.createElement("div", { className: "align-popover" },
|
|
54
|
+
react_1.default.createElement("button", { type: "button", className: "toolbar-button ".concat(selectedTextObject.textAlign === 'left' ? 'active' : ''), onClick: function () { return handleAlign('left'); } },
|
|
55
|
+
react_1.default.createElement(grommet_icons_1.TextAlignLeft, null)),
|
|
56
|
+
react_1.default.createElement("button", { type: "button", className: "toolbar-button ".concat(selectedTextObject.textAlign === 'center' ? 'active' : ''), onClick: function () { return handleAlign('center'); } },
|
|
57
|
+
react_1.default.createElement(grommet_icons_1.TextAlignCenter, null)),
|
|
58
|
+
react_1.default.createElement("button", { type: "button", className: "toolbar-button ".concat(selectedTextObject.textAlign === 'right' ? 'active' : ''), onClick: function () { return handleAlign('right'); } },
|
|
59
|
+
react_1.default.createElement(grommet_icons_1.TextAlignRight, null))))));
|
|
60
|
+
};
|
|
61
|
+
exports.AlignMenu = AlignMenu;
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
+
if (mod && mod.__esModule) return mod;
|
|
20
|
+
var result = {};
|
|
21
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
+
__setModuleDefault(result, mod);
|
|
23
|
+
return result;
|
|
24
|
+
};
|
|
25
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
26
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
27
|
+
};
|
|
28
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
29
|
+
var react_1 = __importStar(require("react"));
|
|
30
|
+
var Toolbar_1 = __importDefault(require("./Toolbar"));
|
|
31
|
+
require("../styles/FloatingToolbar.css");
|
|
32
|
+
var FloatingToolbar = function () {
|
|
33
|
+
var _a = (0, react_1.useState)({ x: 15, y: 15 }), position = _a[0], setPosition = _a[1];
|
|
34
|
+
var _b = (0, react_1.useState)(false), isDragging = _b[0], setIsDragging = _b[1];
|
|
35
|
+
var toolbarRef = (0, react_1.useRef)(null);
|
|
36
|
+
var offset = (0, react_1.useRef)({ x: 0, y: 0 });
|
|
37
|
+
var handleMouseDown = function (e) {
|
|
38
|
+
if (!toolbarRef.current)
|
|
39
|
+
return;
|
|
40
|
+
// Prevent dragging if clicking on buttons inside the toolbar
|
|
41
|
+
if (e.target.closest('.toolbar-button')) {
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
setIsDragging(true);
|
|
45
|
+
var rect = toolbarRef.current.getBoundingClientRect();
|
|
46
|
+
offset.current = {
|
|
47
|
+
x: e.clientX - rect.left,
|
|
48
|
+
y: e.clientY - rect.top,
|
|
49
|
+
};
|
|
50
|
+
if (toolbarRef.current) {
|
|
51
|
+
toolbarRef.current.style.cursor = 'grabbing';
|
|
52
|
+
}
|
|
53
|
+
e.preventDefault();
|
|
54
|
+
};
|
|
55
|
+
var handleMouseMove = function (e) {
|
|
56
|
+
if (!isDragging || !toolbarRef.current)
|
|
57
|
+
return;
|
|
58
|
+
var newX = e.clientX - offset.current.x;
|
|
59
|
+
var newY = e.clientY - offset.current.y;
|
|
60
|
+
var parent = toolbarRef.current.parentElement;
|
|
61
|
+
if (parent) {
|
|
62
|
+
var parentRect = parent.getBoundingClientRect();
|
|
63
|
+
var toolbarRect = toolbarRef.current.getBoundingClientRect();
|
|
64
|
+
newX = Math.max(parentRect.left, Math.min(newX, parentRect.right - toolbarRect.width));
|
|
65
|
+
newY = Math.max(parentRect.top, Math.min(newY, parentRect.bottom - toolbarRect.height));
|
|
66
|
+
newX -= parentRect.left;
|
|
67
|
+
newY -= parentRect.top;
|
|
68
|
+
}
|
|
69
|
+
setPosition({ x: newX, y: newY });
|
|
70
|
+
};
|
|
71
|
+
var handleMouseUp = function () {
|
|
72
|
+
setIsDragging(false);
|
|
73
|
+
if (toolbarRef.current) {
|
|
74
|
+
toolbarRef.current.style.cursor = 'grab';
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
(0, react_1.useEffect)(function () {
|
|
78
|
+
if (isDragging) {
|
|
79
|
+
window.addEventListener('mousemove', handleMouseMove);
|
|
80
|
+
window.addEventListener('mouseup', handleMouseUp);
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
window.removeEventListener('mousemove', handleMouseMove);
|
|
84
|
+
window.removeEventListener('mouseup', handleMouseUp);
|
|
85
|
+
}
|
|
86
|
+
return function () {
|
|
87
|
+
window.removeEventListener('mousemove', handleMouseMove);
|
|
88
|
+
window.removeEventListener('mouseup', handleMouseUp);
|
|
89
|
+
};
|
|
90
|
+
}, [isDragging]);
|
|
91
|
+
return (react_1.default.createElement("div", { ref: toolbarRef, className: "floating-toolbar", style: {
|
|
92
|
+
left: "".concat(position.x, "px"),
|
|
93
|
+
top: "".concat(position.y, "px"),
|
|
94
|
+
}, onMouseDown: handleMouseDown },
|
|
95
|
+
react_1.default.createElement(Toolbar_1.default, null)));
|
|
96
|
+
};
|
|
97
|
+
exports.default = FloatingToolbar;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.FormatButtons = void 0;
|
|
7
|
+
var react_1 = __importDefault(require("react"));
|
|
8
|
+
var ProductEditorContext_1 = require("../context/ProductEditorContext");
|
|
9
|
+
var FormatButtons = function () {
|
|
10
|
+
var _a = (0, ProductEditorContext_1.useProductEditor)(), selectedTextObject = _a.selectedTextObject, updateSelectedText = _a.updateSelectedText;
|
|
11
|
+
if (!selectedTextObject || !updateSelectedText)
|
|
12
|
+
return null;
|
|
13
|
+
var isBold = selectedTextObject.fontWeight === 'bold';
|
|
14
|
+
var isUnderlined = selectedTextObject.underline === true;
|
|
15
|
+
return (react_1.default.createElement(react_1.default.Fragment, null,
|
|
16
|
+
react_1.default.createElement("button", { type: "button", className: "toolbar-button bold-button ".concat(isBold ? 'active' : ''), onClick: function () { return updateSelectedText({ fontWeight: isBold ? 'normal' : 'bold' }); }, title: "Bold" },
|
|
17
|
+
react_1.default.createElement("strong", null, "B")),
|
|
18
|
+
react_1.default.createElement("button", { type: "button", className: "toolbar-button underline-button ".concat(isUnderlined ? 'active' : ''), onClick: function () { return updateSelectedText({ underline: !isUnderlined }); }, title: "Underline" },
|
|
19
|
+
react_1.default.createElement("strong", null,
|
|
20
|
+
react_1.default.createElement("u", null, "U")))));
|
|
21
|
+
};
|
|
22
|
+
exports.FormatButtons = FormatButtons;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
var react_1 = __importDefault(require("react"));
|
|
7
|
+
var grommet_icons_1 = require("grommet-icons");
|
|
8
|
+
var ImageNavButton = function (_a) {
|
|
9
|
+
var direction = _a.direction, onClick = _a.onClick;
|
|
10
|
+
return (react_1.default.createElement("button", { onClick: onClick, className: "nav-button ".concat(direction), "aria-label": direction === 'left' ? 'Previous image' : 'Next image', type: "button" }, direction === 'left'
|
|
11
|
+
? react_1.default.createElement(grommet_icons_1.FormPrevious, { className: "nav-button-icon", color: "#6b7280" })
|
|
12
|
+
: react_1.default.createElement(grommet_icons_1.FormNext, { className: "nav-button-icon", color: "#6b7280" })));
|
|
13
|
+
};
|
|
14
|
+
exports.default = ImageNavButton;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
interface ImageZoomModalProps {
|
|
3
|
+
isOpen: boolean;
|
|
4
|
+
onClose: () => void;
|
|
5
|
+
imageUrl: string;
|
|
6
|
+
productName: string;
|
|
7
|
+
totalImages: number;
|
|
8
|
+
currentIndex: number;
|
|
9
|
+
allImages: any[];
|
|
10
|
+
}
|
|
11
|
+
declare const ImageZoomModal: ({ isOpen, onClose, imageUrl, productName, totalImages, currentIndex, allImages }: ImageZoomModalProps) => React.ReactPortal | null;
|
|
12
|
+
export default ImageZoomModal;
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __assign = (this && this.__assign) || function () {
|
|
3
|
+
__assign = Object.assign || function(t) {
|
|
4
|
+
for (var s, i = 1, n = arguments.length; i < n; i++) {
|
|
5
|
+
s = arguments[i];
|
|
6
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
|
|
7
|
+
t[p] = s[p];
|
|
8
|
+
}
|
|
9
|
+
return t;
|
|
10
|
+
};
|
|
11
|
+
return __assign.apply(this, arguments);
|
|
12
|
+
};
|
|
13
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
14
|
+
if (k2 === undefined) k2 = k;
|
|
15
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
16
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
17
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
18
|
+
}
|
|
19
|
+
Object.defineProperty(o, k2, desc);
|
|
20
|
+
}) : (function(o, m, k, k2) {
|
|
21
|
+
if (k2 === undefined) k2 = k;
|
|
22
|
+
o[k2] = m[k];
|
|
23
|
+
}));
|
|
24
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
25
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
26
|
+
}) : function(o, v) {
|
|
27
|
+
o["default"] = v;
|
|
28
|
+
});
|
|
29
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
30
|
+
if (mod && mod.__esModule) return mod;
|
|
31
|
+
var result = {};
|
|
32
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
33
|
+
__setModuleDefault(result, mod);
|
|
34
|
+
return result;
|
|
35
|
+
};
|
|
36
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
37
|
+
var react_1 = __importStar(require("react"));
|
|
38
|
+
var react_dom_1 = require("react-dom");
|
|
39
|
+
var grommet_icons_1 = require("grommet-icons");
|
|
40
|
+
var framer_motion_1 = require("framer-motion");
|
|
41
|
+
var useSwipeNavigate_1 = require("../hooks/useSwipeNavigate");
|
|
42
|
+
var MODAL_BODY_CLASS = 'has-preview-modal-open';
|
|
43
|
+
var ImageZoomModal = function (_a) {
|
|
44
|
+
var isOpen = _a.isOpen, onClose = _a.onClose, imageUrl = _a.imageUrl, productName = _a.productName, totalImages = _a.totalImages, currentIndex = _a.currentIndex, allImages = _a.allImages;
|
|
45
|
+
var _b = (0, react_1.useState)(currentIndex), modalImageIndex = _b[0], setModalImageIndex = _b[1];
|
|
46
|
+
var _c = (0, react_1.useState)(imageUrl), currentImageUrl = _c[0], setCurrentImageUrl = _c[1];
|
|
47
|
+
var _d = (0, react_1.useState)('right'), slideDirection = _d[0], setSlideDirection = _d[1];
|
|
48
|
+
(0, react_1.useEffect)(function () {
|
|
49
|
+
if (isOpen) {
|
|
50
|
+
document.body.style.overflow = 'hidden';
|
|
51
|
+
document.body.classList.add(MODAL_BODY_CLASS);
|
|
52
|
+
document.documentElement.classList.add(MODAL_BODY_CLASS);
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
document.body.style.overflow = '';
|
|
56
|
+
document.body.classList.remove(MODAL_BODY_CLASS);
|
|
57
|
+
document.documentElement.classList.remove(MODAL_BODY_CLASS);
|
|
58
|
+
}
|
|
59
|
+
return function () {
|
|
60
|
+
document.body.style.overflow = '';
|
|
61
|
+
document.body.classList.remove(MODAL_BODY_CLASS);
|
|
62
|
+
document.documentElement.classList.remove(MODAL_BODY_CLASS);
|
|
63
|
+
};
|
|
64
|
+
}, [isOpen]);
|
|
65
|
+
(0, react_1.useEffect)(function () {
|
|
66
|
+
setModalImageIndex(currentIndex);
|
|
67
|
+
setCurrentImageUrl(imageUrl);
|
|
68
|
+
setSlideDirection('right');
|
|
69
|
+
}, [currentIndex, imageUrl, isOpen]);
|
|
70
|
+
var handlePrevious = function () {
|
|
71
|
+
setSlideDirection('left');
|
|
72
|
+
setModalImageIndex(function (prev) {
|
|
73
|
+
var newIndex = (prev - 1 + totalImages) % totalImages;
|
|
74
|
+
setCurrentImageUrl(allImages[newIndex].viewUrl);
|
|
75
|
+
return newIndex;
|
|
76
|
+
});
|
|
77
|
+
};
|
|
78
|
+
var handleNext = function () {
|
|
79
|
+
setSlideDirection('right');
|
|
80
|
+
setModalImageIndex(function (prev) {
|
|
81
|
+
var newIndex = (prev + 1) % totalImages;
|
|
82
|
+
setCurrentImageUrl(allImages[newIndex].viewUrl);
|
|
83
|
+
return newIndex;
|
|
84
|
+
});
|
|
85
|
+
};
|
|
86
|
+
var swipeHandlers = (0, useSwipeNavigate_1.useSwipeNavigate)({
|
|
87
|
+
onSwipeLeft: handleNext,
|
|
88
|
+
onSwipeRight: handlePrevious,
|
|
89
|
+
});
|
|
90
|
+
var slideVariants = {
|
|
91
|
+
enter: function (direction) { return ({
|
|
92
|
+
x: direction === 'right' ? 1000 : -1000,
|
|
93
|
+
opacity: 0
|
|
94
|
+
}); },
|
|
95
|
+
center: {
|
|
96
|
+
zIndex: 1,
|
|
97
|
+
x: 0,
|
|
98
|
+
opacity: 1,
|
|
99
|
+
transition: {
|
|
100
|
+
x: { type: "spring", stiffness: 300, damping: 30 },
|
|
101
|
+
opacity: { duration: 0.2 }
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
exit: function (direction) { return ({
|
|
105
|
+
zIndex: 0,
|
|
106
|
+
x: direction === 'left' ? 1000 : -1000,
|
|
107
|
+
opacity: 0,
|
|
108
|
+
transition: {
|
|
109
|
+
x: { type: "spring", stiffness: 300, damping: 30 },
|
|
110
|
+
opacity: { duration: 0.05 }
|
|
111
|
+
}
|
|
112
|
+
}); }
|
|
113
|
+
};
|
|
114
|
+
if (!isOpen)
|
|
115
|
+
return null;
|
|
116
|
+
// Render the modal directly at the document body level
|
|
117
|
+
return (0, react_dom_1.createPortal)(react_1.default.createElement("div", { className: "zoom-modal" },
|
|
118
|
+
react_1.default.createElement("div", { className: "zoom-modal-container" },
|
|
119
|
+
react_1.default.createElement("button", { onClick: onClose, className: "close-button", type: "button" },
|
|
120
|
+
react_1.default.createElement(grommet_icons_1.Close, { width: 36, height: 36, color: "white" })),
|
|
121
|
+
totalImages > 1 && (react_1.default.createElement("button", { onClick: handlePrevious, className: "nav-button-zoom left", type: "button" },
|
|
122
|
+
react_1.default.createElement(grommet_icons_1.FormPrevious, { width: 42, height: 42, color: "white" }))),
|
|
123
|
+
react_1.default.createElement("div", __assign({ className: "zoom-image-container" }, swipeHandlers),
|
|
124
|
+
react_1.default.createElement(framer_motion_1.AnimatePresence, { initial: false, custom: slideDirection },
|
|
125
|
+
react_1.default.createElement(framer_motion_1.motion.img, { key: modalImageIndex, src: currentImageUrl, alt: productName, className: "zoom-image", loading: "lazy", custom: slideDirection, variants: slideVariants, initial: "enter", animate: "center", exit: "exit" }))),
|
|
126
|
+
totalImages > 1 && (react_1.default.createElement("button", { onClick: handleNext, className: "nav-button-zoom right", type: "button" },
|
|
127
|
+
react_1.default.createElement(grommet_icons_1.FormNext, { width: 42, height: 42, color: "white" }))))), document.body);
|
|
128
|
+
};
|
|
129
|
+
exports.default = ImageZoomModal;
|