Skip to content

Commit

Permalink
[Feature] theme 추가 (#1)
Browse files Browse the repository at this point in the history
* test(api/moon.svg): theme-shadow 추가

* test(svg.ts): 이미지 path 통합

* test: theme 선택 UI 추가

* style: input 스타일 추가

* delete: 테스트용 이미지 파일 삭제

* theme를 위한 type 추가

* theme type 기반 디렉토리 구조로 변경

* 브라우저에서 theme를 이용하도록 변경

* shadow 테마 뼈대 코드 추가

* theme 이름 변경 및 shadow 테마에 해당하는 svg 그려주는 로직 추가

* theme에 default 값 추가

Co-authored-by: Minung Han <hmu332233@gmail.com>

* Basic 테마 이름 수정

* <select> className 수정

Co-authored-by: Minung Han <hmu332233@gmail.com>
  • Loading branch information
rayrny and hmu332233 authored May 4, 2022
1 parent da568e0 commit 45a9344
Show file tree
Hide file tree
Showing 9 changed files with 190 additions and 13 deletions.
19 changes: 11 additions & 8 deletions pages/api/moon.svg/index.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,25 @@
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
import type { NextApiRequest, NextApiResponse } from 'next';

import { getMoonPhases } from '../../../server/utils/moon';
import { createMoon } from '../../../server/utils/svg';
import type { Theme } from 'server/theme/types';

import createMoonFuncMap from 'server/theme';
import { isTheme } from 'server/utils/type';

export default async function handler(
req: NextApiRequest,
res: NextApiResponse,
) {
const { date, size = '100', round } = req.query;
const { date, size = '100', theme = 'basic' } = req.query;

if (!isTheme(theme)) {
return res.status(200).end('');
}

const { k, isWaxing } = await getMoonPhases(
date ? new Date(date as string) : undefined,
);
const moonSvg = createMoon(k, isWaxing, size as string, round === 'true');
const createMoon = createMoonFuncMap[theme as Theme];
const moonSvg = await createMoon(date as string, size as string);

res.setHeader('Content-Type', 'image/svg+xml');
res.setHeader('Cache-Control', 's-maxage=3600, max-age=3600');

res.status(200).end(moonSvg);
}
27 changes: 26 additions & 1 deletion pages/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,13 @@ function Home() {
const [isLiveMode, toggleIsLiveMode] = useToggle(true);
const [dateString, setDateString] = useState('');
const [size, setSize] = useState('');
const [theme, setTheme] = useState('basic');

const queryString = useDebounce(
objectToQueryString({
date: isLiveMode ? '' : dateString,
size,
round: true,
theme,
}),
300,
);
Expand All @@ -38,6 +40,14 @@ function Home() {
setSize(value);
};

const handleThemeChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
const {
currentTarget: { value },
} = e;

setTheme(value);
};

const svgUrl = `/moon.svg${queryString}`;

return (
Expand Down Expand Up @@ -70,6 +80,21 @@ function Home() {
/>
</div>
)}
<div className="form-control w-full max-w-xs">
<label className="label">
<span className="label-text">Theme</span>
</label>
<select
id="theme"
name="theme"
className="select input-bordered w-full max-w-xs"
value={theme}
onChange={handleThemeChange}
>
<option value="basic">Basic</option>
<option value="ray">Ray</option>
</select>
</div>
<div className="form-control w-full max-w-xs">
<label className="label">
<span className="label-text">Size</span>
Expand Down
60 changes: 60 additions & 0 deletions server/theme/basic.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import type { CreateMoonFunc } from './types';
import { getMoonPhases } from '../utils/moon';

const createMoon: CreateMoonFunc = async (date: string, size: string) => {
const { k, isWaxing } = await getMoonPhases(
date ? new Date(date as string) : undefined,
);

let percent = k * 100;

if (percent < 1) {
const path = `<path d="m 160 10 a 20 20 0 1 1 0 300 a 20 20 0 1 1 0 -300" style="fill: #000; stroke:black; stroke-width:2" />`;
return `<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="${size}" height="${size}" viewBox="0 0 320 320">${path}</svg>`;
}

if (percent < 5) {
percent = 5;
}

let rx1;
let ry1;
let flag1;
let rx2;
let ry2;
let flag2;

if (isWaxing) {
rx1 = 20;
ry1 = 20;
rx2 = Math.abs(((percent - 50) / 5) * 2);
ry2 = 20;
flag1 = 1;
flag2 = percent < 50 ? 0 : 1;
} else {
rx1 = Math.abs(((percent - 50) / 5) * 2);
ry1 = 20;
rx2 = 20;
ry2 = 20;
flag1 = percent < 50 ? 0 : 1;
flag2 = 1;
}

const background =
'<path d="m 160 10 a 20 20 0 1 1 0 300 a 20 20 0 1 1 0 -300" style="fill: #000; stroke:black; stroke-width:2" />';
const path = `<path d="m 160 10 a ${rx1} ${ry1} 0 1 ${flag1} 0 300 a ${rx2} ${ry2} 0 1 ${flag2} 0 -300" style="fill: #FEFCD7; stroke:black; stroke-width:4" />`;

return `<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="${size}" height="${size}" viewBox="0 0 320 320">${background}${path}</svg>`;
};

// isWaxing percent < 50
// a20 / a20 -> 0 / 1 / 0
// isWaxing percent > 50
// a20 / a0 -> 20 / 1 / 1

// !isWaxing percent < 50
// a20 -> 0 / a20 / 0 / 1
// !isWaxing percent > 50
// a0 -> 20 / a20 / 1 / 1

export default createMoon;
11 changes: 11 additions & 0 deletions server/theme/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import type { CreateMoonFuncMap } from './types';

import basic from './basic';
import ray from './ray';

const createMoonFuncMap: CreateMoonFuncMap = {
basic,
ray,
};

export default createMoonFuncMap;
57 changes: 57 additions & 0 deletions server/theme/ray.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import type { CreateMoonFunc } from './types';
import { getMoonPhases } from '../utils/moon';

const createMoon: CreateMoonFunc = async (date: string, size: string) => {
const { k, isWaxing } = await getMoonPhases(
date ? new Date(date as string) : undefined,
);

let percent = k * 100;

if (percent < 1) {
const path = `<path d="m 160 10 a 20 20 0 1 1 0 300 a 20 20 0 1 1 0 -300" style="fill: #000; stroke:black; stroke-width:2" />`;
return `<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="${size}" height="${size}" viewBox="0 0 320 320">${path}</svg>`;
}

if (percent < 5) {
percent = 5;
}

let rx1;
let ry1;
let flag1;
let rx2;
let ry2;
let flag2;

if (isWaxing) {
rx1 = 20;
ry1 = 20;
rx2 = Math.abs(((percent - 50) / 5) * 2);
ry2 = 20;
flag1 = 1;
flag2 = percent < 50 ? 0 : 1;
} else {
rx1 = Math.abs(((percent - 50) / 5) * 2);
ry1 = 20;
rx2 = 20;
ry2 = 20;
flag1 = percent < 50 ? 0 : 1;
flag2 = 1;
}

const color = '#FEFCD7';
const strokeColor = 'rgba(239, 234, 143)';
const shadow = `<g className="moon-shadow" xmlns="http://www.w3.org/2000/svg" transform="translate(4, 316) scale(0.240, -0.240)" fill="rgba(239, 234, 143, 0.40)" stroke="none">
<path d="M511 1275 c-178 -39 -348 -172 -429 -336 -127 -256 -81 -540 118 -739 251 -252 649 -252 900 0 252 251 252 649 0 900 -155 156 -375 221 -589 175z m60 -119 c10 -27 17 -33 38 -32 14 0 31 7 36 14 9 11 16 11 41 0 27 -11 29 -15 17 -27 -8 -7 -23 -11 -34 -8 -10 3 -19 1 -19 -4 0 -17 73 -42 118 -40 25 1 42 -3 42 -10 0 -6 6 -9 13 -6 7 3 23 -6 36 -19 13 -13 33 -24 45 -24 11 -1 44 -9 71 -19 43 -15 53 -16 71 -4 30 19 45 -3 51 -75 3 -31 9 -60 14 -63 5 -3 10 -30 12 -60 1 -42 -1 -54 -13 -54 -11 0 -14 10 -13 39 3 37 2 38 -26 33 -22 -5 -36 0 -56 17 -15 13 -25 28 -21 34 3 6 -3 13 -14 17 -11 3 -20 11 -20 16 0 6 -15 9 -32 7 -40 -4 -47 -27 -22 -74 16 -30 23 -34 59 -34 37 0 42 -3 56 -37 20 -46 8 -65 -33 -51 -28 10 -32 17 -29 50 2 14 -5 17 -37 14 -46 -4 -75 14 -66 39 7 16 3 17 -34 12 -23 -3 -44 -1 -47 4 -3 4 -12 6 -20 2 -9 -3 -15 1 -15 10 0 9 -8 22 -19 29 -10 7 -19 17 -19 23 -1 15 -20 18 -19 3 1 -44 -6 -69 -27 -89 -20 -20 -22 -26 -12 -43 7 -12 12 -27 11 -33 -2 -7 -3 -17 -4 -22 0 -5 -20 -11 -43 -15 -31 -5 -39 -9 -28 -16 12 -7 12 -12 -3 -28 -23 -25 -21 -32 6 -32 12 0 28 -9 35 -20 7 -12 16 -18 21 -15 12 7 33 -21 31 -42 -4 -33 0 -40 25 -51 14 -6 25 -20 25 -30 0 -31 -24 -72 -42 -72 -10 0 -18 -4 -18 -10 0 -5 -11 -10 -25 -10 -14 0 -25 -2 -25 -4 0 -2 -3 -12 -7 -22 -4 -11 -3 -15 5 -10 6 3 13 2 17 -4 3 -6 -1 -13 -9 -16 -9 -3 -16 -12 -16 -20 0 -8 -5 -14 -10 -14 -13 0 -29 40 -23 56 2 7 -1 15 -8 20 -9 5 -10 2 -6 -9 4 -10 2 -17 -5 -17 -6 0 -8 -11 -4 -30 3 -16 2 -30 -2 -31 -4 0 -20 -2 -36 -4 -34 -5 -85 22 -88 45 0 8 -2 21 -2 29 -1 8 -8 16 -16 19 -8 3 -12 1 -8 -3 4 -4 -4 -19 -18 -34 -23 -24 -26 -25 -35 -8 -7 11 -17 16 -29 12 -15 -5 -57 9 -102 33 -3 2 -4 -7 -1 -19 5 -16 3 -20 -6 -14 -7 4 -11 16 -8 26 3 10 -1 23 -9 30 -8 6 -14 17 -14 24 0 6 -9 20 -20 30 -11 10 -20 15 -20 12 0 -20 -45 35 -67 82 -24 49 -25 61 -20 155 5 104 42 241 65 241 6 0 27 19 47 43 38 44 75 59 55 23 -5 -11 -16 -26 -22 -33 -22 -24 -48 -78 -37 -78 5 0 12 7 15 15 5 12 9 13 20 4 12 -10 13 -9 8 5 -5 12 6 29 35 57 22 22 41 44 41 50 0 5 11 14 25 19 14 5 25 14 25 19 0 12 152 3 165 -10 11 -11 36 -12 34 -1 -4 21 3 33 24 43 23 10 23 12 9 44 -13 27 -20 33 -41 30 -14 -2 -29 -4 -34 -4 -4 -1 -12 -13 -17 -27 -8 -19 -16 -25 -30 -21 -38 10 -108 -13 -137 -46 -31 -34 -63 -47 -63 -24 0 8 8 21 18 31 27 24 132 76 154 76 11 0 16 5 13 10 -3 6 2 10 11 10 10 0 25 6 33 14 16 14 66 34 88 35 6 1 17 -14 24 -33z m392 -51 c47 -30 57 -85 16 -85 -50 0 -107 56 -90 88 12 23 37 22 74 -3z m105 -92 c-5 -23 -23 -20 -26 4 -3 18 0 21 13 17 9 -4 15 -13 13 -21z m-503 -803 c3 -6 -5 -10 -19 -10 -14 0 -28 5 -31 10 -3 6 5 10 19 10 14 0 28 -4 31 -10z
M505 904 c-12 -18 -14 -29 -6 -37 15 -15 41 10 41 39 0 31 -14 30 -35 -2z
M373 713 c-27 -5 -30 -19 -5 -27 9 -3 0 -4 -20 -3 -42 3 -42 3 -28 -24 15 -26 53 -24 87 6 15 13 22 25 16 25 -7 0 -13 7 -13 15 0 8 -3 14 -7 14 -5 -1 -18 -4 -30 -6z
M265 581 c-9 -16 6 -41 26 -41 19 0 25 17 11 35 -10 14 -30 17 -37 6z
M530 539 c0 -5 7 -9 15 -9 8 0 15 -4 15 -10 0 -5 7 -10 16 -10 13 0 14 3 5 14 -14 17 -51 28 -51 15z
"/></g>`;

const path = `<path d="m 160 10 a ${rx1} ${ry1} 0 1 ${flag1} 0 300 a ${rx2} ${ry2} 0 1 ${flag2} 0 -300" style="fill: ${color}; stroke:${strokeColor}; stroke-width:4" />`;
return `<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="${size}" height="${size}" viewBox="0 0 320 320">${path}${shadow}</svg>`;
};

export default createMoon;
6 changes: 6 additions & 0 deletions server/theme/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export const THEMES = ['basic', 'ray'] as const;
export type Theme = typeof THEMES[number];
export type CreateMoonFunc = (date: string, size: string) => Promise<string>;
export type CreateMoonFuncMap = {
[key in Theme]: CreateMoonFunc;
};
18 changes: 15 additions & 3 deletions server/utils/svg.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ export const createMoon = (
isWaxing: boolean,
size: string,
round: boolean,
shadow: boolean,
) => {
let percent = k * 100;

if (percent < 1) {
const path = `<path d="m 160 10 a 20 20 0 1 1 0 300 a 20 20 0 1 1 0 -300" style="fill: #000; stroke:black; stroke-width:2" />`;
return `<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="${size}" height="${size}" viewBox="0 0 320 320">${path}</svg>`;
Expand Down Expand Up @@ -41,8 +41,20 @@ export const createMoon = (
const background = round
? `<path d="m 160 10 a 20 20 0 1 1 0 300 a 20 20 0 1 1 0 -300" style="fill: #000; stroke:black; stroke-width:2" />`
: '';
const path = `<path d="m 160 10 a ${rx1} ${ry1} 0 1 ${flag1} 0 300 a ${rx2} ${ry2} 0 1 ${flag2} 0 -300" style="fill: #FEFCD7; stroke:black; stroke-width:4" />`;
return `<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="${size}" height="${size}" viewBox="0 0 320 320">${background}${path}</svg>`;

const strokeColor = shadow ? 'rgba(239, 234, 143)' : 'black';
const moonShadow = shadow
? `<g className="moon-shadow" xmlns="http://www.w3.org/2000/svg" transform="translate(4, 316) scale(0.240, -0.240)" fill="rgba(239, 234, 143, 0.45)" stroke="none">
<path d="M511 1275 c-178 -39 -348 -172 -429 -336 -127 -256 -81 -540 118 -739 251 -252 649 -252 900 0 252 251 252 649 0 900 -155 156 -375 221 -589 175z m60 -119 c10 -27 17 -33 38 -32 14 0 31 7 36 14 9 11 16 11 41 0 27 -11 29 -15 17 -27 -8 -7 -23 -11 -34 -8 -10 3 -19 1 -19 -4 0 -17 73 -42 118 -40 25 1 42 -3 42 -10 0 -6 6 -9 13 -6 7 3 23 -6 36 -19 13 -13 33 -24 45 -24 11 -1 44 -9 71 -19 43 -15 53 -16 71 -4 30 19 45 -3 51 -75 3 -31 9 -60 14 -63 5 -3 10 -30 12 -60 1 -42 -1 -54 -13 -54 -11 0 -14 10 -13 39 3 37 2 38 -26 33 -22 -5 -36 0 -56 17 -15 13 -25 28 -21 34 3 6 -3 13 -14 17 -11 3 -20 11 -20 16 0 6 -15 9 -32 7 -40 -4 -47 -27 -22 -74 16 -30 23 -34 59 -34 37 0 42 -3 56 -37 20 -46 8 -65 -33 -51 -28 10 -32 17 -29 50 2 14 -5 17 -37 14 -46 -4 -75 14 -66 39 7 16 3 17 -34 12 -23 -3 -44 -1 -47 4 -3 4 -12 6 -20 2 -9 -3 -15 1 -15 10 0 9 -8 22 -19 29 -10 7 -19 17 -19 23 -1 15 -20 18 -19 3 1 -44 -6 -69 -27 -89 -20 -20 -22 -26 -12 -43 7 -12 12 -27 11 -33 -2 -7 -3 -17 -4 -22 0 -5 -20 -11 -43 -15 -31 -5 -39 -9 -28 -16 12 -7 12 -12 -3 -28 -23 -25 -21 -32 6 -32 12 0 28 -9 35 -20 7 -12 16 -18 21 -15 12 7 33 -21 31 -42 -4 -33 0 -40 25 -51 14 -6 25 -20 25 -30 0 -31 -24 -72 -42 -72 -10 0 -18 -4 -18 -10 0 -5 -11 -10 -25 -10 -14 0 -25 -2 -25 -4 0 -2 -3 -12 -7 -22 -4 -11 -3 -15 5 -10 6 3 13 2 17 -4 3 -6 -1 -13 -9 -16 -9 -3 -16 -12 -16 -20 0 -8 -5 -14 -10 -14 -13 0 -29 40 -23 56 2 7 -1 15 -8 20 -9 5 -10 2 -6 -9 4 -10 2 -17 -5 -17 -6 0 -8 -11 -4 -30 3 -16 2 -30 -2 -31 -4 0 -20 -2 -36 -4 -34 -5 -85 22 -88 45 0 8 -2 21 -2 29 -1 8 -8 16 -16 19 -8 3 -12 1 -8 -3 4 -4 -4 -19 -18 -34 -23 -24 -26 -25 -35 -8 -7 11 -17 16 -29 12 -15 -5 -57 9 -102 33 -3 2 -4 -7 -1 -19 5 -16 3 -20 -6 -14 -7 4 -11 16 -8 26 3 10 -1 23 -9 30 -8 6 -14 17 -14 24 0 6 -9 20 -20 30 -11 10 -20 15 -20 12 0 -20 -45 35 -67 82 -24 49 -25 61 -20 155 5 104 42 241 65 241 6 0 27 19 47 43 38 44 75 59 55 23 -5 -11 -16 -26 -22 -33 -22 -24 -48 -78 -37 -78 5 0 12 7 15 15 5 12 9 13 20 4 12 -10 13 -9 8 5 -5 12 6 29 35 57 22 22 41 44 41 50 0 5 11 14 25 19 14 5 25 14 25 19 0 12 152 3 165 -10 11 -11 36 -12 34 -1 -4 21 3 33 24 43 23 10 23 12 9 44 -13 27 -20 33 -41 30 -14 -2 -29 -4 -34 -4 -4 -1 -12 -13 -17 -27 -8 -19 -16 -25 -30 -21 -38 10 -108 -13 -137 -46 -31 -34 -63 -47 -63 -24 0 8 8 21 18 31 27 24 132 76 154 76 11 0 16 5 13 10 -3 6 2 10 11 10 10 0 25 6 33 14 16 14 66 34 88 35 6 1 17 -14 24 -33z m392 -51 c47 -30 57 -85 16 -85 -50 0 -107 56 -90 88 12 23 37 22 74 -3z m105 -92 c-5 -23 -23 -20 -26 4 -3 18 0 21 13 17 9 -4 15 -13 13 -21z m-503 -803 c3 -6 -5 -10 -19 -10 -14 0 -28 5 -31 10 -3 6 5 10 19 10 14 0 28 -4 31 -10z
M505 904 c-12 -18 -14 -29 -6 -37 15 -15 41 10 41 39 0 31 -14 30 -35 -2z
M373 713 c-27 -5 -30 -19 -5 -27 9 -3 0 -4 -20 -3 -42 3 -42 3 -28 -24 15 -26 53 -24 87 6 15 13 22 25 16 25 -7 0 -13 7 -13 15 0 8 -3 14 -7 14 -5 -1 -18 -4 -30 -6z
M265 581 c-9 -16 6 -41 26 -41 19 0 25 17 11 35 -10 14 -30 17 -37 6z
M530 539 c0 -5 7 -9 15 -9 8 0 15 -4 15 -10 0 -5 7 -10 16 -10 13 0 14 3 5 14 -14 17 -51 28 -51 15z
"/></g>`
: '';

const path = `<path d="m 160 10 a ${rx1} ${ry1} 0 1 ${flag1} 0 300 a ${rx2} ${ry2} 0 1 ${flag2} 0 -300" style="fill: #FEFCD7; stroke:${strokeColor}; stroke-width:4" />`;
return `<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="${size}" height="${size}" viewBox="0 0 320 320">${background}${path}${moonShadow}</svg>`;
};

// isWaxing percent < 50
Expand Down
3 changes: 3 additions & 0 deletions server/utils/type.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { THEMES } from 'server/theme/types';

export const isTheme = (theme: any) => THEMES.includes(theme);
2 changes: 1 addition & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
"paths": {
"components/*": ["components/*"],
"utils/*": ["utils/*"],
"hooks/*": ["hooks/*"],
"hooks/*": ["hooks/*"]
}
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
Expand Down

1 comment on commit 45a9344

@vercel
Copy link

@vercel vercel bot commented on 45a9344 May 4, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.