jbilcke-hf HF staff commited on
Commit
8c5d17c
1 Parent(s): 932a7fd
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: 1,
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 items-center space-x-3 font-mono">
41
- <Label className="flex text-sm">Select a preset:</Label>
42
- <Select
43
- defaultValue={defaultPreset}
44
- onValueChange={(value) => { setPreset(getPreset(value as FontName)) }}
45
- disabled={atLeastOnePanelIsBusy}
46
- >
47
- <SelectTrigger className="w-[180px]">
48
- <SelectValue className="text-sm" placeholder="Type" />
49
- </SelectTrigger>
50
- <SelectContent>
51
- {Object.entries(presets).map(([key, preset]) =>
52
- <SelectItem key={key} value={key}>{preset.label}</SelectItem>
53
- )}
54
- </SelectContent>
55
- </Select>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
56
  </div>
57
- <div className="flex flex-row flex-grow items-center space-x-3 font-mono">
 
 
 
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="flex flex-row items-center space-x-3 font-mono">
83
- <Label className="flex text-sm">Font:</Label>
 
 
 
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="w-[144px]">
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-4 w-[1160px] h-screen">
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-4 w-[1160px] h-screen">
47
- <div className="bg-gray-100 row-span-2 col-span-1">
48
  <Panel
49
  panel={0}
50
- width={1024}
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-2">
69
  <Panel
70
  panel={3}
71
  width={1024}
72
- height={1024}
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-2 grid-rows-3 gap-4 w-[1160px] h-screen">
84
- <div className="bg-zinc-100 row-span-2">
85
  <Panel
86
  panel={0}
87
  width={1024}
 
 
 
 
 
 
 
88
  height={1024}
89
  />
90
  </div>
91
- <div className="bg-stone-100">
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
92
  <Panel
93
  panel={1}
94
  width={1024}
95
  height={512}
96
  />
97
  </div>
98
- <div className="bg-slate-100">
99
  <Panel
100
  panel={2}
101
  width={1024}
102
  height={512}
103
  />
104
  </div>
105
- <div className="bg-gray-100 row-span-2 col-span-1">
106
  <Panel
107
  panel={3}
108
- width={1024}
109
  height={1024}
110
  />
111
  </div>
@@ -113,33 +151,71 @@ export function Layout3() {
113
  )
114
  }
115
 
116
- export function Layout4() {
 
117
  return (
118
  <div
119
  // the "fixed" width ensure our comic keeps a consistent ratio
120
- className="grid grid-cols-2 grid-rows-3 gap-4 w-[1160px] h-screen">
121
- <div className="bg-slate-100">
122
  <Panel
123
  panel={0}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
124
  width={1024}
125
  height={512}
126
  />
127
  </div>
128
- <div className="bg-gray-100 row-span-2 col-span-1">
 
 
 
 
 
 
 
 
 
129
  <Panel
130
- panel={1}
131
  width={1024}
132
- height={1024}
133
  />
134
  </div>
135
- <div className="bg-zinc-100 row-span-2">
 
 
 
 
 
 
 
136
  <Panel
137
  panel={2}
138
- width={1024}
139
- height={1024}
140
  />
141
  </div>
142
- <div className="bg-stone-100">
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 className={cn(
68
- ``
69
- )}>
70
  <TopMenu />
71
- <div className="flex flex-col items-center w-screen h-screen pt-16 overflow-y-scroll">
72
- <LayoutElement />
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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: "Layout1",
 
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 }