Skip to content

Commit 6d5d670

Browse files
committed
Merge branch 'ExtractCast'
2 parents 97adb4f + 0093009 commit 6d5d670

40 files changed

Lines changed: 3442 additions & 543 deletions

components/ImagePreviewDialog.tsx

Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
import React from "react";
2+
import {
3+
Box,
4+
Button,
5+
Dialog,
6+
DialogActions,
7+
DialogContent,
8+
DialogTitle,
9+
IconButton,
10+
Typography,
11+
} from "@mui/material";
12+
import CloseIcon from "@mui/icons-material/Close";
13+
import { ImageRecord } from "../types";
14+
15+
export interface ImagePreviewDialogProps {
16+
open: boolean;
17+
images: ImageRecord[];
18+
onClose: () => void;
19+
}
20+
21+
const getGridTemplateColumns = (imageCount: number) => {
22+
if (imageCount <= 1) {
23+
return "minmax(0, 1fr)";
24+
}
25+
26+
return "repeat(2, minmax(0, 1fr))";
27+
};
28+
29+
const getGridTemplateRows = (imageCount: number) => {
30+
if (imageCount <= 2) {
31+
return "minmax(0, 1fr)";
32+
}
33+
34+
return "repeat(2, minmax(0, 1fr))";
35+
};
36+
37+
export const ImagePreviewDialog: React.FC<ImagePreviewDialogProps> = ({
38+
open,
39+
images,
40+
onClose,
41+
}) => {
42+
const visibleImages = React.useMemo(() => images.slice(-4), [images]);
43+
44+
return (
45+
<Dialog
46+
open={open && visibleImages.length > 0}
47+
onClose={onClose}
48+
fullScreen
49+
PaperProps={{
50+
"data-testid": "image-preview-dialog",
51+
sx: {
52+
backgroundColor: "#06080d",
53+
color: "#f8fafc",
54+
},
55+
}}
56+
>
57+
<DialogTitle sx={{ px: { xs: 2, sm: 3 }, py: 2, pr: 8, position: "relative" }}>
58+
<Typography component="span" variant="h6" sx={{ fontWeight: 600 }}>
59+
{visibleImages.length === 1
60+
? "Image preview"
61+
: `${visibleImages.length} image preview`}
62+
</Typography>
63+
<IconButton
64+
aria-label="Close image preview"
65+
onClick={onClose}
66+
data-testid="image-preview-dialog-close"
67+
sx={{
68+
position: "absolute",
69+
right: 12,
70+
top: 12,
71+
color: "inherit",
72+
}}
73+
>
74+
<CloseIcon />
75+
</IconButton>
76+
</DialogTitle>
77+
78+
<DialogContent
79+
sx={{
80+
px: { xs: 2, sm: 3 },
81+
py: 0,
82+
display: "flex",
83+
minHeight: 0,
84+
}}
85+
>
86+
<Box
87+
sx={{
88+
display: "grid",
89+
gridTemplateColumns: {
90+
xs: "minmax(0, 1fr)",
91+
sm: getGridTemplateColumns(visibleImages.length),
92+
},
93+
gridTemplateRows: {
94+
xs: `repeat(${visibleImages.length}, minmax(0, 1fr))`,
95+
sm: getGridTemplateRows(visibleImages.length),
96+
},
97+
gap: { xs: 2, sm: 3 },
98+
width: "100%",
99+
height: "100%",
100+
minHeight: 0,
101+
}}
102+
>
103+
{visibleImages.map((image, index) => {
104+
const resolution = image.resolution
105+
? `${image.resolution.width} x ${image.resolution.height}`
106+
: null;
107+
108+
return (
109+
<Box
110+
key={image.id}
111+
data-testid={`image-preview-dialog-item-${index}`}
112+
sx={{
113+
minWidth: 0,
114+
minHeight: 0,
115+
display: "flex",
116+
alignItems: "center",
117+
justifyContent: "center",
118+
borderRadius: 3,
119+
backgroundColor: "rgba(15, 23, 42, 0.68)",
120+
overflow: "hidden",
121+
position: "relative",
122+
}}
123+
>
124+
<Box
125+
sx={{
126+
width: "100%",
127+
height: "100%",
128+
display: "flex",
129+
alignItems: "center",
130+
justifyContent: "center",
131+
p: { xs: 1.5, sm: 2 },
132+
minWidth: 0,
133+
minHeight: 0,
134+
}}
135+
>
136+
<img
137+
src={image.imageData}
138+
alt={image.imageFileName || `Preview image ${index + 1}`}
139+
draggable={false}
140+
style={{
141+
display: "block",
142+
width: "auto",
143+
height: "auto",
144+
maxWidth: "100%",
145+
maxHeight: "100%",
146+
objectFit: "contain",
147+
}}
148+
/>
149+
</Box>
150+
{resolution && (
151+
<Typography
152+
variant="caption"
153+
sx={{
154+
position: "absolute",
155+
left: 12,
156+
bottom: 12,
157+
px: 1,
158+
py: 0.5,
159+
borderRadius: 999,
160+
backgroundColor: "rgba(6, 8, 13, 0.76)",
161+
color: "#e2e8f0",
162+
}}
163+
>
164+
{resolution}
165+
</Typography>
166+
)}
167+
</Box>
168+
);
169+
})}
170+
</Box>
171+
</DialogContent>
172+
173+
<DialogActions sx={{ px: { xs: 2, sm: 3 }, py: 2.5, justifyContent: "center" }}>
174+
<Button onClick={onClose} variant="contained" color="inherit">
175+
Close
176+
</Button>
177+
</DialogActions>
178+
</Dialog>
179+
);
180+
};

components/ImageSlot.tsx

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import React from "react";
22
import imagePlaceholder from "../assets/image_placeholder.svg";
3-
import { GenerationProgressState, ImageRecord } from "../types";
3+
import { GenerationProgressState, ImageRecord, ImageSlotActionKey } from "../types";
44
import { MagnifiableImage } from "./MagnifiableImage";
5+
import { Icon, Icons } from "./Icons";
56
import { kPrimary, theme } from "../themes";
67
import { ImageSlotHeader } from "./ImageSlotHeader";
78
import { ImageSlotActions, ImageSlotActionsHandle } from "./ImageSlotActions";
@@ -70,6 +71,8 @@ export interface ImageSlotProps {
7071
disabled?: boolean;
7172
isDropZone?: boolean;
7273
onClick?: () => void;
74+
previewModifierActive?: boolean;
75+
previewSelected?: boolean;
7376
isSelected?: boolean;
7477
onDrop?: (imageId: string) => void;
7578
onUpload?: (file: File) => void;
@@ -87,6 +90,7 @@ export interface ImageSlotProps {
8790
dropLabel?: string;
8891
dataTestId?: string;
8992
actionLabels?: Partial<Record<keyof ImageSlotControls, string>>;
93+
actionDisabledReasons?: Partial<Record<ImageSlotActionKey, string>>;
9094
removeIcon?: string;
9195
starState?: { isStarred: boolean; onToggle: () => void };
9296
isAnyDndDragging?: boolean;
@@ -240,6 +244,8 @@ export const ImageSlot: React.FC<ImageSlotProps> = ({
240244
disabled = false,
241245
isDropZone = false,
242246
onClick,
247+
previewModifierActive = false,
248+
previewSelected = false,
243249
isSelected = false,
244250
onDrop,
245251
onUpload,
@@ -257,6 +263,7 @@ export const ImageSlot: React.FC<ImageSlotProps> = ({
257263
dropLabel = "Drop image",
258264
dataTestId,
259265
actionLabels,
266+
actionDisabledReasons,
260267
removeIcon,
261268
starState,
262269
isAnyDndDragging: isAnyDndDraggingProp = false,
@@ -643,6 +650,7 @@ export const ImageSlot: React.FC<ImageSlotProps> = ({
643650
supportsUpload={!!onUpload}
644651
supportsRemove={!!onRemove}
645652
actionLabels={actionLabels}
653+
actionDisabledReasons={actionDisabledReasons}
646654
removeIcon={removeIcon}
647655
iconSize={ACTION_ICON_SIZE}
648656
buttonPadding={ACTION_BUTTON_PADDING}
@@ -710,7 +718,12 @@ export const ImageSlot: React.FC<ImageSlotProps> = ({
710718
? 1
711719
: 0.8
712720
: 1,
713-
cursor: !disabled && (onClick || variant === "thumb") ? "pointer" : "default",
721+
cursor:
722+
!disabled && variant === "thumb" && !!image && previewModifierActive
723+
? "zoom-in"
724+
: !disabled && (onClick || variant === "thumb")
725+
? "pointer"
726+
: "default",
714727
pointerEvents: disabled ? "none" : "auto",
715728
filter: disabled ? "grayscale(1)" : "none",
716729
borderColor: isDragOver
@@ -795,6 +808,37 @@ export const ImageSlot: React.FC<ImageSlotProps> = ({
795808
emptyStateContent
796809
)}
797810

811+
{variant === "thumb" && image && previewSelected ? (
812+
<div
813+
data-testid="preview-selection-indicator"
814+
style={{
815+
position: "absolute",
816+
inset: 0,
817+
display: "flex",
818+
alignItems: "center",
819+
justifyContent: "center",
820+
backgroundColor: "rgba(7, 12, 20, 0.34)",
821+
pointerEvents: "none",
822+
}}
823+
>
824+
<div
825+
style={{
826+
width: 52,
827+
height: 52,
828+
borderRadius: "50%",
829+
display: "flex",
830+
alignItems: "center",
831+
justifyContent: "center",
832+
backgroundColor: "rgba(255, 255, 255, 0.92)",
833+
boxShadow: "0 12px 28px rgba(15, 23, 42, 0.32)",
834+
color: theme.colors.textPrimary,
835+
}}
836+
>
837+
<Icon path={Icons.Magnifier} width={28} height={28} />
838+
</div>
839+
</div>
840+
) : null}
841+
798842
<ImageSlotActions
799843
ref={thumbActionsRef}
800844
placement="overlay"
@@ -806,6 +850,7 @@ export const ImageSlot: React.FC<ImageSlotProps> = ({
806850
supportsUpload={!!onUpload}
807851
supportsRemove={!!onRemove}
808852
actionLabels={actionLabels}
853+
actionDisabledReasons={actionDisabledReasons}
809854
removeIcon={removeIcon}
810855
iconSize={ACTION_ICON_SIZE}
811856
buttonPadding={ACTION_BUTTON_PADDING}

0 commit comments

Comments
 (0)