返回 presentation-ai
media-preview-dialog.tsx
根目录 / src / components / plate / ui / media-preview-dialog.tsx
1 "use client";
2
3 import {
4 PreviewImage,
5 useImagePreview,
6 useImagePreviewValue,
7 useScaleInput,
8 } from "@platejs/media/react";
9 import { cva } from "class-variance-authority";
10 import { ArrowLeft, ArrowRight, Download, Minus, Plus, X } from "lucide-react";
11 import { useEditorRef } from "platejs/react";
12
13 import { cn } from "@/lib/utils";
14
15 const buttonVariants = cva("rounded bg-[rgba(0,0,0,0.5)] px-1", {
16 defaultVariants: {
17 variant: "default",
18 },
19 variants: {
20 variant: {
21 default: "text-white",
22 disabled: "cursor-not-allowed text-gray-400",
23 },
24 },
25 });
26
27 const SCROLL_SPEED = 4;
28
29 export function MediaPreviewDialog() {
30 const editor = useEditorRef();
31 const isOpen = useImagePreviewValue("isOpen", editor.id);
32 const scale = useImagePreviewValue("scale");
33 const isEditingScale = useImagePreviewValue("isEditingScale");
34 const {
35 closeProps,
36 currentUrlIndex,
37 maskLayerProps,
38 nextDisabled,
39 nextProps,
40 prevDisabled,
41 prevProps,
42 scaleTextProps,
43 zommOutProps,
44 zoomInDisabled,
45 zoomInProps,
46 zoomOutDisabled,
47 } = useImagePreview({ scrollSpeed: SCROLL_SPEED });
48
49 return (
50 <div
51 className={cn(
52 "fixed top-0 left-0 z-50 h-screen w-screen select-none",
53 !isOpen && "hidden",
54 )}
55 onContextMenu={(e) => e.stopPropagation()}
56 {...maskLayerProps}
57 >
58 <div className="absolute inset-0 size-full bg-gray-950 opacity-30"></div>
59 <div className="absolute inset-0 size-full bg-gray-950 opacity-30"></div>
60 <div className="absolute inset-0 flex items-center justify-center">
61 <div className="relative flex max-h-screen w-full items-center">
62 <PreviewImage
63 className={cn(
64 "mx-auto block max-h-[calc(100vh-4rem)] w-auto object-contain transition-transform",
65 )}
66 />
67 <div
68 className="absolute bottom-0 left-1/2 z-40 flex w-fit -translate-x-1/2 justify-center gap-4 p-2 text-center text-white"
69 onClick={(e) => e.stopPropagation()}
70 >
71 <div className="flex gap-1">
72 <button
73 {...prevProps}
74 className={cn(
75 buttonVariants({
76 variant: prevDisabled ? "disabled" : "default",
77 }),
78 )}
79 type="button"
80 >
81 <ArrowLeft />
82 </button>
83 {(currentUrlIndex ?? 0) + 1}
84 <button
85 {...nextProps}
86 className={cn(
87 buttonVariants({
88 variant: nextDisabled ? "disabled" : "default",
89 }),
90 )}
91 type="button"
92 >
93 <ArrowRight />
94 </button>
95 </div>
96 <div className="flex">
97 <button
98 className={cn(
99 buttonVariants({
100 variant: zoomOutDisabled ? "disabled" : "default",
101 }),
102 )}
103 {...zommOutProps}
104 type="button"
105 >
106 <Minus className="size-4" />
107 </button>
108 <div className="mx-px">
109 {isEditingScale ? (
110 <>
111 <ScaleInput className="w-10 rounded px-1 text-slate-500 outline-solid" />{" "}
112 <span>%</span>
113 </>
114 ) : (
115 <span {...scaleTextProps}>{scale * 100 + "%"}</span>
116 )}
117 </div>
118 <button
119 className={cn(
120 buttonVariants({
121 variant: zoomInDisabled ? "disabled" : "default",
122 }),
123 )}
124 {...zoomInProps}
125 type="button"
126 >
127 <Plus className="size-4" />
128 </button>
129 </div>
130 {/* TODO: downLoad the image */}
131 <button className={cn(buttonVariants())} type="button">
132 <Download className="size-4" />
133 </button>
134 <button
135 {...closeProps}
136 className={cn(buttonVariants())}
137 type="button"
138 >
139 <X className="size-4" />
140 </button>
141 </div>
142 </div>
143 </div>
144 </div>
145 );
146 }
147
148 function ScaleInput(props: React.ComponentProps<"input">) {
149 const { props: scaleInputProps, ref } = useScaleInput();
150
151 return <input {...scaleInputProps} {...props} ref={ref} />;
152 }
153
153 lines Plain Text