mirror of
https://github.com/storytold/artcraft.git
synced 2026-10-21 01:12:40 -04:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5f6d8f607d |
@@ -42,11 +42,13 @@ export const PopoverMenu = ({
|
||||
}: PopoverMenuProps) => {
|
||||
const selectedItem = items.find((item) => item.selected);
|
||||
|
||||
const handleItemClick = (item: PopoverItem) => {
|
||||
const handleItemClick = (item: PopoverItem, close: () => void) => {
|
||||
if (mode === "button" && item.action && onAction) {
|
||||
onAction(item.action);
|
||||
close();
|
||||
} else {
|
||||
onSelect?.(item);
|
||||
close();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -60,67 +62,75 @@ export const PopoverMenu = ({
|
||||
|
||||
return (
|
||||
<Popover className="relative">
|
||||
<Popover.Button className={className}>
|
||||
{triggerIcon}
|
||||
{mode === "toggle" && selectedItem ? selectedItem.label : null}
|
||||
{mode === "default" && triggerLabel ? triggerLabel : null}
|
||||
</Popover.Button>
|
||||
{({ close }) => (
|
||||
<>
|
||||
<Popover.Button className={className}>
|
||||
{triggerIcon}
|
||||
{mode === "toggle" && selectedItem ? selectedItem.label : null}
|
||||
{mode === "default" && triggerLabel ? triggerLabel : null}
|
||||
</Popover.Button>
|
||||
|
||||
<div className="absolute bottom-full left-0 transform-gpu">
|
||||
<Transition
|
||||
enter="transition duration-100 ease-out"
|
||||
enterFrom="translate-y-2 opacity-0"
|
||||
enterTo="translate-y-0 opacity-100"
|
||||
leave="transition duration-100 ease-in"
|
||||
leaveFrom="translate-y-0 opacity-100"
|
||||
leaveTo="translate-y-2 opacity-0"
|
||||
>
|
||||
<Popover.Panel className="origin-bottom">
|
||||
<div
|
||||
className={twMerge(
|
||||
"z-10 mb-2 min-w-48 rounded-lg bg-[#46464B] p-1.5 shadow-lg",
|
||||
)}
|
||||
<div className="absolute bottom-full left-0 transform-gpu">
|
||||
<Transition
|
||||
enter="transition duration-100 ease-out"
|
||||
enterFrom="translate-y-2 opacity-0"
|
||||
enterTo="translate-y-0 opacity-100"
|
||||
leave="transition duration-100 ease-in"
|
||||
leaveFrom="translate-y-0 opacity-100"
|
||||
leaveTo="translate-y-2 opacity-0"
|
||||
>
|
||||
{panelTitle && (
|
||||
<div className="mb-2 mt-0.5 px-1.5 text-sm font-normal text-white opacity-70">
|
||||
{panelTitle}
|
||||
</div>
|
||||
)}
|
||||
{mode === "default" && children ? (
|
||||
<div className="text-sm text-white">{children}</div>
|
||||
) : (
|
||||
<div className="flex flex-col gap-0 text-sm text-white">
|
||||
{items.map((item, index) => (
|
||||
<Button
|
||||
key={index}
|
||||
className={`flex items-center justify-between border-transparent bg-transparent px-1.5 hover:bg-[#63636B]/60 ${mode === "toggle" && item.selected ? "hover:bg-[#63636B]" : "bg-transparent"}`}
|
||||
onClick={() => handleItemClick(item)}
|
||||
variant="secondary"
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
{showIconsInList && item.icon}
|
||||
{item.label}
|
||||
</div>
|
||||
{mode === "toggle" && (
|
||||
<input type="radio" checked={item.selected} readOnly />
|
||||
<Popover.Panel className="origin-bottom">
|
||||
<div
|
||||
className={twMerge(
|
||||
"z-10 mb-2 min-w-48 rounded-lg bg-[#46464B] p-1.5 shadow-lg",
|
||||
)}
|
||||
>
|
||||
{panelTitle && (
|
||||
<div className="mb-2 mt-0.5 px-1.5 text-sm font-normal text-white opacity-70">
|
||||
{panelTitle}
|
||||
</div>
|
||||
)}
|
||||
{mode === "default" && children ? (
|
||||
<div className="text-sm text-white">{children}</div>
|
||||
) : (
|
||||
<div className="flex flex-col gap-0 text-sm text-white">
|
||||
{items.map((item, index) => (
|
||||
<Button
|
||||
key={index}
|
||||
className={`flex items-center justify-between border-transparent bg-transparent px-1.5 hover:bg-[#63636B]/60 ${mode === "toggle" && item.selected ? "hover:bg-[#63636B]" : "bg-transparent"}`}
|
||||
onClick={() => handleItemClick(item, close)}
|
||||
variant="secondary"
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
{showIconsInList && item.icon}
|
||||
{item.label}
|
||||
</div>
|
||||
{mode === "toggle" && (
|
||||
<input
|
||||
type="radio"
|
||||
checked={item.selected}
|
||||
readOnly
|
||||
/>
|
||||
)}
|
||||
</Button>
|
||||
))}
|
||||
{showAddButton && onAdd && (
|
||||
<Button
|
||||
variant="secondary"
|
||||
className="mb-0.5 mt-2 border-none bg-[#7B7B84] py-1 hover:bg-[#8c8c96]"
|
||||
onClick={onAdd}
|
||||
>
|
||||
+ Add
|
||||
</Button>
|
||||
)}
|
||||
</Button>
|
||||
))}
|
||||
{showAddButton && onAdd && (
|
||||
<Button
|
||||
variant="secondary"
|
||||
className="mb-0.5 mt-2 border-none bg-[#7B7B84] py-1 hover:bg-[#8c8c96]"
|
||||
onClick={onAdd}
|
||||
>
|
||||
+ Add
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Popover.Panel>
|
||||
</Transition>
|
||||
</div>
|
||||
</Popover.Panel>
|
||||
</Transition>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</Popover>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -334,6 +334,20 @@ class Editor {
|
||||
aspectRatio: 16 / 9,
|
||||
};
|
||||
}
|
||||
case CameraAspectRatio.HORIZONTAL_3_2: {
|
||||
return {
|
||||
width: 1200,
|
||||
height: 800,
|
||||
aspectRatio: 3 / 2,
|
||||
};
|
||||
}
|
||||
case CameraAspectRatio.VERTICAL_2_3: {
|
||||
return {
|
||||
width: 800,
|
||||
height: 1200,
|
||||
aspectRatio: 2 / 3,
|
||||
};
|
||||
}
|
||||
case CameraAspectRatio.VERTICAL_9_16: {
|
||||
return {
|
||||
width: 720,
|
||||
@@ -948,16 +962,28 @@ class Editor {
|
||||
this.render_camera_aspect_ratio === CameraAspectRatio.HORIZONTAL_16_9
|
||||
? 1024
|
||||
: this.render_camera_aspect_ratio ===
|
||||
CameraAspectRatio.VERTICAL_9_16
|
||||
CameraAspectRatio.VERTICAL_9_16
|
||||
? 576
|
||||
: 1000;
|
||||
: this.render_camera_aspect_ratio ===
|
||||
CameraAspectRatio.HORIZONTAL_3_2
|
||||
? 900
|
||||
: this.render_camera_aspect_ratio ===
|
||||
CameraAspectRatio.VERTICAL_2_3
|
||||
? 600
|
||||
: 1000;
|
||||
const height =
|
||||
this.render_camera_aspect_ratio === CameraAspectRatio.HORIZONTAL_16_9
|
||||
? 576
|
||||
: this.render_camera_aspect_ratio ===
|
||||
CameraAspectRatio.VERTICAL_9_16
|
||||
CameraAspectRatio.VERTICAL_9_16
|
||||
? 1024
|
||||
: 1000;
|
||||
: this.render_camera_aspect_ratio ===
|
||||
CameraAspectRatio.HORIZONTAL_3_2
|
||||
? 600
|
||||
: this.render_camera_aspect_ratio ===
|
||||
CameraAspectRatio.VERTICAL_2_3
|
||||
? 900
|
||||
: 1000;
|
||||
|
||||
this.rawRenderer.setSize(width, height);
|
||||
|
||||
|
||||
@@ -113,7 +113,6 @@ export const PageEditor = () => {
|
||||
<div className="grid grid-cols-3 gap-4">
|
||||
<ControlsTopButtons />
|
||||
<Controls3D />
|
||||
<AspectRatioMenu />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -18,13 +18,21 @@ export const AspectRatioMenu = () => {
|
||||
? faRectangle
|
||||
: cameraAspectRatio.value === CameraAspectRatio.VERTICAL_9_16
|
||||
? faRectangleVertical
|
||||
: faSquare;
|
||||
: cameraAspectRatio.value === CameraAspectRatio.HORIZONTAL_3_2
|
||||
? faRectangle
|
||||
: cameraAspectRatio.value === CameraAspectRatio.VERTICAL_2_3
|
||||
? faRectangleVertical
|
||||
: faSquare;
|
||||
const buttonText =
|
||||
cameraAspectRatio.value === CameraAspectRatio.HORIZONTAL_16_9
|
||||
? "16:9 Horizontal"
|
||||
: cameraAspectRatio.value === CameraAspectRatio.VERTICAL_9_16
|
||||
? "9:16 Vertical"
|
||||
: "1:1 Squared";
|
||||
: cameraAspectRatio.value === CameraAspectRatio.HORIZONTAL_3_2
|
||||
? "3:2 Horizontal"
|
||||
: cameraAspectRatio.value === CameraAspectRatio.VERTICAL_2_3
|
||||
? "2:3 Vertical"
|
||||
: "1:1 Squared";
|
||||
|
||||
const handleChangeAspectRatio = (newRatio: CameraAspectRatio) => {
|
||||
Queue.publish({
|
||||
@@ -52,6 +60,26 @@ export const AspectRatioMenu = () => {
|
||||
onClick: () =>
|
||||
handleChangeAspectRatio(CameraAspectRatio.HORIZONTAL_16_9),
|
||||
},
|
||||
{
|
||||
label: "3:2",
|
||||
icon: faRectangle,
|
||||
className: "pl-4",
|
||||
description: "Horizontal",
|
||||
selected:
|
||||
cameraAspectRatio.value === CameraAspectRatio.HORIZONTAL_3_2,
|
||||
onClick: () =>
|
||||
handleChangeAspectRatio(CameraAspectRatio.HORIZONTAL_3_2),
|
||||
},
|
||||
{
|
||||
label: "2:3",
|
||||
icon: faRectangleVertical,
|
||||
className: "pl-4",
|
||||
description: "Vertical",
|
||||
selected:
|
||||
cameraAspectRatio.value === CameraAspectRatio.VERTICAL_2_3,
|
||||
onClick: () =>
|
||||
handleChangeAspectRatio(CameraAspectRatio.VERTICAL_2_3),
|
||||
},
|
||||
{
|
||||
label: "9:16",
|
||||
icon: faRectangleVertical,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useState, useRef, useEffect } from "react";
|
||||
import { useSignals } from "@preact/signals-react/runtime";
|
||||
|
||||
import {
|
||||
faPlus,
|
||||
@@ -10,18 +11,46 @@ import {
|
||||
faDownload,
|
||||
faSpinnerThird,
|
||||
} from "@fortawesome/pro-solid-svg-icons";
|
||||
import {
|
||||
faRectangleWide,
|
||||
faRectangleVertical,
|
||||
faSquare,
|
||||
faRectangle,
|
||||
} from "@fortawesome/pro-regular-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { PopoverItem, PopoverMenu } from "~/components/reusable/Popover";
|
||||
import { Button } from "~/components";
|
||||
import { CameraAspectRatio } from "~/pages/PageEnigma/enums";
|
||||
import { cameraAspectRatio } from "~/pages/PageEnigma/signals";
|
||||
import { QueueNames } from "~/pages/PageEnigma/Queue/QueueNames";
|
||||
import Queue from "~/pages/PageEnigma/Queue/Queue";
|
||||
import { toEngineActions } from "~/pages/PageEnigma/Queue/toEngineActions";
|
||||
|
||||
export const PromptBox = () => {
|
||||
useSignals();
|
||||
const [prompt, setPrompt] = useState("");
|
||||
const [isEnqueueing, setisEnqueueing] = useState(false);
|
||||
const [aspectRatioList, setAspectRatioList] = useState<PopoverItem[]>([
|
||||
{ label: "16:9", selected: true },
|
||||
{ label: "3:2", selected: false },
|
||||
{ label: "2:3", selected: false },
|
||||
{ label: "1:1", selected: false },
|
||||
{
|
||||
label: "3:2",
|
||||
selected: true,
|
||||
icon: <FontAwesomeIcon icon={faRectangle} className="h-4 w-4" />,
|
||||
},
|
||||
{
|
||||
label: "2:3",
|
||||
selected: false,
|
||||
icon: <FontAwesomeIcon icon={faRectangleVertical} className="h-4 w-4" />,
|
||||
},
|
||||
{
|
||||
label: "1:1",
|
||||
selected: false,
|
||||
icon: <FontAwesomeIcon icon={faSquare} className="h-4 w-4" />,
|
||||
},
|
||||
{
|
||||
label: "16:9",
|
||||
selected: false,
|
||||
icon: <FontAwesomeIcon icon={faRectangleWide} className="h-4 w-4" />,
|
||||
},
|
||||
]);
|
||||
const [cameraList, setCameraList] = useState<PopoverItem[]>([
|
||||
{
|
||||
@@ -45,6 +74,25 @@ export const PromptBox = () => {
|
||||
}
|
||||
}, [prompt]);
|
||||
|
||||
// Update aspect ratio list based on the current cameraAspectRatio signal
|
||||
useEffect(() => {
|
||||
setAspectRatioList((prev) =>
|
||||
prev.map((item) => ({
|
||||
...item,
|
||||
selected:
|
||||
(item.label === "16:9" &&
|
||||
cameraAspectRatio.value === CameraAspectRatio.HORIZONTAL_16_9) ||
|
||||
(item.label === "3:2" &&
|
||||
cameraAspectRatio.value === CameraAspectRatio.HORIZONTAL_3_2) ||
|
||||
(item.label === "2:3" &&
|
||||
cameraAspectRatio.value === CameraAspectRatio.VERTICAL_2_3) ||
|
||||
(item.label === "1:1" &&
|
||||
cameraAspectRatio.value === CameraAspectRatio.SQUARE_1_1),
|
||||
})),
|
||||
);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [cameraAspectRatio.value]);
|
||||
|
||||
const handleCameraSelect = (selectedItem: PopoverItem) => {
|
||||
setCameraList((prev) =>
|
||||
prev.map((item) => ({
|
||||
@@ -73,6 +121,32 @@ export const PromptBox = () => {
|
||||
selected: item.label === selectedItem.label,
|
||||
})),
|
||||
);
|
||||
|
||||
// Map the selected label to the corresponding CameraAspectRatio enum value
|
||||
let newRatio: CameraAspectRatio;
|
||||
switch (selectedItem.label) {
|
||||
case "16:9":
|
||||
newRatio = CameraAspectRatio.HORIZONTAL_16_9;
|
||||
break;
|
||||
case "3:2":
|
||||
newRatio = CameraAspectRatio.HORIZONTAL_3_2;
|
||||
break;
|
||||
case "2:3":
|
||||
newRatio = CameraAspectRatio.VERTICAL_2_3;
|
||||
break;
|
||||
case "1:1":
|
||||
newRatio = CameraAspectRatio.SQUARE_1_1;
|
||||
break;
|
||||
default:
|
||||
newRatio = CameraAspectRatio.HORIZONTAL_16_9;
|
||||
}
|
||||
|
||||
// Publish the change to the engine
|
||||
Queue.publish({
|
||||
queueName: QueueNames.TO_ENGINE,
|
||||
action: toEngineActions.CHANGE_CAMERA_ASPECT_RATIO,
|
||||
data: newRatio,
|
||||
});
|
||||
};
|
||||
|
||||
const handleFileUpload = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
@@ -133,6 +207,24 @@ export const PromptBox = () => {
|
||||
}
|
||||
};
|
||||
|
||||
// Get the current aspect ratio icon based on the cameraAspectRatio signal
|
||||
const getCurrentAspectRatioIcon = () => {
|
||||
switch (cameraAspectRatio.value) {
|
||||
case CameraAspectRatio.HORIZONTAL_16_9:
|
||||
return faRectangleWide;
|
||||
case CameraAspectRatio.HORIZONTAL_3_2:
|
||||
return faRectangle;
|
||||
case CameraAspectRatio.VERTICAL_2_3:
|
||||
return faRectangleVertical;
|
||||
case CameraAspectRatio.VERTICAL_9_16:
|
||||
return faRectangleVertical;
|
||||
case CameraAspectRatio.SQUARE_1_1:
|
||||
return faSquare;
|
||||
default:
|
||||
return faRectangleWide;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="glass absolute bottom-4 left-1/2 w-[730px] -translate-x-1/2 rounded-xl p-4">
|
||||
<input
|
||||
@@ -184,6 +276,13 @@ export const PromptBox = () => {
|
||||
onSelect={handleAspectRatioSelect}
|
||||
mode="toggle"
|
||||
panelTitle="Aspect Ratio"
|
||||
showIconsInList
|
||||
triggerIcon={
|
||||
<FontAwesomeIcon
|
||||
icon={getCurrentAspectRatioIcon()}
|
||||
className="h-4 w-4"
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<PopoverMenu
|
||||
items={cameraList}
|
||||
|
||||
@@ -25,6 +25,16 @@ export const getMatteOrientation = ({
|
||||
? MatteOrientation.TOP_BOTTOM
|
||||
: MatteOrientation.LEFT_RIGHT;
|
||||
}
|
||||
case CameraAspectRatio.VERTICAL_2_3: {
|
||||
return (height / 3) * 2 - width >= 0
|
||||
? MatteOrientation.TOP_BOTTOM
|
||||
: MatteOrientation.LEFT_RIGHT;
|
||||
}
|
||||
case CameraAspectRatio.HORIZONTAL_3_2: {
|
||||
return (width / 3) * 2 - height >= 0
|
||||
? MatteOrientation.LEFT_RIGHT
|
||||
: MatteOrientation.TOP_BOTTOM;
|
||||
}
|
||||
case CameraAspectRatio.HORIZONTAL_16_9:
|
||||
default: {
|
||||
return (width / 16) * 9 - height >= 0
|
||||
@@ -58,6 +68,12 @@ export const calcMatteWidth = ({
|
||||
case CameraAspectRatio.VERTICAL_9_16: {
|
||||
return (width - (height / 16) * 9) / 2;
|
||||
}
|
||||
case CameraAspectRatio.HORIZONTAL_3_2: {
|
||||
return (width - (height / 2) * 3) / 2;
|
||||
}
|
||||
case CameraAspectRatio.VERTICAL_2_3: {
|
||||
return (width - (height / 3) * 2) / 2;
|
||||
}
|
||||
default: {
|
||||
return undefined;
|
||||
}
|
||||
@@ -88,6 +104,12 @@ export const calcMatteHeight = ({
|
||||
case CameraAspectRatio.VERTICAL_9_16: {
|
||||
return (height - (width / 9) * 16) / 2;
|
||||
}
|
||||
case CameraAspectRatio.HORIZONTAL_3_2: {
|
||||
return (height - (width / 3) * 2) / 2;
|
||||
}
|
||||
case CameraAspectRatio.VERTICAL_2_3: {
|
||||
return (height - (width / 2) * 3) / 2;
|
||||
}
|
||||
default: {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
@@ -8,4 +8,6 @@ export enum CameraAspectRatio {
|
||||
HORIZONTAL_16_9 = "horizontal_16_9",
|
||||
VERTICAL_9_16 = "vertical_9_16",
|
||||
SQUARE_1_1 = "SQUARE_1_1",
|
||||
HORIZONTAL_3_2 = "horizontal_3_2",
|
||||
VERTICAL_2_3 = "vertical_2_3",
|
||||
}
|
||||
|
||||
Generated
+12
@@ -9,6 +9,7 @@
|
||||
"@awesome.me/kit-fde2be5eb0": "^1.0.7",
|
||||
"@fortawesome/fontawesome-svg-core": "^6.5.1",
|
||||
"@fortawesome/free-brands-svg-icons": "^6.5.2",
|
||||
"@fortawesome/pro-regular-svg-icons": "^6.5.1",
|
||||
"@fortawesome/pro-solid-svg-icons": "^6.5.1",
|
||||
"@fortawesome/react-fontawesome": "^0.2.0",
|
||||
"@headlessui/react": "^1.7.18",
|
||||
@@ -1423,6 +1424,17 @@
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/@fortawesome/pro-regular-svg-icons": {
|
||||
"version": "6.7.2",
|
||||
"resolved": "https://npm.fontawesome.com/@fortawesome/pro-regular-svg-icons/-/6.7.2/pro-regular-svg-icons-6.7.2.tgz",
|
||||
"integrity": "sha512-cJolK4K/s56mHAqOyno24SmjviHqSqG9ZfckBKCpWjJlgDexi61EfET5wWbmmEot6/BqasFjw9S6tudFQUL/PQ==",
|
||||
"dependencies": {
|
||||
"@fortawesome/fontawesome-common-types": "6.7.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/@fortawesome/pro-solid-svg-icons": {
|
||||
"version": "6.7.2",
|
||||
"resolved": "https://npm.fontawesome.com/@fortawesome/pro-solid-svg-icons/-/6.7.2/pro-solid-svg-icons-6.7.2.tgz",
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
"@awesome.me/kit-fde2be5eb0": "^1.0.7",
|
||||
"@fortawesome/fontawesome-svg-core": "^6.5.1",
|
||||
"@fortawesome/free-brands-svg-icons": "^6.5.2",
|
||||
"@fortawesome/pro-regular-svg-icons": "^6.5.1",
|
||||
"@fortawesome/pro-solid-svg-icons": "^6.5.1",
|
||||
"@fortawesome/react-fontawesome": "^0.2.0",
|
||||
"@headlessui/react": "^1.7.18",
|
||||
|
||||
Reference in New Issue
Block a user