오윤석

Merge branch 'develop'

......@@ -37,7 +37,7 @@ services:
volumes:
- ./node:/app
- ./config:/app/config
command: bash -c "npm install && node app.js"
command: bash -c "npm install && node_modules/.bin/nodemon app.js"
environment:
- NODE_ENV=production
restart: on-failure
......
......@@ -6,6 +6,6 @@ let routes = require('./routes');
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());
app.get('/api/home', routes.home);
app.get('/api/character', routes.character.getCharacter);
let server = app.listen(80);
\ No newline at end of file
......
This diff is collapsed. Click to expand it.
module.exports = {
'STR': {
'korean': '힘'
},
'DEX': {
'korean': '민첩성'
},
'INT': {
'korean': '지력'
},
'LUK': {
'korean': '운'
}
}
\ No newline at end of file
module.exports = {
'한손검': 1.2,
'한손도끼': 1.2,
'한손둔기': 1.2,
'스태프': 1,
'완드': 1,
'샤이닝 로드': 1.2,
'단검': 1.3,
'케인': 1.3,
'데스페라도': 1.3,
'에너지소드': 1.5,
'소울 슈터': 1.7,
'ESP 리미터': 1.2,
'체인': 1.3,
'매직 건틀렛': 1.2,
'부채': 1.3,
'튜너': 1.3,
'두손검': 1.34,
'두손도끼': 1.34,
'두손둔기': 1.34,
'창': 1.34,
'폴암': 1.49,
'태도': 1.34,
'건틀렛 리볼버': 1.7,
'활': 1.3,
'석궁': 1.35,
'듀얼 보우건': 1.3,
'에인션트 보우': 1.3,
'아대': 1.75,
'건': 1.5,
'너클': 1.7,
'핸드캐논': 1.5
}
\ No newline at end of file
This diff is collapsed. Click to expand it.
......@@ -9,7 +9,11 @@
"author": "",
"license": "ISC",
"dependencies": {
"axios": "^0.19.2",
"express": "^4.17.1",
"http": "0.0.1-security"
"http": "0.0.1-security",
"jquery": "^3.5.1",
"jsdom": "^16.2.2",
"nodemon": "^2.0.4"
}
}
......
This diff is collapsed. Click to expand it.
module.exports = function(req, res) {
res.send('this is home');
}
\ No newline at end of file
let routes = {};
routes.home = require('./home');
routes.character = require('./character');
module.exports = routes;
\ No newline at end of file
......
This diff is collapsed. Click to expand it.
......@@ -10,12 +10,13 @@
"@rollup/plugin-commonjs": "11.0.2",
"@rollup/plugin-node-resolve": "^7.0.0",
"rollup": "^1.20.0",
"rollup-plugin-livereload": "^1.0.0",
"rollup-plugin-svelte": "^5.0.3",
"rollup-plugin-terser": "^5.1.2",
"svelte": "^3.0.0"
},
"dependencies": {
"@lottiefiles/svelte-lottie-player": "^0.1.4",
"axios": "^0.19.2",
"rollup-plugin-copy": "^3.3.0",
"sirv-cli": "^0.4.4",
"svelte-spa-router": "^2.1.0"
......
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset='utf-8'>
<meta name='viewport' content='width=device-width,initial-scale=1'>
<title>Svelte app</title>
<head lang="ko">
<meta charset='utf-8'>
<meta name='viewport' content='width=device-width,initial-scale=1'>
<link rel='icon' type='image/png' href='/favicon.png'>
<link rel='stylesheet' href='/global.css'>
<link rel='stylesheet' href='/build/bundle.css'>
<title>::메이플스토리 스펙 계산기::</title>
<script defer src='/build/bundle.js'></script>
<link rel='icon' type='shortcut icon' href='./build/static/images/favicon.ico'>
<link rel='stylesheet' href='./static/css/global.css'>
<link rel='stylesheet' href='/build/bundle.css'>
<meta property="og:image" content="./build/static/images/ogimage.png">
<meta property="og:title" content="::메이플스토리 스펙 계산기::">
<meta property="og:description" content="당신의 메이플스토리 스펙 효율을 빠르게 계산해보세요!">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/css/materialize.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/js/materialize.min.js"></script>
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
<script defer src='/build/bundle.js'></script>
</head>
<body>
</body>
</html>
</html>
\ No newline at end of file
......
html, body {
position: relative;
width: 100%;
height: 100%;
html,
body {
position: relative;
width: 100%;
height: 100%;
background-color: #ffebee;
}
body {
color: #333;
margin: 0;
padding: 8px;
box-sizing: border-box;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
color: #333;
margin: 0;
box-sizing: border-box;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
}
a {
color: rgb(0,100,200);
text-decoration: none;
color: rgb(0, 100, 200);
text-decoration: none;
}
a:hover {
text-decoration: underline;
text-decoration: underline;
}
a:visited {
color: rgb(0,80,160);
color: rgb(0, 80, 160);
}
label {
display: block;
display: block;
}
input, button, select, textarea {
font-family: inherit;
font-size: inherit;
padding: 0.4em;
margin: 0 0 0.5em 0;
box-sizing: border-box;
border: 1px solid #ccc;
border-radius: 2px;
input,
button,
select,
textarea {
font-family: inherit;
font-size: inherit;
padding: 0.4em;
margin: 0 0 0.5em 0;
box-sizing: border-box;
border: 1px solid #ccc;
border-radius: 2px;
}
input:disabled {
color: #ccc;
color: #ccc;
}
input[type="range"] {
height: 0;
height: 0;
}
button {
color: #333;
background-color: #f4f4f4;
outline: none;
color: #333;
background-color: #f4f4f4;
outline: none;
}
button:disabled {
color: #999;
color: #999;
}
button:not(:disabled):active {
background-color: #ddd;
background-color: #ddd;
}
button:focus {
border-color: #666;
}
border-color: #666;
}
\ No newline at end of file
......
import svelte from 'rollup-plugin-svelte';
import resolve from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';
import livereload from 'rollup-plugin-livereload';
import { terser } from 'rollup-plugin-terser';
import copy from 'rollup-plugin-copy';
const production = !process.env.ROLLUP_WATCH;
export default {
input: 'src/main.js',
output: {
sourcemap: true,
format: 'iife',
name: 'app',
file: 'public/build/bundle.js'
},
plugins: [
svelte({
// enable run-time checks when not in production
dev: !production,
// we'll extract any component CSS out into
// a separate file - better for performance
css: css => {
css.write('public/build/bundle.css');
}
}),
input: 'src/main.js',
output: {
sourcemap: true,
format: 'iife',
name: 'app',
file: 'public/build/bundle.js'
},
plugins: [
svelte({
// enable run-time checks when not in production
dev: !production,
// we'll extract any component CSS out into
// a separate file - better for performance
css: css => {
css.write('public/build/bundle.css');
}
}),
// If you have external dependencies installed from
// npm, you'll most likely need these plugins. In
// some cases you'll need additional configuration -
// consult the documentation for details:
// https://github.com/rollup/plugins/tree/master/packages/commonjs
resolve({
browser: true,
dedupe: ['svelte']
}),
commonjs(),
// If you have external dependencies installed from
// npm, you'll most likely need these plugins. In
// some cases you'll need additional configuration -
// consult the documentation for details:
// https://github.com/rollup/plugins/tree/master/packages/commonjs
resolve({
browser: true,
dedupe: ['svelte']
}),
commonjs(),
copy({
targets:[
{ src:'src/images', dest:'public' }
]
}),
copy({
targets: [
{ src: 'src/images', dest: 'public/build/static' }
]
}),
// In dev mode, call `npm run start` once
// the bundle has been generated
!production && serve(),
// In dev mode, call `npm run start` once
// the bundle has been generated
!production && serve(),
// Watch the `public` directory and refresh the
// browser on changes when not in production
!production && livereload('public'),
// If we're building for production (npm run build
// instead of npm run dev), minify
production && terser()
],
watch: {
clearScreen: false
}
// If we're building for production (npm run build
// instead of npm run dev), minify
production && terser()
],
watch: {
clearScreen: false
}
};
function serve() {
let started = false;
let started = false;
return {
writeBundle() {
if (!started) {
started = true;
return {
writeBundle() {
if (!started) {
started = true;
require('child_process').spawn('npm', ['run', 'start', '--', '--dev'], {
stdio: ['ignore', 'inherit', 'inherit'],
shell: true
});
}
}
};
}
require('child_process').spawn('npm', ['run', 'start', '--', '--dev'], {
stdio: ['ignore', 'inherit', 'inherit'],
shell: true
});
}
}
};
}
\ No newline at end of file
......
<script>
import {LottiePlayer} from '@lottiefiles/svelte-lottie-player';
export let count = 0;
</script>
<div id="loading" class:show={count > 0}>
<LottiePlayer
src="https://assets5.lottiefiles.com/packages/lf20_kblbXu.json" background="transparent" speed="1" width={300} height={300} controls={false} controlsLayout={null} loop autoplay>
</LottiePlayer>
</div>
<style>
#loading { position:fixed; width:100%; height:100%; z-index:9999; background-color:rgba(0,0,0,0.3); align-items:center; justify-content:center; display:none; }
#loading.show { display:flex; }
</style>
\ No newline at end of file
No preview for this file type
import App from './App.svelte';
import App from './App.svelte';
const app = new App({
target: document.body,
props: {
name: 'world'
}
target: document.body
});
export default app;
\ No newline at end of file
......
import Home from './routes/Home.svelte';
import Search from './routes/Search.svelte';
import Character from './routes/Character.svelte';
const routes = {
'/': Home,
'/': Search,
'/character': Character,
'/character/:character': Character,
};
export default routes;
\ No newline at end of file
......
<script>
export let params = {};
import {push} from 'svelte-spa-router';
import axios from 'axios';
import Loading from '../components/Loading.svelte';
let callCount = 0;
let isBuff = false;
let isCharacterLoading = false;
let stats;
let character = {
info:{
avatar:'',
nickname:'',
characterCode:'',
job:'',
majorName:'',
attackPowerName:'',
server:{
name:'',
icon:''
},
level:237
},
analysis:{
default:{
stats:{
major:{
pure:0,
percent:0,
added:0,
},
minor:0,
damage:{
all:0,
boss:0
},
finalDamage:0,
criticalDamage:0,
attackPower:{
pure:0,
percent:0,
},
ignoreGuard:0
},
efficiency:{
major:{
pure:1,
percent:1
},
attackPower:{
pure:1,
percent:1
},
damage:1,
criticalDamage:1,
ignoreGuard:1
}
}
},
buff:{
stats:{
major:{
pure:0,
percent:0,
added:0,
},
minor:0,
damage:{
all:0,
boss:0
},
finalDamage:0,
criticalDamage:0,
attackPower:{
pure:0,
percent:0,
},
ignoreGuard:0
},
efficiency:{
major:{
pure:1,
percent:1
},
attackPower:{
pure:1,
percent:1
},
damage:1,
criticalDamage:1,
ignoreGuard:1
}
}
}
function init() {
let nickname;
if(!params.character) {
M.toast({html:"캐릭터명을 입력해주세요."});
push("/");
}
nickname = decodeURI(params.character);
callCount++;
axios.get('/api/character', {
params:{
nickname:nickname
}
}).then(function(response) {
character = response.data;
isCharacterLoading = true;
}).catch(function(error) {
switch(error.response.status) {
case 404:
M.toast({html:"존재하지 않는 캐릭터입니다."});
break;
case 403:
M.toast({html:"캐릭터 정보 공개설정이 필요합니다."});
setTimeout(function() {
window.open("https://maplestory.nexon.com/MyMaple/Account/Character/Visibility");
}, 2000);
break;
case 503:
M.toast({html:"메이플스토리가 점검중입니다."});
break;
default:
M.toast({html:"서버와의 통신이 원활하지 않습니다.<br><br>잠시 후에 시도해주세요."});
break;
}
push('/');
}).finally(function() {
callCount--;
});
}
function showValue(value) {
return parseFloat(value).toFixed(2);
}
function goBack() {
push('/');
}
$:
if(isCharacterLoading && isBuff) {
stats = character.analysis.buff;
} else {
stats = character.analysis.default;
}
init();
</script>
<svelte:head>
</svelte:head>
<Loading count={callCount} />
<section>
{#if isCharacterLoading}
<article class="info-box">
<div class="row">
<div class="col s12 m10 l8 offset-m1 offset-l2">
<div class="row">
<div class="col s12 m4 l3">
<div class="card character-card">
<div class="card-stacked">
<div class="card-content">
<div class="character-img">
<img src={character.info.avatar} class="responsive-img" alt="캐릭터">
</div>
<h6 class="pink-text text-accent-3">
<img src={character.info.server.icon} alt={character.info.server.name}>
{character.info.nickname}
</h6>
<div class="job grey-text text-darken-2">
{character.info.job}
</div>
<div class="level grey-text text-darken-2">
Lv.{character.info.level}
</div>
</div>
</div>
</div>
<div class="back-button-box">
<button class="btn waves-light btn red accent-2" on:click={goBack}>
<i class="material-icons">arrow_back</i>
<span>뒤로가기</span>
</button>
</div>
</div>
<div class="col s12 m8 l9">
<div class="card character-card">
<div class="card-stacked">
<div class="card-content">
<div class="buff-switch">
<div class="switch">
<label>
노버프
<input type="checkbox" bind:checked={isBuff}>
<span class="lever"></span>
버프(자벞,링크,노블,영메)
</label>
</div>
</div>
<table class="table-efficiency">
<thead>
<tr>
<th>스탯</th>
<th>효율</th>
</tr>
</thead>
<tbody>
<tr>
<th rowspan="2">{character.info.majorName} 1%</th>
<td>{character.info.majorName} {showValue(stats.efficiency.major.percent)}</td>
</tr>
<tr>
<td>{character.info.attackPowerName} {showValue(stats.efficiency.major.percent / stats.efficiency.attackPower.pure)}</td>
</tr>
<tr>
<th>{character.info.attackPowerName} 1</th>
<td>{character.info.majorName} {showValue(stats.efficiency.attackPower.pure)}</td>
</tr>
<tr>
<th rowspan="5">{character.info.attackPowerName} 1%</th>
<td>{character.info.majorName} {showValue(stats.efficiency.attackPower.percent)}</td>
</tr>
<tr>
<td>{character.info.majorName} {showValue(stats.efficiency.attackPower.percent / stats.efficiency.major.percent)}%</td>
</tr>
<tr>
<td>{character.info.attackPowerName} {showValue(stats.efficiency.attackPower.percent / stats.efficiency.attackPower.pure)}</td>
</tr>
<tr>
<td>데미지(보공) {showValue(stats.efficiency.attackPower.percent / stats.efficiency.damage)}%</td>
</tr>
<tr>
<td>방무 {showValue(stats.efficiency.attackPower.percent / stats.efficiency.ignoreGuard)}%</td>
</tr>
<tr>
<th rowspan="2">데미지(보공) 1%</th>
<td>{character.info.majorName} {showValue(stats.efficiency.damage)}</td>
</tr>
<tr>
<td>방무 {showValue(stats.efficiency.damage / stats.efficiency.ignoreGuard)}%</td>
</tr>
<tr>
<th rowspan="2">크뎀 1%</th>
<td>{character.info.majorName} {showValue(stats.efficiency.criticalDamage)}</td>
</tr>
<tr>
<td>{character.info.majorName} {showValue(stats.efficiency.criticalDamage / stats.efficiency.major.percent)}%</td>
</tr>
<tr>
<th>방무 1%</th>
<td>{character.info.majorName} {showValue(stats.efficiency.ignoreGuard)}</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</article>
{/if}
</section>
<style>
section { width:100%; height:100%; display:flex; flex-direction:column; padding:20px 0; }
.info-box { margin:auto 0; }
.info-box > .row > .col > .row > .col { margin-bottom:.5rem; }
.character-card .card-content { text-align:center; }
.character-card .card-content .job { font-size:0.8em }
.character-card .card-content .level { font-size:0.8em }
.character-card .card-content h6 img { width:14px; height:14px; }
.character-card .card-content h6 { font-weight:bold; }
.table-efficiency td, .table-efficiency th { text-align:left; }
.back-button-box button { width:100%; display:block; height:48px; line-height:48px; }
.back-button-box button i.material-icons { vertical-align:middle; }
.back-button-box button span { vertical-align:middle; }
.buff-switch .switch label input[type=checkbox]:checked+.lever:after { background-color:#e57373; }
.buff-switch .switch label input[type=checkbox]:not(:checked)+.lever { background-color:#ffcdd2; }
.buff-switch .switch label input[type=checkbox]:checked+.lever { background-color:#ef9a9a ; }
</style>
\ No newline at end of file
<main>
<h1>Hello, This is Home!</h1>
<p>Visit the <a href="https://svelte.dev/tutorial">Svelte tutorial</a> to learn how to build Svelte apps.</p>
</main>
<style>
main {
text-align: center;
padding: 1em;
max-width: 240px;
margin: 0 auto;
}
h1 {
color: #ff3e00;
text-transform: uppercase;
font-size: 4em;
font-weight: 100;
}
@media (min-width: 640px) {
main {
max-width: none;
}
}
</style>
\ No newline at end of file
<script>
import {push} from 'svelte-spa-router';
let character;
function searchCharacter() {
if(!character) {
M.toast({html:"캐릭터명을 입력해주세요."});
return false;
}
push('/character/' + character);
}
</script>
<svelte:head>
</svelte:head>
<section>
<article class="search-box row">
<div class="col l4 m6 s12 offset-l4 offset-m3">
<div class="card">
<div class="card-stacked">
<div class="card-content">
<h4>내 캐릭터 분석하기</h4>
<form class="search-form" on:submit={searchCharacter} onsubmit="return false;">
<div class="input-field">
<label>캐릭터명</label>
<input type="text" bind:value={character}>
<button class="btn waves-effect waves-light red darken-1">확인</button>
</div>
</form>
</div>
<div class="card-action">
<p class="teal-text text-lighten-2">
메이플스토리 스펙 계산기를 이용하기 위해서는 캐릭터 정보 공개(기본정보, 장비, 스킬)가 필요합니다.
</p>
<p>
<a href="https://maplestory.nexon.com/MyMaple/Account/Character/Visibility" target="_BLANK">공개설정하러 가기</a>
</p>
<p class="red-text text-accent-1">
<i class="material-icons">info_outline</i>
<span>제논, 데몬어벤져는 지원하지 않습니다.</span>
</p>
</div>
</div>
</div>
</div>
</article>
</section>
<style>
section { width:100%; height:100%; display:flex; flex-direction:column; }
.search-box { width:100%; margin:auto 0; }
h4 { font-size:20px; text-align:center; }
.input-field { padding-right:70px; }
.input-field button { position:absolute; right:0; top:7px; }
.card-action i.material-icons { font-size:1.1em; vertical-align:middle; }
.card-action i.material-icons ~ span { vertical-align:middle; }
</style>
\ No newline at end of file