Commit
•
8c5d17c
1
Parent(s):
932a7fd
zooom
Browse files- package-lock.json +34 -0
- package.json +1 -0
- src/app/engine/presets.ts +6 -1
- src/app/engine/render.ts +1 -1
- src/app/interface/panel/index.tsx +4 -0
- src/app/interface/top-menu/index.tsx +59 -22
- src/app/interface/zoom/index.tsx +31 -0
- src/app/layouts/index.tsx +101 -24
- src/app/main.tsx +25 -5
- src/app/store/index.ts +6 -2
- src/components/ui/slider.tsx +28 -0
- src/components/ui/vertical-slider.tsx +27 -0
package-lock.json
CHANGED
@@ -21,6 +21,7 @@
|
|
21 |
"@radix-ui/react-popover": "^1.0.6",
|
22 |
"@radix-ui/react-select": "^1.2.2",
|
23 |
"@radix-ui/react-separator": "^1.0.3",
|
|
|
24 |
"@radix-ui/react-slot": "^1.0.2",
|
25 |
"@radix-ui/react-switch": "^1.0.3",
|
26 |
"@radix-ui/react-tooltip": "^1.0.6",
|
@@ -3182,6 +3183,39 @@
|
|
3182 |
}
|
3183 |
}
|
3184 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
3185 |
"node_modules/@radix-ui/react-slot": {
|
3186 |
"version": "1.0.2",
|
3187 |
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.0.2.tgz",
|
|
|
21 |
"@radix-ui/react-popover": "^1.0.6",
|
22 |
"@radix-ui/react-select": "^1.2.2",
|
23 |
"@radix-ui/react-separator": "^1.0.3",
|
24 |
+
"@radix-ui/react-slider": "^1.1.2",
|
25 |
"@radix-ui/react-slot": "^1.0.2",
|
26 |
"@radix-ui/react-switch": "^1.0.3",
|
27 |
"@radix-ui/react-tooltip": "^1.0.6",
|
|
|
3183 |
}
|
3184 |
}
|
3185 |
},
|
3186 |
+
"node_modules/@radix-ui/react-slider": {
|
3187 |
+
"version": "1.1.2",
|
3188 |
+
"resolved": "https://registry.npmjs.org/@radix-ui/react-slider/-/react-slider-1.1.2.tgz",
|
3189 |
+
"integrity": "sha512-NKs15MJylfzVsCagVSWKhGGLNR1W9qWs+HtgbmjjVUB3B9+lb3PYoXxVju3kOrpf0VKyVCtZp+iTwVoqpa1Chw==",
|
3190 |
+
"dependencies": {
|
3191 |
+
"@babel/runtime": "^7.13.10",
|
3192 |
+
"@radix-ui/number": "1.0.1",
|
3193 |
+
"@radix-ui/primitive": "1.0.1",
|
3194 |
+
"@radix-ui/react-collection": "1.0.3",
|
3195 |
+
"@radix-ui/react-compose-refs": "1.0.1",
|
3196 |
+
"@radix-ui/react-context": "1.0.1",
|
3197 |
+
"@radix-ui/react-direction": "1.0.1",
|
3198 |
+
"@radix-ui/react-primitive": "1.0.3",
|
3199 |
+
"@radix-ui/react-use-controllable-state": "1.0.1",
|
3200 |
+
"@radix-ui/react-use-layout-effect": "1.0.1",
|
3201 |
+
"@radix-ui/react-use-previous": "1.0.1",
|
3202 |
+
"@radix-ui/react-use-size": "1.0.1"
|
3203 |
+
},
|
3204 |
+
"peerDependencies": {
|
3205 |
+
"@types/react": "*",
|
3206 |
+
"@types/react-dom": "*",
|
3207 |
+
"react": "^16.8 || ^17.0 || ^18.0",
|
3208 |
+
"react-dom": "^16.8 || ^17.0 || ^18.0"
|
3209 |
+
},
|
3210 |
+
"peerDependenciesMeta": {
|
3211 |
+
"@types/react": {
|
3212 |
+
"optional": true
|
3213 |
+
},
|
3214 |
+
"@types/react-dom": {
|
3215 |
+
"optional": true
|
3216 |
+
}
|
3217 |
+
}
|
3218 |
+
},
|
3219 |
"node_modules/@radix-ui/react-slot": {
|
3220 |
"version": "1.0.2",
|
3221 |
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.0.2.tgz",
|
package.json
CHANGED
@@ -22,6 +22,7 @@
|
|
22 |
"@radix-ui/react-popover": "^1.0.6",
|
23 |
"@radix-ui/react-select": "^1.2.2",
|
24 |
"@radix-ui/react-separator": "^1.0.3",
|
|
|
25 |
"@radix-ui/react-slot": "^1.0.2",
|
26 |
"@radix-ui/react-switch": "^1.0.3",
|
27 |
"@radix-ui/react-tooltip": "^1.0.6",
|
|
|
22 |
"@radix-ui/react-popover": "^1.0.6",
|
23 |
"@radix-ui/react-select": "^1.2.2",
|
24 |
"@radix-ui/react-separator": "^1.0.3",
|
25 |
+
"@radix-ui/react-slider": "^1.1.2",
|
26 |
"@radix-ui/react-slot": "^1.0.2",
|
27 |
"@radix-ui/react-switch": "^1.0.3",
|
28 |
"@radix-ui/react-tooltip": "^1.0.6",
|
src/app/engine/presets.ts
CHANGED
@@ -85,6 +85,8 @@ export const presets: Record<string, Preset> = {
|
|
85 |
"single panel",
|
86 |
"american comic",
|
87 |
"comicbook style",
|
|
|
|
|
88 |
"color comicbook",
|
89 |
"color drawing"
|
90 |
],
|
@@ -109,10 +111,13 @@ export const presets: Record<string, Preset> = {
|
|
109 |
imagePrompt: (prompt: string) => [
|
110 |
`color album panel`,
|
111 |
`about ${prompt}`,
|
|
|
|
|
|
|
|
|
112 |
"bande dessinée",
|
113 |
"single panel",
|
114 |
"comical",
|
115 |
-
"franco-belgian comic",
|
116 |
"comic album",
|
117 |
"color drawing"
|
118 |
],
|
|
|
85 |
"single panel",
|
86 |
"american comic",
|
87 |
"comicbook style",
|
88 |
+
"1950",
|
89 |
+
"50s",
|
90 |
"color comicbook",
|
91 |
"color drawing"
|
92 |
],
|
|
|
111 |
imagePrompt: (prompt: string) => [
|
112 |
`color album panel`,
|
113 |
`about ${prompt}`,
|
114 |
+
"romans",
|
115 |
+
"gauls",
|
116 |
+
"french comic panel",
|
117 |
+
"franco-belgian style",
|
118 |
"bande dessinée",
|
119 |
"single panel",
|
120 |
"comical",
|
|
|
121 |
"comic album",
|
122 |
"color drawing"
|
123 |
],
|
src/app/engine/render.ts
CHANGED
@@ -57,7 +57,7 @@ export async function newRender({
|
|
57 |
|
58 |
// no need to upscale right now as we generate tiny panels
|
59 |
// maybe later we can provide an "export" button to PDF
|
60 |
-
upscalingFactor:
|
61 |
|
62 |
cache: "ignore"
|
63 |
} as Partial<RenderRequest>),
|
|
|
57 |
|
58 |
// no need to upscale right now as we generate tiny panels
|
59 |
// maybe later we can provide an "export" button to PDF
|
60 |
+
upscalingFactor: 2,
|
61 |
|
62 |
cache: "ignore"
|
63 |
} as Partial<RenderRequest>),
|
src/app/interface/panel/index.tsx
CHANGED
@@ -143,6 +143,10 @@ export function Panel({
|
|
143 |
<div className={cn(
|
144 |
`w-full h-full`,
|
145 |
{ "grayscale": preset.color === "grayscale" },
|
|
|
|
|
|
|
|
|
146 |
className
|
147 |
)}>
|
148 |
{rendered.assetUrl && <img
|
|
|
143 |
<div className={cn(
|
144 |
`w-full h-full`,
|
145 |
{ "grayscale": preset.color === "grayscale" },
|
146 |
+
`border-2 border-stone-900`,
|
147 |
+
`shadow-sm`,
|
148 |
+
`rounded-sm`,
|
149 |
+
`overflow-hidden`,
|
150 |
className
|
151 |
)}>
|
152 |
{rendered.assetUrl && <img
|
src/app/interface/top-menu/index.tsx
CHANGED
@@ -31,30 +31,64 @@ export function TopMenu() {
|
|
31 |
return (
|
32 |
<div className={cn(
|
33 |
`z-10 fixed top-0 left-0 right-0`,
|
34 |
-
`flex flex-row w-full justify-between items-center`,
|
35 |
`backdrop-blur-xl`,
|
|
|
36 |
`px-2 py-2 border-b-1 border-gray-50 dark:border-gray-50`,
|
37 |
`bg-stone-900/70 dark:bg-stone-900/70 text-gray-50 dark:text-gray-50`,
|
38 |
-
`space-x-6`
|
39 |
)}>
|
40 |
-
<div className="flex flex-row
|
41 |
-
<
|
42 |
-
|
43 |
-
|
44 |
-
|
45 |
-
|
46 |
-
|
47 |
-
|
48 |
-
|
49 |
-
|
50 |
-
|
51 |
-
|
52 |
-
<
|
53 |
-
|
54 |
-
|
55 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
56 |
</div>
|
57 |
-
<div className=
|
|
|
|
|
|
|
58 |
<Input
|
59 |
placeholder="Story"
|
60 |
className="w-full bg-neutral-300 text-neutral-800 dark:bg-neutral-300 dark:text-neutral-800"
|
@@ -79,14 +113,17 @@ export function TopMenu() {
|
|
79 |
value={draft}
|
80 |
/>
|
81 |
</div>
|
82 |
-
<div className=
|
83 |
-
|
|
|
|
|
|
|
84 |
<Select
|
85 |
defaultValue={fontList.includes(preset.font) ? preset.font : "cartoonist"}
|
86 |
onValueChange={(value) => { setFont(value as FontName) }}
|
87 |
disabled={atLeastOnePanelIsBusy}
|
88 |
>
|
89 |
-
<SelectTrigger className="
|
90 |
<SelectValue className="text-sm" placeholder="Type" />
|
91 |
</SelectTrigger>
|
92 |
<SelectContent>
|
|
|
31 |
return (
|
32 |
<div className={cn(
|
33 |
`z-10 fixed top-0 left-0 right-0`,
|
34 |
+
`flex flex-col md:flex-row w-full justify-between items-center`,
|
35 |
`backdrop-blur-xl`,
|
36 |
+
`transition-all duration-200 ease-in-out`,
|
37 |
`px-2 py-2 border-b-1 border-gray-50 dark:border-gray-50`,
|
38 |
`bg-stone-900/70 dark:bg-stone-900/70 text-gray-50 dark:text-gray-50`,
|
39 |
+
`space-y-2 md:space-y-0 md:space-x-3 lg:space-x-6`
|
40 |
)}>
|
41 |
+
<div className="flex flex-row space-x-3">
|
42 |
+
<div className={cn(
|
43 |
+
`transition-all duration-200 ease-in-out`,
|
44 |
+
`flex flex-row items-center justify-start space-x-3 font-mono w-1/2 md:w-auto`
|
45 |
+
)}>
|
46 |
+
<Label className="flex text-sm w-24">Preset:</Label>
|
47 |
+
<Select
|
48 |
+
defaultValue={defaultPreset}
|
49 |
+
onValueChange={(value) => { setPreset(getPreset(value as FontName)) }}
|
50 |
+
disabled={atLeastOnePanelIsBusy}
|
51 |
+
>
|
52 |
+
<SelectTrigger className="flex-grow">
|
53 |
+
<SelectValue className="text-sm" placeholder="Type" />
|
54 |
+
</SelectTrigger>
|
55 |
+
<SelectContent>
|
56 |
+
{Object.entries(presets).map(([key, preset]) =>
|
57 |
+
<SelectItem key={key} value={key}>{preset.label}</SelectItem>
|
58 |
+
)}
|
59 |
+
</SelectContent>
|
60 |
+
</Select>
|
61 |
+
</div>
|
62 |
+
<div className={cn(
|
63 |
+
`transition-all duration-200 ease-in-out`,
|
64 |
+
`flex flex-row items-center space-x-3 font-mono w-1/2 md:w-auto md:hidden`
|
65 |
+
)}>
|
66 |
+
<Label className="flex text-sm w-24">Font:</Label>
|
67 |
+
<Select
|
68 |
+
defaultValue={fontList.includes(preset.font) ? preset.font : "cartoonist"}
|
69 |
+
onValueChange={(value) => { setFont(value as FontName) }}
|
70 |
+
disabled={atLeastOnePanelIsBusy}
|
71 |
+
>
|
72 |
+
<SelectTrigger className="flex-grow">
|
73 |
+
<SelectValue className="text-sm" placeholder="Type" />
|
74 |
+
</SelectTrigger>
|
75 |
+
<SelectContent>
|
76 |
+
{Object.keys(fonts)
|
77 |
+
.map((font) =>
|
78 |
+
<SelectItem
|
79 |
+
key={font}
|
80 |
+
value={font}>{
|
81 |
+
font
|
82 |
+
}</SelectItem>
|
83 |
+
)}
|
84 |
+
</SelectContent>
|
85 |
+
</Select>
|
86 |
+
</div>
|
87 |
</div>
|
88 |
+
<div className={cn(
|
89 |
+
`transition-all duration-200 ease-in-out`,
|
90 |
+
`flex flex-row flex-grow items-center space-x-3 font-mono w-full md:w-auto`
|
91 |
+
)}>
|
92 |
<Input
|
93 |
placeholder="Story"
|
94 |
className="w-full bg-neutral-300 text-neutral-800 dark:bg-neutral-300 dark:text-neutral-800"
|
|
|
113 |
value={draft}
|
114 |
/>
|
115 |
</div>
|
116 |
+
<div className={cn(
|
117 |
+
`transition-all duration-200 ease-in-out`,
|
118 |
+
`hidden md:flex flex-row items-center space-x-3 font-mono w-full md:w-auto`
|
119 |
+
)}>
|
120 |
+
<Label className="flex text-sm w-24">Font:</Label>
|
121 |
<Select
|
122 |
defaultValue={fontList.includes(preset.font) ? preset.font : "cartoonist"}
|
123 |
onValueChange={(value) => { setFont(value as FontName) }}
|
124 |
disabled={atLeastOnePanelIsBusy}
|
125 |
>
|
126 |
+
<SelectTrigger className="flex-grow">
|
127 |
<SelectValue className="text-sm" placeholder="Type" />
|
128 |
</SelectTrigger>
|
129 |
<SelectContent>
|
src/app/interface/zoom/index.tsx
ADDED
@@ -0,0 +1,31 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { useStore } from "@/app/store"
|
2 |
+
import { VerticalSlider } from "@/components/ui/vertical-slider"
|
3 |
+
import { cn } from "@/lib/utils"
|
4 |
+
|
5 |
+
export function Zoom() {
|
6 |
+
const zoomLevel = useStore((state) => state.zoomLevel)
|
7 |
+
const setZoomLevel = useStore((state) => state.setZoomLevel)
|
8 |
+
|
9 |
+
return (
|
10 |
+
<div className={cn(
|
11 |
+
// `fixed flex items-center justify-center bottom-8 top-32 right-8 z-10 h-screen`,
|
12 |
+
`fixed flex flex-col items-center bottom-8 top-32 md:top-20 right-6 z-10`
|
13 |
+
)}>
|
14 |
+
<div className="font-mono text-xs pb-1 text-stone-700">
|
15 |
+
Zoom
|
16 |
+
</div>
|
17 |
+
<div className="w-2">
|
18 |
+
<VerticalSlider
|
19 |
+
defaultValue={[zoomLevel]}
|
20 |
+
min={30}
|
21 |
+
max={100}
|
22 |
+
step={1}
|
23 |
+
onValueChange={value => setZoomLevel(value[0] || 10)}
|
24 |
+
value={[zoomLevel]}
|
25 |
+
className="h-48"
|
26 |
+
orientation="vertical"
|
27 |
+
/>
|
28 |
+
</div>
|
29 |
+
</div>
|
30 |
+
)
|
31 |
+
}
|
src/app/layouts/index.tsx
CHANGED
@@ -1,12 +1,11 @@
|
|
1 |
import { Panel } from "@/app/interface/panel"
|
2 |
import { pick } from "@/lib/pick"
|
3 |
-
import { useStore } from "@/app/store"
|
4 |
|
5 |
export function Layout1() {
|
6 |
return (
|
7 |
<div
|
8 |
// the "fixed" width ensure our comic keeps a consistent ratio
|
9 |
-
className="grid grid-cols-2 grid-rows-3 gap-
|
10 |
<div className="bg-stone-100">
|
11 |
<Panel
|
12 |
panel={0}
|
@@ -43,11 +42,11 @@ export function Layout2() {
|
|
43 |
return (
|
44 |
<div
|
45 |
// the "fixed" width ensure our comic keeps a consistent ratio
|
46 |
-
className="grid grid-cols-2 grid-rows-3 gap-
|
47 |
-
<div className="bg-gray-100 row-span-
|
48 |
<Panel
|
49 |
panel={0}
|
50 |
-
width={
|
51 |
height={1024}
|
52 |
/>
|
53 |
</div>
|
@@ -65,11 +64,11 @@ export function Layout2() {
|
|
65 |
height={512}
|
66 |
/>
|
67 |
</div>
|
68 |
-
<div className="bg-zinc-100 row-span-
|
69 |
<Panel
|
70 |
panel={3}
|
71 |
width={1024}
|
72 |
-
height={
|
73 |
/>
|
74 |
</div>
|
75 |
</div>
|
@@ -80,32 +79,71 @@ export function Layout3() {
|
|
80 |
return (
|
81 |
<div
|
82 |
// the "fixed" width ensure our comic keeps a consistent ratio
|
83 |
-
className="grid grid-cols-
|
84 |
-
<div className="bg-zinc-100
|
85 |
<Panel
|
86 |
panel={0}
|
87 |
width={1024}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
88 |
height={1024}
|
89 |
/>
|
90 |
</div>
|
91 |
-
<div className="
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
92 |
<Panel
|
93 |
panel={1}
|
94 |
width={1024}
|
95 |
height={512}
|
96 |
/>
|
97 |
</div>
|
98 |
-
<div className="bg-
|
99 |
<Panel
|
100 |
panel={2}
|
101 |
width={1024}
|
102 |
height={512}
|
103 |
/>
|
104 |
</div>
|
105 |
-
<div className="bg-
|
106 |
<Panel
|
107 |
panel={3}
|
108 |
-
width={
|
109 |
height={1024}
|
110 |
/>
|
111 |
</div>
|
@@ -113,33 +151,71 @@ export function Layout3() {
|
|
113 |
)
|
114 |
}
|
115 |
|
116 |
-
|
|
|
117 |
return (
|
118 |
<div
|
119 |
// the "fixed" width ensure our comic keeps a consistent ratio
|
120 |
-
className="grid grid-cols-
|
121 |
-
<div className="bg-
|
122 |
<Panel
|
123 |
panel={0}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
124 |
width={1024}
|
125 |
height={512}
|
126 |
/>
|
127 |
</div>
|
128 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
129 |
<Panel
|
130 |
-
panel={
|
131 |
width={1024}
|
132 |
-
height={
|
133 |
/>
|
134 |
</div>
|
135 |
-
<div className="bg-zinc-100 row-span-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
136 |
<Panel
|
137 |
panel={2}
|
138 |
-
width={
|
139 |
-
height={
|
140 |
/>
|
141 |
</div>
|
142 |
-
<div className="bg-
|
143 |
<Panel
|
144 |
panel={3}
|
145 |
width={1024}
|
@@ -150,7 +226,8 @@ export function Layout4() {
|
|
150 |
)
|
151 |
}
|
152 |
|
153 |
-
export const layouts = { Layout1, Layout2, Layout3, Layout4 }
|
|
|
154 |
|
155 |
export type LayoutName = keyof typeof layouts
|
156 |
|
|
|
1 |
import { Panel } from "@/app/interface/panel"
|
2 |
import { pick } from "@/lib/pick"
|
|
|
3 |
|
4 |
export function Layout1() {
|
5 |
return (
|
6 |
<div
|
7 |
// the "fixed" width ensure our comic keeps a consistent ratio
|
8 |
+
className="w-full h-full grid grid-cols-2 grid-rows-3 gap-2">
|
9 |
<div className="bg-stone-100">
|
10 |
<Panel
|
11 |
panel={0}
|
|
|
42 |
return (
|
43 |
<div
|
44 |
// the "fixed" width ensure our comic keeps a consistent ratio
|
45 |
+
className="w-full h-full grid grid-cols-2 grid-rows-3 gap-2">
|
46 |
+
<div className="bg-gray-100 row-span-3 col-span-1">
|
47 |
<Panel
|
48 |
panel={0}
|
49 |
+
width={512}
|
50 |
height={1024}
|
51 |
/>
|
52 |
</div>
|
|
|
64 |
height={512}
|
65 |
/>
|
66 |
</div>
|
67 |
+
<div className="bg-zinc-100 row-span-1 col-span-1">
|
68 |
<Panel
|
69 |
panel={3}
|
70 |
width={1024}
|
71 |
+
height={512}
|
72 |
/>
|
73 |
</div>
|
74 |
</div>
|
|
|
79 |
return (
|
80 |
<div
|
81 |
// the "fixed" width ensure our comic keeps a consistent ratio
|
82 |
+
className="w-full h-full grid grid-cols-5 grid-rows-2 gap-2">
|
83 |
+
<div className="bg-zinc-100 col-span-3">
|
84 |
<Panel
|
85 |
panel={0}
|
86 |
width={1024}
|
87 |
+
height={512}
|
88 |
+
/>
|
89 |
+
</div>
|
90 |
+
<div className="bg-zinc-100 col-span-2 row-span-2">
|
91 |
+
<Panel
|
92 |
+
panel={1}
|
93 |
+
width={512}
|
94 |
height={1024}
|
95 |
/>
|
96 |
</div>
|
97 |
+
<div className="col-span-3 grid grid-cols-2 gap-2">
|
98 |
+
<div className="bg-stone-100">
|
99 |
+
<Panel
|
100 |
+
panel={2}
|
101 |
+
width={512}
|
102 |
+
height={758}
|
103 |
+
/>
|
104 |
+
</div>
|
105 |
+
<div className="bg-slate-100">
|
106 |
+
<Panel
|
107 |
+
panel={3}
|
108 |
+
width={512}
|
109 |
+
height={758}
|
110 |
+
/>
|
111 |
+
</div>
|
112 |
+
</div>
|
113 |
+
</div>
|
114 |
+
)
|
115 |
+
}
|
116 |
+
|
117 |
+
export function Layout4() {
|
118 |
+
return (
|
119 |
+
<div
|
120 |
+
// the "fixed" width ensure our comic keeps a consistent ratio
|
121 |
+
className="w-full h-full grid grid-cols-2 grid-rows-3 gap-2">
|
122 |
+
<div className="bg-slate-100 row-span-2">
|
123 |
+
<Panel
|
124 |
+
panel={0}
|
125 |
+
width={768}
|
126 |
+
height={1024}
|
127 |
+
/>
|
128 |
+
</div>
|
129 |
+
<div className="bg-gray-100 row-span-1 col-span-1">
|
130 |
<Panel
|
131 |
panel={1}
|
132 |
width={1024}
|
133 |
height={512}
|
134 |
/>
|
135 |
</div>
|
136 |
+
<div className="bg-zinc-100 row-span-2">
|
137 |
<Panel
|
138 |
panel={2}
|
139 |
width={1024}
|
140 |
height={512}
|
141 |
/>
|
142 |
</div>
|
143 |
+
<div className="bg-stone-100">
|
144 |
<Panel
|
145 |
panel={3}
|
146 |
+
width={768}
|
147 |
height={1024}
|
148 |
/>
|
149 |
</div>
|
|
|
151 |
)
|
152 |
}
|
153 |
|
154 |
+
|
155 |
+
export function Layout5() {
|
156 |
return (
|
157 |
<div
|
158 |
// the "fixed" width ensure our comic keeps a consistent ratio
|
159 |
+
className="w-full h-full grid grid-cols-3 grid-rows-2 gap-2">
|
160 |
+
<div className="bg-zinc-100 col-span-1 row-span-1">
|
161 |
<Panel
|
162 |
panel={0}
|
163 |
+
width={512}
|
164 |
+
height={768}
|
165 |
+
/>
|
166 |
+
</div>
|
167 |
+
<div className="bg-zinc-100 col-span-1 row-span-1">
|
168 |
+
<Panel
|
169 |
+
panel={1}
|
170 |
+
width={512}
|
171 |
+
height={768}
|
172 |
+
/>
|
173 |
+
</div>
|
174 |
+
<div className="bg-stone-100 row-span-2 col-span-1">
|
175 |
+
<Panel
|
176 |
+
panel={2}
|
177 |
+
width={512}
|
178 |
+
height={1024}
|
179 |
+
/>
|
180 |
+
</div>
|
181 |
+
<div className="bg-slate-100 row-span-1 col-span-2">
|
182 |
+
<Panel
|
183 |
+
panel={3}
|
184 |
width={1024}
|
185 |
height={512}
|
186 |
/>
|
187 |
</div>
|
188 |
+
</div>
|
189 |
+
)
|
190 |
+
}
|
191 |
+
|
192 |
+
export function Layout6() {
|
193 |
+
return (
|
194 |
+
<div
|
195 |
+
// the "fixed" width ensure our comic keeps a consistent ratio
|
196 |
+
className="w-full h-full grid grid-cols-3 grid-rows-2 gap-2">
|
197 |
+
<div className="bg-zinc-100 col-span-2 row-span-1">
|
198 |
<Panel
|
199 |
+
panel={0}
|
200 |
width={1024}
|
201 |
+
height={512}
|
202 |
/>
|
203 |
</div>
|
204 |
+
<div className="bg-zinc-100 col-span-1 row-span-1">
|
205 |
+
<Panel
|
206 |
+
panel={1}
|
207 |
+
width={512}
|
208 |
+
height={768}
|
209 |
+
/>
|
210 |
+
</div>
|
211 |
+
<div className="bg-stone-100 row-span-1 col-span-1">
|
212 |
<Panel
|
213 |
panel={2}
|
214 |
+
width={512}
|
215 |
+
height={768}
|
216 |
/>
|
217 |
</div>
|
218 |
+
<div className="bg-slate-100 row-span-1 col-span-2">
|
219 |
<Panel
|
220 |
panel={3}
|
221 |
width={1024}
|
|
|
226 |
)
|
227 |
}
|
228 |
|
229 |
+
// export const layouts = { Layout1, Layout2, Layout3, Layout4, Layout5, Layout6 }
|
230 |
+
export const layouts = { Layout1, Layout4, Layout6 }
|
231 |
|
232 |
export type LayoutName = keyof typeof layouts
|
233 |
|
src/app/main.tsx
CHANGED
@@ -10,6 +10,7 @@ import { TopMenu } from "./interface/top-menu"
|
|
10 |
import { FontName, defaultFont } from "@/lib/fonts"
|
11 |
import { getRandomLayoutName, layouts } from "./layouts"
|
12 |
import { useStore } from "./store"
|
|
|
13 |
|
14 |
export default function Main() {
|
15 |
const [_isPending, startTransition] = useTransition()
|
@@ -33,6 +34,8 @@ export default function Main() {
|
|
33 |
|
34 |
const setPanels = useStore(state => state.setPanels)
|
35 |
|
|
|
|
|
36 |
// react to URL params
|
37 |
useEffect(() => {
|
38 |
if (requestedPreset && requestedPreset !== preset.label) { setPreset(getPreset(requestedPreset)) }
|
@@ -64,13 +67,30 @@ export default function Main() {
|
|
64 |
const LayoutElement = (layouts as any)[layout]
|
65 |
|
66 |
return (
|
67 |
-
<div
|
68 |
-
``
|
69 |
-
)}>
|
70 |
<TopMenu />
|
71 |
-
<div className=
|
72 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
73 |
</div>
|
|
|
74 |
</div>
|
75 |
)
|
76 |
}
|
|
|
10 |
import { FontName, defaultFont } from "@/lib/fonts"
|
11 |
import { getRandomLayoutName, layouts } from "./layouts"
|
12 |
import { useStore } from "./store"
|
13 |
+
import { Zoom } from "./interface/zoom"
|
14 |
|
15 |
export default function Main() {
|
16 |
const [_isPending, startTransition] = useTransition()
|
|
|
34 |
|
35 |
const setPanels = useStore(state => state.setPanels)
|
36 |
|
37 |
+
const zoomLevel = useStore(state => state.zoomLevel)
|
38 |
+
|
39 |
// react to URL params
|
40 |
useEffect(() => {
|
41 |
if (requestedPreset && requestedPreset !== preset.label) { setPreset(getPreset(requestedPreset)) }
|
|
|
67 |
const LayoutElement = (layouts as any)[layout]
|
68 |
|
69 |
return (
|
70 |
+
<div>
|
|
|
|
|
71 |
<TopMenu />
|
72 |
+
<div className={cn(
|
73 |
+
`flex items-start w-screen h-screen pt-[120px] px-16 md:pt-[72px] overflow-y-scroll`,
|
74 |
+
`transition-all duration-200 ease-in-out`
|
75 |
+
)}>
|
76 |
+
<div className="flex flex-col items-center w-full">
|
77 |
+
<div
|
78 |
+
// we are trying to reach a "book" look
|
79 |
+
// we are using aspect-[297/210] because it matches A4 (297mm x 210mm)
|
80 |
+
className={cn(
|
81 |
+
`flex flex-col items-center justify-start aspect-[210/297]`,
|
82 |
+
`transition-all duration-100 ease-in-out`,
|
83 |
+
`p-4`,
|
84 |
+
`border border-stone-200`,
|
85 |
+
`shadow-2xl`
|
86 |
+
)}
|
87 |
+
style={{ width: `${zoomLevel}%` }}
|
88 |
+
>
|
89 |
+
<LayoutElement />
|
90 |
+
</div>
|
91 |
+
</div>
|
92 |
</div>
|
93 |
+
<Zoom />
|
94 |
</div>
|
95 |
)
|
96 |
}
|
src/app/store/index.ts
CHANGED
@@ -3,7 +3,7 @@ import { create } from "zustand"
|
|
3 |
|
4 |
import { FontName } from "@/lib/fonts"
|
5 |
import { Preset, getPreset } from "@/app/engine/presets"
|
6 |
-
import { LayoutName } from "../layouts"
|
7 |
|
8 |
export const useStore = create<{
|
9 |
prompt: string
|
@@ -11,6 +11,7 @@ export const useStore = create<{
|
|
11 |
preset: Preset
|
12 |
panels: string[]
|
13 |
layout: LayoutName
|
|
|
14 |
isGeneratingLogic: boolean
|
15 |
panelGenerationStatus: Record<number, boolean>
|
16 |
isGeneratingText: boolean
|
@@ -20,6 +21,7 @@ export const useStore = create<{
|
|
20 |
setPreset: (preset: Preset) => void
|
21 |
setPanels: (panels: string[]) => void
|
22 |
setLayout: (layout: LayoutName) => void
|
|
|
23 |
setGeneratingLogic: (isGeneratingLogic: boolean) => void
|
24 |
setGeneratingImages: (panelId: number, value: boolean) => void
|
25 |
setGeneratingText: (isGeneratingText: boolean) => void
|
@@ -28,7 +30,8 @@ export const useStore = create<{
|
|
28 |
font: "cartoonist",
|
29 |
preset: getPreset("japanese_manga"),
|
30 |
panels: [],
|
31 |
-
layout:
|
|
|
32 |
isGeneratingLogic: false,
|
33 |
panelGenerationStatus: {},
|
34 |
isGeneratingText: false,
|
@@ -38,6 +41,7 @@ export const useStore = create<{
|
|
38 |
setPreset: (preset: Preset) => set({ preset }),
|
39 |
setPanels: (panels: string[]) => set({ panels }),
|
40 |
setLayout: (layout: LayoutName) => set({ layout }),
|
|
|
41 |
setGeneratingLogic: (isGeneratingLogic: boolean) => set({ isGeneratingLogic }),
|
42 |
setGeneratingImages: (panelId: number, value: boolean) => {
|
43 |
|
|
|
3 |
|
4 |
import { FontName } from "@/lib/fonts"
|
5 |
import { Preset, getPreset } from "@/app/engine/presets"
|
6 |
+
import { LayoutName, getRandomLayoutName } from "../layouts"
|
7 |
|
8 |
export const useStore = create<{
|
9 |
prompt: string
|
|
|
11 |
preset: Preset
|
12 |
panels: string[]
|
13 |
layout: LayoutName
|
14 |
+
zoomLevel: number
|
15 |
isGeneratingLogic: boolean
|
16 |
panelGenerationStatus: Record<number, boolean>
|
17 |
isGeneratingText: boolean
|
|
|
21 |
setPreset: (preset: Preset) => void
|
22 |
setPanels: (panels: string[]) => void
|
23 |
setLayout: (layout: LayoutName) => void
|
24 |
+
setZoomLevel: (zoomLevel: number) => void
|
25 |
setGeneratingLogic: (isGeneratingLogic: boolean) => void
|
26 |
setGeneratingImages: (panelId: number, value: boolean) => void
|
27 |
setGeneratingText: (isGeneratingText: boolean) => void
|
|
|
30 |
font: "cartoonist",
|
31 |
preset: getPreset("japanese_manga"),
|
32 |
panels: [],
|
33 |
+
layout: getRandomLayoutName(),
|
34 |
+
zoomLevel: 50,
|
35 |
isGeneratingLogic: false,
|
36 |
panelGenerationStatus: {},
|
37 |
isGeneratingText: false,
|
|
|
41 |
setPreset: (preset: Preset) => set({ preset }),
|
42 |
setPanels: (panels: string[]) => set({ panels }),
|
43 |
setLayout: (layout: LayoutName) => set({ layout }),
|
44 |
+
setZoomLevel: (zoomLevel: number) => set({ zoomLevel }),
|
45 |
setGeneratingLogic: (isGeneratingLogic: boolean) => set({ isGeneratingLogic }),
|
46 |
setGeneratingImages: (panelId: number, value: boolean) => {
|
47 |
|
src/components/ui/slider.tsx
ADDED
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"use client"
|
2 |
+
|
3 |
+
import * as React from "react"
|
4 |
+
import * as SliderPrimitive from "@radix-ui/react-slider"
|
5 |
+
|
6 |
+
import { cn } from "@/lib/utils"
|
7 |
+
|
8 |
+
const Slider = React.forwardRef<
|
9 |
+
React.ElementRef<typeof SliderPrimitive.Root>,
|
10 |
+
React.ComponentPropsWithoutRef<typeof SliderPrimitive.Root>
|
11 |
+
>(({ className, ...props }, ref) => (
|
12 |
+
<SliderPrimitive.Root
|
13 |
+
ref={ref}
|
14 |
+
className={cn(
|
15 |
+
"relative flex w-full touch-none select-none items-center",
|
16 |
+
className
|
17 |
+
)}
|
18 |
+
{...props}
|
19 |
+
>
|
20 |
+
<SliderPrimitive.Track className="relative h-2 w-full grow overflow-hidden rounded-full bg-stone-300 dark:bg-stone-700">
|
21 |
+
<SliderPrimitive.Range className="absolute h-full bg-stone-700 dark:bg-stone-50" />
|
22 |
+
</SliderPrimitive.Track>
|
23 |
+
<SliderPrimitive.Thumb className="block h-5 w-5 rounded-full border-2 border-stone-700 bg-stone-300 ring-offset-white transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-stone-950 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 dark:border-stone-50 dark:bg-stone-700 dark:ring-offset-stone-950 dark:focus-visible:ring-stone-300" />
|
24 |
+
</SliderPrimitive.Root>
|
25 |
+
))
|
26 |
+
Slider.displayName = SliderPrimitive.Root.displayName
|
27 |
+
|
28 |
+
export { Slider }
|
src/components/ui/vertical-slider.tsx
ADDED
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"use client"
|
2 |
+
|
3 |
+
import * as React from "react"
|
4 |
+
import * as SliderPrimitive from "@radix-ui/react-slider"
|
5 |
+
|
6 |
+
import { cn } from "@/lib/utils"
|
7 |
+
|
8 |
+
const VerticalSlider = React.forwardRef<
|
9 |
+
React.ElementRef<typeof SliderPrimitive.Root>,
|
10 |
+
React.ComponentPropsWithoutRef<typeof SliderPrimitive.Root>
|
11 |
+
>(({ className, ...props }, ref) => (
|
12 |
+
<SliderPrimitive.Root
|
13 |
+
ref={ref}
|
14 |
+
className={cn(
|
15 |
+
"relative flex w-full touch-none select-none items-center",
|
16 |
+
className
|
17 |
+
)}
|
18 |
+
{...props}
|
19 |
+
>
|
20 |
+
<SliderPrimitive.Track className="relative w-2 h-full grow overflow-hidden rounded-full bg-stone-300 dark:bg-stone-700">
|
21 |
+
<SliderPrimitive.Range className="absolute w-full bg-stone-700 dark:bg-stone-50" />
|
22 |
+
</SliderPrimitive.Track>
|
23 |
+
<SliderPrimitive.Thumb className="block -ml-1.5 h-5 w-5 rounded-full border-2 border-stone-700 bg-stone-300 ring-offset-white transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-stone-950 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 dark:border-stone-50 dark:bg-stone-700 dark:ring-offset-stone-950 dark:focus-visible:ring-stone-300" />
|
24 |
+
</SliderPrimitive.Root>
|
25 |
+
))
|
26 |
+
VerticalSlider.displayName = "VerticalSlider"
|
27 |
+
export { VerticalSlider }
|