From 7459bcf171cccdd615fe39607300a63378b46f2a Mon Sep 17 00:00:00 2001 From: yoon1017 Date: Sat, 7 Jun 2025 18:50:12 +0900 Subject: [PATCH] =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EC=8B=9C=EA=B0=81?= =?UTF-8?q?=ED=99=94=20=EB=AA=A8=EB=8B=AC=20=EC=B6=94=EA=B0=80,=EC=BD=94?= =?UTF-8?q?=EB=93=9C=20=EC=8B=9C=EA=B0=81=ED=99=94=20=EB=AA=A8=EB=8B=AC=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-lock.json | 441 +++++++ package.json | 1 + src/components/ide/AnimationFactory.js | 288 +++++ src/components/ide/IDE.css | 4 +- src/components/ide/IDE.jsx | 83 +- src/components/ide/VisualizationModal.css | 1078 +++++++++++++++++ src/components/ide/VisualizationModal.jsx | 1057 ++++++++++++++++ .../ide/animations/PlaceholderAnimation.css | 415 +++++++ .../ide/animations/PlaceholderAnimation.jsx | 228 ++++ src/components/ide/utils/AnimationDetector.js | 325 +++++ src/components/ide/utils/AnimationUtils.js | 0 11 files changed, 3890 insertions(+), 30 deletions(-) create mode 100644 src/components/ide/AnimationFactory.js create mode 100644 src/components/ide/VisualizationModal.css create mode 100644 src/components/ide/VisualizationModal.jsx create mode 100644 src/components/ide/animations/PlaceholderAnimation.css create mode 100644 src/components/ide/animations/PlaceholderAnimation.jsx create mode 100644 src/components/ide/utils/AnimationDetector.js create mode 100644 src/components/ide/utils/AnimationUtils.js diff --git a/package-lock.json b/package-lock.json index 55412b1..a6c48e8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,6 +14,7 @@ "@testing-library/react": "^16.3.0", "@testing-library/user-event": "^13.5.0", "axios": "^0.27.2", + "d3": "^7.9.0", "react": "^19.1.0", "react-dom": "^19.1.0", "react-icons": "^5.5.0", @@ -6414,6 +6415,416 @@ "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==", "license": "MIT" }, + "node_modules/d3": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/d3/-/d3-7.9.0.tgz", + "integrity": "sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA==", + "license": "ISC", + "dependencies": { + "d3-array": "3", + "d3-axis": "3", + "d3-brush": "3", + "d3-chord": "3", + "d3-color": "3", + "d3-contour": "4", + "d3-delaunay": "6", + "d3-dispatch": "3", + "d3-drag": "3", + "d3-dsv": "3", + "d3-ease": "3", + "d3-fetch": "3", + "d3-force": "3", + "d3-format": "3", + "d3-geo": "3", + "d3-hierarchy": "3", + "d3-interpolate": "3", + "d3-path": "3", + "d3-polygon": "3", + "d3-quadtree": "3", + "d3-random": "3", + "d3-scale": "4", + "d3-scale-chromatic": "3", + "d3-selection": "3", + "d3-shape": "3", + "d3-time": "3", + "d3-time-format": "4", + "d3-timer": "3", + "d3-transition": "3", + "d3-zoom": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-array": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", + "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", + "license": "ISC", + "dependencies": { + "internmap": "1 - 2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-axis": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-axis/-/d3-axis-3.0.0.tgz", + "integrity": "sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-brush": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-3.0.0.tgz", + "integrity": "sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "3", + "d3-transition": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-chord": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-chord/-/d3-chord-3.0.1.tgz", + "integrity": "sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==", + "license": "ISC", + "dependencies": { + "d3-path": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-contour": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-contour/-/d3-contour-4.0.2.tgz", + "integrity": "sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==", + "license": "ISC", + "dependencies": { + "d3-array": "^3.2.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-delaunay": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-6.0.4.tgz", + "integrity": "sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==", + "license": "ISC", + "dependencies": { + "delaunator": "5" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dispatch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz", + "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-drag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz", + "integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-selection": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dsv": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-3.0.1.tgz", + "integrity": "sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==", + "license": "ISC", + "dependencies": { + "commander": "7", + "iconv-lite": "0.6", + "rw": "1" + }, + "bin": { + "csv2json": "bin/dsv2json.js", + "csv2tsv": "bin/dsv2dsv.js", + "dsv2dsv": "bin/dsv2dsv.js", + "dsv2json": "bin/dsv2json.js", + "json2csv": "bin/json2dsv.js", + "json2dsv": "bin/json2dsv.js", + "json2tsv": "bin/json2dsv.js", + "tsv2csv": "bin/dsv2dsv.js", + "tsv2json": "bin/dsv2json.js" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dsv/node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/d3-ease": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-fetch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-fetch/-/d3-fetch-3.0.1.tgz", + "integrity": "sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==", + "license": "ISC", + "dependencies": { + "d3-dsv": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-force": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-3.0.0.tgz", + "integrity": "sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-quadtree": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-format": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz", + "integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-geo": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-3.1.1.tgz", + "integrity": "sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==", + "license": "ISC", + "dependencies": { + "d3-array": "2.5.0 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-hierarchy": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz", + "integrity": "sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-polygon": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-3.0.1.tgz", + "integrity": "sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-quadtree": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-3.0.1.tgz", + "integrity": "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-random": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-random/-/d3-random-3.0.1.tgz", + "integrity": "sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", + "license": "ISC", + "dependencies": { + "d3-array": "2.10.0 - 3", + "d3-format": "1 - 3", + "d3-interpolate": "1.2.0 - 3", + "d3-time": "2.1.1 - 3", + "d3-time-format": "2 - 4" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale-chromatic": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz", + "integrity": "sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3", + "d3-interpolate": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-selection": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", + "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-shape": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", + "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", + "license": "ISC", + "dependencies": { + "d3-path": "^3.1.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", + "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", + "license": "ISC", + "dependencies": { + "d3-array": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time-format": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", + "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", + "license": "ISC", + "dependencies": { + "d3-time": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-timer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-transition": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz", + "integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3", + "d3-dispatch": "1 - 3", + "d3-ease": "1 - 3", + "d3-interpolate": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "d3-selection": "2 - 3" + } + }, + "node_modules/d3-zoom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz", + "integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "2 - 3", + "d3-transition": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/damerau-levenshtein": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", @@ -6584,6 +6995,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/delaunator": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.0.1.tgz", + "integrity": "sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw==", + "license": "ISC", + "dependencies": { + "robust-predicates": "^3.0.2" + } + }, "node_modules/delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -9318,6 +9738,15 @@ "node": ">= 0.4" } }, + "node_modules/internmap": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", + "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, "node_modules/ipaddr.js": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.2.0.tgz", @@ -14502,6 +14931,12 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/robust-predicates": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.2.tgz", + "integrity": "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==", + "license": "Unlicense" + }, "node_modules/rollup": { "version": "2.79.2", "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.79.2.tgz", @@ -14579,6 +15014,12 @@ "queue-microtask": "^1.2.2" } }, + "node_modules/rw": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", + "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==", + "license": "BSD-3-Clause" + }, "node_modules/safe-array-concat": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz", diff --git a/package.json b/package.json index e696477..4cfad09 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ "@testing-library/react": "^16.3.0", "@testing-library/user-event": "^13.5.0", "axios": "^0.27.2", + "d3": "^7.9.0", "react": "^19.1.0", "react-dom": "^19.1.0", "react-icons": "^5.5.0", diff --git a/src/components/ide/AnimationFactory.js b/src/components/ide/AnimationFactory.js new file mode 100644 index 0000000..84a5920 --- /dev/null +++ b/src/components/ide/AnimationFactory.js @@ -0,0 +1,288 @@ +import React from 'react'; + +// ๐ŸŽจ ์ž„์‹œ ํ”Œ๋ ˆ์ด์Šคํ™€๋” ์ปดํฌ๋„ŒํŠธ (ํŒŒ์ผ์ด ์—†์„ ๊ฒฝ์šฐ ๋Œ€๋น„) +const FallbackPlaceholder = ({ type = 'unknown', data = null, currentStep = 0, totalSteps = 0 }) => ( +
+
๐Ÿšง
+

+ {type} ์• ๋‹ˆ๋ฉ”์ด์…˜ ๊ฐœ๋ฐœ ์ค‘ +

+

+ ๊ณง ์™„์„ฑ๋  ์˜ˆ์ •์ž…๋‹ˆ๋‹ค! +

+
+
+
ํ˜„์žฌ ๋‹จ๊ณ„
+
+ {currentStep + 1} / {totalSteps} +
+
+
+
์• ๋‹ˆ๋ฉ”์ด์…˜
+
{type}
+
+
+
+); + +// PlaceholderAnimation import ์‹œ๋„ (์‹คํŒจ์‹œ FallbackPlaceholder ์‚ฌ์šฉ) +let PlaceholderAnimation; +try { + PlaceholderAnimation = require('./animations/PlaceholderAnimation').default; +} catch (error) { + console.warn('PlaceholderAnimation ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋ถˆ๋Ÿฌ์˜ฌ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. Fallback์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.', error); + PlaceholderAnimation = FallbackPlaceholder; +} + +/** + * ๐Ÿญ AnimationFactory ํด๋ž˜์Šค (์ž„์‹œ ๊ฐœ๋ฐœ ๋ฒ„์ „) + * ์‹ค์ œ ์• ๋‹ˆ๋ฉ”์ด์…˜ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์ค€๋น„๋˜๋ฉด ๊ต์ฒด๋  ์˜ˆ์ • + */ +export class AnimationFactory { + // ๐Ÿ“‹ ์• ๋‹ˆ๋ฉ”์ด์…˜ ํƒ€์ž… โ†’ ์ปดํฌ๋„ŒํŠธ ๋งคํ•‘ ํ…Œ์ด๋ธ” (๋ชจ๋‘ ํ”Œ๋ ˆ์ด์Šคํ™€๋”) + static animations = { + // ๐Ÿ”„ ์žฌ๊ท€ ์• ๋‹ˆ๋ฉ”์ด์…˜๋“ค + 'fibonacci-recursion': PlaceholderAnimation, + 'factorial-recursion': PlaceholderAnimation, + 'hanoi-tower': PlaceholderAnimation, + 'recursion-tree': PlaceholderAnimation, + + // ๐Ÿ”ข ์ •๋ ฌ ์• ๋‹ˆ๋ฉ”์ด์…˜๋“ค + 'bubble-sort': PlaceholderAnimation, + 'quick-sort': PlaceholderAnimation, + 'merge-sort': PlaceholderAnimation, + 'insertion-sort': PlaceholderAnimation, + 'selection-sort': PlaceholderAnimation, + 'heap-sort': PlaceholderAnimation, + + // ๐Ÿ“Š ์ž๋ฃŒ๊ตฌ์กฐ ์• ๋‹ˆ๋ฉ”์ด์…˜๋“ค + 'array': PlaceholderAnimation, + 'array-manipulation': PlaceholderAnimation, + 'linked-list': PlaceholderAnimation, + 'stack': PlaceholderAnimation, + 'queue': PlaceholderAnimation, + 'tree': PlaceholderAnimation, + 'binary-tree': PlaceholderAnimation, + 'bst': PlaceholderAnimation, + + // ๐Ÿ” ํƒ์ƒ‰ ์• ๋‹ˆ๋ฉ”์ด์…˜๋“ค + 'binary-search': PlaceholderAnimation, + 'linear-search': PlaceholderAnimation, + 'breadth-first-search': PlaceholderAnimation, + 'depth-first-search': PlaceholderAnimation, + 'bfs': PlaceholderAnimation, + 'dfs': PlaceholderAnimation, + + // ๐Ÿ“ ๋ณ€์ˆ˜ ์ถ”์  ์• ๋‹ˆ๋ฉ”์ด์…˜๋“ค + 'variables': PlaceholderAnimation, + 'variable-tracking': PlaceholderAnimation, + 'basic-algorithm': PlaceholderAnimation, + + // ๐ŸŽฏ ๊ธฐ๋ณธ ์• ๋‹ˆ๋ฉ”์ด์…˜ (fallback) + 'default': PlaceholderAnimation, + 'unknown': PlaceholderAnimation + }; + + // ๐Ÿ“š ์• ๋‹ˆ๋ฉ”์ด์…˜ ์นดํ…Œ๊ณ ๋ฆฌ๋ณ„ ๋ถ„๋ฅ˜ + static categories = { + recursion: [ + 'fibonacci-recursion', 'factorial-recursion', + 'hanoi-tower', 'recursion-tree' + ], + sorting: [ + 'bubble-sort', 'quick-sort', 'merge-sort', + 'insertion-sort', 'selection-sort', 'heap-sort' + ], + dataStructures: [ + 'array', 'array-manipulation', 'linked-list', + 'stack', 'queue', 'tree', 'binary-tree', 'bst' + ], + searching: [ + 'binary-search', 'linear-search', 'breadth-first-search', + 'depth-first-search', 'bfs', 'dfs' + ], + variables: [ + 'variables', 'variable-tracking', 'basic-algorithm' + ] + }; + + /** + * ๐ŸŽจ ์• ๋‹ˆ๋ฉ”์ด์…˜ ์ปดํฌ๋„ŒํŠธ ์ƒ์„ฑ + * @param {string} type - ์• ๋‹ˆ๋ฉ”์ด์…˜ ํƒ€์ž… + * @param {Object} props - ์ปดํฌ๋„ŒํŠธ์— ์ „๋‹ฌํ•  props + * @returns {React.Component} ์ƒ์„ฑ๋œ ์• ๋‹ˆ๋ฉ”์ด์…˜ ์ปดํฌ๋„ŒํŠธ + */ + static createAnimation(type, props = {}) { + console.log(`๐Ÿญ Creating animation: ${type} (๊ฐœ๋ฐœ ์ค‘ ๋ฒ„์ „)`); + console.log('Props:', props); + + // 1. ์• ๋‹ˆ๋ฉ”์ด์…˜ ํƒ€์ž… ์ •๊ทœํ™” + const normalizedType = this.normalizeType(type); + console.log('Normalized type:', normalizedType); + + // 2. ์ปดํฌ๋„ŒํŠธ ์ฐพ๊ธฐ (ํ˜„์žฌ๋Š” ๋ชจ๋‘ ํ”Œ๋ ˆ์ด์Šคํ™€๋”) + const AnimationComponent = this.animations[normalizedType] || PlaceholderAnimation; + console.log('Selected component:', AnimationComponent); + + // 3. ์ปดํฌ๋„ŒํŠธ ์ƒ์„ฑ ๋ฐ ๋ฐ˜ํ™˜ + const element = React.createElement(AnimationComponent, { + key: `animation-${normalizedType}-${Date.now()}`, + type: normalizedType, + animationType: normalizedType, + ...props + }); + + console.log('Created element:', element); + return element; + } + + /** + * ๐Ÿ”ง ์• ๋‹ˆ๋ฉ”์ด์…˜ ํƒ€์ž… ์ •๊ทœํ™” + * @param {string} type - ์›๋ณธ ํƒ€์ž… + * @returns {string} ์ •๊ทœํ™”๋œ ํƒ€์ž… + */ + static normalizeType(type) { + if (!type) return 'unknown'; + + // ์†Œ๋ฌธ์ž๋กœ ๋ณ€ํ™˜ํ•˜๊ณ  ๊ณต๋ฐฑ/์–ธ๋”์Šค์ฝ”์–ด๋ฅผ ํ•˜์ดํ”ˆ์œผ๋กœ ๋ณ€ํ™˜ + return type.toLowerCase() + .replace(/[\s_]+/g, '-') + .replace(/[^a-z0-9-]/g, ''); + } + + /** + * ๐Ÿ“‹ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ์• ๋‹ˆ๋ฉ”์ด์…˜ ํƒ€์ž… ๋ชฉ๋ก + * @returns {Array} ์• ๋‹ˆ๋ฉ”์ด์…˜ ํƒ€์ž… ๋ฐฐ์—ด + */ + static getAvailableTypes() { + return Object.keys(this.animations).filter(type => + type !== 'unknown' && type !== 'default' + ); + } + + /** + * ๐Ÿท๏ธ ์• ๋‹ˆ๋ฉ”์ด์…˜ ํƒ€์ž…์ด ์œ ํšจํ•œ์ง€ ํ™•์ธ + * @param {string} type - ํ™•์ธํ•  ํƒ€์ž… + * @returns {boolean} ์œ ํšจ์„ฑ ์—ฌ๋ถ€ + */ + static isValidType(type) { + const normalizedType = this.normalizeType(type); + return normalizedType in this.animations; + } + + /** + * ๐Ÿ“‚ ์นดํ…Œ๊ณ ๋ฆฌ๋ณ„ ์• ๋‹ˆ๋ฉ”์ด์…˜ ํƒ€์ž… ์กฐํšŒ + * @param {string} category - ์นดํ…Œ๊ณ ๋ฆฌ๋ช… + * @returns {Array} ํ•ด๋‹น ์นดํ…Œ๊ณ ๋ฆฌ์˜ ์• ๋‹ˆ๋ฉ”์ด์…˜ ํƒ€์ž…๋“ค + */ + static getTypesByCategory(category) { + return this.categories[category] || []; + } + + /** + * ๐Ÿ” ์• ๋‹ˆ๋ฉ”์ด์…˜ ํƒ€์ž…์˜ ์นดํ…Œ๊ณ ๋ฆฌ ์ฐพ๊ธฐ + * @param {string} type - ์• ๋‹ˆ๋ฉ”์ด์…˜ ํƒ€์ž… + * @returns {string|null} ์นดํ…Œ๊ณ ๋ฆฌ๋ช… + */ + static getCategoryByType(type) { + const normalizedType = this.normalizeType(type); + + for (const [category, types] of Object.entries(this.categories)) { + if (types.includes(normalizedType)) { + return category; + } + } + return null; + } + + /** + * ๐ŸŽฏ ์ƒˆ๋กœ์šด ์• ๋‹ˆ๋ฉ”์ด์…˜ ํƒ€์ž… ๋“ฑ๋ก (์‹ค์ œ ์ปดํฌ๋„ŒํŠธ ์™„์„ฑ ํ›„ ์‚ฌ์šฉ) + * @param {string} type - ์• ๋‹ˆ๋ฉ”์ด์…˜ ํƒ€์ž… + * @param {React.Component} component - ์ปดํฌ๋„ŒํŠธ + * @param {string} category - ์นดํ…Œ๊ณ ๋ฆฌ (์„ ํƒ์‚ฌํ•ญ) + */ + static registerAnimation(type, component, category = null) { + const normalizedType = this.normalizeType(type); + + // ์• ๋‹ˆ๋ฉ”์ด์…˜ ๋“ฑ๋ก + this.animations[normalizedType] = component; + + // ์นดํ…Œ๊ณ ๋ฆฌ์— ์ถ”๊ฐ€ (์ง€์ •๋œ ๊ฒฝ์šฐ) + if (category && this.categories[category]) { + if (!this.categories[category].includes(normalizedType)) { + this.categories[category].push(normalizedType); + } + } + + console.log(`โœ… Registered animation: ${normalizedType}`); + } + + /** + * ๐Ÿ“Š ํŒฉํ† ๋ฆฌ ์ƒํƒœ ์ •๋ณด + * @returns {Object} ํŒฉํ† ๋ฆฌ ์ƒํƒœ + */ + static getFactoryInfo() { + return { + version: 'development', + mode: 'placeholder-only', + totalAnimations: Object.keys(this.animations).length, + categories: Object.keys(this.categories), + availableTypes: this.getAvailableTypes(), + categoriesInfo: Object.entries(this.categories).map(([name, types]) => ({ + name, + count: types.length, + types + })), + note: '๋ชจ๋“  ์• ๋‹ˆ๋ฉ”์ด์…˜์ด ๊ฐœ๋ฐœ ์ค‘์ž…๋‹ˆ๋‹ค. ์‹ค์ œ ์ปดํฌ๋„ŒํŠธ๋Š” ๊ณง ์ถ”๊ฐ€๋  ์˜ˆ์ •์ž…๋‹ˆ๋‹ค.' + }; + } +} + +// ๐ŸŽจ ํŽธ์˜ ํ•จ์ˆ˜๋“ค +export const createAnimation = (type, props) => + AnimationFactory.createAnimation(type, props); + +export const isValidAnimationType = (type) => + AnimationFactory.isValidType(type); + +export const getAnimationTypes = () => + AnimationFactory.getAvailableTypes(); + +export const registerAnimation = (type, component, category) => + AnimationFactory.registerAnimation(type, component, category); + +// ๊ธฐ๋ณธ export +export default AnimationFactory; + +// ๊ฐœ๋ฐœ ์ƒํƒœ ๋กœ๊ทธ +console.log('๐Ÿญ AnimationFactory (๊ฐœ๋ฐœ ์ค‘) ๋กœ๋“œ๋จ:', AnimationFactory.getFactoryInfo()); \ No newline at end of file diff --git a/src/components/ide/IDE.css b/src/components/ide/IDE.css index 58e0a6b..ec4c905 100644 --- a/src/components/ide/IDE.css +++ b/src/components/ide/IDE.css @@ -726,8 +726,8 @@ body.dark-mode .visualization-button.active { position: absolute; top: 0; right: 0; - bottom: 0; - width: 65%; /* ํ™”๋ฉด์˜ 65% ์ฐจ์ง€ */ + bottom: 20px; + width: 90%; /* ํ™”๋ฉด์˜ 65% ์ฐจ์ง€ */ background-color: var(--element); border-left: 1px solid var(--border); display: flex; diff --git a/src/components/ide/IDE.jsx b/src/components/ide/IDE.jsx index b49a651..e45d5d1 100644 --- a/src/components/ide/IDE.jsx +++ b/src/components/ide/IDE.jsx @@ -1,6 +1,8 @@ import React, { useState, useEffect, useRef } from 'react'; import { Link, useNavigate, useLocation, useParams } from 'react-router-dom'; import Editor from '@monaco-editor/react'; +import CodeVisualizer from './VisualizationModal'; +import VisualizationModal from './VisualizationModal'; // ์ƒˆ๋กœ ์ถ”๊ฐ€ import './IDE.css'; //npm install @monaco-editor/react @@ -77,6 +79,20 @@ const applyResizeObserverFix = () => { const IDE = () => { // ์ปดํฌ๋„ŒํŠธ ๋งˆ์šดํŠธ ์‹œ ResizeObserver ํŒจ์น˜ ์ ์šฉ + + const [isVisualizationModalOpen, setIsVisualizationModalOpen] = useState(false); + const handleVisualizationClick = () => { + if (!code.trim()) { + alert('์‹œ๊ฐํ™”ํ•  ์ฝ”๋“œ๋ฅผ ๋จผ์ € ์ž‘์„ฑํ•ด์ฃผ์„ธ์š”.'); + return; + } + setIsVisualizationModalOpen(true); + }; + const handleVisualizationClose = () => { + setIsVisualizationModalOpen(false); + }; + + useEffect(() => { applyResizeObserverFix(); @@ -621,7 +637,7 @@ const IDE = () => { // ํ˜„์žฌ ์—๋””ํ„ฐ์˜ ๊ฐ’์„ ๊ฐ€์ ธ์˜ด const currentCode = editorRef.current.getValue(); - // API ์š”์ฒญ ๋ณธ๋ฌธ ์ƒ์„ฑ (์„ฑ๊ณตํ•œ ํ˜•์‹๊ณผ ๋™์ผํ•˜๊ฒŒ) + // API ์š”์ฒญ ๋ณธ๋ฌธ ์ƒ์„ฑ const requestBody = { code: currentCode, input: input, @@ -648,9 +664,32 @@ const IDE = () => { throw new Error(`API ์š”์ฒญ ์‹คํŒจ: ${response.status} ${response.statusText}`); } - // ์‘๋‹ต์ด ํ…์ŠคํŠธ ํ˜•์‹์ผ ๊ฒƒ์œผ๋กœ ์˜ˆ์ƒ - const result = await response.text(); - setOutput(result || "์‹คํ–‰ ๊ฒฐ๊ณผ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค."); + // ๐Ÿ”ฅ ์ˆ˜์ •๋œ ๋ถ€๋ถ„: JSON ์‘๋‹ต ํŒŒ์‹ฑ ํ›„ stdout ์ถ”์ถœ + const result = await response.json(); // text() ๋Œ€์‹  json() ์‚ฌ์šฉ + + console.log('API ์‘๋‹ต ๋ฐ์ดํ„ฐ:', result); // ๋””๋ฒ„๊น…์šฉ ๋กœ๊ทธ + + // stdout ๊ฐ’๋งŒ ์ถ”์ถœํ•ด์„œ ์ถœ๋ ฅ + if (result && typeof result === 'object') { + // stdout, Stdout, STDOUT ๋“ฑ ๋‹ค์–‘ํ•œ ์ผ€์ด์Šค ๋Œ€์‘ + const stdout = result.stdout || result.Stdout || result.STDOUT || + result.output || result.Output || result.OUTPUT; + + if (stdout !== undefined) { + setOutput(stdout || "์‹คํ–‰ ์™„๋ฃŒ (์ถœ๋ ฅ ์—†์Œ)"); + } else { + // stdout์ด ์—†๋Š” ๊ฒฝ์šฐ ์ „์ฒด ์‘๋‹ต์„ ๋ณด์—ฌ์ฃผ๋˜, ์—๋Ÿฌ ์ •๋ณด ์šฐ์„  + const errorMsg = result.stderr || result.error || result.message; + if (errorMsg) { + setOutput(`์˜ค๋ฅ˜: ${errorMsg}`); + } else { + setOutput("์‹คํ–‰ ์™„๋ฃŒ๋˜์—ˆ์ง€๋งŒ ์ถœ๋ ฅ์ด ์—†์Šต๋‹ˆ๋‹ค."); + } + } + } else { + // ์‘๋‹ต์ด ๊ฐ์ฒด๊ฐ€ ์•„๋‹Œ ๊ฒฝ์šฐ (๋ฌธ์ž์—ด ๋“ฑ) + setOutput(result || "์‹คํ–‰ ๊ฒฐ๊ณผ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค."); + } } catch (error) { console.error('์ฝ”๋“œ ์‹คํ–‰ ์ค‘ ์˜ค๋ฅ˜:', error); @@ -953,8 +992,9 @@ const IDE = () => { ์‹คํ–‰ ์ฝ”๋“œ - -
-
-

์ฝ”๋“œ ์‹คํ–‰ ๊ฒฐ๊ณผ์˜ ์‹œ๊ฐํ™”๊ฐ€ ์ด๊ณณ์— ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค.

-

ํ˜„์žฌ ๊ฐœ๋ฐœ ์ค‘์ธ ๊ธฐ๋Šฅ์ž…๋‹ˆ๋‹ค.

-
-
- - )} + {/* ๐ŸŽฌ ์‹œ๊ฐํ™” ๋ชจ๋‹ฌ */} + + {/* ํ† ์ŠคํŠธ ๋ฉ”์‹œ์ง€ ์ปจํ…Œ์ด๋„ˆ */}
diff --git a/src/components/ide/VisualizationModal.css b/src/components/ide/VisualizationModal.css new file mode 100644 index 0000000..5424961 --- /dev/null +++ b/src/components/ide/VisualizationModal.css @@ -0,0 +1,1078 @@ +/* VisualizationModal.css - ์™„์ „ํ•œ 2์—ด ๋ ˆ์ด์•„์›ƒ ๋ฒ„์ „ */ + +:root { + --primary: #8b5cf6; + --bg: #f1f5f9; + --card: #ffffff; + --border: #e2e8f0; + --text: #1e293b; + --text-light: #64748b; + --success: #10b981; + --warning: #f59e0b; + --danger: #ef4444; +} + +body.dark-mode { + --primary: #a78bfa; + --bg: #0f172a; + --card: #1e293b; + --border: #475569; + --text: #f1f5f9; + --text-light: #94a3b8; +} + +/* ๐ŸŒ ๋ชจ๋‹ฌ ์˜ค๋ฒ„๋ ˆ์ด */ +.visualization-modal-overlay { + position: fixed; + inset: 0; + background: rgba(0, 0, 0, 0.7); + backdrop-filter: blur(4px); + display: flex; + align-items: center; + justify-content: center; + z-index: 1000; + padding: 20px; + animation: fadeIn 0.3s ease; +} + +@keyframes fadeIn { + from { opacity: 0; } + to { opacity: 1; } +} + +/* ๐Ÿ“ฆ ๋ชจ๋‹ฌ ์ปจํ…Œ์ด๋„ˆ */ +.visualization-modal-container { + background: var(--card); + border-radius: 16px; + box-shadow: 0 20px 40px rgba(0, 0, 0, 0.15); + width: 95vw; + height: 92vh; + max-width: 1600px; + max-height: 1000px; + min-height: 700px; + display: flex; + flex-direction: column; + overflow: hidden; + border: 1px solid var(--border); + animation: slideIn 0.4s ease; +} + +@keyframes slideIn { + from { transform: scale(0.9) translateY(20px); opacity: 0; } + to { transform: scale(1) translateY(0); opacity: 1; } +} + +/* ๐ŸŽฏ ๋ชจ๋‹ฌ ํ—ค๋” */ +.modal-header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 20px 24px; + background: linear-gradient(135deg, var(--primary), #7c3aed); + color: white; + border-radius: 16px 16px 0 0; + flex-shrink: 0; +} + +.modal-title { + display: flex; + align-items: center; + gap: 12px; +} + +.modal-icon { + font-size: 24px; +} + +.modal-title h2 { + margin: 0; + font-size: 20px; + font-weight: 600; +} + +.language-badge { + background: rgba(255, 255, 255, 0.2); + padding: 4px 12px; + border-radius: 12px; + font-size: 12px; + font-weight: 500; +} + +.modal-header-controls { + display: flex; + align-items: center; + gap: 16px; +} + +/* ๐ŸŽฎ ์• ๋‹ˆ๋ฉ”์ด์…˜ ์ปจํŠธ๋กค */ +.visualization-controls { + display: flex; + align-items: center; + gap: 12px; +} + +.control-btn { + display: flex; + align-items: center; + gap: 6px; + padding: 10px 16px; + border: none; + border-radius: 8px; + font-size: 12px; + font-weight: 500; + cursor: pointer; + background: rgba(255, 255, 255, 0.9); + color: #374151; + min-width: 70px; + height: 36px; + justify-content: center; + transition: all 0.2s; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); +} + +.control-btn:hover:not(:disabled) { + background: white; + transform: translateY(-1px); + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15); +} + +.control-btn:disabled { + opacity: 0.5; + cursor: not-allowed; +} + +.play-pause-btn { + background: var(--success); + color: white; + min-width: 80px; +} + +.play-pause-btn.playing { + background: var(--warning); +} + +.step-display { + display: flex; + align-items: center; + gap: 6px; + background: rgba(255, 255, 255, 0.9); + padding: 8px 12px; + border-radius: 8px; + font-size: 12px; + font-weight: 600; + color: #374151; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + min-width: 80px; + height: 36px; +} + +.step-input { + width: 50px; + border: none; + background: transparent; + text-align: center; + font-size: 12px; + font-weight: 600; + color: var(--primary); + outline: none; +} + +.step-total { + font-size: 12px; + color: #6b7280; +} + +.speed-controls { + display: flex; + align-items: center; + gap: 6px; + background: rgba(255, 255, 255, 0.9); + padding: 6px 10px; + border-radius: 8px; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + height: 36px; +} + +.speed-label { + font-size: 11px; + font-weight: 500; + color: #6b7280; + margin-right: 4px; +} + +.speed-btn { + padding: 4px 8px; + border: none; + border-radius: 6px; + font-size: 10px; + cursor: pointer; + background: transparent; + color: #6b7280; + min-width: 32px; + height: 24px; + transition: all 0.2s; +} + +.speed-btn:hover { + background: rgba(139, 92, 246, 0.1); + color: var(--primary); +} + +.speed-btn.active { + background: var(--primary); + color: white; +} + +.close-modal-btn { + background: rgba(255, 255, 255, 0.2); + border: none; + border-radius: 8px; + width: 36px; + height: 36px; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + color: white; + transition: background 0.2s; + font-size: 16px; +} + +.close-modal-btn:hover { + background: rgba(255, 255, 255, 0.3); +} + +.regenerate-btn-header { + background: rgba(255, 255, 255, 0.2); + border: none; + border-radius: 8px; + width: 36px; + height: 36px; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + color: white; + transition: background 0.2s; + font-size: 16px; + margin-left: 8px; +} + +.regenerate-btn-header:hover:not(:disabled) { + background: rgba(255, 255, 255, 0.3); +} + +.regenerate-btn-header:disabled { + opacity: 0.5; + cursor: not-allowed; +} + +/* ๐Ÿ“‹ 2์—ด ๋ ˆ์ด์•„์›ƒ: ์™ผ์ชฝ ์ •๋ณดํŒจ๋„ + ์˜ค๋ฅธ์ชฝ ์• ๋‹ˆ๋ฉ”์ด์…˜ */ +.modal-content { + flex: 1; + background: var(--bg); + overflow: hidden; + display: flex; + flex-direction: row; /* ๋ช…์‹œ์ ์œผ๋กœ ๊ฐ€๋กœ ๋ฐฐ์น˜ */ + width: 100%; + height: 100%; +} + +/* ๐Ÿ“‹ ์™ผ์ชฝ: ์ •๋ณด ํŒจ๋„ (ํŒŒ๋ž€์ƒ‰ ์˜์—ญ, ์Šคํฌ๋กค ๊ฐ€๋Šฅ) - ๋” ์ž‘๊ฒŒ */ +.info-panel-sidebar { + width: 250px; /* 350px โ†’ 250px๋กœ ์ถ•์†Œ */ + min-width: 200px; /* 300px โ†’ 200px๋กœ ์ถ•์†Œ */ + max-width: 280px; /* 400px โ†’ 280px๋กœ ์ถ•์†Œ */ + background: var(--card); + border-right: 1px solid var(--border); + display: flex; + flex-direction: column; + gap: 12px; /* 16px โ†’ 12px๋กœ ์ถ•์†Œ */ + padding: 16px; /* 20px โ†’ 16px๋กœ ์ถ•์†Œ */ + overflow-y: auto; + height: 100%; + box-shadow: 2px 0 8px rgba(0, 0, 0, 0.05); + flex-shrink: 0; /* ์ถ•์†Œ๋˜์ง€ ์•Š๋„๋ก ๊ณ ์ • */ +} + +/* ๐ŸŽฌ ์˜ค๋ฅธ์ชฝ: ๋ฉ”์ธ ์• ๋‹ˆ๋ฉ”์ด์…˜ ์˜์—ญ (๋นจ๊ฐ„์ƒ‰ ์˜์—ญ) */ +.main-animation-area { + flex: 1; /* ๋‚จ์€ ๊ณต๊ฐ„ ๋ชจ๋‘ ์ฐจ์ง€ */ + min-width: 400px; /* ์ตœ์†Œ ๋„ˆ๋น„ ๋ณด์žฅ */ + background: var(--card); + padding: 20px; + overflow: auto; + display: flex; + flex-direction: column; + min-height: 100%; + border: 2px solid #ef4444; /* ๋””๋ฒ„๊น…์šฉ ๋นจ๊ฐ„ ํ…Œ๋‘๋ฆฌ */ +} + +/* ์• ๋‹ˆ๋ฉ”์ด์…˜ ๋ž˜ํผ - ํ”Œ๋ ˆ์ด์Šคํ™€๋”๊ฐ€ ์ „์ฒด ์˜์—ญ์„ ์ฑ„์šฐ๋„๋ก */ +.animation-wrapper { + flex: 1; + display: flex; + flex-direction: column; + min-height: 400px; /* ์ตœ์†Œ ๋†’์ด ๋ณด์žฅ */ + width: 100%; + border: 2px solid #10b981; /* ๋””๋ฒ„๊น…์šฉ ์ดˆ๋ก ํ…Œ๋‘๋ฆฌ ์ถ”๊ฐ€ */ +} + +/* ๋กœ๋”ฉ/์—๋Ÿฌ ์ƒํƒœ๊ฐ€ ๋ฉ”์ธ ์˜์—ญ์— ํ‘œ์‹œ๋˜๋„๋ก ์กฐ์ • */ +.main-animation-area .loading-container, +.main-animation-area .error-container, +.main-animation-area .initial-container { + flex: 1; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 16px; + padding: 40px; + text-align: center; + min-height: 400px; +} + +.loading-spinner { + width: 40px; + height: 40px; + border: 3px solid var(--border); + border-top: 3px solid var(--primary); + border-radius: 50%; + animation: spin 1s linear infinite; +} + +@keyframes spin { + 0% { transform: rotate(0deg); } + 100% { transform: rotate(360deg); } +} + +.loading-container h3, +.error-container h3, +.initial-container h3 { + margin: 0; + font-size: 18px; + color: var(--text); + font-weight: 600; +} + +.loading-container p, +.error-container p, +.initial-container p { + margin: 0; + font-size: 14px; + color: var(--text-light); +} + +.loading-info { + display: flex; + gap: 16px; + margin-top: 8px; +} + +.loading-info span { + font-size: 12px; + padding: 4px 8px; + background: var(--card); + border-radius: 6px; + color: var(--text-light); + border: 1px solid var(--border); +} + +.error-icon, +.initial-icon { + font-size: 48px; + margin-bottom: 16px; +} + +.retry-btn { + background: var(--primary); + color: white; + border: none; + border-radius: 8px; + padding: 12px 24px; + cursor: pointer; + transition: all 0.2s; + font-weight: 500; + margin-top: 12px; +} + +.retry-btn:hover { + background: #7c3aed; + transform: translateY(-1px); + box-shadow: 0 4px 12px rgba(139, 92, 246, 0.3); +} + +/* ํ”Œ๋ ˆ์ด์Šคํ™€๋” ์• ๋‹ˆ๋ฉ”์ด์…˜์ด ๋ฉ”์ธ ์˜์—ญ ์ „์ฒด๋ฅผ ์ฑ„์šฐ๋„๋ก */ +.placeholder-animation { + width: 100%; + flex: 1; + display: flex; + flex-direction: column; + min-height: 400px; /* ์ตœ์†Œ ๋†’์ด ๋ณด์žฅ */ + background: var(--card); + border-radius: 12px; /* ๋ชจ์„œ๋ฆฌ ๋‘ฅ๊ธ€๊ธฐ ๋ณต์› */ + border: 1px solid var(--border); /* ํ…Œ๋‘๋ฆฌ ๋ณต์› */ + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05); /* ๊ทธ๋ฆผ์ž ๋ณต์› */ + overflow: hidden; +} + +.placeholder-header { + padding: 20px 24px 16px 24px; + background: linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%); + border-bottom: 1px solid var(--border); + flex-shrink: 0; +} + +.placeholder-header h3 { + margin: 0 0 8px 0; + color: var(--text); + font-size: 18px; + font-weight: 600; + display: flex; + align-items: center; + gap: 8px; +} + +.placeholder-header p { + margin: 0; + color: var(--text-light); + font-size: 14px; +} + +.placeholder-content { + flex: 1; + display: flex; + align-items: flex-start; + justify-content: center; + padding: 32px 20px; + overflow-y: auto; +} + +.placeholder-box { + text-align: center; + max-width: 600px; + width: 100%; + padding: 32px; + border: 2px dashed var(--border); + border-radius: 16px; + background: linear-gradient(135deg, #fafbfc 0%, #f8fafc 100%); +} + +/* ๐ŸŽจ ๋ฉ”์ธ ์•„์ด์ฝ˜ */ +.placeholder-icon { + font-size: 64px; + margin-bottom: 20px; + animation: float 4s ease-in-out infinite; + display: block; +} + +@keyframes float { + 0%, 100% { + transform: translateY(0px) scale(1); + } + 50% { + transform: translateY(-12px) scale(1.05); + } +} + +.placeholder-box h4 { + margin: 0 0 16px 0; + color: var(--text); + font-size: 24px; + font-weight: 700; + letter-spacing: -0.5px; +} + +.placeholder-box > p { + margin: 0 0 32px 0; + color: var(--text-light); + font-size: 16px; + line-height: 1.6; +} + +/* ๐Ÿ“Š ์ƒํƒœ ์ •๋ณด ๊ทธ๋ฆฌ๋“œ */ +.placeholder-stats { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); + gap: 16px; + margin: 32px 0; + text-align: left; +} + +.stat-item { + background: var(--card); + padding: 16px; + border-radius: 12px; + border: 1px solid var(--border); + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05); + transition: all 0.2s ease; +} + +.stat-item:hover { + transform: translateY(-2px); + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); + border-color: var(--primary); +} + +.stat-label { + display: block; + font-size: 12px; + color: var(--text-light); + margin-bottom: 8px; + font-weight: 500; + text-transform: uppercase; + letter-spacing: 0.5px; +} + +.stat-value { + display: block; + font-size: 16px; + color: var(--primary); + font-weight: 700; + line-height: 1.2; +} + +/* ๐ŸŽฏ ์˜ˆ์ƒ ๊ธฐ๋Šฅ ์„น์…˜ */ +.coming-soon { + margin: 32px 0; + padding: 24px; + background: linear-gradient(135deg, #fef7ff 0%, #faf5ff 100%); + border-radius: 12px; + border-left: 4px solid var(--primary); + text-align: left; + box-shadow: 0 2px 8px rgba(139, 92, 246, 0.1); +} + +.coming-soon p { + margin: 0 0 16px 0; + font-size: 14px; + font-weight: 600; + color: var(--text); + display: flex; + align-items: center; + gap: 8px; +} + +.coming-soon ul { + margin: 0; + padding-left: 20px; + list-style-type: none; +} + +.coming-soon li { + font-size: 13px; + color: var(--text-light); + margin-bottom: 8px; + line-height: 1.5; + position: relative; + padding-left: 24px; +} + +.coming-soon li::before { + content: "โœจ"; + position: absolute; + left: 0; + top: 0; + font-size: 12px; +} + +/* ๐Ÿ“ˆ ๊ฐœ๋ฐœ ์ง„ํ–‰๋„ */ +.development-progress { + margin: 32px 0; + padding: 24px; + background: linear-gradient(135deg, #f0f9ff 0%, #e0f2fe 100%); + border-radius: 12px; + border-left: 4px solid #0ea5e9; + text-align: left; +} + +.development-progress h5 { + margin: 0 0 16px 0; + font-size: 14px; + font-weight: 600; + color: var(--text); + display: flex; + align-items: center; + gap: 8px; +} + +.progress-bar { + width: 100%; + height: 8px; + background: #e2e8f0; + border-radius: 4px; + overflow: hidden; + margin-bottom: 8px; +} + +.progress-fill { + height: 100%; + background: linear-gradient(90deg, #0ea5e9, #06b6d4); + border-radius: 4px; + transition: width 0.5s ease; + animation: progress-pulse 2s ease-in-out infinite; +} + +@keyframes progress-pulse { + 0%, 100% { opacity: 1; } + 50% { opacity: 0.8; } +} + +.progress-text { + display: flex; + justify-content: space-between; + align-items: center; + font-size: 12px; + color: var(--text-light); + margin-bottom: 16px; +} + +.progress-steps { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); + gap: 12px; +} + +.step { + padding: 8px 12px; + border-radius: 8px; + font-size: 11px; + font-weight: 500; + text-align: center; + transition: all 0.2s ease; +} + +.step.completed { + background: #dcfce7; + color: #166534; + border: 1px solid #bbf7d0; +} + +.step.current { + background: #fef3c7; + color: #92400e; + border: 1px solid #fde68a; + animation: pulse 2s ease-in-out infinite; +} + +.step.pending { + background: #f1f5f9; + color: #64748b; + border: 1px solid #e2e8f0; +} + +@keyframes pulse { + 0%, 100% { transform: scale(1); } + 50% { transform: scale(1.02); } +} + +/* ๐Ÿ“„ ๋ฐ์ดํ„ฐ ๋ฏธ๋ฆฌ๋ณด๊ธฐ */ +.data-preview { + text-align: left; + background: var(--card); + border: 1px solid var(--border); + border-radius: 12px; + margin: 32px 0; + overflow: hidden; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05); +} + +.data-preview details { + padding: 20px; +} + +.data-preview summary { + cursor: pointer; + color: var(--text); + font-size: 14px; + font-weight: 600; + margin-bottom: 12px; + padding: 4px 0; + border-radius: 6px; + transition: all 0.2s ease; + display: flex; + align-items: center; + gap: 8px; +} + +.data-preview summary:hover { + color: var(--primary); +} + +.data-preview summary::marker { + content: ""; +} + +.data-preview summary::before { + content: "โ–ถ"; + transition: transform 0.2s ease; + font-size: 12px; +} + +.data-preview details[open] summary::before { + transform: rotate(90deg); +} + +.json-preview { + margin: 12px 0 0 0; + font-family: 'SF Mono', 'Monaco', 'Cascadia Code', 'Roboto Mono', monospace; + font-size: 12px; + color: var(--text-light); + white-space: pre-wrap; + line-height: 1.5; + max-height: 300px; + overflow-y: auto; + background: #f8fafc; + padding: 16px; + border-radius: 8px; + border: 1px solid #e2e8f0; +} + +/* ๐Ÿ’ก ๋„์›€๋ง ์„น์…˜ */ +.help-section { + margin: 32px 0 0 0; + padding: 20px; + background: linear-gradient(135deg, #fffbeb 0%, #fef3c7 100%); + border-radius: 12px; + border-left: 4px solid #f59e0b; + text-align: left; +} + +.help-section p { + margin: 0 0 8px 0; + font-size: 13px; + color: var(--text-light); + line-height: 1.5; +} + +.help-section p:first-child { + font-weight: 600; + color: var(--text); +} + +/* ๐Ÿ“Š ์ •๋ณด ํŒจ๋„ ์Šคํƒ€์ผ - ๋” ์ปดํŒฉํŠธํ•˜๊ฒŒ */ +.step-info, +.algorithm-info { + background: var(--card); + border-radius: 8px; /* 12px โ†’ 8px */ + border: 1px solid var(--border); + padding: 12px; /* 20px โ†’ 12px */ + box-shadow: 0 1px 4px rgba(0, 0, 0, 0.05); + transition: transform 0.2s; +} + +.step-info:hover, +.algorithm-info:hover { + transform: translateY(-1px); /* -2px โ†’ -1px */ + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); +} + +.step-info h4, +.algorithm-info h4 { + font-size: 14px; /* 17px โ†’ 14px */ + margin: 0 0 10px 0; /* 16px โ†’ 10px */ + color: var(--text); + font-weight: 600; + display: flex; + align-items: center; + gap: 6px; /* 10px โ†’ 6px */ +} + +.step-info h4::before { content: "๐ŸŽฏ"; } +.algorithm-info h4::before { content: "๐Ÿ“Š"; } + +.step-details { + display: flex; + flex-direction: column; + gap: 12px; +} + +.step-text { + display: flex; + align-items: baseline; + justify-content: flex-start; + margin-bottom: 12px; + font-weight: 600; + gap: 8px; +} + +.step-label { + font-size: 16px; + color: var(--text); + font-weight: 600; +} + +.step-current { + font-size: 20px; + font-weight: 700; + color: var(--primary); + line-height: 1; +} + +.step-separator { + font-size: 16px; + color: var(--text-light); + margin: 0 2px; + font-weight: 400; +} + +.step-total-text { + font-size: 16px; + color: var(--text-light); + font-weight: 600; + line-height: 1; +} + +.step-description { + font-size: 15px; + color: var(--text-light); + padding: 10px 14px; + background: var(--bg); + border-radius: 8px; + border-left: 4px solid var(--primary); + line-height: 1.5; +} + +.step-line { + font-size: 13px; + color: var(--primary); + padding: 8px 12px; + background: rgba(139, 92, 246, 0.1); + border-radius: 8px; + margin-top: 8px; + font-weight: 500; +} + +.step-condition { + font-size: 13px; + padding: 8px 12px; + background: var(--bg); + border-radius: 8px; + margin-top: 8px; + border-left: 4px solid var(--warning); + line-height: 1.4; +} + +.condition-result { + font-weight: 600; + padding: 2px 6px; + border-radius: 4px; + font-size: 10px; +} + +.condition-result.true { + background: var(--success); + color: white; +} + +.condition-result.false { + background: var(--danger); + color: white; +} + +.algorithm-details { + display: flex; + flex-direction: column; + gap: 8px; +} + +.algorithm-details span { + font-size: 13px; + padding: 8px 12px; + background: var(--bg); + border-radius: 8px; + color: var(--text-light); + font-weight: 500; +} + +.variables-section, +.changes-section, +.loop-section { + margin-top: 12px; + padding-top: 8px; + border-top: 1px solid var(--border); +} + +.variables-section h5, +.changes-section h5, +.loop-section h5 { + margin: 0 0 6px 0; + font-size: 12px; + font-weight: 600; + color: var(--text); +} + +.variables-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(100px, 1fr)); + gap: 6px; +} + +.variable-item { + background: var(--bg); + padding: 6px; + border-radius: 6px; + text-align: center; + border: 1px solid var(--border); +} + +.var-name { + font-weight: 600; + color: var(--primary); + font-size: 12px; + display: block; +} + +.var-type { + font-size: 10px; + color: var(--text-light); + display: block; + margin: 2px 0; +} + +.var-value { + font-weight: 600; + font-size: 12px; + background: rgba(139, 92, 246, 0.1); + padding: 2px 6px; + border-radius: 4px; + margin-top: 2px; + display: inline-block; + color: var(--primary); +} + +.changes-list { + display: flex; + flex-direction: column; + gap: 4px; +} + +.change-item { + background: var(--bg); + padding: 6px 8px; + border-radius: 6px; + border-left: 3px solid var(--warning); + font-size: 12px; +} + +.change-var { + font-weight: 600; + color: var(--primary); +} + +.change-arrow { + color: var(--text-light); +} + +.loop-info { + display: flex; + flex-direction: column; + gap: 4px; +} + +.loop-info span { + background: var(--bg); + padding: 4px 8px; + border-radius: 6px; + font-size: 11px; + border-left: 3px solid var(--primary); + color: var(--text-light); +} + +/* ๋ฐ˜์‘ํ˜• - ๋ชจ๋ฐ”์ผ์—์„œ๋Š” ์„ธ๋กœ ๋ฐฐ์น˜ */ +@media (max-width: 768px) { + .visualization-modal-container { + width: 98vw; + height: 95vh; + } + + .modal-header { + padding: 16px 20px; + } + + .modal-header-controls { + flex-direction: column; + gap: 8px; + } + + .visualization-controls { + gap: 8px; + flex-wrap: wrap; + } + + .control-btn { + min-width: 40px; + height: 32px; + padding: 6px 10px; + } + + .btn-text { + display: none; + } + + .step-display { + min-width: 60px; + height: 32px; + padding: 6px 8px; + } + + .speed-controls { + height: 32px; + padding: 4px 8px; + } + + /* ๋ชจ๋ฐ”์ผ์—์„œ๋Š” ์„ธ๋กœ ๋ฐฐ์น˜ */ + .modal-content { + flex-direction: column; + } + + .info-panel-sidebar { + width: 100%; + max-height: 250px; + border-right: none; + border-bottom: 1px solid var(--border); + padding: 16px; + } + + .main-animation-area { + flex: 1; + min-height: 300px; + padding: 16px; + } + + .step-info, + .algorithm-info { + padding: 16px; + } + + .placeholder-box { + padding: 30px 20px; + } +} + +@media (max-width: 480px) { + .visualization-modal-container { + width: 100vw; + height: 100vh; + border-radius: 0; + } + + .modal-header { + padding: 12px 16px; + border-radius: 0; + } + + .modal-title h2 { + font-size: 18px; + } + + .info-panel-sidebar { + max-height: 200px; + padding: 12px; + } + + .main-animation-area { + padding: 12px; + } +} \ No newline at end of file diff --git a/src/components/ide/VisualizationModal.jsx b/src/components/ide/VisualizationModal.jsx new file mode 100644 index 0000000..6ff0dc2 --- /dev/null +++ b/src/components/ide/VisualizationModal.jsx @@ -0,0 +1,1057 @@ +import React, { useState, useEffect, useCallback } from 'react'; +import ReactDOM from 'react-dom'; + +// ์• ๋‹ˆ๋ฉ”์ด์…˜ ์ปจํŠธ๋กค ํ›… +const useAnimationControls = (totalSteps) => { + const [isPlaying, setIsPlaying] = useState(false); + const [currentStep, setCurrentStep] = useState(0); + const [speed, setSpeed] = useState(1); + + const play = useCallback(() => setIsPlaying(true), []); + const pause = useCallback(() => setIsPlaying(false), []); + const stepBack = useCallback(() => setCurrentStep(prev => Math.max(0, prev - 1)), []); + const stepForward = useCallback(() => setCurrentStep(prev => Math.min(totalSteps - 1, prev + 1)), [totalSteps]); + const reset = useCallback(() => { + setCurrentStep(0); + setIsPlaying(false); + }, []); + const goToStep = useCallback((step) => setCurrentStep(Math.max(0, Math.min(totalSteps - 1, step))), [totalSteps]); + + return { + isPlaying, + currentStep, + speed, + play, + pause, + stepBack, + stepForward, + reset, + goToStep, + setSpeed + }; +}; + +// ์ปจํŠธ๋กค ๋ฒ„ํŠผ ์ปดํฌ๋„ŒํŠธ +const ControlButton = ({ onClick, disabled, variant = 'default', children, title }) => { + const getButtonStyle = () => { + const baseStyle = { + display: 'flex', + alignItems: 'center', + gap: '6px', + padding: '8px 16px', + border: 'none', + borderRadius: '8px', + fontSize: '12px', + fontWeight: '500', + cursor: disabled ? 'not-allowed' : 'pointer', + transition: 'all 0.2s', + opacity: disabled ? 0.5 : 1, + minWidth: '70px', + height: '36px', + justifyContent: 'center' + }; + + switch (variant) { + case 'play': + return { ...baseStyle, background: '#10b981', color: 'white' }; + case 'playing': + return { ...baseStyle, background: '#f59e0b', color: 'white' }; + default: + return { ...baseStyle, background: 'rgba(255, 255, 255, 0.9)', color: '#374151' }; + } + }; + + return ( + + ); +}; + +// ์‹œ๊ฐํ™” ์ปจํŠธ๋กค ์ปดํฌ๋„ŒํŠธ +const VisualizationControls = ({ + isPlaying, + currentStep, + totalSteps, + speed, + onPlay, + onPause, + onStepBack, + onStepForward, + onReset, + onSpeedChange, + onStepChange + }) => { + return ( +
+ + {isPlaying ? 'โธ' : 'โ–ถ'} {isPlaying ? '์ผ์‹œ' : '์‹œ์ž‘'} + + + + โช ์ด์ „ + + + = totalSteps - 1 || totalSteps === 0} + title="๋‹ค์Œ ๋‹จ๊ณ„" + > + โฉ ๋‹ค์Œ + + + + โฎ ์ฒ˜์Œ + + +
+ { + const value = parseInt(e.target.value); + if (!isNaN(value) && value >= 1 && value <= totalSteps) { + onStepChange(value - 1); + } + }} + min="1" + max={totalSteps} + disabled={totalSteps === 0} + style={{ + width: '50px', + border: 'none', + background: 'transparent', + textAlign: 'center', + fontSize: '12px', + fontWeight: '600', + outline: 'none' + }} + /> + / {totalSteps} +
+ +
+ ์†๋„: + {[0.5, 1, 1.5, 2].map(speedValue => ( + + ))} +
+
+ ); +}; + +// ๋กœ๋”ฉ ์ปดํฌ๋„ŒํŠธ +const LoadingAnimation = ({ message = "์‹œ๊ฐํ™” ์ƒ์„ฑ ์ค‘...", code, language }) => ( +
+
+

{message}

+

์ฝ”๋“œ๋ฅผ ๋ถ„์„ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค

+
+ + ์–ธ์–ด: {language} + + + ์ฝ”๋“œ ๊ธธ์ด: {code?.length || 0}์ž + +
+ +
+); + +// ์—๋Ÿฌ ์ปดํฌ๋„ŒํŠธ +const ErrorDisplay = ({ error, onRetry }) => ( +
+
โŒ
+

์‹œ๊ฐํ™” ์ƒ์„ฑ ์‹คํŒจ

+

+ {error} +

+ +
+); + +// ๐Ÿ”ฅ ํ•ต์‹ฌ: ์˜ค๋ฅธ์ชฝ ์˜์—ญ์— ํ‘œ์‹œ๋  ์• ๋‹ˆ๋ฉ”์ด์…˜ ์ปดํฌ๋„ŒํŠธ +const AnimationDisplay = ({ data, currentStep, totalSteps, animationType }) => { + // ์• ๋‹ˆ๋ฉ”์ด์…˜ ํƒ€์ž…๋ณ„ ์•„์ด์ฝ˜ ๋ฐ ์ด๋ฆ„ ์„ค์ • + const getAnimationInfo = (type) => { + const typeMap = { + 'fibonacci-recursion': { icon: '๐ŸŒณ', name: 'ํ”ผ๋ณด๋‚˜์น˜ ์žฌ๊ท€' }, + 'bubble-sort': { icon: '๐Ÿ“Š', name: '๋ฒ„๋ธ” ์ •๋ ฌ' }, + 'variables': { icon: '๐Ÿ“', name: '๋ณ€์ˆ˜ ์ถ”์ ' }, + 'array': { icon: '๐Ÿ“‹', name: '๋ฐฐ์—ด ์กฐ์ž‘' }, + 'recursion-tree': { icon: '๐ŸŒฒ', name: '์žฌ๊ท€ ํŠธ๋ฆฌ' } + }; + return typeMap[type] || { icon: '๐ŸŽฌ', name: '์•Œ๊ณ ๋ฆฌ์ฆ˜ ์‹œ๊ฐํ™”' }; + }; + + const { icon, name } = getAnimationInfo(animationType); + + // ๐ŸŽฏ ์‹ค์ œ ์• ๋‹ˆ๋ฉ”์ด์…˜์ด ์—ฌ๊ธฐ์— ๋ Œ๋”๋ง๋ฉ๋‹ˆ๋‹ค + return ( +
+ {/* ์• ๋‹ˆ๋ฉ”์ด์…˜ ํ—ค๋” */} +
+

+ {icon} {name} ์‹œ๊ฐํ™” +

+

+ ํ˜„์žฌ {currentStep + 1}๋‹จ๊ณ„ / ์ด {totalSteps}๋‹จ๊ณ„ +

+
+ + {/* ๋ฉ”์ธ ์• ๋‹ˆ๋ฉ”์ด์…˜ ์˜์—ญ */} +
+ {/* ๐ŸŽฌ ์—ฌ๊ธฐ๊ฐ€ ์‹ค์ œ ์• ๋‹ˆ๋ฉ”์ด์…˜์ด ํ‘œ์‹œ๋  ์˜์—ญ์ž…๋‹ˆ๋‹ค */} +
+ {/* ํ˜„์žฌ๋Š” ํ”Œ๋ ˆ์ด์Šคํ™€๋”, ์‹ค์ œ ์• ๋‹ˆ๋ฉ”์ด์…˜ ์ปดํฌ๋„ŒํŠธ๋กœ ๊ต์ฒด ์˜ˆ์ • */} +
+ {icon} +
+ +

+ {name} ์• ๋‹ˆ๋ฉ”์ด์…˜ +

+ +

+ ์ด ์˜์—ญ์— {name} ๊ณผ์ •์ด ์‹ค์‹œ๊ฐ„์œผ๋กœ ์‹œ๊ฐํ™”๋ฉ๋‹ˆ๋‹ค. +

+ + {/* ํ˜„์žฌ ๋‹จ๊ณ„ ์ •๋ณด */} +
+
+
+ ํ˜„์žฌ ๋‹จ๊ณ„ +
+
+ {currentStep + 1} +
+
+ +
+
+ ์ด ๋‹จ๊ณ„ +
+
+ {totalSteps} +
+
+ + {data?.variables && ( +
+
+ ๋ณ€์ˆ˜ ๊ฐœ์ˆ˜ +
+
+ {data.variables.length} +
+
+ )} +
+ + {/* ํ˜„์žฌ ๋‹จ๊ณ„ ์„ค๋ช… */} + {data?.steps?.[currentStep] && ( +
+
+ ๐Ÿ“ ํ˜„์žฌ ์‹คํ–‰ ์ค‘์ธ ๋‹จ๊ณ„: +
+
+ {data.steps[currentStep].description || '๋‹จ๊ณ„๋ฅผ ์‹คํ–‰ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค...'} +
+ {data.steps[currentStep].line && ( +
+ ๐Ÿ“ ์ฝ”๋“œ ๋ผ์ธ: {data.steps[currentStep].line} +
+ )} +
+ )} +
+
+ + +
+ ); +}; + +// ์ •๋ณด ํŒจ๋„ ์ปดํฌ๋„ŒํŠธ (์™ผ์ชฝ ์˜์—ญ) +const InfoPanel = ({ data, currentStep, totalSteps, animationType }) => { + const InfoCard = ({ title, icon, children }) => ( +
+

+ {icon} {title} +

+ {children} +
+ ); + + const currentStepData = data?.steps?.[currentStep]; + + return ( +
+ {/* ํ˜„์žฌ ๋‹จ๊ณ„ ์ •๋ณด */} + +
+ + {currentStep + 1} + + / {totalSteps} +
+ + {currentStepData?.description && ( +
+ {currentStepData.description} +
+ )} + + {currentStepData?.line && ( +
+ ๐Ÿ“ ๋ผ์ธ {currentStepData.line} +
+ )} +
+ + {/* ํ”„๋กœ๊ทธ๋žจ ์ •๋ณด */} + +
+ {[ + { label: '์–ธ์–ด', value: data?.lang || 'Unknown' }, + { label: '์ž…๋ ฅ๊ฐ’', value: data?.input || '์—†์Œ' }, + { label: '๋ณ€์ˆ˜ ๊ฐœ์ˆ˜', value: `${data?.variables?.length || 0}๊ฐœ` }, + { label: '์‹คํ–‰ ๋‹จ๊ณ„', value: `${totalSteps}๋‹จ๊ณ„` }, + { label: '์• ๋‹ˆ๋ฉ”์ด์…˜', value: animationType } + ].map((item, index) => ( +
+ {item.label}: + {item.value} +
+ ))} +
+
+ + {/* ๋ณ€์ˆ˜ ์ƒํƒœ */} + {data?.variables && data.variables.length > 0 && ( + +
+ {data.variables.map((variable, index) => ( +
+
+ + {variable.name} + + + ({variable.type}) + +
+ + {variable.currentValue ?? 'null'} + +
+ ))} +
+
+ )} + + {/* ๋ณ€์ˆ˜ ๋ณ€ํ™” */} + {currentStepData?.changes && currentStepData.changes.length > 0 && ( + +
+ {currentStepData.changes.map((change, index) => ( +
+ + {change.variable} + + + : {change.before} โ†’ {change.after} + +
+ ))} +
+
+ )} +
+ ); +}; + +// ๋ฉ”์ธ ๋ชจ๋‹ฌ ์ปดํฌ๋„ŒํŠธ +const VisualizationModal = ({ isOpen, onClose, code, language, input }) => { + const [data, setData] = useState(null); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(null); + const [animationType, setAnimationType] = useState('variables'); + const [totalSteps, setTotalSteps] = useState(0); + + const animationControls = useAnimationControls(totalSteps); + + // Mock ๋ฐ์ดํ„ฐ ์ƒ์„ฑ ํ•จ์ˆ˜ + const getMockDataByCodePattern = (code, language, input) => { + const codeContent = code?.toLowerCase() || ''; + + if (codeContent.includes('fibo') && codeContent.includes('return')) { + return { + lang: language, + code: code, + input: input || "7", + variables: [ + { name: "n", type: "int", initialValue: null, currentValue: parseInt(input) || 7 } + ], + functions: [ + { name: "fibo", params: ["n"], called: 25 } + ], + steps: [ + { + line: 5, + description: "์ž…๋ ฅ๊ฐ’ n์„ ์‚ฌ์šฉ์ž๋กœ๋ถ€ํ„ฐ ์ž…๋ ฅ๋ฐ›์Œ", + changes: [ + { variable: "n", before: null, after: parseInt(input) || 7 } + ] + }, + { + line: 6, + description: `fibo(${parseInt(input) || 7}) ํ˜ธ์ถœ`, + changes: [] + }, + { + line: 6, + description: `fibo(${parseInt(input) || 7})์˜ ๊ฒฐ๊ณผ๋ฅผ ์ถœ๋ ฅ`, + changes: [] + } + ] + }; + } + + // ๊ธฐ๋ณธ ๋ณ€์ˆ˜ ์ถ”์  + return { + lang: language, + input: input || "5", + variables: [ + { name: "n", type: "int", initialValue: null, currentValue: parseInt(input) || 5 }, + { name: "i", type: "int", initialValue: null, currentValue: 1 }, + { name: "j", type: "int", initialValue: null, currentValue: 0 } + ], + steps: [ + { line: 3, description: "๋ณ€์ˆ˜ n ์„ ์–ธ" }, + { line: 4, description: "n ์ž…๋ ฅ ๋ฐ›์Œ", changes: [{ variable: "n", before: null, after: parseInt(input) || 5 }] }, + { line: 6, description: "i=1๋ถ€ํ„ฐ n๊นŒ์ง€ for๋ฌธ ์‹œ์ž‘" }, + { line: 8, description: "๊ณต๋ฐฑ ์ถœ๋ ฅ" }, + { line: 10, description: "* ์ถœ๋ ฅ" }, + { line: 11, description: "์ค„๋ฐ”๊ฟˆ ์ถœ๋ ฅ" }, + { line: 6, description: "ํ”„๋กœ๊ทธ๋žจ ์™„๋ฃŒ" } + ] + }; + }; + + // ๋ฐ์ดํ„ฐ ๊ฐ€์ ธ์˜ค๊ธฐ ํ•จ์ˆ˜ + const fetchVisualizationData = async () => { + if (!code?.trim()) { + setError('์ฝ”๋“œ๊ฐ€ ๋น„์–ด์žˆ์Šต๋‹ˆ๋‹ค.'); + return; + } + + setIsLoading(true); + setError(null); + + try { + await new Promise(resolve => setTimeout(resolve, 1500)); + + const mockData = getMockDataByCodePattern(code, language, input); + setData(mockData); + const steps = mockData.steps?.length || 0; + setTotalSteps(steps); + animationControls.reset(); + + // ์• ๋‹ˆ๋ฉ”์ด์…˜ ํƒ€์ž… ์„ค์ • + const codeContent = code.toLowerCase(); + if (codeContent.includes('fibo')) { + setAnimationType('fibonacci-recursion'); + } else if (codeContent.includes('bubble')) { + setAnimationType('bubble-sort'); + } else { + setAnimationType('variables'); + } + + } catch (err) { + setError(err.message); + } finally { + setIsLoading(false); + } + }; + + // ๋ชจ๋‹ฌ ์ดˆ๊ธฐํ™” + const resetModal = () => { + setData(null); + setError(null); + setTotalSteps(0); + animationControls.reset(); + }; + + // ๋ชจ๋‹ฌ์ด ์—ด๋ฆด ๋•Œ ์ž๋™์œผ๋กœ ์‹œ๊ฐํ™” ์ƒ์„ฑ + useEffect(() => { + if (isOpen && !data && !isLoading) { + fetchVisualizationData(); + } + }, [isOpen]); + + // ๋ชจ๋‹ฌ์ด ๋‹ซํž ๋•Œ ์ƒํƒœ ์ดˆ๊ธฐํ™” + useEffect(() => { + if (!isOpen) { + resetModal(); + } + }, [isOpen]); + + // ESC ํ‚ค๋กœ ๋ชจ๋‹ฌ ๋‹ซ๊ธฐ + useEffect(() => { + const handleEscape = (e) => { + if (e.key === 'Escape' && isOpen) { + onClose(); + } + }; + + document.addEventListener('keydown', handleEscape); + return () => document.removeEventListener('keydown', handleEscape); + }, [isOpen, onClose]); + + // ๋ชจ๋‹ฌ์ด ์—ด๋ ค์žˆ์ง€ ์•Š์œผ๋ฉด ๋ Œ๋”๋งํ•˜์ง€ ์•Š์Œ + if (!isOpen) return null; + + // ํฌํ„ธ์„ ์‚ฌ์šฉํ•˜์—ฌ body์— ์ง์ ‘ ๋ Œ๋”๋ง + return ReactDOM.createPortal( +
+
e.stopPropagation()} + > + {/* ๐ŸŽฏ ๋ชจ๋‹ฌ ํ—ค๋” */} +
+
+ ๐Ÿ“Š +

์ฝ”๋“œ ์‹œ๊ฐํ™”

+
+ {language} +
+
+ +
+ {/* ๐ŸŽฎ ์• ๋‹ˆ๋ฉ”์ด์…˜ ์ปจํŠธ๋กค ๋ฒ„ํŠผ๋“ค */} + {data && !isLoading && ( + <> + + + + + )} + + +
+
+ + {/* ๐Ÿ”ฅ ํ•ต์‹ฌ: 2์—ด ๋ ˆ์ด์•„์›ƒ - ์™ผ์ชฝ ์ •๋ณดํŒจ๋„ + ์˜ค๋ฅธ์ชฝ ์• ๋‹ˆ๋ฉ”์ด์…˜ */} +
+ {/* ๐Ÿ“‹ ์™ผ์ชฝ: ์ •๋ณด ํŒจ๋„ */} +
+ {data ? ( + + ) : ( +
+
+
๐Ÿ“Š
+

๋ฐ์ดํ„ฐ ๋กœ๋”ฉ ์ค‘...

+
+
+ )} +
+ + {/* ๐ŸŽฌ ์˜ค๋ฅธ์ชฝ: ๋ฉ”์ธ ์• ๋‹ˆ๋ฉ”์ด์…˜ ์˜์—ญ */} +
+ {isLoading ? ( + + ) : error ? ( + + ) : data ? ( + + ) : ( +
+
๐Ÿš€
+

์‹œ๊ฐํ™” ์ค€๋น„ ์ค‘...

+

์ž ์‹œ๋งŒ ๊ธฐ๋‹ค๋ ค์ฃผ์„ธ์š”

+
+ )} +
+
+
+ + {/* CSS ์• ๋‹ˆ๋ฉ”์ด์…˜ ์Šคํƒ€์ผ */} + +
, + document.body + ); +}; + +export default VisualizationModal; \ No newline at end of file diff --git a/src/components/ide/animations/PlaceholderAnimation.css b/src/components/ide/animations/PlaceholderAnimation.css new file mode 100644 index 0000000..85aa494 --- /dev/null +++ b/src/components/ide/animations/PlaceholderAnimation.css @@ -0,0 +1,415 @@ +/* animations/PlaceholderAnimation.css - ํ”Œ๋ ˆ์ด์Šคํ™€๋” ์• ๋‹ˆ๋ฉ”์ด์…˜ ์Šคํƒ€์ผ */ + +.placeholder-animation { + width: 100%; + height: 100%; + display: flex; + flex-direction: column; + min-height: 500px; + background: var(--card, #ffffff); + border-radius: 12px; + border: 1px solid var(--border, #e2e8f0); + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05); + overflow: hidden; +} + +/* ๐Ÿ“‹ ํ—ค๋” ์˜์—ญ */ +.placeholder-header { + padding: 24px 32px 20px 32px; + background: linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%); + border-bottom: 1px solid var(--border, #e2e8f0); +} + +.placeholder-header h3 { + margin: 0 0 8px 0; + color: var(--text, #1e293b); + font-size: 20px; + font-weight: 600; + display: flex; + align-items: center; + gap: 12px; +} + +.placeholder-header p { + margin: 0; + color: var(--text-light, #64748b); + font-size: 14px; + line-height: 1.5; +} + +/* ๐ŸŽฏ ๋ฉ”์ธ ์ฝ˜ํ…์ธ  ์˜์—ญ */ +.placeholder-content { + flex: 1; + display: flex; + align-items: flex-start; + justify-content: center; + padding: 32px 24px; + overflow-y: auto; +} + +.placeholder-box { + text-align: center; + max-width: 600px; + width: 100%; + padding: 32px; + border: 2px dashed var(--border, #e2e8f0); + border-radius: 16px; + background: linear-gradient(135deg, #fafbfc 0%, #f8fafc 100%); +} + +/* ๐ŸŽจ ๋ฉ”์ธ ์•„์ด์ฝ˜ */ +.placeholder-icon { + font-size: 64px; + margin-bottom: 20px; + animation: float 4s ease-in-out infinite; + display: block; +} + +@keyframes float { + 0%, 100% { + transform: translateY(0px) scale(1); + } + 50% { + transform: translateY(-12px) scale(1.05); + } +} + +.placeholder-box h4 { + margin: 0 0 16px 0; + color: var(--text, #1e293b); + font-size: 24px; + font-weight: 700; + letter-spacing: -0.5px; +} + +.placeholder-box > p { + margin: 0 0 32px 0; + color: var(--text-light, #64748b); + font-size: 16px; + line-height: 1.6; +} + +/* ๐Ÿ“Š ์ƒํƒœ ์ •๋ณด ๊ทธ๋ฆฌ๋“œ */ +.placeholder-stats { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); + gap: 16px; + margin: 32px 0; + text-align: left; +} + +.stat-item { + background: var(--card, #ffffff); + padding: 16px; + border-radius: 12px; + border: 1px solid var(--border, #e2e8f0); + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05); + transition: all 0.2s ease; +} + +.stat-item:hover { + transform: translateY(-2px); + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); + border-color: var(--primary, #8b5cf6); +} + +.stat-label { + display: block; + font-size: 12px; + color: var(--text-light, #64748b); + margin-bottom: 8px; + font-weight: 500; + text-transform: uppercase; + letter-spacing: 0.5px; +} + +.stat-value { + display: block; + font-size: 16px; + color: var(--primary, #8b5cf6); + font-weight: 700; + line-height: 1.2; +} + +/* ๐ŸŽฏ ์˜ˆ์ƒ ๊ธฐ๋Šฅ ์„น์…˜ */ +.coming-soon { + margin: 32px 0; + padding: 24px; + background: linear-gradient(135deg, #fef7ff 0%, #faf5ff 100%); + border-radius: 12px; + border-left: 4px solid var(--primary, #8b5cf6); + text-align: left; + box-shadow: 0 2px 8px rgba(139, 92, 246, 0.1); +} + +.coming-soon p { + margin: 0 0 16px 0; + font-size: 14px; + font-weight: 600; + color: var(--text, #1e293b); + display: flex; + align-items: center; + gap: 8px; +} + +.coming-soon ul { + margin: 0; + padding-left: 20px; + list-style-type: none; +} + +.coming-soon li { + font-size: 13px; + color: var(--text-light, #64748b); + margin-bottom: 8px; + line-height: 1.5; + position: relative; + padding-left: 24px; +} + +.coming-soon li::before { + content: "โœจ"; + position: absolute; + left: 0; + top: 0; + font-size: 12px; +} + +/* ๐Ÿ“ˆ ๊ฐœ๋ฐœ ์ง„ํ–‰๋„ */ +.development-progress { + margin: 32px 0; + padding: 24px; + background: linear-gradient(135deg, #f0f9ff 0%, #e0f2fe 100%); + border-radius: 12px; + border-left: 4px solid #0ea5e9; + text-align: left; +} + +.development-progress h5 { + margin: 0 0 16px 0; + font-size: 14px; + font-weight: 600; + color: var(--text, #1e293b); + display: flex; + align-items: center; + gap: 8px; +} + +.progress-bar { + width: 100%; + height: 8px; + background: #e2e8f0; + border-radius: 4px; + overflow: hidden; + margin-bottom: 8px; +} + +.progress-fill { + height: 100%; + background: linear-gradient(90deg, #0ea5e9, #06b6d4); + border-radius: 4px; + transition: width 0.5s ease; + animation: progress-pulse 2s ease-in-out infinite; +} + +@keyframes progress-pulse { + 0%, 100% { opacity: 1; } + 50% { opacity: 0.8; } +} + +.progress-text { + display: flex; + justify-content: space-between; + align-items: center; + font-size: 12px; + color: var(--text-light, #64748b); + margin-bottom: 16px; +} + +.progress-steps { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); + gap: 12px; +} + +.step { + padding: 8px 12px; + border-radius: 8px; + font-size: 11px; + font-weight: 500; + text-align: center; + transition: all 0.2s ease; +} + +.step.completed { + background: #dcfce7; + color: #166534; + border: 1px solid #bbf7d0; +} + +.step.current { + background: #fef3c7; + color: #92400e; + border: 1px solid #fde68a; + animation: pulse 2s ease-in-out infinite; +} + +.step.pending { + background: #f1f5f9; + color: #64748b; + border: 1px solid #e2e8f0; +} + +@keyframes pulse { + 0%, 100% { transform: scale(1); } + 50% { transform: scale(1.02); } +} + +/* ๐Ÿ“„ ๋ฐ์ดํ„ฐ ๋ฏธ๋ฆฌ๋ณด๊ธฐ */ +.data-preview { + text-align: left; + background: var(--card, #ffffff); + border: 1px solid var(--border, #e2e8f0); + border-radius: 12px; + margin: 32px 0; + overflow: hidden; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05); +} + +.data-preview details { + padding: 20px; +} + +.data-preview summary { + cursor: pointer; + color: var(--text, #1e293b); + font-size: 14px; + font-weight: 600; + margin-bottom: 12px; + padding: 4px 0; + border-radius: 6px; + transition: all 0.2s ease; + display: flex; + align-items: center; + gap: 8px; +} + +.data-preview summary:hover { + color: var(--primary, #8b5cf6); +} + +.data-preview summary::marker { + content: ""; +} + +.data-preview summary::before { + content: "โ–ถ"; + transition: transform 0.2s ease; + font-size: 12px; +} + +.data-preview details[open] summary::before { + transform: rotate(90deg); +} + +.json-preview { + margin: 12px 0 0 0; + font-family: 'SF Mono', 'Monaco', 'Cascadia Code', 'Roboto Mono', monospace; + font-size: 12px; + color: var(--text-light, #64748b); + white-space: pre-wrap; + line-height: 1.5; + max-height: 300px; + overflow-y: auto; + background: #f8fafc; + padding: 16px; + border-radius: 8px; + border: 1px solid #e2e8f0; +} + +/* ๐Ÿ’ก ๋„์›€๋ง ์„น์…˜ */ +.help-section { + margin: 32px 0 0 0; + padding: 20px; + background: linear-gradient(135deg, #fffbeb 0%, #fef3c7 100%); + border-radius: 12px; + border-left: 4px solid #f59e0b; + text-align: left; +} + +.help-section p { + margin: 0 0 8px 0; + font-size: 13px; + color: var(--text-light, #64748b); + line-height: 1.5; +} + +.help-section p:first-child { + font-weight: 600; + color: var(--text, #1e293b); +} + +/* ๐Ÿ”„ ๋ฐ˜์‘ํ˜• ๋””์ž์ธ */ +@media (max-width: 768px) { + .placeholder-header { + padding: 20px 24px 16px 24px; + } + + .placeholder-header h3 { + font-size: 18px; + } + + .placeholder-content { + padding: 24px 16px; + } + + .placeholder-box { + padding: 24px; + } + + .placeholder-icon { + font-size: 48px; + margin-bottom: 16px; + } + + .placeholder-box h4 { + font-size: 20px; + } + + .placeholder-stats { + grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); + gap: 12px; + margin: 24px 0; + } + + .stat-item { + padding: 12px; + } + + .progress-steps { + grid-template-columns: 1fr 1fr; + gap: 8px; + } +} + +@media (max-width: 480px) { + .placeholder-box { + padding: 20px 16px; + } + + .placeholder-stats { + grid-template-columns: 1fr; + gap: 8px; + } + + .progress-steps { + grid-template-columns: 1fr; + } + + .development-progress, + .coming-soon, + .help-section { + padding: 16px; + margin: 20px 0; + } +} \ No newline at end of file diff --git a/src/components/ide/animations/PlaceholderAnimation.jsx b/src/components/ide/animations/PlaceholderAnimation.jsx new file mode 100644 index 0000000..2fd3081 --- /dev/null +++ b/src/components/ide/animations/PlaceholderAnimation.jsx @@ -0,0 +1,228 @@ +// animations/PlaceholderAnimation.jsx - ๊ฐœ๋ฐœ ์ค‘ ์• ๋‹ˆ๋ฉ”์ด์…˜ ํ‘œ์‹œ ์ปดํฌ๋„ŒํŠธ +import React from 'react'; +import './PlaceholderAnimation.css'; + +const PlaceholderAnimation = ({ + type = 'unknown', + data = null, + currentStep = 0, + totalSteps = 0, + animationType = 'unknown' + }) => { + // ์• ๋‹ˆ๋ฉ”์ด์…˜ ํƒ€์ž…๋ณ„ ์•„์ด์ฝ˜ ์„ ํƒ + const getAnimationIcon = (type) => { + if (type.includes('fibonacci') || type.includes('recursion')) return '๐ŸŒณ'; + if (type.includes('bubble') || type.includes('sort')) return '๐Ÿ“Š'; + if (type.includes('search') || type.includes('binary') || type.includes('linear')) return '๐Ÿ”'; + if (type.includes('array')) return '๐Ÿ“‹'; + if (type.includes('tree')) return '๐ŸŒฒ'; + if (type.includes('stack')) return '๐Ÿ“š'; + if (type.includes('queue')) return '๐Ÿš‡'; + if (type.includes('linked')) return '๐Ÿ”—'; + if (type.includes('variables')) return '๐Ÿ“'; + return '๐ŸŽฌ'; + }; + + // ์• ๋‹ˆ๋ฉ”์ด์…˜ ํƒ€์ž…๋ณ„ ํ•œ๊ตญ์–ด ์ด๋ฆ„ + const getAnimationName = (type) => { + const names = { + 'fibonacci-recursion': 'ํ”ผ๋ณด๋‚˜์น˜ ์žฌ๊ท€', + 'factorial-recursion': 'ํŒฉํ† ๋ฆฌ์–ผ ์žฌ๊ท€', + 'hanoi-tower': 'ํ•˜๋…ธ์ด ํƒ‘', + 'recursion-tree': '์žฌ๊ท€ ํŠธ๋ฆฌ', + 'bubble-sort': '๋ฒ„๋ธ” ์ •๋ ฌ', + 'quick-sort': 'ํ€ต ์ •๋ ฌ', + 'merge-sort': '๋ณ‘ํ•ฉ ์ •๋ ฌ', + 'insertion-sort': '์‚ฝ์ž… ์ •๋ ฌ', + 'selection-sort': '์„ ํƒ ์ •๋ ฌ', + 'heap-sort': 'ํž™ ์ •๋ ฌ', + 'array': '๋ฐฐ์—ด ์กฐ์ž‘', + 'array-manipulation': '๋ฐฐ์—ด ์กฐ์ž‘', + 'linked-list': '์—ฐ๊ฒฐ ๋ฆฌ์ŠคํŠธ', + 'stack': '์Šคํƒ', + 'queue': 'ํ', + 'tree': 'ํŠธ๋ฆฌ', + 'binary-tree': '์ด์ง„ ํŠธ๋ฆฌ', + 'bst': '์ด์ง„ ํƒ์ƒ‰ ํŠธ๋ฆฌ', + 'binary-search': '์ด์ง„ ํƒ์ƒ‰', + 'linear-search': '์„ ํ˜• ํƒ์ƒ‰', + 'breadth-first-search': '๋„ˆ๋น„ ์šฐ์„  ํƒ์ƒ‰', + 'depth-first-search': '๊นŠ์ด ์šฐ์„  ํƒ์ƒ‰', + 'bfs': '๋„ˆ๋น„ ์šฐ์„  ํƒ์ƒ‰', + 'dfs': '๊นŠ์ด ์šฐ์„  ํƒ์ƒ‰', + 'variables': '๋ณ€์ˆ˜ ์ถ”์ ', + 'variable-tracking': '๋ณ€์ˆ˜ ์ถ”์ ', + 'basic-algorithm': '๊ธฐ๋ณธ ์•Œ๊ณ ๋ฆฌ์ฆ˜' + }; + return names[type] || '์•Œ ์ˆ˜ ์—†๋Š” ์•Œ๊ณ ๋ฆฌ์ฆ˜'; + }; + + // ์• ๋‹ˆ๋ฉ”์ด์…˜ ํƒ€์ž…๋ณ„ ์˜ˆ์ƒ ๊ธฐ๋Šฅ + const getExpectedFeatures = (type) => { + if (type.includes('fibonacci')) { + return [ + '์žฌ๊ท€ ํ˜ธ์ถœ ํŠธ๋ฆฌ ์‹œ๊ฐํ™”', + 'ํ•จ์ˆ˜ ํ˜ธ์ถœ ์Šคํƒ ํ‘œ์‹œ', + '์ค‘๋ณต ๊ณ„์‚ฐ ํ•˜์ด๋ผ์ดํŠธ', + '๋ฉ”๋ชจ์ด์ œ์ด์…˜ ํšจ๊ณผ ๋น„๊ต', + '์‹œ๊ฐ„ ๋ณต์žก๋„ ๋ถ„์„' + ]; + } + + if (type.includes('bubble') || type.includes('sort')) { + return [ + '๋ฐฐ์—ด ์š”์†Œ ๋น„๊ต ์• ๋‹ˆ๋ฉ”์ด์…˜', + '์Šค์™‘ ๊ณผ์ • ์‹œ๊ฐํ™”', + '์ •๋ ฌ ์ง„ํ–‰๋„ ํ‘œ์‹œ', + '๋น„๊ต ํšŸ์ˆ˜ ์นด์šดํ„ฐ', + '์ƒ‰์ƒ์œผ๋กœ ์ƒํƒœ ๊ตฌ๋ถ„' + ]; + } + + if (type.includes('variables') || type.includes('basic')) { + return [ + '๋ณ€์ˆ˜ ๊ฐ’ ๋ณ€ํ™” ์ถ”์ ', + '๋ฉ”๋ชจ๋ฆฌ ์ƒํƒœ ํ‘œ์‹œ', + '์‹คํ–‰ ํ๋ฆ„ ์‹œ๊ฐํ™”', + '์กฐ๊ฑด๋ฌธ ๋ถ„๊ธฐ ํ‘œ์‹œ', + '๋ฐ˜๋ณต๋ฌธ ์ง„ํ–‰๋„' + ]; + } + + if (type.includes('search')) { + return [ + 'ํƒ์ƒ‰ ์˜์—ญ ์‹œ๊ฐํ™”', + 'ํ˜„์žฌ ํƒ์ƒ‰ ์œ„์น˜ ํ‘œ์‹œ', + '๋น„๊ต ๊ณผ์ • ์• ๋‹ˆ๋ฉ”์ด์…˜', + 'ํƒ์ƒ‰ ๊ฒฝ๋กœ ์ถ”์ ', + '์„ฑ๋Šฅ ๋ถ„์„' + ]; + } + + return [ + '์•Œ๊ณ ๋ฆฌ์ฆ˜ ๋‹จ๊ณ„๋ณ„ ์‹œ๊ฐํ™”', + '๋ฐ์ดํ„ฐ ๊ตฌ์กฐ ์ƒํƒœ ํ‘œ์‹œ', + '์‹คํ–‰ ๊ณผ์ • ์• ๋‹ˆ๋ฉ”์ด์…˜', + '์„ฑ๋Šฅ ์ง€ํ‘œ ํ‘œ์‹œ', + '์ƒํ˜ธ์ž‘์šฉ ๊ธฐ๋Šฅ' + ]; + }; + + const icon = getAnimationIcon(type); + const name = getAnimationName(type); + const features = getExpectedFeatures(type); + + return ( +
+ {/* ํ—ค๋” */} +
+

+ {icon} {name} ์• ๋‹ˆ๋ฉ”์ด์…˜ ๊ฐœ๋ฐœ ์ค‘ +

+

์ด ์• ๋‹ˆ๋ฉ”์ด์…˜์€ ๊ณง ์™„์„ฑ๋  ์˜ˆ์ •์ž…๋‹ˆ๋‹ค!

+
+ + {/* ๋ฉ”์ธ ์ฝ˜ํ…์ธ  */} +
+
+ {/* ๋ฉ”์ธ ์•„์ด์ฝ˜ */} +
+ {icon} +
+ + {/* ์ œ๋ชฉ */} +

{name} ์‹œ๊ฐํ™”

+

์—ฌ๊ธฐ์— {name} ๊ณผ์ •์ด ๋‹จ๊ณ„๋ณ„๋กœ ์‹œ๊ฐํ™”๋ฉ๋‹ˆ๋‹ค.

+ + {/* ํ˜„์žฌ ์ƒํƒœ ์ •๋ณด */} +
+
+ ํ˜„์žฌ ๋‹จ๊ณ„ + {currentStep + 1} / {totalSteps} +
+ + {data?.dataStructure?.type && ( +
+ ๋ฐ์ดํ„ฐ ๊ตฌ์กฐ + {data.dataStructure.type} +
+ )} + + {data?.functions?.[0]?.name && ( +
+ ํ•จ์ˆ˜ + {data.functions[0].name} +
+ )} + + {data?.variables?.length && ( +
+ ๋ณ€์ˆ˜ ๊ฐœ์ˆ˜ + {data.variables.length}๊ฐœ +
+ )} + +
+ ์• ๋‹ˆ๋ฉ”์ด์…˜ ํƒ€์ž… + {animationType} +
+ +
+ ๊ฐœ๋ฐœ ์ƒํƒœ + ๐Ÿšง ๊ฐœ๋ฐœ ์ค‘ +
+
+ + {/* ์˜ˆ์ƒ ๊ธฐ๋Šฅ ๋ชฉ๋ก */} +
+

๐ŸŽฏ ๊ตฌํ˜„ ์˜ˆ์ • ๊ธฐ๋Šฅ:

+
    + {features.map((feature, index) => ( +
  • {feature}
  • + ))} +
+
+ + {/* ๊ฐœ๋ฐœ ์ง„ํ–‰๋„ */} +
+
๐Ÿ“ˆ ๊ฐœ๋ฐœ ์ง„ํ–‰๋„
+
+
+
+
+ UI ์„ค๊ณ„ ์™„๋ฃŒ + 25% +
+
+
โœ… ๊ธฐ๋ณธ ๊ตฌ์กฐ
+
๐Ÿ”„ ์• ๋‹ˆ๋ฉ”์ด์…˜ ๋กœ์ง
+
โณ ์ธํ„ฐ๋ž™์…˜
+
โณ ์ตœ์ ํ™”
+
+
+ + {/* ๋ฐ์ดํ„ฐ ๋ฏธ๋ฆฌ๋ณด๊ธฐ */} + {data && ( +
+
+ ๐Ÿ“„ ์ „๋‹ฌ๋ฐ›์€ ๋ฐ์ดํ„ฐ ๋ฏธ๋ฆฌ๋ณด๊ธฐ +
+                  {JSON.stringify(data, null, 2).substring(0, 800)}
+                                    {JSON.stringify(data, null, 2).length > 800 && '\n...'}
+                
+
+
+ )} + + {/* ๋„์›€๋ง */} +
+

๐Ÿ’ก ๊ฐœ๋ฐœ์ž ๋…ธํŠธ:

+

์ด ์• ๋‹ˆ๋ฉ”์ด์…˜์€ ํ˜„์žฌ ๊ฐœ๋ฐœ ์ค‘์ž…๋‹ˆ๋‹ค. ์™„์„ฑ๋˜๋ฉด ์‹ค์‹œ๊ฐ„์œผ๋กœ ์•Œ๊ณ ๋ฆฌ์ฆ˜ ์‹คํ–‰ ๊ณผ์ •์„ ์‹œ๊ฐ์ ์œผ๋กœ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

+
+
+
+
+ ); +}; + +export default PlaceholderAnimation; \ No newline at end of file diff --git a/src/components/ide/utils/AnimationDetector.js b/src/components/ide/utils/AnimationDetector.js new file mode 100644 index 0000000..4db163d --- /dev/null +++ b/src/components/ide/utils/AnimationDetector.js @@ -0,0 +1,325 @@ +/** + * ๐Ÿ” AnimationDetector - JSON ๋ฐ์ดํ„ฐ ๋ถ„์„ํ•ด์„œ ์ ์ ˆํ•œ ์• ๋‹ˆ๋ฉ”์ด์…˜ ํƒ€์ž… ๊ฐ์ง€ + * ์ž„์‹œ ๊ฐœ๋ฐœ ๋ฒ„์ „ - ๊ธฐ๋ณธ์ ์ธ ํŒจํ„ด ๊ฐ์ง€๋งŒ ๊ตฌํ˜„ + */ +export class AnimationDetector { + + /** + * ๐ŸŽฏ ๋ฉ”์ธ ๊ฐ์ง€ ํ•จ์ˆ˜ - JSON ๋ฐ์ดํ„ฐ๋ฅผ ๋ถ„์„ํ•ด์„œ ์• ๋‹ˆ๋ฉ”์ด์…˜ ํƒ€์ž… ๋ฐ˜ํ™˜ + * @param {Object} data - API์—์„œ ๋ฐ›์€ JSON ๋ฐ์ดํ„ฐ + * @returns {string} ๊ฐ์ง€๋œ ์• ๋‹ˆ๋ฉ”์ด์…˜ ํƒ€์ž… + */ + static detectAnimationType(data) { + console.log('๐Ÿ” ์• ๋‹ˆ๋ฉ”์ด์…˜ ํƒ€์ž… ๊ฐ์ง€ ์‹œ์ž‘:', data); + + try { + // 1. ๐Ÿ“Š ๋ฐ์ดํ„ฐ ๊ตฌ์กฐ ํƒ€์ž… ๊ธฐ๋ฐ˜ ๊ฐ์ง€ + const dataStructureType = this.detectByDataStructure(data); + if (dataStructureType) { + console.log('โœ… ๋ฐ์ดํ„ฐ ๊ตฌ์กฐ๋กœ ๊ฐ์ง€๋จ:', dataStructureType); + return dataStructureType; + } + + // 2. ๐Ÿ”ง ํ•จ์ˆ˜๋ช… ๊ธฐ๋ฐ˜ ๊ฐ์ง€ + const functionType = this.detectByFunctionName(data); + if (functionType) { + console.log('โœ… ํ•จ์ˆ˜๋ช…์œผ๋กœ ๊ฐ์ง€๋จ:', functionType); + return functionType; + } + + // 3. ๐Ÿ“ ์ฝ”๋“œ ํŒจํ„ด ๊ธฐ๋ฐ˜ ๊ฐ์ง€ + const codePatternType = this.detectByCodePattern(data); + if (codePatternType) { + console.log('โœ… ์ฝ”๋“œ ํŒจํ„ด์œผ๋กœ ๊ฐ์ง€๋จ:', codePatternType); + return codePatternType; + } + + // 4. ๐Ÿ“‹ ๋ณ€์ˆ˜ ํŒจํ„ด ๊ธฐ๋ฐ˜ ๊ฐ์ง€ + const variableType = this.detectByVariables(data); + if (variableType) { + console.log('โœ… ๋ณ€์ˆ˜ ํŒจํ„ด์œผ๋กœ ๊ฐ์ง€๋จ:', variableType); + return variableType; + } + + // 5. ๐ŸŽฏ ๊ธฐ๋ณธ๊ฐ’: ๋ณ€์ˆ˜ ์ถ”์  ์• ๋‹ˆ๋ฉ”์ด์…˜ + console.log('โš ๏ธ ํŠน์ • ํŒจํ„ด์„ ๊ฐ์ง€ํ•˜์ง€ ๋ชปํ•จ, ๊ธฐ๋ณธ ๋ณ€์ˆ˜ ์ถ”์  ์‚ฌ์šฉ'); + return 'variables'; + + } catch (error) { + console.error('โŒ ์• ๋‹ˆ๋ฉ”์ด์…˜ ํƒ€์ž… ๊ฐ์ง€ ์ค‘ ์˜ค๋ฅ˜:', error); + return 'variables'; + } + } + + /** + * ๐Ÿ“Š ๋ฐ์ดํ„ฐ ๊ตฌ์กฐ ํƒ€์ž…์œผ๋กœ ์• ๋‹ˆ๋ฉ”์ด์…˜ ๊ฐ์ง€ + * @param {Object} data - JSON ๋ฐ์ดํ„ฐ + * @returns {string|null} ๊ฐ์ง€๋œ ํƒ€์ž… ๋˜๋Š” null + */ + static detectByDataStructure(data) { + const dataStructure = data?.dataStructure || data?.steps?.find(step => step.dataStructure)?.dataStructure; + + if (!dataStructure) return null; + + const type = dataStructure.type?.toLowerCase(); + + switch (type) { + case 'recursiontree': + case 'recursion_tree': + case 'recursion-tree': + // ์žฌ๊ท€ ํŠธ๋ฆฌ์ธ ๊ฒฝ์šฐ ํ•จ์ˆ˜๋ช…์œผ๋กœ ์„ธ๋ถ€ ํƒ€์ž… ๊ฒฐ์ • + const functionName = data?.functions?.[0]?.name?.toLowerCase(); + if (functionName?.includes('fibo')) return 'fibonacci-recursion'; + if (functionName?.includes('factorial')) return 'factorial-recursion'; + if (functionName?.includes('hanoi')) return 'hanoi-tower'; + return 'recursion-tree'; + + case 'array': + case 'list': + return 'array'; + + case 'stack': + return 'stack'; + + case 'queue': + return 'queue'; + + case 'tree': + case 'binarytree': + case 'binary_tree': + case 'binary-tree': + return 'tree'; + + case 'linkedlist': + case 'linked_list': + case 'linked-list': + return 'linked-list'; + + default: + return null; + } + } + + /** + * ๐Ÿ”ง ํ•จ์ˆ˜๋ช…์œผ๋กœ ์• ๋‹ˆ๋ฉ”์ด์…˜ ๊ฐ์ง€ + * @param {Object} data - JSON ๋ฐ์ดํ„ฐ + * @returns {string|null} ๊ฐ์ง€๋œ ํƒ€์ž… ๋˜๋Š” null + */ + static detectByFunctionName(data) { + const functions = data?.functions || []; + if (functions.length === 0) return null; + + const functionName = functions[0]?.name?.toLowerCase(); + if (!functionName) return null; + + // ์žฌ๊ท€ ํ•จ์ˆ˜๋“ค + if (functionName.includes('fibo')) return 'fibonacci-recursion'; + if (functionName.includes('factorial')) return 'factorial-recursion'; + if (functionName.includes('hanoi')) return 'hanoi-tower'; + + // ์ •๋ ฌ ํ•จ์ˆ˜๋“ค + if (functionName.includes('bubble')) return 'bubble-sort'; + if (functionName.includes('quick')) return 'quick-sort'; + if (functionName.includes('merge')) return 'merge-sort'; + if (functionName.includes('insertion')) return 'insertion-sort'; + if (functionName.includes('selection')) return 'selection-sort'; + if (functionName.includes('heap')) return 'heap-sort'; + if (functionName.includes('sort')) return 'bubble-sort'; // ๊ธฐ๋ณธ ์ •๋ ฌ + + // ํƒ์ƒ‰ ํ•จ์ˆ˜๋“ค + if (functionName.includes('binary_search') || functionName.includes('binarysearch')) return 'binary-search'; + if (functionName.includes('linear_search') || functionName.includes('linearsearch')) return 'linear-search'; + if (functionName.includes('bfs') || functionName.includes('breadth')) return 'bfs'; + if (functionName.includes('dfs') || functionName.includes('depth')) return 'dfs'; + + return null; + } + + /** + * ๐Ÿ“ ์ฝ”๋“œ ํŒจํ„ด์œผ๋กœ ์• ๋‹ˆ๋ฉ”์ด์…˜ ๊ฐ์ง€ + * @param {Object} data - JSON ๋ฐ์ดํ„ฐ + * @returns {string|null} ๊ฐ์ง€๋œ ํƒ€์ž… ๋˜๋Š” null + */ + static detectByCodePattern(data) { + const code = data?.code?.toLowerCase() || ''; + if (!code) return null; + + // ์žฌ๊ท€ ํŒจํ„ด ๊ฐ์ง€ + if (code.includes('fibo') && code.includes('return') && code.includes('fibo(')) { + return 'fibonacci-recursion'; + } + + if (code.includes('factorial') && code.includes('return') && code.includes('factorial(')) { + return 'factorial-recursion'; + } + + // ์ •๋ ฌ ํŒจํ„ด ๊ฐ์ง€ + if (code.includes('bubble') || (code.includes('for') && code.includes('swap') && code.includes('temp'))) { + return 'bubble-sort'; + } + + if (code.includes('quicksort') || code.includes('quick_sort')) { + return 'quick-sort'; + } + + if (code.includes('mergesort') || code.includes('merge_sort')) { + return 'merge-sort'; + } + + // ํƒ์ƒ‰ ํŒจํ„ด ๊ฐ์ง€ + if (code.includes('binary_search') || code.includes('binarysearch')) { + return 'binary-search'; + } + + if (code.includes('linear_search') || code.includes('linearsearch')) { + return 'linear-search'; + } + + return null; + } + + /** + * ๐Ÿ“‹ ๋ณ€์ˆ˜ ํŒจํ„ด์œผ๋กœ ์• ๋‹ˆ๋ฉ”์ด์…˜ ๊ฐ์ง€ + * @param {Object} data - JSON ๋ฐ์ดํ„ฐ + * @returns {string|null} ๊ฐ์ง€๋œ ํƒ€์ž… ๋˜๋Š” null + */ + static detectByVariables(data) { + const variables = data?.variables || []; + if (variables.length === 0) return null; + + const variableNames = variables.map(v => v.name?.toLowerCase()).filter(Boolean); + + // ๋ฐฐ์—ด ๊ด€๋ จ ๋ณ€์ˆ˜๋“ค + if (variableNames.some(name => ['list', 'arr', 'array', 'nums', 'data'].includes(name))) { + // ์ •๋ ฌ ๊ด€๋ จ ๋ณ€์ˆ˜๊ฐ€ ์žˆ๋Š”์ง€ ํ™•์ธ + if (variableNames.some(name => ['temp', 'i', 'j'].includes(name))) { + return 'bubble-sort'; // ๊ธฐ๋ณธ ์ •๋ ฌ ์• ๋‹ˆ๋ฉ”์ด์…˜ + } + return 'array'; + } + + // ์ผ๋ฐ˜์ ์ธ ๋ฐ˜๋ณต๋ฌธ ๋ณ€์ˆ˜๋“ค (i, j, k ๋“ฑ) + if (variableNames.some(name => ['i', 'j', 'k'].includes(name))) { + return 'variables'; + } + + return null; + } + + /** + * ๐ŸŽฏ ๋‹จ๊ณ„๋ณ„ ์„ธ๋ถ€ ์ •๋ณด ๋ถ„์„ + * @param {Object} data - JSON ๋ฐ์ดํ„ฐ + * @returns {Object} ๋ถ„์„ ๊ฒฐ๊ณผ + */ + static analyzeSteps(data) { + const steps = data?.steps || []; + + return { + totalSteps: steps.length, + hasLoops: steps.some(step => step.loop), + hasConditions: steps.some(step => step.condition), + hasRecursion: steps.some(step => step.stack && step.stack.length > 0), + hasArrayOperations: steps.some(step => + step.changes?.some(change => + change.variable?.toLowerCase().includes('list') || + change.variable?.toLowerCase().includes('array') + ) + ), + complexityLevel: this.calculateComplexity(steps) + }; + } + + /** + * ๐Ÿ“Š ๋ณต์žก๋„ ๊ณ„์‚ฐ + * @param {Array} steps - ์‹คํ–‰ ๋‹จ๊ณ„ ๋ฐฐ์—ด + * @returns {string} ๋ณต์žก๋„ ๋ ˆ๋ฒจ + */ + static calculateComplexity(steps) { + if (steps.length === 0) return 'unknown'; + if (steps.length < 5) return 'simple'; + if (steps.length < 20) return 'medium'; + if (steps.length < 50) return 'complex'; + return 'very-complex'; + } + + /** + * ๐Ÿ” ๊ฐ์ง€ ๊ฒฐ๊ณผ ์ƒ์„ธ ์ •๋ณด + * @param {Object} data - JSON ๋ฐ์ดํ„ฐ + * @returns {Object} ์ƒ์„ธ ๋ถ„์„ ๊ฒฐ๊ณผ + */ + static getDetectionDetails(data) { + const detectedType = this.detectAnimationType(data); + const stepsAnalysis = this.analyzeSteps(data); + + return { + detectedType, + confidence: this.calculateConfidence(data, detectedType), + stepsAnalysis, + dataStructure: data?.dataStructure, + functions: data?.functions || [], + variables: data?.variables || [], + recommendations: this.getRecommendations(detectedType, stepsAnalysis) + }; + } + + /** + * ๐Ÿ“ˆ ๊ฐ์ง€ ์‹ ๋ขฐ๋„ ๊ณ„์‚ฐ + * @param {Object} data - JSON ๋ฐ์ดํ„ฐ + * @param {string} detectedType - ๊ฐ์ง€๋œ ํƒ€์ž… + * @returns {number} ์‹ ๋ขฐ๋„ (0-100) + */ + static calculateConfidence(data, detectedType) { + let confidence = 50; // ๊ธฐ๋ณธ๊ฐ’ + + // ๋ฐ์ดํ„ฐ ๊ตฌ์กฐ๊ฐ€ ๋ช…ํ™•ํ•˜๋ฉด +30 + if (data?.dataStructure?.type) confidence += 30; + + // ํ•จ์ˆ˜๋ช…์ด ๋ช…ํ™•ํ•˜๋ฉด +20 + if (data?.functions?.[0]?.name) confidence += 20; + + // ์ฝ”๋“œ๊ฐ€ ์žˆ์œผ๋ฉด +10 + if (data?.code) confidence += 10; + + // ๋‹จ๊ณ„๊ฐ€ ์ถฉ๋ถ„ํ•˜๋ฉด +10 + if (data?.steps?.length > 5) confidence += 10; + + return Math.min(100, confidence); + } + + /** + * ๐Ÿ’ก ๊ฐœ์„  ๊ถŒ์žฅ์‚ฌํ•ญ + * @param {string} detectedType - ๊ฐ์ง€๋œ ํƒ€์ž… + * @param {Object} stepsAnalysis - ๋‹จ๊ณ„ ๋ถ„์„ ๊ฒฐ๊ณผ + * @returns {Array} ๊ถŒ์žฅ์‚ฌํ•ญ ๋ฐฐ์—ด + */ + static getRecommendations(detectedType, stepsAnalysis) { + const recommendations = []; + + if (stepsAnalysis.complexityLevel === 'very-complex') { + recommendations.push('๋ณต์žก๋„๊ฐ€ ๋†’์Šต๋‹ˆ๋‹ค. ๋‹จ๊ณ„๋ฅผ ๋‚˜๋ˆ„์–ด ๋ณด๋Š” ๊ฒƒ์„ ๊ถŒ์žฅํ•ฉ๋‹ˆ๋‹ค.'); + } + + if (!stepsAnalysis.hasLoops && !stepsAnalysis.hasRecursion) { + recommendations.push('๋ฐ˜๋ณต๋ฌธ์ด๋‚˜ ์žฌ๊ท€๊ฐ€ ์—†๋Š” ๋‹จ์ˆœํ•œ ์•Œ๊ณ ๋ฆฌ์ฆ˜์ž…๋‹ˆ๋‹ค.'); + } + + if (detectedType === 'variables') { + recommendations.push('๊ธฐ๋ณธ ๋ณ€์ˆ˜ ์ถ”์  ์• ๋‹ˆ๋ฉ”์ด์…˜์ด ์ ์šฉ๋ฉ๋‹ˆ๋‹ค.'); + } + + return recommendations; + } +} + +// ํŽธ์˜ ํ•จ์ˆ˜๋“ค +export const detectAnimationType = (data) => + AnimationDetector.detectAnimationType(data); + +export const getDetectionDetails = (data) => + AnimationDetector.getDetectionDetails(data); + +// ๊ธฐ๋ณธ export +export default AnimationDetector; + +// ๊ฐœ๋ฐœ ์ƒํƒœ ๋กœ๊ทธ +console.log('๐Ÿ” AnimationDetector (๊ฐœ๋ฐœ ์ค‘) ๋กœ๋“œ๋จ'); \ No newline at end of file diff --git a/src/components/ide/utils/AnimationUtils.js b/src/components/ide/utils/AnimationUtils.js new file mode 100644 index 0000000..e69de29