From 877874e4bcdf7cacac6fd67b9cdd16cc9666676c Mon Sep 17 00:00:00 2001 From: Sun Jung Kim Date: Wed, 9 Dec 2020 21:05:38 +0900 Subject: [PATCH 01/29] =?UTF-8?q?chore:=20=EA=B0=9C=EB=B0=9C=20=ED=99=98?= =?UTF-8?q?=EA=B2=BD=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - eslint 설정 - prettier 설정 - gitignore와 gitmessage 설정 - server.js로 서버 설정 --- .eslintrc.json | 17 + .gitignore | 3 + .gitmessage.txt | 27 + .prettierrc.js | 18 + package-lock.json | 3362 +++++++++++++++++++++++++++++++++++++++++++++ package.json | 31 + server.js | 41 + 7 files changed, 3499 insertions(+) create mode 100644 .eslintrc.json create mode 100644 .gitignore create mode 100644 .gitmessage.txt create mode 100644 .prettierrc.js create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 server.js diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 000000000..7a3d274b5 --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,17 @@ +{ + "env": { + "browser": true, + "node": true, + "es2020": true + }, + "extends": ["eslint:recommended","airbnb-base", "plugin:prettier/recommended"], + "parserOptions": { + "ecmaVersion": 11, + "sourceType": "module" + }, + "rules": { + "import/extensions": ["off"], + "no-underscore-dangle": ["error", { "allowAfterThis": true }], + "no-plusplus": "off" + } +} diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..c3f8c8ae0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +node_modules +learned.md +workspace.code-workspace \ No newline at end of file diff --git a/.gitmessage.txt b/.gitmessage.txt new file mode 100644 index 000000000..bd0e0e745 --- /dev/null +++ b/.gitmessage.txt @@ -0,0 +1,27 @@ +# <타입>: <제목> + +##### 제목은 최대 50 글자까지만 입력 ############## -> | + + +# 본문은 위에 작성 +######## 본문은 한 줄에 최대 72 글자까지만 입력 ########################### -> | + +# 꼬릿말은 아래에 작성: ex) #이슈 번호 + +# --- COMMIT END --- +# <타입> 리스트 +# feat : 기능 (새로운 기능) +# fix : 버그 (버그 수정) +# refactor: 리팩토링 +# style : 스타일 (코드 형식, 세미콜론 추가: 비즈니스 로직에 변경 없음) +# docs : 문서 (문서 추가, 수정, 삭제) +# test : 테스트 (테스트 코드 추가, 수정, 삭제: 비즈니스 로직에 변경 없음) +# chore : 기타 변경사항 (빌드 스크립트 수정 등) +# ------------------ +# 제목 첫 글자를 대문자로 +# 제목은 명령문으로 +# 제목 끝에 마침표(.) 금지 +# 제목과 본문을 한 줄 띄워 분리하기 +# 본문은 "어떻게" 보다 "무엇을", "왜"를 설명한다. +# 본문에 여러줄의 메시지를 작성할 땐 "-"로 구분 +# ------------------ \ No newline at end of file diff --git a/.prettierrc.js b/.prettierrc.js new file mode 100644 index 000000000..470991bf8 --- /dev/null +++ b/.prettierrc.js @@ -0,0 +1,18 @@ +module.exports = { + singleQuote: true, + // 문자열은 홑따옴표로 formatting + semi: true, + //코드 마지막에 세미콜른이 있게 formatting + useTabs: false, + //탭의 사용을 금하고 스페이스바 사용으로 대체하게 formatting + tabWidth: 2, + // 들여쓰기 너비는 2칸 + trailingComma: 'all', + // 자세한 설명은 구글링이 짱이긴하나 객체나 배열 키:값 뒤에 항상 콤마를 붙히도록 //formatting + printWidth: 80, + // 코드 한줄이 maximum 80칸 + arrowParens: 'avoid', + // 화살표 함수가 하나의 매개변수를 받을 때 괄호를 생략하게 formatting + endOfLine: "auto", + // windows에 뜨는 'Delete cr' 에러 해결 + }; \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 000000000..2598a4664 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,3362 @@ +{ + "name": "javascript-racingcar-precourse", + "version": "1.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@babel/code-frame": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz", + "integrity": "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==", + "dev": true, + "requires": { + "@babel/highlight": "^7.10.4" + } + }, + "@babel/helper-validator-identifier": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz", + "integrity": "sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw==", + "dev": true + }, + "@babel/highlight": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz", + "integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.10.4", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + }, + "dependencies": { + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + } + } + }, + "@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=", + "dev": true + }, + "accepts": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", + "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", + "requires": { + "mime-types": "~2.1.24", + "negotiator": "0.6.2" + } + }, + "acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "dev": true + }, + "acorn-jsx": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.1.tgz", + "integrity": "sha512-K0Ptm/47OKfQRpNQ2J/oIN/3QYiK6FwW+eJbILhsdxh2WTLdl+30o8aGdTbm5JbffpFFAg/g+zi1E+jvJha5ng==", + "dev": true + }, + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ansi-escapes": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.1.tgz", + "integrity": "sha512-JWF7ocqNrp8u9oqpgV+wH5ftbt+cfvv+PTjOvKLT3AdYly/LmORARfEVT1iyjwN+4MqE5UmVKoAdIBqeoCHgLA==", + "dev": true, + "requires": { + "type-fest": "^0.11.0" + }, + "dependencies": { + "type-fest": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.11.0.tgz", + "integrity": "sha512-OdjXJxnCN1AvyLSzeKIgXTXxV+99ZuXl3Hpo9XpJAv9MBcHrrJOQ5kV7ypXOuQie+AmWG25hLbiKdwYTifzcfQ==", + "dev": true + } + } + }, + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "dev": true + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "anymatch": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", + "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", + "requires": { + "micromatch": "^3.1.4", + "normalize-path": "^2.1.1" + }, + "dependencies": { + "normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "requires": { + "remove-trailing-separator": "^1.0.1" + } + } + } + }, + "apache-crypt": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/apache-crypt/-/apache-crypt-1.2.4.tgz", + "integrity": "sha512-Icze5ny5W5uv3xgMgl8U+iGmRCC0iIDrb2PVPuRBtL3Zy1Y5TMewXP1Vtc4r5X9eNNBEk7KYPu0Qby9m/PmcHg==", + "requires": { + "unix-crypt-td-js": "^1.1.4" + } + }, + "apache-md5": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/apache-md5/-/apache-md5-1.1.5.tgz", + "integrity": "sha512-sbLEIMQrkV7RkIruqTPXxeCMkAAycv4yzTkBzRgOR1BrR5UB7qZtupqxkersTJSf0HZ3sbaNRrNV80TnnM7cUw==" + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=" + }, + "arr-flatten": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", + "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==" + }, + "arr-union": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", + "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=" + }, + "array-includes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.2.tgz", + "integrity": "sha512-w2GspexNQpx+PutG3QpT437/BenZBj0M/MZGn5mzv/MofYqo0xmRHzn4lFsoDlWJ+THYsGJmFlW68WlDFx7VRw==", + "dev": true, + "requires": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3", + "es-abstract": "^1.18.0-next.1", + "get-intrinsic": "^1.0.1", + "is-string": "^1.0.5" + } + }, + "array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=" + }, + "array.prototype.flat": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.2.4.tgz", + "integrity": "sha512-4470Xi3GAPAjZqFcljX2xzckv1qeKPizoNkiS0+O4IoPR2ZNpcjE0pkhdihlDouK+x6QOast26B4Q/O9DJnwSg==", + "dev": true, + "requires": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3", + "es-abstract": "^1.18.0-next.1" + } + }, + "assign-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", + "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=" + }, + "astral-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", + "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", + "dev": true + }, + "async-each": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.3.tgz", + "integrity": "sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ==" + }, + "atob": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", + "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==" + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "base": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", + "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", + "requires": { + "cache-base": "^1.0.1", + "class-utils": "^0.3.5", + "component-emitter": "^1.2.1", + "define-property": "^1.0.0", + "isobject": "^3.0.1", + "mixin-deep": "^1.2.0", + "pascalcase": "^0.1.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "basic-auth": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", + "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", + "requires": { + "safe-buffer": "5.1.2" + } + }, + "batch": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", + "integrity": "sha1-3DQxT05nkxgJP8dgJyUl+UvyXBY=" + }, + "bcryptjs": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-2.4.3.tgz", + "integrity": "sha1-mrVie5PmBiH/fNrF2pczAn3x0Ms=" + }, + "binary-extensions": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz", + "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==" + }, + "bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "optional": true, + "requires": { + "file-uri-to-path": "1.0.0" + } + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "requires": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "cache-base": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", + "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", + "requires": { + "collection-visit": "^1.0.0", + "component-emitter": "^1.2.1", + "get-value": "^2.0.6", + "has-value": "^1.0.0", + "isobject": "^3.0.1", + "set-value": "^2.0.0", + "to-object-path": "^0.3.0", + "union-value": "^1.0.0", + "unset-value": "^1.0.0" + } + }, + "call-bind": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.0.tgz", + "integrity": "sha512-AEXsYIyyDY3MCzbwdhzG3Jx1R0J2wetQyUynn6dYHAO+bg8l1k7jwZtRv4ryryFs7EP+NDlikJlVe59jr0cM2w==", + "dev": true, + "requires": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.0" + } + }, + "callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "chardet": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", + "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", + "dev": true + }, + "chokidar": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz", + "integrity": "sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==", + "requires": { + "anymatch": "^2.0.0", + "async-each": "^1.0.1", + "braces": "^2.3.2", + "fsevents": "^1.2.7", + "glob-parent": "^3.1.0", + "inherits": "^2.0.3", + "is-binary-path": "^1.0.0", + "is-glob": "^4.0.0", + "normalize-path": "^3.0.0", + "path-is-absolute": "^1.0.0", + "readdirp": "^2.2.1", + "upath": "^1.1.1" + }, + "dependencies": { + "glob-parent": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", + "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", + "requires": { + "is-glob": "^3.1.0", + "path-dirname": "^1.0.0" + }, + "dependencies": { + "is-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", + "requires": { + "is-extglob": "^2.1.0" + } + } + } + } + } + }, + "class-utils": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", + "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", + "requires": { + "arr-union": "^3.1.0", + "define-property": "^0.2.5", + "isobject": "^3.0.0", + "static-extend": "^0.1.1" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "requires": { + "is-descriptor": "^0.1.0" + } + } + } + }, + "cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "dev": true, + "requires": { + "restore-cursor": "^3.1.0" + } + }, + "cli-width": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", + "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==", + "dev": true + }, + "collection-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", + "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", + "requires": { + "map-visit": "^1.0.0", + "object-visit": "^1.0.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "colors": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", + "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==" + }, + "component-emitter": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", + "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==" + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "confusing-browser-globals": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/confusing-browser-globals/-/confusing-browser-globals-1.0.10.tgz", + "integrity": "sha512-gNld/3lySHwuhaVluJUKLePYirM3QNCKzVxqAdhJII9/WXKVX5PURzMVJspS1jTslSqjeuG4KMVTSouit5YPHA==", + "dev": true + }, + "connect": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/connect/-/connect-3.7.0.tgz", + "integrity": "sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==", + "requires": { + "debug": "2.6.9", + "finalhandler": "1.1.2", + "parseurl": "~1.3.3", + "utils-merge": "1.0.1" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + } + } + }, + "contains-path": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/contains-path/-/contains-path-0.1.0.tgz", + "integrity": "sha1-/ozxhP9mcLa67wGp1IYaXL7EEgo=", + "dev": true + }, + "copy-descriptor": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", + "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=" + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + }, + "cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "requires": { + "object-assign": "^4", + "vary": "^1" + } + }, + "cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, + "debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "decode-uri-component": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", + "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=" + }, + "deep-is": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", + "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", + "dev": true + }, + "define-properties": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", + "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "dev": true, + "requires": { + "object-keys": "^1.0.12" + } + }, + "define-property": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", + "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", + "requires": { + "is-descriptor": "^1.0.2", + "isobject": "^3.0.1" + }, + "dependencies": { + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" + }, + "destroy": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", + "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" + }, + "doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + }, + "duplexer": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", + "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==" + }, + "ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" + }, + "error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "requires": { + "is-arrayish": "^0.2.1" + } + }, + "es-abstract": { + "version": "1.18.0-next.1", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.0-next.1.tgz", + "integrity": "sha512-I4UGspA0wpZXWENrdA0uHbnhte683t3qT/1VFH9aX2dA5PPSf6QW5HHXf5HImaqPmjXaVeVk4RGWnaylmV7uAA==", + "dev": true, + "requires": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.2.2", + "is-negative-zero": "^2.0.0", + "is-regex": "^1.1.1", + "object-inspect": "^1.8.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.1", + "string.prototype.trimend": "^1.0.1", + "string.prototype.trimstart": "^1.0.1" + } + }, + "es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, + "escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + }, + "eslint": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.2.0.tgz", + "integrity": "sha512-B3BtEyaDKC5MlfDa2Ha8/D6DsS4fju95zs0hjS3HdGazw+LNayai38A25qMppK37wWGWNYSPOR6oYzlz5MHsRQ==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "ajv": "^6.10.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.0.1", + "doctrine": "^3.0.0", + "eslint-scope": "^5.1.0", + "eslint-utils": "^2.0.0", + "eslint-visitor-keys": "^1.2.0", + "espree": "^7.1.0", + "esquery": "^1.2.0", + "esutils": "^2.0.2", + "file-entry-cache": "^5.0.1", + "functional-red-black-tree": "^1.0.1", + "glob-parent": "^5.0.0", + "globals": "^12.1.0", + "ignore": "^4.0.6", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "inquirer": "^7.0.0", + "is-glob": "^4.0.0", + "js-yaml": "^3.13.1", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash": "^4.17.14", + "minimatch": "^3.0.4", + "natural-compare": "^1.4.0", + "optionator": "^0.9.1", + "progress": "^2.0.0", + "regexpp": "^3.1.0", + "semver": "^7.2.1", + "strip-ansi": "^6.0.0", + "strip-json-comments": "^3.1.0", + "table": "^5.2.3", + "text-table": "^0.2.0", + "v8-compile-cache": "^2.0.3" + } + }, + "eslint-config-airbnb-base": { + "version": "14.2.1", + "resolved": "https://registry.npmjs.org/eslint-config-airbnb-base/-/eslint-config-airbnb-base-14.2.1.tgz", + "integrity": "sha512-GOrQyDtVEc1Xy20U7vsB2yAoB4nBlfH5HZJeatRXHleO+OS5Ot+MWij4Dpltw4/DyIkqUfqz1epfhVR5XWWQPA==", + "dev": true, + "requires": { + "confusing-browser-globals": "^1.0.10", + "object.assign": "^4.1.2", + "object.entries": "^1.1.2" + } + }, + "eslint-config-prettier": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-6.15.0.tgz", + "integrity": "sha512-a1+kOYLR8wMGustcgAjdydMsQ2A/2ipRPwRKUmfYaSxc9ZPcrku080Ctl6zrZzZNs/U82MjSv+qKREkoq3bJaw==", + "dev": true, + "requires": { + "get-stdin": "^6.0.0" + } + }, + "eslint-import-resolver-node": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.4.tgz", + "integrity": "sha512-ogtf+5AB/O+nM6DIeBUNr2fuT7ot9Qg/1harBfBtaP13ekEWFQEEMP94BCB7zaNW3gyY+8SHYF00rnqYwXKWOA==", + "dev": true, + "requires": { + "debug": "^2.6.9", + "resolve": "^1.13.1" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } + } + }, + "eslint-module-utils": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.6.0.tgz", + "integrity": "sha512-6j9xxegbqe8/kZY8cYpcp0xhbK0EgJlg3g9mib3/miLaExuuwc3n5UEfSnU6hWMbT0FAYVvDbL9RrRgpUeQIvA==", + "dev": true, + "requires": { + "debug": "^2.6.9", + "pkg-dir": "^2.0.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } + } + }, + "eslint-plugin-import": { + "version": "2.22.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.22.1.tgz", + "integrity": "sha512-8K7JjINHOpH64ozkAhpT3sd+FswIZTfMZTjdx052pnWrgRCVfp8op9tbjpAk3DdUeI/Ba4C8OjdC0r90erHEOw==", + "dev": true, + "requires": { + "array-includes": "^3.1.1", + "array.prototype.flat": "^1.2.3", + "contains-path": "^0.1.0", + "debug": "^2.6.9", + "doctrine": "1.5.0", + "eslint-import-resolver-node": "^0.3.4", + "eslint-module-utils": "^2.6.0", + "has": "^1.0.3", + "minimatch": "^3.0.4", + "object.values": "^1.1.1", + "read-pkg-up": "^2.0.0", + "resolve": "^1.17.0", + "tsconfig-paths": "^3.9.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "doctrine": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-1.5.0.tgz", + "integrity": "sha1-N53Ocw9hZvds76TmcHoVmwLFpvo=", + "dev": true, + "requires": { + "esutils": "^2.0.2", + "isarray": "^1.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } + } + }, + "eslint-plugin-prettier": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-3.1.4.tgz", + "integrity": "sha512-jZDa8z76klRqo+TdGDTFJSavwbnWK2ZpqGKNZ+VvweMW516pDUMmQ2koXvxEE4JhzNvTv+radye/bWGBmA6jmg==", + "dev": true, + "requires": { + "prettier-linter-helpers": "^1.0.0" + } + }, + "eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "requires": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + } + }, + "eslint-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", + "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^1.1.0" + } + }, + "eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true + }, + "espree": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.0.tgz", + "integrity": "sha512-dksIWsvKCixn1yrEXO8UosNSxaDoSYpq9reEjZSbHLpT5hpaCAKTLBwq0RHtLrIr+c0ByiYzWT8KTMRzoRCNlw==", + "dev": true, + "requires": { + "acorn": "^7.4.0", + "acorn-jsx": "^5.2.0", + "eslint-visitor-keys": "^1.3.0" + } + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true + }, + "esquery": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.3.1.tgz", + "integrity": "sha512-olpvt9QG0vniUBZspVRN6lwB7hOZoTRtT+jzR+tS4ffYx2mzbw+z0XCOk44aaLYKApNX5nMm+E+P6o25ip/DHQ==", + "dev": true, + "requires": { + "estraverse": "^5.1.0" + }, + "dependencies": { + "estraverse": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", + "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", + "dev": true + } + } + }, + "esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "requires": { + "estraverse": "^5.2.0" + }, + "dependencies": { + "estraverse": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", + "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", + "dev": true + } + } + }, + "estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true + }, + "esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true + }, + "etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" + }, + "event-stream": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/event-stream/-/event-stream-3.3.4.tgz", + "integrity": "sha1-SrTJoPWlTbkzi0w02Gv86PSzVXE=", + "requires": { + "duplexer": "~0.1.1", + "from": "~0", + "map-stream": "~0.1.0", + "pause-stream": "0.0.11", + "split": "0.3", + "stream-combiner": "~0.0.4", + "through": "~2.3.1" + } + }, + "expand-brackets": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", + "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", + "requires": { + "debug": "^2.3.3", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "posix-character-classes": "^0.1.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "^0.1.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + } + } + }, + "extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", + "requires": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + }, + "dependencies": { + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "requires": { + "is-plain-object": "^2.0.4" + } + } + } + }, + "external-editor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", + "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", + "dev": true, + "requires": { + "chardet": "^0.7.0", + "iconv-lite": "^0.4.24", + "tmp": "^0.0.33" + } + }, + "extglob": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", + "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", + "requires": { + "array-unique": "^0.3.2", + "define-property": "^1.0.0", + "expand-brackets": "^2.1.4", + "extend-shallow": "^2.0.1", + "fragment-cache": "^0.2.1", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "^0.1.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "fast-diff": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.2.0.tgz", + "integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==", + "dev": true + }, + "fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", + "dev": true + }, + "faye-websocket": { + "version": "0.11.3", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.3.tgz", + "integrity": "sha512-D2y4bovYpzziGgbHYtGCMjlJM36vAl/y+xUyn1C+FVx8szd1E+86KwVw6XvYSzOP8iMpm1X0I4xJD+QtUb36OA==", + "requires": { + "websocket-driver": ">=0.5.1" + } + }, + "figures": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", + "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", + "dev": true, + "requires": { + "escape-string-regexp": "^1.0.5" + } + }, + "file-entry-cache": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-5.0.1.tgz", + "integrity": "sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g==", + "dev": true, + "requires": { + "flat-cache": "^2.0.1" + } + }, + "file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", + "optional": true + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "requires": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "finalhandler": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", + "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "requires": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "statuses": "~1.5.0", + "unpipe": "~1.0.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + } + } + }, + "find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "dev": true, + "requires": { + "locate-path": "^2.0.0" + } + }, + "flat-cache": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-2.0.1.tgz", + "integrity": "sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA==", + "dev": true, + "requires": { + "flatted": "^2.0.0", + "rimraf": "2.6.3", + "write": "1.0.3" + } + }, + "flatted": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.2.tgz", + "integrity": "sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA==", + "dev": true + }, + "for-in": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", + "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=" + }, + "fragment-cache": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", + "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", + "requires": { + "map-cache": "^0.2.2" + } + }, + "fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" + }, + "from": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/from/-/from-0.1.7.tgz", + "integrity": "sha1-g8YK/Fi5xWmXAH7Rp2izqzA6RP4=" + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "fsevents": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz", + "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==", + "optional": true, + "requires": { + "bindings": "^1.5.0", + "nan": "^2.12.1" + } + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", + "dev": true + }, + "get-intrinsic": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.0.1.tgz", + "integrity": "sha512-ZnWP+AmS1VUaLgTRy47+zKtjTxz+0xMpx3I52i+aalBK1QP19ggLF3Db89KJX7kjfOfP2eoa01qc++GwPgufPg==", + "dev": true, + "requires": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1" + } + }, + "get-stdin": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-6.0.0.tgz", + "integrity": "sha512-jp4tHawyV7+fkkSKyvjuLZswblUtz+SQKzSWnBbii16BuZksJlU1wuBYXY75r+duh/llF1ur6oNwi+2ZzjKZ7g==", + "dev": true + }, + "get-value": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", + "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=" + }, + "glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "glob-parent": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz", + "integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + }, + "globals": { + "version": "12.4.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-12.4.0.tgz", + "integrity": "sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg==", + "dev": true, + "requires": { + "type-fest": "^0.8.1" + } + }, + "graceful-fs": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", + "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==" + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "has-symbols": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", + "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==", + "dev": true + }, + "has-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", + "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", + "requires": { + "get-value": "^2.0.6", + "has-values": "^1.0.0", + "isobject": "^3.0.0" + } + }, + "has-values": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", + "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", + "requires": { + "is-number": "^3.0.0", + "kind-of": "^4.0.0" + }, + "dependencies": { + "kind-of": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", + "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "hosted-git-info": { + "version": "2.8.8", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.8.tgz", + "integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==", + "dev": true + }, + "http-auth": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/http-auth/-/http-auth-3.1.3.tgz", + "integrity": "sha1-lFz63WZSHq+PfISRPTd9exXyTjE=", + "requires": { + "apache-crypt": "^1.1.2", + "apache-md5": "^1.0.6", + "bcryptjs": "^2.3.0", + "uuid": "^3.0.0" + } + }, + "http-errors": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.3.tgz", + "integrity": "sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw==", + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.4", + "setprototypeof": "1.1.1", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.0" + }, + "dependencies": { + "depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" + } + } + }, + "http-parser-js": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.2.tgz", + "integrity": "sha512-opCO9ASqg5Wy2FNo7A0sxy71yGbbkJJXLdgMK04Tcypw9jr2MgWbyubb0+WdmDmGnFflO7fRbqbaihh/ENDlRQ==" + }, + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "ignore": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", + "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "dev": true + }, + "import-fresh": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.2.2.tgz", + "integrity": "sha512-cTPNrlvJT6twpYy+YmKUKrTSjWFs3bjYjAhCwm+z4EOCubZxAuO+hHpRN64TqjEaYSHs7tJAE0w1CKMGmsG/lw==", + "dev": true, + "requires": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + } + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "inquirer": { + "version": "7.3.3", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.3.3.tgz", + "integrity": "sha512-JG3eIAj5V9CwcGvuOmoo6LB9kbAYT8HXffUl6memuszlwDC/qvFAJw49XJ5NROSFNPxp3iQg1GqkFhaY/CR0IA==", + "dev": true, + "requires": { + "ansi-escapes": "^4.2.1", + "chalk": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-width": "^3.0.0", + "external-editor": "^3.0.3", + "figures": "^3.0.0", + "lodash": "^4.17.19", + "mute-stream": "0.0.8", + "run-async": "^2.4.0", + "rxjs": "^6.6.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0", + "through": "^2.3.6" + } + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", + "dev": true + }, + "is-binary-path": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", + "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", + "requires": { + "binary-extensions": "^1.0.0" + } + }, + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" + }, + "is-callable": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.2.tgz", + "integrity": "sha512-dnMqspv5nU3LoewK2N/y7KLtxtakvTuaCsU9FU50/QDmdbHNy/4/JuRtMHqRU22o3q+W89YQndQEeCVwK+3qrA==", + "dev": true + }, + "is-core-module": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.2.0.tgz", + "integrity": "sha512-XRAfAdyyY5F5cOXn7hYQDqh2Xmii+DEfIcQGxK/uNwMHhIkPWO0g8msXcbzLe+MpGoR951MlqM/2iIlU4vKDdQ==", + "dev": true, + "requires": { + "has": "^1.0.3" + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-date-object": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz", + "integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==", + "dev": true + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "requires": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + }, + "dependencies": { + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==" + } + } + }, + "is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=" + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=" + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "is-glob": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", + "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-negative-zero": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.0.tgz", + "integrity": "sha1-lVOxIbD6wohp2p7UWeIMdUN4hGE=", + "dev": true + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "requires": { + "isobject": "^3.0.1" + } + }, + "is-regex": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz", + "integrity": "sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==", + "dev": true, + "requires": { + "has-symbols": "^1.0.1" + } + }, + "is-string": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.5.tgz", + "integrity": "sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ==", + "dev": true + }, + "is-symbol": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz", + "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==", + "dev": true, + "requires": { + "has-symbols": "^1.0.1" + } + }, + "is-windows": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==" + }, + "is-wsl": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", + "integrity": "sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=" + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=" + }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "js-yaml": { + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.0.tgz", + "integrity": "sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", + "dev": true + }, + "json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "dev": true, + "requires": { + "minimist": "^1.2.0" + } + }, + "kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==" + }, + "levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "requires": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + } + }, + "live-server": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/live-server/-/live-server-1.2.1.tgz", + "integrity": "sha512-Yn2XCVjErTkqnM3FfTmM7/kWy3zP7+cEtC7x6u+wUzlQ+1UW3zEYbbyJrc0jNDwiMDZI0m4a0i3dxlGHVyXczw==", + "requires": { + "chokidar": "^2.0.4", + "colors": "^1.4.0", + "connect": "^3.6.6", + "cors": "^2.8.5", + "event-stream": "3.3.4", + "faye-websocket": "0.11.x", + "http-auth": "3.1.x", + "morgan": "^1.9.1", + "object-assign": "^4.1.1", + "opn": "^6.0.0", + "proxy-middleware": "^0.15.0", + "send": "^0.17.1", + "serve-index": "^1.9.1" + } + }, + "load-json-file": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz", + "integrity": "sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "parse-json": "^2.2.0", + "pify": "^2.0.0", + "strip-bom": "^3.0.0" + } + }, + "locate-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", + "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", + "dev": true, + "requires": { + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" + } + }, + "lodash": { + "version": "4.17.20", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", + "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==", + "dev": true + }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "map-cache": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", + "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=" + }, + "map-stream": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/map-stream/-/map-stream-0.1.0.tgz", + "integrity": "sha1-5WqpTEyAVaFkBKBnS3jyFffI4ZQ=" + }, + "map-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", + "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", + "requires": { + "object-visit": "^1.0.0" + } + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + } + }, + "mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" + }, + "mime-db": { + "version": "1.44.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz", + "integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==" + }, + "mime-types": { + "version": "2.1.27", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz", + "integrity": "sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==", + "requires": { + "mime-db": "1.44.0" + } + }, + "mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", + "dev": true + }, + "mixin-deep": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", + "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", + "requires": { + "for-in": "^1.0.2", + "is-extendable": "^1.0.1" + }, + "dependencies": { + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "requires": { + "is-plain-object": "^2.0.4" + } + } + } + }, + "mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "dev": true, + "requires": { + "minimist": "^1.2.5" + } + }, + "morgan": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.0.tgz", + "integrity": "sha512-AbegBVI4sh6El+1gNwvD5YIck7nSA36weD7xvIxG4in80j/UoK8AEGaWnnz8v1GxonMCltmlNs5ZKbGvl9b1XQ==", + "requires": { + "basic-auth": "~2.0.1", + "debug": "2.6.9", + "depd": "~2.0.0", + "on-finished": "~2.3.0", + "on-headers": "~1.0.2" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + } + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "mute-stream": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", + "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", + "dev": true + }, + "nan": { + "version": "2.14.2", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.2.tgz", + "integrity": "sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ==", + "optional": true + }, + "nanomatch": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", + "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "fragment-cache": "^0.2.1", + "is-windows": "^1.0.2", + "kind-of": "^6.0.2", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + } + }, + "natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", + "dev": true + }, + "negotiator": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", + "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==" + }, + "normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "dev": true, + "requires": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + } + } + }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==" + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" + }, + "object-copy": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", + "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", + "requires": { + "copy-descriptor": "^0.1.0", + "define-property": "^0.2.5", + "kind-of": "^3.0.3" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "object-inspect": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.9.0.tgz", + "integrity": "sha512-i3Bp9iTqwhaLZBxGkRfo5ZbE07BQRT7MGu8+nNgwW9ItGp1TzCTw2DLEoWwjClxBjOFI/hWljTAmYGCEwmtnOw==", + "dev": true + }, + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true + }, + "object-visit": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", + "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", + "requires": { + "isobject": "^3.0.0" + } + }, + "object.assign": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", + "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3", + "has-symbols": "^1.0.1", + "object-keys": "^1.1.1" + } + }, + "object.entries": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.3.tgz", + "integrity": "sha512-ym7h7OZebNS96hn5IJeyUmaWhaSM4SVtAPPfNLQEI2MYWCO2egsITb9nab2+i/Pwibx+R0mtn+ltKJXRSeTMGg==", + "dev": true, + "requires": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3", + "es-abstract": "^1.18.0-next.1", + "has": "^1.0.3" + } + }, + "object.pick": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", + "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", + "requires": { + "isobject": "^3.0.1" + } + }, + "object.values": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.2.tgz", + "integrity": "sha512-MYC0jvJopr8EK6dPBiO8Nb9mvjdypOachO5REGk6MXzujbBrAisKo3HmdEI6kZDL6fC31Mwee/5YbtMebixeag==", + "dev": true, + "requires": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3", + "es-abstract": "^1.18.0-next.1", + "has": "^1.0.3" + } + }, + "on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "requires": { + "ee-first": "1.1.1" + } + }, + "on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==" + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "requires": { + "mimic-fn": "^2.1.0" + } + }, + "opn": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/opn/-/opn-6.0.0.tgz", + "integrity": "sha512-I9PKfIZC+e4RXZ/qr1RhgyCnGgYX0UEIlXgWnCOVACIvFgaC9rz6Won7xbdhoHrd8IIhV7YEpHjreNUNkqCGkQ==", + "requires": { + "is-wsl": "^1.1.0" + } + }, + "optionator": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", + "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "dev": true, + "requires": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.3" + } + }, + "os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", + "dev": true + }, + "p-limit": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "dev": true, + "requires": { + "p-try": "^1.0.0" + } + }, + "p-locate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", + "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", + "dev": true, + "requires": { + "p-limit": "^1.1.0" + } + }, + "p-try": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", + "dev": true + }, + "parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "requires": { + "callsites": "^3.0.0" + } + }, + "parse-json": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", + "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", + "dev": true, + "requires": { + "error-ex": "^1.2.0" + } + }, + "parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" + }, + "pascalcase": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", + "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=" + }, + "path-dirname": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz", + "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=" + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" + }, + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true + }, + "path-parse": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", + "dev": true + }, + "path-type": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz", + "integrity": "sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM=", + "dev": true, + "requires": { + "pify": "^2.0.0" + } + }, + "pause-stream": { + "version": "0.0.11", + "resolved": "https://registry.npmjs.org/pause-stream/-/pause-stream-0.0.11.tgz", + "integrity": "sha1-/lo0sMvOErWqaitAPuLnO2AvFEU=", + "requires": { + "through": "~2.3" + } + }, + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + }, + "pkg-dir": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-2.0.0.tgz", + "integrity": "sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s=", + "dev": true, + "requires": { + "find-up": "^2.1.0" + } + }, + "posix-character-classes": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", + "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=" + }, + "prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true + }, + "prettier": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.2.1.tgz", + "integrity": "sha512-PqyhM2yCjg/oKkFPtTGUojv7gnZAoG80ttl45O6x2Ug/rMJw4wcc9k6aaf2hibP7BGVCCM33gZoGjyvt9mm16Q==", + "dev": true + }, + "prettier-linter-helpers": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", + "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", + "dev": true, + "requires": { + "fast-diff": "^1.1.2" + } + }, + "process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + }, + "progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true + }, + "proxy-middleware": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/proxy-middleware/-/proxy-middleware-0.15.0.tgz", + "integrity": "sha1-o/3xvvtzD5UZZYcqwvYHTGFHelY=" + }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true + }, + "range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" + }, + "read-pkg": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz", + "integrity": "sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg=", + "dev": true, + "requires": { + "load-json-file": "^2.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^2.0.0" + } + }, + "read-pkg-up": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-2.0.0.tgz", + "integrity": "sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4=", + "dev": true, + "requires": { + "find-up": "^2.0.0", + "read-pkg": "^2.0.0" + } + }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "readdirp": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", + "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==", + "requires": { + "graceful-fs": "^4.1.11", + "micromatch": "^3.1.10", + "readable-stream": "^2.0.2" + } + }, + "regex-not": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", + "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", + "requires": { + "extend-shallow": "^3.0.2", + "safe-regex": "^1.1.0" + } + }, + "regexpp": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.1.0.tgz", + "integrity": "sha512-ZOIzd8yVsQQA7j8GCSlPGXwg5PfmA1mrq0JP4nGhh54LaKN3xdai/vHUDu74pKwV8OxseMS65u2NImosQcSD0Q==", + "dev": true + }, + "remove-trailing-separator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", + "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=" + }, + "repeat-element": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz", + "integrity": "sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==" + }, + "repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=" + }, + "resolve": { + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.19.0.tgz", + "integrity": "sha512-rArEXAgsBG4UgRGcynxWIWKFvh/XZCcS8UJdHhwy91zwAvCZIbcs+vAbflgBnNjYMs/i/i+/Ux6IZhML1yPvxg==", + "dev": true, + "requires": { + "is-core-module": "^2.1.0", + "path-parse": "^1.0.6" + } + }, + "resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true + }, + "resolve-url": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", + "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=" + }, + "restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "dev": true, + "requires": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + } + }, + "ret": { + "version": "0.1.15", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", + "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==" + }, + "rimraf": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", + "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "run-async": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", + "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==", + "dev": true + }, + "rxjs": { + "version": "6.6.3", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.3.tgz", + "integrity": "sha512-trsQc+xYYXZ3urjOiJOuCOa5N3jAZ3eiSpQB5hIT8zGlL2QfnHLJ2r7GMkBGuIausdJN1OneaI6gQlsqNHHmZQ==", + "dev": true, + "requires": { + "tslib": "^1.9.0" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "safe-regex": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", + "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", + "requires": { + "ret": "~0.1.10" + } + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, + "semver": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.4.tgz", + "integrity": "sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, + "send": { + "version": "0.17.1", + "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", + "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==", + "requires": { + "debug": "2.6.9", + "depd": "~1.1.2", + "destroy": "~1.0.4", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "~1.7.2", + "mime": "1.6.0", + "ms": "2.1.1", + "on-finished": "~2.3.0", + "range-parser": "~1.2.1", + "statuses": "~1.5.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + }, + "dependencies": { + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + } + } + }, + "depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" + } + } + }, + "serve-index": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz", + "integrity": "sha1-03aNabHn2C5c4FD/9bRTvqEqkjk=", + "requires": { + "accepts": "~1.3.4", + "batch": "0.6.1", + "debug": "2.6.9", + "escape-html": "~1.0.3", + "http-errors": "~1.6.2", + "mime-types": "~2.1.17", + "parseurl": "~1.3.2" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" + }, + "http-errors": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.0", + "statuses": ">= 1.4.0 < 2" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, + "setprototypeof": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", + "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==" + } + } + }, + "set-value": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", + "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", + "requires": { + "extend-shallow": "^2.0.1", + "is-extendable": "^0.1.1", + "is-plain-object": "^2.0.3", + "split-string": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "setprototypeof": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", + "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" + }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true + }, + "signal-exit": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", + "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==", + "dev": true + }, + "slice-ansi": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz", + "integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.0", + "astral-regex": "^1.0.0", + "is-fullwidth-code-point": "^2.0.0" + }, + "dependencies": { + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + } + } + }, + "snapdragon": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", + "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", + "requires": { + "base": "^0.11.1", + "debug": "^2.2.0", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "map-cache": "^0.2.2", + "source-map": "^0.5.6", + "source-map-resolve": "^0.5.0", + "use": "^3.1.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "^0.1.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + } + } + }, + "snapdragon-node": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", + "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", + "requires": { + "define-property": "^1.0.0", + "isobject": "^3.0.0", + "snapdragon-util": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "snapdragon-util": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", + "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", + "requires": { + "kind-of": "^3.2.0" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" + }, + "source-map-resolve": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz", + "integrity": "sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==", + "requires": { + "atob": "^2.1.2", + "decode-uri-component": "^0.2.0", + "resolve-url": "^0.2.1", + "source-map-url": "^0.4.0", + "urix": "^0.1.0" + } + }, + "source-map-url": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz", + "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=" + }, + "spdx-correct": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", + "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==", + "dev": true, + "requires": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-exceptions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", + "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", + "dev": true + }, + "spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "dev": true, + "requires": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-license-ids": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.7.tgz", + "integrity": "sha512-U+MTEOO0AiDzxwFvoa4JVnMV6mZlJKk2sBLt90s7G0Gd0Mlknc7kxEn3nuDPNZRta7O2uy8oLcZLVT+4sqNZHQ==", + "dev": true + }, + "split": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/split/-/split-0.3.3.tgz", + "integrity": "sha1-zQ7qXmOiEd//frDwkcQTPi0N0o8=", + "requires": { + "through": "2" + } + }, + "split-string": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", + "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", + "requires": { + "extend-shallow": "^3.0.0" + } + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "static-extend": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", + "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", + "requires": { + "define-property": "^0.2.5", + "object-copy": "^0.1.0" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "requires": { + "is-descriptor": "^0.1.0" + } + } + } + }, + "statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" + }, + "stream-combiner": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/stream-combiner/-/stream-combiner-0.0.4.tgz", + "integrity": "sha1-TV5DPBhSYd3mI8o/RMWGvPXErRQ=", + "requires": { + "duplexer": "~0.1.1" + } + }, + "string-width": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", + "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + } + }, + "string.prototype.trimend": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.3.tgz", + "integrity": "sha512-ayH0pB+uf0U28CtjlLvL7NaohvR1amUvVZk+y3DYb0Ey2PUV5zPkkKy9+U1ndVEIXO8hNg18eIv9Jntbii+dKw==", + "dev": true, + "requires": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3" + } + }, + "string.prototype.trimstart": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.3.tgz", + "integrity": "sha512-oBIBUy5lea5tt0ovtOFiEQaBkoBBkyJhZXzJYrSmDo5IUUqbOPvVezuRs/agBIdZ2p2Eo1FD6bD9USyBLfl3xg==", + "dev": true, + "requires": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.0" + } + }, + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "dev": true + }, + "strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + }, + "table": { + "version": "5.4.6", + "resolved": "https://registry.npmjs.org/table/-/table-5.4.6.tgz", + "integrity": "sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug==", + "dev": true, + "requires": { + "ajv": "^6.10.2", + "lodash": "^4.17.14", + "slice-ansi": "^2.1.0", + "string-width": "^3.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, + "text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", + "dev": true + }, + "through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=" + }, + "tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "dev": true, + "requires": { + "os-tmpdir": "~1.0.2" + } + }, + "to-object-path": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", + "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "to-regex": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", + "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", + "requires": { + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "regex-not": "^1.0.2", + "safe-regex": "^1.1.0" + } + }, + "to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "requires": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + } + }, + "toidentifier": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", + "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==" + }, + "tsconfig-paths": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.9.0.tgz", + "integrity": "sha512-dRcuzokWhajtZWkQsDVKbWyY+jgcLC5sqJhg2PSgf4ZkH2aHPvaOY8YWGhmjb68b5qqTfasSsDO9k7RUiEmZAw==", + "dev": true, + "requires": { + "@types/json5": "^0.0.29", + "json5": "^1.0.1", + "minimist": "^1.2.0", + "strip-bom": "^3.0.0" + } + }, + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, + "type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "requires": { + "prelude-ls": "^1.2.1" + } + }, + "type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "dev": true + }, + "union-value": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", + "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==", + "requires": { + "arr-union": "^3.1.0", + "get-value": "^2.0.6", + "is-extendable": "^0.1.1", + "set-value": "^2.0.1" + } + }, + "unix-crypt-td-js": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/unix-crypt-td-js/-/unix-crypt-td-js-1.1.4.tgz", + "integrity": "sha512-8rMeVYWSIyccIJscb9NdCfZKSRBKYTeVnwmiRYT2ulE3qd1RaDQ0xQDP+rI3ccIWbhu/zuo5cgN8z73belNZgw==" + }, + "unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" + }, + "unset-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", + "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", + "requires": { + "has-value": "^0.3.1", + "isobject": "^3.0.0" + }, + "dependencies": { + "has-value": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", + "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", + "requires": { + "get-value": "^2.0.3", + "has-values": "^0.1.4", + "isobject": "^2.0.0" + }, + "dependencies": { + "isobject": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", + "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", + "requires": { + "isarray": "1.0.0" + } + } + } + }, + "has-values": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", + "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=" + } + } + }, + "upath": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/upath/-/upath-1.2.0.tgz", + "integrity": "sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==" + }, + "uri-js": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.0.tgz", + "integrity": "sha512-B0yRTzYdUCCn9n+F4+Gh4yIDtMQcaJsmYBDsTSG8g/OejKBodLQ2IHfN3bM7jUsRXndopT7OIXWdYqc1fjmV6g==", + "dev": true, + "requires": { + "punycode": "^2.1.0" + } + }, + "urix": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", + "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=" + }, + "use": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", + "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==" + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + }, + "utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" + }, + "uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==" + }, + "v8-compile-cache": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.2.0.tgz", + "integrity": "sha512-gTpR5XQNKFwOd4clxfnhaqvfqMpqEwr4tOtCyz4MtYZX2JYhfr1JvBFKdS+7K/9rfpZR3VLX+YWBbKoxCgS43Q==", + "dev": true + }, + "validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "dev": true, + "requires": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" + }, + "websocket-driver": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", + "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", + "requires": { + "http-parser-js": ">=0.5.1", + "safe-buffer": ">=5.1.0", + "websocket-extensions": ">=0.1.1" + } + }, + "websocket-extensions": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", + "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==" + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "word-wrap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "dev": true + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "write": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/write/-/write-1.0.3.tgz", + "integrity": "sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig==", + "dev": true, + "requires": { + "mkdirp": "^0.5.1" + } + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 000000000..0d077b91e --- /dev/null +++ b/package.json @@ -0,0 +1,31 @@ +{ + "name": "javascript-racingcar-precourse", + "version": "1.0.0", + "description": "- 주어진 횟수 동안 n대의 자동차는 전진 또는 멈출 수 있다.\r - 자동차에 이름을 부여할 수 있다. 전진하는 자동차를 출력할 때 자동차 이름을 같이 출력한다.\r - 자동차 이름은 쉼표(,)를 기준으로 구분하며 이름은 5자 이하만 가능하다.\r - 사용자는 몇 번의 이동을 할 것인지를 입력할 수 있어야 한다.\r - 전진하는 조건은 0에서 9 사이에서 random 값을 구한 후 random 값이 4 이상일 경우 전진하고, 3 이하의 값이면 멈춘다.\r - 자동차 경주 게임을 완료한 후 누가 우승했는지를 알려준다. 우승자는 한 명 이상일 수 있다.\r - 우승자가 여러명일 경우 ,를 이용하여 구분한다.", + "main": "index.js", + "dependencies": { + "live-server": "^1.2.1" + }, + "devDependencies": { + "eslint": "^7.2.0", + "eslint-config-airbnb-base": "^14.2.1", + "eslint-config-prettier": "^6.15.0", + "eslint-plugin-import": "^2.22.1", + "eslint-plugin-prettier": "^3.1.4", + "prettier": "2.2.1" + }, + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1", + "start": "node server.js" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/Sunmon/javascript-racingcar-precourse.git" + }, + "author": "", + "license": "ISC", + "bugs": { + "url": "https://github.com/Sunmon/javascript-racingcar-precourse/issues" + }, + "homepage": "https://github.com/Sunmon/javascript-racingcar-precourse#readme" +} diff --git a/server.js b/server.js new file mode 100644 index 000000000..c7a6f86e0 --- /dev/null +++ b/server.js @@ -0,0 +1,41 @@ +/** + * server 구동을 위한 파일 + * 실행방법: node server.js + */ +// const StaticServer = require('static-server'); + +// const server = new StaticServer({ +// rootPath: '.', // required, the root of the server file tree +// port: 8080, // required, the port to listen +// name: 'car-game-sunmon', // optional, will set "X-Powered-by" HTTP header +// host: '127.0.0.1', // optional, defaults to any interface +// cors: '*', // optional, defaults to undefined +// followSymlink: true, // optional, defaults to a 404 error +// templates: { +// index: 'index.html', // optional, defaults to 'index.html' +// }, +// }); + +// server.start(function () { +// console.log('Server listening to', server.port); +// }); + +const liveServer = require('live-server'); + +const params = { + port: 8080, // Set the server port. Defaults to 8080. + host: '0.0.0.0', // Set the address to bind to. Defaults to 0.0.0.0 or process.env.IP. + root: '.', // Set root directory that's being served. Defaults to cwd. + open: false, // When false, it won't load your browser by default. + ignore: 'scss,my/templates', // comma-separated string for paths to ignore + file: 'index.html', // When set, serve this file (server root relative) for every 404 (useful for single-page applications) + wait: 500, // Waits for all changes, before reloading. Defaults to 0 sec. + mount: [['/components', './node_modules']], // Mount a directory to a route. + logLevel: 0, // 0 = errors only, 1 = some, 2 = lots + middleware: [ + function (req, res, next) { + next(); + }, + ], // Takes an array of Connect-compatible middleware that are injected into the server middleware stack +}; +liveServer.start(params); From aa4481904155defa5adfb4bf1a825e192ce04140 Mon Sep 17 00:00:00 2001 From: Sun Jung Kim Date: Wed, 9 Dec 2020 21:37:27 +0900 Subject: [PATCH 02/29] =?UTF-8?q?docs:=20README=20=EA=B8=B0=EB=8A=A5=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84=20=EB=AA=A9=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 기능 구현 목록: 지하철 역 추가 --- README.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/README.md b/README.md index e97a1d649..a12f495f6 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,16 @@ # 🚇 지하철 노선도 미션 +## 📝 기능 구현 목록 + +### 지하철 역 관련 기능 +- 지하철 역을 등록할 수 있다 +- 지하철 역을 삭제할 수 있다 + - 예외: 노선에 등록된 역은 삭제할 수 없다 +- 역이 노선에 등록되었는지 판단할 수 있다 +- 지하철 역은 2글자 이상이어야 한다 +- 지하철 역의 목록을 조회할 수 있다 + + ## 🚀 기능 요구사항 ### 지하철 역 관련 기능 From 37905b5bcc06c9a81b4c6392f3c409f26f5e39af Mon Sep 17 00:00:00 2001 From: Sun Jung Kim Date: Thu, 10 Dec 2020 21:50:04 +0900 Subject: [PATCH 03/29] =?UTF-8?q?chore:=20README=20=EA=B8=B0=EB=8A=A5=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84=20=EB=AA=A9=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 기능 구현 목록: 화면 관련 기능 추가 - 일부 단어 수정 --- README.md | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index a12f495f6..cafe7cd2a 100644 --- a/README.md +++ b/README.md @@ -2,13 +2,16 @@ ## 📝 기능 구현 목록 +### 화면 관련 기능 +- 각 기능의 버튼을 눌러 화면을 전환한다 + ### 지하철 역 관련 기능 -- 지하철 역을 등록할 수 있다 -- 지하철 역을 삭제할 수 있다 +- 지하철 역을 등록한다 +- 지하철 역을 삭제한다 - 예외: 노선에 등록된 역은 삭제할 수 없다 -- 역이 노선에 등록되었는지 판단할 수 있다 +- 역이 노선에 등록되었는지 판단한다 - 지하철 역은 2글자 이상이어야 한다 -- 지하철 역의 목록을 조회할 수 있다 +- 지하철 역의 목록을 조회한다 ## 🚀 기능 요구사항 From 9576d9755b77bf80098ce2382a4a830707d19141 Mon Sep 17 00:00:00 2001 From: Sun Jung Kim Date: Fri, 11 Dec 2020 07:48:46 +0900 Subject: [PATCH 04/29] =?UTF-8?q?docs:=20README=20=EA=B8=B0=EB=8A=A5=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84=20=EB=AA=A9=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 기능 구현 목록:화면 관련 기능 수정 --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index cafe7cd2a..a7d164ffe 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,10 @@ ### 화면 관련 기능 - 각 기능의 버튼을 눌러 화면을 전환한다 +- 역 관리 버튼을 누르면 지하철 역 관리 화면을 보여준다 +- 노선 관리 버튼을 누르면 노선 관리 화면을 보여준다 +- 구간 관리 버튼을 누르면 구간 관리 화면을 보여준다 +- 노선도 출력 버튼을 누르면 노선도 화면을 보여준다 ### 지하철 역 관련 기능 - 지하철 역을 등록한다 From a140ff72ae5d59a013b8bff074fb72a8e57818e8 Mon Sep 17 00:00:00 2001 From: Sun Jung Kim Date: Fri, 11 Dec 2020 08:16:58 +0900 Subject: [PATCH 05/29] =?UTF-8?q?feat:=20=EB=A9=94=EC=9D=B8=EA=B3=BC=20?= =?UTF-8?q?=EC=A7=80=ED=95=98=EC=B2=A0=EC=97=AD=20=EB=A0=88=EC=9D=B4?= =?UTF-8?q?=EC=95=84=EC=9B=83=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 메인 레이아웃 구현 - 지하철역 관리화면 레이아웃 구현 - 메인 레이아웃 내 섹션으로 각 기능 화면을 이용한다 - '역 관리' 버튼을 누르면 섹션을 지하철역 화면으로 교체한다 --- src/index.js | 27 ++++++++++++++++++ src/layout/mainLayout.js | 24 ++++++++++++++++ src/layout/station.js | 59 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 110 insertions(+) create mode 100644 src/layout/mainLayout.js create mode 100644 src/layout/station.js diff --git a/src/index.js b/src/index.js index e69de29bb..6512ab5e1 100644 --- a/src/index.js +++ b/src/index.js @@ -0,0 +1,27 @@ +import { + app, + managerContainer, + sectionContainer, +} from './layout/mainLayout.js'; +import { stationElements } from './layout/station.js'; + +const initHTML = function () { + app.append(managerContainer, sectionContainer); + managerContainer.append(stationElements.managerButton); + stationElements.managerButton.addEventListener('click', handleStationManagerButton); +}; + +// TODO: html data 속성으로 page 동적으로 바꾸기 +const handleStationManagerButton = function () { + // TODO: 버튼을 누르면 역 바꾸기 + const section = sectionContainer.firstElementChild; + if (!section) { + sectionContainer.appendChild(stationElements.section); + } else { + section.replaceWith(stationElements.section); + } + console.log('station showed') + +}; + +initHTML(); diff --git a/src/layout/mainLayout.js b/src/layout/mainLayout.js new file mode 100644 index 000000000..2fa1d2c6f --- /dev/null +++ b/src/layout/mainLayout.js @@ -0,0 +1,24 @@ +/** + * 전반적인 페이지 골격에 관련된 변수와 함수 모음 + * + */ +export const app = document.querySelector('#app'); +export const managerContainer = document.createElement('div'); +export const sectionContainer = document.createElement('div'); + +const initElements = function () { + managerContainer.id = 'manager-container'; + sectionContainer.id = 'section-container'; +}; + +const appendNodesToDOM = function () { + app.append(managerContainer, sectionContainer); +} + +const buildDefaultPage = function () { + initElements(); + appendNodesToDOM(); + console.log('page build'); +}; + +buildDefaultPage(); diff --git a/src/layout/station.js b/src/layout/station.js new file mode 100644 index 000000000..fdde0281d --- /dev/null +++ b/src/layout/station.js @@ -0,0 +1,59 @@ +/** + * 기능 - 지하철 역 관리 + */ + +const stationManagerButton = document.createElement('button'); +const stationSection = document.createElement('section'); +// TODO: 내부에서만 쓰는 엘리먼트들을 전역변수에서 뺄 수 없을까? +const stationNameContainer = document.createElement('article'); +const stationNameTitle = document.createElement('h3'); +const stationNameInput = document.createElement('input'); +const stationAddButton = document.createElement('button'); + +const stationResultContainer = document.createElement('article'); +const stationResultTitle = document.createElement('h2'); +const stationResultTable = document.createElement('table'); + +const initElements = function () { + stationSection.id = 'station-section'; + stationManagerButton.id = 'station-manager-button'; + stationManagerButton.innerHTML = '1. 역 관리'; + stationNameTitle.innerHTML = '역 이름'; + stationNameInput.id = 'station-name-input'; + stationNameInput.placeholder = '역 이름을 입력해주세요.'; + stationAddButton.id = 'station-add-button'; + stationAddButton.innerHTML = '역 추가'; + stationResultTitle.innerHTML = '🚉 지하철 역 목록'; +}; + +const appendNodesToDOM = function () { + stationSection.append(stationNameContainer, stationResultContainer); + stationNameContainer.append( + stationNameTitle, + stationNameInput, + stationAddButton, + ); + stationResultContainer.append(stationResultTitle, stationResultTable); +}; + +const buildStationSection = function () { + initElements(); + appendNodesToDOM(); + console.log('station section build'); +}; + +buildStationSection(); + +// eslint-disable-next-line import/prefer-default-export +export const stationElements = { + managerButton: stationManagerButton, + section: stationSection, + // nameContainer: stationNameContainer, + // nameContainer: { + // container: stationNameContainer, + // title: stationNameTitle, + // input: stationNameInput, + // addButton: stationAddButton, + // }, + // resultContainer: stationResultContainer, +}; From 82b2cdb99a63039ddbcdbd195e6ec5f6fe7d57a4 Mon Sep 17 00:00:00 2001 From: Sun Jung Kim Date: Fri, 11 Dec 2020 16:42:18 +0900 Subject: [PATCH 06/29] =?UTF-8?q?feat:=20=EB=85=B8=EC=84=A0=EA=B4=80?= =?UTF-8?q?=EB=A6=AC=20=EB=A0=88=EC=9D=B4=EC=95=84=EC=9B=83=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 지하철 노선관리 레이아웃 구현 - '노선 관리' 버튼을 누르면 섹션을 지하철 노선관리 화면으로 교체한다 --- src/index.js | 6 +++- src/layout/line.js | 77 ++++++++++++++++++++++++++++++++++++++++ src/layout/mainLayout.js | 3 +- 3 files changed, 84 insertions(+), 2 deletions(-) create mode 100644 src/layout/line.js diff --git a/src/index.js b/src/index.js index 6512ab5e1..1ec2b0614 100644 --- a/src/index.js +++ b/src/index.js @@ -4,10 +4,14 @@ import { sectionContainer, } from './layout/mainLayout.js'; import { stationElements } from './layout/station.js'; +import { lineElements } from './layout/line.js'; const initHTML = function () { app.append(managerContainer, sectionContainer); - managerContainer.append(stationElements.managerButton); + managerContainer.append( + stationElements.managerButton, + lineElements.managerButton, + ); stationElements.managerButton.addEventListener('click', handleStationManagerButton); }; diff --git a/src/layout/line.js b/src/layout/line.js new file mode 100644 index 000000000..f115c4992 --- /dev/null +++ b/src/layout/line.js @@ -0,0 +1,77 @@ +/** + * 지하철 노선 관련 모듈 + */ + +// 외부에 노출되는 변수 +const lineManagerButton = document.createElement('button'); +const lineSection = document.createElement('section'); + +// 내부 변수 +const lineNameContainer = document.createElement('article'); +const lineNameInput = document.createElement('input'); +const lineStartStationSector = document.createElement('select'); +const lineEndStationSector = document.createElement('select'); +const lineAddButton = document.createElement('button'); + +const lineResultContainer = document.createElement('article'); +const lineResultTable = document.createElement('tb'); + +const replaceSectionToLine = function (section) { + section.replaceWith(lineSection); +}; + +const handleLineManagerButton = function () { + const section = document.querySelector('#section-container > section'); + replaceSectionToLine(section); + console.log('line showed'); +}; + +const initElements = function () { + lineManagerButton.id = 'line-manager-button'; + lineSection.id = 'line-section'; + lineManagerButton.innerHTML = '2. 노선 관리'; + lineManagerButton.addEventListener('click', handleLineManagerButton); + + lineNameInput.id = 'line-name-input'; + lineNameInput.placeholder = '노선 이름을 입력해주세요'; + + lineStartStationSector.id = ' line-start-station-selector'; + lineEndStationSector.id = 'line-end-station-selector'; + + lineAddButton.id = 'line-add-button'; + lineAddButton.innerHTML = '노선 추가'; +}; + +const appendNodesToDOM = function () { + lineSection.append(lineNameContainer, lineResultContainer); + lineNameContainer.append( + lineNameInput, + lineStartStationSector, + lineEndStationSector, + lineAddButton, + ); + lineNameInput.insertAdjacentHTML('beforebegin', '

노선 이름

'); + lineStartStationSector.insertAdjacentHTML( + 'beforebegin', + '

상행 종점

', + ); + lineEndStationSector.insertAdjacentHTML('beforebegin', '

하행 종점

'); + lineResultContainer.append(lineResultTable); + lineResultContainer.insertAdjacentHTML( + 'afterbegin', + '

🚉 지하철 노선 목록

', + ); +}; + +const buildLineSection = function () { + initElements(); + appendNodesToDOM(); + console.log('line section build'); +}; + +buildLineSection(); + +export const lineElements = { + managerButton: lineManagerButton, + section: lineSection, +}; diff --git a/src/layout/mainLayout.js b/src/layout/mainLayout.js index 2fa1d2c6f..45984c34e 100644 --- a/src/layout/mainLayout.js +++ b/src/layout/mainLayout.js @@ -13,7 +13,8 @@ const initElements = function () { const appendNodesToDOM = function () { app.append(managerContainer, sectionContainer); -} + sectionContainer.insertAdjacentHTML('afterbegin', '
'); +}; const buildDefaultPage = function () { initElements(); From 2d24670f3188f7e5f9b13d04cfb1087cbc3e799c Mon Sep 17 00:00:00 2001 From: Sun Jung Kim Date: Fri, 11 Dec 2020 16:44:08 +0900 Subject: [PATCH 07/29] =?UTF-8?q?refactor:=20=ED=99=94=EB=A9=B4=EA=B4=80?= =?UTF-8?q?=EB=A6=AC=20=EB=B2=84=ED=8A=BC=20=EA=B4=80=EB=A0=A8=20=ED=95=A8?= =?UTF-8?q?=EC=88=98=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 화면 관리버튼의 이벤트 리스너를 화면 관련 모듈 내로 이동 - 모듈의 응집성을 고려하여 이동함 - 이름을 좀 더 명확하게 짓기 위하여 새 메소드 생성 --- src/index.js | 14 -------------- src/layout/station.js | 21 +++++++++++++-------- 2 files changed, 13 insertions(+), 22 deletions(-) diff --git a/src/index.js b/src/index.js index 1ec2b0614..4e29a160e 100644 --- a/src/index.js +++ b/src/index.js @@ -12,20 +12,6 @@ const initHTML = function () { stationElements.managerButton, lineElements.managerButton, ); - stationElements.managerButton.addEventListener('click', handleStationManagerButton); -}; - -// TODO: html data 속성으로 page 동적으로 바꾸기 -const handleStationManagerButton = function () { - // TODO: 버튼을 누르면 역 바꾸기 - const section = sectionContainer.firstElementChild; - if (!section) { - sectionContainer.appendChild(stationElements.section); - } else { - section.replaceWith(stationElements.section); - } - console.log('station showed') - }; initHTML(); diff --git a/src/layout/station.js b/src/layout/station.js index fdde0281d..6453a7f4b 100644 --- a/src/layout/station.js +++ b/src/layout/station.js @@ -14,6 +14,18 @@ const stationResultContainer = document.createElement('article'); const stationResultTitle = document.createElement('h2'); const stationResultTable = document.createElement('table'); +// TODO: 나중에 replace가 많이 쓰이면 common으로 뺄 것 +const replaceSectionToStation = function (section) { + section.replaceWith(stationSection); +}; + +// TODO: html data 속성으로 page 동적으로 바꾸기 +const handleStationManagerButton = function () { + const section = document.querySelector('#section-container > section'); + replaceSectionToStation(section); + console.log('station showed'); +}; + const initElements = function () { stationSection.id = 'station-section'; stationManagerButton.id = 'station-manager-button'; @@ -24,6 +36,7 @@ const initElements = function () { stationAddButton.id = 'station-add-button'; stationAddButton.innerHTML = '역 추가'; stationResultTitle.innerHTML = '🚉 지하철 역 목록'; + stationManagerButton.addEventListener('click', handleStationManagerButton); }; const appendNodesToDOM = function () { @@ -48,12 +61,4 @@ buildStationSection(); export const stationElements = { managerButton: stationManagerButton, section: stationSection, - // nameContainer: stationNameContainer, - // nameContainer: { - // container: stationNameContainer, - // title: stationNameTitle, - // input: stationNameInput, - // addButton: stationAddButton, - // }, - // resultContainer: stationResultContainer, }; From 7581ce6990938989e26e6dd5e400f05bb9ed8890 Mon Sep 17 00:00:00 2001 From: Sun Jung Kim Date: Fri, 11 Dec 2020 21:50:44 +0900 Subject: [PATCH 08/29] =?UTF-8?q?feat:=20=EC=A7=80=ED=95=98=EC=B2=A0=20?= =?UTF-8?q?=EA=B5=AC=EA=B0=84=20=EA=B4=80=EB=A6=AC=20=EB=A0=88=EC=9D=B4?= =?UTF-8?q?=EC=95=84=EC=9B=83=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 구간 관리 레이아웃 구현 - '구간 관리' 버튼을 누르면 레이아웃이 전환되는 기능 구현 --- src/index.js | 2 ++ src/layout/section.js | 72 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 74 insertions(+) create mode 100644 src/layout/section.js diff --git a/src/index.js b/src/index.js index 4e29a160e..5eeee7e92 100644 --- a/src/index.js +++ b/src/index.js @@ -5,12 +5,14 @@ import { } from './layout/mainLayout.js'; import { stationElements } from './layout/station.js'; import { lineElements } from './layout/line.js'; +import { sectionElements } from './layout/section.js'; const initHTML = function () { app.append(managerContainer, sectionContainer); managerContainer.append( stationElements.managerButton, lineElements.managerButton, + sectionElements.managerButton, ); }; diff --git a/src/layout/section.js b/src/layout/section.js new file mode 100644 index 000000000..3d1cc9f0b --- /dev/null +++ b/src/layout/section.js @@ -0,0 +1,72 @@ +/** + * 지하철 노선 관련 모듈 + */ + +// 외부에 노출되는 변수 +const sectionManagerButton = document.createElement('button'); +const sectionSection = document.createElement('section'); + +// 내부 변수 +const sectionLineMenuButtonContainer = document.createElement('div'); +const sectionOrderContainer = document.createElement('div'); +const sectionStationSelector = document.createElement('select'); +const sectionOrderInput = document.createElement('input'); +const sectionAddButton = document.createElement('button'); +const sectionResultContainer = document.createElement('div'); + +const showPageInsteadOf = function (section) { + section.replaceWith(sectionSection); +}; + +const handleSectionManagerButton = function () { + const section = document.querySelector('#section-container > section'); + showPageInsteadOf(section); + console.log('section showed'); +}; + +const initElements = function () { + sectionManagerButton.id = 'section-manager-button'; + sectionManagerButton.innerHTML = '3. 구간 관리'; + sectionManagerButton.addEventListener('click', handleSectionManagerButton); + sectionSection.id = 'section-section'; + sectionOrderInput.id = 'section-order-input'; + sectionOrderInput.placeholder = '순서'; + sectionAddButton.id = 'section-add-button'; + sectionAddButton.innerHTML = '등록'; +}; + +const appendNodesToDOM = function () { + sectionSection.append( + sectionLineMenuButtonContainer, + sectionOrderContainer, + sectionResultContainer, + ); + sectionOrderContainer.append( + sectionStationSelector, + sectionOrderInput, + sectionAddButton, + ); + + sectionLineMenuButtonContainer.insertAdjacentHTML( + 'beforebegin', + '

구간을 수정할 노선을 선택해주세요.

', + ); + sectionOrderContainer.insertAdjacentHTML( + 'beforebegin', + '

n호선 관리

', + ); + sectionOrderContainer.insertAdjacentHTML('afterbegin', '

구간 등록

'); +}; + +const buildsectionSection = function () { + initElements(); + appendNodesToDOM(); + console.log('section section build'); +}; + +buildsectionSection(); + +export const sectionElements = { + managerButton: sectionManagerButton, + section: sectionSection, +}; From 542f5d9b86a37f223d1595607ae68c96560ad6c9 Mon Sep 17 00:00:00 2001 From: Sun Jung Kim Date: Fri, 11 Dec 2020 22:08:38 +0900 Subject: [PATCH 09/29] =?UTF-8?q?feat:=20=EC=A7=80=ED=95=98=EC=B2=A0=20?= =?UTF-8?q?=EB=85=B8=EC=84=A0=EB=8F=84=20=EC=B6=9C=EB=A0=A5=20=EB=A0=88?= =?UTF-8?q?=EC=9D=B4=EC=95=84=EC=9B=83=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 지하철 노선도 출력 레이아웃 구현 - '지하철 노선도 출력' 버튼을 누르면 레이아웃 전환 --- src/index.js | 2 ++ src/layout/mapPrintLayout.js | 50 ++++++++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+) create mode 100644 src/layout/mapPrintLayout.js diff --git a/src/index.js b/src/index.js index 5eeee7e92..92551b179 100644 --- a/src/index.js +++ b/src/index.js @@ -6,6 +6,7 @@ import { import { stationElements } from './layout/station.js'; import { lineElements } from './layout/line.js'; import { sectionElements } from './layout/section.js'; +import { mapPrintElements } from './layout/mapPrintLayout.js'; const initHTML = function () { app.append(managerContainer, sectionContainer); @@ -13,6 +14,7 @@ const initHTML = function () { stationElements.managerButton, lineElements.managerButton, sectionElements.managerButton, + mapPrintElements.managerButton, ); }; diff --git a/src/layout/mapPrintLayout.js b/src/layout/mapPrintLayout.js new file mode 100644 index 000000000..ebafae5e8 --- /dev/null +++ b/src/layout/mapPrintLayout.js @@ -0,0 +1,50 @@ +/** + * 지하철 노선도 출력과 관련된 레이아웃을 관리하는 모듈 + */ + +const mapPrintManagerButton = document.createElement('button'); +const mapPrintSection = document.createElement('section'); +// TODO: 내부에서만 쓰는 엘리먼트들을 전역변수에서 뺄 수 없을까? + +const mapPrintResultContainer = document.createElement('div'); +const mapPrintContainer = document.createElement('div'); + +// TODO: 나중에 replace가 많이 쓰이면 common으로 뺄 것 +const showPageInsteadOf = function (section) { + section.replaceWith(mapPrintSection); +}; + +// TODO: html data 속성으로 page 동적으로 바꾸기 +const handlemapPrintManagerButton = function () { + const section = document.querySelector('#section-container > section'); + showPageInsteadOf(section); + console.log('mapPrint showed'); +}; + +const initElements = function () { + mapPrintManagerButton.id = 'map-print-manager-button'; + mapPrintManagerButton.innerHTML = '4. 지하철 노선도 출력'; + mapPrintManagerButton.addEventListener('click', handlemapPrintManagerButton); + mapPrintContainer.className = 'map'; +}; + +const appendNodesToDOM = function () { + mapPrintSection.append(mapPrintResultContainer); + mapPrintResultContainer.append(mapPrintContainer); //노선 개수만큼 append + mapPrintContainer.insertAdjacentHTML('afterbegin', '

n호선

'); + mapPrintContainer.insertAdjacentHTML('beforeend', '
  • 인천
'); +}; + +const buildMapPrintSection = function () { + initElements(); + appendNodesToDOM(); + console.log('MapPrint section build'); +}; + +buildMapPrintSection(); + +// eslint-disable-next-line import/prefer-default-export +export const mapPrintElements = { + managerButton: mapPrintManagerButton, + section: mapPrintSection, +}; From 689747cf93566fb309d7adcf2494363e87858ab7 Mon Sep 17 00:00:00 2001 From: Sun Jung Kim Date: Sun, 13 Dec 2020 13:20:17 +0900 Subject: [PATCH 10/29] =?UTF-8?q?feat:=20=EC=A7=80=ED=95=98=EC=B2=A0?= =?UTF-8?q?=EC=97=AD=20=EB=93=B1=EB=A1=9D=20=EB=B2=84=ED=8A=BC=20=ED=81=B4?= =?UTF-8?q?=EB=A6=AD=EC=8B=9C=20=ED=91=9C=20=ED=95=9C=EC=A4=84=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 지하철 열 등록 버튼을 누르면 표 행 추가 - 아직 데이터를 저장하는 기능은 구현하지 않음 - section의 오타 수정 --- src/layout/section.js | 4 ++-- src/layout/station.js | 26 ++++++++++++++++++++++++++ 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/src/layout/section.js b/src/layout/section.js index 3d1cc9f0b..e07268c67 100644 --- a/src/layout/section.js +++ b/src/layout/section.js @@ -58,13 +58,13 @@ const appendNodesToDOM = function () { sectionOrderContainer.insertAdjacentHTML('afterbegin', '

구간 등록

'); }; -const buildsectionSection = function () { +const buildSectionSection = function () { initElements(); appendNodesToDOM(); console.log('section section build'); }; -buildsectionSection(); +buildSectionSection(); export const sectionElements = { managerButton: sectionManagerButton, diff --git a/src/layout/station.js b/src/layout/station.js index 6453a7f4b..e407c655c 100644 --- a/src/layout/station.js +++ b/src/layout/station.js @@ -14,6 +14,10 @@ const stationResultContainer = document.createElement('article'); const stationResultTitle = document.createElement('h2'); const stationResultTable = document.createElement('table'); +/** + * 레이아웃에 관련된 메소드 + */ + // TODO: 나중에 replace가 많이 쓰이면 common으로 뺄 것 const replaceSectionToStation = function (section) { section.replaceWith(stationSection); @@ -37,6 +41,8 @@ const initElements = function () { stationAddButton.innerHTML = '역 추가'; stationResultTitle.innerHTML = '🚉 지하철 역 목록'; stationManagerButton.addEventListener('click', handleStationManagerButton); + stationResultTable.innerHTML = + '역이름설정'; }; const appendNodesToDOM = function () { @@ -62,3 +68,23 @@ export const stationElements = { managerButton: stationManagerButton, section: stationSection, }; + +/** + * 데이터를 관리하는 메소드 + * TODO: Controller로 이전 + */ + +const getStationNameFromUser = function () { + return stationNameInput.value; +}; + +const addRow = function () { + const row = stationResultTable.insertRow(); + row.insertCell(0).innerHTML = getStationNameFromUser(); + row.insertCell( + 1, + ).innerHTML = ``; +}; + + +stationAddButton.addEventListener('click', addRow); \ No newline at end of file From 8a8721f999fbb97d3a40ef687e1f753088b7313a Mon Sep 17 00:00:00 2001 From: Sunmon Date: Sun, 13 Dec 2020 19:47:23 +0900 Subject: [PATCH 11/29] =?UTF-8?q?refactor:=20=EB=A0=88=EC=9D=B4=EC=95=84?= =?UTF-8?q?=EC=9B=83=20=ED=81=B4=EB=9E=98=EC=8A=A4=20=EB=B3=80=ED=99=98(st?= =?UTF-8?q?ation=20->=20stationLayout)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - station.js -> stationLayout.js 로 변환 - 지하철역 레이아웃은 '클래스' 로 작성 - 상위클래스 pageLayout 추가 - MVC패턴을 이용하는 controller 추가 - 비즈니스 로직은 일단 controller에서 관리함 --- src/controllers/controller.js | 30 +++++++++ src/index.js | 8 ++- src/layout/pageLayout.js | 27 ++++++++ src/layout/station.js | 90 -------------------------- src/layout/stationLayout.js | 117 ++++++++++++++++++++++++++++++++++ 5 files changed, 180 insertions(+), 92 deletions(-) create mode 100644 src/controllers/controller.js create mode 100644 src/layout/pageLayout.js delete mode 100644 src/layout/station.js create mode 100644 src/layout/stationLayout.js diff --git a/src/controllers/controller.js b/src/controllers/controller.js new file mode 100644 index 000000000..d3396771e --- /dev/null +++ b/src/controllers/controller.js @@ -0,0 +1,30 @@ +import StationLayout from '../layout/stationLayout.js'; + +export default class Controller { + constructor() { + this.viewList = { + station: new StationLayout(this), + }; + this.currentView = ''; + this.currentView = this.viewList.station; + } + + /** + * view 안 input 태그를 찾아 값을 반환한다 + * @param {PageLayout} view + */ + getInputFromUser(view) { + return view.elements.inputContainer.querySelector('input').value; + } + + setCurrentView(view) { + this.currentView = this.replaceCurrentView(view); + } + + replaceCurrentView(view) { + const currentSection = document.querySelector('section'); + currentSection.replaceWith(view.elements.section); + + return view; + } +} diff --git a/src/index.js b/src/index.js index 92551b179..bbe6e00fa 100644 --- a/src/index.js +++ b/src/index.js @@ -3,15 +3,19 @@ import { managerContainer, sectionContainer, } from './layout/mainLayout.js'; -import { stationElements } from './layout/station.js'; +import StationLayout from './layout/stationLayout.js'; import { lineElements } from './layout/line.js'; import { sectionElements } from './layout/section.js'; import { mapPrintElements } from './layout/mapPrintLayout.js'; +import Controller from './controllers/controller.js'; const initHTML = function () { + // const stationLayout = new StationLayout(); + const controller = new Controller(); app.append(managerContainer, sectionContainer); managerContainer.append( - stationElements.managerButton, + // stationElements.managerButton, + controller.viewList.station.elements.managerButton, lineElements.managerButton, sectionElements.managerButton, mapPrintElements.managerButton, diff --git a/src/layout/pageLayout.js b/src/layout/pageLayout.js new file mode 100644 index 000000000..2bda18a23 --- /dev/null +++ b/src/layout/pageLayout.js @@ -0,0 +1,27 @@ +export default class PageLayout { + constructor(controller) { + this.elements = { + managerButton: '', + section: '', + inputContainer: '', + resultContainer: '', + }; + this.controller = controller; + this.elements = this.createElements(); + this.buildLayout(); + } + + handleManagerButton() { + this.controller.setCurrentView(this); + console.log(`${this.constructor.name} showed!`); + } + + // TODO: template 을 이용하여 생성하도록 수정하기 + createElements() { + console.log(`${this.constructor.name} created`); + } + + buildLayout() { + console.log(`${this.constructor.name} build!`); + } +} diff --git a/src/layout/station.js b/src/layout/station.js deleted file mode 100644 index e407c655c..000000000 --- a/src/layout/station.js +++ /dev/null @@ -1,90 +0,0 @@ -/** - * 기능 - 지하철 역 관리 - */ - -const stationManagerButton = document.createElement('button'); -const stationSection = document.createElement('section'); -// TODO: 내부에서만 쓰는 엘리먼트들을 전역변수에서 뺄 수 없을까? -const stationNameContainer = document.createElement('article'); -const stationNameTitle = document.createElement('h3'); -const stationNameInput = document.createElement('input'); -const stationAddButton = document.createElement('button'); - -const stationResultContainer = document.createElement('article'); -const stationResultTitle = document.createElement('h2'); -const stationResultTable = document.createElement('table'); - -/** - * 레이아웃에 관련된 메소드 - */ - -// TODO: 나중에 replace가 많이 쓰이면 common으로 뺄 것 -const replaceSectionToStation = function (section) { - section.replaceWith(stationSection); -}; - -// TODO: html data 속성으로 page 동적으로 바꾸기 -const handleStationManagerButton = function () { - const section = document.querySelector('#section-container > section'); - replaceSectionToStation(section); - console.log('station showed'); -}; - -const initElements = function () { - stationSection.id = 'station-section'; - stationManagerButton.id = 'station-manager-button'; - stationManagerButton.innerHTML = '1. 역 관리'; - stationNameTitle.innerHTML = '역 이름'; - stationNameInput.id = 'station-name-input'; - stationNameInput.placeholder = '역 이름을 입력해주세요.'; - stationAddButton.id = 'station-add-button'; - stationAddButton.innerHTML = '역 추가'; - stationResultTitle.innerHTML = '🚉 지하철 역 목록'; - stationManagerButton.addEventListener('click', handleStationManagerButton); - stationResultTable.innerHTML = - '역이름설정'; -}; - -const appendNodesToDOM = function () { - stationSection.append(stationNameContainer, stationResultContainer); - stationNameContainer.append( - stationNameTitle, - stationNameInput, - stationAddButton, - ); - stationResultContainer.append(stationResultTitle, stationResultTable); -}; - -const buildStationSection = function () { - initElements(); - appendNodesToDOM(); - console.log('station section build'); -}; - -buildStationSection(); - -// eslint-disable-next-line import/prefer-default-export -export const stationElements = { - managerButton: stationManagerButton, - section: stationSection, -}; - -/** - * 데이터를 관리하는 메소드 - * TODO: Controller로 이전 - */ - -const getStationNameFromUser = function () { - return stationNameInput.value; -}; - -const addRow = function () { - const row = stationResultTable.insertRow(); - row.insertCell(0).innerHTML = getStationNameFromUser(); - row.insertCell( - 1, - ).innerHTML = ``; -}; - - -stationAddButton.addEventListener('click', addRow); \ No newline at end of file diff --git a/src/layout/stationLayout.js b/src/layout/stationLayout.js new file mode 100644 index 000000000..b2130f479 --- /dev/null +++ b/src/layout/stationLayout.js @@ -0,0 +1,117 @@ +/** + * 지하철 역 관리 레이아웃 + */ + +import PageLayout from './pageLayout.js'; + +export default class StationLayout extends PageLayout { + createManagerButton() { + const stationManagerButton = document.createElement('button'); + stationManagerButton.id = 'station-manager-button'; + stationManagerButton.innerHTML = '1. 역 관리'; + // TODO: 이거 공통클래스로 뺄수있지않나? + stationManagerButton.addEventListener('click', () => + this.handleManagerButton(), + ); + + return stationManagerButton; + } + + createSection() { + const stationSection = document.createElement('section'); + stationSection.id = 'station-section'; + + return stationSection; + } + + createInput() { + const stationNameInput = document.createElement('input'); + stationNameInput.id = 'station-name-input'; + stationNameInput.placeholder = '역 이름을 입력해주세요.'; + + return stationNameInput; + } + + createInputTitle() { + const stationNameTitle = document.createElement('h3'); + stationNameTitle.innerHTML = '역 이름'; + + return stationNameTitle; + } + + createInputAddButton() { + const stationAddButton = document.createElement('button'); + stationAddButton.id = 'station-add-button'; + stationAddButton.innerHTML = '역 추가'; + stationAddButton.addEventListener('click', () => this.addRow()); + return stationAddButton; + } + + createInputContainer() { + const stationNameContainer = document.createElement('article'); + + stationNameContainer.append( + this.createInputTitle(), + this.createInput(), + this.createInputAddButton(), + ); + + return stationNameContainer; + } + + createResultContainer() { + const stationResultContainer = document.createElement('article'); + const stationResultTitle = this.createResultTitle(); + const stationResultTable = this.createResultTable(); + + stationResultContainer.append(stationResultTitle, stationResultTable); + + return stationResultContainer; + } + + createResultTitle() { + const stationResultTitle = document.createElement('h2'); + stationResultTitle.innerHTML = '🚉 지하철 역 목록'; + + return stationResultTitle; + } + + createResultTable() { + const stationResultTable = document.createElement('table'); + stationResultTable.innerHTML = + '역이름설정'; + + return stationResultTable; + } + + addRow() { + // TODO: 자식구조도 object로 돌릴수있겠는데? + // TODO: controller 로 옮기기 + // TODO: VDOM 쓰면 한번에 append하지않는방향으로.. + const row = this.elements.resultContainer + .querySelector('table') + .insertRow(); + row.insertCell(0).innerHTML = this.controller.getInputFromUser(this); + row.insertCell( + 1, + ).innerHTML = ``; + this.elements.inputContainer.querySelector('input').value = ''; + } + + // override + createElements() { + const managerButton = this.createManagerButton(); + const section = this.createSection(); + const inputContainer = this.createInputContainer(); + const resultContainer = this.createResultContainer(); + + return { managerButton, section, inputContainer, resultContainer }; + } + + // override + buildLayout() { + const { section, inputContainer, resultContainer } = this.elements; + + section.append(inputContainer, resultContainer); + } +} From 00c55a3edd6b86d027255b8bfec059dcb85f4975 Mon Sep 17 00:00:00 2001 From: Sunmon Date: Sun, 13 Dec 2020 22:26:20 +0900 Subject: [PATCH 12/29] =?UTF-8?q?feat:=20=EC=A7=80=ED=95=98=EC=B2=A0?= =?UTF-8?q?=EC=97=AD=20=EC=A0=80=EC=9E=A5=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 지하철역 '추가' 버튼을 누르면 테이블에 추가 - 지하철역 '추가' 버튼을 누르면 localStorage에 저장 - '역관리' 버튼을 누르면 localStorage에 저장한 정보를 테이블에 표시 --- README.md | 3 +++ src/controllers/controller.js | 7 +++++++ src/layout/pageLayout.js | 9 +++++++++ src/layout/stationLayout.js | 29 +++++++++++++++++++++++++---- src/model/stationModel.js | 23 +++++++++++++++++++++++ 5 files changed, 67 insertions(+), 4 deletions(-) create mode 100644 src/model/stationModel.js diff --git a/README.md b/README.md index a7d164ffe..c8f58e431 100644 --- a/README.md +++ b/README.md @@ -11,11 +11,14 @@ ### 지하철 역 관련 기능 - 지하철 역을 등록한다 + - 새로고침하더라도 저장한 지하철역은 사라지지 않는다 - 지하철 역을 삭제한다 - 예외: 노선에 등록된 역은 삭제할 수 없다 - 역이 노선에 등록되었는지 판단한다 - 지하철 역은 2글자 이상이어야 한다 - 지하철 역의 목록을 조회한다 + - 지하철역 페이지 접근시 최근 작업한 목록을 불러온다 + ## 🚀 기능 요구사항 diff --git a/src/controllers/controller.js b/src/controllers/controller.js index d3396771e..55d836607 100644 --- a/src/controllers/controller.js +++ b/src/controllers/controller.js @@ -1,7 +1,10 @@ import StationLayout from '../layout/stationLayout.js'; +import StationModel from '../model/stationModel.js'; export default class Controller { constructor() { + // TODO: model을 여기서 초기화하는게 맞나? + this.modelList = { station: new StationModel() }; this.viewList = { station: new StationLayout(this), }; @@ -27,4 +30,8 @@ export default class Controller { return view; } + + addStationData(station) { + this.modelList.station.addData(station); + } } diff --git a/src/layout/pageLayout.js b/src/layout/pageLayout.js index 2bda18a23..1631a038f 100644 --- a/src/layout/pageLayout.js +++ b/src/layout/pageLayout.js @@ -9,6 +9,7 @@ export default class PageLayout { this.controller = controller; this.elements = this.createElements(); this.buildLayout(); + this.displaySavedData(); } handleManagerButton() { @@ -16,6 +17,14 @@ export default class PageLayout { console.log(`${this.constructor.name} showed!`); } + handleAddButton(view) { + console.log(`${this.constructor.name} add button clicked`); + } + + displaySavedData() { + console.log(`${this.constructor.name} displayed saved data`); + } + // TODO: template 을 이용하여 생성하도록 수정하기 createElements() { console.log(`${this.constructor.name} created`); diff --git a/src/layout/stationLayout.js b/src/layout/stationLayout.js index b2130f479..ee3627267 100644 --- a/src/layout/stationLayout.js +++ b/src/layout/stationLayout.js @@ -43,7 +43,8 @@ export default class StationLayout extends PageLayout { const stationAddButton = document.createElement('button'); stationAddButton.id = 'station-add-button'; stationAddButton.innerHTML = '역 추가'; - stationAddButton.addEventListener('click', () => this.addRow()); + stationAddButton.addEventListener('click', () => this.handleAddButton()); + return stationAddButton; } @@ -84,17 +85,29 @@ export default class StationLayout extends PageLayout { return stationResultTable; } - addRow() { + addRow(stationName) { // TODO: 자식구조도 object로 돌릴수있겠는데? // TODO: controller 로 옮기기 // TODO: VDOM 쓰면 한번에 append하지않는방향으로.. const row = this.elements.resultContainer .querySelector('table') .insertRow(); - row.insertCell(0).innerHTML = this.controller.getInputFromUser(this); + row.insertCell(0).innerHTML = stationName; row.insertCell( 1, ).innerHTML = ``; + } + + // override + handleAddButton() { + const input = this.controller.getInputFromUser(this); + console.log(input); + this.addRow(input); + this.controller.addStationData(input); + this.clearInput(); + } + + clearInput() { this.elements.inputContainer.querySelector('input').value = ''; } @@ -108,10 +121,18 @@ export default class StationLayout extends PageLayout { return { managerButton, section, inputContainer, resultContainer }; } + // override + displaySavedData() { + // TODO: storageData 비동기? + const stationList = this.controller.modelList.station.getList(); + for (const station of stationList) { + this.addRow(station); + } + } + // override buildLayout() { const { section, inputContainer, resultContainer } = this.elements; - section.append(inputContainer, resultContainer); } } diff --git a/src/model/stationModel.js b/src/model/stationModel.js new file mode 100644 index 000000000..b119dc7f9 --- /dev/null +++ b/src/model/stationModel.js @@ -0,0 +1,23 @@ +export default class StationModel { + constructor() {} + + /** + * localStorage + * stations: [] + */ + addData(station) { + const stationList = this.getList(); + stationList.push(station); + localStorage.setItem('stationList', JSON.stringify(stationList)); + } + + getList() { + const storageStationList = localStorage.getItem('stationList'); + let stationList = []; + if (storageStationList) { + stationList = [...JSON.parse(storageStationList)]; + } + + return stationList; + } +} From ee6df8ef6354c90e37a3011873df52ad27de13c3 Mon Sep 17 00:00:00 2001 From: Sunmon Date: Mon, 14 Dec 2020 00:32:01 +0900 Subject: [PATCH 13/29] =?UTF-8?q?feat:=20=EC=A7=80=ED=95=98=EC=B2=A0?= =?UTF-8?q?=EC=97=AD=20=EC=82=AD=EC=A0=9C=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - '삭제' 버튼을 누르면 지하철역을 테이블에서 삭제한다 - '삭제' 버튼을 누르면 지하철역을 localStorage에서 삭제한다 - insert-delete 단어쌍이 맞도록 메소드 이름 수정 --- src/controllers/controller.js | 8 ++++++-- src/layout/stationLayout.js | 37 +++++++++++++++++++++++++++++------ src/model/stationModel.js | 11 +++++++++-- 3 files changed, 46 insertions(+), 10 deletions(-) diff --git a/src/controllers/controller.js b/src/controllers/controller.js index 55d836607..09aeedfdc 100644 --- a/src/controllers/controller.js +++ b/src/controllers/controller.js @@ -31,7 +31,11 @@ export default class Controller { return view; } - addStationData(station) { - this.modelList.station.addData(station); + insertStationData(stationName) { + this.modelList.station.insertData(stationName); + } + + deleteStationData(stationName) { + this.modelList.station.deleteData(stationName); } } diff --git a/src/layout/stationLayout.js b/src/layout/stationLayout.js index ee3627267..030331212 100644 --- a/src/layout/stationLayout.js +++ b/src/layout/stationLayout.js @@ -85,24 +85,49 @@ export default class StationLayout extends PageLayout { return stationResultTable; } - addRow(stationName) { + createDeleteButton() { + const deleteButton = document.createElement('button'); + deleteButton.innerHTML = '삭제'; + deleteButton.className = 'station-delete-button'; + deleteButton.addEventListener('click', e => + this.handleDeleteButton(e.target), + ); + + return deleteButton; + } + + deleteRow(index) { + // TODO: do something + const table = this.elements.resultContainer.querySelector('table'); + table.deleteRow(index); + } + + handleDeleteButton(target) { + // TODO: 삭제버튼을 눌렀을때 + const tr = target.parentElement.parentElement; + console.log(tr.dataset.stationName); + this.deleteRow(tr.rowIndex); + this.controller.deleteStationData(tr.dataset.stationName); + console.log('delete button clicked'); + } + + insertRow(stationName) { // TODO: 자식구조도 object로 돌릴수있겠는데? // TODO: controller 로 옮기기 // TODO: VDOM 쓰면 한번에 append하지않는방향으로.. const row = this.elements.resultContainer .querySelector('table') .insertRow(); + row.dataset.stationName = stationName; row.insertCell(0).innerHTML = stationName; - row.insertCell( - 1, - ).innerHTML = ``; + row.insertCell(1).append(this.createDeleteButton()); } // override handleAddButton() { const input = this.controller.getInputFromUser(this); console.log(input); - this.addRow(input); + this.insertRow(input); this.controller.addStationData(input); this.clearInput(); } @@ -126,7 +151,7 @@ export default class StationLayout extends PageLayout { // TODO: storageData 비동기? const stationList = this.controller.modelList.station.getList(); for (const station of stationList) { - this.addRow(station); + this.insertRow(station); } } diff --git a/src/model/stationModel.js b/src/model/stationModel.js index b119dc7f9..518296406 100644 --- a/src/model/stationModel.js +++ b/src/model/stationModel.js @@ -5,9 +5,16 @@ export default class StationModel { * localStorage * stations: [] */ - addData(station) { + insertData(stationName) { const stationList = this.getList(); - stationList.push(station); + stationList.push(stationName); + localStorage.setItem('stationList', JSON.stringify(stationList)); + } + + deleteData(stationName) { + const stationList = this.getList(); + const index = stationList.findIndex(name => name === stationName); + stationList.splice(index, 1); localStorage.setItem('stationList', JSON.stringify(stationList)); } From 9b3792e3066f35ad7289b1e367add747da167548 Mon Sep 17 00:00:00 2001 From: Sunmon Date: Mon, 14 Dec 2020 01:35:29 +0900 Subject: [PATCH 14/29] =?UTF-8?q?refactor:=20=EB=A0=88=EC=9D=B4=EC=95=84?= =?UTF-8?q?=EC=9B=83=20=ED=81=B4=EB=9E=98=EC=8A=A4=20=EB=B3=80=ED=99=98=20?= =?UTF-8?q?(line=20->=20lineLayout)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - line.js -> lineLayout.js 로 변환 - 지하철 노선 관련 레이아웃 모듈을 클래스로 변환 --- src/controllers/controller.js | 5 +- src/index.js | 7 +- src/layout/line.js | 77 ---------------------- src/layout/lineLayout.js | 117 ++++++++++++++++++++++++++++++++++ 4 files changed, 125 insertions(+), 81 deletions(-) delete mode 100644 src/layout/line.js create mode 100644 src/layout/lineLayout.js diff --git a/src/controllers/controller.js b/src/controllers/controller.js index 09aeedfdc..820b7f6d5 100644 --- a/src/controllers/controller.js +++ b/src/controllers/controller.js @@ -1,5 +1,6 @@ import StationLayout from '../layout/stationLayout.js'; import StationModel from '../model/stationModel.js'; +import LineLayout from '../layout/lineLayout.js'; export default class Controller { constructor() { @@ -7,9 +8,11 @@ export default class Controller { this.modelList = { station: new StationModel() }; this.viewList = { station: new StationLayout(this), + line: new LineLayout(this), }; + console.log('new controller: '); + console.log(this.viewList); this.currentView = ''; - this.currentView = this.viewList.station; } /** diff --git a/src/index.js b/src/index.js index bbe6e00fa..3461f7960 100644 --- a/src/index.js +++ b/src/index.js @@ -3,8 +3,8 @@ import { managerContainer, sectionContainer, } from './layout/mainLayout.js'; -import StationLayout from './layout/stationLayout.js'; -import { lineElements } from './layout/line.js'; +// import StationLayout from './layout/stationLayout.js'; +// import { lineElements } from './layout/lineLayout.js'; import { sectionElements } from './layout/section.js'; import { mapPrintElements } from './layout/mapPrintLayout.js'; import Controller from './controllers/controller.js'; @@ -15,8 +15,9 @@ const initHTML = function () { app.append(managerContainer, sectionContainer); managerContainer.append( // stationElements.managerButton, + // lineElements.managerButton, controller.viewList.station.elements.managerButton, - lineElements.managerButton, + controller.viewList.line.elements.managerButton, sectionElements.managerButton, mapPrintElements.managerButton, ); diff --git a/src/layout/line.js b/src/layout/line.js deleted file mode 100644 index f115c4992..000000000 --- a/src/layout/line.js +++ /dev/null @@ -1,77 +0,0 @@ -/** - * 지하철 노선 관련 모듈 - */ - -// 외부에 노출되는 변수 -const lineManagerButton = document.createElement('button'); -const lineSection = document.createElement('section'); - -// 내부 변수 -const lineNameContainer = document.createElement('article'); -const lineNameInput = document.createElement('input'); -const lineStartStationSector = document.createElement('select'); -const lineEndStationSector = document.createElement('select'); -const lineAddButton = document.createElement('button'); - -const lineResultContainer = document.createElement('article'); -const lineResultTable = document.createElement('tb'); - -const replaceSectionToLine = function (section) { - section.replaceWith(lineSection); -}; - -const handleLineManagerButton = function () { - const section = document.querySelector('#section-container > section'); - replaceSectionToLine(section); - console.log('line showed'); -}; - -const initElements = function () { - lineManagerButton.id = 'line-manager-button'; - lineSection.id = 'line-section'; - lineManagerButton.innerHTML = '2. 노선 관리'; - lineManagerButton.addEventListener('click', handleLineManagerButton); - - lineNameInput.id = 'line-name-input'; - lineNameInput.placeholder = '노선 이름을 입력해주세요'; - - lineStartStationSector.id = ' line-start-station-selector'; - lineEndStationSector.id = 'line-end-station-selector'; - - lineAddButton.id = 'line-add-button'; - lineAddButton.innerHTML = '노선 추가'; -}; - -const appendNodesToDOM = function () { - lineSection.append(lineNameContainer, lineResultContainer); - lineNameContainer.append( - lineNameInput, - lineStartStationSector, - lineEndStationSector, - lineAddButton, - ); - lineNameInput.insertAdjacentHTML('beforebegin', '

노선 이름

'); - lineStartStationSector.insertAdjacentHTML( - 'beforebegin', - '

상행 종점

', - ); - lineEndStationSector.insertAdjacentHTML('beforebegin', '

하행 종점

'); - lineResultContainer.append(lineResultTable); - lineResultContainer.insertAdjacentHTML( - 'afterbegin', - '

🚉 지하철 노선 목록

', - ); -}; - -const buildLineSection = function () { - initElements(); - appendNodesToDOM(); - console.log('line section build'); -}; - -buildLineSection(); - -export const lineElements = { - managerButton: lineManagerButton, - section: lineSection, -}; diff --git a/src/layout/lineLayout.js b/src/layout/lineLayout.js new file mode 100644 index 000000000..20e98019e --- /dev/null +++ b/src/layout/lineLayout.js @@ -0,0 +1,117 @@ +import PageLayout from './pageLayout.js'; +/** + * 지하철 노선 관련 클래스 + */ + +export default class LineLayout extends PageLayout { + // override + createElements() { + const managerButton = this.createManagerButton(); + const section = this.createSection(); + const inputContainer = this.createInputContainer(); + const resultContainer = this.createResultContainer(); + + return { managerButton, section, inputContainer, resultContainer }; + } + + // override + buildLayout() { + const { section, inputContainer, resultContainer } = this.elements; + section.append(inputContainer, resultContainer); + } + + createManagerButton() { + const lineManagerButton = document.createElement('button'); + lineManagerButton.id = 'line-manager-button'; + lineManagerButton.innerHTML = '2. 노선 관리'; + lineManagerButton.addEventListener('click', () => + this.handleManagerButton(), + ); + + return lineManagerButton; + } + + createSection() { + const lineSection = document.createElement('section'); + lineSection.id = 'line-section'; + + return lineSection; + } + + createResultContainer() { + const lineResultContainer = document.createElement('article'); + lineResultContainer.append( + this.createResultTitle(), + this.createResultTable(), + ); + + return lineResultContainer; + } + + createResultTitle() { + const title = document.createElement('h2'); + title.innerHTML = '🚉 지하철 노선 목록'; + + return title; + } + + createResultTable() { + const lineResultTable = document.createElement('table'); + lineResultTable.innerHTML = + '노선 이름상행 종점역하행 종점역'; + + return lineResultTable; + } + + createInputTitle() { + const lineNameTitle = document.createElement('h3'); + lineNameTitle.innerHTML = '노선 이름'; + + return lineNameTitle; + } + + createInput() { + const lineNameInput = document.createElement('input'); + lineNameInput.id = 'line-name-input'; + lineNameInput.placeholder = '노선 이름을 입력해주세요'; + + return lineNameInput; + } + + createStationSubtitle(text) { + const subtitle = document.createElement('h3'); + subtitle.innerHTML = text; + + return subtitle; + } + + createStationSelector(position) { + const stationSelector = document.createElement('select'); + stationSelector.id = `line-${position}-station-selector`; + + return stationSelector; + } + + createAddButton() { + const lineAddButton = document.createElement('button'); + lineAddButton.id = 'line-add-button'; + lineAddButton.innerHTML = '노선 추가'; + + return lineAddButton; + } + + createInputContainer() { + const lineNameContainer = document.createElement('article'); + lineNameContainer.append( + this.createInputTitle(), + this.createInput(), + this.createStationSubtitle('상행 종점'), + this.createStationSelector('start'), + this.createStationSubtitle('하행 종점'), + this.createStationSelector('end'), + this.createAddButton(), + ); + + return lineNameContainer; + } +} From 5706a82a3a0e15969420d9137e03bba58b85f771 Mon Sep 17 00:00:00 2001 From: Sun Jung Kim Date: Mon, 14 Dec 2020 09:21:49 +0900 Subject: [PATCH 15/29] =?UTF-8?q?refactor:=20=EB=A0=88=EC=9D=B4=EC=95=84?= =?UTF-8?q?=EC=9B=83=20=ED=81=B4=EB=9E=98=EC=8A=A4=20=EB=B3=80=ED=99=98=20?= =?UTF-8?q?(section->sectionLayout)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - section.js -> sectionLayout.js로 변환 - 지하철 구간 레이아웃 모듈을 클래스로 변환 - 아직 데이터를 모델에 저장/불러오는 기능은 구현하지 않음 --- src/controllers/controller.js | 2 + src/index.js | 5 +- src/layout/section.js | 72 ------------------ src/layout/sectionLayout.js | 133 ++++++++++++++++++++++++++++++++++ 4 files changed, 138 insertions(+), 74 deletions(-) delete mode 100644 src/layout/section.js create mode 100644 src/layout/sectionLayout.js diff --git a/src/controllers/controller.js b/src/controllers/controller.js index 820b7f6d5..fc5b6ec1e 100644 --- a/src/controllers/controller.js +++ b/src/controllers/controller.js @@ -1,6 +1,7 @@ import StationLayout from '../layout/stationLayout.js'; import StationModel from '../model/stationModel.js'; import LineLayout from '../layout/lineLayout.js'; +import SectionLayout from '../layout/sectionLayout.js'; export default class Controller { constructor() { @@ -9,6 +10,7 @@ export default class Controller { this.viewList = { station: new StationLayout(this), line: new LineLayout(this), + section: new SectionLayout(this), }; console.log('new controller: '); console.log(this.viewList); diff --git a/src/index.js b/src/index.js index 3461f7960..8ffdb8efd 100644 --- a/src/index.js +++ b/src/index.js @@ -5,7 +5,7 @@ import { } from './layout/mainLayout.js'; // import StationLayout from './layout/stationLayout.js'; // import { lineElements } from './layout/lineLayout.js'; -import { sectionElements } from './layout/section.js'; +// import sectionLayout from './layout/section.js'; import { mapPrintElements } from './layout/mapPrintLayout.js'; import Controller from './controllers/controller.js'; @@ -16,9 +16,10 @@ const initHTML = function () { managerContainer.append( // stationElements.managerButton, // lineElements.managerButton, + // sectionElements.managerButton, controller.viewList.station.elements.managerButton, controller.viewList.line.elements.managerButton, - sectionElements.managerButton, + controller.viewList.section.elements.managerButton, mapPrintElements.managerButton, ); }; diff --git a/src/layout/section.js b/src/layout/section.js deleted file mode 100644 index e07268c67..000000000 --- a/src/layout/section.js +++ /dev/null @@ -1,72 +0,0 @@ -/** - * 지하철 노선 관련 모듈 - */ - -// 외부에 노출되는 변수 -const sectionManagerButton = document.createElement('button'); -const sectionSection = document.createElement('section'); - -// 내부 변수 -const sectionLineMenuButtonContainer = document.createElement('div'); -const sectionOrderContainer = document.createElement('div'); -const sectionStationSelector = document.createElement('select'); -const sectionOrderInput = document.createElement('input'); -const sectionAddButton = document.createElement('button'); -const sectionResultContainer = document.createElement('div'); - -const showPageInsteadOf = function (section) { - section.replaceWith(sectionSection); -}; - -const handleSectionManagerButton = function () { - const section = document.querySelector('#section-container > section'); - showPageInsteadOf(section); - console.log('section showed'); -}; - -const initElements = function () { - sectionManagerButton.id = 'section-manager-button'; - sectionManagerButton.innerHTML = '3. 구간 관리'; - sectionManagerButton.addEventListener('click', handleSectionManagerButton); - sectionSection.id = 'section-section'; - sectionOrderInput.id = 'section-order-input'; - sectionOrderInput.placeholder = '순서'; - sectionAddButton.id = 'section-add-button'; - sectionAddButton.innerHTML = '등록'; -}; - -const appendNodesToDOM = function () { - sectionSection.append( - sectionLineMenuButtonContainer, - sectionOrderContainer, - sectionResultContainer, - ); - sectionOrderContainer.append( - sectionStationSelector, - sectionOrderInput, - sectionAddButton, - ); - - sectionLineMenuButtonContainer.insertAdjacentHTML( - 'beforebegin', - '

구간을 수정할 노선을 선택해주세요.

', - ); - sectionOrderContainer.insertAdjacentHTML( - 'beforebegin', - '

n호선 관리

', - ); - sectionOrderContainer.insertAdjacentHTML('afterbegin', '

구간 등록

'); -}; - -const buildSectionSection = function () { - initElements(); - appendNodesToDOM(); - console.log('section section build'); -}; - -buildSectionSection(); - -export const sectionElements = { - managerButton: sectionManagerButton, - section: sectionSection, -}; diff --git a/src/layout/sectionLayout.js b/src/layout/sectionLayout.js new file mode 100644 index 000000000..e4a37e7fa --- /dev/null +++ b/src/layout/sectionLayout.js @@ -0,0 +1,133 @@ +/** + * 지하철 노선 관련 모듈 + */ + +import PageLayout from './pageLayout.js'; + +export default class SectionLayout extends PageLayout { + createManagerButton() { + const sectionManagerButton = document.createElement('button'); + sectionManagerButton.id = 'section-manager-button'; + sectionManagerButton.innerHTML = '3. 구간 관리'; + sectionManagerButton.addEventListener('click', () => + this.handleManagerButton(), + ); + + return sectionManagerButton; + } + + createSection() { + const section = document.createElement('section'); + // section.id = 'section-section'; // TODO: id 필요없으면 지워버리자 + + return section; + } + + createMenuButtonTitle(text) { + const title = document.createElement('h2'); + title.innerHTML = text; + + return title; + } + + createStationSelector() { + const sectionStationSelector = document.createElement('select'); + sectionStationSelector.id = '#section-station-selector'; + + return sectionStationSelector; + } + + createSectionOrderTitle() { + const title = document.createElement('h3'); + title.innerHTML = '구간 등록'; + + return title; + } + createSectionOrderInput() { + const sectionOrderInput = document.createElement('input'); + sectionOrderInput.id = 'section-order-input'; + sectionOrderInput.placeholder = '순서'; + + return sectionOrderInput; + } + + createSectionAddButton() { + const sectionAddButton = document.createElement('button'); + sectionAddButton.id = 'section-add-button'; + sectionAddButton.innerHTML = '등록'; + + return sectionAddButton; + } + + createSectionOrderContainer() { + const sectionOrderContainer = document.createElement('div'); + sectionOrderContainer.append( + this.createSectionOrderTitle(), + this.createStationSelector(), + this.createSectionOrderInput(), + this.createSectionAddButton(), + ); + + return sectionOrderContainer; + } + + createSectionLineMenuButton(text) { + const button = document.createElement('button'); + button.innerHTML = text; + + return button; + } + + createInputContainer() { + const sectionLineMenuButtonContainer = document.createElement('div'); + // TODO: 버튼 동적으로 생성하기. + // class = '.section-line-menu-button' + sectionLineMenuButtonContainer.append( + this.createMenuButtonTitle('구간을 수정할 노선을 선택해주세요'), + this.createSectionLineMenuButton('temp'), + ); + + return sectionLineMenuButtonContainer; + } + + createResultTable() { + const sectionLineResultTable = document.createElement('table'); + sectionLineResultTable.innerHTML = + '순서이름설정'; + + return sectionLineResultTable; + } + + createResultTitle() { + const resultTitle = document.createElement('h2'); + resultTitle.innerHTML = 'n호선 관리'; // TODO: 동적으로 바꾸기 + + return resultTitle; + } + createResultContainer() { + const sectionResultContainer = document.createElement('div'); + sectionResultContainer.append( + this.createResultTitle(), + this.createSectionOrderContainer(), + this.createResultTable(), + ); + + return sectionResultContainer; + } + + // override + createElements() { + const managerButton = this.createManagerButton(); + const section = this.createSection(); + const inputContainer = this.createInputContainer(); + const resultContainer = this.createResultContainer(); + + return { managerButton, section, inputContainer, resultContainer }; + } + + // override + buildLayout() { + const { section, inputContainer, resultContainer } = this.elements; + section.append(inputContainer, resultContainer); + } +} From 01d4dae2b302dd80fdf054ef59de8d4cb6979997 Mon Sep 17 00:00:00 2001 From: Sun Jung Kim Date: Mon, 14 Dec 2020 09:31:59 +0900 Subject: [PATCH 16/29] =?UTF-8?q?fix:=20=EC=97=AD=20=EC=B6=94=EA=B0=80?= =?UTF-8?q?=EC=8B=9C=20localStorage=EC=97=90=20=EC=A0=80=EC=9E=A5=EB=90=98?= =?UTF-8?q?=EC=A7=80=20=EC=95=8A=EB=8A=94=20=EB=B2=84=EA=B7=B8=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 새로 추가한 지하철역이 localStorage에 저장되지 않는 버그 수정 --- src/layout/stationLayout.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/layout/stationLayout.js b/src/layout/stationLayout.js index 030331212..62023f8a5 100644 --- a/src/layout/stationLayout.js +++ b/src/layout/stationLayout.js @@ -128,7 +128,7 @@ export default class StationLayout extends PageLayout { const input = this.controller.getInputFromUser(this); console.log(input); this.insertRow(input); - this.controller.addStationData(input); + this.controller.insertStationData(input); this.clearInput(); } From a98978f701ffdb70a9d518bfd3247a558e5ad8c6 Mon Sep 17 00:00:00 2001 From: Sun Jung Kim Date: Mon, 14 Dec 2020 14:46:29 +0900 Subject: [PATCH 17/29] =?UTF-8?q?refactor:=20localStorage=EC=99=80=20DTO?= =?UTF-8?q?=EB=A1=9C=20=ED=86=B5=EC=8B=A0=ED=95=98=EB=8F=84=EB=A1=9D=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - stationNode 클래스 추가. localStorage에 저장되는 DTO 역할. - localStorage에 stationList에 저장하는 객체 변경) - stationList에 저장하는 내용을 stationName -> stationNode 로 변경함. - stationNode는 DTO 객체 & 노선정보 링크드리스트에 쓰일 노드 --- src/layout/stationLayout.js | 2 +- src/model/staionNode.js | 12 ++++++++++++ src/model/stationModel.js | 33 +++++++++++++++++++++++++++++---- 3 files changed, 42 insertions(+), 5 deletions(-) create mode 100644 src/model/staionNode.js diff --git a/src/layout/stationLayout.js b/src/layout/stationLayout.js index 62023f8a5..e659f02ca 100644 --- a/src/layout/stationLayout.js +++ b/src/layout/stationLayout.js @@ -151,7 +151,7 @@ export default class StationLayout extends PageLayout { // TODO: storageData 비동기? const stationList = this.controller.modelList.station.getList(); for (const station of stationList) { - this.insertRow(station); + this.insertRow(station.name); } } diff --git a/src/model/staionNode.js b/src/model/staionNode.js new file mode 100644 index 000000000..5c6e5d9ec --- /dev/null +++ b/src/model/staionNode.js @@ -0,0 +1,12 @@ +/** + * localStorage에 저장하는 지하철역 포맷 (DTO) + * 노선 링크드리스트에 쓰이는 노드 + */ +export default class StationNode { + constructor({ name, line = null, prev = null, next = null } = {}) { + this.name = name; + this.line = line; + this.prev = prev; + this.next = next; + } +} diff --git a/src/model/stationModel.js b/src/model/stationModel.js index 518296406..bc5c37210 100644 --- a/src/model/stationModel.js +++ b/src/model/stationModel.js @@ -1,19 +1,44 @@ +import StationNode from './staionNode.js'; + +/** + * localStorage와 통신하는 DAO + * 데이터 저장/수정시에 StaionNode를 이용한다. + * + * localStorage 구조: + * { + * stationList [] : staionNode 배열 + * } + */ export default class StationModel { constructor() {} + createNode(stationName) { + return new StationNode({ name: stationName }); + } + + findNodeByName(stationName) { + const stationList = this.getList(); + return stationList.find(station => station.name === stationName); + } + /** - * localStorage - * stations: [] + * + * @param {*} stationName */ insertData(stationName) { const stationList = this.getList(); - stationList.push(stationName); + stationList.push(this.createNode(stationName)); + // stationList.push(stationName); localStorage.setItem('stationList', JSON.stringify(stationList)); } deleteData(stationName) { const stationList = this.getList(); - const index = stationList.findIndex(name => name === stationName); + const index = stationList.findIndex( + station => station.name === stationName, + ); + // const index = stationList.findIndex(name => name === stationName); + stationList.splice(index, 1); localStorage.setItem('stationList', JSON.stringify(stationList)); } From c8f0ea31a948129a1d6783a4b6768d9854c7bd4a Mon Sep 17 00:00:00 2001 From: Sun Jung Kim Date: Mon, 14 Dec 2020 18:36:57 +0900 Subject: [PATCH 18/29] =?UTF-8?q?feat:=20=EB=85=B8=EC=84=A0=EC=A0=95?= =?UTF-8?q?=EB=B3=B4=20=EC=A0=80=EC=9E=A5=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 지하철역 노선 '추가' 버튼을 누르면 localStorage에 저장 - 지하철역 노선 '추가' 버튼을 누르면 결과 테이블에 추가 --- src/controllers/controller.js | 9 ++- src/layout/lineLayout.js | 61 ++++++++++++++++++++- src/model/lineModel.js | 31 +++++++++++ src/model/stationModel.js | 26 +++++---- src/model/{staionNode.js => stationNode.js} | 2 +- 5 files changed, 114 insertions(+), 15 deletions(-) create mode 100644 src/model/lineModel.js rename src/model/{staionNode.js => stationNode.js} (77%) diff --git a/src/controllers/controller.js b/src/controllers/controller.js index fc5b6ec1e..16f54ce06 100644 --- a/src/controllers/controller.js +++ b/src/controllers/controller.js @@ -2,11 +2,11 @@ import StationLayout from '../layout/stationLayout.js'; import StationModel from '../model/stationModel.js'; import LineLayout from '../layout/lineLayout.js'; import SectionLayout from '../layout/sectionLayout.js'; +import LineModel from '../model/lineModel.js'; export default class Controller { constructor() { - // TODO: model을 여기서 초기화하는게 맞나? - this.modelList = { station: new StationModel() }; + this.modelList = { station: new StationModel(), line: new LineModel() }; this.viewList = { station: new StationLayout(this), line: new LineLayout(this), @@ -43,4 +43,9 @@ export default class Controller { deleteStationData(stationName) { this.modelList.station.deleteData(stationName); } + + insertLineData(line, start, end) { + const nodes = this.modelList.line.insertData(line, start, end); + this.modelList.station.updateData(nodes); + } } diff --git a/src/layout/lineLayout.js b/src/layout/lineLayout.js index 20e98019e..ba2ea32da 100644 --- a/src/layout/lineLayout.js +++ b/src/layout/lineLayout.js @@ -79,7 +79,7 @@ export default class LineLayout extends PageLayout { } createStationSubtitle(text) { - const subtitle = document.createElement('h3'); + const subtitle = document.createElement('h3'); // TODO: label로 바꾸기 subtitle.innerHTML = text; return subtitle; @@ -88,6 +88,7 @@ export default class LineLayout extends PageLayout { createStationSelector(position) { const stationSelector = document.createElement('select'); stationSelector.id = `line-${position}-station-selector`; + stationSelector.name = position; return stationSelector; } @@ -96,10 +97,68 @@ export default class LineLayout extends PageLayout { const lineAddButton = document.createElement('button'); lineAddButton.id = 'line-add-button'; lineAddButton.innerHTML = '노선 추가'; + lineAddButton.addEventListener('click', () => this.handleAddButton()); return lineAddButton; } + insertRow(lineName, start, end) { + const row = this.elements.resultContainer + .querySelector('table') + .insertRow(); + row.dataset.lineName = lineName; + row.dataset.lineStart = start; + row.dataset.lineEnd = end; + row.insertCell(0).innerHTML = lineName; + row.insertCell(1).innerHTML = start; + row.insertCell(2).innerHTML = end; + row.insertCell(3).innerHTML = ''; + // row.insertCell(1).append(this.createDeleteButton()); + } + + getSelectedOption(selectElement) { + return selectElement.options[selectElement.selectedIndex]; + } + + // override + handleAddButton() { + const line = this.controller.getInputFromUser(this); + const { inputContainer } = this.elements; + console.log(`line line: ${line}, inputContainer: ${inputContainer}`); + const start = this.getSelectedOption( + inputContainer.querySelector(`select[name='start']`), + ); + const end = this.getSelectedOption( + inputContainer.querySelector(`select[name='end']`), + ); + + // TODO: querySelector말고 다른 방법은 없나? + // const selectors = this.elements.inputContainer.querySelectorAll('select'); + this.insertRow(line, start.value, end.value); + this.controller.insertLineData(line, start.value, end.value); + } + + // override + displaySavedData() { + const start = this.elements.inputContainer.querySelector( + `select[name='start']`, + ); + const end = this.elements.inputContainer.querySelector( + `select[name='end']`, + ); + const stationList = this.controller.modelList.station.getList(); + // FIXME: 새로 역을 추가 후 탭을 이동하면 추가한 역이 나오지 않음 + // FIXME: displaySavedData를 create시말고 tab show할때로 옮기기 + + // TODO: 예외로 등록 ) 이미 노선이 등록된 경우 옵션에 추가하지 않음 + for (const station of stationList) { + start.insertAdjacentHTML('beforeend', ``); + end.insertAdjacentHTML('beforeend', ``); + } + + // TODO: 데이터 테이블에 추가하기 -> 데이터 테이블 업데이트 함수 만들어서 그냥 불러오기 + } + createInputContainer() { const lineNameContainer = document.createElement('article'); lineNameContainer.append( diff --git a/src/model/lineModel.js b/src/model/lineModel.js new file mode 100644 index 000000000..52b14d447 --- /dev/null +++ b/src/model/lineModel.js @@ -0,0 +1,31 @@ +import StationNode from './stationNode.js'; + +/** + * localStorage에 저장하는 LineList 구조 + * lineList : [][] 이차원배열 + * lineList[a][b]: a번째로 추가한 호선의 b+1번째 역 + */ + +export default class LineModel { + + insertData(line, start, end) { + let lineList = this.getList(); + const startNode = new StationNode({ name: start, line }); + const endNode = new StationNode({ name: end, line }); + + lineList = [...lineList, [startNode, endNode]]; + localStorage.setItem('lineList', JSON.stringify(lineList)); + + return [startNode, endNode]; + } + + getList() { + const storageLineList = localStorage.getItem('lineList'); + let lineList = []; + if (storageLineList) { + lineList = [...JSON.parse(storageLineList)]; + } + + return lineList; + } +} diff --git a/src/model/stationModel.js b/src/model/stationModel.js index bc5c37210..9a3a76cce 100644 --- a/src/model/stationModel.js +++ b/src/model/stationModel.js @@ -1,13 +1,8 @@ -import StationNode from './staionNode.js'; +import StationNode from './stationNode.js'; /** * localStorage와 통신하는 DAO * 데이터 저장/수정시에 StaionNode를 이용한다. - * - * localStorage 구조: - * { - * stationList [] : staionNode 배열 - * } */ export default class StationModel { constructor() {} @@ -21,10 +16,20 @@ export default class StationModel { return stationList.find(station => station.name === stationName); } - /** - * - * @param {*} stationName - */ + updateData(newNodes) { + const stationList = this.getList(); + for (const newNode of newNodes) { + let oldNode = stationList.find(station => station.name === newNode.name); + const newLine = newNode.line; + // 만약 없다면 노드에 라인 추가 + if (!oldNode.line.includes(newLine)) { + oldNode.line.push(newLine); + } + } + console.log(stationList); + localStorage.setItem('stationList', JSON.stringify(stationList)); + } + insertData(stationName) { const stationList = this.getList(); stationList.push(this.createNode(stationName)); @@ -37,7 +42,6 @@ export default class StationModel { const index = stationList.findIndex( station => station.name === stationName, ); - // const index = stationList.findIndex(name => name === stationName); stationList.splice(index, 1); localStorage.setItem('stationList', JSON.stringify(stationList)); diff --git a/src/model/staionNode.js b/src/model/stationNode.js similarity index 77% rename from src/model/staionNode.js rename to src/model/stationNode.js index 5c6e5d9ec..228406e50 100644 --- a/src/model/staionNode.js +++ b/src/model/stationNode.js @@ -3,7 +3,7 @@ * 노선 링크드리스트에 쓰이는 노드 */ export default class StationNode { - constructor({ name, line = null, prev = null, next = null } = {}) { + constructor({ name, line = [], prev = null, next = null } = {}) { this.name = name; this.line = line; this.prev = prev; From 8b162a6d1d789ff5edbbd0d128d95262a4fccb7f Mon Sep 17 00:00:00 2001 From: Sunmon Date: Mon, 14 Dec 2020 20:51:09 +0900 Subject: [PATCH 19/29] =?UTF-8?q?style:=20mainLayout=20->=20mainPageLayout?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20=EC=9D=B4=EB=A6=84=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 모호한 이름 변경 --- src/index.js | 2 +- src/layout/{mainLayout.js => mainPageLayout.js} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename src/layout/{mainLayout.js => mainPageLayout.js} (100%) diff --git a/src/index.js b/src/index.js index 8ffdb8efd..2abcb5112 100644 --- a/src/index.js +++ b/src/index.js @@ -2,7 +2,7 @@ import { app, managerContainer, sectionContainer, -} from './layout/mainLayout.js'; +} from './layout/mainPageLayout.js'; // import StationLayout from './layout/stationLayout.js'; // import { lineElements } from './layout/lineLayout.js'; // import sectionLayout from './layout/section.js'; diff --git a/src/layout/mainLayout.js b/src/layout/mainPageLayout.js similarity index 100% rename from src/layout/mainLayout.js rename to src/layout/mainPageLayout.js From 834e24829b7231563f4ee63b366224d0d3947e23 Mon Sep 17 00:00:00 2001 From: Sunmon Date: Mon, 14 Dec 2020 20:52:45 +0900 Subject: [PATCH 20/29] =?UTF-8?q?feat:=20=EB=85=B8=EC=84=A0=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 노선 '삭제' 버튼을 누르면 테이블에서 노선 정보를 삭제한다 - 노선 '삭제' 버튼을 누르면 localStorage에서 노선 정보를 삭제한다 - 삭제한 노선에 포함된 지하철역의 노선리스트에서 해당 노선을 삭제한다 --- README.md | 5 +++++ src/controllers/controller.js | 5 +++++ src/layout/lineLayout.js | 30 ++++++++++++++++++++++++++++-- src/model/lineModel.js | 10 +++++++++- src/model/stationModel.js | 15 +++++++++------ 5 files changed, 56 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index c8f58e431..499d628dd 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,11 @@ - 지하철 역의 목록을 조회한다 - 지하철역 페이지 접근시 최근 작업한 목록을 불러온다 +### 지하철 노선 관련 기능 +- 지하철 노선을 등록한다 +- 노선 등록 시 상행 종점역과 하행 종점역을 입력받는다 +- 지하철 노선을 삭제한다 + ## 🚀 기능 요구사항 diff --git a/src/controllers/controller.js b/src/controllers/controller.js index 16f54ce06..7830989c5 100644 --- a/src/controllers/controller.js +++ b/src/controllers/controller.js @@ -48,4 +48,9 @@ export default class Controller { const nodes = this.modelList.line.insertData(line, start, end); this.modelList.station.updateData(nodes); } + + deleteLineData(lineName) { + const nodes = this.modelList.line.deleteData(lineName); + this.modelList.station.updateData(nodes); + } } diff --git a/src/layout/lineLayout.js b/src/layout/lineLayout.js index ba2ea32da..071147672 100644 --- a/src/layout/lineLayout.js +++ b/src/layout/lineLayout.js @@ -102,6 +102,33 @@ export default class LineLayout extends PageLayout { return lineAddButton; } + createDeleteButton() { + const deleteButton = document.createElement('button'); + deleteButton.innerHTML = '삭제'; + deleteButton.className = 'line-delete-button'; + deleteButton.addEventListener('click', e => + this.handleDeleteButton(e.target), + ); + + return deleteButton; + } + + // TODO: add, deleteRow 그냥 updateTable로 바꾸기 + deleteRow(index) { + const table = this.elements.resultContainer.querySelector('table'); + table.deleteRow(index); + } + + handleDeleteButton(target) { + const tr = target.parentElement.parentElement; + console.log( + `${tr.dataset.lineName}, ${tr.dataset.lineStart}, ${tr.dataset.lineEnd}`, + ); + this.deleteRow(tr.rowIndex); + this.controller.deleteLineData(tr.dataset.lineName); + console.log('delete button clicked'); + } + insertRow(lineName, start, end) { const row = this.elements.resultContainer .querySelector('table') @@ -112,8 +139,7 @@ export default class LineLayout extends PageLayout { row.insertCell(0).innerHTML = lineName; row.insertCell(1).innerHTML = start; row.insertCell(2).innerHTML = end; - row.insertCell(3).innerHTML = ''; - // row.insertCell(1).append(this.createDeleteButton()); + row.insertCell(3).append(this.createDeleteButton()); } getSelectedOption(selectElement) { diff --git a/src/model/lineModel.js b/src/model/lineModel.js index 52b14d447..9b738294a 100644 --- a/src/model/lineModel.js +++ b/src/model/lineModel.js @@ -7,7 +7,6 @@ import StationNode from './stationNode.js'; */ export default class LineModel { - insertData(line, start, end) { let lineList = this.getList(); const startNode = new StationNode({ name: start, line }); @@ -19,6 +18,15 @@ export default class LineModel { return [startNode, endNode]; } + deleteData(line) { + const lineList = this.getList(); + const index = lineList.findIndex(lineNodes => lineNodes[0].line === line); + const nodes = lineList.splice(index, 1)[0]; // splice결과가 이차원배열이기 때문 + localStorage.setItem('lineList', JSON.stringify(lineList)); + + return nodes; + } + getList() { const storageLineList = localStorage.getItem('lineList'); let lineList = []; diff --git a/src/model/stationModel.js b/src/model/stationModel.js index 9a3a76cce..4ccaa15b7 100644 --- a/src/model/stationModel.js +++ b/src/model/stationModel.js @@ -18,15 +18,17 @@ export default class StationModel { updateData(newNodes) { const stationList = this.getList(); - for (const newNode of newNodes) { - let oldNode = stationList.find(station => station.name === newNode.name); - const newLine = newNode.line; - // 만약 없다면 노드에 라인 추가 - if (!oldNode.line.includes(newLine)) { + const updateNodes = [...newNodes]; + for (const updateNode of updateNodes) { + const newLine = updateNode.line; + const oldNode = stationList.find(node => node.name === updateNode.name); + const index = oldNode.line.findIndex(line => line === newLine); + if (index === -1) { oldNode.line.push(newLine); + } else { + oldNode.line.splice(index, 1); } } - console.log(stationList); localStorage.setItem('stationList', JSON.stringify(stationList)); } @@ -47,6 +49,7 @@ export default class StationModel { localStorage.setItem('stationList', JSON.stringify(stationList)); } + // TODO: getList도 통일 가능할듯 getList() { const storageStationList = localStorage.getItem('stationList'); let stationList = []; From 79349c30643e55cd0d767beed6e22b8e7b07e6c8 Mon Sep 17 00:00:00 2001 From: Sunmon Date: Mon, 14 Dec 2020 22:21:00 +0900 Subject: [PATCH 21/29] =?UTF-8?q?feat:=20=EC=A7=80=ED=95=98=EC=B2=A0=20?= =?UTF-8?q?=EA=B5=AC=EA=B0=84=EC=97=90=EC=84=9C=20=EC=84=A0=ED=83=9D?= =?UTF-8?q?=ED=95=9C=20=EB=85=B8=EC=84=A0=EC=9D=98=20=EC=97=AD=20=EC=9D=B4?= =?UTF-8?q?=EB=A6=84=EC=9D=84=20=EC=84=A0=ED=83=9D=EC=9E=90=EC=97=90=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 지하철 구간관리 > 수정할 노선 선택시 구간 등록 화면 표시 - 선택한 노선의 역 정보를 구간 등록 화면의 역 선택자에 표시 - 아직 결과 테이블은 구현하지 않음 --- README.md | 2 ++ src/controllers/controller.js | 8 +++++++ src/layout/sectionLayout.js | 45 ++++++++++++++++++++++++++++++----- src/model/lineModel.js | 2 +- 4 files changed, 50 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 499d628dd..5ff78cc4f 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,8 @@ - 노선 등록 시 상행 종점역과 하행 종점역을 입력받는다 - 지하철 노선을 삭제한다 +### 지하철 구간 관련 기능 +- 수정할 지하철 노선을 선택하면 해당 노선구간 관리화면을 보여준다 ## 🚀 기능 요구사항 diff --git a/src/controllers/controller.js b/src/controllers/controller.js index 7830989c5..e66e237b9 100644 --- a/src/controllers/controller.js +++ b/src/controllers/controller.js @@ -53,4 +53,12 @@ export default class Controller { const nodes = this.modelList.line.deleteData(lineName); this.modelList.station.updateData(nodes); } + + getLineListAll() { + return this.modelList.line.getList(); + } + + getLineList(lineName) { + return this.modelList.line.getList().find(row => row[0].line === lineName); + } } diff --git a/src/layout/sectionLayout.js b/src/layout/sectionLayout.js index e4a37e7fa..99bda6096 100644 --- a/src/layout/sectionLayout.js +++ b/src/layout/sectionLayout.js @@ -30,9 +30,20 @@ export default class SectionLayout extends PageLayout { return title; } - createStationSelector() { + createStationSelector(line) { const sectionStationSelector = document.createElement('select'); - sectionStationSelector.id = '#section-station-selector'; + sectionStationSelector.id = 'section-station-selector'; + if (!line) return sectionStationSelector; + + const lineList = this.controller.getLineList(line); + console.log(`lineList:`); + console.log(lineList); + for (const node of lineList) { + sectionStationSelector.insertAdjacentHTML( + 'beforeend', + ``, + ); + } return sectionStationSelector; } @@ -43,6 +54,7 @@ export default class SectionLayout extends PageLayout { return title; } + createSectionOrderInput() { const sectionOrderInput = document.createElement('input'); sectionOrderInput.id = 'section-order-input'; @@ -71,22 +83,42 @@ export default class SectionLayout extends PageLayout { return sectionOrderContainer; } + handleSectionLineMenuButton(target) { + // TODO: section 바꾸는 함수 일반화시켜서 사용 + const { section, resultContainer } = this.elements; + resultContainer.querySelector('h2').innerHTML = `${target.innerText}`; + resultContainer + .querySelector('select') + .replaceWith(this.createStationSelector(target.innerText)); + + section.append(resultContainer); + } + createSectionLineMenuButton(text) { const button = document.createElement('button'); button.innerHTML = text; + button.className = 'section-line-menu-button'; + + button.addEventListener('click', e => + this.handleSectionLineMenuButton(e.target), + ); return button; } createInputContainer() { const sectionLineMenuButtonContainer = document.createElement('div'); - // TODO: 버튼 동적으로 생성하기. - // class = '.section-line-menu-button' sectionLineMenuButtonContainer.append( this.createMenuButtonTitle('구간을 수정할 노선을 선택해주세요'), - this.createSectionLineMenuButton('temp'), ); + const lineList = this.controller.getLineListAll(); // 2차원배열 + for (const row of lineList) { + sectionLineMenuButtonContainer.append( + this.createSectionLineMenuButton(row[0].line), + ); + } + return sectionLineMenuButtonContainer; } @@ -104,6 +136,7 @@ export default class SectionLayout extends PageLayout { return resultTitle; } + createResultContainer() { const sectionResultContainer = document.createElement('div'); sectionResultContainer.append( @@ -128,6 +161,6 @@ export default class SectionLayout extends PageLayout { // override buildLayout() { const { section, inputContainer, resultContainer } = this.elements; - section.append(inputContainer, resultContainer); + section.append(inputContainer); } } diff --git a/src/model/lineModel.js b/src/model/lineModel.js index 9b738294a..66abcc2fd 100644 --- a/src/model/lineModel.js +++ b/src/model/lineModel.js @@ -21,7 +21,7 @@ export default class LineModel { deleteData(line) { const lineList = this.getList(); const index = lineList.findIndex(lineNodes => lineNodes[0].line === line); - const nodes = lineList.splice(index, 1)[0]; // splice결과가 이차원배열이기 때문 + const nodes = lineList.splice(index, 1)[0]; // splice의 결과는 이차원배열 localStorage.setItem('lineList', JSON.stringify(lineList)); return nodes; From c602b6f6d9fd55149e58da28c1513f03850d4229 Mon Sep 17 00:00:00 2001 From: Sunmon Date: Mon, 14 Dec 2020 23:20:46 +0900 Subject: [PATCH 22/29] =?UTF-8?q?refactor:=20=EA=B3=B5=ED=86=B5=20?= =?UTF-8?q?=EC=9C=A0=ED=8B=B8=EB=A6=AC=ED=8B=B0=20=ED=81=B4=EB=9E=98?= =?UTF-8?q?=EC=8A=A4(CommonUtils)=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 모든 클래스에서 공통으로 사용할 수 있는 유틸리티 클래스 구현 - isEmpty 메소드 추가 --- src/common/utils.js | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 src/common/utils.js diff --git a/src/common/utils.js b/src/common/utils.js new file mode 100644 index 000000000..6e23ff8b7 --- /dev/null +++ b/src/common/utils.js @@ -0,0 +1,20 @@ +/** + * 모든 파일에서 참고하여 사용할 수 있는 모듈 + */ +export default class CommonUtils { + static isEmpty(something) { + if (something === undefined || something === null) { + return true; + } + if (something === '' || something === '') { + return true; + } + if (something.constructor === Object) { + return Object.keys(something).length === 0; + } + if (something.constructor === Array) { + return something.length === 0; + } + return false; + } +} From a988b2922dd75cec5878db9fc6415e3aa3f165f1 Mon Sep 17 00:00:00 2001 From: Sunmon Date: Tue, 15 Dec 2020 12:02:55 +0900 Subject: [PATCH 23/29] =?UTF-8?q?refactor:=20=EA=B0=80=EC=83=81=20DOM=20?= =?UTF-8?q?=EB=A0=88=EC=9D=B4=EC=95=84=EC=9B=83=20=EA=B5=AC=ED=98=84=20?= =?UTF-8?q?=EB=B0=8F=20=EC=A0=81=EC=9A=A9=20(stationLayout)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - element를 만드는 함수와 DOM에 추가하는 함수를 분리 - 아직 DOM에 추가되지 않은 엘리먼트들은 가상DOM에 있다고 가정함 - 가상DOM을 조작하는 함수명은 앞에 '$'가 붙는다 - 가상 DOM 엘리먼트 구조: element = { $el: 실제 엘리먼트노드 (아직 실제 DOM에 추가되진 않음) $children: { 해당 컨테이너의 child로 추가될 가상 DOM 엘리먼트} } - 가상 DOM의 구조를 바탕으로 실제 DOM에 붙이는 함수 : $render() --- src/common/utils.js | 2 + src/controllers/controller.js | 11 +- src/index.js | 6 +- src/layout/pageLayout.js | 102 ++++++++++++++-- src/layout/stationLayout.js | 222 +++++++++++++++++----------------- 5 files changed, 223 insertions(+), 120 deletions(-) diff --git a/src/common/utils.js b/src/common/utils.js index 6e23ff8b7..43b7f1595 100644 --- a/src/common/utils.js +++ b/src/common/utils.js @@ -17,4 +17,6 @@ export default class CommonUtils { } return false; } + + static DO_NOTHING() {} // DO NOTHING } diff --git a/src/controllers/controller.js b/src/controllers/controller.js index e66e237b9..2bc052675 100644 --- a/src/controllers/controller.js +++ b/src/controllers/controller.js @@ -22,6 +22,9 @@ export default class Controller { * @param {PageLayout} view */ getInputFromUser(view) { + if (view instanceof StationLayout) { + return view.rendered.querySelector('input').value; + } return view.elements.inputContainer.querySelector('input').value; } @@ -31,7 +34,13 @@ export default class Controller { replaceCurrentView(view) { const currentSection = document.querySelector('section'); - currentSection.replaceWith(view.elements.section); + // FIXME: station 고치는중이라서 임시로 넣어둠 + if (view instanceof StationLayout) { + console.log(view.elements.section.$el); + currentSection.replaceWith(view.elements.section.$el); + } else { + currentSection.replaceWith(view.elements.section); + } return view; } diff --git a/src/index.js b/src/index.js index 2abcb5112..fa04cdb91 100644 --- a/src/index.js +++ b/src/index.js @@ -14,10 +14,8 @@ const initHTML = function () { const controller = new Controller(); app.append(managerContainer, sectionContainer); managerContainer.append( - // stationElements.managerButton, - // lineElements.managerButton, - // sectionElements.managerButton, - controller.viewList.station.elements.managerButton, + // controller.viewList.station.elements.managerButton, + controller.viewList.station.elements.managerButton.$el, controller.viewList.line.elements.managerButton, controller.viewList.section.elements.managerButton, mapPrintElements.managerButton, diff --git a/src/layout/pageLayout.js b/src/layout/pageLayout.js index 1631a038f..07f599586 100644 --- a/src/layout/pageLayout.js +++ b/src/layout/pageLayout.js @@ -1,12 +1,15 @@ +import CommonUtils from '../common/utils.js'; + export default class PageLayout { constructor(controller) { - this.elements = { - managerButton: '', - section: '', - inputContainer: '', - resultContainer: '', - }; + // this.elements = { + // managerButton: '', + // section: '', + // inputContainer: '', + // resultContainer: '', + // }; this.controller = controller; + this.template = this.createTemplate(); this.elements = this.createElements(); this.buildLayout(); this.displaySavedData(); @@ -14,6 +17,7 @@ export default class PageLayout { handleManagerButton() { this.controller.setCurrentView(this); + this.refreshResultData(); console.log(`${this.constructor.name} showed!`); } @@ -25,7 +29,6 @@ export default class PageLayout { console.log(`${this.constructor.name} displayed saved data`); } - // TODO: template 을 이용하여 생성하도록 수정하기 createElements() { console.log(`${this.constructor.name} created`); } @@ -33,4 +36,89 @@ export default class PageLayout { buildLayout() { console.log(`${this.constructor.name} build!`); } + + refreshResultData() {} + + // object형태로 받은 객체를 element로 내보내기 + createElement({ + tag, + id = '', + innerHTML = '', + classList = [], + // children = [], + eventListener = {}, // {click: [], hover: []} + ...rest + } = {}) { + const node = document.createElement(tag); + CommonUtils.isEmpty(id) ? '' : (node.id = id); + CommonUtils.isEmpty(innerHTML) ? '' : (node.innerHTML = innerHTML); + CommonUtils.isEmpty(classList) ? '' : node.classList.add(...classList); + if (!CommonUtils.isEmpty(eventListener)) { + this.$addEventListener(node, eventListener); + } + if (!CommonUtils.isEmpty(rest)) { + this.$setAttribute(node, rest); + } + // TODO: childeren + return node; + } + + $setAttribute(node, attribute) { + for (const key in attribute) { + node.setAttribute(`${key}`, attribute[key]); + } + } + + $addEventListener(node, eventObject) { + for (const key in eventObject) { + eventObject[key].map(handler => { + node.addEventListener(`${key}`, handler); + }); + } + } + + $appendChildElement(parent, name, child) { + parent.$children[name] = child; + } + + createRowTemplate() { + // TODO: resultTemplate + } + + createTemplate() { + // TODO: template 구성 + const template = document.createElement('template'); + template.innerHTML = ` + +
+ `; + console.log('template created!'); + } + + // TODO: 가상 DOM (elements) 관련 => Static으로 뺄수있음 => 모듈 따로 만들기 + $createCommonElements() { + const elements = { + managerButton: { $el: this.createManagerButton(), $children: {} }, + section: { $el: this.createSection(), $children: {} }, + }; + return elements; + } + + $createElementNode(element, children = {}) { + return { + $el: element, + $children: children, + }; + } + + $render(root) { + const element = root.$el; + if (!CommonUtils.isEmpty(root.$children)) { + for (const child in root.$children) { + element.append(this.$render(root.$children[child])); + } + } + + return element; + } } diff --git a/src/layout/stationLayout.js b/src/layout/stationLayout.js index e659f02ca..8c0e3e78b 100644 --- a/src/layout/stationLayout.js +++ b/src/layout/stationLayout.js @@ -3,161 +3,167 @@ */ import PageLayout from './pageLayout.js'; +import CommonUtils from '../common/utils.js'; export default class StationLayout extends PageLayout { - createManagerButton() { - const stationManagerButton = document.createElement('button'); - stationManagerButton.id = 'station-manager-button'; - stationManagerButton.innerHTML = '1. 역 관리'; - // TODO: 이거 공통클래스로 뺄수있지않나? - stationManagerButton.addEventListener('click', () => - this.handleManagerButton(), + constructor(controller) { + super(controller); + this.elements = this.createElements(); // elemenet와 Child 저장 + this.rowTemplate = this.createRowTemplate(); + this.rendered = this.$render(this.elements.section); + console.log(this.elements); + console.log(this.rendered); + } + + // element 구조를 설정 + createElements() { + const elements = super.$createCommonElements(); + this.$appendChildElement( + elements.section, + 'inputContainer', + this.$createInputContainer(), + ); + this.$appendChildElement( + elements.section, + 'resultContainer', + this.$createResultContainer(), ); - return stationManagerButton; + return elements; } - createSection() { - const stationSection = document.createElement('section'); - stationSection.id = 'station-section'; + createManagerButton() { + return this.createElement({ + tag: 'button', + id: 'station-manager-button', + innerHTML: '1. 역 관리', + eventListener: { click: [() => this.handleManagerButton()] }, + }); + } - return stationSection; + createSection() { + return this.createElement({ + tag: 'section', + }); } createInput() { - const stationNameInput = document.createElement('input'); - stationNameInput.id = 'station-name-input'; - stationNameInput.placeholder = '역 이름을 입력해주세요.'; - - return stationNameInput; + return this.createElement({ + tag: 'input', + id: 'station-name-input', + placeholder: '역 이름을 입력해주세요', + }); } createInputTitle() { - const stationNameTitle = document.createElement('h3'); - stationNameTitle.innerHTML = '역 이름'; - - return stationNameTitle; + return this.createElement({ + tag: 'h3', + innerHTML: '역 이름', + }); } createInputAddButton() { - const stationAddButton = document.createElement('button'); - stationAddButton.id = 'station-add-button'; - stationAddButton.innerHTML = '역 추가'; - stationAddButton.addEventListener('click', () => this.handleAddButton()); - - return stationAddButton; + return this.createElement({ + tag: 'button', + id: 'station-add-button', + innerHTML: '역 추가', + eventListener: { click: [() => this.handleAddButton()] }, + }); } - createInputContainer() { - const stationNameContainer = document.createElement('article'); - - stationNameContainer.append( - this.createInputTitle(), - this.createInput(), - this.createInputAddButton(), - ); + createResultTitle() { + return this.createElement({ + tag: 'h2', + innerHTML: '🚉 지하철 역 목록', + }); + } - return stationNameContainer; + createResultTable() { + return this.createElement({ + tag: 'table', + innerHTML: + '역이름설정', + }); } - createResultContainer() { - const stationResultContainer = document.createElement('article'); - const stationResultTitle = this.createResultTitle(); - const stationResultTable = this.createResultTable(); + createDeleteButton() { + return this.createElement({ + tag: 'button', + innerHTML: '삭제', + classList: ['station-delete-button'], + eventListener: { click: [e => this.handleDeleteButton(e.target)] }, + }); + } - stationResultContainer.append(stationResultTitle, stationResultTable); + $createInputContainer() { + const element = this.createElement({ tag: 'article' }); + const title = this.$createElementNode(this.createInputTitle()); + const input = this.$createElementNode(this.createInput()); + const button = this.$createElementNode(this.createInputAddButton()); - return stationResultContainer; + return this.$createElementNode(element, { title, input, button }); } - createResultTitle() { - const stationResultTitle = document.createElement('h2'); - stationResultTitle.innerHTML = '🚉 지하철 역 목록'; + $createResultContainer() { + const element = this.createElement({ tag: 'article' }); + const title = this.$createElementNode(this.createResultTitle()); + const table = this.$createElementNode(this.createResultTable()); - return stationResultTitle; + return this.$createElementNode(element, { title, table }); } - createResultTable() { - const stationResultTable = document.createElement('table'); - stationResultTable.innerHTML = - '역이름설정'; - - return stationResultTable; + // override + createRowTemplate() { + return this.createElement({ + tag: 'template', + id: 'station-row', + innerHTML: '', + }); } - createDeleteButton() { - const deleteButton = document.createElement('button'); - deleteButton.innerHTML = '삭제'; - deleteButton.className = 'station-delete-button'; - deleteButton.addEventListener('click', e => - this.handleDeleteButton(e.target), - ); + // override + refreshResultData() { + this.rendered.querySelector('tbody').replaceWith(this.loadTableData()); + console.log('refresh!'); + } - return deleteButton; + loadTableData() { + const stationList = this.controller.modelList.station.getList(); + const tableRows = stationList.map(station => this.createRow(station.name)); + const tbody = this.createElement({ tag: 'tbody' }); + tbody.append(...tableRows); + return tbody; } - deleteRow(index) { - // TODO: do something - const table = this.elements.resultContainer.querySelector('table'); - table.deleteRow(index); + createRow(stationName) { + const clone = this.rowTemplate.content.cloneNode(true); + const td = clone.querySelectorAll('td'); + clone.querySelector('tr').dataset.stationName = stationName; + td[0].textContent = stationName; + td[1].append(this.createDeleteButton()); + + return clone; } + // TODO: 부모로 빼기 handleDeleteButton(target) { - // TODO: 삭제버튼을 눌렀을때 const tr = target.parentElement.parentElement; - console.log(tr.dataset.stationName); - this.deleteRow(tr.rowIndex); this.controller.deleteStationData(tr.dataset.stationName); - console.log('delete button clicked'); - } - - insertRow(stationName) { - // TODO: 자식구조도 object로 돌릴수있겠는데? - // TODO: controller 로 옮기기 - // TODO: VDOM 쓰면 한번에 append하지않는방향으로.. - const row = this.elements.resultContainer - .querySelector('table') - .insertRow(); - row.dataset.stationName = stationName; - row.insertCell(0).innerHTML = stationName; - row.insertCell(1).append(this.createDeleteButton()); + this.refreshResultData(); + console.log(`${tr.dataset.stationName} deleted`); } // override handleAddButton() { const input = this.controller.getInputFromUser(this); console.log(input); - this.insertRow(input); - this.controller.insertStationData(input); + this.controller.insertStationData(input); // TODO: model 클래스만들어서 상속 -> 이 메소드 부모로빼기 + this.refreshResultData(); this.clearInput(); } + // TODO: 부모로 빼기 clearInput() { - this.elements.inputContainer.querySelector('input').value = ''; - } - - // override - createElements() { - const managerButton = this.createManagerButton(); - const section = this.createSection(); - const inputContainer = this.createInputContainer(); - const resultContainer = this.createResultContainer(); - - return { managerButton, section, inputContainer, resultContainer }; - } - - // override - displaySavedData() { - // TODO: storageData 비동기? - const stationList = this.controller.modelList.station.getList(); - for (const station of stationList) { - this.insertRow(station.name); - } - } - - // override - buildLayout() { - const { section, inputContainer, resultContainer } = this.elements; - section.append(inputContainer, resultContainer); + this.rendered.querySelector('input').value = ''; } } From ec5cc5f78bb6de33c18db813c26338f0ced5715f Mon Sep 17 00:00:00 2001 From: Sunmon Date: Tue, 15 Dec 2020 15:56:48 +0900 Subject: [PATCH 24/29] =?UTF-8?q?refactor:=20=EA=B0=80=EC=83=81=20DOM=20?= =?UTF-8?q?=EB=A0=88=EC=9D=B4=EC=95=84=EC=9B=83=20=EA=B5=AC=ED=98=84=20?= =?UTF-8?q?=EB=B0=8F=20=EC=A0=81=EC=9A=A9=20(lineLayout)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - lineLayout을 가상 DOM을 이용하여 렌더링함 - element를 만드는 함수와 DOM에 추가하는 함수를 분리 - 아직 DOM에 추가되지 않은 엘리먼트들은 가상DOM에 있다고 가정함 - 가상DOM을 조작하는 함수명은 앞에 '$'가 붙는다 --- src/controllers/controller.js | 4 +- src/index.js | 3 +- src/layout/lineLayout.js | 311 +++++++++++++++++++--------------- 3 files changed, 180 insertions(+), 138 deletions(-) diff --git a/src/controllers/controller.js b/src/controllers/controller.js index 2bc052675..7265e7167 100644 --- a/src/controllers/controller.js +++ b/src/controllers/controller.js @@ -22,7 +22,7 @@ export default class Controller { * @param {PageLayout} view */ getInputFromUser(view) { - if (view instanceof StationLayout) { + if (view instanceof StationLayout || view instanceof LineLayout) { return view.rendered.querySelector('input').value; } return view.elements.inputContainer.querySelector('input').value; @@ -35,7 +35,7 @@ export default class Controller { replaceCurrentView(view) { const currentSection = document.querySelector('section'); // FIXME: station 고치는중이라서 임시로 넣어둠 - if (view instanceof StationLayout) { + if (view instanceof StationLayout || view instanceof LineLayout) { console.log(view.elements.section.$el); currentSection.replaceWith(view.elements.section.$el); } else { diff --git a/src/index.js b/src/index.js index fa04cdb91..c15ffd5f4 100644 --- a/src/index.js +++ b/src/index.js @@ -15,8 +15,9 @@ const initHTML = function () { app.append(managerContainer, sectionContainer); managerContainer.append( // controller.viewList.station.elements.managerButton, + // controller.viewList.line.elements.managerButton, controller.viewList.station.elements.managerButton.$el, - controller.viewList.line.elements.managerButton, + controller.viewList.line.elements.managerButton.$el, controller.viewList.section.elements.managerButton, mapPrintElements.managerButton, ); diff --git a/src/layout/lineLayout.js b/src/layout/lineLayout.js index 071147672..992937654 100644 --- a/src/layout/lineLayout.js +++ b/src/layout/lineLayout.js @@ -1,145 +1,226 @@ import PageLayout from './pageLayout.js'; /** - * 지하철 노선 관련 클래스 + * 지하철 노선 관련 뷰 클래스 */ export default class LineLayout extends PageLayout { + constructor(controller) { + super(controller); + this.elements = this.createElements(); // elemenet와 Child 저장 + this.rowTemplate = this.createRowTemplate(); + this.optionTemplate = this.createOptionTemplate(); + this.rendered = this.$render(this.elements.section); + } + // override createElements() { - const managerButton = this.createManagerButton(); - const section = this.createSection(); - const inputContainer = this.createInputContainer(); - const resultContainer = this.createResultContainer(); + const elements = super.$createCommonElements(); + this.$appendChildElement( + elements.section, + 'inputContainer', + this.$createInputContainer(), + ); + this.$appendChildElement( + elements.section, + 'resultContainer', + this.$createResultContainer(), + ); - return { managerButton, section, inputContainer, resultContainer }; + return elements; } - // override - buildLayout() { - const { section, inputContainer, resultContainer } = this.elements; - section.append(inputContainer, resultContainer); + createManagerButton() { + return this.createElement({ + tag: 'button', + id: 'line-manager-button', + innerHTML: '2. 노선 관리', + eventListener: { click: [() => this.handleManagerButton()] }, + }); } - createManagerButton() { - const lineManagerButton = document.createElement('button'); - lineManagerButton.id = 'line-manager-button'; - lineManagerButton.innerHTML = '2. 노선 관리'; - lineManagerButton.addEventListener('click', () => - this.handleManagerButton(), - ); + createSection() { + return this.createElement({ tag: 'section' }); + } - return lineManagerButton; + createInput() { + return this.createElement({ + tag: 'input', + id: 'line-name-input', + placeholder: '노선 이름을 입력해주세요', + }); } - createSection() { - const lineSection = document.createElement('section'); - lineSection.id = 'line-section'; + createInputTitle() { + return this.createElement({ + tag: 'h3', + innerHTML: '노선 이름', + }); + } - return lineSection; + createInputAddButton() { + return this.createElement({ + tag: 'button', + id: 'line-add-button', + innerHTML: '노선 추가', + eventListener: { click: [() => this.handleAddButton()] }, + }); } - createResultContainer() { - const lineResultContainer = document.createElement('article'); - lineResultContainer.append( - this.createResultTitle(), - this.createResultTable(), - ); + createSelectorTitle(text) { + return this.createElement({ + tag: 'h3', + innerHTML: text, + }); + } - return lineResultContainer; + createStationSelector(position) { + return this.createElement({ + tag: 'select', + id: `line-${position}-station-selector`, + name: position, + }); } createResultTitle() { - const title = document.createElement('h2'); - title.innerHTML = '🚉 지하철 노선 목록'; - - return title; + return this.createElement({ + tag: 'h2', + innerHTML: '🚉 지하철 노선 목록', + }); } createResultTable() { - const lineResultTable = document.createElement('table'); - lineResultTable.innerHTML = - '노선 이름상행 종점역하행 종점역'; + return this.createElement({ + tag: 'table', + innerHTML: + '노선 이름상행 종점역하행 종점역설정', + }); + } - return lineResultTable; + createDeleteButton() { + return this.createElement({ + tag: 'button', + innerHTML: '삭제', + classList: ['line-delete-button'], + eventListener: { click: [e => this.handleDeleteButton(e.target)] }, + }); + } + + $createSlectorContainer(text, position) { + const container = this.createElement({ tag: 'div' }); + const title = this.$createElementNode(this.createSelectorTitle(text)); + const selector = this.$createElementNode( + this.createStationSelector(position), + ); + + return this.$createElementNode(container, { title, selector }); } - createInputTitle() { - const lineNameTitle = document.createElement('h3'); - lineNameTitle.innerHTML = '노선 이름'; + $createInputContainer() { + const element = this.createElement({ tag: 'article' }); + const title = this.$createElementNode(this.createInputTitle()); + const input = this.$createElementNode(this.createInput()); + const startSelector = this.$createSlectorContainer('상행 종점', 'start'); + const endSelector = this.$createSlectorContainer('하행 종점', 'end'); + const button = this.$createElementNode(this.createInputAddButton()); - return lineNameTitle; + return this.$createElementNode(element, { + title, + input, + startSelector, + endSelector, + button, + }); } - createInput() { - const lineNameInput = document.createElement('input'); - lineNameInput.id = 'line-name-input'; - lineNameInput.placeholder = '노선 이름을 입력해주세요'; + $createResultContainer() { + const element = this.createElement({ tag: 'article' }); + const title = this.$createElementNode(this.createResultTitle()); + const table = this.$createElementNode(this.createResultTable()); - return lineNameInput; + return this.$createElementNode(element, { title, table }); } - createStationSubtitle(text) { - const subtitle = document.createElement('h3'); // TODO: label로 바꾸기 - subtitle.innerHTML = text; + // override + createRowTemplate() { + return this.createElement({ + tag: 'template', + id: 'line-row', + innerHTML: '', + }); + } - return subtitle; + // override + refreshResultData() { + const [start, end] = this.rendered.querySelectorAll('select'); + start.replaceWith(this.loadSelectorData('start')); + end.replaceWith(this.loadSelectorData('end')); + this.rendered.querySelector('tbody').replaceWith(this.loadTableData()); } - createStationSelector(position) { - const stationSelector = document.createElement('select'); - stationSelector.id = `line-${position}-station-selector`; - stationSelector.name = position; + loadSelectorData(position) { + const stationList = this.controller.modelList.station.getList(); + const options = stationList.map(station => this.createOption(station.name)); + const selector = this.createStationSelector(position); + selector.append(...options); - return stationSelector; + return selector; } - createAddButton() { - const lineAddButton = document.createElement('button'); - lineAddButton.id = 'line-add-button'; - lineAddButton.innerHTML = '노선 추가'; - lineAddButton.addEventListener('click', () => this.handleAddButton()); + createOption(stationName) { + const clone = this.optionTemplate.content.cloneNode(true); + const option = clone.querySelector('option'); + option.textContent = stationName; - return lineAddButton; + return clone; } - createDeleteButton() { - const deleteButton = document.createElement('button'); - deleteButton.innerHTML = '삭제'; - deleteButton.className = 'line-delete-button'; - deleteButton.addEventListener('click', e => - this.handleDeleteButton(e.target), - ); + createOptionTemplate() { + return this.createElement({ + tag: 'template', + id: 'select-option', + innerHTML: '', + }); + } + + loadTableData() { + const lineList = this.controller.modelList.line.getList(); + const tableRows = lineList.map(line => this.createRow(line)); + const tbody = this.createElement({ tag: 'tbody' }); + tbody.append(...tableRows); - return deleteButton; + return tbody; } - // TODO: add, deleteRow 그냥 updateTable로 바꾸기 - deleteRow(index) { - const table = this.elements.resultContainer.querySelector('table'); - table.deleteRow(index); + getLineAndLastStations(lineArray) { + return [ + lineArray[0].line, + lineArray[0].name, + lineArray[lineArray.length - 1].name, + ]; } - handleDeleteButton(target) { - const tr = target.parentElement.parentElement; - console.log( - `${tr.dataset.lineName}, ${tr.dataset.lineStart}, ${tr.dataset.lineEnd}`, + createRow(lineArray) { + const [lineName, lineStart, lineEnd] = this.getLineAndLastStations( + lineArray, ); - this.deleteRow(tr.rowIndex); - this.controller.deleteLineData(tr.dataset.lineName); - console.log('delete button clicked'); + const clone = this.rowTemplate.content.cloneNode(true); + const td = clone.querySelectorAll('td'); + const tr = clone.querySelector('tr'); + tr.dataset.lineName = lineName; + tr.dataset.lineStart = lineStart; + tr.dataset.lineEnd = lineEnd; + td[0].textContent = lineName; + td[1].textContent = lineStart; + td[2].textContent = lineEnd; + td[3].append(this.createDeleteButton()); + + return clone; } - insertRow(lineName, start, end) { - const row = this.elements.resultContainer - .querySelector('table') - .insertRow(); - row.dataset.lineName = lineName; - row.dataset.lineStart = start; - row.dataset.lineEnd = end; - row.insertCell(0).innerHTML = lineName; - row.insertCell(1).innerHTML = start; - row.insertCell(2).innerHTML = end; - row.insertCell(3).append(this.createDeleteButton()); + handleDeleteButton(target) { + const tr = target.parentElement.parentElement; + this.controller.deleteLineData(tr.dataset.lineName); + this.refreshResultData(); } getSelectedOption(selectElement) { @@ -149,54 +230,14 @@ export default class LineLayout extends PageLayout { // override handleAddButton() { const line = this.controller.getInputFromUser(this); - const { inputContainer } = this.elements; - console.log(`line line: ${line}, inputContainer: ${inputContainer}`); const start = this.getSelectedOption( - inputContainer.querySelector(`select[name='start']`), + this.rendered.querySelector(`select[name='start']`), ); const end = this.getSelectedOption( - inputContainer.querySelector(`select[name='end']`), + this.rendered.querySelector(`select[name='end']`), ); - - // TODO: querySelector말고 다른 방법은 없나? - // const selectors = this.elements.inputContainer.querySelectorAll('select'); - this.insertRow(line, start.value, end.value); this.controller.insertLineData(line, start.value, end.value); - } - - // override - displaySavedData() { - const start = this.elements.inputContainer.querySelector( - `select[name='start']`, - ); - const end = this.elements.inputContainer.querySelector( - `select[name='end']`, - ); - const stationList = this.controller.modelList.station.getList(); - // FIXME: 새로 역을 추가 후 탭을 이동하면 추가한 역이 나오지 않음 - // FIXME: displaySavedData를 create시말고 tab show할때로 옮기기 - - // TODO: 예외로 등록 ) 이미 노선이 등록된 경우 옵션에 추가하지 않음 - for (const station of stationList) { - start.insertAdjacentHTML('beforeend', ``); - end.insertAdjacentHTML('beforeend', ``); - } - - // TODO: 데이터 테이블에 추가하기 -> 데이터 테이블 업데이트 함수 만들어서 그냥 불러오기 - } - - createInputContainer() { - const lineNameContainer = document.createElement('article'); - lineNameContainer.append( - this.createInputTitle(), - this.createInput(), - this.createStationSubtitle('상행 종점'), - this.createStationSelector('start'), - this.createStationSubtitle('하행 종점'), - this.createStationSelector('end'), - this.createAddButton(), - ); - - return lineNameContainer; + this.refreshResultData(); + // this.clearInput(); // TODO: 넣기 } } From 64a4df98dc5716c404b13236a3886de93cbb3c37 Mon Sep 17 00:00:00 2001 From: Sunmon Date: Tue, 15 Dec 2020 19:57:32 +0900 Subject: [PATCH 25/29] =?UTF-8?q?refactor:=20=EA=B0=80=EC=83=81=20DOM=20?= =?UTF-8?q?=EB=A0=88=EC=9D=B4=EC=95=84=EC=9B=83=20=EA=B5=AC=ED=98=84=20?= =?UTF-8?q?=EB=B0=8F=20=EC=A0=81=EC=9A=A9=20(sectionLayout)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - sectionLayout을 가상 DOM 이용하게끔 변경 - section관리에 필요한 비즈니스 로직 추가 --- index.html | 1 + src/controllers/controller.js | 35 +++- src/index.js | 3 +- src/layout/lineLayout.js | 22 +-- src/layout/pageLayout.js | 21 +++ src/layout/sectionLayout.js | 327 ++++++++++++++++++++++------------ src/model/lineModel.js | 33 +++- src/style/style.css | 3 + 8 files changed, 307 insertions(+), 138 deletions(-) create mode 100644 src/style/style.css diff --git a/index.html b/index.html index fc99deac2..da21f7d35 100644 --- a/index.html +++ b/index.html @@ -1,6 +1,7 @@ + 지하철 노선도 관리 diff --git a/src/controllers/controller.js b/src/controllers/controller.js index 7265e7167..2dfa06840 100644 --- a/src/controllers/controller.js +++ b/src/controllers/controller.js @@ -22,7 +22,11 @@ export default class Controller { * @param {PageLayout} view */ getInputFromUser(view) { - if (view instanceof StationLayout || view instanceof LineLayout) { + if ( + view instanceof StationLayout || + view instanceof LineLayout || + view instanceof SectionLayout + ) { return view.rendered.querySelector('input').value; } return view.elements.inputContainer.querySelector('input').value; @@ -35,7 +39,11 @@ export default class Controller { replaceCurrentView(view) { const currentSection = document.querySelector('section'); // FIXME: station 고치는중이라서 임시로 넣어둠 - if (view instanceof StationLayout || view instanceof LineLayout) { + if ( + view instanceof StationLayout || + view instanceof LineLayout || + view instanceof SectionLayout + ) { console.log(view.elements.section.$el); currentSection.replaceWith(view.elements.section.$el); } else { @@ -64,10 +72,29 @@ export default class Controller { } getLineListAll() { - return this.modelList.line.getList(); + return this.modelList.line.getLineListAll(); } getLineList(lineName) { - return this.modelList.line.getList().find(row => row[0].line === lineName); + return this.modelList.line.getLineList(lineName); + // return this.modelList.line.getLineListAll().find(row => row[0].line === lineName); + } + + deleteSectionData(index, lineName, stationName) { + const node = this.modelList.line.deleteSectionData(index, lineName); + this.modelList.station.updateData(node); + } + + insertSectionData(index, lineName, stationName) { + const node = this.modelList.line.insertSectionData( + index, + lineName, + stationName, + ); + this.modelList.station.updateData([node]); + } + + getStationListAll() { + return this.modelList.station.getList(); } } diff --git a/src/index.js b/src/index.js index c15ffd5f4..0e24216b3 100644 --- a/src/index.js +++ b/src/index.js @@ -16,9 +16,10 @@ const initHTML = function () { managerContainer.append( // controller.viewList.station.elements.managerButton, // controller.viewList.line.elements.managerButton, + // controller.viewList.section.elements.managerButton, controller.viewList.station.elements.managerButton.$el, controller.viewList.line.elements.managerButton.$el, - controller.viewList.section.elements.managerButton, + controller.viewList.section.elements.managerButton.$el, mapPrintElements.managerButton, ); }; diff --git a/src/layout/lineLayout.js b/src/layout/lineLayout.js index 992937654..5bf113348 100644 --- a/src/layout/lineLayout.js +++ b/src/layout/lineLayout.js @@ -166,24 +166,8 @@ export default class LineLayout extends PageLayout { return selector; } - createOption(stationName) { - const clone = this.optionTemplate.content.cloneNode(true); - const option = clone.querySelector('option'); - option.textContent = stationName; - - return clone; - } - - createOptionTemplate() { - return this.createElement({ - tag: 'template', - id: 'select-option', - innerHTML: '', - }); - } - loadTableData() { - const lineList = this.controller.modelList.line.getList(); + const lineList = this.controller.getLineListAll(); const tableRows = lineList.map(line => this.createRow(line)); const tbody = this.createElement({ tag: 'tbody' }); tbody.append(...tableRows); @@ -223,10 +207,6 @@ export default class LineLayout extends PageLayout { this.refreshResultData(); } - getSelectedOption(selectElement) { - return selectElement.options[selectElement.selectedIndex]; - } - // override handleAddButton() { const line = this.controller.getInputFromUser(this); diff --git a/src/layout/pageLayout.js b/src/layout/pageLayout.js index 07f599586..c0f791f05 100644 --- a/src/layout/pageLayout.js +++ b/src/layout/pageLayout.js @@ -121,4 +121,25 @@ export default class PageLayout { return element; } + + // TODO: createOption은 부모로 빼기 + createOption(stationName) { + const clone = this.optionTemplate.content.cloneNode(true); + const option = clone.querySelector('option'); + option.textContent = stationName; + + return clone; + } + + createOptionTemplate() { + return this.createElement({ + tag: 'template', + id: 'select-option', + innerHTML: '', + }); + } + + getSelectedOption(selectElement) { + return selectElement.options[selectElement.selectedIndex]; + } } diff --git a/src/layout/sectionLayout.js b/src/layout/sectionLayout.js index 99bda6096..b68d9c2b2 100644 --- a/src/layout/sectionLayout.js +++ b/src/layout/sectionLayout.js @@ -5,93 +5,58 @@ import PageLayout from './pageLayout.js'; export default class SectionLayout extends PageLayout { - createManagerButton() { - const sectionManagerButton = document.createElement('button'); - sectionManagerButton.id = 'section-manager-button'; - sectionManagerButton.innerHTML = '3. 구간 관리'; - sectionManagerButton.addEventListener('click', () => - this.handleManagerButton(), - ); - - return sectionManagerButton; - } - - createSection() { - const section = document.createElement('section'); - // section.id = 'section-section'; // TODO: id 필요없으면 지워버리자 - - return section; - } - - createMenuButtonTitle(text) { - const title = document.createElement('h2'); - title.innerHTML = text; - - return title; + constructor(controller) { + super(controller); + this.elements = this.createElements(); // elemenet와 Child 저장 + this.rowTemplate = this.createRowTemplate(); + this.optionTemplate = this.createOptionTemplate(); + this.rendered = this.$render(this.elements.section); } - createStationSelector(line) { - const sectionStationSelector = document.createElement('select'); - sectionStationSelector.id = 'section-station-selector'; - if (!line) return sectionStationSelector; - - const lineList = this.controller.getLineList(line); - console.log(`lineList:`); - console.log(lineList); - for (const node of lineList) { - sectionStationSelector.insertAdjacentHTML( - 'beforeend', - ``, - ); - } + // override + createElements() { + const elements = super.$createCommonElements(); + this.$appendChildElement( + elements.section, + 'lineMenuButtonContainer', + this.$createLineMenuButtonContainer(), + ); + this.$appendChildElement( + elements.section, + 'resultContainer', + this.$createResultContainer(), + ); - return sectionStationSelector; + return elements; } - createSectionOrderTitle() { - const title = document.createElement('h3'); - title.innerHTML = '구간 등록'; - - return title; + createManagerButton() { + return this.createElement({ + tag: 'button', + id: 'section-manager-button', + innerHTML: '3. 구간 관리', + eventListener: { click: [() => this.handleManagerButton()] }, + }); } - createSectionOrderInput() { - const sectionOrderInput = document.createElement('input'); - sectionOrderInput.id = 'section-order-input'; - sectionOrderInput.placeholder = '순서'; - - return sectionOrderInput; + createSection() { + return this.createElement({ tag: 'section' }); } - createSectionAddButton() { - const sectionAddButton = document.createElement('button'); - sectionAddButton.id = 'section-add-button'; - sectionAddButton.innerHTML = '등록'; - - return sectionAddButton; + createMenuButtonTitle(text) { + return this.createElement({ tag: 'h2', innerHTML: text }); } - createSectionOrderContainer() { - const sectionOrderContainer = document.createElement('div'); - sectionOrderContainer.append( - this.createSectionOrderTitle(), - this.createStationSelector(), - this.createSectionOrderInput(), - this.createSectionAddButton(), - ); - - return sectionOrderContainer; + createStationSelector(line) { + return this.createElement({ + tag: 'select', + id: 'section-station-selector', + }); } handleSectionLineMenuButton(target) { - // TODO: section 바꾸는 함수 일반화시켜서 사용 - const { section, resultContainer } = this.elements; - resultContainer.querySelector('h2').innerHTML = `${target.innerText}`; - resultContainer - .querySelector('select') - .replaceWith(this.createStationSelector(target.innerText)); - - section.append(resultContainer); + this.refreshResultDataContainer(target.innerText); + this.rendered.querySelector('#result-container').classList.remove('hide'); } createSectionLineMenuButton(text) { @@ -106,61 +71,205 @@ export default class SectionLayout extends PageLayout { return button; } - createInputContainer() { - const sectionLineMenuButtonContainer = document.createElement('div'); - sectionLineMenuButtonContainer.append( + createLineButton(text) { + return this.createElement({ + tag: 'button', + innerHTML: text, + classList: ['section-line-menu-button'], + eventListener: { + click: [e => this.handleSectionLineMenuButton(e.target)], + }, + }); + } + + $createLineMenuButtonContainer() { + const container = this.createElement({ tag: 'article' }); + const title = this.$createElementNode( this.createMenuButtonTitle('구간을 수정할 노선을 선택해주세요'), ); + const div = this.$createElementNode(this.createElement({ tag: 'div' })); - const lineList = this.controller.getLineListAll(); // 2차원배열 - for (const row of lineList) { - sectionLineMenuButtonContainer.append( - this.createSectionLineMenuButton(row[0].line), - ); - } + return this.$createElementNode(container, { title, div }); + } - return sectionLineMenuButtonContainer; + $createInputContainer() { + const element = this.createElement({ tag: 'article' }); + const title = this.$createElementNode(this.createInputTitle()); + const selector = this.$createElementNode(this.createStationSelector()); + const input = this.$createElementNode(this.createInput()); + const button = this.$createElementNode(this.createInputAddButton()); + + return this.$createElementNode(element, { + title, + selector, + input, + button, + }); + } + + createInput() { + return this.createElement({ + tag: 'input', + id: 'section-order-input', + placeholder: '순서', + }); + } + + createInputTitle() { + return this.createElement({ + tag: 'h3', + innerHTML: '구간 등록', + }); + } + + createInputAddButton() { + return this.createElement({ + tag: 'button', + id: 'section-add-button', + innerHTML: '등록', + eventListener: { click: [() => this.handleAddButton()] }, + }); + } + + // override + handleAddButton() { + const order = this.controller.getInputFromUser(this); + const { lineName } = this.rendered.querySelectorAll('tr')[1].dataset; + const stationName = this.getSelectedOption( + this.rendered.querySelector('select'), + ).value; + + this.controller.insertSectionData(order, lineName, stationName); + this.refreshResultDataContainer(lineName); + // this.clearInput(); // TODO: 넣기 } createResultTable() { - const sectionLineResultTable = document.createElement('table'); - sectionLineResultTable.innerHTML = - '순서이름설정'; + return this.createElement({ + tag: 'table', + innerHTML: + '순서이름설정', + }); + } + + createResultTitle(target = 'n호선') { + return this.createElement({ + tag: 'h2', + innerHTML: `${target} 관리`, + }); + } - return sectionLineResultTable; + $createResultContainer() { + const container = this.createElement({ + tag: 'article', + id: 'result-container', + classList: ['hide'], + }); + const title = this.$createElementNode(this.createResultTitle()); + const inputContainer = this.$createInputContainer(); + const table = this.$createElementNode(this.createResultTable()); + + return this.$createElementNode(container, { + title, + inputContainer, + table, + }); } - createResultTitle() { - const resultTitle = document.createElement('h2'); - resultTitle.innerHTML = 'n호선 관리'; // TODO: 동적으로 바꾸기 + // override + refreshResultData() { + this.rendered + .querySelector('article > div') + .replaceWith(this.loadLineMenuButtonData()); + } - return resultTitle; + loadSelectorData(position) { + const stationList = this.controller.getStationListAll(); + const options = stationList.map(node => this.createOption(node.name)); + const selector = this.createStationSelector(position); + selector.append(...options); + + return selector; } - createResultContainer() { - const sectionResultContainer = document.createElement('div'); - sectionResultContainer.append( - this.createResultTitle(), - this.createSectionOrderContainer(), - this.createResultTable(), + refreshResultDataContainer(text) { + this.rendered // selector 업데이트 + .querySelector('#section-station-selector') + .replaceWith(this.loadSelectorData(text)); + this.rendered // title 변환 + .querySelector('#result-container > h2') + .replaceWith(this.createResultTitle(text)); + this.rendered.querySelector('tbody').replaceWith(this.loadTableData(text)); + } + + createLineMenuButtonTemplate() { + return this.createElement({ + tag: 'template', + id: 'line-menu-button', + innerHTML: '', + }); + } + + loadTableData(lineName) { + const lineList = this.controller.getLineList(lineName); // 1차원 배열 + const tableRows = lineList.map((station, index) => + this.createRow(index, station.line, station.name), ); + const tbody = this.createElement({ tag: 'tbody' }); + tbody.append(...tableRows); - return sectionResultContainer; + return tbody; } - // override - createElements() { - const managerButton = this.createManagerButton(); - const section = this.createSection(); - const inputContainer = this.createInputContainer(); - const resultContainer = this.createResultContainer(); + createRowTemplate() { + return this.createElement({ + tag: 'template', + id: 'line-row', + innerHTML: '', + }); + } - return { managerButton, section, inputContainer, resultContainer }; + createRow(index, lineName, stationName) { + const clone = this.rowTemplate.content.cloneNode(true); + const td = clone.querySelectorAll('td'); + const tr = clone.querySelector('tr'); + tr.dataset.index = index; + tr.dataset.lineName = lineName; + tr.dataset.stationName = stationName; + td[0].textContent = index; + td[1].textContent = stationName; + td[2].append(this.createDeleteButton()); + + return clone; } - // override - buildLayout() { - const { section, inputContainer, resultContainer } = this.elements; - section.append(inputContainer); + createDeleteButton() { + return this.createElement({ + tag: 'button', + innerHTML: '노선에서 제거', + classList: ['section-delete-button'], + eventListener: { click: [e => this.handleDeleteButton(e.target)] }, + }); + } + + handleDeleteButton(target) { + const tr = target.parentElement.parentElement; + this.controller.deleteSectionData( + tr.dataset.index, + tr.dataset.lineName, + tr.dataset.stationName, + ); + this.refreshResultDataContainer(tr.dataset.lineName); + } + + loadLineMenuButtonData() { + const lineList = this.controller.getLineListAll(); // 2차원배열 + const container = this.createElement({ tag: 'div' }); + const buttonList = lineList.map(list => + this.createLineButton(list[0].line), + ); + container.append(...buttonList); + + return container; } } diff --git a/src/model/lineModel.js b/src/model/lineModel.js index 66abcc2fd..7c355fdb6 100644 --- a/src/model/lineModel.js +++ b/src/model/lineModel.js @@ -8,7 +8,7 @@ import StationNode from './stationNode.js'; export default class LineModel { insertData(line, start, end) { - let lineList = this.getList(); + let lineList = this.getLineListAll(); const startNode = new StationNode({ name: start, line }); const endNode = new StationNode({ name: end, line }); @@ -19,7 +19,7 @@ export default class LineModel { } deleteData(line) { - const lineList = this.getList(); + const lineList = this.getLineListAll(); const index = lineList.findIndex(lineNodes => lineNodes[0].line === line); const nodes = lineList.splice(index, 1)[0]; // splice의 결과는 이차원배열 localStorage.setItem('lineList', JSON.stringify(lineList)); @@ -27,7 +27,34 @@ export default class LineModel { return nodes; } - getList() { + deleteSectionData(index, lineName) { + const lineList = this.getLineListAll(); + const lineIndex = lineList.findIndex( + lineNodes => lineNodes[0].line === lineName, + ); + const node = lineList[lineIndex].splice(index, 1); // splice의 결과는 이차원배열 + localStorage.setItem('lineList', JSON.stringify(lineList)); + + return node; + } + + insertSectionData(index, lineName, stationName) { + const lineList = this.getLineListAll(); + const lineIndex = lineList.findIndex( + lineNodes => lineNodes[0].line === lineName, + ); + const node = new StationNode({ name: stationName, line: lineName }); + lineList[lineIndex].splice(index, 0, node); + localStorage.setItem('lineList', JSON.stringify(lineList)); + + return node; + } + + getLineList(lineName) { + return this.getLineListAll().find(row => row[0].line === lineName); + } + + getLineListAll() { const storageLineList = localStorage.getItem('lineList'); let lineList = []; if (storageLineList) { diff --git a/src/style/style.css b/src/style/style.css new file mode 100644 index 000000000..5aef5ed05 --- /dev/null +++ b/src/style/style.css @@ -0,0 +1,3 @@ +.hide { + visibility: hidden; +} \ No newline at end of file From 307b3cef34a7391a3d46b6efbe1298d3d9ec7fb7 Mon Sep 17 00:00:00 2001 From: Sunmon Date: Tue, 15 Dec 2020 21:13:08 +0900 Subject: [PATCH 26/29] =?UTF-8?q?refactor:=20=EA=B0=80=EC=83=81=20DOM=20?= =?UTF-8?q?=EB=A0=88=EC=9D=B4=EC=95=84=EC=9B=83=20=EA=B5=AC=ED=98=84=20?= =?UTF-8?q?=EB=B0=8F=20=EC=A0=81=EC=9A=A9=20(mapPrintLayout)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/controllers/controller.js | 25 +----- src/index.js | 10 +-- src/layout/lineLayout.js | 4 - src/layout/mainPageLayout.js | 1 - src/layout/mapPrintLayout.js | 142 +++++++++++++++++++++++----------- src/layout/pageLayout.js | 46 +++-------- src/layout/sectionLayout.js | 4 - src/layout/stationLayout.js | 14 +--- src/model/stationModel.js | 1 - 9 files changed, 112 insertions(+), 135 deletions(-) diff --git a/src/controllers/controller.js b/src/controllers/controller.js index 2dfa06840..9297930c7 100644 --- a/src/controllers/controller.js +++ b/src/controllers/controller.js @@ -3,6 +3,7 @@ import StationModel from '../model/stationModel.js'; import LineLayout from '../layout/lineLayout.js'; import SectionLayout from '../layout/sectionLayout.js'; import LineModel from '../model/lineModel.js'; +import MapPrintLayout from '../layout/mapPrintLayout.js'; export default class Controller { constructor() { @@ -11,9 +12,8 @@ export default class Controller { station: new StationLayout(this), line: new LineLayout(this), section: new SectionLayout(this), + mapPrint: new MapPrintLayout(this), }; - console.log('new controller: '); - console.log(this.viewList); this.currentView = ''; } @@ -22,14 +22,7 @@ export default class Controller { * @param {PageLayout} view */ getInputFromUser(view) { - if ( - view instanceof StationLayout || - view instanceof LineLayout || - view instanceof SectionLayout - ) { - return view.rendered.querySelector('input').value; - } - return view.elements.inputContainer.querySelector('input').value; + return view.rendered.querySelector('input').value; } setCurrentView(view) { @@ -38,17 +31,7 @@ export default class Controller { replaceCurrentView(view) { const currentSection = document.querySelector('section'); - // FIXME: station 고치는중이라서 임시로 넣어둠 - if ( - view instanceof StationLayout || - view instanceof LineLayout || - view instanceof SectionLayout - ) { - console.log(view.elements.section.$el); - currentSection.replaceWith(view.elements.section.$el); - } else { - currentSection.replaceWith(view.elements.section); - } + currentSection.replaceWith(view.elements.section.$el); return view; } diff --git a/src/index.js b/src/index.js index 0e24216b3..9729572e6 100644 --- a/src/index.js +++ b/src/index.js @@ -3,24 +3,16 @@ import { managerContainer, sectionContainer, } from './layout/mainPageLayout.js'; -// import StationLayout from './layout/stationLayout.js'; -// import { lineElements } from './layout/lineLayout.js'; -// import sectionLayout from './layout/section.js'; -import { mapPrintElements } from './layout/mapPrintLayout.js'; import Controller from './controllers/controller.js'; const initHTML = function () { - // const stationLayout = new StationLayout(); const controller = new Controller(); app.append(managerContainer, sectionContainer); managerContainer.append( - // controller.viewList.station.elements.managerButton, - // controller.viewList.line.elements.managerButton, - // controller.viewList.section.elements.managerButton, controller.viewList.station.elements.managerButton.$el, controller.viewList.line.elements.managerButton.$el, controller.viewList.section.elements.managerButton.$el, - mapPrintElements.managerButton, + controller.viewList.mapPrint.elements.managerButton.$el, ); }; diff --git a/src/layout/lineLayout.js b/src/layout/lineLayout.js index 5bf113348..4e1a30856 100644 --- a/src/layout/lineLayout.js +++ b/src/layout/lineLayout.js @@ -38,10 +38,6 @@ export default class LineLayout extends PageLayout { }); } - createSection() { - return this.createElement({ tag: 'section' }); - } - createInput() { return this.createElement({ tag: 'input', diff --git a/src/layout/mainPageLayout.js b/src/layout/mainPageLayout.js index 45984c34e..28870031d 100644 --- a/src/layout/mainPageLayout.js +++ b/src/layout/mainPageLayout.js @@ -19,7 +19,6 @@ const appendNodesToDOM = function () { const buildDefaultPage = function () { initElements(); appendNodesToDOM(); - console.log('page build'); }; buildDefaultPage(); diff --git a/src/layout/mapPrintLayout.js b/src/layout/mapPrintLayout.js index ebafae5e8..b892d6c6a 100644 --- a/src/layout/mapPrintLayout.js +++ b/src/layout/mapPrintLayout.js @@ -2,49 +2,99 @@ * 지하철 노선도 출력과 관련된 레이아웃을 관리하는 모듈 */ -const mapPrintManagerButton = document.createElement('button'); -const mapPrintSection = document.createElement('section'); -// TODO: 내부에서만 쓰는 엘리먼트들을 전역변수에서 뺄 수 없을까? - -const mapPrintResultContainer = document.createElement('div'); -const mapPrintContainer = document.createElement('div'); - -// TODO: 나중에 replace가 많이 쓰이면 common으로 뺄 것 -const showPageInsteadOf = function (section) { - section.replaceWith(mapPrintSection); -}; - -// TODO: html data 속성으로 page 동적으로 바꾸기 -const handlemapPrintManagerButton = function () { - const section = document.querySelector('#section-container > section'); - showPageInsteadOf(section); - console.log('mapPrint showed'); -}; - -const initElements = function () { - mapPrintManagerButton.id = 'map-print-manager-button'; - mapPrintManagerButton.innerHTML = '4. 지하철 노선도 출력'; - mapPrintManagerButton.addEventListener('click', handlemapPrintManagerButton); - mapPrintContainer.className = 'map'; -}; - -const appendNodesToDOM = function () { - mapPrintSection.append(mapPrintResultContainer); - mapPrintResultContainer.append(mapPrintContainer); //노선 개수만큼 append - mapPrintContainer.insertAdjacentHTML('afterbegin', '

n호선

'); - mapPrintContainer.insertAdjacentHTML('beforeend', '
  • 인천
'); -}; - -const buildMapPrintSection = function () { - initElements(); - appendNodesToDOM(); - console.log('MapPrint section build'); -}; - -buildMapPrintSection(); - -// eslint-disable-next-line import/prefer-default-export -export const mapPrintElements = { - managerButton: mapPrintManagerButton, - section: mapPrintSection, -}; +import PageLayout from './pageLayout.js'; + +export default class MapPrintLayout extends PageLayout { + constructor(controller) { + super(controller); + this.elements = this.createElements(); // elemenet와 Child 저장 + this.liTemplate = this.createLiTemplate(); + this.resultMapTemplate = this.createResultMapTemplate(); + this.rendered = this.$render(this.elements.section); + } + + // override + createElements() { + const elements = super.$createCommonElements(); + this.$appendChildElement( + elements.section, + 'resultContainer', + this.$createResultContainer(), + ); + + return elements; + } + + createManagerButton() { + return this.createElement({ + tag: 'button', + id: 'map-print-manager-button', + innerHTML: '4. 지하철 노선도 출력', + eventListener: { click: [() => this.handleManagerButton()] }, + }); + } + + $createResultContainer() { + const element = this.createElement({ tag: 'article' }); + return this.$createElementNode(element); + } + + createResultTitle(text) { + return this.createElement({ + tag: 'h2', + innerHTML: text, + }); + } + + createResultMap(lineName) { + const clone = this.resultMapTemplate.content.cloneNode(true); + const div = clone.querySelector('div'); + const title = clone.querySelector('h2'); + const ul = clone.querySelector('ul'); + const liList = this.loadLiData(lineName); + ul.append(...liList); + title.innerHTML = lineName; + div.append(title, ul); + return div; + } + + // override + refreshResultData() { + const lineListAll = this.controller.getLineListAll(); + this.rendered.querySelector('article').innerHTML = ''; + const mapList = lineListAll.map(lineList => + this.createResultMap(lineList[0].line), + ); + this.rendered.querySelector('article').append(...mapList); + } + + createResultMapTemplate() { + return this.createElement({ + tag: 'template', + id: 'station-map', + innerHTML: '

    ', + }); + } + + // override + createLiTemplate() { + return this.createElement({ + tag: 'template', + id: 'station-li', + innerHTML: '
  • ', + }); + } + + createLi(stationName) { + return this.createElement({ + tag: 'li', + innerHTML: stationName, + }); + } + + loadLiData(lineName) { + const lineList = this.controller.getLineList(lineName); // 1차원 + const liList = lineList.map(station => this.createLi(station.name)); + return liList; + } +} diff --git a/src/layout/pageLayout.js b/src/layout/pageLayout.js index c0f791f05..ea0780770 100644 --- a/src/layout/pageLayout.js +++ b/src/layout/pageLayout.js @@ -2,40 +2,22 @@ import CommonUtils from '../common/utils.js'; export default class PageLayout { constructor(controller) { - // this.elements = { - // managerButton: '', - // section: '', - // inputContainer: '', - // resultContainer: '', - // }; this.controller = controller; - this.template = this.createTemplate(); this.elements = this.createElements(); - this.buildLayout(); - this.displaySavedData(); } handleManagerButton() { this.controller.setCurrentView(this); this.refreshResultData(); - console.log(`${this.constructor.name} showed!`); } - handleAddButton(view) { - console.log(`${this.constructor.name} add button clicked`); - } + handleAddButton(view) {} - displaySavedData() { - console.log(`${this.constructor.name} displayed saved data`); - } + displaySavedData() {} - createElements() { - console.log(`${this.constructor.name} created`); - } + createElements() {} - buildLayout() { - console.log(`${this.constructor.name} build!`); - } + buildLayout() {} refreshResultData() {} @@ -81,21 +63,8 @@ export default class PageLayout { parent.$children[name] = child; } - createRowTemplate() { - // TODO: resultTemplate - } + createRowTemplate() {} - createTemplate() { - // TODO: template 구성 - const template = document.createElement('template'); - template.innerHTML = ` - -
    - `; - console.log('template created!'); - } - - // TODO: 가상 DOM (elements) 관련 => Static으로 뺄수있음 => 모듈 따로 만들기 $createCommonElements() { const elements = { managerButton: { $el: this.createManagerButton(), $children: {} }, @@ -104,6 +73,10 @@ export default class PageLayout { return elements; } + createSection() { + return this.createElement({ tag: 'section' }); + } + $createElementNode(element, children = {}) { return { $el: element, @@ -122,7 +95,6 @@ export default class PageLayout { return element; } - // TODO: createOption은 부모로 빼기 createOption(stationName) { const clone = this.optionTemplate.content.cloneNode(true); const option = clone.querySelector('option'); diff --git a/src/layout/sectionLayout.js b/src/layout/sectionLayout.js index b68d9c2b2..94aa74a2b 100644 --- a/src/layout/sectionLayout.js +++ b/src/layout/sectionLayout.js @@ -39,10 +39,6 @@ export default class SectionLayout extends PageLayout { }); } - createSection() { - return this.createElement({ tag: 'section' }); - } - createMenuButtonTitle(text) { return this.createElement({ tag: 'h2', innerHTML: text }); } diff --git a/src/layout/stationLayout.js b/src/layout/stationLayout.js index 8c0e3e78b..fabb781fd 100644 --- a/src/layout/stationLayout.js +++ b/src/layout/stationLayout.js @@ -11,8 +11,6 @@ export default class StationLayout extends PageLayout { this.elements = this.createElements(); // elemenet와 Child 저장 this.rowTemplate = this.createRowTemplate(); this.rendered = this.$render(this.elements.section); - console.log(this.elements); - console.log(this.rendered); } // element 구조를 설정 @@ -41,12 +39,6 @@ export default class StationLayout extends PageLayout { }); } - createSection() { - return this.createElement({ - tag: 'section', - }); - } - createInput() { return this.createElement({ tag: 'input', @@ -124,14 +116,14 @@ export default class StationLayout extends PageLayout { // override refreshResultData() { this.rendered.querySelector('tbody').replaceWith(this.loadTableData()); - console.log('refresh!'); } loadTableData() { - const stationList = this.controller.modelList.station.getList(); + const stationList = this.controller.getStationListAll(); const tableRows = stationList.map(station => this.createRow(station.name)); const tbody = this.createElement({ tag: 'tbody' }); tbody.append(...tableRows); + return tbody; } @@ -150,13 +142,11 @@ export default class StationLayout extends PageLayout { const tr = target.parentElement.parentElement; this.controller.deleteStationData(tr.dataset.stationName); this.refreshResultData(); - console.log(`${tr.dataset.stationName} deleted`); } // override handleAddButton() { const input = this.controller.getInputFromUser(this); - console.log(input); this.controller.insertStationData(input); // TODO: model 클래스만들어서 상속 -> 이 메소드 부모로빼기 this.refreshResultData(); this.clearInput(); diff --git a/src/model/stationModel.js b/src/model/stationModel.js index 4ccaa15b7..7b9d9123d 100644 --- a/src/model/stationModel.js +++ b/src/model/stationModel.js @@ -49,7 +49,6 @@ export default class StationModel { localStorage.setItem('stationList', JSON.stringify(stationList)); } - // TODO: getList도 통일 가능할듯 getList() { const storageStationList = localStorage.getItem('stationList'); let stationList = []; From 76c93ee5fd770490304c7537cf530752e40256da Mon Sep 17 00:00:00 2001 From: Sun Jung Kim Date: Tue, 15 Dec 2020 21:52:16 +0900 Subject: [PATCH 27/29] =?UTF-8?q?style:=20=EC=83=81=EC=88=98=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/common/constants.js | 38 ++++++++++++++++++++++++++++++++++++ src/layout/lineLayout.js | 33 +++++++++++++++++++++---------- src/layout/mapPrintLayout.js | 3 ++- src/layout/sectionLayout.js | 25 ++++++++++++++++-------- src/layout/stationLayout.js | 27 +++++++++++++++---------- 5 files changed, 97 insertions(+), 29 deletions(-) create mode 100644 src/common/constants.js diff --git a/src/common/constants.js b/src/common/constants.js new file mode 100644 index 000000000..500b2577a --- /dev/null +++ b/src/common/constants.js @@ -0,0 +1,38 @@ +export const LINE_MANAGER_BUTTON = '2. 노선관리'; +export const LINE_INPUT_PLACEHOLDER = '노선 이름을 입력해주세요'; +export const LINE_INPUT_TITLE = '노선 이름'; +export const LINE_ADD_BUTTON = '노선 추가'; +export const LINE_RESULT_TITLE = '🚉 지하철 노선 목록'; +export const LINE_TABLE_TD_NAME = '노선 이름'; +export const LINE_TABLE_TD_START = '상행 종점역'; +export const LINE_TABLE_TD_END = '하행 종점역'; +export const LINE_START = '상행 종점'; +export const LINE_END = '하행 종점'; + +export const COMMON_DELETE = '삭제'; +export const COMMON_TABLE_TD_SETTING = '설정'; +export const MAP_MANAGER_BUTTON = '4. 지하철 노선도 출력'; + +export const SECTION_MANAGER_BUTTON = '3. 구간 관리'; +export const SECTION_MENU_BUTTON_TITLE = '구간을 수정할 노선을 선택해주세요'; +export const SECTION_INPUT_PLACEHOLDER = '순서'; +export const SECTION_INPUT_TITLE = '구간 등록'; +export const SECTION_INPUT_ADD_BUTTON = '등록'; +export const SECTION_RESULT_TITLE_APPEND = '관리'; +export const SECTION_DELETE_BUTTON = '노선에서 제거'; + +export const STATION_MANAGER_BUTTON = '1. 역 관리'; +export const STATION_INPUT_PLACEHOLDER = '역 이름을 입력해주세요'; +export const STATION_INPUT_TITLE = '역 이름'; +export const STATION_INPUT_ADD_BUTTON = '역 추가'; +export const STATION_RESULT_TITLE = '🚉 지하철 역 목록'; +export const STATION_TABLE_TD_NAME = '역이름'; + +export const MIN_STATION_NAME_LENGTH = 2; +export const MIN_STATION_LENGTH = 2; + +export const ERR_DUPLICATE_MESSAGE = '중복된 이름이 있습니다'; +export const ERR_DUPLICATE_STATION = '시작역과 종착역은 달라야 합니다'; +export const ERR_ENROLLED_STATION = '노선에 등록된 역은 삭제할 수 없습니다'; +export const ERR_LESS_THEN_MIN_LENGTH = + '역이 두개 이하일때는 제거할 수 없습니다'; diff --git a/src/layout/lineLayout.js b/src/layout/lineLayout.js index 4e1a30856..c5b825ca6 100644 --- a/src/layout/lineLayout.js +++ b/src/layout/lineLayout.js @@ -1,3 +1,17 @@ +import { + COMMON_DELETE, + LINE_ADD_BUTTON, + LINE_END, + LINE_INPUT_PLACEHOLDER, + LINE_INPUT_TITLE, + LINE_MANAGER_BUTTON, + LINE_RESULT_TITLE, + LINE_START, + LINE_TABLE_TD_END, + LINE_TABLE_TD_NAME, + COMMON_TABLE_TD_SETTING, + LINE_TABLE_TD_START, +} from '../common/constants.js'; import PageLayout from './pageLayout.js'; /** * 지하철 노선 관련 뷰 클래스 @@ -33,7 +47,7 @@ export default class LineLayout extends PageLayout { return this.createElement({ tag: 'button', id: 'line-manager-button', - innerHTML: '2. 노선 관리', + innerHTML: LINE_MANAGER_BUTTON, eventListener: { click: [() => this.handleManagerButton()] }, }); } @@ -42,14 +56,14 @@ export default class LineLayout extends PageLayout { return this.createElement({ tag: 'input', id: 'line-name-input', - placeholder: '노선 이름을 입력해주세요', + placeholder: LINE_INPUT_PLACEHOLDER, }); } createInputTitle() { return this.createElement({ tag: 'h3', - innerHTML: '노선 이름', + innerHTML: LINE_INPUT_TITLE, }); } @@ -57,7 +71,7 @@ export default class LineLayout extends PageLayout { return this.createElement({ tag: 'button', id: 'line-add-button', - innerHTML: '노선 추가', + innerHTML: LINE_ADD_BUTTON, eventListener: { click: [() => this.handleAddButton()] }, }); } @@ -80,22 +94,21 @@ export default class LineLayout extends PageLayout { createResultTitle() { return this.createElement({ tag: 'h2', - innerHTML: '🚉 지하철 노선 목록', + innerHTML: LINE_RESULT_TITLE, }); } createResultTable() { return this.createElement({ tag: 'table', - innerHTML: - '노선 이름상행 종점역하행 종점역설정', + innerHTML: `${LINE_TABLE_TD_NAME}${LINE_TABLE_TD_START}${LINE_TABLE_TD_END}${COMMON_TABLE_TD_SETTING}`, }); } createDeleteButton() { return this.createElement({ tag: 'button', - innerHTML: '삭제', + innerHTML: COMMON_DELETE, classList: ['line-delete-button'], eventListener: { click: [e => this.handleDeleteButton(e.target)] }, }); @@ -115,8 +128,8 @@ export default class LineLayout extends PageLayout { const element = this.createElement({ tag: 'article' }); const title = this.$createElementNode(this.createInputTitle()); const input = this.$createElementNode(this.createInput()); - const startSelector = this.$createSlectorContainer('상행 종점', 'start'); - const endSelector = this.$createSlectorContainer('하행 종점', 'end'); + const startSelector = this.$createSlectorContainer(LINE_START, 'start'); + const endSelector = this.$createSlectorContainer(LINE_END, 'end'); const button = this.$createElementNode(this.createInputAddButton()); return this.$createElementNode(element, { diff --git a/src/layout/mapPrintLayout.js b/src/layout/mapPrintLayout.js index b892d6c6a..0e0771a12 100644 --- a/src/layout/mapPrintLayout.js +++ b/src/layout/mapPrintLayout.js @@ -2,6 +2,7 @@ * 지하철 노선도 출력과 관련된 레이아웃을 관리하는 모듈 */ +import { MAP_MANAGER_BUTTON } from '../common/constants.js'; import PageLayout from './pageLayout.js'; export default class MapPrintLayout extends PageLayout { @@ -29,7 +30,7 @@ export default class MapPrintLayout extends PageLayout { return this.createElement({ tag: 'button', id: 'map-print-manager-button', - innerHTML: '4. 지하철 노선도 출력', + innerHTML: MAP_MANAGER_BUTTON, eventListener: { click: [() => this.handleManagerButton()] }, }); } diff --git a/src/layout/sectionLayout.js b/src/layout/sectionLayout.js index 94aa74a2b..666d5a94b 100644 --- a/src/layout/sectionLayout.js +++ b/src/layout/sectionLayout.js @@ -2,6 +2,15 @@ * 지하철 노선 관련 모듈 */ +import { + SECTION_MANAGER_BUTTON, + SECTION_MENU_BUTTON_TITLE, + SECTION_INPUT_PLACEHOLDER, + SECTION_INPUT_ADD_BUTTON, + SECTION_RESULT_TITLE_APPEND, + SECTION_INPUT_TITLE, + SECTION_DELETE_BUTTON, +} from '../common/constants.js'; import PageLayout from './pageLayout.js'; export default class SectionLayout extends PageLayout { @@ -34,7 +43,7 @@ export default class SectionLayout extends PageLayout { return this.createElement({ tag: 'button', id: 'section-manager-button', - innerHTML: '3. 구간 관리', + innerHTML: SECTION_MANAGER_BUTTON, eventListener: { click: [() => this.handleManagerButton()] }, }); } @@ -81,7 +90,7 @@ export default class SectionLayout extends PageLayout { $createLineMenuButtonContainer() { const container = this.createElement({ tag: 'article' }); const title = this.$createElementNode( - this.createMenuButtonTitle('구간을 수정할 노선을 선택해주세요'), + this.createMenuButtonTitle(SECTION_MENU_BUTTON_TITLE), ); const div = this.$createElementNode(this.createElement({ tag: 'div' })); @@ -107,14 +116,14 @@ export default class SectionLayout extends PageLayout { return this.createElement({ tag: 'input', id: 'section-order-input', - placeholder: '순서', + placeholder: SECTION_INPUT_PLACEHOLDER, }); } createInputTitle() { return this.createElement({ tag: 'h3', - innerHTML: '구간 등록', + innerHTML: SECTION_INPUT_TITLE, }); } @@ -122,7 +131,7 @@ export default class SectionLayout extends PageLayout { return this.createElement({ tag: 'button', id: 'section-add-button', - innerHTML: '등록', + innerHTML: SECTION_INPUT_ADD_BUTTON, eventListener: { click: [() => this.handleAddButton()] }, }); } @@ -148,10 +157,10 @@ export default class SectionLayout extends PageLayout { }); } - createResultTitle(target = 'n호선') { + createResultTitle(target = '') { return this.createElement({ tag: 'h2', - innerHTML: `${target} 관리`, + innerHTML: `${target} ${SECTION_RESULT_TITLE_APPEND}`, }); } @@ -242,7 +251,7 @@ export default class SectionLayout extends PageLayout { createDeleteButton() { return this.createElement({ tag: 'button', - innerHTML: '노선에서 제거', + innerHTML: SECTION_DELETE_BUTTON, classList: ['section-delete-button'], eventListener: { click: [e => this.handleDeleteButton(e.target)] }, }); diff --git a/src/layout/stationLayout.js b/src/layout/stationLayout.js index fabb781fd..842ee6728 100644 --- a/src/layout/stationLayout.js +++ b/src/layout/stationLayout.js @@ -4,7 +4,16 @@ import PageLayout from './pageLayout.js'; import CommonUtils from '../common/utils.js'; - +import { + STATION_MANAGER_BUTTON, + STATION_INPUT_PLACEHOLDER, + STATION_INPUT_TITLE, + STATION_INPUT_ADD_BUTTON, + STATION_RESULT_TITLE, + STATION_TABLE_TD_NAME, + COMMON_DELETE, + COMMON_TABLE_TD_SETTING, +} from '../common/constants.js'; export default class StationLayout extends PageLayout { constructor(controller) { super(controller); @@ -34,7 +43,7 @@ export default class StationLayout extends PageLayout { return this.createElement({ tag: 'button', id: 'station-manager-button', - innerHTML: '1. 역 관리', + innerHTML: STATION_MANAGER_BUTTON, eventListener: { click: [() => this.handleManagerButton()] }, }); } @@ -43,14 +52,14 @@ export default class StationLayout extends PageLayout { return this.createElement({ tag: 'input', id: 'station-name-input', - placeholder: '역 이름을 입력해주세요', + placeholder: STATION_INPUT_PLACEHOLDER, }); } createInputTitle() { return this.createElement({ tag: 'h3', - innerHTML: '역 이름', + innerHTML: STATION_INPUT_TITLE, }); } @@ -58,7 +67,7 @@ export default class StationLayout extends PageLayout { return this.createElement({ tag: 'button', id: 'station-add-button', - innerHTML: '역 추가', + innerHTML: STATION_INPUT_ADD_BUTTON, eventListener: { click: [() => this.handleAddButton()] }, }); } @@ -66,22 +75,21 @@ export default class StationLayout extends PageLayout { createResultTitle() { return this.createElement({ tag: 'h2', - innerHTML: '🚉 지하철 역 목록', + innerHTML: STATION_RESULT_TITLE, }); } createResultTable() { return this.createElement({ tag: 'table', - innerHTML: - '역이름설정', + innerHTML: `${STATION_TABLE_TD_NAME}${COMMON_TABLE_TD_SETTING}`, }); } createDeleteButton() { return this.createElement({ tag: 'button', - innerHTML: '삭제', + innerHTML: COMMON_DELETE, classList: ['station-delete-button'], eventListener: { click: [e => this.handleDeleteButton(e.target)] }, }); @@ -137,7 +145,6 @@ export default class StationLayout extends PageLayout { return clone; } - // TODO: 부모로 빼기 handleDeleteButton(target) { const tr = target.parentElement.parentElement; this.controller.deleteStationData(tr.dataset.stationName); From 6506bae4ce533f661611aeadfa76dbe54997799f Mon Sep 17 00:00:00 2001 From: Sun Jung Kim Date: Tue, 15 Dec 2020 23:01:44 +0900 Subject: [PATCH 28/29] =?UTF-8?q?feat:=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84=20=EB=AA=A9=EB=A1=9D=EC=9D=98=20=EC=98=88=EC=99=B8?= =?UTF-8?q?=EC=82=AC=ED=95=AD=20=EB=B0=9C=EC=83=9D=EC=8B=9C=20=EC=95=8C?= =?UTF-8?q?=EB=A6=BC=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 예외사항이 일어나면 알림을 띄운다 - 예외사항은 README.MD참고 - 메세지는 상수로 처리했다 --- README.md | 28 +++++++++++++++++++++++++--- src/common/constants.js | 6 +++++- src/common/customError.js | 14 ++++++++++++++ src/controllers/controller.js | 15 +++++++++++++++ src/layout/lineLayout.js | 8 ++++++-- src/layout/sectionLayout.js | 23 ++++++++++++++++------- src/layout/stationLayout.js | 13 +++++++++++-- src/model/lineModel.js | 14 ++++++++++++++ src/model/stationModel.js | 19 ++++++++++++++++--- 9 files changed, 122 insertions(+), 18 deletions(-) create mode 100644 src/common/customError.js diff --git a/README.md b/README.md index 5ff78cc4f..b5a595e5c 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,12 @@ # 🚇 지하철 노선도 미션 +## 📝 프로그램 설명 + +이 프로그램은 **'지하철 노선도 관리 프로그램'**이다. +사용자는 직접 역을 추가, 삭제 할 수 있으며 노선을 수정할 수 있다. +또한, 자신이 만든 노선을 확인할 수 있다. +만든 노선은 브라우저에 저장되며, 브라우저를 닫지 않는 한 정보가 유지된다. + ## 📝 기능 구현 목록 ### 화면 관련 기능 @@ -12,21 +19,36 @@ ### 지하철 역 관련 기능 - 지하철 역을 등록한다 - 새로고침하더라도 저장한 지하철역은 사라지지 않는다 + - 예외: 이름을 중복 저장 시도시 에러를 띄운다 - 지하철 역을 삭제한다 - 예외: 노선에 등록된 역은 삭제할 수 없다 -- 역이 노선에 등록되었는지 판단한다 - 지하철 역은 2글자 이상이어야 한다 + - 예외: 2글자 미만의 역을 저장 시도시 에러를 띄운다 - 지하철 역의 목록을 조회한다 - - 지하철역 페이지 접근시 최근 작업한 목록을 불러온다 +- 지하철역 페이지 접근시 최근 작업한 목록을 불러온다 ### 지하철 노선 관련 기능 - 지하철 노선을 등록한다 + - 예외: 같은 이름의 노선을 중복해서 저장 시도시 에러를 띄운다 - 노선 등록 시 상행 종점역과 하행 종점역을 입력받는다 + - 예외: 시작역과 종착역이 같으면 에러를 띄운다 - 지하철 노선을 삭제한다 +- 지하철 노선을 삭제하는경우, 해당 노선의 구간도 전부 삭제한다 ### 지하철 구간 관련 기능 - 수정할 지하철 노선을 선택하면 해당 노선구간 관리화면을 보여준다 - +- 수정할 노선과 위치를 입력하면 해당 자리에 역을 삽입한다. + - 예외: 현재 노선의 길이보다 긴 자리에 역을 삽입하면, 자동으로 해당 노선의 맨 뒷자리로 보낸다 + - 예외: 위치가 0보다 작으면 에러를 띄운다 + - 예외: 이미 해당 노선에 해당 역이 삽입되어있으면 에러를 띄운다 +- 지하철 구간의 길이를 삭제한다 + - 예외: 구간의 길이가 2보다 작으면 삭제하지않고 에러를 띄운다 +- 종점을 제거하는 경우 다음 역이 종점이 된다. + +### 지하철 맵 관련 기능 +- 지하철 맵을 보여준다 +- 맵은 노선에 연결된 순서대로 보여준다 +- 맵을 띄울때는 항상 최신 정보를 가져와서 갱신한다 ## 🚀 기능 요구사항 diff --git a/src/common/constants.js b/src/common/constants.js index 500b2577a..6eb28e259 100644 --- a/src/common/constants.js +++ b/src/common/constants.js @@ -31,8 +31,12 @@ export const STATION_TABLE_TD_NAME = '역이름'; export const MIN_STATION_NAME_LENGTH = 2; export const MIN_STATION_LENGTH = 2; -export const ERR_DUPLICATE_MESSAGE = '중복된 이름이 있습니다'; +export const ERR_DUPLICATE_NAME = '중복된 이름이 있습니다'; export const ERR_DUPLICATE_STATION = '시작역과 종착역은 달라야 합니다'; export const ERR_ENROLLED_STATION = '노선에 등록된 역은 삭제할 수 없습니다'; export const ERR_LESS_THEN_MIN_LENGTH = '역이 두개 이하일때는 제거할 수 없습니다'; +export const ERR_STATION_NAME_LENGTH = + '역 이름의 길이는 최소 2글자 이상이어야 합니다.'; +export const ERR_SECTION_INDEX = '역 삽입 위치는 0 이상이어야합니다.'; +export const ERR_DUPLICATE_SECTION = '이미 해당 노선에 역이 존재합니다'; diff --git a/src/common/customError.js b/src/common/customError.js new file mode 100644 index 000000000..c8ca0e184 --- /dev/null +++ b/src/common/customError.js @@ -0,0 +1,14 @@ +export default class CustomError extends Error { + constructor(message, errElem) { + super(message); + this.name = this.constructor.name; + this.errElem = errElem; + } + + alertUser() { + alert(this.message); + if (this.errElem) { + this.errElem.focus(); + } + } +} diff --git a/src/controllers/controller.js b/src/controllers/controller.js index 9297930c7..7b13e2de2 100644 --- a/src/controllers/controller.js +++ b/src/controllers/controller.js @@ -4,6 +4,14 @@ import LineLayout from '../layout/lineLayout.js'; import SectionLayout from '../layout/sectionLayout.js'; import LineModel from '../model/lineModel.js'; import MapPrintLayout from '../layout/mapPrintLayout.js'; +import CustomError from '../common/customError.js'; +import CustomUtils from '../common/utils.js'; +import { + ERR_DUPLICATE_STATION, + ERR_LESS_THEN_MIN_LENGTH, + MIN_STATION_NAME_LENGTH, + ERR_DUPLICATE_NAME, +} from '../common/constants.js'; export default class Controller { constructor() { @@ -45,6 +53,13 @@ export default class Controller { } insertLineData(line, start, end) { + const duplicated = this.getLineList(line); + if (start === end) { + throw new CustomError(ERR_DUPLICATE_STATION); + } + if (!CustomUtils.isEmpty(duplicated)){ + throw new CustomError(ERR_DUPLICATE_NAME); + } const nodes = this.modelList.line.insertData(line, start, end); this.modelList.station.updateData(nodes); } diff --git a/src/layout/lineLayout.js b/src/layout/lineLayout.js index c5b825ca6..ed6a1984f 100644 --- a/src/layout/lineLayout.js +++ b/src/layout/lineLayout.js @@ -12,6 +12,7 @@ import { COMMON_TABLE_TD_SETTING, LINE_TABLE_TD_START, } from '../common/constants.js'; +import CustomError from '../common/customError.js'; import PageLayout from './pageLayout.js'; /** * 지하철 노선 관련 뷰 클래스 @@ -225,8 +226,11 @@ export default class LineLayout extends PageLayout { const end = this.getSelectedOption( this.rendered.querySelector(`select[name='end']`), ); - this.controller.insertLineData(line, start.value, end.value); + try { + this.controller.insertLineData(line, start.value, end.value); + } catch (error) { + error.alertUser(); + } this.refreshResultData(); - // this.clearInput(); // TODO: 넣기 } } diff --git a/src/layout/sectionLayout.js b/src/layout/sectionLayout.js index 666d5a94b..9b19f19ed 100644 --- a/src/layout/sectionLayout.js +++ b/src/layout/sectionLayout.js @@ -115,6 +115,8 @@ export default class SectionLayout extends PageLayout { createInput() { return this.createElement({ tag: 'input', + type: 'number', + min: '0', id: 'section-order-input', placeholder: SECTION_INPUT_PLACEHOLDER, }); @@ -144,9 +146,12 @@ export default class SectionLayout extends PageLayout { this.rendered.querySelector('select'), ).value; - this.controller.insertSectionData(order, lineName, stationName); + try { + this.controller.insertSectionData(order, lineName, stationName); + } catch (err) { + err.alertUser(); + } this.refreshResultDataContainer(lineName); - // this.clearInput(); // TODO: 넣기 } createResultTable() { @@ -259,11 +264,15 @@ export default class SectionLayout extends PageLayout { handleDeleteButton(target) { const tr = target.parentElement.parentElement; - this.controller.deleteSectionData( - tr.dataset.index, - tr.dataset.lineName, - tr.dataset.stationName, - ); + try { + this.controller.deleteSectionData( + tr.dataset.index, + tr.dataset.lineName, + tr.dataset.stationName, + ); + } catch (err) { + err.alertUser(); + } this.refreshResultDataContainer(tr.dataset.lineName); } diff --git a/src/layout/stationLayout.js b/src/layout/stationLayout.js index 842ee6728..4b7d8f085 100644 --- a/src/layout/stationLayout.js +++ b/src/layout/stationLayout.js @@ -4,6 +4,7 @@ import PageLayout from './pageLayout.js'; import CommonUtils from '../common/utils.js'; +import CustomError from '../common/customError.js'; import { STATION_MANAGER_BUTTON, STATION_INPUT_PLACEHOLDER, @@ -147,14 +148,22 @@ export default class StationLayout extends PageLayout { handleDeleteButton(target) { const tr = target.parentElement.parentElement; - this.controller.deleteStationData(tr.dataset.stationName); + try { + this.controller.deleteStationData(tr.dataset.stationName); + } catch (err) { + err.alertUser(); + } this.refreshResultData(); } // override handleAddButton() { const input = this.controller.getInputFromUser(this); - this.controller.insertStationData(input); // TODO: model 클래스만들어서 상속 -> 이 메소드 부모로빼기 + try { + this.controller.insertStationData(input); // TODO: model 클래스만들어서 상속 -> 이 메소드 부모로빼기 + } catch (err) { + err.alertUser(); + } this.refreshResultData(); this.clearInput(); } diff --git a/src/model/lineModel.js b/src/model/lineModel.js index 7c355fdb6..4e119e63e 100644 --- a/src/model/lineModel.js +++ b/src/model/lineModel.js @@ -1,3 +1,10 @@ +import { + ERR_DUPLICATE_SECTION, + ERR_LESS_THEN_MIN_LENGTH, + ERR_SECTION_INDEX, + MIN_STATION_LENGTH, +} from '../common/constants.js'; +import CustomError from '../common/customError.js'; import StationNode from './stationNode.js'; /** @@ -32,6 +39,8 @@ export default class LineModel { const lineIndex = lineList.findIndex( lineNodes => lineNodes[0].line === lineName, ); + if (lineList[lineIndex].length < MIN_STATION_LENGTH) + throw new CustomError(ERR_LESS_THEN_MIN_LENGTH); const node = lineList[lineIndex].splice(index, 1); // splice의 결과는 이차원배열 localStorage.setItem('lineList', JSON.stringify(lineList)); @@ -43,6 +52,11 @@ export default class LineModel { const lineIndex = lineList.findIndex( lineNodes => lineNodes[0].line === lineName, ); + if (index < 0) throw new CustomError(ERR_SECTION_INDEX); + const exist = lineList[lineIndex].find( + station => station.name === stationName, + ); + if (exist) throw new CustomError(ERR_DUPLICATE_SECTION); const node = new StationNode({ name: stationName, line: lineName }); lineList[lineIndex].splice(index, 0, node); localStorage.setItem('lineList', JSON.stringify(lineList)); diff --git a/src/model/stationModel.js b/src/model/stationModel.js index 7b9d9123d..799a8ea3c 100644 --- a/src/model/stationModel.js +++ b/src/model/stationModel.js @@ -1,4 +1,12 @@ +import CustomError from '../common/customError.js'; import StationNode from './stationNode.js'; +import CommonUtils from '../common/utils.js'; +import { + ERR_DUPLICATE_NAME, + ERR_ENROLLED_STATION, + ERR_STATION_NAME_LENGTH, + MIN_STATION_NAME_LENGTH, +} from '../common/constants.js'; /** * localStorage와 통신하는 DAO @@ -34,8 +42,11 @@ export default class StationModel { insertData(stationName) { const stationList = this.getList(); + const exist = stationList.find(station => station.name === stationName); + if (exist) throw new CustomError(ERR_DUPLICATE_NAME); + if (stationName.length < MIN_STATION_NAME_LENGTH) + throw new CustomError(ERR_STATION_NAME_LENGTH); stationList.push(this.createNode(stationName)); - // stationList.push(stationName); localStorage.setItem('stationList', JSON.stringify(stationList)); } @@ -44,8 +55,10 @@ export default class StationModel { const index = stationList.findIndex( station => station.name === stationName, ); - - stationList.splice(index, 1); + const node = stationList.splice(index, 1)[0]; + if (!CommonUtils.isEmpty(node.line)) { + throw new CustomError(ERR_ENROLLED_STATION); + } localStorage.setItem('stationList', JSON.stringify(stationList)); } From f6b4f6de8f7edaa838e91421ae0f467a4bc91ada Mon Sep 17 00:00:00 2001 From: Sun-Jung Kim Date: Tue, 15 Dec 2020 23:05:26 +0900 Subject: [PATCH 29/29] Update README.md --- README.md | 38 +++++++++++++++++++++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index b5a595e5c..98201ba77 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ ## 📝 프로그램 설명 -이 프로그램은 **'지하철 노선도 관리 프로그램'**이다. +이 프로그램은 **지하철 노선도 관리 프로그램**이다. 사용자는 직접 역을 추가, 삭제 할 수 있으며 노선을 수정할 수 있다. 또한, 자신이 만든 노선을 확인할 수 있다. 만든 노선은 브라우저에 저장되며, 브라우저를 닫지 않는 한 정보가 유지된다. @@ -50,6 +50,42 @@ - 맵은 노선에 연결된 순서대로 보여준다 - 맵을 띄울때는 항상 최신 정보를 가져와서 갱신한다 + +## 🕹️ 실행방법 + + +실행하기 전 node.js와 npm을 먼저 설치한다. () + + +### 실행 준비 + +이 repo를 다운받은 후 sunmon 브랜치로 이동한다 + +```bash +git clone https://github.com/Sunmon/javascript-subway-map-precourse.git +cd javascript-subway-map-precourse +git checkout sunmon +``` + +의존성 패키지를 다운받는다 + +```bash +npm install +``` + +### 실행 + +서버를 실행한다 + +```bash +cd javascript-subway-map-precourse +npm start +``` + +브라우저에 `localhost:8080`을 쳐서 접속한다 + + + ## 🚀 기능 요구사항 ### 지하철 역 관련 기능