Remove HTML Preview Tool from navigation and add to gitignore
- Removed HTML Preview Tool from navigation menu in Layout.js - Cleaned up unused Code import - Added HTML Preview related files to .gitignore - Project builds successfully without HTML Preview Tool
This commit is contained in:
12
.gitignore
vendored
12
.gitignore
vendored
@@ -8,6 +8,7 @@ node_modules/
|
|||||||
|
|
||||||
# Production
|
# Production
|
||||||
/build
|
/build
|
||||||
|
/backup
|
||||||
|
|
||||||
# Misc
|
# Misc
|
||||||
.DS_Store
|
.DS_Store
|
||||||
@@ -21,6 +22,17 @@ npm-debug.log*
|
|||||||
yarn-debug.log*
|
yarn-debug.log*
|
||||||
yarn-error.log*
|
yarn-error.log*
|
||||||
|
|
||||||
|
# HTML Preview Tool (abandoned)
|
||||||
|
src/pages/HtmlPreviewTool.js
|
||||||
|
src/pages/HtmlPreviewTool.js.backup
|
||||||
|
src/pages/components/PreviewFrame*.js
|
||||||
|
src/pages/components/ElementEditor.js
|
||||||
|
src/pages/components/InspectorSidebar.js
|
||||||
|
src/pages/components/Toolbar.js
|
||||||
|
src/pages/components/CodeInputs.js
|
||||||
|
src/pages/components/SimplePreviewFrame.js
|
||||||
|
src/pages/components/PreviewServer.js
|
||||||
|
|
||||||
# Runtime data
|
# Runtime data
|
||||||
pids
|
pids
|
||||||
*.pid
|
*.pid
|
||||||
|
|||||||
322
package-lock.json
generated
322
package-lock.json
generated
@@ -8,14 +8,23 @@
|
|||||||
"name": "developer-tools-mvp",
|
"name": "developer-tools-mvp",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@codemirror/commands": "^6.8.1",
|
||||||
|
"@codemirror/lang-css": "^6.3.1",
|
||||||
|
"@codemirror/lang-html": "^6.4.9",
|
||||||
|
"@codemirror/lang-javascript": "^6.2.4",
|
||||||
|
"@codemirror/search": "^6.5.11",
|
||||||
|
"@codemirror/theme-one-dark": "^6.1.3",
|
||||||
"@testing-library/jest-dom": "^5.16.4",
|
"@testing-library/jest-dom": "^5.16.4",
|
||||||
"@testing-library/react": "^13.3.0",
|
"@testing-library/react": "^13.3.0",
|
||||||
"@testing-library/user-event": "^13.5.0",
|
"@testing-library/user-event": "^13.5.0",
|
||||||
|
"@uiw/react-codemirror": "^4.24.2",
|
||||||
|
"codemirror": "^5.65.19",
|
||||||
"diff-match-patch": "^1.0.5",
|
"diff-match-patch": "^1.0.5",
|
||||||
"js-beautify": "^1.14.9",
|
"js-beautify": "^1.15.4",
|
||||||
"lucide-react": "^0.263.1",
|
"lucide-react": "^0.263.1",
|
||||||
"papaparse": "^5.4.1",
|
"papaparse": "^5.4.1",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
|
"react-codemirror2": "^8.0.1",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-router-dom": "^6.3.0",
|
"react-router-dom": "^6.3.0",
|
||||||
"react-scripts": "5.0.1",
|
"react-scripts": "5.0.1",
|
||||||
@@ -2112,6 +2121,144 @@
|
|||||||
"integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==",
|
"integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/@codemirror/autocomplete": {
|
||||||
|
"version": "6.18.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.18.6.tgz",
|
||||||
|
"integrity": "sha512-PHHBXFomUs5DF+9tCOM/UoW6XQ4R44lLNNhRaW9PKPTU0D7lIjRg3ElxaJnTwsl/oHiR93WSXDBrekhoUGCPtg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@codemirror/language": "^6.0.0",
|
||||||
|
"@codemirror/state": "^6.0.0",
|
||||||
|
"@codemirror/view": "^6.17.0",
|
||||||
|
"@lezer/common": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@codemirror/commands": {
|
||||||
|
"version": "6.8.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.8.1.tgz",
|
||||||
|
"integrity": "sha512-KlGVYufHMQzxbdQONiLyGQDUW0itrLZwq3CcY7xpv9ZLRHqzkBSoteocBHtMCoY7/Ci4xhzSrToIeLg7FxHuaw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@codemirror/language": "^6.0.0",
|
||||||
|
"@codemirror/state": "^6.4.0",
|
||||||
|
"@codemirror/view": "^6.27.0",
|
||||||
|
"@lezer/common": "^1.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@codemirror/lang-css": {
|
||||||
|
"version": "6.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@codemirror/lang-css/-/lang-css-6.3.1.tgz",
|
||||||
|
"integrity": "sha512-kr5fwBGiGtmz6l0LSJIbno9QrifNMUusivHbnA1H6Dmqy4HZFte3UAICix1VuKo0lMPKQr2rqB+0BkKi/S3Ejg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@codemirror/autocomplete": "^6.0.0",
|
||||||
|
"@codemirror/language": "^6.0.0",
|
||||||
|
"@codemirror/state": "^6.0.0",
|
||||||
|
"@lezer/common": "^1.0.2",
|
||||||
|
"@lezer/css": "^1.1.7"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@codemirror/lang-html": {
|
||||||
|
"version": "6.4.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/@codemirror/lang-html/-/lang-html-6.4.9.tgz",
|
||||||
|
"integrity": "sha512-aQv37pIMSlueybId/2PVSP6NPnmurFDVmZwzc7jszd2KAF8qd4VBbvNYPXWQq90WIARjsdVkPbw29pszmHws3Q==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@codemirror/autocomplete": "^6.0.0",
|
||||||
|
"@codemirror/lang-css": "^6.0.0",
|
||||||
|
"@codemirror/lang-javascript": "^6.0.0",
|
||||||
|
"@codemirror/language": "^6.4.0",
|
||||||
|
"@codemirror/state": "^6.0.0",
|
||||||
|
"@codemirror/view": "^6.17.0",
|
||||||
|
"@lezer/common": "^1.0.0",
|
||||||
|
"@lezer/css": "^1.1.0",
|
||||||
|
"@lezer/html": "^1.3.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@codemirror/lang-javascript": {
|
||||||
|
"version": "6.2.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@codemirror/lang-javascript/-/lang-javascript-6.2.4.tgz",
|
||||||
|
"integrity": "sha512-0WVmhp1QOqZ4Rt6GlVGwKJN3KW7Xh4H2q8ZZNGZaP6lRdxXJzmjm4FqvmOojVj6khWJHIb9sp7U/72W7xQgqAA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@codemirror/autocomplete": "^6.0.0",
|
||||||
|
"@codemirror/language": "^6.6.0",
|
||||||
|
"@codemirror/lint": "^6.0.0",
|
||||||
|
"@codemirror/state": "^6.0.0",
|
||||||
|
"@codemirror/view": "^6.17.0",
|
||||||
|
"@lezer/common": "^1.0.0",
|
||||||
|
"@lezer/javascript": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@codemirror/language": {
|
||||||
|
"version": "6.11.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.11.2.tgz",
|
||||||
|
"integrity": "sha512-p44TsNArL4IVXDTbapUmEkAlvWs2CFQbcfc0ymDsis1kH2wh0gcY96AS29c/vp2d0y2Tquk1EDSaawpzilUiAw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@codemirror/state": "^6.0.0",
|
||||||
|
"@codemirror/view": "^6.23.0",
|
||||||
|
"@lezer/common": "^1.1.0",
|
||||||
|
"@lezer/highlight": "^1.0.0",
|
||||||
|
"@lezer/lr": "^1.0.0",
|
||||||
|
"style-mod": "^4.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@codemirror/lint": {
|
||||||
|
"version": "6.8.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.8.5.tgz",
|
||||||
|
"integrity": "sha512-s3n3KisH7dx3vsoeGMxsbRAgKe4O1vbrnKBClm99PU0fWxmxsx5rR2PfqQgIt+2MMJBHbiJ5rfIdLYfB9NNvsA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@codemirror/state": "^6.0.0",
|
||||||
|
"@codemirror/view": "^6.35.0",
|
||||||
|
"crelt": "^1.0.5"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@codemirror/search": {
|
||||||
|
"version": "6.5.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/@codemirror/search/-/search-6.5.11.tgz",
|
||||||
|
"integrity": "sha512-KmWepDE6jUdL6n8cAAqIpRmLPBZ5ZKnicE8oGU/s3QrAVID+0VhLFrzUucVKHG5035/BSykhExDL/Xm7dHthiA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@codemirror/state": "^6.0.0",
|
||||||
|
"@codemirror/view": "^6.0.0",
|
||||||
|
"crelt": "^1.0.5"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@codemirror/state": {
|
||||||
|
"version": "6.5.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.5.2.tgz",
|
||||||
|
"integrity": "sha512-FVqsPqtPWKVVL3dPSxy8wEF/ymIEuVzF1PK3VbUgrxXpJUSHQWWZz4JMToquRxnkw+36LTamCZG2iua2Ptq0fA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@marijn/find-cluster-break": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@codemirror/theme-one-dark": {
|
||||||
|
"version": "6.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@codemirror/theme-one-dark/-/theme-one-dark-6.1.3.tgz",
|
||||||
|
"integrity": "sha512-NzBdIvEJmx6fjeremiGp3t/okrLPYT0d9orIc7AFun8oZcRk58aejkqhv6spnz4MLAevrKNPMQYXEWMg4s+sKA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@codemirror/language": "^6.0.0",
|
||||||
|
"@codemirror/state": "^6.0.0",
|
||||||
|
"@codemirror/view": "^6.0.0",
|
||||||
|
"@lezer/highlight": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@codemirror/view": {
|
||||||
|
"version": "6.38.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.38.1.tgz",
|
||||||
|
"integrity": "sha512-RmTOkE7hRU3OVREqFVITWHz6ocgBjv08GoePscAakgVQfciA3SGCEk7mb9IzwW61cKKmlTpHXG6DUE5Ubx+MGQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@codemirror/state": "^6.5.0",
|
||||||
|
"crelt": "^1.0.6",
|
||||||
|
"style-mod": "^4.1.0",
|
||||||
|
"w3c-keyname": "^2.2.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@csstools/normalize.css": {
|
"node_modules/@csstools/normalize.css": {
|
||||||
"version": "12.1.1",
|
"version": "12.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/@csstools/normalize.css/-/normalize.css-12.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/@csstools/normalize.css/-/normalize.css-12.1.1.tgz",
|
||||||
@@ -3774,6 +3921,69 @@
|
|||||||
"integrity": "sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==",
|
"integrity": "sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/@lezer/common": {
|
||||||
|
"version": "1.2.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.2.3.tgz",
|
||||||
|
"integrity": "sha512-w7ojc8ejBqr2REPsWxJjrMFsA/ysDCFICn8zEOR9mrqzOu2amhITYuLD8ag6XZf0CFXDrhKqw7+tW8cX66NaDA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/@lezer/css": {
|
||||||
|
"version": "1.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@lezer/css/-/css-1.3.0.tgz",
|
||||||
|
"integrity": "sha512-pBL7hup88KbI7hXnZV3PQsn43DHy6TWyzuyk2AO9UyoXcDltvIdqWKE1dLL/45JVZ+YZkHe1WVHqO6wugZZWcw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@lezer/common": "^1.2.0",
|
||||||
|
"@lezer/highlight": "^1.0.0",
|
||||||
|
"@lezer/lr": "^1.3.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@lezer/highlight": {
|
||||||
|
"version": "1.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.2.1.tgz",
|
||||||
|
"integrity": "sha512-Z5duk4RN/3zuVO7Jq0pGLJ3qynpxUVsh7IbUbGj88+uV2ApSAn6kWg2au3iJb+0Zi7kKtqffIESgNcRXWZWmSA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@lezer/common": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@lezer/html": {
|
||||||
|
"version": "1.3.10",
|
||||||
|
"resolved": "https://registry.npmjs.org/@lezer/html/-/html-1.3.10.tgz",
|
||||||
|
"integrity": "sha512-dqpT8nISx/p9Do3AchvYGV3qYc4/rKr3IBZxlHmpIKam56P47RSHkSF5f13Vu9hebS1jM0HmtJIwLbWz1VIY6w==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@lezer/common": "^1.2.0",
|
||||||
|
"@lezer/highlight": "^1.0.0",
|
||||||
|
"@lezer/lr": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@lezer/javascript": {
|
||||||
|
"version": "1.5.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@lezer/javascript/-/javascript-1.5.1.tgz",
|
||||||
|
"integrity": "sha512-ATOImjeVJuvgm3JQ/bpo2Tmv55HSScE2MTPnKRMRIPx2cLhHGyX2VnqpHhtIV1tVzIjZDbcWQm+NCTF40ggZVw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@lezer/common": "^1.2.0",
|
||||||
|
"@lezer/highlight": "^1.1.3",
|
||||||
|
"@lezer/lr": "^1.3.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@lezer/lr": {
|
||||||
|
"version": "1.4.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.4.2.tgz",
|
||||||
|
"integrity": "sha512-pu0K1jCIdnQ12aWNaAVU5bzi7Bd1w54J3ECgANPmYLtQKP0HBj2cE/5coBD66MT10xbtIuUr7tg0Shbsvk0mDA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@lezer/common": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@marijn/find-cluster-break": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@marijn/find-cluster-break/-/find-cluster-break-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@nicolo-ribaudo/eslint-scope-5-internals": {
|
"node_modules/@nicolo-ribaudo/eslint-scope-5-internals": {
|
||||||
"version": "5.1.1-v1",
|
"version": "5.1.1-v1",
|
||||||
"resolved": "https://registry.npmjs.org/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz",
|
"resolved": "https://registry.npmjs.org/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz",
|
||||||
@@ -5081,6 +5291,74 @@
|
|||||||
"url": "https://opencollective.com/typescript-eslint"
|
"url": "https://opencollective.com/typescript-eslint"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@uiw/codemirror-extensions-basic-setup": {
|
||||||
|
"version": "4.24.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@uiw/codemirror-extensions-basic-setup/-/codemirror-extensions-basic-setup-4.24.2.tgz",
|
||||||
|
"integrity": "sha512-wW/gjLRvVUeYyhdh2TApn25cvdcR+Rhg6R/j3eTOvXQzU1HNzNYCVH4YKVIfgtfdM/Xs+N8fkk+rbr1YvBppCg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@codemirror/autocomplete": "^6.0.0",
|
||||||
|
"@codemirror/commands": "^6.0.0",
|
||||||
|
"@codemirror/language": "^6.0.0",
|
||||||
|
"@codemirror/lint": "^6.0.0",
|
||||||
|
"@codemirror/search": "^6.0.0",
|
||||||
|
"@codemirror/state": "^6.0.0",
|
||||||
|
"@codemirror/view": "^6.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://jaywcjlove.github.io/#/sponsor"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@codemirror/autocomplete": ">=6.0.0",
|
||||||
|
"@codemirror/commands": ">=6.0.0",
|
||||||
|
"@codemirror/language": ">=6.0.0",
|
||||||
|
"@codemirror/lint": ">=6.0.0",
|
||||||
|
"@codemirror/search": ">=6.0.0",
|
||||||
|
"@codemirror/state": ">=6.0.0",
|
||||||
|
"@codemirror/view": ">=6.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@uiw/react-codemirror": {
|
||||||
|
"version": "4.24.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@uiw/react-codemirror/-/react-codemirror-4.24.2.tgz",
|
||||||
|
"integrity": "sha512-kp7DhTq4RR+M2zJBQBrHn1dIkBrtOskcwJX4vVsKGByReOvfMrhqRkGTxYMRDTX6x75EG2mvBJPDKYcUQcHWBw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.18.6",
|
||||||
|
"@codemirror/commands": "^6.1.0",
|
||||||
|
"@codemirror/state": "^6.1.1",
|
||||||
|
"@codemirror/theme-one-dark": "^6.0.0",
|
||||||
|
"@uiw/codemirror-extensions-basic-setup": "4.24.2",
|
||||||
|
"codemirror": "^6.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://jaywcjlove.github.io/#/sponsor"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@babel/runtime": ">=7.11.0",
|
||||||
|
"@codemirror/state": ">=6.0.0",
|
||||||
|
"@codemirror/theme-one-dark": ">=6.0.0",
|
||||||
|
"@codemirror/view": ">=6.0.0",
|
||||||
|
"codemirror": ">=6.0.0",
|
||||||
|
"react": ">=17.0.0",
|
||||||
|
"react-dom": ">=17.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@uiw/react-codemirror/node_modules/codemirror": {
|
||||||
|
"version": "6.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/codemirror/-/codemirror-6.0.2.tgz",
|
||||||
|
"integrity": "sha512-VhydHotNW5w1UGK0Qj96BwSk/Zqbp9WbnyK2W/eVMv4QyF41INRGpjUhFJY7/uDNuudSc33a/PKr4iDqRduvHw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@codemirror/autocomplete": "^6.0.0",
|
||||||
|
"@codemirror/commands": "^6.0.0",
|
||||||
|
"@codemirror/language": "^6.0.0",
|
||||||
|
"@codemirror/lint": "^6.0.0",
|
||||||
|
"@codemirror/search": "^6.0.0",
|
||||||
|
"@codemirror/state": "^6.0.0",
|
||||||
|
"@codemirror/view": "^6.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@ungap/structured-clone": {
|
"node_modules/@ungap/structured-clone": {
|
||||||
"version": "1.3.0",
|
"version": "1.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz",
|
||||||
@@ -6786,6 +7064,12 @@
|
|||||||
"node": ">=4"
|
"node": ">=4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/codemirror": {
|
||||||
|
"version": "5.65.19",
|
||||||
|
"resolved": "https://registry.npmjs.org/codemirror/-/codemirror-5.65.19.tgz",
|
||||||
|
"integrity": "sha512-+aFkvqhaAVr1gferNMuN8vkTSrWIFvzlMV9I2KBLCWS2WpZ2+UAkZjlMZmEuT+gcXTi6RrGQCkWq1/bDtGqhIA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/collect-v8-coverage": {
|
"node_modules/collect-v8-coverage": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz",
|
||||||
@@ -7033,6 +7317,12 @@
|
|||||||
"node": ">=10"
|
"node": ">=10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/crelt": {
|
||||||
|
"version": "1.0.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz",
|
||||||
|
"integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/cross-spawn": {
|
"node_modules/cross-spawn": {
|
||||||
"version": "7.0.6",
|
"version": "7.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
|
||||||
@@ -17075,6 +17365,16 @@
|
|||||||
"node": ">=14"
|
"node": ">=14"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/react-codemirror2": {
|
||||||
|
"version": "8.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-codemirror2/-/react-codemirror2-8.0.1.tgz",
|
||||||
|
"integrity": "sha512-ZALowE5sGK1t66i0Fm1hoJLWT/iZHNjaAmcjwgYl9gyl2v1sqWwxzWHLmgq6K9KCk7p5+I+U3jMF1vsLaIkrmA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"codemirror": "5.x",
|
||||||
|
"react": ">=15.5 <=18.x"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/react-dev-utils": {
|
"node_modules/react-dev-utils": {
|
||||||
"version": "12.0.1",
|
"version": "12.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-12.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-12.0.1.tgz",
|
||||||
@@ -19048,6 +19348,12 @@
|
|||||||
"webpack": "^5.0.0"
|
"webpack": "^5.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/style-mod": {
|
||||||
|
"version": "4.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.2.tgz",
|
||||||
|
"integrity": "sha512-wnD1HyVqpJUI2+eKZ+eo1UwghftP6yuFheBqqe+bWCotBjC2K1YnteJILRMs3SM4V/0dLEW1SC27MWP5y+mwmw==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/stylehacks": {
|
"node_modules/stylehacks": {
|
||||||
"version": "5.1.1",
|
"version": "5.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-5.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-5.1.1.tgz",
|
||||||
@@ -19834,9 +20140,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/typescript": {
|
"node_modules/typescript": {
|
||||||
"version": "5.9.2",
|
"version": "4.9.5",
|
||||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.2.tgz",
|
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz",
|
||||||
"integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==",
|
"integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"peer": true,
|
"peer": true,
|
||||||
"bin": {
|
"bin": {
|
||||||
@@ -19844,7 +20150,7 @@
|
|||||||
"tsserver": "bin/tsserver"
|
"tsserver": "bin/tsserver"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=14.17"
|
"node": ">=4.2.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/unbox-primitive": {
|
"node_modules/unbox-primitive": {
|
||||||
@@ -20096,6 +20402,12 @@
|
|||||||
"browser-process-hrtime": "^1.0.0"
|
"browser-process-hrtime": "^1.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/w3c-keyname": {
|
||||||
|
"version": "2.2.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz",
|
||||||
|
"integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/w3c-xmlserializer": {
|
"node_modules/w3c-xmlserializer": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-2.0.0.tgz",
|
||||||
|
|||||||
21
package.json
21
package.json
@@ -4,24 +4,33 @@
|
|||||||
"description": "Web Developer Tools MVP - Utilities Toolkit",
|
"description": "Web Developer Tools MVP - Utilities Toolkit",
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@codemirror/commands": "^6.8.1",
|
||||||
|
"@codemirror/lang-css": "^6.3.1",
|
||||||
|
"@codemirror/lang-html": "^6.4.9",
|
||||||
|
"@codemirror/lang-javascript": "^6.2.4",
|
||||||
|
"@codemirror/search": "^6.5.11",
|
||||||
|
"@codemirror/theme-one-dark": "^6.1.3",
|
||||||
"@testing-library/jest-dom": "^5.16.4",
|
"@testing-library/jest-dom": "^5.16.4",
|
||||||
"@testing-library/react": "^13.3.0",
|
"@testing-library/react": "^13.3.0",
|
||||||
"@testing-library/user-event": "^13.5.0",
|
"@testing-library/user-event": "^13.5.0",
|
||||||
|
"@uiw/react-codemirror": "^4.24.2",
|
||||||
|
"codemirror": "^5.65.19",
|
||||||
|
"diff-match-patch": "^1.0.5",
|
||||||
|
"js-beautify": "^1.15.4",
|
||||||
|
"lucide-react": "^0.263.1",
|
||||||
|
"papaparse": "^5.4.1",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
|
"react-codemirror2": "^8.0.1",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-router-dom": "^6.3.0",
|
"react-router-dom": "^6.3.0",
|
||||||
"react-scripts": "5.0.1",
|
"react-scripts": "5.0.1",
|
||||||
"js-beautify": "^1.14.9",
|
|
||||||
"diff-match-patch": "^1.0.5",
|
|
||||||
"papaparse": "^5.4.1",
|
|
||||||
"serialize-javascript": "^6.0.0",
|
"serialize-javascript": "^6.0.0",
|
||||||
"lucide-react": "^0.263.1",
|
|
||||||
"web-vitals": "^2.1.4"
|
"web-vitals": "^2.1.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"tailwindcss": "^3.3.0",
|
|
||||||
"autoprefixer": "^10.4.14",
|
"autoprefixer": "^10.4.14",
|
||||||
"postcss": "^8.4.24"
|
"postcss": "^8.4.24",
|
||||||
|
"tailwindcss": "^3.3.0"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "react-scripts start",
|
"start": "react-scripts start",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import React, { useState, useEffect, useRef } from 'react';
|
import React, { useState, useEffect, useRef } from 'react';
|
||||||
import { Link, useLocation } from 'react-router-dom';
|
import { Link, useLocation } from 'react-router-dom';
|
||||||
import { Code2, Home, ChevronDown, Menu, X, Database, FileText, Link as LinkIcon, Hash, FileSpreadsheet, Wand2, GitCompare, Code } from 'lucide-react';
|
import { Home, Hash, FileText, Key, Palette, QrCode, FileSpreadsheet, Wand2, GitCompare, Menu, X } from 'lucide-react';
|
||||||
import ThemeToggle from './ThemeToggle';
|
import ThemeToggle from './ThemeToggle';
|
||||||
|
|
||||||
const Layout = ({ children }) => {
|
const Layout = ({ children }) => {
|
||||||
@@ -41,7 +41,7 @@ const Layout = ({ children }) => {
|
|||||||
{ path: '/csv-json', name: 'CSV/JSON Tool', icon: FileSpreadsheet, description: 'Convert CSV ↔ JSON' },
|
{ path: '/csv-json', name: 'CSV/JSON Tool', icon: FileSpreadsheet, description: 'Convert CSV ↔ JSON' },
|
||||||
{ path: '/beautifier', name: 'Beautifier Tool', icon: Wand2, description: 'Beautify/minify code' },
|
{ path: '/beautifier', name: 'Beautifier Tool', icon: Wand2, description: 'Beautify/minify code' },
|
||||||
{ path: '/diff', name: 'Diff Tool', icon: GitCompare, description: 'Compare text differences' },
|
{ path: '/diff', name: 'Diff Tool', icon: GitCompare, description: 'Compare text differences' },
|
||||||
{ path: '/html-preview', name: 'HTML Preview', icon: Code, description: 'Render HTML, CSS, JS' }
|
|
||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -316,7 +316,7 @@ const HtmlPreviewTool = () => {
|
|||||||
<div className="p-4 border-b border-gray-200 dark:border-gray-700">
|
<div className="p-4 border-b border-gray-200 dark:border-gray-700">
|
||||||
<h2 className="text-lg font-semibold text-gray-900 dark:text-gray-100">Code Editor</h2>
|
<h2 className="text-lg font-semibold text-gray-900 dark:text-gray-100">Code Editor</h2>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-1 p-4 overflow-y-auto">
|
<div className="flex-1 flex flex-col p-4">
|
||||||
<CodeInputs
|
<CodeInputs
|
||||||
htmlInput={htmlInput}
|
htmlInput={htmlInput}
|
||||||
setHtmlInput={setHtmlInput}
|
setHtmlInput={setHtmlInput}
|
||||||
@@ -381,7 +381,7 @@ const HtmlPreviewTool = () => {
|
|||||||
<ToolLayout title="HTML Preview Tool">
|
<ToolLayout title="HTML Preview Tool">
|
||||||
<div className={`flex h-full ${inspectedElementInfo ? 'gap-4' : 'gap-6'}`}>
|
<div className={`flex h-full ${inspectedElementInfo ? 'gap-4' : 'gap-6'}`}>
|
||||||
{/* Left column - Code inputs */}
|
{/* Left column - Code inputs */}
|
||||||
<div className={`space-y-6 transition-all duration-300 ${
|
<div className={`flex flex-col transition-all duration-300 ${
|
||||||
inspectedElementInfo ? 'flex-1' : 'w-1/2'
|
inspectedElementInfo ? 'flex-1' : 'w-1/2'
|
||||||
}`}>
|
}`}>
|
||||||
<CodeInputs
|
<CodeInputs
|
||||||
|
|||||||
@@ -1,5 +1,15 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState, useRef } from 'react';
|
||||||
import { ChevronDown, ChevronUp, Maximize2, Minimize2 } from 'lucide-react';
|
import { Search, Copy, Download } from 'lucide-react';
|
||||||
|
import { Controlled as CodeMirror } from 'react-codemirror2';
|
||||||
|
import 'codemirror/lib/codemirror.css';
|
||||||
|
import 'codemirror/theme/material.css';
|
||||||
|
import 'codemirror/mode/xml/xml';
|
||||||
|
import 'codemirror/mode/css/css';
|
||||||
|
import 'codemirror/mode/javascript/javascript';
|
||||||
|
import 'codemirror/addon/search/search';
|
||||||
|
import 'codemirror/addon/search/searchcursor';
|
||||||
|
import 'codemirror/addon/dialog/dialog';
|
||||||
|
import 'codemirror/addon/dialog/dialog.css';
|
||||||
|
|
||||||
const CodeInputs = ({
|
const CodeInputs = ({
|
||||||
htmlInput,
|
htmlInput,
|
||||||
@@ -10,129 +20,192 @@ const CodeInputs = ({
|
|||||||
setJsInput,
|
setJsInput,
|
||||||
isFullscreen
|
isFullscreen
|
||||||
}) => {
|
}) => {
|
||||||
const [showCss, setShowCss] = useState(false);
|
const [activeTab, setActiveTab] = useState('html');
|
||||||
const [showJs, setShowJs] = useState(false);
|
const htmlEditorRef = useRef(null);
|
||||||
const [htmlExtended, setHtmlExtended] = useState(true); // Default: HTML box extended
|
const cssEditorRef = useRef(null);
|
||||||
const [cssExtended, setCssExtended] = useState(false);
|
const jsEditorRef = useRef(null);
|
||||||
const [jsExtended, setJsExtended] = useState(false);
|
|
||||||
|
|
||||||
const getTextareaHeight = (type, isExtended) => {
|
// Handle search functionality
|
||||||
if (isFullscreen) {
|
const handleSearch = (editorRef) => {
|
||||||
// In fullscreen, give HTML much more space as main script area
|
if (editorRef.current && editorRef.current.editor) {
|
||||||
if (type === 'html') {
|
editorRef.current.editor.execCommand('find');
|
||||||
return isExtended ? 'h-[32rem]' : 'h-96'; // Much taller for HTML in fullscreen to balance with iPhone frame
|
|
||||||
} else {
|
|
||||||
return isExtended ? 'h-64' : 'h-32'; // CSS/JS remain smaller
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// In non-fullscreen, give much more space for HTML as main script area
|
// Handle copy functionality
|
||||||
if (type === 'html') {
|
const handleCopy = async (content) => {
|
||||||
if (!showCss && !showJs) {
|
try {
|
||||||
// When CSS/JS hidden, HTML gets maximum space
|
await navigator.clipboard.writeText(content);
|
||||||
return isExtended ? 'h-[36rem]' : 'h-[28rem]'; // Much taller for main script to balance with iPhone frame
|
} catch (err) {
|
||||||
} else {
|
console.error('Failed to copy:', err);
|
||||||
// When CSS/JS visible, HTML still gets substantial space
|
}
|
||||||
return isExtended ? 'h-[28rem]' : 'h-96';
|
};
|
||||||
}
|
|
||||||
} else {
|
// Handle export functionality
|
||||||
// CSS and JS boxes remain smaller
|
const handleExport = (content, filename) => {
|
||||||
return isExtended ? 'h-64' : 'h-32';
|
const blob = new Blob([content], { type: 'text/plain' });
|
||||||
|
const url = URL.createObjectURL(blob);
|
||||||
|
const a = document.createElement('a');
|
||||||
|
a.href = url;
|
||||||
|
a.download = filename;
|
||||||
|
document.body.appendChild(a);
|
||||||
|
a.click();
|
||||||
|
document.body.removeChild(a);
|
||||||
|
URL.revokeObjectURL(url);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Get current editor ref based on active tab
|
||||||
|
const getCurrentEditorRef = () => {
|
||||||
|
switch (activeTab) {
|
||||||
|
case 'html': return htmlEditorRef;
|
||||||
|
case 'css': return cssEditorRef;
|
||||||
|
case 'js': return jsEditorRef;
|
||||||
|
default: return htmlEditorRef;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Get current content based on active tab
|
||||||
|
const getCurrentContent = () => {
|
||||||
|
switch (activeTab) {
|
||||||
|
case 'html': return htmlInput;
|
||||||
|
case 'css': return cssInput;
|
||||||
|
case 'js': return jsInput;
|
||||||
|
default: return htmlInput;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Get filename for export based on active tab
|
||||||
|
const getExportFilename = () => {
|
||||||
|
switch (activeTab) {
|
||||||
|
case 'html': return 'code.html';
|
||||||
|
case 'css': return 'styles.css';
|
||||||
|
case 'js': return 'script.js';
|
||||||
|
default: return 'code.txt';
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-4">
|
<div className="flex flex-col h-full">
|
||||||
{/* HTML Input */}
|
{/* Tab Navigation */}
|
||||||
<div className="space-y-2">
|
<div className="flex border-b border-gray-200 dark:border-gray-700">
|
||||||
<div className="flex items-center justify-between">
|
{[
|
||||||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
{ id: 'html', label: 'HTML' },
|
||||||
HTML
|
{ id: 'css', label: 'CSS' },
|
||||||
</label>
|
{ id: 'js', label: 'JavaScript' }
|
||||||
|
].map((tab) => (
|
||||||
<button
|
<button
|
||||||
onClick={() => setHtmlExtended(!htmlExtended)}
|
key={tab.id}
|
||||||
className="flex items-center space-x-1 px-2 py-1 text-xs bg-gray-100 dark:bg-gray-700 text-gray-600 dark:text-gray-400 rounded-md hover:bg-gray-200 dark:hover:bg-gray-600 transition-colors"
|
onClick={() => setActiveTab(tab.id)}
|
||||||
title={htmlExtended ? 'Minimize HTML box' : 'Maximize HTML box'}
|
className={`px-4 py-2 text-sm font-medium border-b-2 transition-colors ${
|
||||||
|
activeTab === tab.id
|
||||||
|
? 'border-blue-500 text-blue-600 dark:text-blue-400'
|
||||||
|
: 'border-transparent text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-300'
|
||||||
|
}`}
|
||||||
>
|
>
|
||||||
{htmlExtended ? <Minimize2 className="w-3 h-3" /> : <Maximize2 className="w-3 h-3" />}
|
{tab.label}
|
||||||
<span>{htmlExtended ? 'Min' : 'Max'}</span>
|
|
||||||
</button>
|
</button>
|
||||||
</div>
|
))}
|
||||||
<textarea
|
|
||||||
value={htmlInput}
|
|
||||||
onChange={(e) => setHtmlInput(e.target.value)}
|
|
||||||
placeholder="Enter your HTML code here..."
|
|
||||||
className={`w-full ${getTextareaHeight('html', htmlExtended)} p-3 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 font-mono text-sm resize-none focus:ring-2 focus:ring-blue-500 focus:border-transparent`}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/* Toggle buttons for CSS and JS */}
|
|
||||||
<div className="flex space-x-2">
|
|
||||||
<button
|
|
||||||
onClick={() => setShowCss(!showCss)}
|
|
||||||
className="flex items-center space-x-1 px-3 py-1 text-xs bg-gray-100 dark:bg-gray-700 text-gray-600 dark:text-gray-400 rounded-md hover:bg-gray-200 dark:hover:bg-gray-600 transition-colors"
|
|
||||||
>
|
|
||||||
{showCss ? <ChevronUp className="w-3 h-3" /> : <ChevronDown className="w-3 h-3" />}
|
|
||||||
<span>CSS</span>
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
onClick={() => setShowJs(!showJs)}
|
|
||||||
className="flex items-center space-x-1 px-3 py-1 text-xs bg-gray-100 dark:bg-gray-700 text-gray-600 dark:text-gray-400 rounded-md hover:bg-gray-200 dark:hover:bg-gray-600 transition-colors"
|
|
||||||
>
|
|
||||||
{showJs ? <ChevronUp className="w-3 h-3" /> : <ChevronDown className="w-3 h-3" />}
|
|
||||||
<span>JS</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* CSS Input */}
|
{/* Action buttons above editor */}
|
||||||
{showCss && (
|
<div className="flex items-center justify-end space-x-2 p-2 bg-gray-50 dark:bg-gray-800 border-b border-gray-200 dark:border-gray-700">
|
||||||
<div className="space-y-2">
|
<button
|
||||||
<div className="flex items-center justify-between">
|
onClick={() => handleSearch(getCurrentEditorRef())}
|
||||||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
className="flex items-center gap-1 px-2 py-1 text-xs text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-300 transition-colors"
|
||||||
CSS
|
title="Search"
|
||||||
</label>
|
>
|
||||||
<button
|
<Search className="w-3 h-3" />
|
||||||
onClick={() => setCssExtended(!cssExtended)}
|
Search
|
||||||
className="flex items-center space-x-1 px-2 py-1 text-xs bg-gray-100 dark:bg-gray-700 text-gray-600 dark:text-gray-400 rounded-md hover:bg-gray-200 dark:hover:bg-gray-600 transition-colors"
|
</button>
|
||||||
title={cssExtended ? 'Minimize CSS box' : 'Maximize CSS box'}
|
<button
|
||||||
>
|
onClick={() => handleCopy(getCurrentContent())}
|
||||||
{cssExtended ? <Minimize2 className="w-3 h-3" /> : <Maximize2 className="w-3 h-3" />}
|
className="flex items-center gap-1 px-2 py-1 text-xs text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-300 transition-colors"
|
||||||
<span>{cssExtended ? 'Min' : 'Max'}</span>
|
title="Copy"
|
||||||
</button>
|
>
|
||||||
</div>
|
<Copy className="w-3 h-3" />
|
||||||
<textarea
|
Copy
|
||||||
value={cssInput}
|
</button>
|
||||||
onChange={(e) => setCssInput(e.target.value)}
|
<button
|
||||||
placeholder="Enter your CSS code here..."
|
onClick={() => handleExport(getCurrentContent(), getExportFilename())}
|
||||||
className={`w-full ${getTextareaHeight('css', cssExtended)} p-3 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 font-mono text-sm resize-none focus:ring-2 focus:ring-blue-500 focus:border-transparent`}
|
className="flex items-center gap-1 px-2 py-1 text-xs text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-300 transition-colors"
|
||||||
/>
|
title="Export"
|
||||||
</div>
|
>
|
||||||
)}
|
<Download className="w-3 h-3" />
|
||||||
|
Export
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* JS Input */}
|
{/* Code Editor */}
|
||||||
{showJs && (
|
<div className="flex-1">
|
||||||
<div className="space-y-2">
|
{activeTab === 'html' && (
|
||||||
<div className="flex items-center justify-between">
|
<CodeMirror
|
||||||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
ref={htmlEditorRef}
|
||||||
JavaScript
|
value={htmlInput}
|
||||||
</label>
|
onBeforeChange={(editor, data, value) => setHtmlInput(value)}
|
||||||
<button
|
options={{
|
||||||
onClick={() => setJsExtended(!jsExtended)}
|
mode: 'xml',
|
||||||
className="flex items-center space-x-1 px-2 py-1 text-xs bg-gray-100 dark:bg-gray-700 text-gray-600 dark:text-gray-400 rounded-md hover:bg-gray-200 dark:hover:bg-gray-600 transition-colors"
|
theme: 'material',
|
||||||
title={jsExtended ? 'Minimize JS box' : 'Maximize JS box'}
|
lineNumbers: true,
|
||||||
>
|
lineWrapping: true,
|
||||||
{jsExtended ? <Minimize2 className="w-3 h-3" /> : <Maximize2 className="w-3 h-3" />}
|
autoCloseTags: true,
|
||||||
<span>{jsExtended ? 'Min' : 'Max'}</span>
|
matchBrackets: true,
|
||||||
</button>
|
indentUnit: 2,
|
||||||
</div>
|
tabSize: 2,
|
||||||
<textarea
|
extraKeys: {
|
||||||
value={jsInput}
|
'Ctrl-F': 'findPersistent',
|
||||||
onChange={(e) => setJsInput(e.target.value)}
|
'Cmd-F': 'findPersistent'
|
||||||
placeholder="Enter your JavaScript code here..."
|
}
|
||||||
className={`w-full ${getTextareaHeight('js', jsExtended)} p-3 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 font-mono text-sm resize-none focus:ring-2 focus:ring-blue-500 focus:border-transparent`}
|
}}
|
||||||
|
className="h-full"
|
||||||
/>
|
/>
|
||||||
</div>
|
)}
|
||||||
)}
|
|
||||||
|
{activeTab === 'css' && (
|
||||||
|
<CodeMirror
|
||||||
|
ref={cssEditorRef}
|
||||||
|
value={cssInput}
|
||||||
|
onBeforeChange={(editor, data, value) => setCssInput(value)}
|
||||||
|
options={{
|
||||||
|
mode: 'css',
|
||||||
|
theme: 'material',
|
||||||
|
lineNumbers: true,
|
||||||
|
lineWrapping: true,
|
||||||
|
autoCloseBrackets: true,
|
||||||
|
matchBrackets: true,
|
||||||
|
indentUnit: 2,
|
||||||
|
tabSize: 2,
|
||||||
|
extraKeys: {
|
||||||
|
'Ctrl-F': 'findPersistent',
|
||||||
|
'Cmd-F': 'findPersistent'
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
className="h-full"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{activeTab === 'js' && (
|
||||||
|
<CodeMirror
|
||||||
|
ref={jsEditorRef}
|
||||||
|
value={jsInput}
|
||||||
|
onBeforeChange={(editor, data, value) => setJsInput(value)}
|
||||||
|
options={{
|
||||||
|
mode: 'javascript',
|
||||||
|
theme: 'material',
|
||||||
|
lineNumbers: true,
|
||||||
|
lineWrapping: true,
|
||||||
|
autoCloseBrackets: true,
|
||||||
|
matchBrackets: true,
|
||||||
|
indentUnit: 2,
|
||||||
|
tabSize: 2,
|
||||||
|
extraKeys: {
|
||||||
|
'Ctrl-F': 'findPersistent',
|
||||||
|
'Cmd-F': 'findPersistent'
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
className="h-full"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
213
src/pages/components/CodeInputsNew.js
Normal file
213
src/pages/components/CodeInputsNew.js
Normal file
@@ -0,0 +1,213 @@
|
|||||||
|
import React, { useState, useRef } from 'react';
|
||||||
|
import { Search, Copy, Download } from 'lucide-react';
|
||||||
|
import { Controlled as CodeMirror } from 'react-codemirror2';
|
||||||
|
import 'codemirror/lib/codemirror.css';
|
||||||
|
import 'codemirror/theme/material.css';
|
||||||
|
import 'codemirror/mode/xml/xml';
|
||||||
|
import 'codemirror/mode/css/css';
|
||||||
|
import 'codemirror/mode/javascript/javascript';
|
||||||
|
import 'codemirror/addon/search/search';
|
||||||
|
import 'codemirror/addon/search/searchcursor';
|
||||||
|
import 'codemirror/addon/dialog/dialog';
|
||||||
|
import 'codemirror/addon/dialog/dialog.css';
|
||||||
|
|
||||||
|
const CodeInputs = ({
|
||||||
|
htmlInput,
|
||||||
|
setHtmlInput,
|
||||||
|
cssInput,
|
||||||
|
setCssInput,
|
||||||
|
jsInput,
|
||||||
|
setJsInput,
|
||||||
|
isFullscreen
|
||||||
|
}) => {
|
||||||
|
const [activeTab, setActiveTab] = useState('html');
|
||||||
|
const htmlEditorRef = useRef(null);
|
||||||
|
const cssEditorRef = useRef(null);
|
||||||
|
const jsEditorRef = useRef(null);
|
||||||
|
|
||||||
|
// Handle search functionality
|
||||||
|
const handleSearch = (editorRef) => {
|
||||||
|
if (editorRef.current && editorRef.current.editor) {
|
||||||
|
editorRef.current.editor.execCommand('find');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Handle copy functionality
|
||||||
|
const handleCopy = async (content) => {
|
||||||
|
try {
|
||||||
|
await navigator.clipboard.writeText(content);
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Failed to copy:', err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Handle export functionality
|
||||||
|
const handleExport = (content, filename) => {
|
||||||
|
const blob = new Blob([content], { type: 'text/plain' });
|
||||||
|
const url = URL.createObjectURL(blob);
|
||||||
|
const a = document.createElement('a');
|
||||||
|
a.href = url;
|
||||||
|
a.download = filename;
|
||||||
|
document.body.appendChild(a);
|
||||||
|
a.click();
|
||||||
|
document.body.removeChild(a);
|
||||||
|
URL.revokeObjectURL(url);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Get current editor ref based on active tab
|
||||||
|
const getCurrentEditorRef = () => {
|
||||||
|
switch (activeTab) {
|
||||||
|
case 'html': return htmlEditorRef;
|
||||||
|
case 'css': return cssEditorRef;
|
||||||
|
case 'js': return jsEditorRef;
|
||||||
|
default: return htmlEditorRef;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Get current content based on active tab
|
||||||
|
const getCurrentContent = () => {
|
||||||
|
switch (activeTab) {
|
||||||
|
case 'html': return htmlInput;
|
||||||
|
case 'css': return cssInput;
|
||||||
|
case 'js': return jsInput;
|
||||||
|
default: return htmlInput;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Get filename for export based on active tab
|
||||||
|
const getExportFilename = () => {
|
||||||
|
switch (activeTab) {
|
||||||
|
case 'html': return 'code.html';
|
||||||
|
case 'css': return 'styles.css';
|
||||||
|
case 'js': return 'script.js';
|
||||||
|
default: return 'code.txt';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col h-full">
|
||||||
|
{/* Tab Navigation */}
|
||||||
|
<div className="flex border-b border-gray-200 dark:border-gray-700">
|
||||||
|
{[
|
||||||
|
{ id: 'html', label: 'HTML' },
|
||||||
|
{ id: 'css', label: 'CSS' },
|
||||||
|
{ id: 'js', label: 'JavaScript' }
|
||||||
|
].map((tab) => (
|
||||||
|
<button
|
||||||
|
key={tab.id}
|
||||||
|
onClick={() => setActiveTab(tab.id)}
|
||||||
|
className={`px-4 py-2 text-sm font-medium border-b-2 transition-colors ${
|
||||||
|
activeTab === tab.id
|
||||||
|
? 'border-blue-500 text-blue-600 dark:text-blue-400'
|
||||||
|
: 'border-transparent text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-300'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{tab.label}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Action buttons above editor */}
|
||||||
|
<div className="flex items-center justify-end space-x-2 p-2 bg-gray-50 dark:bg-gray-800 border-b border-gray-200 dark:border-gray-700">
|
||||||
|
<button
|
||||||
|
onClick={() => handleSearch(getCurrentEditorRef())}
|
||||||
|
className="flex items-center gap-1 px-2 py-1 text-xs text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-300 transition-colors"
|
||||||
|
title="Search"
|
||||||
|
>
|
||||||
|
<Search className="w-3 h-3" />
|
||||||
|
Search
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => handleCopy(getCurrentContent())}
|
||||||
|
className="flex items-center gap-1 px-2 py-1 text-xs text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-300 transition-colors"
|
||||||
|
title="Copy"
|
||||||
|
>
|
||||||
|
<Copy className="w-3 h-3" />
|
||||||
|
Copy
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => handleExport(getCurrentContent(), getExportFilename())}
|
||||||
|
className="flex items-center gap-1 px-2 py-1 text-xs text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-300 transition-colors"
|
||||||
|
title="Export"
|
||||||
|
>
|
||||||
|
<Download className="w-3 h-3" />
|
||||||
|
Export
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Code Editor */}
|
||||||
|
<div className="flex-1">
|
||||||
|
{activeTab === 'html' && (
|
||||||
|
<CodeMirror
|
||||||
|
ref={htmlEditorRef}
|
||||||
|
value={htmlInput}
|
||||||
|
onBeforeChange={(editor, data, value) => setHtmlInput(value)}
|
||||||
|
options={{
|
||||||
|
mode: 'xml',
|
||||||
|
theme: 'material',
|
||||||
|
lineNumbers: true,
|
||||||
|
lineWrapping: true,
|
||||||
|
autoCloseTags: true,
|
||||||
|
matchBrackets: true,
|
||||||
|
indentUnit: 2,
|
||||||
|
tabSize: 2,
|
||||||
|
extraKeys: {
|
||||||
|
'Ctrl-F': 'findPersistent',
|
||||||
|
'Cmd-F': 'findPersistent'
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
className="h-full"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{activeTab === 'css' && (
|
||||||
|
<CodeMirror
|
||||||
|
ref={cssEditorRef}
|
||||||
|
value={cssInput}
|
||||||
|
onBeforeChange={(editor, data, value) => setCssInput(value)}
|
||||||
|
options={{
|
||||||
|
mode: 'css',
|
||||||
|
theme: 'material',
|
||||||
|
lineNumbers: true,
|
||||||
|
lineWrapping: true,
|
||||||
|
autoCloseBrackets: true,
|
||||||
|
matchBrackets: true,
|
||||||
|
indentUnit: 2,
|
||||||
|
tabSize: 2,
|
||||||
|
extraKeys: {
|
||||||
|
'Ctrl-F': 'findPersistent',
|
||||||
|
'Cmd-F': 'findPersistent'
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
className="h-full"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{activeTab === 'js' && (
|
||||||
|
<CodeMirror
|
||||||
|
ref={jsEditorRef}
|
||||||
|
value={jsInput}
|
||||||
|
onBeforeChange={(editor, data, value) => setJsInput(value)}
|
||||||
|
options={{
|
||||||
|
mode: 'javascript',
|
||||||
|
theme: 'material',
|
||||||
|
lineNumbers: true,
|
||||||
|
lineWrapping: true,
|
||||||
|
autoCloseBrackets: true,
|
||||||
|
matchBrackets: true,
|
||||||
|
indentUnit: 2,
|
||||||
|
tabSize: 2,
|
||||||
|
extraKeys: {
|
||||||
|
'Ctrl-F': 'findPersistent',
|
||||||
|
'Cmd-F': 'findPersistent'
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
className="h-full"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CodeInputs;
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
import React, { useState, useEffect, useRef } from 'react';
|
import React, { useState, useEffect, useRef } from 'react';
|
||||||
|
import { Copy } from 'lucide-react';
|
||||||
|
|
||||||
const ElementEditor = ({ htmlInput, setHtmlInput, onClose, onSave, previewFrameRef, selectedElementInfo }) => {
|
const ElementEditor = ({ htmlInput, setHtmlInput, onClose, onSave, previewFrameRef, selectedElementInfo }) => {
|
||||||
console.log('🔍 ELEMENT EDITOR: Received props:', { selectedElementInfo, previewFrameRef: !!previewFrameRef });
|
console.log('🔍 ELEMENT EDITOR: Received props:', { selectedElementInfo, previewFrameRef: !!previewFrameRef });
|
||||||
@@ -10,14 +11,14 @@ const ElementEditor = ({ htmlInput, setHtmlInput, onClose, onSave, previewFrameR
|
|||||||
if (selectedElementInfo) {
|
if (selectedElementInfo) {
|
||||||
const elementInfo = {
|
const elementInfo = {
|
||||||
tagName: selectedElementInfo.tagName,
|
tagName: selectedElementInfo.tagName,
|
||||||
innerText: selectedElementInfo.textContent || '',
|
innerHTML: selectedElementInfo.innerHTML || selectedElementInfo.textContent || '',
|
||||||
id: selectedElementInfo.attributes.id || '',
|
id: selectedElementInfo.attributes.id || '',
|
||||||
className: selectedElementInfo.attributes.class || '',
|
className: selectedElementInfo.attributes.class || '',
|
||||||
cascadeId: selectedElementInfo.cascadeId,
|
cascadeId: selectedElementInfo.cascadeId,
|
||||||
isContainer: selectedElementInfo.attributes.children?.length > 0 || isContainerElement(selectedElementInfo.tagName),
|
isContainer: selectedElementInfo.attributes.children?.length > 0 || isContainerElement(selectedElementInfo.tagName),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Add all other attributes
|
// Add all other attributes (excluding cascadeId from display)
|
||||||
Object.entries(selectedElementInfo.attributes).forEach(([name, value]) => {
|
Object.entries(selectedElementInfo.attributes).forEach(([name, value]) => {
|
||||||
if (!['id', 'class', 'data-original', 'data-cascade-id'].includes(name)) {
|
if (!['id', 'class', 'data-original', 'data-cascade-id'].includes(name)) {
|
||||||
elementInfo[name] = value;
|
elementInfo[name] = value;
|
||||||
@@ -68,7 +69,7 @@ const ElementEditor = ({ htmlInput, setHtmlInput, onClose, onSave, previewFrameR
|
|||||||
if (previewFrameRef?.current && edited?.cascadeId) {
|
if (previewFrameRef?.current && edited?.cascadeId) {
|
||||||
let success = false;
|
let success = false;
|
||||||
|
|
||||||
if (field === 'innerText') {
|
if (field === 'innerHTML') {
|
||||||
success = previewFrameRef.current.updateElementText(edited.cascadeId, value);
|
success = previewFrameRef.current.updateElementText(edited.cascadeId, value);
|
||||||
} else if (field === 'className') {
|
} else if (field === 'className') {
|
||||||
success = previewFrameRef.current.updateElementClass(edited.cascadeId, value);
|
success = previewFrameRef.current.updateElementClass(edited.cascadeId, value);
|
||||||
@@ -143,12 +144,48 @@ const ElementEditor = ({ htmlInput, setHtmlInput, onClose, onSave, previewFrameR
|
|||||||
|
|
||||||
if (!edited) return <p className="dark:text-gray-300">Loading editor...</p>;
|
if (!edited) return <p className="dark:text-gray-300">Loading editor...</p>;
|
||||||
|
|
||||||
const otherAttributes = Object.keys(edited || {}).filter(
|
// Filter out system attributes for display (hide cascadeId)
|
||||||
key => key !== 'tagName' && key !== 'id' && key !== 'className' && key !== 'innerText' && key !== 'isContainer'
|
const otherAttributes = Object.keys(edited).filter(key =>
|
||||||
|
!['tagName', 'innerHTML', 'id', 'className', 'cascadeId', 'isContainer'].includes(key)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Handle copy element functionality
|
||||||
|
const handleCopyElement = async () => {
|
||||||
|
if (!edited) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Create a clean element string without cascade attributes
|
||||||
|
const elementString = `<${edited.tagName}${edited.id ? ` id="${edited.id}"` : ''}${edited.className ? ` class="${edited.className}"` : ''}${otherAttributes.map(attr => ` ${attr}="${edited[attr]}"`).join('')}>${edited.innerHTML || ''}</${edited.tagName}>`;
|
||||||
|
|
||||||
|
await navigator.clipboard.writeText(elementString);
|
||||||
|
console.log('✅ Element copied to clipboard');
|
||||||
|
} catch (err) {
|
||||||
|
console.error('❌ Failed to copy element:', err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
|
<div className="flex items-center justify-between mb-4">
|
||||||
|
<h3 className="text-lg font-semibold text-gray-900 dark:text-gray-100">
|
||||||
|
Edit {edited.tagName.toUpperCase()}
|
||||||
|
</h3>
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<button
|
||||||
|
onClick={handleCopyElement}
|
||||||
|
className="p-1 text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200 transition-colors"
|
||||||
|
title="Copy element"
|
||||||
|
>
|
||||||
|
<Copy className="w-4 h-4" />
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={onClose}
|
||||||
|
className="text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200"
|
||||||
|
>
|
||||||
|
✕
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300">Tag Name</label>
|
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300">Tag Name</label>
|
||||||
<textarea
|
<textarea
|
||||||
@@ -189,34 +226,26 @@ const ElementEditor = ({ htmlInput, setHtmlInput, onClose, onSave, previewFrameR
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Only show innerText field for non-container elements */}
|
{/* Show innerHTML field for all elements */}
|
||||||
{!edited.isContainer && (
|
<div className="space-y-2">
|
||||||
<div className="space-y-2">
|
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
Inner HTML
|
||||||
Inner Text
|
<span className="text-xs text-gray-500 ml-2">(HTML content inside element)</span>
|
||||||
<span className="text-xs text-gray-500 ml-2">(text content)</span>
|
</label>
|
||||||
</label>
|
<textarea
|
||||||
<textarea
|
ref={(el) => textareaRefs.current.innerHTML = el}
|
||||||
ref={(el) => textareaRefs.current.innerText = el}
|
value={edited.innerHTML || ''}
|
||||||
value={edited.innerText || ''}
|
onChange={(e) => {
|
||||||
onChange={(e) => {
|
handleFieldChange('innerHTML', e.target.value);
|
||||||
handleFieldChange('innerText', e.target.value);
|
autoResizeTextarea(e.target);
|
||||||
autoResizeTextarea(e.target);
|
}}
|
||||||
}}
|
rows="3"
|
||||||
rows="2"
|
className="w-full p-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 font-mono text-sm resize-none overflow-hidden"
|
||||||
className="w-full p-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 font-mono text-sm resize-none overflow-hidden"
|
placeholder="Enter HTML content or text..."
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Show container hint for container elements */}
|
|
||||||
{edited.isContainer && (
|
|
||||||
<div className="p-3 bg-blue-50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-800 rounded-md">
|
|
||||||
<p className="text-sm text-blue-700 dark:text-blue-300">
|
|
||||||
📦 This is a container element. Edit its ID, class, or other attributes instead of text content.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{otherAttributes.map(attr => (
|
{otherAttributes.map(attr => (
|
||||||
<div key={attr} className="space-y-2">
|
<div key={attr} className="space-y-2">
|
||||||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300">{attr}</label>
|
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300">{attr}</label>
|
||||||
|
|||||||
90
src/pages/components/SimpleToolbar.js
Normal file
90
src/pages/components/SimpleToolbar.js
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Smartphone, Tablet, Monitor, RotateCcw, Maximize, Minimize, Eye, EyeOff } from 'lucide-react';
|
||||||
|
|
||||||
|
const SimpleToolbar = ({
|
||||||
|
selectedDevice,
|
||||||
|
setSelectedDevice,
|
||||||
|
isFullscreen,
|
||||||
|
onRefresh,
|
||||||
|
onToggleFullscreen,
|
||||||
|
onToggleSidebar,
|
||||||
|
showSidebar,
|
||||||
|
isSidebarExpanded
|
||||||
|
}) => {
|
||||||
|
const devices = [
|
||||||
|
{ id: 'mobile', label: 'Mobile', icon: Smartphone },
|
||||||
|
{ id: 'tablet', label: 'Tablet', icon: Tablet },
|
||||||
|
{ id: 'desktop', label: 'Desktop', icon: Monitor }
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg p-4">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
{/* Device selection */}
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<span className="text-sm font-medium text-gray-700 dark:text-gray-300 mr-3">
|
||||||
|
Device:
|
||||||
|
</span>
|
||||||
|
{devices.map((device) => {
|
||||||
|
const Icon = device.icon;
|
||||||
|
const isDisabled = isSidebarExpanded && device.id !== 'desktop';
|
||||||
|
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
key={device.id}
|
||||||
|
onClick={() => !isDisabled && setSelectedDevice(device.id)}
|
||||||
|
disabled={isDisabled}
|
||||||
|
className={`flex items-center gap-2 px-3 py-2 rounded-md text-sm font-medium transition-colors ${
|
||||||
|
selectedDevice === device.id
|
||||||
|
? 'bg-blue-100 dark:bg-blue-900 text-blue-600 dark:text-blue-400'
|
||||||
|
: isDisabled
|
||||||
|
? 'text-gray-400 dark:text-gray-600 cursor-not-allowed'
|
||||||
|
: 'text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700'
|
||||||
|
}`}
|
||||||
|
title={isDisabled ? 'Disabled when sidebar is expanded' : `Switch to ${device.label} view`}
|
||||||
|
>
|
||||||
|
<Icon className="w-4 h-4" />
|
||||||
|
{device.label}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Action buttons */}
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
{/* Refresh button */}
|
||||||
|
<button
|
||||||
|
onClick={onRefresh}
|
||||||
|
className="flex items-center gap-2 px-3 py-2 text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-md transition-colors"
|
||||||
|
title="Refresh preview"
|
||||||
|
>
|
||||||
|
<RotateCcw className="w-4 h-4" />
|
||||||
|
Refresh
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{/* Sidebar toggle */}
|
||||||
|
<button
|
||||||
|
onClick={onToggleSidebar}
|
||||||
|
className="flex items-center gap-2 px-3 py-2 text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-md transition-colors"
|
||||||
|
title={showSidebar ? 'Hide sidebar' : 'Show sidebar'}
|
||||||
|
>
|
||||||
|
{showSidebar ? <EyeOff className="w-4 h-4" /> : <Eye className="w-4 h-4" />}
|
||||||
|
{showSidebar ? 'Hide' : 'Show'} Sidebar
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{/* Fullscreen toggle */}
|
||||||
|
<button
|
||||||
|
onClick={onToggleFullscreen}
|
||||||
|
className="flex items-center gap-2 px-3 py-2 text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-md transition-colors"
|
||||||
|
title={isFullscreen ? 'Exit fullscreen' : 'Enter fullscreen'}
|
||||||
|
>
|
||||||
|
{isFullscreen ? <Minimize className="w-4 h-4" /> : <Maximize className="w-4 h-4" />}
|
||||||
|
{isFullscreen ? 'Exit' : 'Enter'} Fullscreen
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SimpleToolbar;
|
||||||
Reference in New Issue
Block a user