@lodashventure/medusa-banner 0.0.6

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.
@@ -0,0 +1,1297 @@
1
+ import { jsx, jsxs, Fragment } from "react/jsx-runtime";
2
+ import { defineRouteConfig } from "@medusajs/admin-sdk";
3
+ import { Trash, StackPerspective } from "@medusajs/icons";
4
+ import { KeyboardCode, closestCorners, getFirstCollision, getScrollableAncestors, useDndContext, useDroppable, useDraggable, getClientRect, useSensors, useSensor, PointerSensor, KeyboardSensor, DndContext, closestCenter, DragOverlay } from "@dnd-kit/core";
5
+ import React, { useLayoutEffect, useEffect, useMemo, useRef, useContext, useState, useCallback } from "react";
6
+ import { clx, Button, usePrompt, Drawer } from "@medusajs/ui";
7
+ import { Check, Upload, ImageIcon, Trash2, MoveHorizontal, X } from "lucide-react";
8
+ import { useQuery } from "@tanstack/react-query";
9
+ import { useDropzone } from "react-dropzone";
10
+ function useCombinedRefs() {
11
+ for (var _len = arguments.length, refs = new Array(_len), _key = 0; _key < _len; _key++) {
12
+ refs[_key] = arguments[_key];
13
+ }
14
+ return useMemo(
15
+ () => (node) => {
16
+ refs.forEach((ref) => ref(node));
17
+ },
18
+ // eslint-disable-next-line react-hooks/exhaustive-deps
19
+ refs
20
+ );
21
+ }
22
+ const canUseDOM = typeof window !== "undefined" && typeof window.document !== "undefined" && typeof window.document.createElement !== "undefined";
23
+ function isWindow(element) {
24
+ const elementString = Object.prototype.toString.call(element);
25
+ return elementString === "[object Window]" || // In Electron context the Window object serializes to [object global]
26
+ elementString === "[object global]";
27
+ }
28
+ function isNode(node) {
29
+ return "nodeType" in node;
30
+ }
31
+ function getWindow(target) {
32
+ var _target$ownerDocument, _target$ownerDocument2;
33
+ if (!target) {
34
+ return window;
35
+ }
36
+ if (isWindow(target)) {
37
+ return target;
38
+ }
39
+ if (!isNode(target)) {
40
+ return window;
41
+ }
42
+ return (_target$ownerDocument = (_target$ownerDocument2 = target.ownerDocument) == null ? void 0 : _target$ownerDocument2.defaultView) != null ? _target$ownerDocument : window;
43
+ }
44
+ const useIsomorphicLayoutEffect = canUseDOM ? useLayoutEffect : useEffect;
45
+ let ids = {};
46
+ function useUniqueId(prefix, value) {
47
+ return useMemo(() => {
48
+ if (value) {
49
+ return value;
50
+ }
51
+ const id = ids[prefix] == null ? 0 : ids[prefix] + 1;
52
+ ids[prefix] = id;
53
+ return prefix + "-" + id;
54
+ }, [prefix, value]);
55
+ }
56
+ function createAdjustmentFn(modifier) {
57
+ return function(object) {
58
+ for (var _len = arguments.length, adjustments = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
59
+ adjustments[_key - 1] = arguments[_key];
60
+ }
61
+ return adjustments.reduce((accumulator, adjustment) => {
62
+ const entries = Object.entries(adjustment);
63
+ for (const [key, valueAdjustment] of entries) {
64
+ const value = accumulator[key];
65
+ if (value != null) {
66
+ accumulator[key] = value + modifier * valueAdjustment;
67
+ }
68
+ }
69
+ return accumulator;
70
+ }, {
71
+ ...object
72
+ });
73
+ };
74
+ }
75
+ const subtract = /* @__PURE__ */ createAdjustmentFn(-1);
76
+ function isKeyboardEvent(event) {
77
+ if (!event) {
78
+ return false;
79
+ }
80
+ const {
81
+ KeyboardEvent
82
+ } = getWindow(event.target);
83
+ return KeyboardEvent && event instanceof KeyboardEvent;
84
+ }
85
+ const CSS = /* @__PURE__ */ Object.freeze({
86
+ Translate: {
87
+ toString(transform) {
88
+ if (!transform) {
89
+ return;
90
+ }
91
+ const {
92
+ x,
93
+ y
94
+ } = transform;
95
+ return "translate3d(" + (x ? Math.round(x) : 0) + "px, " + (y ? Math.round(y) : 0) + "px, 0)";
96
+ }
97
+ },
98
+ Scale: {
99
+ toString(transform) {
100
+ if (!transform) {
101
+ return;
102
+ }
103
+ const {
104
+ scaleX,
105
+ scaleY
106
+ } = transform;
107
+ return "scaleX(" + scaleX + ") scaleY(" + scaleY + ")";
108
+ }
109
+ },
110
+ Transform: {
111
+ toString(transform) {
112
+ if (!transform) {
113
+ return;
114
+ }
115
+ return [CSS.Translate.toString(transform), CSS.Scale.toString(transform)].join(" ");
116
+ }
117
+ },
118
+ Transition: {
119
+ toString(_ref) {
120
+ let {
121
+ property,
122
+ duration,
123
+ easing
124
+ } = _ref;
125
+ return property + " " + duration + "ms " + easing;
126
+ }
127
+ }
128
+ });
129
+ function arrayMove(array, from, to) {
130
+ const newArray = array.slice();
131
+ newArray.splice(to < 0 ? newArray.length + to : to, 0, newArray.splice(from, 1)[0]);
132
+ return newArray;
133
+ }
134
+ function getSortedRects(items, rects) {
135
+ return items.reduce((accumulator, id, index) => {
136
+ const rect = rects.get(id);
137
+ if (rect) {
138
+ accumulator[index] = rect;
139
+ }
140
+ return accumulator;
141
+ }, Array(items.length));
142
+ }
143
+ function isValidIndex(index) {
144
+ return index !== null && index >= 0;
145
+ }
146
+ function itemsEqual(a, b) {
147
+ if (a === b) {
148
+ return true;
149
+ }
150
+ if (a.length !== b.length) {
151
+ return false;
152
+ }
153
+ for (let i = 0; i < a.length; i++) {
154
+ if (a[i] !== b[i]) {
155
+ return false;
156
+ }
157
+ }
158
+ return true;
159
+ }
160
+ function normalizeDisabled(disabled) {
161
+ if (typeof disabled === "boolean") {
162
+ return {
163
+ draggable: disabled,
164
+ droppable: disabled
165
+ };
166
+ }
167
+ return disabled;
168
+ }
169
+ const rectSortingStrategy = (_ref) => {
170
+ let {
171
+ rects,
172
+ activeIndex,
173
+ overIndex,
174
+ index
175
+ } = _ref;
176
+ const newRects = arrayMove(rects, overIndex, activeIndex);
177
+ const oldRect = rects[index];
178
+ const newRect = newRects[index];
179
+ if (!newRect || !oldRect) {
180
+ return null;
181
+ }
182
+ return {
183
+ x: newRect.left - oldRect.left,
184
+ y: newRect.top - oldRect.top,
185
+ scaleX: newRect.width / oldRect.width,
186
+ scaleY: newRect.height / oldRect.height
187
+ };
188
+ };
189
+ const ID_PREFIX = "Sortable";
190
+ const Context = /* @__PURE__ */ React.createContext({
191
+ activeIndex: -1,
192
+ containerId: ID_PREFIX,
193
+ disableTransforms: false,
194
+ items: [],
195
+ overIndex: -1,
196
+ useDragOverlay: false,
197
+ sortedRects: [],
198
+ strategy: rectSortingStrategy,
199
+ disabled: {
200
+ draggable: false,
201
+ droppable: false
202
+ }
203
+ });
204
+ function SortableContext(_ref) {
205
+ let {
206
+ children,
207
+ id,
208
+ items: userDefinedItems,
209
+ strategy = rectSortingStrategy,
210
+ disabled: disabledProp = false
211
+ } = _ref;
212
+ const {
213
+ active,
214
+ dragOverlay,
215
+ droppableRects,
216
+ over,
217
+ measureDroppableContainers
218
+ } = useDndContext();
219
+ const containerId = useUniqueId(ID_PREFIX, id);
220
+ const useDragOverlay = Boolean(dragOverlay.rect !== null);
221
+ const items = useMemo(() => userDefinedItems.map((item) => typeof item === "object" && "id" in item ? item.id : item), [userDefinedItems]);
222
+ const isDragging = active != null;
223
+ const activeIndex = active ? items.indexOf(active.id) : -1;
224
+ const overIndex = over ? items.indexOf(over.id) : -1;
225
+ const previousItemsRef = useRef(items);
226
+ const itemsHaveChanged = !itemsEqual(items, previousItemsRef.current);
227
+ const disableTransforms = overIndex !== -1 && activeIndex === -1 || itemsHaveChanged;
228
+ const disabled = normalizeDisabled(disabledProp);
229
+ useIsomorphicLayoutEffect(() => {
230
+ if (itemsHaveChanged && isDragging) {
231
+ measureDroppableContainers(items);
232
+ }
233
+ }, [itemsHaveChanged, items, isDragging, measureDroppableContainers]);
234
+ useEffect(() => {
235
+ previousItemsRef.current = items;
236
+ }, [items]);
237
+ const contextValue = useMemo(
238
+ () => ({
239
+ activeIndex,
240
+ containerId,
241
+ disabled,
242
+ disableTransforms,
243
+ items,
244
+ overIndex,
245
+ useDragOverlay,
246
+ sortedRects: getSortedRects(items, droppableRects),
247
+ strategy
248
+ }),
249
+ // eslint-disable-next-line react-hooks/exhaustive-deps
250
+ [activeIndex, containerId, disabled.draggable, disabled.droppable, disableTransforms, items, overIndex, droppableRects, useDragOverlay, strategy]
251
+ );
252
+ return React.createElement(Context.Provider, {
253
+ value: contextValue
254
+ }, children);
255
+ }
256
+ const defaultNewIndexGetter = (_ref) => {
257
+ let {
258
+ id,
259
+ items,
260
+ activeIndex,
261
+ overIndex
262
+ } = _ref;
263
+ return arrayMove(items, activeIndex, overIndex).indexOf(id);
264
+ };
265
+ const defaultAnimateLayoutChanges = (_ref2) => {
266
+ let {
267
+ containerId,
268
+ isSorting,
269
+ wasDragging,
270
+ index,
271
+ items,
272
+ newIndex,
273
+ previousItems,
274
+ previousContainerId,
275
+ transition
276
+ } = _ref2;
277
+ if (!transition || !wasDragging) {
278
+ return false;
279
+ }
280
+ if (previousItems !== items && index === newIndex) {
281
+ return false;
282
+ }
283
+ if (isSorting) {
284
+ return true;
285
+ }
286
+ return newIndex !== index && containerId === previousContainerId;
287
+ };
288
+ const defaultTransition = {
289
+ duration: 200,
290
+ easing: "ease"
291
+ };
292
+ const transitionProperty = "transform";
293
+ const disabledTransition = /* @__PURE__ */ CSS.Transition.toString({
294
+ property: transitionProperty,
295
+ duration: 0,
296
+ easing: "linear"
297
+ });
298
+ const defaultAttributes = {
299
+ roleDescription: "sortable"
300
+ };
301
+ function useDerivedTransform(_ref) {
302
+ let {
303
+ disabled,
304
+ index,
305
+ node,
306
+ rect
307
+ } = _ref;
308
+ const [derivedTransform, setDerivedtransform] = useState(null);
309
+ const previousIndex = useRef(index);
310
+ useIsomorphicLayoutEffect(() => {
311
+ if (!disabled && index !== previousIndex.current && node.current) {
312
+ const initial = rect.current;
313
+ if (initial) {
314
+ const current = getClientRect(node.current, {
315
+ ignoreTransform: true
316
+ });
317
+ const delta = {
318
+ x: initial.left - current.left,
319
+ y: initial.top - current.top,
320
+ scaleX: initial.width / current.width,
321
+ scaleY: initial.height / current.height
322
+ };
323
+ if (delta.x || delta.y) {
324
+ setDerivedtransform(delta);
325
+ }
326
+ }
327
+ }
328
+ if (index !== previousIndex.current) {
329
+ previousIndex.current = index;
330
+ }
331
+ }, [disabled, index, node, rect]);
332
+ useEffect(() => {
333
+ if (derivedTransform) {
334
+ setDerivedtransform(null);
335
+ }
336
+ }, [derivedTransform]);
337
+ return derivedTransform;
338
+ }
339
+ function useSortable(_ref) {
340
+ let {
341
+ animateLayoutChanges = defaultAnimateLayoutChanges,
342
+ attributes: userDefinedAttributes,
343
+ disabled: localDisabled,
344
+ data: customData,
345
+ getNewIndex = defaultNewIndexGetter,
346
+ id,
347
+ strategy: localStrategy,
348
+ resizeObserverConfig,
349
+ transition = defaultTransition
350
+ } = _ref;
351
+ const {
352
+ items,
353
+ containerId,
354
+ activeIndex,
355
+ disabled: globalDisabled,
356
+ disableTransforms,
357
+ sortedRects,
358
+ overIndex,
359
+ useDragOverlay,
360
+ strategy: globalStrategy
361
+ } = useContext(Context);
362
+ const disabled = normalizeLocalDisabled(localDisabled, globalDisabled);
363
+ const index = items.indexOf(id);
364
+ const data = useMemo(() => ({
365
+ sortable: {
366
+ containerId,
367
+ index,
368
+ items
369
+ },
370
+ ...customData
371
+ }), [containerId, customData, index, items]);
372
+ const itemsAfterCurrentSortable = useMemo(() => items.slice(items.indexOf(id)), [items, id]);
373
+ const {
374
+ rect,
375
+ node,
376
+ isOver,
377
+ setNodeRef: setDroppableNodeRef
378
+ } = useDroppable({
379
+ id,
380
+ data,
381
+ disabled: disabled.droppable,
382
+ resizeObserverConfig: {
383
+ updateMeasurementsFor: itemsAfterCurrentSortable,
384
+ ...resizeObserverConfig
385
+ }
386
+ });
387
+ const {
388
+ active,
389
+ activatorEvent,
390
+ activeNodeRect,
391
+ attributes,
392
+ setNodeRef: setDraggableNodeRef,
393
+ listeners,
394
+ isDragging,
395
+ over,
396
+ setActivatorNodeRef,
397
+ transform
398
+ } = useDraggable({
399
+ id,
400
+ data,
401
+ attributes: {
402
+ ...defaultAttributes,
403
+ ...userDefinedAttributes
404
+ },
405
+ disabled: disabled.draggable
406
+ });
407
+ const setNodeRef = useCombinedRefs(setDroppableNodeRef, setDraggableNodeRef);
408
+ const isSorting = Boolean(active);
409
+ const displaceItem = isSorting && !disableTransforms && isValidIndex(activeIndex) && isValidIndex(overIndex);
410
+ const shouldDisplaceDragSource = !useDragOverlay && isDragging;
411
+ const dragSourceDisplacement = shouldDisplaceDragSource && displaceItem ? transform : null;
412
+ const strategy = localStrategy != null ? localStrategy : globalStrategy;
413
+ const finalTransform = displaceItem ? dragSourceDisplacement != null ? dragSourceDisplacement : strategy({
414
+ rects: sortedRects,
415
+ activeNodeRect,
416
+ activeIndex,
417
+ overIndex,
418
+ index
419
+ }) : null;
420
+ const newIndex = isValidIndex(activeIndex) && isValidIndex(overIndex) ? getNewIndex({
421
+ id,
422
+ items,
423
+ activeIndex,
424
+ overIndex
425
+ }) : index;
426
+ const activeId = active == null ? void 0 : active.id;
427
+ const previous = useRef({
428
+ activeId,
429
+ items,
430
+ newIndex,
431
+ containerId
432
+ });
433
+ const itemsHaveChanged = items !== previous.current.items;
434
+ const shouldAnimateLayoutChanges = animateLayoutChanges({
435
+ active,
436
+ containerId,
437
+ isDragging,
438
+ isSorting,
439
+ id,
440
+ index,
441
+ items,
442
+ newIndex: previous.current.newIndex,
443
+ previousItems: previous.current.items,
444
+ previousContainerId: previous.current.containerId,
445
+ transition,
446
+ wasDragging: previous.current.activeId != null
447
+ });
448
+ const derivedTransform = useDerivedTransform({
449
+ disabled: !shouldAnimateLayoutChanges,
450
+ index,
451
+ node,
452
+ rect
453
+ });
454
+ useEffect(() => {
455
+ if (isSorting && previous.current.newIndex !== newIndex) {
456
+ previous.current.newIndex = newIndex;
457
+ }
458
+ if (containerId !== previous.current.containerId) {
459
+ previous.current.containerId = containerId;
460
+ }
461
+ if (items !== previous.current.items) {
462
+ previous.current.items = items;
463
+ }
464
+ }, [isSorting, newIndex, containerId, items]);
465
+ useEffect(() => {
466
+ if (activeId === previous.current.activeId) {
467
+ return;
468
+ }
469
+ if (activeId && !previous.current.activeId) {
470
+ previous.current.activeId = activeId;
471
+ return;
472
+ }
473
+ const timeoutId = setTimeout(() => {
474
+ previous.current.activeId = activeId;
475
+ }, 50);
476
+ return () => clearTimeout(timeoutId);
477
+ }, [activeId]);
478
+ return {
479
+ active,
480
+ activeIndex,
481
+ attributes,
482
+ data,
483
+ rect,
484
+ index,
485
+ newIndex,
486
+ items,
487
+ isOver,
488
+ isSorting,
489
+ isDragging,
490
+ listeners,
491
+ node,
492
+ overIndex,
493
+ over,
494
+ setNodeRef,
495
+ setActivatorNodeRef,
496
+ setDroppableNodeRef,
497
+ setDraggableNodeRef,
498
+ transform: derivedTransform != null ? derivedTransform : finalTransform,
499
+ transition: getTransition()
500
+ };
501
+ function getTransition() {
502
+ if (
503
+ // Temporarily disable transitions for a single frame to set up derived transforms
504
+ derivedTransform || // Or to prevent items jumping to back to their "new" position when items change
505
+ itemsHaveChanged && previous.current.newIndex === index
506
+ ) {
507
+ return disabledTransition;
508
+ }
509
+ if (shouldDisplaceDragSource && !isKeyboardEvent(activatorEvent) || !transition) {
510
+ return void 0;
511
+ }
512
+ if (isSorting || shouldAnimateLayoutChanges) {
513
+ return CSS.Transition.toString({
514
+ ...transition,
515
+ property: transitionProperty
516
+ });
517
+ }
518
+ return void 0;
519
+ }
520
+ }
521
+ function normalizeLocalDisabled(localDisabled, globalDisabled) {
522
+ var _localDisabled$dragga, _localDisabled$droppa;
523
+ if (typeof localDisabled === "boolean") {
524
+ return {
525
+ draggable: localDisabled,
526
+ // Backwards compatibility
527
+ droppable: false
528
+ };
529
+ }
530
+ return {
531
+ draggable: (_localDisabled$dragga = localDisabled == null ? void 0 : localDisabled.draggable) != null ? _localDisabled$dragga : globalDisabled.draggable,
532
+ droppable: (_localDisabled$droppa = localDisabled == null ? void 0 : localDisabled.droppable) != null ? _localDisabled$droppa : globalDisabled.droppable
533
+ };
534
+ }
535
+ function hasSortableData(entry) {
536
+ if (!entry) {
537
+ return false;
538
+ }
539
+ const data = entry.data.current;
540
+ if (data && "sortable" in data && typeof data.sortable === "object" && "containerId" in data.sortable && "items" in data.sortable && "index" in data.sortable) {
541
+ return true;
542
+ }
543
+ return false;
544
+ }
545
+ const directions = [KeyboardCode.Down, KeyboardCode.Right, KeyboardCode.Up, KeyboardCode.Left];
546
+ const sortableKeyboardCoordinates = (event, _ref) => {
547
+ let {
548
+ context: {
549
+ active,
550
+ collisionRect,
551
+ droppableRects,
552
+ droppableContainers,
553
+ over,
554
+ scrollableAncestors
555
+ }
556
+ } = _ref;
557
+ if (directions.includes(event.code)) {
558
+ event.preventDefault();
559
+ if (!active || !collisionRect) {
560
+ return;
561
+ }
562
+ const filteredContainers = [];
563
+ droppableContainers.getEnabled().forEach((entry) => {
564
+ if (!entry || entry != null && entry.disabled) {
565
+ return;
566
+ }
567
+ const rect = droppableRects.get(entry.id);
568
+ if (!rect) {
569
+ return;
570
+ }
571
+ switch (event.code) {
572
+ case KeyboardCode.Down:
573
+ if (collisionRect.top < rect.top) {
574
+ filteredContainers.push(entry);
575
+ }
576
+ break;
577
+ case KeyboardCode.Up:
578
+ if (collisionRect.top > rect.top) {
579
+ filteredContainers.push(entry);
580
+ }
581
+ break;
582
+ case KeyboardCode.Left:
583
+ if (collisionRect.left > rect.left) {
584
+ filteredContainers.push(entry);
585
+ }
586
+ break;
587
+ case KeyboardCode.Right:
588
+ if (collisionRect.left < rect.left) {
589
+ filteredContainers.push(entry);
590
+ }
591
+ break;
592
+ }
593
+ });
594
+ const collisions = closestCorners({
595
+ active,
596
+ collisionRect,
597
+ droppableRects,
598
+ droppableContainers: filteredContainers,
599
+ pointerCoordinates: null
600
+ });
601
+ let closestId = getFirstCollision(collisions, "id");
602
+ if (closestId === (over == null ? void 0 : over.id) && collisions.length > 1) {
603
+ closestId = collisions[1].id;
604
+ }
605
+ if (closestId != null) {
606
+ const activeDroppable = droppableContainers.get(active.id);
607
+ const newDroppable = droppableContainers.get(closestId);
608
+ const newRect = newDroppable ? droppableRects.get(newDroppable.id) : null;
609
+ const newNode = newDroppable == null ? void 0 : newDroppable.node.current;
610
+ if (newNode && newRect && activeDroppable && newDroppable) {
611
+ const newScrollAncestors = getScrollableAncestors(newNode);
612
+ const hasDifferentScrollAncestors = newScrollAncestors.some((element, index) => scrollableAncestors[index] !== element);
613
+ const hasSameContainer = isSameContainer(activeDroppable, newDroppable);
614
+ const isAfterActive = isAfter(activeDroppable, newDroppable);
615
+ const offset = hasDifferentScrollAncestors || !hasSameContainer ? {
616
+ x: 0,
617
+ y: 0
618
+ } : {
619
+ x: isAfterActive ? collisionRect.width - newRect.width : 0,
620
+ y: isAfterActive ? collisionRect.height - newRect.height : 0
621
+ };
622
+ const rectCoordinates = {
623
+ x: newRect.left,
624
+ y: newRect.top
625
+ };
626
+ const newCoordinates = offset.x && offset.y ? rectCoordinates : subtract(rectCoordinates, offset);
627
+ return newCoordinates;
628
+ }
629
+ }
630
+ }
631
+ return void 0;
632
+ };
633
+ function isSameContainer(a, b) {
634
+ if (!hasSortableData(a) || !hasSortableData(b)) {
635
+ return false;
636
+ }
637
+ return a.data.current.sortable.containerId === b.data.current.sortable.containerId;
638
+ }
639
+ function isAfter(a, b) {
640
+ if (!hasSortableData(a) || !hasSortableData(b)) {
641
+ return false;
642
+ }
643
+ if (!isSameContainer(a, b)) {
644
+ return false;
645
+ }
646
+ return a.data.current.sortable.index < b.data.current.sortable.index;
647
+ }
648
+ const useBanners = () => {
649
+ const getUploadedFiles = async () => {
650
+ try {
651
+ const response = await fetch("/admin/banners", {
652
+ method: "GET",
653
+ credentials: "include"
654
+ });
655
+ if (!response.ok) {
656
+ throw new Error(`HTTP error! Status: ${response.status}`);
657
+ }
658
+ const data = await response.json();
659
+ return data ?? {
660
+ banners: [],
661
+ count: 0
662
+ };
663
+ } catch (error) {
664
+ console.error("Error fetching banners:", error);
665
+ throw error;
666
+ }
667
+ };
668
+ return useQuery({
669
+ queryKey: ["banners"],
670
+ queryFn: getUploadedFiles,
671
+ // No need for the arrow function wrapper
672
+ retry: false
673
+ });
674
+ };
675
+ const DraggableItem = ({ banner }) => {
676
+ return /* @__PURE__ */ jsx("div", { className: "rounded-lg shadow-md border border-gray-200 opacity-80 scale-105", children: /* @__PURE__ */ jsxs("div", { children: [
677
+ /* @__PURE__ */ jsx(
678
+ "img",
679
+ {
680
+ src: banner.url,
681
+ alt: `Banner ${banner.id}`,
682
+ className: "object-cover w-full h-48 rounded-t-lg"
683
+ }
684
+ ),
685
+ /* @__PURE__ */ jsx("div", { className: "p-2 text-sm truncate", children: banner.id })
686
+ ] }) });
687
+ };
688
+ const GridItem = ({
689
+ banner,
690
+ isSelected,
691
+ onClick,
692
+ onSelect
693
+ }) => {
694
+ return /* @__PURE__ */ jsxs(
695
+ "div",
696
+ {
697
+ className: `
698
+ relative overflow-hidden rounded-lg shadow-md cursor-pointer hover:shadow-lg
699
+ transition-all duration-200 ease-in-out
700
+ ${isSelected ? "ring-2 ring-offset-2 ring-blue-500" : ""}
701
+ `,
702
+ onClick,
703
+ children: [
704
+ /* @__PURE__ */ jsxs("div", { children: [
705
+ /* @__PURE__ */ jsx(
706
+ "img",
707
+ {
708
+ src: banner.url || "/placeholder.svg?height=200&width=300",
709
+ alt: `Banner ${banner.id}`,
710
+ className: "object-cover w-full h-48"
711
+ }
712
+ ),
713
+ /* @__PURE__ */ jsx("div", { className: "truncate p-2 text-sm", children: banner.id })
714
+ ] }),
715
+ /* @__PURE__ */ jsx(
716
+ "div",
717
+ {
718
+ className: `
719
+ absolute top-2 left-2 w-6 h-6 rounded-md flex items-center justify-center
720
+ ${isSelected ? "bg-blue-500 text-white" : "bg-white/80 border border-gray-300"}
721
+ ${isSelected ? "visible" : "invisible group-hover:visible"}
722
+ transition-opacity duration-200
723
+ `,
724
+ onClick: onSelect,
725
+ children: isSelected && /* @__PURE__ */ jsx(Check, { className: "h-4 w-4" })
726
+ }
727
+ )
728
+ ]
729
+ }
730
+ );
731
+ };
732
+ const SortableItem = ({ banner }) => {
733
+ const {
734
+ attributes,
735
+ listeners,
736
+ setNodeRef,
737
+ transform,
738
+ transition,
739
+ isDragging
740
+ } = useSortable({ id: banner.id });
741
+ const style = {
742
+ transform: CSS.Transform.toString(transform),
743
+ transition,
744
+ zIndex: isDragging ? 10 : 1,
745
+ opacity: isDragging ? 0.5 : 1
746
+ };
747
+ return /* @__PURE__ */ jsx(
748
+ "div",
749
+ {
750
+ ref: setNodeRef,
751
+ style,
752
+ className: "rounded-lg shadow-md border border-gray-200 hover:border-gray-300 transition-all relative",
753
+ ...attributes,
754
+ children: /* @__PURE__ */ jsxs("div", { ...listeners, children: [
755
+ /* @__PURE__ */ jsx(
756
+ "img",
757
+ {
758
+ src: banner.url,
759
+ alt: `Banner ${banner.id}`,
760
+ className: "object-cover w-full h-48 rounded-t-lg"
761
+ }
762
+ ),
763
+ /* @__PURE__ */ jsx("div", { className: "p-2 text-sm truncate", children: banner.id })
764
+ ] })
765
+ }
766
+ );
767
+ };
768
+ const UploadBannerDrawerContent = ({
769
+ maxFileSizeMb = 10,
770
+ onSuccess
771
+ }) => {
772
+ const [files, setFiles] = useState([]);
773
+ const [isLoading, setIsLoading] = useState(false);
774
+ const onDrop = useCallback((acceptedFiles) => {
775
+ const newFiles = acceptedFiles.map((file) => {
776
+ const id = crypto.randomUUID();
777
+ let preview;
778
+ if (file.type.startsWith("image/")) {
779
+ preview = URL.createObjectURL(file);
780
+ }
781
+ return {
782
+ file,
783
+ id,
784
+ preview
785
+ };
786
+ });
787
+ setFiles((prev) => [...prev, ...newFiles]);
788
+ }, []);
789
+ const removeFile = (id) => {
790
+ setFiles((files2) => {
791
+ const fileToRemove = files2.find((f) => f.id === id);
792
+ if (fileToRemove == null ? void 0 : fileToRemove.preview) {
793
+ URL.revokeObjectURL(fileToRemove.preview);
794
+ }
795
+ return files2.filter((f) => f.id !== id);
796
+ });
797
+ };
798
+ const handleResetFiles = () => {
799
+ setFiles([]);
800
+ };
801
+ const handleUploadFiles = async () => {
802
+ setIsLoading(true);
803
+ try {
804
+ const formData = new FormData();
805
+ files.forEach((file) => {
806
+ if (!file.file) {
807
+ return;
808
+ }
809
+ formData.append("files", file.file);
810
+ });
811
+ const { uploadedFiles } = await fetch("/admin/banners", {
812
+ method: "POST",
813
+ credentials: "include",
814
+ body: formData
815
+ }).then((res) => res.json());
816
+ onSuccess(uploadedFiles);
817
+ return {
818
+ files,
819
+ uploadedFiles
820
+ };
821
+ } catch (error) {
822
+ console.error(error);
823
+ alert("Error uploading files");
824
+ } finally {
825
+ setIsLoading(false);
826
+ }
827
+ };
828
+ const {
829
+ getRootProps,
830
+ getInputProps,
831
+ isDragActive,
832
+ isDragAccept,
833
+ isDragReject,
834
+ isFocused
835
+ } = useDropzone({
836
+ onDrop,
837
+ accept: {
838
+ "image/*": []
839
+ },
840
+ maxSize: maxFileSizeMb * 1024 * 1024
841
+ // maxFileSizeMb
842
+ });
843
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-6", children: [
844
+ /* @__PURE__ */ jsxs(
845
+ "div",
846
+ {
847
+ ...getRootProps(),
848
+ className: clx(
849
+ "border-2 border-dashed rounded-xl p-10 transition-all duration-150 ease-in-out cursor-pointer",
850
+ "flex flex-col items-center justify-center gap-4",
851
+ isDragActive ? "bg-primary/5 border-primary/50" : "bg-background hover:bg-muted/50",
852
+ isDragAccept ? "border-green-500 bg-green-50 dark:bg-green-950/20" : "",
853
+ isDragReject ? "border-red-500 bg-red-50 dark:bg-red-950/20" : "",
854
+ isFocused ? "ring-2 ring-ring ring-offset-2" : "",
855
+ "focus-visible:outline-none"
856
+ ),
857
+ children: [
858
+ /* @__PURE__ */ jsx("input", { ...getInputProps(), disabled: isLoading }),
859
+ /* @__PURE__ */ jsx("div", { className: "rounded-full bg-primary/10 p-4", children: /* @__PURE__ */ jsx(Upload, { className: "h-8 w-8 text-primary" }) }),
860
+ /* @__PURE__ */ jsxs("div", { className: "text-center space-y-2", children: [
861
+ /* @__PURE__ */ jsx("h3", { className: "font-medium text-lg", children: isDragActive ? isDragAccept ? "Drop files to upload" : "This file type is not supported" : "Drag & drop images here" }),
862
+ /* @__PURE__ */ jsxs("p", { className: "text-sm text-muted-foreground", children: [
863
+ "or ",
864
+ /* @__PURE__ */ jsx("span", { className: "text-primary font-medium", children: "browse images" }),
865
+ " ",
866
+ "from your computer"
867
+ ] }),
868
+ /* @__PURE__ */ jsxs("p", { className: "text-xs text-muted-foreground", children: [
869
+ "(max ",
870
+ maxFileSizeMb,
871
+ "MB)"
872
+ ] })
873
+ ] })
874
+ ]
875
+ }
876
+ ),
877
+ files.length > 0 && /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
878
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
879
+ /* @__PURE__ */ jsxs("div", { className: "inline flex items-center gap-4", children: [
880
+ /* @__PURE__ */ jsxs("h3", { className: "text-lg font-medium", children: [
881
+ "Selected Files (",
882
+ files.length,
883
+ ")"
884
+ ] }),
885
+ /* @__PURE__ */ jsx(
886
+ Button,
887
+ {
888
+ isLoading,
889
+ variant: "primary",
890
+ onClick: handleUploadFiles,
891
+ children: "Confirm Upload"
892
+ }
893
+ )
894
+ ] }),
895
+ /* @__PURE__ */ jsx(
896
+ Button,
897
+ {
898
+ isLoading,
899
+ variant: "secondary",
900
+ onClick: handleResetFiles,
901
+ children: "Clear All"
902
+ }
903
+ )
904
+ ] }),
905
+ /* @__PURE__ */ jsx("div", { className: "grid gap-4 grid-cols-2 sm:grid-cols-3", children: files.map((fileItem) => /* @__PURE__ */ jsxs(
906
+ "div",
907
+ {
908
+ className: "group relative rounded-lg border bg-card text-card-foreground shadow-sm overflow-hidden",
909
+ children: [
910
+ fileItem.preview ? /* @__PURE__ */ jsx("div", { className: "aspect-square w-full overflow-hidden", children: /* @__PURE__ */ jsx(
911
+ "img",
912
+ {
913
+ src: fileItem.preview || "/placeholder.svg",
914
+ alt: fileItem.file.name,
915
+ className: "h-full w-full object-contain transition-all hover:scale-105"
916
+ }
917
+ ) }) : /* @__PURE__ */ jsxs("div", { className: "aspect-square w-full flex items-center justify-center bg-muted/50", children: [
918
+ /* @__PURE__ */ jsx(ImageIcon, { className: "h-6 w-6 text-primary" }),
919
+ ";"
920
+ ] }),
921
+ /* @__PURE__ */ jsx("div", { className: "p-3", children: /* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between gap-2", children: [
922
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1 overflow-hidden", children: [
923
+ /* @__PURE__ */ jsx("p", { className: "truncate text-sm font-medium", children: fileItem.file.name }),
924
+ /* @__PURE__ */ jsxs("p", { className: "text-xs text-muted-foreground", children: [
925
+ (fileItem.file.size / 1024).toFixed(1),
926
+ " KB"
927
+ ] })
928
+ ] }),
929
+ /* @__PURE__ */ jsxs(
930
+ Button,
931
+ {
932
+ isLoading,
933
+ variant: "secondary",
934
+ className: "size-7 rounded-full p-0.5 text-red-400 shrink-0",
935
+ onClick: (e) => {
936
+ e.stopPropagation();
937
+ removeFile(fileItem.id);
938
+ },
939
+ children: [
940
+ /* @__PURE__ */ jsx(Trash, {}),
941
+ /* @__PURE__ */ jsx("span", { className: "sr-only", children: "Remove file" })
942
+ ]
943
+ }
944
+ )
945
+ ] }) })
946
+ ]
947
+ },
948
+ fileItem.id
949
+ )) })
950
+ ] })
951
+ ] });
952
+ };
953
+ const BannerPage = () => {
954
+ const [open, setOpen] = useState(false);
955
+ const [selectedImage, setSelectedImage] = useState(null);
956
+ const [selectedIds, setSelectedIds] = useState(/* @__PURE__ */ new Set());
957
+ const [reorderMode, setReorderMode] = useState(false);
958
+ const [orderedBanners, setOrderedBanners] = useState([]);
959
+ const [activeBanner, setActiveBanner] = useState(null);
960
+ const [isLoading, setIsLoading] = useState(false);
961
+ const dialog = usePrompt();
962
+ const { data: bannerData, refetch } = useBanners();
963
+ const { count = 0, banners = [] } = bannerData || {};
964
+ const sensors = useSensors(
965
+ useSensor(PointerSensor, {
966
+ activationConstraint: {
967
+ distance: 8
968
+ // 8px movement required before drag starts
969
+ }
970
+ }),
971
+ useSensor(KeyboardSensor, {
972
+ coordinateGetter: sortableKeyboardCoordinates
973
+ })
974
+ );
975
+ const toggleImageSelection = (e, id) => {
976
+ e.stopPropagation();
977
+ const newSelectedIds = new Set(selectedIds);
978
+ if (newSelectedIds.has(id)) {
979
+ newSelectedIds.delete(id);
980
+ } else {
981
+ newSelectedIds.add(id);
982
+ }
983
+ setSelectedIds(newSelectedIds);
984
+ };
985
+ const selectAll = () => {
986
+ const allIds = banners.map((banner) => banner.id);
987
+ setSelectedIds(new Set(allIds));
988
+ };
989
+ const deselectAll = () => {
990
+ setSelectedIds(/* @__PURE__ */ new Set());
991
+ };
992
+ const openLightbox = (banner) => {
993
+ setSelectedImage(banner);
994
+ };
995
+ const closeLightbox = () => {
996
+ setSelectedImage(null);
997
+ };
998
+ const toggleReorderMode = async () => {
999
+ if (reorderMode) {
1000
+ const confirmed = await dialog({
1001
+ title: "Save Changes?",
1002
+ description: "Do you want to save your reordering changes?",
1003
+ variant: "confirmation",
1004
+ confirmText: "Save",
1005
+ cancelText: "Discard"
1006
+ });
1007
+ if (confirmed) {
1008
+ saveNewOrder();
1009
+ } else {
1010
+ setOrderedBanners([]);
1011
+ setReorderMode(false);
1012
+ }
1013
+ } else {
1014
+ setOrderedBanners([...banners]);
1015
+ setReorderMode(true);
1016
+ }
1017
+ };
1018
+ const handleDragStart = (event) => {
1019
+ const { active } = event;
1020
+ const activeBanner2 = orderedBanners.find(
1021
+ (banner) => banner.id === active.id
1022
+ );
1023
+ if (activeBanner2) {
1024
+ setActiveBanner(activeBanner2);
1025
+ }
1026
+ };
1027
+ const handleDragEnd = (event) => {
1028
+ const { active, over } = event;
1029
+ if (over && active.id !== over.id) {
1030
+ setOrderedBanners((items) => {
1031
+ const oldIndex = items.findIndex((item) => item.id === active.id);
1032
+ const newIndex = items.findIndex((item) => item.id === over.id);
1033
+ return arrayMove(items, oldIndex, newIndex);
1034
+ });
1035
+ }
1036
+ setActiveBanner(null);
1037
+ };
1038
+ const saveNewOrder = async () => {
1039
+ try {
1040
+ await fetch("/admin/banners/reorder", {
1041
+ method: "POST",
1042
+ credentials: "include",
1043
+ headers: {
1044
+ "Content-Type": "application/json",
1045
+ Accept: "application/json"
1046
+ },
1047
+ body: JSON.stringify({
1048
+ ids: orderedBanners.map((banner) => banner.id)
1049
+ })
1050
+ });
1051
+ setReorderMode(false);
1052
+ refetch();
1053
+ } catch (error) {
1054
+ console.error("Failed to save new order:", error);
1055
+ }
1056
+ };
1057
+ const handleDeleteMultiple = async () => {
1058
+ const isConfirmed = await dialog({
1059
+ title: "Confirm Deletion",
1060
+ description: `Are you sure you want to delete ${selectedIds.size} selected images? This action cannot be undone.`,
1061
+ confirmText: "Delete",
1062
+ cancelText: "Cancel"
1063
+ });
1064
+ if (!isConfirmed) return;
1065
+ const ids2 = Array.from(selectedIds);
1066
+ setIsLoading(true);
1067
+ try {
1068
+ await fetch("/admin/banners", {
1069
+ method: "DELETE",
1070
+ credentials: "include",
1071
+ headers: {
1072
+ "Content-Type": "application/json",
1073
+ Accept: "application/json"
1074
+ },
1075
+ body: JSON.stringify({ ids: ids2 })
1076
+ });
1077
+ setSelectedIds(/* @__PURE__ */ new Set());
1078
+ refetch();
1079
+ } catch {
1080
+ console.error("Failed to delete banners");
1081
+ } finally {
1082
+ setIsLoading(false);
1083
+ }
1084
+ };
1085
+ return /* @__PURE__ */ jsx("div", { className: "space-y-6", children: /* @__PURE__ */ jsxs("div", { className: "container mx-auto px-4 py-8", children: [
1086
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between mb-6", children: [
1087
+ /* @__PURE__ */ jsxs("h2", { className: "text-2xl font-bold", children: [
1088
+ "Banners (",
1089
+ count,
1090
+ ")"
1091
+ ] }),
1092
+ /* @__PURE__ */ jsxs(
1093
+ Drawer,
1094
+ {
1095
+ open,
1096
+ onOpenChange: (openChanged) => setOpen(openChanged),
1097
+ children: [
1098
+ /* @__PURE__ */ jsx(
1099
+ Drawer.Trigger,
1100
+ {
1101
+ onClick: () => {
1102
+ setOpen(true);
1103
+ },
1104
+ asChild: true,
1105
+ children: /* @__PURE__ */ jsx(Button, { isLoading, children: "Upload new banner" })
1106
+ }
1107
+ ),
1108
+ /* @__PURE__ */ jsxs(Drawer.Content, { children: [
1109
+ /* @__PURE__ */ jsx(Drawer.Header, { children: /* @__PURE__ */ jsx(Drawer.Title, { children: "Upload new banner" }) }),
1110
+ /* @__PURE__ */ jsx(Drawer.Body, { children: /* @__PURE__ */ jsx(
1111
+ UploadBannerDrawerContent,
1112
+ {
1113
+ maxFileSizeMb: 10,
1114
+ onSuccess: () => {
1115
+ setOpen(false);
1116
+ refetch();
1117
+ }
1118
+ }
1119
+ ) })
1120
+ ] })
1121
+ ]
1122
+ }
1123
+ )
1124
+ ] }),
1125
+ /* @__PURE__ */ jsxs("div", { className: "container mx-auto px-4 py-8", children: [
1126
+ /* @__PURE__ */ jsxs("div", { className: "flex justify-between items-center mb-6", children: [
1127
+ selectedIds.size > 0 && !reorderMode && /* @__PURE__ */ jsx("div", { className: "flex gap-2", children: /* @__PURE__ */ jsxs(Fragment, { children: [
1128
+ /* @__PURE__ */ jsx(
1129
+ Button,
1130
+ {
1131
+ isLoading,
1132
+ onClick: selectAll,
1133
+ disabled: count === 0,
1134
+ children: "Select All"
1135
+ }
1136
+ ),
1137
+ /* @__PURE__ */ jsx(
1138
+ Button,
1139
+ {
1140
+ isLoading,
1141
+ onClick: deselectAll,
1142
+ disabled: selectedIds.size === 0,
1143
+ children: "Deselect All"
1144
+ }
1145
+ ),
1146
+ /* @__PURE__ */ jsxs(
1147
+ Button,
1148
+ {
1149
+ isLoading,
1150
+ variant: "danger",
1151
+ onClick: () => handleDeleteMultiple(),
1152
+ disabled: selectedIds.size === 0,
1153
+ children: [
1154
+ /* @__PURE__ */ jsx(Trash2, { className: "h-4 w-4" }),
1155
+ "Delete (",
1156
+ selectedIds.size,
1157
+ ")"
1158
+ ]
1159
+ }
1160
+ )
1161
+ ] }) }),
1162
+ /* @__PURE__ */ jsxs("div", { className: "ml-auto", children: [
1163
+ banners.length > 0 && /* @__PURE__ */ jsxs(
1164
+ Button,
1165
+ {
1166
+ isLoading,
1167
+ onClick: toggleReorderMode,
1168
+ variant: reorderMode ? "secondary" : "primary",
1169
+ children: [
1170
+ /* @__PURE__ */ jsx(MoveHorizontal, { className: "h-4 w-4 mr-2" }),
1171
+ reorderMode ? "Exit Reorder" : "Reorder"
1172
+ ]
1173
+ }
1174
+ ),
1175
+ reorderMode && /* @__PURE__ */ jsxs(
1176
+ Button,
1177
+ {
1178
+ isLoading,
1179
+ onClick: saveNewOrder,
1180
+ variant: "primary",
1181
+ className: "ml-2",
1182
+ children: [
1183
+ /* @__PURE__ */ jsx(Check, { className: "h-4 w-4 mr-2" }),
1184
+ "Save Order"
1185
+ ]
1186
+ }
1187
+ )
1188
+ ] })
1189
+ ] }),
1190
+ reorderMode ? /* @__PURE__ */ jsxs(
1191
+ DndContext,
1192
+ {
1193
+ sensors,
1194
+ collisionDetection: closestCenter,
1195
+ onDragStart: handleDragStart,
1196
+ onDragEnd: handleDragEnd,
1197
+ children: [
1198
+ /* @__PURE__ */ jsx(
1199
+ SortableContext,
1200
+ {
1201
+ items: orderedBanners.map((banner) => banner.id),
1202
+ strategy: rectSortingStrategy,
1203
+ children: /* @__PURE__ */ jsx("div", { className: "grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4 isolate", children: orderedBanners.map((banner) => /* @__PURE__ */ jsx(SortableItem, { banner }, banner.id)) })
1204
+ }
1205
+ ),
1206
+ /* @__PURE__ */ jsx(DragOverlay, { children: activeBanner ? /* @__PURE__ */ jsx(DraggableItem, { banner: activeBanner }) : null })
1207
+ ]
1208
+ }
1209
+ ) : /* @__PURE__ */ jsx("div", { className: "grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4 group", children: banners.map((banner) => /* @__PURE__ */ jsx(
1210
+ GridItem,
1211
+ {
1212
+ banner,
1213
+ isSelected: selectedIds.has(banner.id),
1214
+ onClick: () => openLightbox(banner),
1215
+ onSelect: (e) => toggleImageSelection(e, banner.id)
1216
+ },
1217
+ banner.id
1218
+ )) }),
1219
+ selectedImage && /* @__PURE__ */ jsxs(
1220
+ "div",
1221
+ {
1222
+ className: "fixed inset-0 bg-black/80 z-50 flex items-center justify-center p-4",
1223
+ onClick: closeLightbox,
1224
+ children: [
1225
+ /* @__PURE__ */ jsx(
1226
+ Button,
1227
+ {
1228
+ isLoading,
1229
+ className: "absolute top-4 right-4 text-white bg-black/50 p-2 rounded-full hover:bg-black/70",
1230
+ onClick: (e) => {
1231
+ e.stopPropagation();
1232
+ closeLightbox();
1233
+ },
1234
+ children: /* @__PURE__ */ jsx(X, { className: "h-6 w-6" })
1235
+ }
1236
+ ),
1237
+ /* @__PURE__ */ jsx(
1238
+ "div",
1239
+ {
1240
+ className: "relative max-w-screen-xl max-h-screen overflow-auto",
1241
+ onClick: (e) => e.stopPropagation(),
1242
+ children: /* @__PURE__ */ jsx(
1243
+ "img",
1244
+ {
1245
+ src: selectedImage.url || "/placeholder.svg",
1246
+ alt: `Banner ${selectedImage.id}`,
1247
+ className: "max-h-[90vh] max-w-full object-contain mx-auto"
1248
+ }
1249
+ )
1250
+ }
1251
+ )
1252
+ ]
1253
+ }
1254
+ )
1255
+ ] })
1256
+ ] }) });
1257
+ };
1258
+ const BannerUpload = () => {
1259
+ return /* @__PURE__ */ jsx(BannerPage, {});
1260
+ };
1261
+ const config = defineRouteConfig({
1262
+ label: "Banner",
1263
+ icon: StackPerspective
1264
+ });
1265
+ const widgetModule = { widgets: [] };
1266
+ const routeModule = {
1267
+ routes: [
1268
+ {
1269
+ Component: BannerUpload,
1270
+ path: "/banners"
1271
+ }
1272
+ ]
1273
+ };
1274
+ const menuItemModule = {
1275
+ menuItems: [
1276
+ {
1277
+ label: config.label,
1278
+ icon: config.icon,
1279
+ path: "/banners",
1280
+ nested: void 0
1281
+ }
1282
+ ]
1283
+ };
1284
+ const formModule = { customFields: {} };
1285
+ const displayModule = {
1286
+ displays: {}
1287
+ };
1288
+ const plugin = {
1289
+ widgetModule,
1290
+ routeModule,
1291
+ menuItemModule,
1292
+ formModule,
1293
+ displayModule
1294
+ };
1295
+ export {
1296
+ plugin as default
1297
+ };