diff --git a/README.md b/README.md index 4d8eadd6..b4304b3f 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,6 @@

- diff --git a/backend/src/application/application.js b/backend/src/application/application.js index cf422c5a..e77964b7 100644 --- a/backend/src/application/application.js +++ b/backend/src/application/application.js @@ -71,6 +71,7 @@ class Application { this.httpServer.use(express.json()); this.httpServer.use(express.urlencoded({ extended: false })); this.httpServer.use(router); + this.httpServer.use(express.static(`${process.cwd()}/public`)); this.httpServer.use(errorHandler); } } diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 1965e0ea..084aca0c 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -882,6 +882,26 @@ "@babel/helper-plugin-utils": "^7.10.4" } }, + "@babel/plugin-transform-runtime": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.12.1.tgz", + "integrity": "sha512-Ac/H6G9FEIkS2tXsZjL4RAdS3L3WHxci0usAnz7laPWUmFiGtj7tIASChqKZMHTSQTQY6xDbOq+V1/vIq3QrWg==", + "dev": true, + "requires": { + "@babel/helper-module-imports": "^7.12.1", + "@babel/helper-plugin-utils": "^7.10.4", + "resolve": "^1.8.1", + "semver": "^5.5.1" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + } + } + }, "@babel/plugin-transform-shorthand-properties": { "version": "7.12.1", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.12.1.tgz", @@ -1671,6 +1691,7 @@ "version": "26.6.2", "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", + "dev": true, "requires": { "@types/istanbul-lib-coverage": "^2.0.0", "@types/istanbul-reports": "^3.0.0", @@ -1683,6 +1704,7 @@ "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" } @@ -1691,6 +1713,7 @@ "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" @@ -1700,6 +1723,7 @@ "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" } @@ -1707,17 +1731,20 @@ "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==" + "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==" + "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" } @@ -2154,12 +2181,14 @@ "@types/istanbul-lib-coverage": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz", - "integrity": "sha512-sz7iLqvVUg1gIedBOvlkxPlc8/uVzyS5OwGz1cKjXzkl3FpL3al0crU8YGU1WoHkxn0Wxbw5tyi6hvzJKNzFsw==" + "integrity": "sha512-sz7iLqvVUg1gIedBOvlkxPlc8/uVzyS5OwGz1cKjXzkl3FpL3al0crU8YGU1WoHkxn0Wxbw5tyi6hvzJKNzFsw==", + "dev": true }, "@types/istanbul-lib-report": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==", + "dev": true, "requires": { "@types/istanbul-lib-coverage": "*" } @@ -2168,6 +2197,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.0.tgz", "integrity": "sha512-nwKNbvnwJ2/mndE9ItP/zc2TCzw6uuodnF4EHYWD+gCQDVBuRQL5UzbZD0/ezy1iKsFU2ZQiDqg4M9dN4+wZgA==", + "dev": true, "requires": { "@types/istanbul-lib-report": "*" } @@ -2176,6 +2206,7 @@ "version": "26.0.15", "resolved": "https://registry.npmjs.org/@types/jest/-/jest-26.0.15.tgz", "integrity": "sha512-s2VMReFXRg9XXxV+CW9e5Nz8fH2K1aEhwgjUqPPbQd7g95T0laAcvLv032EhFHIa5GHsZ8W7iJEQVaJq6k3Gog==", + "dev": true, "requires": { "jest-diff": "^26.0.0", "pretty-format": "^26.0.0" @@ -2202,7 +2233,8 @@ "@types/node": { "version": "12.19.5", "resolved": "https://registry.npmjs.org/@types/node/-/node-12.19.5.tgz", - "integrity": "sha512-Wgdl27uw/jUYUFyajUGKSjDNGxmJrZi9sjeG6UJImgUtKbJoO9aldx+1XODN1EpNDX9DirvbvHHmTsNlb8GwMA==" + "integrity": "sha512-Wgdl27uw/jUYUFyajUGKSjDNGxmJrZi9sjeG6UJImgUtKbJoO9aldx+1XODN1EpNDX9DirvbvHHmTsNlb8GwMA==", + "dev": true }, "@types/normalize-package-data": { "version": "2.4.0", @@ -2305,6 +2337,7 @@ "version": "15.0.10", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.10.tgz", "integrity": "sha512-z8PNtlhrj7eJNLmrAivM7rjBESG6JwC5xP3RVk12i/8HVP7Xnx/sEmERnRImyEuUaJfO942X0qMOYsoupaJbZQ==", + "dev": true, "requires": { "@types/yargs-parser": "*" } @@ -2312,7 +2345,8 @@ "@types/yargs-parser": { "version": "15.0.0", "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-15.0.0.tgz", - "integrity": "sha512-FA/BWv8t8ZWJ+gEOnLLd8ygxH/2UFbAvgEonyfN6yWGLKc7zVjbpl2Y4CTjid9h2RfgPP6SEt6uHwEOply00yw==" + "integrity": "sha512-FA/BWv8t8ZWJ+gEOnLLd8ygxH/2UFbAvgEonyfN6yWGLKc7zVjbpl2Y4CTjid9h2RfgPP6SEt6uHwEOply00yw==", + "dev": true }, "@typescript-eslint/eslint-plugin": { "version": "4.8.1", @@ -2684,7 +2718,8 @@ "ansi-regex": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==" + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "dev": true }, "ansi-styles": { "version": "3.2.1", @@ -3475,6 +3510,17 @@ "ssri": "^6.0.1", "unique-filename": "^1.1.1", "y18n": "^4.0.0" + }, + "dependencies": { + "rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + } } }, "cache-base": { @@ -3913,6 +3959,17 @@ "mkdirp": "^0.5.1", "rimraf": "^2.5.4", "run-queue": "^1.0.0" + }, + "dependencies": { + "rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + } } }, "copy-descriptor": { @@ -4337,6 +4394,15 @@ "dev": true } } + }, + "rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } } } }, @@ -4389,7 +4455,8 @@ "diff-sequences": { "version": "26.6.2", "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-26.6.2.tgz", - "integrity": "sha512-Mv/TDa3nZ9sbc5soK+OoA74BsS3mL37yixCvUAQkiuA4Wz6YtwP/K47n2rv2ovzHZvoiQeA5FTQOschKkEwB0Q==" + "integrity": "sha512-Mv/TDa3nZ9sbc5soK+OoA74BsS3mL37yixCvUAQkiuA4Wz6YtwP/K47n2rv2ovzHZvoiQeA5FTQOschKkEwB0Q==", + "dev": true }, "diffie-hellman": { "version": "5.0.3", @@ -5887,6 +5954,17 @@ "flatted": "^2.0.0", "rimraf": "2.6.3", "write": "1.0.3" + }, + "dependencies": { + "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" + } + } } }, "flatted": { @@ -7535,6 +7613,7 @@ "version": "26.6.2", "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-26.6.2.tgz", "integrity": "sha512-6m+9Z3Gv9wN0WFVasqjCL/06+EFCMTqDEUl/b87HYK2rAPTyfz4ZIuSlPhY51PIQRWx5TaxeF1qmXKe9gfN3sA==", + "dev": true, "requires": { "chalk": "^4.0.0", "diff-sequences": "^26.6.2", @@ -7546,6 +7625,7 @@ "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" } @@ -7554,6 +7634,7 @@ "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" @@ -7563,6 +7644,7 @@ "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" } @@ -7570,17 +7652,20 @@ "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==" + "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==" + "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" } @@ -7692,7 +7777,8 @@ "jest-get-type": { "version": "26.3.0", "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-26.3.0.tgz", - "integrity": "sha512-TpfaviN1R2pQWkIihlfEanwOXK0zcxrKEE4MlU6Tn7keoXdN6/3gK/xl0yEh8DOunn5pOVGKf8hB4R9gVh04ig==" + "integrity": "sha512-TpfaviN1R2pQWkIihlfEanwOXK0zcxrKEE4MlU6Tn7keoXdN6/3gK/xl0yEh8DOunn5pOVGKf8hB4R9gVh04ig==", + "dev": true }, "jest-haste-map": { "version": "26.6.2", @@ -9229,6 +9315,17 @@ "mkdirp": "^0.5.1", "rimraf": "^2.5.4", "run-queue": "^1.0.3" + }, + "dependencies": { + "rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + } } }, "ms": { @@ -10122,6 +10219,7 @@ "version": "26.6.2", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.6.2.tgz", "integrity": "sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg==", + "dev": true, "requires": { "@jest/types": "^26.6.2", "ansi-regex": "^5.0.0", @@ -10133,6 +10231,7 @@ "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" } @@ -10141,6 +10240,7 @@ "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" } @@ -10148,12 +10248,14 @@ "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==" + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true }, "react-is": { "version": "17.0.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.1.tgz", - "integrity": "sha512-NAnt2iGDXohE5LI7uBnLnqvLQMtzhkiAOLXTmv+qnF9Ky7xAPcX8Up/xWIhxvLVGJvuLiNc4xQLtuqDRzb4fSA==" + "integrity": "sha512-NAnt2iGDXohE5LI7uBnLnqvLQMtzhkiAOLXTmv+qnF9Ky7xAPcX8Up/xWIhxvLVGJvuLiNc4xQLtuqDRzb4fSA==", + "dev": true } } }, @@ -10799,15 +10901,6 @@ "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", "dev": true }, - "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" - } - }, "ripemd160": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", diff --git a/frontend/package.json b/frontend/package.json index 0dae96e1..04816e46 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -11,14 +11,15 @@ "@babel/cli": "^7.11.6", "@babel/core": "^7.11.6", "@babel/plugin-proposal-class-properties": "^7.10.4", + "@babel/plugin-transform-runtime": "^7.12.1", "@babel/preset-env": "^7.11.5", "@babel/preset-typescript": "^7.12.7", "@testing-library/jest-dom": "^5.11.6", "@testing-library/user-event": "^12.2.2", - "@typescript-eslint/eslint-plugin": "^4.8.1", - "@typescript-eslint/parser": "^4.8.1", "@types/jest": "^26.0.15", "@types/node": "^12.19.5", + "@typescript-eslint/eslint-plugin": "^4.8.1", + "@typescript-eslint/parser": "^4.8.1", "babel-loader": "^8.1.0", "clean-webpack-plugin": "^3.0.0", "core-js": "^3.6.5", diff --git a/frontend/public/index.html b/frontend/public/index.html index d3f6cf13..cdb07f2e 100644 --- a/frontend/public/index.html +++ b/frontend/public/index.html @@ -8,7 +8,7 @@ - + diff --git a/frontend/src/common/style/common.scss b/frontend/src/common/style/common.scss new file mode 100644 index 00000000..415b1376 --- /dev/null +++ b/frontend/src/common/style/common.scss @@ -0,0 +1,3 @@ +.hide{ + display: none !important; +} diff --git a/frontend/src/common/style/index.js b/frontend/src/common/style/index.js new file mode 100644 index 00000000..0fb666be --- /dev/null +++ b/frontend/src/common/style/index.js @@ -0,0 +1,2 @@ +import "./reset.scss"; +import "./common.scss"; diff --git a/frontend/src/common/style/reset.scss b/frontend/src/common/style/reset.scss new file mode 100644 index 00000000..b1a27691 --- /dev/null +++ b/frontend/src/common/style/reset.scss @@ -0,0 +1,148 @@ +html, +body, +div, +span, +applet, +object, +iframe, +p, +blockquote, +pre, +abbr, +acronym, +address, +big, +cite, +code, +del, +dfn, +em, +img, +ins, +kbd, +q, +s, +samp, +small, +strike, +sub, +sup, +tt, +var, +center, +dl, +dt, +dd, +fieldset, +form, +label, +legend, +table, +caption, +tbody, +tfoot, +thead, +tr, +th, +td, +article, +aside, +canvas, +details, +embed, +figure, +figcaption, +footer, +header, +hgroup, +menu, +nav, +output, +ruby, +section, +summary, +time, +mark, +audio, +video { + margin: 0; + padding: 0; + border: 0; + font: inherit; + vertical-align: baseline; + box-sizing: border-box; +} + +article, +aside, +details, +figcaption, +figure, +footer, +header, +hgroup, +menu, +nav, +section { + display: block; + box-sizing: border-box; +} + +body, +input, +textarea, +select, +button { + font-family: Dotum, '돋움', Helvetica, "Apple SD Gothic Neo", sans-serif; +} + +blockquote, +q { + quotes: none; +} + +blockquote:before, +blockquote:after, +q:before, +q:after { + content: ''; + content: none; +} + +table { + border-collapse: collapse; + border-spacing: 0; +} + +html, +body, +#root { + width: 100%; + height: 100%; +} + +button { + background: none; + border: none; + cursor: pointer; + outline: none; +} + +ul, ol{ + margin: 0; + padding: 0; + list-style: none; +} + +em { + font-style: normal +} + +a { + color: inherit; + text-decoration: none; +} + +img { + vertical-align: top; +} diff --git a/frontend/src/common/style/variables.scss b/frontend/src/common/style/variables.scss new file mode 100644 index 00000000..c4487aac --- /dev/null +++ b/frontend/src/common/style/variables.scss @@ -0,0 +1,2 @@ +$theme-color: rgb(40,40,40); +$green-color: rgb(3,199,90); diff --git a/frontend/src/common/types/eventDataType.ts b/frontend/src/common/types/eventDataType.ts new file mode 100644 index 00000000..fb698d47 --- /dev/null +++ b/frontend/src/common/types/eventDataType.ts @@ -0,0 +1,12 @@ +import {EventType} from './eventType'; + +interface EventDataType{ + eventTypes: EventType[]; + eventKey: string; + listeners: EventListener[]; + bindObj: Object; +} + +export { + EventDataType +} diff --git a/frontend/src/common/types/eventTargetDataType.ts b/frontend/src/common/types/eventTargetDataType.ts new file mode 100644 index 00000000..07b7a470 --- /dev/null +++ b/frontend/src/common/types/eventTargetDataType.ts @@ -0,0 +1,8 @@ +interface EventTargetDataType{ + listener: EventListener; + bindObj: Object; +} + +export { + EventTargetDataType +} diff --git a/frontend/src/common/types/eventType.ts b/frontend/src/common/types/eventType.ts new file mode 100644 index 00000000..031d8ab5 --- /dev/null +++ b/frontend/src/common/types/eventType.ts @@ -0,0 +1,11 @@ +enum EventType { + click = 'click', + keyup = 'keyup' +} + +const eventTypes = ['click', 'keyup']; + +export { + EventType, + eventTypes +} diff --git a/frontend/src/common/types/index.js b/frontend/src/common/types/index.js new file mode 100644 index 00000000..5dd372f5 --- /dev/null +++ b/frontend/src/common/types/index.js @@ -0,0 +1,4 @@ +export { EventType, eventTypes } from './eventType'; +export { EventDataType } from './eventDataType'; +export { EventTargetDataType } from './eventTargetDataType'; +export { StoreChannelType } from "./storeChannelType"; diff --git a/frontend/src/common/types/storeChannelType.ts b/frontend/src/common/types/storeChannelType.ts new file mode 100644 index 00000000..b234b4b0 --- /dev/null +++ b/frontend/src/common/types/storeChannelType.ts @@ -0,0 +1,8 @@ +enum StoreChannelType { + SOURCE_LIST_CHANNEL = 'SOURCE_LIST_CHANNEL', + MODAL_DISPLAY_CHANNEL ='MODAL_DISPLAY_CHANNEL' +} + +export{ + StoreChannelType +} diff --git a/frontend/src/common/util/AudioUtil.ts b/frontend/src/common/util/AudioUtil.ts new file mode 100644 index 00000000..79d95a37 --- /dev/null +++ b/frontend/src/common/util/AudioUtil.ts @@ -0,0 +1,10 @@ +const audioContext = new AudioContext(); + +const decodeArrayBufferToAudio = async(arrayBuffer) => { + const audioBuffer = await audioContext.decodeAudioData(arrayBuffer); + return audioBuffer; +} + +export { + decodeArrayBufferToAudio +} diff --git a/frontend/src/common/util/EventUtil.ts b/frontend/src/common/util/EventUtil.ts new file mode 100644 index 00000000..e45b013c --- /dev/null +++ b/frontend/src/common/util/EventUtil.ts @@ -0,0 +1,12 @@ +import { EventDataType } from '@types'; + +interface RootElementType extends HTMLElement{ + registerEventListener: (eventData:EventDataType)=>{}; +} + +const rootElement: RootElementType | null = document.querySelector('#root'); +const registerEventToRoot = (eventData: EventDataType) => rootElement?.registerEventListener(eventData); + +export { + registerEventToRoot +} diff --git a/frontend/src/common/util/FileUtil.ts b/frontend/src/common/util/FileUtil.ts new file mode 100644 index 00000000..0b4f93b1 --- /dev/null +++ b/frontend/src/common/util/FileUtil.ts @@ -0,0 +1,18 @@ +const readFileAsync = (file): Promise => { + return new Promise((resolve, reject) => { + let reader = new FileReader(); + reader.onload = () => { + const {result} = reader; + + if(!result || typeof result === 'string') return; + resolve(result); + }; + + reader.onerror = reject; + reader.readAsArrayBuffer(file); + }) +} + +export{ + readFileAsync +} diff --git a/frontend/src/common/util/compressor.ts b/frontend/src/common/util/compressor.ts new file mode 100644 index 00000000..f02327a7 --- /dev/null +++ b/frontend/src/common/util/compressor.ts @@ -0,0 +1,97 @@ +export const saveFile = async (arrayBuffer: ArrayBuffer, quality: number, fileName: string) => { + const audioCtx: AudioContext = new AudioContext(); + const buffer = await audioCtx.decodeAudioData(arrayBuffer); + + const offlineAudioCtx: OfflineAudioContext = new OfflineAudioContext({ + numberOfChannels: 2, + length: (buffer.sampleRate * quality) * buffer.duration, + sampleRate: buffer.sampleRate * quality, + }); + + const soundSource = offlineAudioCtx.createBufferSource(); + soundSource.buffer = buffer; + + const compressor = offlineAudioCtx.createDynamicsCompressor(); + + compressor.threshold.setValueAtTime(-20, offlineAudioCtx.currentTime); + compressor.knee.setValueAtTime(30, offlineAudioCtx.currentTime); + compressor.ratio.setValueAtTime(5, offlineAudioCtx.currentTime); + compressor.attack.setValueAtTime(.05, offlineAudioCtx.currentTime); + compressor.release.setValueAtTime(.25, offlineAudioCtx.currentTime); + + const gainNode = offlineAudioCtx.createGain(); + gainNode.gain.setValueAtTime(1, offlineAudioCtx.currentTime); + + soundSource.connect(compressor); + compressor.connect(gainNode); + gainNode.connect(offlineAudioCtx.destination); + + soundSource.start(0); + soundSource.loop = false; + const renderedBuffer = await offlineAudioCtx.startRendering(); + + makeDownload(renderedBuffer, offlineAudioCtx.length, fileName); +} + + +const makeDownload = (abuffer: AudioBuffer, total_samples: number, fileName: string) => { + + const newFile = URL.createObjectURL(bufferToWave(abuffer, total_samples)); + + const downloadLink: HTMLElement | null = document.getElementById("download-link"); + downloadLink?.setAttribute('href', newFile); + downloadLink?.setAttribute('download', fileName); + +} + +const bufferToWave = (abuffer: AudioBuffer, len: number) => { + const numOfChan: number = abuffer.numberOfChannels; + const length: number = len * numOfChan * 2 + 44; + const buffer: ArrayBuffer = new ArrayBuffer(length); + const view: DataView = new DataView(buffer); + const channels: Float32Array[] = []; + let sample: number = 0; + let offset: number = 0; + let pos: number = 0; + + const setUint16 = (data) => { + view.setUint16(pos, data, true); + pos += 2; + } + + const setUint32 = (data) => { + view.setUint32(pos, data, true); + pos += 4; + } + + setUint32(0x46464952); + setUint32(length - 8); + setUint32(0x45564157); + + setUint32(0x20746d66); + setUint32(16); + setUint16(1); + setUint16(numOfChan); + setUint32(abuffer.sampleRate); + setUint32(abuffer.sampleRate * 2 * numOfChan); + setUint16(numOfChan * 2); + setUint16(16); + + setUint32(0x61746164); + setUint32(length - pos - 4); + + for (let i = 0; i < abuffer.numberOfChannels; i++) + channels.push(abuffer.getChannelData(i)); + + while (pos < length) { + for (let i = 0; i < numOfChan; i++) { + sample = Math.max(-1, Math.min(1, channels[i][offset])); + sample = (0.5 + sample < 0 ? sample * 32768 : sample * 32767) | 0; + view.setInt16(pos, sample, true); + pos += 2; + } + offset++ + } + + return new Blob([buffer], { type: "audio/wav" }); +} diff --git a/frontend/src/common/util/index.js b/frontend/src/common/util/index.js new file mode 100644 index 00000000..a8c4c1bd --- /dev/null +++ b/frontend/src/common/util/index.js @@ -0,0 +1,4 @@ +export * as EventUtil from "./EventUtil"; +export * as FileUtil from './FileUtil'; +export * as AudioUtil from './AudioUtil'; +export { saveFile } from './compressor'; diff --git a/frontend/src/components/App/App.scss b/frontend/src/components/App/App.scss new file mode 100644 index 00000000..4064c9c2 --- /dev/null +++ b/frontend/src/components/App/App.scss @@ -0,0 +1,12 @@ +@use '@style/variables.scss'as variables; + +audi-app:not(:defined) { + display: hidden; +} + +.audi-app-container{ + width: 100%; + height: 100%; + background-color: variables.$theme-color; + overflow: hidden; +} diff --git a/frontend/src/components/App/App.ts b/frontend/src/components/App/App.ts new file mode 100644 index 00000000..256d7604 --- /dev/null +++ b/frontend/src/components/App/App.ts @@ -0,0 +1,87 @@ +import "./App.scss"; +import { EventDataType, eventTypes, EventTargetDataType, StoreChannelType } from "@types"; + +(() => { + const App = class extends HTMLElement { + private eventListenerCollectors: Map> | null; + private eventsForListener: string[]; + + constructor() { + super(); + this.eventListenerCollectors = null; + this.eventsForListener = eventTypes; + } + + connectedCallback(): void { + this.render(); + this.init(); + this.initEvent(); + } + + render(): void { + this.innerHTML = ` +

+ + + + +
+ `; + } + + init(): void { + this.eventListenerCollectors = this.eventsForListener + .reduce((acc, cur) => acc.set(cur, new Map()), new Map()); + } + + initEvent(): void { + this.eventsForListener.forEach((eventName) => + this.addEventListener(eventName, this.eventListenerForRegistrant.bind(this)) + ); + } + + eventListenerForRegistrant(e): void { + const { target } = e; + + if (!target || !this.isEventTarget(target) || !this.eventListenerCollectors) return; + + const eventType = e.type; + const eventKey = target.getAttribute('event-key'); + + this.excuteEventListenerForTarget(eventType, eventKey, e); + } + + isEventTarget(eventTarget: HTMLElement): Boolean { + const eventKey = eventTarget.getAttribute('event-key'); + return (eventKey) ? true : false; + } + + excuteEventListenerForTarget(eventType: string, eventKey: string, e: Event): void { + if (!this.eventListenerCollectors) return; + + const eventListenerCollector = this.eventListenerCollectors.get(eventType); + if (eventListenerCollector) { + const eventTargetData = eventListenerCollector.get(eventKey); + eventTargetData?.listener.call(eventTargetData.bindObj, e); + } + } + + registerEventListener(eventData: EventDataType): void { + const { eventTypes, eventKey, listeners, bindObj } = eventData; + + eventTypes.forEach((eventType, idx) => { + if (this.eventListenerCollectors && this.eventListenerCollectors.has(eventType)) { + const eventListenerCollector = this.eventListenerCollectors.get(eventType); + if (eventListenerCollector) { + eventListenerCollector.set(eventKey, { listener: listeners[idx], bindObj: bindObj }); + this.eventListenerCollectors.set(eventType, eventListenerCollector); + } + } + }); + } + }; + + customElements.define('audi-app', App); +})(); + +export { }; diff --git a/frontend/src/components/AudioTrack/AudioTrack.scss b/frontend/src/components/AudioTrack/AudioTrack.scss new file mode 100644 index 00000000..b13f13a9 --- /dev/null +++ b/frontend/src/components/AudioTrack/AudioTrack.scss @@ -0,0 +1,69 @@ +audio-track:not(:defined) { + display: hidden; +} + +.audio-track-container{ + display: flex; + flex-direction: column; + position: relative; + width: 100%; + padding: 10px; + background-color: #000000; + box-sizing: border-box; + + .audio-track-area{ + display: flex; + + .audio-track-channel{ + display: flex; + justify-content: center; + align-items: center; + width: 20px; + padding: 30px 5px; + margin-top: 10px; + margin-right: 10px; + border: 1px solid #ffffff; + border-radius: 3px; + box-sizing: border-box; + + span{ + color:rgb(46, 204, 113); + } + } + + .audio-track{ + display: flex; + padding:10px 0; + overflow: visible; + box-sizing: border-box; + } + + &:first-of-type{ + .audio-track{ + border-top: 1px solid #ffffff; + } + } + + &:last-of-type{ + .audio-track{ + border-bottom: 1px solid #ffffff; + } + } + } + + &:last-of-type{ + margin-bottom: 0; + } + + .audio-track-massage{ + position: absolute; + top: 50%; + left: 50%; + transform: translate3d(-50%, -50%, 0); + text-align:center; + + span{ + color:rgb(158, 158, 158) + } + } +} diff --git a/frontend/src/components/AudioTrack/AudioTrack.ts b/frontend/src/components/AudioTrack/AudioTrack.ts new file mode 100644 index 00000000..a8a0016f --- /dev/null +++ b/frontend/src/components/AudioTrack/AudioTrack.ts @@ -0,0 +1,138 @@ +import "./AudioTrack.scss" + +(() => { + const AudioTrack = class extends HTMLElement { + private channels: Float32Array[]; + private sampleRate: number; + private length: number; + private trackWidth: number; + private trackHeight: number; + private trackCanvasEls: NodeListOf | null; + private trackMessage: HTMLDivElement | null; + + constructor() { + super(); + this.channels = []; + this.sampleRate = 0; + this.length = 0; + this.trackWidth = 0; + this.trackHeight = 0; + this.trackCanvasEls = null; + this.trackMessage = null; + } + + static get observedAttributes() { + return ['width', 'height']; + } + + attributeChangedCallback(attrName, oldVal, newVal) { + if(oldVal !== newVal){ + switch(attrName){ + case 'width': + this.trackWidth = Number(newVal); + break; + case 'height': + this.trackHeight = Number(newVal); + break; + } + this[attrName] = newVal; + } + } + + connectedCallback() { + try{ + this.render(); + this.init(); + }catch(e){ + console.log(e); + } + } + + init(){ + this.trackCanvasEls = document.querySelectorAll('.audio-track'); + this.trackMessage = document.querySelector('.audio-track-massage'); + } + + render() { + this.innerHTML = ` +
+
+
L
+ +
+
Drag & Drop
+
+
R
+ +
+
+ `; + } + + hideMessage(){ + this.trackMessage?.classList.add('hide'); + } + + parsePeeks(channelPeaks: Float32Array): number[]{ + const sampleSize = this.length / this.sampleRate; + const sampleStep = Math.floor(sampleSize / 10) || 1; + const resultPeaks: number[] = []; + + Array(this.sampleRate).fill(0).forEach((v, newPeakIndex) => { + const start = Math.floor(newPeakIndex * sampleSize); + const end = Math.floor(start + sampleSize); + let min = channelPeaks[0]; + let max = channelPeaks[0]; + + for (let sampleIndex = start; sampleIndex < end; sampleIndex += sampleStep) { + const v = channelPeaks[sampleIndex]; + + if (v > max) max = v; + else if (v < min) min = v; + } + + resultPeaks[2 * newPeakIndex] = max; + resultPeaks[2 * newPeakIndex + 1] = min; + }); + + return resultPeaks; + } + + draw(){ + if(this.channels.length === 0 || !this.trackCanvasEls) return; + + Object.values(this.trackCanvasEls).forEach((trackCavasElement, idx) =>{ + const peaks: number[] = this.parsePeeks(this.channels[idx]); + this.drawPath(trackCavasElement, peaks); + }); + } + + drawPath(canvas: HTMLCanvasElement, peaks: number[]){ + if(!this.trackWidth) return; + + const canvasCtx = canvas.getContext('2d'); + if(!canvasCtx) return; + + const middleHeight = this.trackHeight / 2 + const defaultLineWidth = 1; + + canvasCtx.strokeStyle = '#2196f3'; + canvasCtx.lineWidth = defaultLineWidth / (this.sampleRate / this.trackWidth); + canvasCtx.beginPath(); + + let offsetX = 0; + let offsetY; + for(let i = 0; i < peaks.length; i++){ + offsetY = middleHeight + Math.floor((peaks[i]*this.trackHeight)/2); + if(i % 2 == 0) + canvasCtx.moveTo(offsetX, offsetY); + else{ + canvasCtx.lineTo(offsetX, offsetY); + offsetX += canvasCtx.lineWidth; + } + } + canvasCtx.stroke(); + } + }; + customElements.define('audio-track', AudioTrack); + })(); diff --git a/frontend/src/components/EditTools/EditTools.scss b/frontend/src/components/EditTools/EditTools.scss new file mode 100644 index 00000000..39cb7002 --- /dev/null +++ b/frontend/src/components/EditTools/EditTools.scss @@ -0,0 +1,16 @@ +.edit-tools { + display:flex; + justify-content: space-around; + height: 50px; + margin: auto 10px; + padding: 0 5px; + + border: 1px solid white; + border-radius: 6px; +} + +.edit-tools > icon-button { + margin: auto 5px; + + cursor: pointer; +} diff --git a/frontend/src/components/EditTools/EditTools.ts b/frontend/src/components/EditTools/EditTools.ts new file mode 100644 index 00000000..2128fcbc --- /dev/null +++ b/frontend/src/components/EditTools/EditTools.ts @@ -0,0 +1,28 @@ +import './EditTools.scss' + +(() => { + const EditTools = class extends HTMLElement { + public iconlist: string[]; + + constructor() { + super(); + this.iconlist = ['cursor', 'blade', 'copy', 'cut', 'paste', 'delete', 'undo', 'redo']; + + } + + connectedCallback() { + this.render(); + } + + render() { + this.innerHTML = ` +
+ ${this.iconlist.reduce((acc, icon) => acc + ``, '')} +
+ `; + } + }; + customElements.define('edit-tools', EditTools); +})() + +export {}; diff --git a/frontend/src/components/EditorMenu/EditorMenu.scss b/frontend/src/components/EditorMenu/EditorMenu.scss new file mode 100644 index 00000000..8d98036e --- /dev/null +++ b/frontend/src/components/EditorMenu/EditorMenu.scss @@ -0,0 +1,33 @@ +#editor-menu { + display: flex; +} + +.icon-wrap { + display:flex; + justify-content: space-around; + height: 50px; + margin: auto 10px; + padding: 0 5px; + + border: 1px solid white; + border-radius: 6px; +} + +.icon-wrap > icon-button { + margin: auto 5px; + + cursor: pointer; +} + + +#user-menu{ + cursor:pointer; +} + +#user-menu:hover { + svg > path { + fill: #ffffff + } +} + + diff --git a/frontend/src/components/EditorMenu/EditorMenu.ts b/frontend/src/components/EditorMenu/EditorMenu.ts new file mode 100644 index 00000000..fb035999 --- /dev/null +++ b/frontend/src/components/EditorMenu/EditorMenu.ts @@ -0,0 +1,68 @@ +import './EditorMenu.scss'; +import { EventUtil } from "@util"; + +(() => { + const EditorMenu = class extends HTMLElement { + constructor() { + super(); + } + + connectedCallback() { + this.render(); + this.initEvent(); + } + + initEvent(){ + this.addEventListener('click', this.openSourceUploadForm.bind(this)); + this.addEventListener('click', this.openSourceDownloadForm.bind(this)); + + } + + openSourceUploadForm(e) { + const { target } = e; + const targetElement = target.closest('#upload'); + const uploadElement = document.getElementById('upload'); + const modalElement = document.querySelector('editor-modal'); + + if (modalElement && targetElement === uploadElement) { + modalElement.showModal(); + } + } + + openSourceDownloadForm(e) { + const { target } = e; + const targetElement = target.closest('#save'); + if (!targetElement) return; + const downloadModal = document.getElementById('save'); + const sourceDownloadModalElement: HTMLElement | null = document.getElementById('download'); + + if (sourceDownloadModalElement && targetElement === downloadModal) { + sourceDownloadModalElement.style.display = 'flex'; + } + } + + render() { + this.innerHTML = ` +
+
+ + +
+ + +
+ +
+ +
+ + +
+
+ `; + } + }; + customElements.define('editor-menu', EditorMenu); +})(); + +export { }; diff --git a/frontend/src/components/EffectList/EffectList.scss b/frontend/src/components/EffectList/EffectList.scss new file mode 100644 index 00000000..74f61823 --- /dev/null +++ b/frontend/src/components/EffectList/EffectList.scss @@ -0,0 +1,31 @@ +@use '@style/variables.scss' as variables; + +audi-effect-list:not(:defined) { + display: hidden; +} + +audi-effect-list{ + width: 100%; +} + +.effect-list-container{ + width: 100%; + + li{ + display: flex; + width: 100%; + padding: 7px 5px; + margin-bottom: 10px; + border: 1px solid #ffffff; + cursor: pointer; + + &:hover{ + color: variables.$green-color; + border-color: variables.$green-color; + } + + &:last-of-type{ + margin-bottom: 0; + } + } +} diff --git a/frontend/src/components/EffectList/EffectList.ts b/frontend/src/components/EffectList/EffectList.ts new file mode 100644 index 00000000..402cf08e --- /dev/null +++ b/frontend/src/components/EffectList/EffectList.ts @@ -0,0 +1,33 @@ +import "./EffectList.scss"; + +(() => { + const EffectList = class extends HTMLElement { + private effects: string[]; + + constructor() { + super(); + this.effects = ['Gain', 'Fade in', 'Fade out', 'Compressor']; + } + + connectedCallback() { + this.render(); + } + + render(): void { + this.innerHTML = ` + + `; + } + + getEffectList(): string{ + return this.effects.reduce((acc,effect)=> + acc +=`
  • ${effect}
  • ` ,''); + } + }; + customElements.define('audi-effect-list', EffectList); +})(); + +export {}; + diff --git a/frontend/src/components/Header/Header.scss b/frontend/src/components/Header/Header.scss new file mode 100644 index 00000000..80d1dd99 --- /dev/null +++ b/frontend/src/components/Header/Header.scss @@ -0,0 +1,16 @@ +#header { + display: flex; + justify-content: space-around; + height: 80px; + + border-bottom: 1px solid white; + background-color: rgb(40,40,40); +} + +logo-component { + margin: auto auto auto 30px; +} + +editor-menu { + margin: auto 0; +} diff --git a/frontend/src/components/Header/Header.ts b/frontend/src/components/Header/Header.ts new file mode 100644 index 00000000..471f5949 --- /dev/null +++ b/frontend/src/components/Header/Header.ts @@ -0,0 +1,27 @@ +import './Header.scss' + +(() => { + const HeaderComponent = class extends HTMLElement { + + + constructor() { + super(); + } + + connectedCallback() { + this.render(); + } + + render() { + this.innerHTML = ` + + `; + } + }; + customElements.define('header-component', HeaderComponent); +})() + +export {}; diff --git a/frontend/src/components/IconButton/IconButton.scss b/frontend/src/components/IconButton/IconButton.scss new file mode 100644 index 00000000..e0a29b19 --- /dev/null +++ b/frontend/src/components/IconButton/IconButton.scss @@ -0,0 +1,7 @@ +@use '@style/variables.scss' as variables; + +.icon:hover { + path { + fill: variables.$green-color; + } +} \ No newline at end of file diff --git a/frontend/src/components/IconButton/IconButton.ts b/frontend/src/components/IconButton/IconButton.ts new file mode 100644 index 00000000..13ddc926 --- /dev/null +++ b/frontend/src/components/IconButton/IconButton.ts @@ -0,0 +1,49 @@ +import icons from './icons'; +import './IconButton.scss'; + +(() => { + const IconButton = class extends HTMLElement { + public icontype: string; + public color: string; + public size: string; + + constructor() { + super(); + this.icontype = ''; + this.color = 'white'; + this.size = '20px'; + } + + static get observedAttributes() { + return ['icontype', 'color', 'size']; + } + connectedCallback() { + this.render(); + } + attributeChangedCallback(attrName, oldVal, newVal) { + this[attrName] = newVal; + this.render(); + } + + + render() { + this.innerHTML = ` +
    + + ${icons[this.icontype](this.color)} + +
    + `; + } + }; + customElements.define('icon-button', IconButton); +})(); + +export { }; diff --git a/frontend/src/components/IconButton/icons.ts b/frontend/src/components/IconButton/icons.ts new file mode 100644 index 00000000..bc370446 --- /dev/null +++ b/frontend/src/components/IconButton/icons.ts @@ -0,0 +1,36 @@ +const icons = { + play: (color: string) => ``, + pause: (color: string) => ``, + stop: (color: string) => ``, + skipNext: (color: string) => ``, + skipPrev: (color: string) => ``, + fastForward: (color: string) => ``, + fastRewind: (color: string) => ``, + repeat: (color: string) => ``, + record: (color: string) => ``, + record_on: (color: string) => ``, + user: (color: string) => ``, + menu: (color: string) => ``, + listAdd: (color: string) => ``, + zoomIn: (color: string) => ` +`, + zoomOut: (color: string) => ``, + headset: (color: string) => ``, + colorLens: (color: string) => ``, + save: (color: string) => ``, + upload: (color: string) => ``, + cursor: (color: string) => ``, + copy: (color: string) => ``, + paste: (color: string) => ``, + cut: (color: string) => ``, + delete: (color: string) => ``, + redo: (color: string) => ``, + undo: (color: string) => ``, + blade: (color: string) => ` + + +`, + link: (color: string) => ``, +} + +export default icons; diff --git a/frontend/src/components/Logo/Logo.ts b/frontend/src/components/Logo/Logo.ts new file mode 100644 index 00000000..753e93f7 --- /dev/null +++ b/frontend/src/components/Logo/Logo.ts @@ -0,0 +1,52 @@ +(() => { + const Logo = class extends HTMLElement { + public color: string; + + constructor() { + super(); + this.color = 'AUDI'; + } + + static get observedAttributes() { + // 모니터링 할 속성 이름 + return ['color']; + } + connectedCallback() { + this.render(); + } + attributeChangedCallback(attrName, oldVal, newVal) { + this[attrName] = newVal; + } + + render() { + this.innerHTML = ` + + + `; + } + }; + customElements.define('logo-component', Logo); +})(); + +export { }; diff --git a/frontend/src/components/Main/Main.scss b/frontend/src/components/Main/Main.scss new file mode 100644 index 00000000..62ba68a5 --- /dev/null +++ b/frontend/src/components/Main/Main.scss @@ -0,0 +1,20 @@ +.audi-main-container{ + width: 100%; + height: 90%; + padding: 30px 20px; + box-sizing: border-box; + .audi-main-content{ + display: flex; + justify-content: space-between; + width: 100%; + height: 100%; + padding: 30px; + border: 1px solid #ffffff; + border-radius: 5px; + background-color: #000000; + + aside{ + width: 20%; + } + } +} diff --git a/frontend/src/components/Main/Main.ts b/frontend/src/components/Main/Main.ts new file mode 100644 index 00000000..c49d9fad --- /dev/null +++ b/frontend/src/components/Main/Main.ts @@ -0,0 +1,31 @@ +import './Main.scss' + +(() => { + const Main = class extends HTMLElement { + constructor() { + super(); + } + + connectedCallback() { + this.render(); + } + + render() { + this.innerHTML = ` +
    +
    + +
    + +
    +
    +
    + `; + } + }; + customElements.define('audi-main', Main); +})(); + +export {}; diff --git a/frontend/src/components/PlaybackTools/PlaybackTools.scss b/frontend/src/components/PlaybackTools/PlaybackTools.scss new file mode 100644 index 00000000..1bc357d7 --- /dev/null +++ b/frontend/src/components/PlaybackTools/PlaybackTools.scss @@ -0,0 +1,16 @@ +.playback-tools { + display:flex; + justify-content: space-around; + height: 50px; + margin: auto 10px; + padding: 0 5px; + + border: 1px solid white; + border-radius: 6px; +} + +.playback-tools > icon-button { + margin: auto 5px; + + cursor: pointer; +} diff --git a/frontend/src/components/PlaybackTools/PlaybackTools.ts b/frontend/src/components/PlaybackTools/PlaybackTools.ts new file mode 100644 index 00000000..ac78eef7 --- /dev/null +++ b/frontend/src/components/PlaybackTools/PlaybackTools.ts @@ -0,0 +1,28 @@ +import './PlaybackTools.scss' + +(() => { + const PlaybackTools = class extends HTMLElement { + public iconlist: string[]; + + constructor() { + super(); + this.iconlist = ['play', 'stop', 'repeat', 'fastRewind', 'fastForward', 'skipPrev', 'skipNext']; + + } + + connectedCallback() { + this.render(); + } + + render() { + this.innerHTML = ` +
    + ${this.iconlist.reduce((acc, icon) => acc + ``, '')} +
    + `; + } + }; + customElements.define('playback-tools', PlaybackTools); +})() + +export { }; diff --git a/frontend/src/components/SourceDownload/SourceDownload.scss b/frontend/src/components/SourceDownload/SourceDownload.scss new file mode 100644 index 00000000..4956f0bc --- /dev/null +++ b/frontend/src/components/SourceDownload/SourceDownload.scss @@ -0,0 +1,52 @@ +@use '@style/variables' as variables; + +audi-source-download:not(:defined) { + display: hidden; +} + +.download-form{ + display:flex; + flex-direction: column; + width: 100%; + height: 100%; + + text-align: left; + + div { + width:100%; + padding: 7px 20px; + margin-bottom: 20px; + + h4{ + margin: 0 0 10px 0; + } + } +} + +.file-name{ + display:flex; + flex-direction: column; + + label { + margin-bottom: 5px; + + font-weight: bold; + } + + input { + height: 25px; + + border: 1px solid #ffffff; + border-radius: 5px; + background-color:variables.$theme-color; + color:#ffffff; + } +} + +.radios{ + + label { + margin-right: 2rem; + } +} + diff --git a/frontend/src/components/SourceDownload/SourceDownload.ts b/frontend/src/components/SourceDownload/SourceDownload.ts new file mode 100644 index 00000000..717ed53d --- /dev/null +++ b/frontend/src/components/SourceDownload/SourceDownload.ts @@ -0,0 +1,127 @@ +import { registerEventToRoot } from "@util"; +import { EventType } from "@types"; +import { saveFile } from '@util' +import './SourceDownload.scss' + +(() => { + const SourceDownload = class extends HTMLElement { + private formElement: HTMLFormElement | null; + private saveButton: HTMLButtonElement | null; + constructor() { + super(); + this.downloadModal = null; + this.formElement = null + this.saveButton = null; + } + + connectedCallback() { + this.render(); + this.initElement(); + // this.initEvent(); + console.log('click'); + + this.saveButton?.addEventListener('click', this.onSubmitHandler); + this.formElement?.addEventListener('keyup', this.onChangeHandler); + + } + + initElement(): void { + this.formElement = document.querySelector('.download-form'); + this.saveButton = document.querySelector('.save-button'); + } + + initEvent(): void { + registerEventToRoot({ + eventTypes: [EventType.click, EventType.keyup], + eventKey: 'save', + listeners: [this.onSubmitHandler, this.onChangeHandler], + bindObj: this + }); + } + + onSubmitHandler = async (e) => { + const fileNmae = `${this.formElement?.fileName.value}.${this.formElement?.extention.value}`; + const quality = this.formElement?.quality.value; + if (this.saveButton) { + this.saveButton.innerText = '압축 중' + this.disabeldButton(this.saveButton) + + // test용 음원파일 + const URL = 'https://s3-us-west-2.amazonaws.com/s.cdpn.io/123941/Yodel_Sound_Effect.mp3'; + const response = await window.fetch(URL); + const arrayBuffer = await response.arrayBuffer(); + + // arrayBuffer만 있으면 됨 + await saveFile(arrayBuffer, quality, fileNmae) + + this.saveButton.innerText = '저장하기' + this.abeldButton(this.saveButton); + } + } + + onChangeHandler = (e) => { + if (this.saveButton) { + if (this.formElement?.fileName.value.length > 0) { + this.abeldButton(this.saveButton) + this.saveButton.addEventListener('click', this.onSubmitHandler); + } else { + console.log(this.formElement?.fileName.value.length); + + this.disabeldButton(this.saveButton); + this.saveButton.removeEventListener('click', this.onSubmitHandler) + } + } + } + + abeldButton = (button) => { + button.style.backgroundColor = "#03c75a"; + button.disabled = false; + } + + disabeldButton = (button) => { + button.style.backgroundColor = "#212121"; + button.disabled = true; + } + + render() { + this.innerHTML = ` +
    +
    +

    파일 이름

    + +
    +
    +

    해상도

    + + + +
    + +
    +

    확장자

    + + +
    +
    + `; + } + }; + customElements.define('audi-source-download', SourceDownload); +})() + +export { }; diff --git a/frontend/src/components/SourceList/SourceList.scss b/frontend/src/components/SourceList/SourceList.scss new file mode 100644 index 00000000..9a5eef9b --- /dev/null +++ b/frontend/src/components/SourceList/SourceList.scss @@ -0,0 +1,73 @@ +.source-list-outer-wrap { + display:flex; + flex-direction:column; + align-items: center; + height:100%; + + border:1px solid white; + border-radius:5px; +} + +.source-list-title { + width:98%; + margin:5px; + + div { + color:white; + margin:8px 0 3px 3px; + } +} + +.source-list-wrap { + height:50%; + width:95%; + + border:1px solid white; + border-radius:5px; +} + +.audio-source { + display: flex; + justify-content: center; + align-items: center; + position: relative; + margin:5px; + height:30px; + padding: 0 8px; + + background-color:black; + color:white; + border:1px solid white; + border-radius:5px; + + cursor: pointer; + + &:hover { + background-color:rgb(60, 60, 60); + .source-info { + display:block; + }; + } + + & span{ + display: block; + font-size: 12px; + + white-space: nowrap; + text-overflow: ellipsis; + overflow: hidden; + } +} + +.source-info { + display:none; + position:absolute; + top: 0; + left: 101%; + padding: 3px; + + border-radius: 3px; + + background-color:grey; + z-index: 1000; +} diff --git a/frontend/src/components/SourceList/SourceList.ts b/frontend/src/components/SourceList/SourceList.ts new file mode 100644 index 00000000..a4eab0dc --- /dev/null +++ b/frontend/src/components/SourceList/SourceList.ts @@ -0,0 +1,80 @@ +import "./SourceList.scss"; +import { StoreChannelType } from "@types"; +import { storeChannel } from '@store'; +import { Source } from '@model'; + +(() => { + const SourceList = class extends HTMLElement { + private sourceList: Source[]; + + constructor() { + super(); + this.sourceList = []; + } + + connectedCallback() { + this.render(); + this.subscribe(); + } + + getSources() { + return this.sourceList.reduce((acc, source) => + acc + `
  • + ${source.fileName} + +
  • ` + , "") + } + + parseFileSize(fileSize: number){ + let parsedFileSize = fileSize / 1024 /1024 + parsedFileSize = parsedFileSize * 100; + parsedFileSize = Math.floor(parsedFileSize); + parsedFileSize = parsedFileSize / 100; + + return `${parsedFileSize}MB`; + } + + parsePlayTime(playTime: number) { + if(playTime < 60){ + const seconds = Math.round(playTime); + return `${seconds}초`; + } + + const minute = Math.floor(playTime / 60); + const seconds = Math.round(playTime % 60); + return `${minute}분 ${seconds}초`; + } + + render() { + this.innerHTML = ` +
    +
    +
    Source
    +
    + +
    + `; + } + + subscribe(){ + storeChannel.subscribe(StoreChannelType.SOURCE_LIST_CHANNEL,this.updateSourceList,this); + } + + updateSourceList(sourceList){ + this.sourceList = sourceList; + this.render(); + } + }; + customElements.define('source-list', SourceList); +})(); + +export {}; diff --git a/frontend/src/components/SourceUploadForm/SourceUploadForm.scss b/frontend/src/components/SourceUploadForm/SourceUploadForm.scss new file mode 100644 index 00000000..d39e809b --- /dev/null +++ b/frontend/src/components/SourceUploadForm/SourceUploadForm.scss @@ -0,0 +1,25 @@ +$baseColor: #212121; +$borderColor: #e0e0e0; +$greenColor: #03c75a; + +.source-upload-content { + display: flex; + flex-direction: column; + justify-content: center; + + width: 100%; + height: 40vh; + + border: 1px solid $borderColor; + + .source-empty:hover{ + *{ + cursor: pointer; + color: $greenColor; + } + } +} + +.hide-source-upload-content { + display: none; +} diff --git a/frontend/src/components/SourceUploadForm/SourceUploadForm.ts b/frontend/src/components/SourceUploadForm/SourceUploadForm.ts new file mode 100644 index 00000000..5f3249a3 --- /dev/null +++ b/frontend/src/components/SourceUploadForm/SourceUploadForm.ts @@ -0,0 +1,121 @@ +import './SourceUploadForm.scss'; +import { FileUtil, AudioUtil } from '@util'; +import { Source } from "@model"; +import { Controller } from '@controllers'; +import { EventUtil } from "@util"; +import { EventType } from "@types"; + +(() => { + const SourceUploadForm = class extends HTMLElement { + private filename: string; + private source: Source | null; + private sourceUploadElement: HTMLElement | null; + + constructor() { + super(); + this.filename = ''; + this.source = null; + this.sourceUploadElement = null; + } + + connectedCallback() { + this.render(); + this.initElement(); + this.initEvent(); + } + + reset(){ + this.filename = ''; + this.source = null; + this.render(); + this.initElement(); + } + + render() { + this.innerHTML = ` +
    +
    ${this.filename}
    + +
    + `; + } + + initElement() { + this.sourceUploadElement = document.querySelector('.source-upload-content'); + } + + initEvent() { + this.addEventListener('dragover', this.uploadFormDragOverListener.bind(this)); + this.addEventListener('drop', this.uploadFileListener.bind(this)); + this.addEventListener('change', this.uploadFileListener.bind(this)); + + EventUtil.registerEventToRoot({ + eventTypes:[EventType.click], + eventKey: 'source-upload', + listeners:[this.uploadBtnClickListener], + bindObj: this + }); + } + + uploadBtnClickListener(e){ + if(!this.source){ + alert('소스를 등록해주세요.'); + return; + }; + + //로드중입니다.... + Controller.addSource(this.source); + const modalElement = document.querySelector('editor-modal'); + modalElement?.hideModal(); + this.reset(); + } + + uploadFormDragOverListener(e){ + e.preventDefault(); + const { target } = e; + if (this.sourceUploadElement && e.type === 'dragover' && target === this.sourceUploadElement) { + this.sourceUploadElement.style.background = 'lightgray'; + } + } + + async uploadFileListener(e) { + e.preventDefault(); + + const ORIGIN_BACKGROUNDCOLOR = '#212121'; + if (this.sourceUploadElement && e.type === 'drop') { + this.sourceUploadElement.style.background = ORIGIN_BACKGROUNDCOLOR; + } + + const files = e.target.files || e.dataTransfer.files; + const file = files[0]; + + if (file) { + const { name } = file; + this.setFilename(name); + await this.setSource(file); + } + } + + async setSource(file) { + const arrayBuffer = await FileUtil.readFileAsync(file); + const audioBuffer = await AudioUtil.decodeArrayBufferToAudio(arrayBuffer); + this.source = new Source(file, audioBuffer); + } + + setFilename(filename) { + this.filename = filename; + this.render(); + + const sourceEmpty: HTMLElement | null = document.querySelector('.source-empty'); + if (sourceEmpty) { + sourceEmpty.className = 'hide-source-upload-content'; + } + } + }; + + customElements.define('source-upload-form', SourceUploadForm); +})(); diff --git a/frontend/src/components/TimeInfo/TimeInfo.scss b/frontend/src/components/TimeInfo/TimeInfo.scss new file mode 100644 index 00000000..5a5d3f47 --- /dev/null +++ b/frontend/src/components/TimeInfo/TimeInfo.scss @@ -0,0 +1,46 @@ +$green-color:rgb(22, 185, 22); + +@mixin my-flex($f-dir:row) { + display:flex; + flex-direction:$f-dir; + align-items:center; + justify-content: space-between; +} + +.time-info-outer-wrap { + @include my-flex(column); + height:100%; + + border:1px solid white; + border-radius:5px; +} + +.time-info-wrap { + @include my-flex(); + height:100%; + width:95%; + + > div { + color: white; + } + + .play-time { + flex-basis:50%; + font-size: 29px; + margin-left:3px; + + color: $green-color; + } + + .other-time { + flex-basis:50%; + .total-time, .cursor-time { + @include my-flex(); + margin:0 4px; + + font-size: 13px; + } + } + + color:white; +} diff --git a/frontend/src/components/TimeInfo/TimeInfo.ts b/frontend/src/components/TimeInfo/TimeInfo.ts new file mode 100644 index 00000000..a06eac98 --- /dev/null +++ b/frontend/src/components/TimeInfo/TimeInfo.ts @@ -0,0 +1,48 @@ +import "./TimeInfo.scss"; + +(() => { + const TimeInfo = class extends HTMLElement { + private playTime:string; + private totalTime:string; + private cursorTime:string; + + constructor() { + super(); + + this.playTime = "00:00:000"; + this.totalTime = "00:00:000"; + this.cursorTime = "00:00:000"; + } + + connectedCallback() { + this.render(); + } + + render() { + this.playTime = "00:00:000"; + this.totalTime = "00:00:000"; + this.cursorTime = "00:00:000"; + + this.innerHTML = ` +
    +
    +
    ${this.playTime}
    +
    +
    +
    Total
    +
    ${this.totalTime}
    +
    +
    +
    Cursor
    +
    ${this.cursorTime}
    +
    +
    +
    +
    + `; + } + }; + customElements.define('time-info', TimeInfo); +})(); + +export {}; diff --git a/frontend/src/components/audio-editor-app/index.ts b/frontend/src/components/audio-editor-app/index.ts deleted file mode 100644 index 43456aee..00000000 --- a/frontend/src/components/audio-editor-app/index.ts +++ /dev/null @@ -1,22 +0,0 @@ -export {}; - -(() => { - const AudioEditorApp = class extends HTMLElement { - constructor() { - super(); - } - - connectedCallback() { - this.render(); - } - - render() { - this.innerHTML = ` -
    - -
    - `; - } - }; - customElements.define('audio-editor-app', AudioEditorApp); -})(); diff --git a/frontend/src/components/index.js b/frontend/src/components/index.js index c8fb0adf..fd60bc43 100644 --- a/frontend/src/components/index.js +++ b/frontend/src/components/index.js @@ -1,2 +1,16 @@ -import './audio-editor-app'; -import './sample-component'; +import './App/App'; +import './AudioTrack/AudioTrack'; +import './IconButton/IconButton'; +import './Logo/Logo'; +import './EditorMenu/EditorMenu'; +import './Header/Header'; +import './EditTools/EditTools'; +import './PlaybackTools/PlaybackTools'; +import './Sidebar/Sidebar'; +import './SourceList/SourceList'; +import './TimeInfo/TimeInfo'; +import './Main/Main'; +import './Modal'; +import './SourceUploadForm/SourceUploadForm'; +import './EffectList/EffectList'; +import './SourceDownload/SourceDownload'; diff --git a/frontend/src/components/modal/Modal.scss b/frontend/src/components/modal/Modal.scss new file mode 100644 index 00000000..a6b71020 --- /dev/null +++ b/frontend/src/components/modal/Modal.scss @@ -0,0 +1,98 @@ +$baseColor: #212121; +$borderColor: #e0e0e0; +$greenColor: #03c75a; + +%modal-buttons { + display: flex; + justify-content: center; + margin-top: 1rem; + + width: 90%; +} + +%modal-button { + width: 25%; + height: 2rem; + + color: #ffffff; + border: 1px solid $borderColor; + border-radius: 3px; +} + +.modal { + display: flex; + justify-content: center; + align-items: center; + position: fixed; + z-index: 1; + left: 0; + top: 0; + width: 100%; + height: 100%; + overflow: auto; + background-color: rgba(0, 0, 0, 0.4); +} + +.modal-content { + display: flex; + flex-direction: column; + align-items: center; + + width: 50%; + padding: 2% 5%; + + color: #ffffff; + background-color: $baseColor; + border: 2px solid $borderColor; + border-radius: 5px; + + text-align: center; + + .modal-title{ + font-size: 18px; + font-weight: 600; + margin-bottom: 20px; + } +} + +.modal-component { + width: 100%; +} +modal-buttons { + width: 100%; +} + +.source-upload-buttons { + @extend %modal-buttons; + width: 100%; + + .modal-green-button { + @extend %modal-button; + margin-right: 0.5rem; + background-color: $greenColor; + } + + .modal-close-button { + @extend %modal-button; + margin-left: 0.5rem; + background-color: $baseColor; + } +} + +.compress-button { + width: 25%; + height: 100%; + margin-right: 0.5rem; + + & > .save-button{ + height: 2rem; + width:100%; + + border: 1px solid $borderColor; + border-radius: 3px; + background-color: $baseColor; + color: #ffffff; + + } +} + diff --git a/frontend/src/components/modal/Modal.ts b/frontend/src/components/modal/Modal.ts new file mode 100644 index 00000000..40f783d2 --- /dev/null +++ b/frontend/src/components/modal/Modal.ts @@ -0,0 +1,93 @@ +import { modalContents } from './modalContents'; +import { ModalType, ModalTitleType } from "./modalType/modalType"; +import { EventUtil } from "@util"; +import { EventType } from "@types"; +import './Modal.scss'; + +(() => { + const Modal = class extends HTMLElement { + public type: ModalType; + private modalElement: HTMLDivElement | null; + + constructor() { + super(); + this.type = ModalType.none; + this.title = ''; + this.modalElement = null; + } + + static get observedAttributes() { + return ['type', 'title']; + } + + connectedCallback() { + this.render(); + this.initElement(); + this.initEvent(); + } + + attributeChangedCallback(attrName, oldVal, newVal) { + if (oldVal !== newVal) { + switch(attrName){ + case 'type': + this.type = newVal; + break; + } + this[attrName] = newVal; + this.render(); + this.initElement(); + } + } + + render(): void { + this.innerHTML = ` + `; + } + + initElement(): void{ + this.modalElement = this.querySelector('.modal'); + } + + initEvent(): void{ + EventUtil.registerEventToRoot({ + eventTypes:[EventType.click], + eventKey: this.type, + listeners:[this.modalClickListener], + bindObj: this + }); + + EventUtil.registerEventToRoot({ + eventTypes:[EventType.click], + eventKey: 'modal-close', + listeners:[this.modalCloseBtnClickListener], + bindObj: this + }); + } + + modalCloseBtnClickListener(e): void{ + this.hideModal(); + } + + modalClickListener(e): void { + this.hideModal(); + } + + showModal(): void{ + this.modalElement?.classList.remove('hide'); + } + + hideModal(): void{ + this.modalElement?.classList.add('hide'); + } + } + customElements.define('editor-modal', Modal); +})(); + +export {}; + diff --git a/frontend/src/components/modal/ModalButtons/ModalButtons.ts b/frontend/src/components/modal/ModalButtons/ModalButtons.ts new file mode 100644 index 00000000..29762bd5 --- /dev/null +++ b/frontend/src/components/modal/ModalButtons/ModalButtons.ts @@ -0,0 +1,55 @@ +import { modalButtonContents } from './modalButtonContents'; + +(() => { + const ModalButtons = class extends HTMLElement { + private type: string; + private closeButtonElement: HTMLButtonElement | null; + + constructor() { + super(); + this.type = 'source'; + this.closeButtonElement = null; + } + + static get observedAttributes() { + return ['type']; + } + + attributeChangedCallback(attrName, oldVal, newVal) { + if (oldVal !== newVal) { + switch(attrName){ + case 'type': + this.type = newVal; + break; + } + this[attrName] = newVal; + this.render(); + } + } + + connectedCallback() { + this.render(); + this.initElement(); + this.changeBtnStyle(); + } + + render(): void { + this.innerHTML = ` +
    + ${modalButtonContents[this.type]} +
    + `; + } + + initElement(): void{ + this.closeButtonElement = this.querySelector('.modal-close-button'); + } + + changeBtnStyle(): void{ + if (this.type === 'effect' && this.closeButtonElement) { + this.closeButtonElement.style.marginLeft = '0rem'; + } + } + }; + customElements.define('modal-buttons', ModalButtons); +})(); diff --git a/frontend/src/components/modal/ModalButtons/modalButtonContents.ts b/frontend/src/components/modal/ModalButtons/modalButtonContents.ts new file mode 100644 index 00000000..9f5f343e --- /dev/null +++ b/frontend/src/components/modal/ModalButtons/modalButtonContents.ts @@ -0,0 +1,14 @@ +import { ModalButtonType } from "../modalType/modalType" + +const modalButtonContents: ModalButtonType = { + source: ` + + `, + effect: ``, + download: ` + ` +}; + +export { + modalButtonContents +}; diff --git a/frontend/src/components/modal/index.js b/frontend/src/components/modal/index.js new file mode 100644 index 00000000..85a6f621 --- /dev/null +++ b/frontend/src/components/modal/index.js @@ -0,0 +1,2 @@ +import './ModalButtons/ModalButtons'; +import './Modal'; diff --git a/frontend/src/components/modal/modalContents.ts b/frontend/src/components/modal/modalContents.ts new file mode 100644 index 00000000..c498e13d --- /dev/null +++ b/frontend/src/components/modal/modalContents.ts @@ -0,0 +1,11 @@ +import { ModalContentType } from "./modalType/modalType"; + +const modalContents: ModalContentType = { + source: '', + effect: '', + download: `` +}; + +export { + modalContents +} diff --git a/frontend/src/components/modal/modalType/modalType.ts b/frontend/src/components/modal/modalType/modalType.ts new file mode 100644 index 00000000..3d730802 --- /dev/null +++ b/frontend/src/components/modal/modalType/modalType.ts @@ -0,0 +1,31 @@ +enum ModalType { + none = '', + source = 'source', + effect = 'effect', + download = 'download', +} + +enum ModalTitleType { + source = '소스 불러오기', + effect = '이펙트 목록', + download = '저장하기', +} + +interface ModalContentType { + source: string; + effect: string; + download: string; +} + +interface ModalButtonType { + source: string; + effect: string; + download: string; +} + +export { + ModalType, + ModalTitleType, + ModalContentType, + ModalButtonType +} diff --git a/frontend/src/components/sample-component/index.ts b/frontend/src/components/sample-component/index.ts deleted file mode 100644 index cc540571..00000000 --- a/frontend/src/components/sample-component/index.ts +++ /dev/null @@ -1,23 +0,0 @@ -export {}; - -(() => { - const SampleComponent = class extends HTMLElement { - public text: string; - - constructor() { - super(); - this.text = 'test'; - } - - connectedCallback() { - this.render(); - } - - render() { - this.innerHTML = ` -

    ${this.text}

    - `; - } - }; - customElements.define('sample-component', SampleComponent); -})(); diff --git a/frontend/src/components/sidebar/Sidebar.ts b/frontend/src/components/sidebar/Sidebar.ts new file mode 100644 index 00000000..891db6e3 --- /dev/null +++ b/frontend/src/components/sidebar/Sidebar.ts @@ -0,0 +1,27 @@ +import "./Sidebar.scss" + +(() => { + const Sidebar = class extends HTMLElement { + + constructor() { + super(); + } + + connectedCallback() { + this.render(); + } + + render() { + this.innerHTML = ` + + + `; + } + }; + customElements.define('side-bar', Sidebar); +})(); + +export {}; diff --git a/frontend/src/components/sidebar/sidebar.scss b/frontend/src/components/sidebar/sidebar.scss new file mode 100644 index 00000000..71d3d77a --- /dev/null +++ b/frontend/src/components/sidebar/sidebar.scss @@ -0,0 +1,28 @@ +side-bar:not(:defined) { + display: hidden; +} + +#sidebar { + display:flex; + flex-direction:column; + align-items:center; + padding:1px; + height:100%; + width:270px; + + border-radius: 5px; + background-color: black; +} + +.sidebar-child { + width:100%; +} + +.sidebar-source-list { + height:85%; +} +.sidebar-time-info { + height:15%; + + margin-bottom: 10px; +} diff --git a/frontend/src/controllers/controller.ts b/frontend/src/controllers/controller.ts new file mode 100644 index 00000000..7deacfb6 --- /dev/null +++ b/frontend/src/controllers/controller.ts @@ -0,0 +1,10 @@ +import { Source } from '@model'; +import { store } from "@store"; + +const addSource = (source: Source): void =>{ + store.setSource(source); +} + +export default { + addSource +} diff --git a/frontend/src/controllers/index.js b/frontend/src/controllers/index.js new file mode 100644 index 00000000..8cbbeb9e --- /dev/null +++ b/frontend/src/controllers/index.js @@ -0,0 +1 @@ +export {default as Controller} from "./controller"; diff --git a/frontend/src/index.js b/frontend/src/index.js index 26366b10..90985b11 100644 --- a/frontend/src/index.js +++ b/frontend/src/index.js @@ -1 +1,2 @@ import '@components'; +import '@style'; diff --git a/frontend/src/model/Source.ts b/frontend/src/model/Source.ts new file mode 100644 index 00000000..ad986587 --- /dev/null +++ b/frontend/src/model/Source.ts @@ -0,0 +1,31 @@ +class Source{ + public id: number; + public fileName: string; + public sampleRate: number; + public length: number; + public duration: number; + public numberOfChannels: number; + public channelData: Float32Array[]; + public fileSize: number; + + constructor(file: File, audioBuffer: AudioBuffer){ + this.id = 0; + this.fileName = file.name; + this.fileSize = file.size; + this.sampleRate = audioBuffer.sampleRate; + this.length = audioBuffer.length; + this.duration = audioBuffer.duration; + this.numberOfChannels = audioBuffer.numberOfChannels; + this.channelData = []; + + this.setChannelData(audioBuffer); + } + + setChannelData(audioBuffer: AudioBuffer){ + for(let i = 0; i < this.numberOfChannels; i++){ + this.channelData[i] = audioBuffer.getChannelData(i); + } + } +} + +export default Source; diff --git a/frontend/src/model/index.js b/frontend/src/model/index.js new file mode 100644 index 00000000..08ffef38 --- /dev/null +++ b/frontend/src/model/index.js @@ -0,0 +1 @@ +export {default as Source} from "./Source"; diff --git a/frontend/src/store/index.js b/frontend/src/store/index.js new file mode 100644 index 00000000..eba699e4 --- /dev/null +++ b/frontend/src/store/index.js @@ -0,0 +1,2 @@ +export { storeChannel, StoreChannel } from "./storeChannel"; +export { store } from "./store"; diff --git a/frontend/src/store/store.ts b/frontend/src/store/store.ts new file mode 100644 index 00000000..a3e3ce86 --- /dev/null +++ b/frontend/src/store/store.ts @@ -0,0 +1,27 @@ +import { StateType } from "./storeType"; +import { Source } from "../model" +import { StoreChannelType } from "@types" +import { storeChannel } from "@store"; + +const store = new (class Store{ + private state: StateType; + + constructor(){ + this.state = { + sourceList:[] + } + } + + setSource(source: Source){ + const { sourceList } = this.state; + source.id = sourceList.length; + + const newSourceList = sourceList.concat(source); + this.state = {...this.state, sourceList: newSourceList}; + storeChannel.publish(StoreChannelType.SOURCE_LIST_CHANNEL, newSourceList); + } +})(); + +export { + store +} diff --git a/frontend/src/store/storeChannel.ts b/frontend/src/store/storeChannel.ts new file mode 100644 index 00000000..aa974a8a --- /dev/null +++ b/frontend/src/store/storeChannel.ts @@ -0,0 +1,43 @@ +interface ObserverData{ + callback: Function; + bindObj: Object; +} + +class StoreChannel{ + private channels: Map + private observers: Map + + constructor(){ + this.channels = new Map(); + this.observers = new Map(); + } + + publish(channel, data) { + this.channels.set(channel, data); + this.notify(channel); + } + + subscribe(channel, callback, bindObj) { + let observerDatas: ObserverData[] | undefined = this.observers.get(channel); + + if(!observerDatas) observerDatas = [{callback, bindObj}]; + + const newObserverDatas = observerDatas.concat({callback, bindObj}); + this.observers.set(channel, newObserverDatas); + } + + notify(channel) { + const observerDatas: ObserverData[] | undefined = this.observers.get(channel); + if (!observerDatas) return; + + const data = this.channels.get(channel); + observerDatas.forEach((observerData) => observerData.callback.call(observerData.bindObj,data)); + } +} + +const storeChannel = new StoreChannel(); + +export{ + storeChannel, + StoreChannel +} diff --git a/frontend/src/store/storeType.ts b/frontend/src/store/storeType.ts new file mode 100644 index 00000000..3d12ff1a --- /dev/null +++ b/frontend/src/store/storeType.ts @@ -0,0 +1,9 @@ +import {Source} from "../model" + +interface StateType { + sourceList: Source[]; +} + +export{ + StateType +} diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json index dfaf4a6b..8aac7deb 100644 --- a/frontend/tsconfig.json +++ b/frontend/tsconfig.json @@ -19,6 +19,17 @@ "isolatedModules": false, "noEmit": true, "noImplicitAny": false, + "baseUrl": ".", + "paths": { + "@components": ["src/components"], + "@style": ["src/common/style"], + "@util": ["src/common/util"], + "@types": ["src/common/types"], + "@store": ["src/store"], + "@model": ["src/model"], + "@controllers": ["src/controllers"] + } + }, "include": [ "src" diff --git a/frontend/webpack.config.common.js b/frontend/webpack.config.common.js index c817ecfc..1f8fb97f 100644 --- a/frontend/webpack.config.common.js +++ b/frontend/webpack.config.common.js @@ -28,7 +28,13 @@ module.exports = (env) => { resolve: { extensions: ['.js', '.ts'], alias: { - '@components': path.resolve(__dirname, 'src/components') + '@components': path.resolve(__dirname, 'src/components'), + '@style': path.resolve(__dirname, 'src/common/style'), + '@util': path.resolve(__dirname, 'src/common/util'), + '@types': path.resolve(__dirname, 'src/common/types'), + '@store': path.resolve(__dirname, 'src/store'), + '@model': path.resolve(__dirname, 'src/model'), + '@controllers': path.resolve(__dirname, 'src/controllers') } }, entry: { @@ -53,7 +59,7 @@ module.exports = (env) => { ], ['@babel/preset-typescript'] ], - plugins: ['@babel/plugin-proposal-class-properties', '@babel/proposal-object-rest-spread'] + plugins: ['@babel/plugin-transform-runtime','@babel/plugin-proposal-class-properties', '@babel/proposal-object-rest-spread'] } }, { @@ -87,7 +93,7 @@ module.exports = (env) => { }) ], output: { - path: path.join(__dirname, 'dist'), + path: path.join(__dirname, '../backend/public'), publicPath: '/', filename: '[name].js' }