Skip to content

Commit 07be76d

Browse files
committed
Add: 画像の生成を追加
1 parent 4334ea4 commit 07be76d

9 files changed

Lines changed: 308 additions & 6 deletions

File tree

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@
5050
"jest": "^29.7.0",
5151
"prettier": "^3.0.3",
5252
"sass": "^1.67.0",
53-
"typescript": "^5.2.2",
53+
"typescript": "^5.3.3",
5454
"typescript-plugin-css-modules": "^5.0.0"
5555
}
5656
}
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
'use client';
2+
import { Button, Tabs } from 'antd';
3+
import React, { FC } from 'react';
4+
import { FormProvider, Controller } from 'react-hook-form';
5+
import { Grid, Row, Col, PanelGroup, Panel, Form } from 'rsuite';
6+
import { AppLayout } from '@/Layout/App';
7+
import { PRESET_SIZE_OPTIONS } from '@/app/image_generator/presetSize';
8+
import {
9+
useImageGenerator,
10+
DEFAULT_VALUES,
11+
SIZE_LIMIT,
12+
UNSPLASH_FILE_TYPES,
13+
PLACEHOLD_FILE_TYPES,
14+
} from '@/app/image_generator/useImageGenerator';
15+
import { FormRow } from '@/components/common/Form/FormRow';
16+
import { InputNumber } from '@/components/common/Form/InputNumber';
17+
import { Select } from '@/components/common/Form/Select';
18+
import { PageTitle } from '@/components/common/PageTitle';
19+
import { PanelHeader } from '@/components/common/PanelHeader';
20+
21+
const width = 250;
22+
23+
export const ImageGenerator: FC = () => {
24+
const title = '画像生成';
25+
const { methods, src, onClickGenerate, onSelectPreset, onChangeTab } = useImageGenerator();
26+
const { control } = methods;
27+
28+
return (
29+
<FormProvider {...methods}>
30+
<AppLayout>
31+
<Grid fluid>
32+
<PageTitle title={title} />
33+
<Row gutter={5}>
34+
<Col xs={24} md={12}>
35+
<Form fluid layout="horizontal">
36+
<PanelGroup bordered>
37+
<Panel bordered header={<PanelHeader title="設定" />}>
38+
<Tabs
39+
defaultActiveKey="Unsplash"
40+
onChange={onChangeTab}
41+
items={[
42+
{
43+
label: 'Unsplash',
44+
key: 'unsplash',
45+
children: <UnsplashTab control={control} />,
46+
},
47+
{
48+
label: 'Placehold',
49+
key: 'placehold',
50+
children: <PlaceholdTab control={control} />,
51+
},
52+
]}
53+
/>
54+
</Panel>
55+
<Panel bordered header={<PanelHeader title="プリセット" />}>
56+
<FormRow label="サイズ">
57+
<Select
58+
style={{ width: 250 }}
59+
options={PRESET_SIZE_OPTIONS}
60+
onSelect={onSelectPreset}
61+
/>
62+
</FormRow>
63+
<Button type="primary" onClick={onClickGenerate}>
64+
生成
65+
</Button>
66+
</Panel>
67+
</PanelGroup>
68+
</Form>
69+
</Col>
70+
<Col xs={24} md={12}>
71+
<Panel bordered header={<PanelHeader title="生成画像" />}>
72+
<img src={src} width="100%" loading="lazy" alt="生成画像" />
73+
</Panel>
74+
</Col>
75+
</Row>
76+
</Grid>
77+
</AppLayout>
78+
</FormProvider>
79+
);
80+
};
81+
82+
const CommonForm: FC<{ control: any }> = ({ control }) => {
83+
return (
84+
<>
85+
<FormRow label="高さ">
86+
<Controller
87+
render={({ field }) => (
88+
<InputNumber {...field} style={{ width }} min={SIZE_LIMIT.min} max={SIZE_LIMIT.max} />
89+
)}
90+
name="height"
91+
control={control}
92+
/>
93+
</FormRow>
94+
<FormRow label="幅">
95+
<Controller
96+
render={({ field }) => (
97+
<InputNumber {...field} style={{ width }} min={SIZE_LIMIT.min} max={SIZE_LIMIT.max} />
98+
)}
99+
name="wight"
100+
control={control}
101+
/>
102+
</FormRow>
103+
</>
104+
);
105+
};
106+
107+
const UnsplashTab: FC<{ control: any }> = ({ control }) => {
108+
return (
109+
<>
110+
<CommonForm control={control} />
111+
<FormRow label="拡張子">
112+
<Controller
113+
render={({ field }) => (
114+
<Select
115+
{...field}
116+
style={{ width }}
117+
options={UNSPLASH_FILE_TYPES}
118+
defaultValue={DEFAULT_VALUES.type}
119+
/>
120+
)}
121+
name="type"
122+
control={control}
123+
/>
124+
</FormRow>
125+
</>
126+
);
127+
};
128+
129+
const PlaceholdTab: FC<{ control: any }> = ({ control }) => {
130+
return (
131+
<>
132+
<CommonForm control={control} />
133+
<FormRow label="拡張子">
134+
<Controller
135+
render={({ field }) => (
136+
<Select
137+
{...field}
138+
style={{ width }}
139+
options={PLACEHOLD_FILE_TYPES}
140+
defaultValue={DEFAULT_VALUES.type}
141+
/>
142+
)}
143+
name="type"
144+
control={control}
145+
/>
146+
</FormRow>
147+
</>
148+
);
149+
};

src/app/image_generator/page.tsx

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { FC } from 'react';
2+
import { ImageGenerator } from '@/app/image_generator/ImageGenerator';
3+
4+
export const metadata = {
5+
title: 'Dev Toolkit - 画像生成',
6+
};
7+
8+
const ImageGeneratorPage: FC = () => {
9+
return <ImageGenerator />;
10+
};
11+
12+
export default ImageGeneratorPage;
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
export const PRESET_SIZE = [
2+
{
3+
label: '100x100',
4+
value: {
5+
width: 100,
6+
height: 100,
7+
},
8+
},
9+
{
10+
label: '256x256',
11+
value: {
12+
width: 256,
13+
height: 256,
14+
},
15+
},
16+
] as const satisfies {
17+
label: string;
18+
value: {
19+
width: number;
20+
height: number;
21+
};
22+
}[];
23+
24+
export const PRESET_SIZE_OPTIONS = PRESET_SIZE.map(({ label }) => ({
25+
label,
26+
value: label,
27+
}));
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
import { useCallback, useState } from 'react';
2+
import { PRESET_SIZE } from '@/app/image_generator/presetSize';
3+
import { useCustomForm } from '@/components/common/Form/useCustomForm';
4+
5+
type ImageGeneratorForm = {
6+
wight: number;
7+
height: number;
8+
type: 'jpg' | 'png' | 'webp';
9+
tab: 'unsplash' | 'placehold';
10+
};
11+
12+
export const DEFAULT_VALUES: ImageGeneratorForm = {
13+
wight: 256,
14+
height: 256,
15+
type: 'jpg',
16+
tab: 'unsplash',
17+
};
18+
19+
export const SIZE_LIMIT = {
20+
min: 1,
21+
max: 5000,
22+
};
23+
24+
export const UNSPLASH_FILE_TYPES = [
25+
{
26+
label: 'JPG',
27+
value: 'jpg',
28+
},
29+
{
30+
label: 'Webp',
31+
value: 'webp',
32+
},
33+
];
34+
35+
export const PLACEHOLD_FILE_TYPES = [
36+
{
37+
label: 'JPG',
38+
value: 'jpg',
39+
},
40+
{
41+
label: 'PNG',
42+
value: 'png',
43+
},
44+
];
45+
46+
export const useImageGenerator = () => {
47+
const methods = useCustomForm<ImageGeneratorForm>({
48+
defaultValues: DEFAULT_VALUES,
49+
});
50+
const { getValues, setValue } = methods;
51+
const [src, setSrc] = useState('');
52+
53+
const onClickGenerate = useCallback(() => {
54+
const values = getValues();
55+
switch (values.tab) {
56+
case 'unsplash':
57+
setSrc(createUnsplashURL(values));
58+
break;
59+
case 'placehold':
60+
setSrc(createPlaceholdURL(values));
61+
break;
62+
}
63+
}, [getValues]);
64+
65+
const onSelectPreset = useCallback(
66+
(value: any) => {
67+
const {
68+
value: { width, height },
69+
} = PRESET_SIZE.find(({ label }) => label === value);
70+
setValue('wight', width);
71+
setValue('height', height);
72+
},
73+
[setValue],
74+
);
75+
76+
const onChangeTab = useCallback(
77+
(key: ImageGeneratorForm['tab']) => {
78+
setValue('tab', key);
79+
},
80+
[setValue],
81+
);
82+
83+
return {
84+
methods,
85+
src,
86+
onClickGenerate,
87+
onSelectPreset,
88+
onChangeTab,
89+
};
90+
};
91+
92+
const createUnsplashURL = (values: ImageGeneratorForm) => {
93+
const { wight, height, type } = values;
94+
const params = new URLSearchParams({
95+
random: Math.random().toString(),
96+
});
97+
98+
return `https://picsum.photos/${wight}/${height}.${type}?${params}`;
99+
};
100+
101+
const createPlaceholdURL = (values: ImageGeneratorForm) => {
102+
const { wight, height, type } = values;
103+
104+
return `https://placehold.jp/${wight}x${height}.${type}`;
105+
};

src/app/uuid/Uuid.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import { Switch, Space } from 'antd';
33
import React, { FC } from 'react';
44
import { Controller, FormProvider } from 'react-hook-form';
5-
import { Col, Grid, Panel, Row, PanelGroup, Form, Button, ButtonToolbar } from 'rsuite';
5+
import { Col, Grid, Panel, Row, PanelGroup, Form, Button } from 'rsuite';
66
import { AppLayout } from '@/Layout/App';
77
import { useUuid } from '@/app/uuid/useUuid';
88
import { ClearButton } from '@/components/common/Form/ClearButton';

src/components/common/Features.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,11 @@ export const features: FeatureGroupType[] = [
109109
title: 'UUID',
110110
path: '/uuid',
111111
},
112+
{
113+
key: 'image_generator',
114+
title: '画像生成',
115+
path: '/image_generator',
116+
},
112117
],
113118
},
114119
];

src/styles/rs-custom.globals.scss

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,4 +40,8 @@
4040

4141
.rs-picker-menu {
4242
background-color: var(--color-bg-code);
43+
}
44+
45+
.ant-tabs-tab {
46+
padding: 0 0 12px !important;
4347
}

yarn.lock

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5304,10 +5304,10 @@ typescript-plugin-css-modules@^5.0.0:
53045304
stylus "^0.59.0"
53055305
tsconfig-paths "^4.1.2"
53065306

5307-
typescript@^5.2.2:
5308-
version "5.2.2"
5309-
resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.2.2.tgz#5ebb5e5a5b75f085f22bc3f8460fba308310fa78"
5310-
integrity sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==
5307+
typescript@^5.3.3:
5308+
version "5.3.3"
5309+
resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.3.3.tgz#b3ce6ba258e72e6305ba66f5c9b452aaee3ffe37"
5310+
integrity sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==
53115311

53125312
unbox-primitive@^1.0.2:
53135313
version "1.0.2"

0 commit comments

Comments
 (0)