├── .babelrc ├── .gitattributes ├── .gitignore ├── .prettierrc ├── LICENSE ├── README.md ├── build ├── index.js └── main.css ├── buildtest └── index.html ├── package-lock.json ├── package.json ├── src ├── Components │ ├── ComponentSelector.js │ ├── DDEditor │ │ ├── EditItem.js │ │ ├── MobileBoundary.js │ │ └── dragCoincideLines.js │ ├── DraggableButton │ │ ├── ButtonSelector.js │ │ ├── PanelControls.js │ │ └── index.js │ ├── DraggableCrypto.js │ ├── DraggableDiv.js │ ├── DraggableGiphy │ │ ├── GiphySelector.js │ │ └── index.js │ ├── DraggableHtml │ │ ├── PanelControls.js │ │ └── index.js │ ├── DraggableImage.js │ └── DraggableText │ │ ├── PanelControls.js │ │ └── index.js ├── DragDrop.js ├── EditMenu │ ├── ControlPanel.js │ ├── EditMenu.js │ ├── defaultButtons.js │ └── formHelpers.js ├── index.css ├── index.js ├── logo.svg ├── pageContext.js └── utils │ ├── helpers.js │ └── ui │ ├── ColorPicker.js │ ├── DropDownMenu.js │ ├── GenericModal.js │ └── MobileBoundary.js ├── srctest └── app.js ├── webpack.config.js ├── webpack.publish.js └── webpack.testServer.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["@babel/preset-env", "@babel/preset-react"], 3 | "plugins": [] 4 | } 5 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | # .prettierrc 2 | printWidth: 100 3 | semi: false 4 | useTabs: false 5 | tabWidth: 2 6 | singleQuote: true 7 | trailingComma: none 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Pranith Hengavalli 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # react-dragd 2 | 3 | A dynamic UI library that allows you to build pages using drag and drop components and configurations. 4 | 5 | [Try it - Code Sandbox](https://codesandbox.io/s/dragd-template-joh6v3) 6 | 7 | ## Installation and Usage 8 | Install the package from NPM 9 | ``` 10 | npm i react-dragd 11 | ``` 12 | 13 | Import the package and CSS in your react project. 14 | ``` 15 | import Dragd from "react-dragd"; 16 | import "../node_modules/react-dragd/build/main.css"; 17 | 18 | export default function App() { 19 | return ( 20 |
21 | 22 |
23 | ); 24 | } 25 | ``` 26 | 27 | ## To start 28 | 29 | ``` 30 | npm i 31 | npm start 32 | ``` 33 | 34 | Edit `src/index.js` (your component) 35 | 36 | ## To view your component in isolation with a basic webpack dev server: 37 | type: 38 | 39 | ``` 40 | npm run dev 41 | ``` 42 | 43 | Edit `/srctest/app.js` to change the parent environment, pass in props, etc. 44 | 45 | ## To test your component in another project (locally), before publishing to npm: 46 | 47 | Build this project: 48 | 49 | ``` 50 | npm run build 51 | ``` 52 | 53 | In this project's root directory, type: 54 | 55 | ``` 56 | npm link 57 | ``` 58 | 59 | And then, in the project (root dir) you would like to use your component: 60 | 61 | ``` 62 | npm link my-awesome-component 63 | ``` 64 | 65 | For this example I've used the package name `my-awesome-component`. 66 | This creates a symlink of your package in your project's node_modules/ dir. 67 | Now, you may import the component in your test project, as if it was a normally installed dependancy: 68 | 69 | ``` 70 | import MyAwesomeComponent from 'my-awesome-component' 71 | ``` 72 | 73 | If you're using a hot-reload system, you should be able to observe any changes you make to your component (as long as you build them) 74 | 75 | ## To publish your component to npm 76 | 77 | In the root directory, type: 78 | 79 | ``` 80 | npm publish 81 | ``` 82 | 83 | [npm docs on publishing packages](https://docs.npmjs.com/packages-and-modules/contributing-packages-to-the-registry) 84 | 85 | ## A note on webpack configs and the dev server: 86 | There are two webpack configs. 87 | 88 | - One for building the published component `webpack.publish.js` 89 | - One for viewing the component in the dev server. `webpack.testServer.js` 90 | 91 | Note that they are separate, so any additions you make will have to be mirrored in both files, if you want to use the dev server. If anyone knows a better way to do this, please let me know. 92 | -------------------------------------------------------------------------------- /build/main.css: -------------------------------------------------------------------------------- 1 | /* DRAGGABLE DIV VISUAL */ 2 | .draggable:hover { 3 | -webkit-filter: drop-shadow(0px 0px 5px rgba(64, 224, 208, 0.81)); 4 | filter: drop-shadow(0px 0px 5px rgba(64, 224, 208, 0.81)); 5 | cursor: move; 6 | } 7 | 8 | .draggableselected { 9 | -webkit-filter: drop-shadow(0px 0px 5px rgba(255, 255, 40, 1)); 10 | filter: drop-shadow(0px 0px 5px rgba(255, 255, 40, 1)); 11 | } 12 | 13 | .dragHandle { 14 | position: absolute; 15 | border: 1px solid black; 16 | border-radius: 2px; 17 | background-color: white; 18 | padding: 3px; 19 | cursor: nesw-resize; 20 | 21 | z-index: 9999999; 22 | } 23 | 24 | @media only screen and (max-width: 768px) { 25 | .dragHandle { 26 | padding: 10px; 27 | } 28 | } 29 | 30 | @media only screen and (min-width: 768px) { 31 | .hovershadow { 32 | box-shadow: 0px 0px 0px rgb(0 0 0 / 16%); 33 | transition: box-shadow 0.1s ease-in-out; 34 | } 35 | 36 | /* Transition to a bigger shadow on hover */ 37 | .hovershadow:hover { 38 | box-shadow: 4px 4px 0px rgb(0 0 0 / 16%); 39 | } 40 | 41 | /* Transition to a bigger shadow on hover */ 42 | .hovershadow:active { 43 | box-shadow: 2px 2px 0px rgb(0 0 0 / 16%); 44 | } 45 | } 46 | 47 | .dragHandle2 { 48 | position: absolute; 49 | padding: 12px; 50 | border: 1px solid black; 51 | background-color: white; 52 | padding: 3px; 53 | z-index: 9999999; 54 | cursor: sw-resize; 55 | } 56 | 57 | /* CONTROL PANEL */ 58 | 59 | .cpanel { 60 | min-width: 10px; 61 | border: 1px solid black; 62 | background-color: rgba(255, 255, 255, 0.64); 63 | backdrop-filter: blur(8px); 64 | border-radius: 3px; 65 | z-index: 9999999999; 66 | } 67 | 68 | .cpanel-col-buttons:hover > * { 69 | opacity: 0.5; 70 | } 71 | 72 | .cpanel-col-buttons:hover > *:hover { 73 | transform: scale(1.1); 74 | opacity: 1; 75 | } 76 | 77 | .cpanel-shadow { 78 | box-shadow: 4px 4px 0px rgb(0 0 0 / 16%); 79 | } 80 | 81 | .cpanel-col:not(:last-child) { 82 | border-right: 1px solid black; 83 | } 84 | 85 | .clabel { 86 | font-size: 12px; 87 | font-weight: bold; 88 | color: black; 89 | padding: 5px; 90 | } 91 | 92 | .cbutton { 93 | width: 50px; 94 | height: 50px; 95 | display: flex; 96 | align-items: center; 97 | justify-content: center; 98 | border-radius: 3px; 99 | cursor: pointer; 100 | } 101 | 102 | .cbuttoninner { 103 | width: 25px; 104 | height: 25px; 105 | } 106 | 107 | .cbuttoninner:hover { 108 | background-color: rgba(1, 1, 1, 0.1); 109 | } 110 | 111 | .cbuttoninner-selected { 112 | background-color: rgba(1, 1, 1, 0.3); 113 | } 114 | 115 | .cbuttonmain { 116 | border: 1px solid black; 117 | color: black; 118 | background-color: white; 119 | width: 52px; 120 | height: 52px; 121 | } 122 | 123 | .flexRow { 124 | display: flex; 125 | flex-direction: row; 126 | align-items: center; 127 | } 128 | 129 | .minimal-input { 130 | border: none; 131 | } 132 | 133 | .minimal-input:focus { 134 | outline: none; 135 | } 136 | 137 | /* [contenteditable]:focus { 138 | outline: 0px solid transparent; 139 | } */ 140 | 141 | /* Tooltip container */ 142 | .tooltip { 143 | } 144 | 145 | .tooltip .tooltiptext { 146 | visibility: hidden; 147 | width: 120px; 148 | background-color: black; 149 | color: #fff; 150 | text-align: center; 151 | padding: 5px 0; 152 | border-radius: 6px; 153 | margin-right: 200px; 154 | position: absolute; 155 | z-index: 1; 156 | } 157 | 158 | .tooltip .tooltipbottom { 159 | margin-top: 80px; 160 | margin-right: 0px; 161 | margin-left: 0px; 162 | } 163 | 164 | .tooltip:hover .tooltiptext { 165 | visibility: visible; 166 | } 167 | 168 | #page-center-align-guide { 169 | left: 50%; 170 | transform: translateX(50vw); 171 | touch-action: none; 172 | pointer-events: none; 173 | z-index: 99999999; 174 | } 175 | 176 | .page-align-guide:not(.active) { 177 | opacity: 0; 178 | } 179 | 180 | .page-align-guide { 181 | height: 100vh; 182 | position: fixed; 183 | width: 1px; 184 | border-right: 1px solid red; 185 | z-index: 99999999; 186 | top: 0; 187 | touch-action: none; 188 | pointer-events: none; 189 | } 190 | 191 | .mobile-align-guide { 192 | border-right: 1px solid grey; 193 | touch-action: none; 194 | pointer-events: none; 195 | } 196 | 197 | .mobile-align-bg { 198 | background-color: rgba(0, 0, 0, 0.1); 199 | z-index: 99999999; 200 | touch-action: none; 201 | pointer-events: none; 202 | } 203 | 204 | /* 205 | Interthing Guide 206 | */ 207 | 208 | .interthing-line { 209 | width: 1px; 210 | height: 1rem; 211 | background: lightblue; 212 | position: absolute; 213 | z-index: 9999999; 214 | 215 | touch-action: none; 216 | pointer-events: none; 217 | } 218 | 219 | .interthing-line-nub { 220 | position: absolute; 221 | 222 | height: 4px; 223 | width: 4px; 224 | background-color: red; 225 | transform: translate(-50%, -50%); 226 | 227 | display: block; 228 | z-index: 999999999; 229 | 230 | touch-action: none; 231 | pointer-events: none; 232 | } 233 | 234 | #builder-drag-select-box { 235 | --selection-color-rgb: 71, 160, 244; 236 | --selection-color: rgba(255, 255, 0, 1); 237 | 238 | display: block; 239 | position: absolute; 240 | 241 | background: rgba(var(--selection-color), 0.2); 242 | border: var(--line-width) solid var(--selection-color); 243 | z-index: 999999999; 244 | 245 | box-sizing: border-box; 246 | } 247 | 248 | /* SECTION: spinning circle animation */ 249 | 250 | .brocorpSaveSpinner { 251 | transition: opacity 0.3s; 252 | opacity: 0; 253 | } 254 | 255 | .brocorpSaveSpinner:hover { 256 | transition: opacity 0.3s; 257 | opacity: 1; 258 | } 259 | 260 | .cssload-wrap { 261 | width: 55px; 262 | height: 55px; 263 | margin: 27px auto; 264 | position: relative; 265 | perspective: 1100px; 266 | -o-perspective: 1100px; 267 | -ms-perspective: 1100px; 268 | -webkit-perspective: 1100px; 269 | -moz-perspective: 1100px; 270 | transform-style: preserve-3d; 271 | -o-transform-style: preserve-3d; 272 | -ms-transform-style: preserve-3d; 273 | -webkit-transform-style: preserve-3d; 274 | -moz-transform-style: preserve-3d; 275 | } 276 | 277 | .cssload-circle { 278 | transform-style: preserve-3d; 279 | -o-transform-style: preserve-3d; 280 | -ms-transform-style: preserve-3d; 281 | -webkit-transform-style: preserve-3d; 282 | -moz-transform-style: preserve-3d; 283 | box-sizing: border-box; 284 | -o-box-sizing: border-box; 285 | -ms-box-sizing: border-box; 286 | -webkit-box-sizing: border-box; 287 | -moz-box-sizing: border-box; 288 | opacity: 0; 289 | width: 55px; 290 | height: 55px; 291 | border: 1px solid rgba(255, 255, 255, 0.8); 292 | border-radius: 41px; 293 | position: absolute; 294 | top: 0; 295 | left: 0; 296 | animation: cssload-spin 12.5s ease-in-out alternate infinite; 297 | -o-animation: cssload-spin 12.5s ease-in-out alternate infinite; 298 | -ms-animation: cssload-spin 12.5s ease-in-out alternate infinite; 299 | -webkit-animation: cssload-spin 12.5s ease-in-out alternate infinite; 300 | -moz-animation: cssload-spin 12.5s ease-in-out alternate infinite; 301 | } 302 | .cssload-circle:nth-of-type(1) { 303 | animation-delay: 375ms; 304 | -o-animation-delay: 375ms; 305 | -ms-animation-delay: 375ms; 306 | -webkit-animation-delay: 375ms; 307 | -moz-animation-delay: 375ms; 308 | } 309 | .cssload-circle:nth-of-type(2) { 310 | animation-delay: 750ms; 311 | -o-animation-delay: 750ms; 312 | -ms-animation-delay: 750ms; 313 | -webkit-animation-delay: 750ms; 314 | -moz-animation-delay: 750ms; 315 | } 316 | .cssload-circle:nth-of-type(3) { 317 | animation-delay: 1125ms; 318 | -o-animation-delay: 1125ms; 319 | -ms-animation-delay: 1125ms; 320 | -webkit-animation-delay: 1125ms; 321 | -moz-animation-delay: 1125ms; 322 | } 323 | .cssload-circle:nth-of-type(4) { 324 | animation-delay: 1500ms; 325 | -o-animation-delay: 1500ms; 326 | -ms-animation-delay: 1500ms; 327 | -webkit-animation-delay: 1500ms; 328 | -moz-animation-delay: 1500ms; 329 | } 330 | .cssload-circle:nth-of-type(5) { 331 | animation-delay: 1875ms; 332 | -o-animation-delay: 1875ms; 333 | -ms-animation-delay: 1875ms; 334 | -webkit-animation-delay: 1875ms; 335 | -moz-animation-delay: 1875ms; 336 | } 337 | 338 | @keyframes cssload-spin { 339 | 0% { 340 | transform: rotateY(0deg) rotateX(0deg); 341 | opacity: 1; 342 | } 343 | 25% { 344 | transform: rotateY(180deg) rotateX(360deg); 345 | } 346 | 50% { 347 | transform: rotateY(540deg) rotateX(540deg); 348 | } 349 | 75% { 350 | transform: rotateY(720deg) rotateX(900deg); 351 | } 352 | 100% { 353 | transform: rotateY(900deg) rotateX(1080deg); 354 | opacity: 1; 355 | } 356 | } 357 | 358 | @-o-keyframes cssload-spin { 359 | 0% { 360 | -o-transform: rotateY(0deg) rotateX(0deg); 361 | opacity: 1; 362 | } 363 | 25% { 364 | -o-transform: rotateY(180deg) rotateX(360deg); 365 | } 366 | 50% { 367 | -o-transform: rotateY(540deg) rotateX(540deg); 368 | } 369 | 75% { 370 | -o-transform: rotateY(720deg) rotateX(900deg); 371 | } 372 | 100% { 373 | -o-transform: rotateY(900deg) rotateX(1080deg); 374 | opacity: 1; 375 | } 376 | } 377 | 378 | @-ms-keyframes cssload-spin { 379 | 0% { 380 | -ms-transform: rotateY(0deg) rotateX(0deg); 381 | opacity: 1; 382 | } 383 | 25% { 384 | -ms-transform: rotateY(180deg) rotateX(360deg); 385 | } 386 | 50% { 387 | -ms-transform: rotateY(540deg) rotateX(540deg); 388 | } 389 | 75% { 390 | -ms-transform: rotateY(720deg) rotateX(900deg); 391 | } 392 | 100% { 393 | -ms-transform: rotateY(900deg) rotateX(1080deg); 394 | opacity: 1; 395 | } 396 | } 397 | 398 | @-webkit-keyframes cssload-spin { 399 | 0% { 400 | -webkit-transform: rotateY(0deg) rotateX(0deg); 401 | opacity: 1; 402 | } 403 | 25% { 404 | -webkit-transform: rotateY(180deg) rotateX(360deg); 405 | } 406 | 50% { 407 | -webkit-transform: rotateY(540deg) rotateX(540deg); 408 | } 409 | 75% { 410 | -webkit-transform: rotateY(720deg) rotateX(900deg); 411 | } 412 | 100% { 413 | -webkit-transform: rotateY(900deg) rotateX(1080deg); 414 | opacity: 1; 415 | } 416 | } 417 | 418 | @-moz-keyframes cssload-spin { 419 | 0% { 420 | -moz-transform: rotateY(0deg) rotateX(0deg); 421 | opacity: 1; 422 | } 423 | 25% { 424 | -moz-transform: rotateY(180deg) rotateX(360deg); 425 | } 426 | 50% { 427 | -moz-transform: rotateY(540deg) rotateX(540deg); 428 | } 429 | 75% { 430 | -moz-transform: rotateY(720deg) rotateX(900deg); 431 | } 432 | 100% { 433 | -moz-transform: rotateY(900deg) rotateX(1080deg); 434 | opacity: 1; 435 | } 436 | } 437 | 438 | 439 | /* body { 440 | max-width: 100vw; 441 | } */ 442 | 443 | .dragd-modal { 444 | position: fixed; /* Stay in place */ 445 | z-index: 1; /* Sit on top */ 446 | left: 0; 447 | top: 0; 448 | width: 100%; /* Full width */ 449 | height: 100%; /* Full height */ 450 | overflow: auto; /* Enable scroll if needed */ 451 | background-color: rgb(0,0,0); /* Fallback color */ 452 | background-color: rgba(0,0,0,0.4); /* Black w/ opacity */ 453 | } 454 | 455 | /* Modal Content/Box */ 456 | .dragd-modal-content { 457 | background-color: #fefefe; 458 | margin: 15% auto; /* 15% from the top and centered */ 459 | padding: 20px; 460 | border: 1px solid #888; 461 | width: 80%; /* Could be more or less, depending on screen size */ 462 | } 463 | 464 | .dropdown { 465 | position: relative; 466 | display: inline-block; 467 | } 468 | 469 | .dropdown-content { 470 | position: absolute; 471 | background-color: #f9f9f9; 472 | min-width: 160px; 473 | box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2); 474 | padding: 12px 16px; 475 | z-index: 1; 476 | } 477 | -------------------------------------------------------------------------------- /buildtest/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Document 6 | 7 | 8 |
9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-dragd", 3 | "version": "1.0.1", 4 | "description": "Drag-and-drop page builder and viewer for React.", 5 | "main": "build/index.js", 6 | "scripts": { 7 | "start": "webpack --env publish --mode production --watch", 8 | "build": "webpack --env publish --mode production", 9 | "dev": "webpack-dev-server --env testServer --mode development --open" 10 | }, 11 | "author": "prnth.com", 12 | "license": "ISC", 13 | "repository": { 14 | "type": "git", 15 | "url": "https://github.com/prnthh/react-dragd" 16 | }, 17 | "peerDependencies": { 18 | "react": "^16.7.0" 19 | }, 20 | "dependencies": { 21 | "@giphy/js-fetch-api": "^4.1.2", 22 | "@giphy/react-components": "^5.7.0", 23 | "@monaco-editor/react": "^4.4.5", 24 | "react-colorful": "^5.5.1", 25 | "react-giphy-searchbox": "^1.5.4", 26 | "react-markdown": "^8.0.3", 27 | "rehype-raw": "^6.1.1" 28 | }, 29 | "devDependencies": { 30 | "@babel/core": "^7.4.5", 31 | "@babel/preset-env": "^7.4.5", 32 | "@babel/preset-react": "^7.0.0", 33 | "babel-loader": "^8.0.6", 34 | "css-loader": "^5.2.7", 35 | "mini-css-extract-plugin": "^0.9.0", 36 | "react": "^16.8.6", 37 | "react-dom": "^16.8.6", 38 | "style-loader": "^2.0.0", 39 | "webpack": "^4.33.0", 40 | "webpack-cli": "^3.3.3", 41 | "webpack-dev-server": "^3.7.1" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/Components/ComponentSelector.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import DraggableText from './DraggableText'; 3 | // import dynamic from 'next/dynamic'; 4 | // const EditItem = dynamic(() => import('./DDEditor/EditItem')); 5 | import DraggableImage from './DraggableImage'; 6 | import DraggableDiv from './DraggableDiv'; 7 | // const DraggableText = dynamic(() => import('./DraggableText')); 8 | import DraggableGiphy from './DraggableGiphy'; 9 | // const DraggableVideo = dynamic(() => import('./DraggableVideo')); 10 | // const DraggableAudio = dynamic(() => import('./DraggableAudio')); 11 | import DraggableButton from './DraggableButton'; 12 | import DraggableHtml from './DraggableHtml'; 13 | import DraggableCrypto from './DraggableCrypto'; 14 | // const DraggableForm = dynamic(() => import('./DraggableForm')); 15 | // const DraggableTemplate = dynamic(() => import('./DraggableTemplate')); 16 | // const NextHead = dynamic(() => import('./NextHead.js')); 17 | 18 | function ComponentSelector({ elem, selected }) { 19 | const isSelected = selected && selected.includes(elem.id); 20 | switch (elem.type) { 21 | // case 'test': 22 | // return ( 23 | // 24 | // Drag Me! 25 | // 26 | // ); 27 | case 'text': 28 | return ( 29 | 33 | ); 34 | case 'button': 35 | return ( 36 | 40 | ); 41 | case 'image': 42 | return ( 43 | 47 | ); 48 | case 'crypto': 49 | return ( 50 | 54 | ); 55 | case 'giphy': 56 | return ( 57 | 61 | ); 62 | case 'color': 63 | return ( 64 | 68 | ); 69 | case 'markdown': 70 | case 'code': 71 | return ( 72 | 76 | ); 77 | default: 78 | return <>; 79 | } 80 | } 81 | 82 | export default ComponentSelector; 83 | -------------------------------------------------------------------------------- /src/Components/DDEditor/EditItem.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect, useRef, useContext } from 'react'; 2 | import { 3 | usePrevious, 4 | getElementOffset, 5 | getAngle, 6 | getLength, 7 | degToRadian, 8 | Input, 9 | isMobile, 10 | getMobileScaleRatio, 11 | isMobileViewport, 12 | } from '../../utils/helpers'; 13 | import SiteContext from '../../pageContext'; 14 | import ControlPanel from '../../EditMenu/ControlPanel'; 15 | import DragCoincideLines from './dragCoincideLines'; 16 | // const ControlPanel = dynamic(() => import('../EditMenu/ControlPanel')); 17 | // const DragCoincideLines = dynamic(() => import('./dragCoincideLines.js')); 18 | 19 | const dragHandleOffset = 5; 20 | const pageCenterSnapDistance = 20; 21 | const itemAlignSnapDistance = 10; 22 | 23 | const dragCornerOffsets = [ 24 | { top: -dragHandleOffset, left: -dragHandleOffset }, 25 | { top: -dragHandleOffset, right: -dragHandleOffset }, 26 | { bottom: -dragHandleOffset, left: -dragHandleOffset }, 27 | { bottom: -dragHandleOffset, right: -dragHandleOffset }, 28 | ]; 29 | 30 | const maxWidthDragCornerOffsets = [ 31 | { top: -dragHandleOffset, left: '50%' }, 32 | { bottom: -dragHandleOffset, left: '50%' }, 33 | ]; 34 | 35 | function EditItem(props) { 36 | const { elemData, selected } = props; 37 | const x = typeof window !== 'undefined' ? window.innerWidth / 2 : 200; 38 | const siteData = useContext(SiteContext); 39 | const { 40 | setSelected: onSelect, 41 | onUpdateDiv: onUpdated, 42 | mode, 43 | setModal, 44 | } = siteData; 45 | 46 | const [state, setState] = useState({}); 47 | const divRef = useRef(); 48 | 49 | function saveElemJson(newProps) { 50 | var updatedProps = { 51 | ...newProps, 52 | }; 53 | onUpdated(elemData.id, updatedProps); 54 | } 55 | 56 | var movementTypes = { 57 | NOOP : 0, 58 | DRAGGING : 1, 59 | ROTATING : 2, 60 | RESIZING : 3, 61 | } 62 | 63 | const [movementType, setMovementType] = useState(movementTypes.NOOP); 64 | const prevMovementType = usePrevious(movementType); 65 | 66 | useEffect(() => { 67 | if ( 68 | (movementType === movementTypes.DRAGGING || 69 | movementType === movementTypes.RESIZING || 70 | movementType === movementTypes.ROTATING) && 71 | prevMovementType !== movementType 72 | ) { 73 | typeof window !== "undefined" && document.addEventListener('mousemove', onMouseMove); 74 | typeof window !== "undefined" && document.addEventListener('mouseup', onMouseUp); 75 | typeof window !== "undefined" && document.addEventListener('touchmove', onMouseMove, { 76 | passive: false, 77 | }); 78 | typeof window !== "undefined" && document.addEventListener('touchend', onMouseUp); 79 | } 80 | }, [movementType]); 81 | 82 | // calculate relative position to the mouse and set dragging=true 83 | function onMouseDown(e) { 84 | // only left mouse button 85 | if (mode != 'edit') return; 86 | var pos = getElementOffset(divRef.current); 87 | 88 | var startX = e.pageX ? e.pageX : e.changedTouches[0].pageX; 89 | var startY = e.pageY ? e.pageY : e.changedTouches[0].pageY; 90 | 91 | var newState = { 92 | rel: { 93 | x: startX - pos.left - pos.width / 2, 94 | y: startY - pos.top - pos.height / 2, 95 | startX: pos.left, 96 | startY: pos.top, 97 | }, 98 | }; 99 | setState(newState); 100 | onSelect(elemData.id); 101 | 102 | if (isMobile() && !selected) { 103 | return; 104 | } 105 | 106 | setMovementType(movementTypes.DRAGGING); 107 | // e.stopPropagation(); 108 | // e.preventDefault(); 109 | } 110 | 111 | function onMouseDownRot(e) { 112 | var pos = getElementOffset(divRef.current); 113 | const rect = getElementOffset(divRef.current); 114 | 115 | var startX = e.pageX ? e.pageX : e.changedTouches[0].pageX; 116 | var startY = e.pageY ? e.pageY : e.changedTouches[0].pageY; 117 | 118 | const startVector = { 119 | x: startX - pos.left - pos.width / 2, 120 | y: startY - pos.top - pos.height / 2, 121 | }; 122 | 123 | setState({ ...state, rot: { startVector, center: rect.center } }); 124 | setMovementType(movementTypes.ROTATING); 125 | e.stopPropagation(); 126 | e.preventDefault(); 127 | } 128 | 129 | function onMouseDownRes(e) { 130 | const rect = getElementOffset(divRef.current); 131 | 132 | var startX = e.pageX ? e.pageX : e.changedTouches[0].pageX; 133 | var startY = e.pageY ? e.pageY : e.changedTouches[0].pageY; 134 | setState({ ...state, res: { startX, startY, rect } }); 135 | setMovementType(movementTypes.RESIZING); 136 | e.stopPropagation(); 137 | e.preventDefault(); 138 | } 139 | 140 | const [coincides, setCoincides] = useState([]); 141 | 142 | function SnapToGrid(toPosition) { 143 | if (toPosition.pos.x > -7 && toPosition.pos.x < 7) { 144 | toPosition.pos.x = 0; 145 | } 146 | 147 | var coincidingItems = []; 148 | Object.values(siteData.items).forEach((item) => { 149 | if (item.id == elemData.id) return; 150 | 151 | var xDel = toPosition.pos.x - item.pos.x, 152 | yDel = toPosition.pos.y - item.pos.y; 153 | if (xDel < itemAlignSnapDistance && xDel > -itemAlignSnapDistance) { 154 | coincidingItems.push(item); 155 | toPosition.pos.x = item.pos.x; 156 | } 157 | if (yDel < itemAlignSnapDistance && yDel > -itemAlignSnapDistance) { 158 | coincidingItems.push(item); 159 | toPosition.pos.y = item.pos.y; 160 | } 161 | }); 162 | setCoincides(coincidingItems); 163 | return toPosition; 164 | } 165 | 166 | function SnapToAngle(angle) { 167 | angle = angle % 360; 168 | let angleExcess = angle % 90; 169 | if (angleExcess > -10 && angleExcess < 10) angle -= angleExcess; 170 | return angle; 171 | } 172 | 173 | function onMouseMove(e) { 174 | e.stopPropagation(); 175 | e.preventDefault(); 176 | 177 | var clientX = e.pageX ? e.pageX : e.changedTouches[0].pageX; 178 | var clientY = e.pageY ? e.pageY : e.changedTouches[0].pageY; 179 | 180 | if (movementType == movementTypes.DRAGGING) { 181 | var toPosition = { 182 | pos: { 183 | x: 184 | (clientX - state.rel.x - x) * 185 | (1 / getMobileScaleRatio()), 186 | y: (clientY - state.rel.y) * (1 / getMobileScaleRatio()), 187 | }, 188 | }; 189 | saveElemJson(SnapToGrid(toPosition)); 190 | } else if (movementType == movementTypes.ROTATING) { 191 | const rotateVector = { 192 | x: clientX - state.rot.center.x, 193 | y: clientY - state.rot.center.y, 194 | }; 195 | let angle = 196 | elemData.rot.deg + 197 | getAngle(state.rot.startVector, rotateVector); 198 | saveElemJson({ rot: { deg: SnapToAngle(angle) } }); 199 | } else if (movementType == movementTypes.RESIZING) { 200 | var deltaX = clientX - state.res.startX; 201 | var deltaY = clientY - state.res.startY; 202 | 203 | if (state.res.startX < state.res.rect.center.x) deltaX *= -1; 204 | if (state.res.startY < state.res.rect.center.y) deltaY *= -1; 205 | 206 | const alpha = Math.atan2(deltaY, deltaX); 207 | const length = getLength(deltaX, deltaY) * 2; 208 | 209 | const beta = alpha - degToRadian(elemData.rot.deg); 210 | const deltaW = length * Math.cos(beta); 211 | const deltaH = length * Math.sin(beta); 212 | 213 | saveElemJson({ 214 | size: { 215 | width: state.res.rect.width + deltaW, 216 | height: state.res.rect.height + deltaH, 217 | }, 218 | pos: { x: elemData.pos.x, y: elemData.pos.y }, 219 | }); 220 | } else return; 221 | } 222 | 223 | function onMouseUp(e) { 224 | setMovementType(movementTypes.NOOP); 225 | 226 | typeof window !== "undefined" && document.removeEventListener('mousemove', onMouseMove); 227 | typeof window !== "undefined" && document.removeEventListener('mouseup', onMouseUp); 228 | typeof window !== "undefined" && document.removeEventListener('touchmove', onMouseMove); 229 | typeof window !== "undefined" && document.removeEventListener('touchend', onMouseUp); 230 | 231 | e.stopPropagation(); 232 | e.preventDefault(); 233 | } 234 | 235 | return ( 236 | <> 237 | {/* SECTION: ALIGNMENT GRIDS */} 238 | {selected && ( 239 | 244 | )} 245 | 246 | {/* SECTION: CONTROL PANEL */} 247 | {selected && ( 248 | <> 249 | 256 | 257 | )} 258 | 259 | {/* SECTION: DRAGGABLE RECT */} 260 | {mode == 'edit' ? ( 261 | 270 |
281 | {props.children} 282 |
283 |
284 | ) : ( 285 | 286 | {props.children} 287 | 288 | )} 289 | 290 | ); 291 | } 292 | 293 | const Rect = React.forwardRef((props, ref) => { 294 | const { 295 | elemData, 296 | selected, 297 | onMouseDownDrag, 298 | onMouseDownRes, 299 | onMouseDownRot, 300 | } = props; 301 | const { pos, size, rot, zIndex } = elemData; 302 | var x = typeof window !== 'undefined' ? window.innerWidth / 2 : 200; 303 | 304 | return ( 305 |
{ 307 | e.stopPropagation(); 308 | }} 309 | ref={ref} 310 | onMouseDown={onMouseDownDrag} 311 | onTouchStart={onMouseDownDrag} 312 | key={elemData.id + '-rect'} 313 | style={{ 314 | zIndex: zIndex, 315 | position: 'absolute', 316 | transform: `translate(-50%, -50%) ${ 317 | rot && rot.deg ? `rotate(${rot.deg}deg)` : `` 318 | }`, 319 | left: pos.x + 'px', 320 | top: pos.y + 'px', 321 | width: (size.width || 50) + 'px', 322 | height: (size.height || 50) + 'px', 323 | textAlign: 'center', 324 | }} 325 | {...props} 326 | > 327 |
337 | {props.children} 338 |
339 | 340 | {/* RESIZE AND ROTATE HANDLES */} 341 | {selected && ( 342 | <> 343 | {!elemData.maxWidth && ( 344 |
353 | )} 354 | {(elemData.maxWidth 355 | ? maxWidthDragCornerOffsets 356 | : dragCornerOffsets 357 | ).map((elem, id) => { 358 | return ( 359 |
366 | ); 367 | })} 368 | 369 | )} 370 |
371 | ); 372 | }); 373 | 374 | function LinkWrapper({ children, link }) { 375 | return link ? {children} : <>{children}; 376 | } 377 | 378 | export default EditItem; 379 | -------------------------------------------------------------------------------- /src/Components/DDEditor/MobileBoundary.js: -------------------------------------------------------------------------------- 1 | export default function MobileBoundary ({mobileWidth = 600}) { 2 | return window.innerWidth > mobileWidth && <> 3 | 4 | 5 | 6 | } 7 | 8 | function Boundary({mobileWidth, right}) { 9 | return <> 10 |
20 |
28 |
36 | ««« Not visible on phone »»» 37 |
38 |
39 | 40 | } -------------------------------------------------------------------------------- /src/Components/DDEditor/dragCoincideLines.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | export default function DrawCoincides({ elemData, coincides, dragging }) { 4 | return ( 5 | <> 6 | {dragging && elemData.pos.x == 0 && ( 7 |
15 | )} 16 | {dragging && elemData.pos.x == 0 && ( 17 |
25 | )} 26 | 27 | {dragging && 28 | coincides.length > 0 && 29 | coincides.map((coincide) => { 30 | return ( 31 | <> 32 |
39 |
46 | {coincide.pos.y == elemData.pos.y && ( 47 |
64 | )} 65 | {coincide.pos.x == elemData.pos.x && ( 66 |
83 | )} 84 | 85 | ); 86 | })} 87 | 88 | ); 89 | } 90 | -------------------------------------------------------------------------------- /src/Components/DraggableButton/ButtonSelector.js: -------------------------------------------------------------------------------- 1 | import React, { useRef, useState } from "react"; 2 | import { Row } from "../../utils/helpers"; 3 | 4 | const linkRegEx = new RegExp(/^(?:(?:https?|ftp):\/\/)(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,}))\.?)(?::\d{2,5})?(?:[/?#]\S*)?$/i); 5 | 6 | export function ButtonSelector(props) { 7 | const inputRef = useRef(null); 8 | const [value, setValue] = useState('https://'); 9 | 10 | // test value using regex to see if it is a valid url 11 | const isValidUrl = linkRegEx.test(value); 12 | 13 | return ( 14 | <> 15 |
ADD A LINK BUTTON
16 |
17 | { 32 | setValue(e.target.value); 33 | }} 34 | > 35 | 52 |
53 |
54 |
55 | 56 | {[ 57 | ["https://", "fas fa-link", "Any Link"], 58 | ["https://instagram.com/", "fab fa-instagram", "Instagram"], 59 | ["https://twitter.com/", "fab fa-twitter", "Twitter"], 60 | ["https://discord.gg/", "fab fa-discord", "Discord"], 61 | ["https://www.youtube.com/channel/", "fab fa-youtube", "Youtube"], 62 | ["https://paypal.me/", "fab fa-paypal", "Paypal"], 63 | ["https://linkedin.com/", "fab fa-linkedin", "LinkedIn"]].map((elem) => {return <> 64 |
{setValue(elem[0]); inputRef.current.focus();}}> 65 | 66 | {elem[2]} 67 |
68 | })} 69 |
70 | 71 | ); 72 | } -------------------------------------------------------------------------------- /src/Components/DraggableButton/PanelControls.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect, useContext } from "react"; 2 | import { Input } from '../../utils/helpers'; 3 | import ColorPicker from '../../utils/ui/ColorPicker'; 4 | import SiteContext from '../../pageContext'; 5 | 6 | export default function PanelControls({ elemData, setPanelControls }) { 7 | const siteData = useContext(SiteContext); 8 | const { setSelected: onSelect, onUpdateDiv: onUpdated, mode, setModal } = siteData; 9 | 10 | function onLocalUpdate(newProps) { 11 | var updatedProps = { 12 | ...newProps, 13 | }; 14 | onUpdated(elemData.id, updatedProps); 15 | } 16 | 17 | const [colorPickerActive, setColorPickerActive] = useState(false); 18 | useEffect(() => { 19 | if (!colorPickerActive) { 20 | setPanelControls(null); 21 | } 22 | }, [colorPickerActive]); 23 | 24 | return ( 25 | <> 26 |
{ 28 | if (!colorPickerActive) { 29 | setColorPickerActive(true); 30 | setPanelControls( 31 | { 37 | onLocalUpdate({...{style:{backgroundColor: color}}}); 38 | }} 39 | onClose={() => { 40 | }} 41 | />, 42 | ); 43 | } else { 44 | setColorPickerActive(false); 45 | } 46 | }} 47 | style={{ 48 | borderRadius: 5, 49 | width: 25, 50 | height: 25, 51 | backgroundColor: elemData.style && elemData.style.backgroundColor, 52 | border: '1px solid black', 53 | marginRight: '10px', 54 | }} 55 | >
56 | { 61 | onLocalUpdate({ label: value }); 62 | }} 63 | /> 64 | 65 | ); 66 | } -------------------------------------------------------------------------------- /src/Components/DraggableButton/index.js: -------------------------------------------------------------------------------- 1 | import React, { useContext, useEffect, useRef, useState } from 'react'; 2 | import EditItem from '../DDEditor/EditItem'; 3 | import SiteContext from '../../pageContext'; 4 | import PanelControls from './PanelControls'; 5 | import { isDarkColor } from '../../utils/helpers'; 6 | 7 | function DraggableButton(props) { 8 | const { elemData, selected } = props; 9 | 10 | const siteData = useContext(SiteContext); 11 | const { 12 | setSelected: onSelect, 13 | onUpdateDiv: onUpdated, 14 | mode, 15 | setModal, 16 | } = siteData; 17 | 18 | return ( 19 | <> 20 | 29 | { 30 | 46 | } 47 | 48 | 49 | ); 50 | } 51 | 52 | export default DraggableButton; 53 | -------------------------------------------------------------------------------- /src/Components/DraggableCrypto.js: -------------------------------------------------------------------------------- 1 | import React, { useContext, useState } from 'react'; 2 | import EditItem from './DDEditor/EditItem'; 3 | import ColorPicker from '../utils/ui/ColorPicker'; 4 | import SiteContext from '../pageContext'; 5 | 6 | function DraggableCrypto(props) { 7 | const { elemData, selected } = props; 8 | const siteData = useContext(SiteContext); 9 | const { 10 | setSelected: onSelect, 11 | onUpdateDiv: onUpdated, 12 | mode, 13 | setModal, 14 | } = siteData; 15 | 16 | function onLocalUpdate(newProps) { 17 | var updatedProps = { 18 | ...newProps, 19 | }; 20 | onUpdated(elemData.id, updatedProps); 21 | } 22 | 23 | function setText(text) { 24 | onLocalUpdate({ 25 | text: text 26 | }); 27 | } 28 | 29 | function PanelControls({ setPanelControls }) { 30 | const [active, setActive] = useState(false); 31 | return ( 32 | <> 33 | {setText(e.target.value)}} /> 34 | 35 | ); 36 | } 37 | 38 | return ( 39 | <> 40 | 50 |
53 | 54 | 55 | ); 56 | } 57 | 58 | export default DraggableCrypto; 59 | -------------------------------------------------------------------------------- /src/Components/DraggableDiv.js: -------------------------------------------------------------------------------- 1 | import React, { useContext, useState } from 'react'; 2 | import EditItem from './DDEditor/EditItem'; 3 | import ColorPicker from '../utils/ui/ColorPicker'; 4 | import SiteContext from '../pageContext'; 5 | 6 | function DraggableImage(props) { 7 | const { elemData, selected } = props; 8 | const siteData = useContext(SiteContext); 9 | const { 10 | setSelected: onSelect, 11 | onUpdateDiv: onUpdated, 12 | mode, 13 | setModal, 14 | } = siteData; 15 | 16 | function onLocalUpdate(newProps) { 17 | var updatedProps = { 18 | ...newProps, 19 | }; 20 | onUpdated(elemData.id, updatedProps); 21 | } 22 | 23 | function setBgColor(color) { 24 | onLocalUpdate({ 25 | style: { 26 | ...elemData.style, 27 | backgroundColor: color, 28 | }, 29 | }); 30 | } 31 | 32 | function setBorderColor(color) { 33 | onLocalUpdate({ 34 | style: { 35 | ...elemData.style, 36 | borderColor: color, 37 | }, 38 | }); 39 | } 40 | 41 | function PanelControls({ setPanelControls }) { 42 | const [active, setActive] = useState(false); 43 | return ( 44 | <> 45 |
{ 47 | if (!active) { 48 | setActive(true); 49 | setPanelControls( 50 | <> 51 |
52 | { 58 | setBgColor(color); 59 | }} 60 | onClose={() => { 61 | setActive(false); 62 | setPanelControls(null); 63 | }} 64 | /> 65 |
66 | , 67 | ); 68 | } else { 69 | setActive(false); 70 | setPanelControls(); 71 | } 72 | }} 73 | style={{ 74 | borderRadius: 5, 75 | width: 25, 76 | height: 25, 77 | backgroundColor: elemData.style &&elemData.style.backgroundColor, 78 | border: '2px solid black', 79 | }} 80 | >
81 |
82 |
{ 84 | if (!active) { 85 | setActive(true); 86 | setPanelControls( 87 | <> 88 |
89 | { 95 | setBorderColor(color); 96 | }} 97 | onClose={() => { 98 | setActive(false); 99 | setPanelControls(null); 100 | }} 101 | /> 102 |
103 | , 104 | ); 105 | } else { 106 | setActive(false); 107 | setPanelControls(); 108 | } 109 | }} 110 | style={{ 111 | borderRadius: 5, 112 | width: 25, 113 | height: 25, 114 | borderStyle: 'solid', 115 | borderColor: elemData.style && elemData.style.borderColor, 116 | borderWidth: 2, 117 | }} 118 | >
119 | 120 | ); 121 | } 122 | 123 | return ( 124 | <> 125 | 134 |
137 | 138 | 139 | ); 140 | } 141 | 142 | export default DraggableImage; 143 | -------------------------------------------------------------------------------- /src/Components/DraggableGiphy/GiphySelector.js: -------------------------------------------------------------------------------- 1 | import ReactGiphySearchbox from 'react-giphy-searchbox'; 2 | import React from 'react' 3 | 4 | export function GiphySelector({ addItemToList }) { 5 | return ( 6 | { 14 | addItemToList({ 15 | type: 'giphy', 16 | size: { 17 | width: 100, 18 | height: 100, 19 | }, 20 | giphyUri: item.id, 21 | }); 22 | }} 23 | masonryConfig={[ 24 | { columns: 2, imageWidth: 110, gutter: 5 }, 25 | { mq: '700px', columns: 3, imageWidth: 110, gutter: 5 }, 26 | ]} 27 | /> 28 | ); 29 | } 30 | -------------------------------------------------------------------------------- /src/Components/DraggableGiphy/index.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import EditItem from '../DDEditor/EditItem'; 3 | import { GiphyFetch } from '@giphy/js-fetch-api'; 4 | import { useAsync } from 'react-async-hook'; 5 | 6 | const giphyFetch = new GiphyFetch( 7 | process.env.GIPHY_API_KEY 8 | ? process.env.GIPHY_API_KEY 9 | : '6s6dfi1SuYlcbne91afF4rsD1b2DFDfQ', 10 | ); 11 | 12 | const mediaGiphyRegex = /media\d+.giphy.com/; 13 | 14 | function DraggableGiphy(props) { 15 | const { elemData, onSelect, onUpdated, selected, mode } = props; 16 | 17 | const [gif, setGif] = useState(null); 18 | useAsync(async () => { 19 | const { data } = await giphyFetch.gif(elemData.giphyUri); 20 | const tempGif = data.images.preview_gif; 21 | const finalGif = { ...tempGif }; 22 | finalGif['url'] = tempGif.url.replace(mediaGiphyRegex, 'i.giphy.com'); 23 | console.log('giphyUri', finalGif); 24 | setGif(finalGif); 25 | }, []); 26 | return ( 27 | <> 28 | 36 | {gif && ( 37 | 41 | )} 42 | 43 | 44 | ); 45 | } 46 | export default DraggableGiphy; 47 | -------------------------------------------------------------------------------- /src/Components/DraggableHtml/PanelControls.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import Editor from '@monaco-editor/react'; 3 | 4 | export default function PanelControls({ onLocalUpdate, elemData, setModal }) { 5 | function CodeEditor() { 6 | const [fileType, setFileType] = useState(elemData.subtype || 'md'); 7 | const [html, setHtml] = useState(''); 8 | const [js, setJs] = useState(''); 9 | 10 | const languages = { md: 'markdown', html: 'html', js: 'javascript' }; 11 | 12 | return ( 13 | <> 14 |
15 |
23 |
24 | {fileType !== 'md' && ( 25 | <> 26 | 35 | 36 | 37 | 38 | 47 | 48 | )} 49 | {fileType === 'md' && ( 50 | 59 | )} 60 |
61 |
62 |
63 | 86 |
87 | 88 |
{ 94 | setModal(null); 95 | }} 96 | > 97 | 98 |
99 |
100 |
101 | 102 |
103 | 104 | { 118 | let data = {}; 119 | if (fileType === 'md') { 120 | data = { text: v, subtype: 'md' }; 121 | } else if (fileType === 'html') { 122 | data = { text: v, subtype: 'html' }; 123 | } else if (fileType === 'js') { 124 | data = { js: v, subtype: 'html' }; 125 | } 126 | console.log(data); 127 | onLocalUpdate(data); 128 | }} 129 | /> 130 |
131 | 132 | ); 133 | } 134 | 135 | return ( 136 | <> 137 | 152 | 153 |
154 |
{ 157 | onLocalUpdate({ maxWidth: !elemData.maxWidth }); 158 | }} 159 | > 160 | 161 |
162 | 163 | ); 164 | } 165 | -------------------------------------------------------------------------------- /src/Components/DraggableHtml/index.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useRef, useContext } from 'react'; 2 | import EditItem from '../DDEditor/EditItem'; 3 | 4 | import SiteContext from '../../pageContext'; 5 | import ReactMarkdown from 'react-markdown'; 6 | import rehypeRaw from 'rehype-raw'; 7 | 8 | import PanelControls from './PanelControls'; 9 | 10 | const defaultTextSize = 24; 11 | 12 | function DraggableHtml(props) { 13 | const { elemData, selected } = props; 14 | 15 | const siteData = useContext(SiteContext); 16 | const { 17 | setSelected: onSelect, 18 | onUpdateDiv: onUpdated, 19 | mode, 20 | setModal, 21 | } = siteData; 22 | 23 | function onLocalUpdate(newProps) { 24 | var updatedProps = { 25 | ...newProps, 26 | }; 27 | siteData.onUpdateDiv(elemData.id, updatedProps); 28 | } 29 | 30 | return ( 31 | <> 32 | 41 | {elemData.subtype == "html" &&
} 44 | {elemData.subtype == "md" &&
45 | 50 |
} 51 |
52 | 53 | ); 54 | } 55 | 56 | export default DraggableHtml; 57 | 58 | 59 | -------------------------------------------------------------------------------- /src/Components/DraggableImage.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect, useContext } from 'react'; 2 | import { Input } from '../utils/helpers'; 3 | import SiteContext from '../pageContext'; 4 | import EditItem from './DDEditor/EditItem'; 5 | 6 | function DraggableImage(props) { 7 | const { elemData, selected } = props; 8 | const siteData = useContext(SiteContext); 9 | const { setSelected: onSelect, onUpdateDiv: onUpdated, mode, setModal } = siteData; 10 | 11 | function onLocalUpdate(newProps) { 12 | var updatedProps = { 13 | ...newProps, 14 | }; 15 | onUpdated(elemData.id, updatedProps); 16 | } 17 | 18 | function setImageUri(uri) { 19 | onLocalUpdate({ imageUri: uri }); 20 | } 21 | 22 | function toDataURL(src, callback, outputFormat) { 23 | var img = new Image(); 24 | img.crossOrigin = 'Anonymous'; 25 | img.onload = function () { 26 | var canvas = typeof window !== "undefined" && document.createElement('CANVAS'); 27 | // @ts-expect-error TODO: getContext exists on canvas, investigate 28 | var ctx = canvas.getContext('2d'); 29 | var dataURL; 30 | // @ts-expect-error TODO: naturalHeight exists on canvas, investigate 31 | canvas.height = this.naturalHeight; 32 | // @ts-expect-error TODO: naturalWidth exists on canvas, investigate 33 | canvas.width = this.naturalWidth; 34 | ctx.drawImage(this, 0, 0); 35 | // @ts-expect-error TODO: toDateURL exists on canvas, investigate 36 | dataURL = canvas.toDataURL(outputFormat); 37 | callback(dataURL); 38 | return dataURL; 39 | }; 40 | img.src = src; 41 | if (img.complete || img.complete === undefined) { 42 | img.src = 43 | 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw=='; 44 | img.src = src; 45 | } 46 | } 47 | 48 | async function loadImageToUri() { 49 | // @ts-expect-error TODO: window fs access not allowed in strict 50 | const [file] = await window.showOpenFilePicker(); 51 | const locFile = await file.getFile(); 52 | console.log(locFile); 53 | const stream = await locFile.arrayBuffer(); 54 | console.log(stream); 55 | var blob = new Blob([stream], { type: locFile.type }); 56 | var urlCreator = window.URL || window.webkitURL; 57 | var imageUrl = urlCreator.createObjectURL(blob); 58 | 59 | toDataURL( 60 | imageUrl, 61 | (dataUrl) => { 62 | console.log(dataUrl); 63 | setImageUri(dataUrl); 64 | }, 65 | locFile.type, 66 | ); 67 | } 68 | 69 | function PanelControls() { 70 | return ( 71 | <> 72 | { 76 | setImageUri(value); 77 | }} 78 | /> 79 | 80 |
{ 83 | loadImageToUri(); 84 | }} 85 | > 86 | 94 |
95 | 96 |
{ 100 | onLocalUpdate({ maxWidth: !elemData.maxWidth }); 101 | }} 102 | > 103 | 104 |
105 | 106 | ); 107 | } 108 | 109 | return ( 110 | <> 111 | 119 | {!elemData.imageUri ? ( 120 |
Set an image URL
121 | ) : ( 122 | 126 | )} 127 |
128 | 129 | ); 130 | } 131 | 132 | export default DraggableImage; 133 | -------------------------------------------------------------------------------- /src/Components/DraggableText/PanelControls.js: -------------------------------------------------------------------------------- 1 | import DropDownMenu from '../../utils/ui/DropDownMenu'; 2 | // import fonts from '../../helpers/ui/fonts.json'; 3 | // import ColorPicker from '../../helpers/ui/ColorPicker'; 4 | // import analytics from '../../../../util/analytics'; 5 | import React, { useEffect, useState } from 'react'; 6 | 7 | // const googleFonts = fonts['googleFonts']; 8 | const fontList = ['Arial', 'Times New Roman', 'Courier New',]; 9 | 10 | export default function PanelControls({ 11 | onLocalUpdate, 12 | elemData, 13 | setPanelControls, 14 | }) { 15 | // const [colorPickerActive, setColorPickerActive] = useState(false); 16 | // // effect to set panel controls to null 17 | // useEffect(() => { 18 | // if (!colorPickerActive) { 19 | // setPanelControls(null); 20 | // } 21 | // }, [colorPickerActive]); 22 | 23 | let alignDirections = ['left', 'center', 'right']; 24 | let alignIcon = ['align-left', 'align-center', 'align-right']; 25 | let currentDirection = alignDirections.indexOf(elemData.style && elemData.style.textAlign); 26 | currentDirection = currentDirection < 0 ? 1 : currentDirection; 27 | return ( 28 | <> 29 | {/*
{ 31 | if(!colorPickerActive){ 32 | setPanelControls( 33 | <> 34 |
35 | { 41 | console.log("color", color); 42 | onLocalUpdate({ style: {...elemData.style, color: color} }); 43 | }} 44 | onClose={() => { 45 | // setPanelControls(null); 46 | }} 47 | /> 48 |
49 | , 50 | ); 51 | setColorPickerActive(true); 52 | } else { 53 | setColorPickerActive(false); 54 | } 55 | }} 56 | style={{ 57 | borderRadius: 100, 58 | width: 25, 59 | height: 25, 60 | backgroundColor: elemData.style && elemData.style.color, 61 | border: '2px solid black', 62 | cursor: 'pointer', 63 | }} 64 | >
*/} 65 |
66 | { 71 | onLocalUpdate({ style: {fontFamily: font} }); 72 | }} 73 | type={'font'} 74 | /> 75 |
76 | { 80 | onLocalUpdate({ style: {fontSize: selectedValue + 'px' }}); 81 | }} 82 | /> 83 |
84 | 85 |
86 | { 89 | elemData.style && elemData.style.fontWeight === 'bold'? 90 | onLocalUpdate({style: {fontWeight: 'normal'}}): 91 | onLocalUpdate({style: {fontWeight: 'bold'}}); 92 | 93 | }} 94 | /> 95 |
96 |
97 | 98 |
99 | { 102 | elemData.style && elemData.style.fontStyle === 'italic'? 103 | onLocalUpdate({style: {fontStyle: 'normal'}}): 104 | onLocalUpdate({style: {fontStyle: 'italic'}}); 105 | 106 | }} 107 | /> 108 |
109 |
110 | 111 |
112 | { 115 | onLocalUpdate({ 116 | style: { 117 | textAlign: 118 | alignDirections[ 119 | (currentDirection + 1) % 120 | alignDirections.length 121 | ], 122 | }, 123 | }); 124 | }} 125 | />
126 |
127 | 128 | ); 129 | } 130 | -------------------------------------------------------------------------------- /src/Components/DraggableText/index.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useRef, useContext, useEffect } from 'react'; 2 | import EditItem from '../DDEditor/EditItem'; 3 | import SiteContext from '../../pageContext'; 4 | import PanelControls from './PanelControls' 5 | 6 | const defaultTextSize = 24; 7 | 8 | const fontList = ['Arial', 'Times New Roman', 'Courier New']; 9 | 10 | function DraggableText(props) { 11 | const { elemData, mode, selected } = props; 12 | const siteData = useContext(SiteContext); 13 | 14 | const fontSource = !fontList.includes(elemData.style &&elemData.style.fontFamily) 15 | ? 'google' 16 | : ''; 17 | 18 | function onLocalUpdate(newProps) { 19 | var updatedProps = { 20 | ...newProps, 21 | }; 22 | console.log(updatedProps) 23 | siteData.onUpdateDiv(elemData.id, updatedProps); 24 | } 25 | 26 | return ( 27 | <> 28 | 35 | {fontSource == 'google' && ( 36 | <> 37 | 41 | 45 | 51 | 52 | )} 53 | { 58 | onLocalUpdate({ text: text }); 59 | }} 60 | style={{ 61 | ...elemData.style, 62 | }} 63 | /> 64 | 65 | 66 | ); 67 | } 68 | 69 | export default DraggableText; 70 | 71 | function EditableDiv(props) { 72 | const { value, contentEditable, onChange, style } = props; 73 | const [text, setText] = useState(value); 74 | const [mobileEditing, setMobileEditing] = useState(false); 75 | const [cursor, setCursor] = useState(null); 76 | const [dragMove, setDragMove] = useState(false); 77 | const inputRef = useRef(); 78 | const inputFakeRef = useRef(null); 79 | 80 | useEffect(()=>{ 81 | if(mobileEditing) { 82 | inputFakeRef.current.focus(); 83 | } 84 | }, [mobileEditing]) 85 | 86 | function emitChange() { 87 | var value = inputRef.current.innerHTML; 88 | onChange && onChange(value); 89 | } 90 | 91 | function onPaste(e) { 92 | e.preventDefault(); 93 | var text = e.clipboardData.getData('text/plain'); 94 | document.execCommand('insertHtml', false, text); 95 | } 96 | 97 | return (<> 98 | {!mobileEditing &&
{ 105 | !contentEditable && setDragMove(true); 106 | }} 107 | onTouchMove={()=> { 108 | setDragMove(true); 109 | }} 110 | onTouchEndCapture={()=>{ 111 | contentEditable && !dragMove && setMobileEditing(true); 112 | setDragMove(false); 113 | }} 114 | onFocus={() => { 115 | setCursor('pointer'); 116 | }} 117 | // onBlur={() => { 118 | // setCursor(undefined); 119 | // }} 120 | style={{ cursor: cursor, ...style }} 121 | > 122 | {text} 123 |
} 124 | {mobileEditing &&
125 | 131 |
} 132 | 133 | ); 134 | } 135 | -------------------------------------------------------------------------------- /src/DragDrop.js: -------------------------------------------------------------------------------- 1 | import React, {useState, useEffect, useCallback} from 'react'; 2 | import GenericModal from './utils/ui/GenericModal'; 3 | import { Column, debounce, getMobileScaleRatio, guidGenerator, mergeDeep } from './utils/helpers'; 4 | import SiteContext from './pageContext'; 5 | import MobileBoundary from './utils/ui/MobileBoundary'; 6 | import Menu from './EditMenu/EditMenu'; 7 | import ComponentSelector from './Components/ComponentSelector'; 8 | 9 | export var EditorModes = { 10 | EDIT: 'edit', 11 | VIEW: 'view', 12 | } 13 | 14 | function DragDrop({ 15 | immutable= false, 16 | saveCallback, 17 | onChangedCallback, 18 | initialState, 19 | pending, 20 | }) { 21 | const [items, setItems] = useState(initialState || {}); 22 | const [selected, setSelected] = useState([]); 23 | const [mode, setMode] = useState(EditorModes.VIEW); 24 | const [modal, setModal] = useState(null); 25 | 26 | const [pastItems, setPastItems] = useState([initialState || {}]); 27 | const [undoCount, setUndoCount] = useState(0); 28 | 29 | const [pageHeight, setPageHeight] = useState(0); 30 | const [pageWidth, setPageWidth] = useState(0); 31 | 32 | function deleteItemFromList(key) { 33 | var newItems = items; 34 | newItems[key] && delete newItems[key]; 35 | setItems(newItems); 36 | debounceElemdataHistoryUpdate(pastItems, newItems, undoCount); 37 | setSelected([]); 38 | } 39 | 40 | function onUpdateDiv(divId, newProps) { 41 | var oldItems = items[divId] || {}; 42 | const updatedItems = { 43 | ...items, 44 | [divId]: {...mergeDeep(oldItems, newProps)}, 45 | }; 46 | debounceElemdataHistoryUpdate(pastItems, updatedItems, undoCount); 47 | setItems(updatedItems); 48 | } 49 | 50 | function undo(e) { 51 | setItems(pastItems[pastItems.length - 1 - (undoCount + 1)]); 52 | setUndoCount(undoCount + 1); 53 | 54 | e.stopPropagation(); 55 | } 56 | 57 | function redo(e) { 58 | setItems(pastItems[pastItems.length - 1 - (undoCount - 1)]); 59 | setUndoCount(undoCount - 1); 60 | 61 | e.stopPropagation(); 62 | } 63 | 64 | function onSaveClicked() { 65 | if (!immutable) { 66 | setMode(EditorModes.VIEW); 67 | saveCallback && saveCallback(items); 68 | } 69 | } 70 | 71 | function onEditClicked() { 72 | setMode(EditorModes.EDIT); 73 | } 74 | 75 | function addItemToList(data, id) { 76 | var newItem = { 77 | id: id || guidGenerator(), 78 | pos: { x: 200, y: 200 }, 79 | rot: { deg: 0 }, 80 | zIndex: 10000 + Object.keys(items).length, 81 | type: 'text', 82 | ...data, 83 | }; 84 | const updatedItems = { ...items, [newItem.id]: newItem }; 85 | setItems(updatedItems); 86 | debounceElemdataHistoryUpdate(pastItems, updatedItems, undoCount); 87 | setSelected([newItem.id]); 88 | } 89 | 90 | const debounceElemdataHistoryUpdate = useCallback( 91 | debounce((oldItemsList, newItem, undoCount) => { 92 | if (undoCount > 0) { 93 | setPastItems([...oldItemsList.slice(0, -undoCount), newItem]); 94 | setUndoCount(0); 95 | } else { 96 | setPastItems([...oldItemsList, newItem]); 97 | } 98 | onChangedCallback && onChangedCallback(newItem); 99 | }, 1000), 100 | [], 101 | ); 102 | 103 | 104 | const providerValues = { 105 | items: items, 106 | selected: selected, 107 | setSelected: (item) => { 108 | setSelected([item]); 109 | // setSelected([...selected, item]) 110 | }, 111 | deleteItemFromList: deleteItemFromList, 112 | addItemToList: addItemToList, 113 | onUpdateDiv: onUpdateDiv, 114 | mode: mode, 115 | setModal: setModal, 116 | }; 117 | 118 | return ( 119 | <> 120 | 121 | 122 |
{ 131 | console.log('bg got clicked now'); 132 | setSelected(['bg']); 133 | console.log(e); 134 | }} 135 | > 136 |
137 |
147 | {Object.keys(items).map((key) => { 148 | var elem = items[key]; 149 | if ( 150 | elem.pos.y + elem.size.height / 2 > 151 | pageHeight 152 | ) { 153 | setPageHeight( 154 | elem.pos.y + elem.size.height / 2, 155 | ); 156 | } 157 | return ( 158 | 163 | ); 164 | })} 165 |
166 |
167 |
168 | 169 | {modal && <> 170 | { 173 | setModal(null); 174 | }} 175 | /> 176 | } 177 | 178 | 179 | 180 | 181 | 182 | 183 | 270 | 271 | 272 | 273 |
274 | 275 | ); 276 | } 277 | 278 | export default DragDrop; 279 | -------------------------------------------------------------------------------- /src/EditMenu/ControlPanel.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useContext } from 'react'; 2 | import { guidGenerator } from '../utils/helpers'; 3 | import SiteContext from '../pageContext'; 4 | 5 | function DefaultControlPanel({ 6 | saveElemJson, 7 | elemData, 8 | setModal, 9 | CustomPanel, 10 | onLocalUpdate, 11 | }) { 12 | const {deleteItemFromList, addItemToList} = useContext(SiteContext); 13 | const [secondaryPanel, setSecondaryPanel] = useState(null); 14 | 15 | const handleEvent = (e) => { 16 | e.stopPropagation(); 17 | }; 18 | 19 | return ( 20 | <> 21 |
34 |
40 | {secondaryPanel && ( 41 |
{e.stopPropagation();}} 43 | className={'cpanel cpanel-shadow'} 44 | style={{ 45 | padding: 10, 46 | marginBottom: 5, 47 | width: 'fit-content', 48 | position: 'relative', 49 | }} 50 | > 51 | {secondaryPanel} 52 |
53 | )} 54 |
61 |
62 | {CustomPanel && ( 63 | 69 | )} 70 | {CustomPanel && ( 71 |
79 | )} 80 |
{ 83 | setModal( 84 | { 87 | saveElemJson({ href: data }); 88 | setModal(null); 89 | }} 90 | />, 91 | ); 92 | }} 93 | > 94 | 95 |
96 |
{ 99 | saveElemJson({ 100 | zIndex: elemData.zIndex + 1000, 101 | }); 102 | }} 103 | > 104 | 105 |
106 |
{ 109 | saveElemJson({ 110 | zIndex: elemData.zIndex - 1000, 111 | }); 112 | }} 113 | > 114 | 115 |
116 |
{ 119 | addItemToList({...elemData, 120 | pos: {x: elemData.pos.x + 10, y: elemData.pos.y + 10}, 121 | id: new guidGenerator() 122 | }); 123 | }} 124 | > 125 | 126 |
127 |
{ 130 | deleteItemFromList(elemData.id); 131 | }} 132 | > 133 | 137 |
138 |
139 |
140 |
141 |
142 | 143 | ); 144 | } 145 | 146 | function UriInputModal(props) { 147 | console.log(props); 148 | const [value, setValue] = useState(props.prefill || 'https://'); 149 | return ( 150 |
151 | { 157 | setValue(e.target.value); 158 | }} 159 | > 160 | 168 |
169 |
170 | ); 171 | } 172 | 173 | export default DefaultControlPanel; 174 | -------------------------------------------------------------------------------- /src/EditMenu/EditMenu.js: -------------------------------------------------------------------------------- 1 | import React, { useContext, useEffect, useState } from 'react'; 2 | import { Column, Row } from '../utils/helpers'; 3 | import defaultButtons from './defaultButtons'; 4 | import { v4 as uuidv4 } from 'uuid'; 5 | import { GiphySelector } from '../Components/DraggableGiphy/GiphySelector'; 6 | // import { HeadConfigurator } from '../NextHead'; 7 | import { ButtonSelector } from '../Components/DraggableButton/ButtonSelector'; 8 | import SiteContext from '../pageContext'; 9 | // import { TemplateSelector } from '../DraggableTemplate'; 10 | 11 | export function AddButton({ item, showMenu, setSelector }) { 12 | const siteData = useContext(SiteContext); 13 | 14 | const SELECTORS = { 15 | giphy: , 16 | // headconf: , 17 | }; 18 | 19 | const FUNCS = { 20 | button: ( 21 | siteData.setModal(null)} 24 | /> 25 | ), 26 | // template: ( 27 | // siteData.setModal(null)} 30 | // /> 31 | // ), 32 | }; 33 | 34 | return ( 35 |
{ 38 | switch (item[1].action) { 39 | case 'add': 40 | siteData.addItemToList(item[1].object); 41 | showMenu(null); 42 | break; 43 | case 'menu': 44 | showMenu(item[1].objects); 45 | break; 46 | case 'selector': 47 | setSelector(SELECTORS[item[1].selector]); 48 | break; 49 | case 'modal': 50 | siteData.setModal(FUNCS[item[1].selector]); 51 | break; 52 | } 53 | e.stopPropagation(); 54 | }} 55 | > 56 | {item[1].label && ( 57 | {item[1].label} 58 | )} 59 | 60 | 61 |
62 | ); 63 | } 64 | 65 | function Menu({ addItemToList, selected }) { 66 | const siteData = useContext(SiteContext); 67 | 68 | return ( 69 |
{ 72 | e.stopPropagation(); 73 | }} 74 | > 75 | 76 | 81 | 82 |
83 | ); 84 | } 85 | 86 | function NestedMenu({ data, addItemToList, parentSelected }) { 87 | const [selected, setSelected] = useState(null); 88 | const [selector, setSelector] = useState(null); 89 | 90 | useEffect(() => { 91 | setSelected(null); 92 | setSelector(null); 93 | }, [parentSelected]); 94 | 95 | useEffect(() => { 96 | setSelector(null); 97 | }, [selected]); 98 | 99 | return ( 100 | <> 101 | {selector && {selector}} 102 | {/* todo make this truly recursive by adding editmenu again */} 103 | {selected != null && ( 104 | 109 | )} 110 | 111 | {Object.entries(data).map((item) => { 112 | return ( 113 | 119 | ); 120 | })} 121 | 122 | 123 | ); 124 | } 125 | 126 | export default Menu; 127 | -------------------------------------------------------------------------------- /src/EditMenu/defaultButtons.js: -------------------------------------------------------------------------------- 1 | const defaultButtons = { 2 | text: { 3 | icon: 'fas fa-font', 4 | label: 'Add Text', 5 | action: 'add', 6 | object: { 7 | type: 'text', 8 | text: 'click to edit!', 9 | fontSize: '48px', 10 | color: 'black', 11 | size: { 12 | width: 200, 13 | height: 100, 14 | }, 15 | }, 16 | }, 17 | button: { 18 | icon: 'fas fa-link', 19 | action: 'modal', 20 | selector: 'button', 21 | label: 'Add Button', 22 | }, 23 | shapes: { 24 | icon: 'fas fa-shapes', 25 | label: 'Add Shape', 26 | action: 'menu', 27 | objects: { 28 | square: { 29 | icon: 'fas fa-square-full', 30 | label: 'Rectangle', 31 | action: 'add', 32 | object: { 33 | type: 'color', 34 | size: { 35 | width: 100, 36 | height: 100, 37 | }, 38 | style: { 39 | backgroundColor: 'grey', 40 | }, 41 | }, 42 | }, 43 | circle: { 44 | icon: 'fas fa-circle', 45 | label: 'Circle', 46 | action: 'add', 47 | object: { 48 | type: 'color', 49 | size: { 50 | width: 100, 51 | height: 100, 52 | }, 53 | style: { 54 | backgroundColor: 'blue', 55 | borderRadius: 9999999, 56 | }, 57 | }, 58 | }, 59 | }, 60 | }, 61 | media: { 62 | icon: 'fas fa-photo-video', 63 | label: 'Add Media', 64 | action: 'menu', 65 | objects: { 66 | image: { 67 | icon: 'fas fa-image', 68 | label: 'Add Image', 69 | action: 'add', 70 | object: { 71 | type: 'image', 72 | size: { 73 | width: 100, 74 | height: 100, 75 | }, 76 | imageUri: 77 | 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9Ii0xMS41IC0xMC4yMzE3NCAyMyAyMC40NjM0OCI+CiAgPHRpdGxlPlJlYWN0IExvZ288L3RpdGxlPgogIDxjaXJjbGUgY3g9IjAiIGN5PSIwIiByPSIyLjA1IiBmaWxsPSIjNjFkYWZiIi8+CiAgPGcgc3Ryb2tlPSIjNjFkYWZiIiBzdHJva2Utd2lkdGg9IjEiIGZpbGw9Im5vbmUiPgogICAgPGVsbGlwc2Ugcng9IjExIiByeT0iNC4yIi8+CiAgICA8ZWxsaXBzZSByeD0iMTEiIHJ5PSI0LjIiIHRyYW5zZm9ybT0icm90YXRlKDYwKSIvPgogICAgPGVsbGlwc2Ugcng9IjExIiByeT0iNC4yIiB0cmFuc2Zvcm09InJvdGF0ZSgxMjApIi8+CiAgPC9nPgo8L3N2Zz4K', 78 | }, 79 | }, 80 | // video: { 81 | // icon: 'fas fa-film', 82 | // label: 'Add Video', 83 | // action: 'add', 84 | // object: { 85 | // type: 'video', 86 | // size: { 87 | // width: 100, 88 | // height: 100, 89 | // }, 90 | // videoUri: 'https://www.youtube.com/watch?v=dQw4w9WgXcQ', 91 | // }, 92 | // }, 93 | // audio: { 94 | // icon: 'fas fa-volume-up', 95 | // label: 'Add Audio', 96 | // action: 'add', 97 | // object: { 98 | // type: 'audio', 99 | // size: { 100 | // width: 200, 101 | // height: 100, 102 | // }, 103 | // audioUri: 104 | // 'https://upload.wikimedia.org/wikipedia/commons/c/c8/Example.ogg', 105 | // }, 106 | // }, 107 | }, 108 | }, 109 | // form: { 110 | // icon: 'fas fa-poll-h', 111 | // label: 'Add Form', 112 | // action: 'add', 113 | // object: { 114 | // type: 'form', 115 | // size: { 116 | // width: 200, 117 | // height: 150, 118 | // }, 119 | // style: { textAlign: 'left' }, 120 | // }, 121 | // }, 122 | giphy: { 123 | icon: 'far fa-laugh-beam', 124 | action: 'selector', 125 | selector: 'giphy', 126 | label: 'Add Sticker', 127 | }, 128 | code2: { 129 | icon: 'fas fa-code', 130 | label: 'Add Code', 131 | action: 'add', 132 | object: { 133 | type: 'crypto', 134 | size: { 135 | width: 100, 136 | height: 100, 137 | }, 138 | text: 'Add your code here!', 139 | }, 140 | }, 141 | code: { 142 | icon: 'fas fa-code', 143 | label: 'Add Code', 144 | action: 'add', 145 | object: { 146 | type: 'code', 147 | size: { 148 | width: 100, 149 | height: 100, 150 | }, 151 | text: 'Add your code here!', 152 | }, 153 | }, 154 | template: { 155 | icon: 'far fa-object-group', 156 | action: 'modal', 157 | selector: 'template', 158 | label: 'Add Template', 159 | }, 160 | head: { 161 | icon: 'fas fa-sliders-h', 162 | label: 'Add Head', 163 | selector: 'headconf', 164 | action: 'selector', 165 | }, 166 | }; 167 | 168 | export default defaultButtons; 169 | -------------------------------------------------------------------------------- /src/EditMenu/formHelpers.js: -------------------------------------------------------------------------------- 1 | import { jsxToJson } from 'jsx-to-json'; 2 | import { useForm, FormProvider, useFormContext } from 'react-hook-form'; 3 | 4 | export const submitHelper = (e) => {}; 5 | 6 | export const formTransformer = (elemDataText) => { 7 | console.log('---pre transform', elemDataText); 8 | const formChildren = ['']; 9 | const jsonArray = jsxToJson(elemDataText); 10 | console.log('---post transform', jsonArray); 11 | 12 | return formChildren; 13 | }; 14 | 15 | export const FormExpander = (props) => { 16 | const { formChildren } = props; 17 | const { register } = useFormContext(); // retrieve all hook methods 18 | 19 | return formChildren.map((eachChild) => { 20 | // console.log(eachChild); 21 | return ; 22 | }); 23 | }; 24 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | /* DRAGGABLE DIV VISUAL */ 2 | .draggable:hover { 3 | -webkit-filter: drop-shadow(0px 0px 5px rgba(64, 224, 208, 0.81)); 4 | filter: drop-shadow(0px 0px 5px rgba(64, 224, 208, 0.81)); 5 | cursor: move; 6 | } 7 | 8 | .draggableselected { 9 | -webkit-filter: drop-shadow(0px 0px 5px rgba(255, 255, 40, 1)); 10 | filter: drop-shadow(0px 0px 5px rgba(255, 255, 40, 1)); 11 | } 12 | 13 | .dragHandle { 14 | position: absolute; 15 | border: 1px solid black; 16 | border-radius: 2px; 17 | background-color: white; 18 | padding: 3px; 19 | cursor: nesw-resize; 20 | 21 | z-index: 9999999; 22 | } 23 | 24 | @media only screen and (max-width: 768px) { 25 | .dragHandle { 26 | padding: 10px; 27 | } 28 | } 29 | 30 | @media only screen and (min-width: 768px) { 31 | .hovershadow { 32 | box-shadow: 0px 0px 0px rgb(0 0 0 / 16%); 33 | transition: box-shadow 0.1s ease-in-out; 34 | } 35 | 36 | /* Transition to a bigger shadow on hover */ 37 | .hovershadow:hover { 38 | box-shadow: 4px 4px 0px rgb(0 0 0 / 16%); 39 | } 40 | 41 | /* Transition to a bigger shadow on hover */ 42 | .hovershadow:active { 43 | box-shadow: 2px 2px 0px rgb(0 0 0 / 16%); 44 | } 45 | } 46 | 47 | .dragHandle2 { 48 | position: absolute; 49 | padding: 12px; 50 | border: 1px solid black; 51 | background-color: white; 52 | padding: 3px; 53 | z-index: 9999999; 54 | cursor: sw-resize; 55 | } 56 | 57 | /* CONTROL PANEL */ 58 | 59 | .cpanel { 60 | min-width: 10px; 61 | border: 1px solid black; 62 | background-color: rgba(255, 255, 255, 0.64); 63 | backdrop-filter: blur(8px); 64 | border-radius: 3px; 65 | z-index: 9999999999; 66 | } 67 | 68 | .cpanel-col-buttons:hover > * { 69 | opacity: 0.5; 70 | } 71 | 72 | .cpanel-col-buttons:hover > *:hover { 73 | transform: scale(1.1); 74 | opacity: 1; 75 | } 76 | 77 | .cpanel-shadow { 78 | box-shadow: 4px 4px 0px rgb(0 0 0 / 16%); 79 | } 80 | 81 | .cpanel-col:not(:last-child) { 82 | border-right: 1px solid black; 83 | } 84 | 85 | .clabel { 86 | font-size: 12px; 87 | font-weight: bold; 88 | color: black; 89 | padding: 5px; 90 | } 91 | 92 | .cbutton { 93 | width: 50px; 94 | height: 50px; 95 | display: flex; 96 | align-items: center; 97 | justify-content: center; 98 | border-radius: 3px; 99 | cursor: pointer; 100 | } 101 | 102 | .cbuttoninner { 103 | width: 25px; 104 | height: 25px; 105 | } 106 | 107 | .cbuttoninner:hover { 108 | background-color: rgba(1, 1, 1, 0.1); 109 | } 110 | 111 | .cbuttoninner-selected { 112 | background-color: rgba(1, 1, 1, 0.3); 113 | } 114 | 115 | .cbuttonmain { 116 | border: 1px solid black; 117 | color: black; 118 | background-color: white; 119 | width: 52px; 120 | height: 52px; 121 | } 122 | 123 | .flexRow { 124 | display: flex; 125 | flex-direction: row; 126 | align-items: center; 127 | } 128 | 129 | .minimal-input { 130 | border: none; 131 | } 132 | 133 | .minimal-input:focus { 134 | outline: none; 135 | } 136 | 137 | /* [contenteditable]:focus { 138 | outline: 0px solid transparent; 139 | } */ 140 | 141 | /* Tooltip container */ 142 | .tooltip { 143 | } 144 | 145 | .tooltip .tooltiptext { 146 | visibility: hidden; 147 | width: 120px; 148 | background-color: black; 149 | color: #fff; 150 | text-align: center; 151 | padding: 5px 0; 152 | border-radius: 6px; 153 | margin-right: 200px; 154 | position: absolute; 155 | z-index: 1; 156 | } 157 | 158 | .tooltip .tooltipbottom { 159 | margin-top: 80px; 160 | margin-right: 0px; 161 | margin-left: 0px; 162 | } 163 | 164 | .tooltip:hover .tooltiptext { 165 | visibility: visible; 166 | } 167 | 168 | #page-center-align-guide { 169 | left: 50%; 170 | transform: translateX(50vw); 171 | touch-action: none; 172 | pointer-events: none; 173 | z-index: 99999999; 174 | } 175 | 176 | .page-align-guide:not(.active) { 177 | opacity: 0; 178 | } 179 | 180 | .page-align-guide { 181 | height: 100vh; 182 | position: fixed; 183 | width: 1px; 184 | border-right: 1px solid red; 185 | z-index: 99999999; 186 | top: 0; 187 | touch-action: none; 188 | pointer-events: none; 189 | } 190 | 191 | .mobile-align-guide { 192 | border-right: 1px solid grey; 193 | touch-action: none; 194 | pointer-events: none; 195 | } 196 | 197 | .mobile-align-bg { 198 | background-color: rgba(0, 0, 0, 0.1); 199 | z-index: 99999999; 200 | touch-action: none; 201 | pointer-events: none; 202 | } 203 | 204 | /* 205 | Interthing Guide 206 | */ 207 | 208 | .interthing-line { 209 | width: 1px; 210 | height: 1rem; 211 | background: lightblue; 212 | position: absolute; 213 | z-index: 9999999; 214 | 215 | touch-action: none; 216 | pointer-events: none; 217 | } 218 | 219 | .interthing-line-nub { 220 | position: absolute; 221 | 222 | height: 4px; 223 | width: 4px; 224 | background-color: red; 225 | transform: translate(-50%, -50%); 226 | 227 | display: block; 228 | z-index: 999999999; 229 | 230 | touch-action: none; 231 | pointer-events: none; 232 | } 233 | 234 | #builder-drag-select-box { 235 | --selection-color-rgb: 71, 160, 244; 236 | --selection-color: rgba(255, 255, 0, 1); 237 | 238 | display: block; 239 | position: absolute; 240 | 241 | background: rgba(var(--selection-color), 0.2); 242 | border: var(--line-width) solid var(--selection-color); 243 | z-index: 999999999; 244 | 245 | box-sizing: border-box; 246 | } 247 | 248 | /* SECTION: spinning circle animation */ 249 | 250 | .brocorpSaveSpinner { 251 | transition: opacity 0.3s; 252 | opacity: 0; 253 | } 254 | 255 | .brocorpSaveSpinner:hover { 256 | transition: opacity 0.3s; 257 | opacity: 1; 258 | } 259 | 260 | .cssload-wrap { 261 | width: 55px; 262 | height: 55px; 263 | margin: 27px auto; 264 | position: relative; 265 | perspective: 1100px; 266 | -o-perspective: 1100px; 267 | -ms-perspective: 1100px; 268 | -webkit-perspective: 1100px; 269 | -moz-perspective: 1100px; 270 | transform-style: preserve-3d; 271 | -o-transform-style: preserve-3d; 272 | -ms-transform-style: preserve-3d; 273 | -webkit-transform-style: preserve-3d; 274 | -moz-transform-style: preserve-3d; 275 | } 276 | 277 | .cssload-circle { 278 | transform-style: preserve-3d; 279 | -o-transform-style: preserve-3d; 280 | -ms-transform-style: preserve-3d; 281 | -webkit-transform-style: preserve-3d; 282 | -moz-transform-style: preserve-3d; 283 | box-sizing: border-box; 284 | -o-box-sizing: border-box; 285 | -ms-box-sizing: border-box; 286 | -webkit-box-sizing: border-box; 287 | -moz-box-sizing: border-box; 288 | opacity: 0; 289 | width: 55px; 290 | height: 55px; 291 | border: 1px solid rgba(255, 255, 255, 0.8); 292 | border-radius: 41px; 293 | position: absolute; 294 | top: 0; 295 | left: 0; 296 | animation: cssload-spin 12.5s ease-in-out alternate infinite; 297 | -o-animation: cssload-spin 12.5s ease-in-out alternate infinite; 298 | -ms-animation: cssload-spin 12.5s ease-in-out alternate infinite; 299 | -webkit-animation: cssload-spin 12.5s ease-in-out alternate infinite; 300 | -moz-animation: cssload-spin 12.5s ease-in-out alternate infinite; 301 | } 302 | .cssload-circle:nth-of-type(1) { 303 | animation-delay: 375ms; 304 | -o-animation-delay: 375ms; 305 | -ms-animation-delay: 375ms; 306 | -webkit-animation-delay: 375ms; 307 | -moz-animation-delay: 375ms; 308 | } 309 | .cssload-circle:nth-of-type(2) { 310 | animation-delay: 750ms; 311 | -o-animation-delay: 750ms; 312 | -ms-animation-delay: 750ms; 313 | -webkit-animation-delay: 750ms; 314 | -moz-animation-delay: 750ms; 315 | } 316 | .cssload-circle:nth-of-type(3) { 317 | animation-delay: 1125ms; 318 | -o-animation-delay: 1125ms; 319 | -ms-animation-delay: 1125ms; 320 | -webkit-animation-delay: 1125ms; 321 | -moz-animation-delay: 1125ms; 322 | } 323 | .cssload-circle:nth-of-type(4) { 324 | animation-delay: 1500ms; 325 | -o-animation-delay: 1500ms; 326 | -ms-animation-delay: 1500ms; 327 | -webkit-animation-delay: 1500ms; 328 | -moz-animation-delay: 1500ms; 329 | } 330 | .cssload-circle:nth-of-type(5) { 331 | animation-delay: 1875ms; 332 | -o-animation-delay: 1875ms; 333 | -ms-animation-delay: 1875ms; 334 | -webkit-animation-delay: 1875ms; 335 | -moz-animation-delay: 1875ms; 336 | } 337 | 338 | @keyframes cssload-spin { 339 | 0% { 340 | transform: rotateY(0deg) rotateX(0deg); 341 | opacity: 1; 342 | } 343 | 25% { 344 | transform: rotateY(180deg) rotateX(360deg); 345 | } 346 | 50% { 347 | transform: rotateY(540deg) rotateX(540deg); 348 | } 349 | 75% { 350 | transform: rotateY(720deg) rotateX(900deg); 351 | } 352 | 100% { 353 | transform: rotateY(900deg) rotateX(1080deg); 354 | opacity: 1; 355 | } 356 | } 357 | 358 | @-o-keyframes cssload-spin { 359 | 0% { 360 | -o-transform: rotateY(0deg) rotateX(0deg); 361 | opacity: 1; 362 | } 363 | 25% { 364 | -o-transform: rotateY(180deg) rotateX(360deg); 365 | } 366 | 50% { 367 | -o-transform: rotateY(540deg) rotateX(540deg); 368 | } 369 | 75% { 370 | -o-transform: rotateY(720deg) rotateX(900deg); 371 | } 372 | 100% { 373 | -o-transform: rotateY(900deg) rotateX(1080deg); 374 | opacity: 1; 375 | } 376 | } 377 | 378 | @-ms-keyframes cssload-spin { 379 | 0% { 380 | -ms-transform: rotateY(0deg) rotateX(0deg); 381 | opacity: 1; 382 | } 383 | 25% { 384 | -ms-transform: rotateY(180deg) rotateX(360deg); 385 | } 386 | 50% { 387 | -ms-transform: rotateY(540deg) rotateX(540deg); 388 | } 389 | 75% { 390 | -ms-transform: rotateY(720deg) rotateX(900deg); 391 | } 392 | 100% { 393 | -ms-transform: rotateY(900deg) rotateX(1080deg); 394 | opacity: 1; 395 | } 396 | } 397 | 398 | @-webkit-keyframes cssload-spin { 399 | 0% { 400 | -webkit-transform: rotateY(0deg) rotateX(0deg); 401 | opacity: 1; 402 | } 403 | 25% { 404 | -webkit-transform: rotateY(180deg) rotateX(360deg); 405 | } 406 | 50% { 407 | -webkit-transform: rotateY(540deg) rotateX(540deg); 408 | } 409 | 75% { 410 | -webkit-transform: rotateY(720deg) rotateX(900deg); 411 | } 412 | 100% { 413 | -webkit-transform: rotateY(900deg) rotateX(1080deg); 414 | opacity: 1; 415 | } 416 | } 417 | 418 | @-moz-keyframes cssload-spin { 419 | 0% { 420 | -moz-transform: rotateY(0deg) rotateX(0deg); 421 | opacity: 1; 422 | } 423 | 25% { 424 | -moz-transform: rotateY(180deg) rotateX(360deg); 425 | } 426 | 50% { 427 | -moz-transform: rotateY(540deg) rotateX(540deg); 428 | } 429 | 75% { 430 | -moz-transform: rotateY(720deg) rotateX(900deg); 431 | } 432 | 100% { 433 | -moz-transform: rotateY(900deg) rotateX(1080deg); 434 | opacity: 1; 435 | } 436 | } 437 | 438 | 439 | /* body { 440 | max-width: 100vw; 441 | } */ 442 | 443 | .dragd-modal { 444 | position: fixed; /* Stay in place */ 445 | z-index: 1; /* Sit on top */ 446 | left: 0; 447 | top: 0; 448 | width: 100%; /* Full width */ 449 | height: 100%; /* Full height */ 450 | overflow: auto; /* Enable scroll if needed */ 451 | background-color: rgb(0,0,0); /* Fallback color */ 452 | background-color: rgba(0,0,0,0.4); /* Black w/ opacity */ 453 | } 454 | 455 | /* Modal Content/Box */ 456 | .dragd-modal-content { 457 | background-color: #fefefe; 458 | margin: 15% auto; /* 15% from the top and centered */ 459 | padding: 20px; 460 | border: 1px solid #888; 461 | width: 80%; /* Could be more or less, depending on screen size */ 462 | } 463 | 464 | .dropdown { 465 | position: relative; 466 | display: inline-block; 467 | } 468 | 469 | .dropdown-content { 470 | position: absolute; 471 | background-color: #f9f9f9; 472 | min-width: 160px; 473 | box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2); 474 | padding: 12px 16px; 475 | z-index: 1; 476 | } -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React, {useState} from 'react' 2 | 3 | import DragDrop from './DragDrop' 4 | 5 | import "./index.css"; 6 | 7 | const MyComponent = props => { 8 | return <> 9 | 10 | 11 | } 12 | export default MyComponent 13 | -------------------------------------------------------------------------------- /src/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/pageContext.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | // this is the equivalent to the createStore method of Redux 4 | const SiteContext = React.createContext(null); 5 | 6 | export default SiteContext; 7 | -------------------------------------------------------------------------------- /src/utils/helpers.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useRef, useEffect } from 'react'; 2 | 3 | export function usePrevious(value) { 4 | // The ref object is a generic container whose current property is mutable ... 5 | // ... and can hold any value, similar to an instance property on a class 6 | const ref = useRef(); 7 | // Store current value in ref 8 | useEffect(() => { 9 | ref.current = value; 10 | }, [value]); // Only re-run if value changes 11 | // Return previous value (happens before update in useEffect above) 12 | return ref.current; 13 | } 14 | 15 | export function isMobile() { 16 | if(/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)){ 17 | // true for mobile device 18 | return true; 19 | }else{ 20 | // false for not mobile device 21 | return false; 22 | } 23 | } 24 | 25 | export function isMobileViewport() { 26 | if(typeof window !== 'undefined' && window.innerWidth < 600) return true; 27 | } 28 | 29 | export function getMobileScaleRatio() { 30 | return (isMobileViewport() ? window.innerWidth / 600 : 1); 31 | } 32 | 33 | export function getElementOffset(element) { 34 | var de = typeof window !== "undefined" && document.documentElement; 35 | var box = element.getBoundingClientRect(); 36 | var top = box.top + window.pageYOffset - de.clientTop; 37 | var left = box.left + window.pageXOffset - de.clientLeft; 38 | var height = box.height; 39 | var width = box.width; 40 | return { 41 | top: top, 42 | left: left, 43 | height, 44 | width, 45 | center: { x: left + width / 2, y: top + height / 2 }, 46 | }; 47 | } 48 | 49 | export function guidGenerator() { 50 | var S4 = function () { 51 | return (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1); 52 | }; 53 | return ( 54 | S4() + 55 | S4() + 56 | '-' + 57 | S4() + 58 | '-' + 59 | S4() + 60 | '-' + 61 | S4() + 62 | '-' + 63 | S4() + 64 | S4() + 65 | S4() 66 | ); 67 | } 68 | 69 | export const getAngle = ({ x: x1, y: y1 }, { x: x2, y: y2 }) => { 70 | const dot = x1 * x2 + y1 * y2; 71 | const det = x1 * y2 - y1 * x2; 72 | const angle = (Math.atan2(det, dot) / Math.PI) * 180; 73 | return (angle + 360) % 360; 74 | }; 75 | 76 | export const degToRadian = (deg) => (deg * Math.PI) / 180; 77 | 78 | export const getLength = (x, y) => Math.sqrt(x * x + y * y); 79 | 80 | export const Input = (props) => { 81 | const { value = '', onChange, placeholder, defaultValue, style } = props; 82 | const [text, setText] = useState(value); 83 | 84 | function update(event) { 85 | setText(event.target.value); 86 | if (typeof onChange === 'function') { 87 | onChange(event.target.value); 88 | } 89 | } 90 | 91 | return ( 92 | 99 | ); 100 | }; 101 | 102 | export const Column = (props) => { 103 | return ( 104 |
108 | {props.children} 109 |
110 | ); 111 | }; 112 | 113 | export const Row = (props) => { 114 | return ( 115 |
119 | {props.children} 120 |
121 | ); 122 | }; 123 | 124 | export function debounce(func, wait) { 125 | var timeout; 126 | 127 | return (...args) => { 128 | var context = this; 129 | 130 | var later = () => { 131 | func.apply(context, args); 132 | }; 133 | 134 | clearTimeout(timeout); 135 | 136 | timeout = setTimeout(later, wait); 137 | }; 138 | }; 139 | 140 | /** 141 | * Simple object check. 142 | * @param item 143 | * @returns {boolean} 144 | */ 145 | export function isObject(item) { 146 | return (item && typeof item === 'object' && !Array.isArray(item)); 147 | } 148 | 149 | export function mergeDeep(target, source) { 150 | let output = Object.assign({}, target); 151 | if (isObject(target) && isObject(source)) { 152 | Object.keys(source).forEach(key => { 153 | if (isObject(source[key])) { 154 | if (!(key in target)) 155 | Object.assign(output, { [key]: source[key] }); 156 | else 157 | output[key] = mergeDeep(target[key], source[key]); 158 | } else { 159 | Object.assign(output, { [key]: source[key] }); 160 | } 161 | }); 162 | } 163 | return output; 164 | } 165 | 166 | export function isDarkColor(bgColor) { 167 | if(typeof bgColor === 'undefined') return false; 168 | var color = (bgColor.charAt(0) === '#') ? bgColor.substring(1, 7) : bgColor; 169 | var r = parseInt(color.substring(0, 2), 16); // hexToR 170 | var g = parseInt(color.substring(2, 4), 16); // hexToG 171 | var b = parseInt(color.substring(4, 6), 16); // hexToB 172 | return (((r * 0.299) + (g * 0.587) + (b * 0.114)) > 186) ? 173 | false : true; 174 | } -------------------------------------------------------------------------------- /src/utils/ui/ColorPicker.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { RgbaStringColorPicker } from "react-colorful"; 3 | 4 | export default function ColorPicker ({color, onChange, onClose}) { 5 | return <> 6 |
{ 7 | onClose(); 8 | }}> 9 | 13 |
14 | 15 | } -------------------------------------------------------------------------------- /src/utils/ui/DropDownMenu.js: -------------------------------------------------------------------------------- 1 | import React, { useRef, useState } from 'react'; 2 | 3 | export default function DropDownMenu({ options, selectedOption, onSelect, type }) { 4 | const [selected, setSelected] = useState(false); 5 | const [scrollLimit, setScrollLimit] = useState(20); 6 | const listInnerRef = useRef(); 7 | 8 | return ( 9 |
10 | 69 |
70 | ); 71 | } 72 | -------------------------------------------------------------------------------- /src/utils/ui/GenericModal.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | 3 | function GenericModal(props) { 4 | return ( 5 |
6 |
props.onDone()} /> 7 |
8 |
9 | {props.content && props.content} 10 |
11 |
12 |
13 | ); 14 | } 15 | 16 | export default GenericModal; 17 | -------------------------------------------------------------------------------- /src/utils/ui/MobileBoundary.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | export default function MobileBoundary ({mobileWidth = 600}) { 4 | return window.innerWidth > mobileWidth && <> 5 | 6 | 7 | 8 | } 9 | 10 | function Boundary({mobileWidth, right}) { 11 | return <> 12 |
22 |
30 |
38 | ««« Not visible on phone »»» 39 |
40 |
41 | 42 | } -------------------------------------------------------------------------------- /srctest/app.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom' 3 | import MyComponent from '../src/index.js' 4 | 5 | const ParentWrapper = () => { 6 | return ( 7 |
16 | 17 |
18 | ) 19 | } 20 | 21 | ReactDOM.render(, document.getElementById('root')) 22 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | module.exports = env => { 2 | return require(`./webpack.${env}.js`) 3 | } 4 | -------------------------------------------------------------------------------- /webpack.publish.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | const MiniCssExtractPlugin = require("mini-css-extract-plugin"); 3 | 4 | module.exports = { 5 | entry: './src/index.js', 6 | output: { 7 | path: path.resolve(__dirname, 'build'), 8 | filename: 'index.js', 9 | libraryTarget: 'commonjs2' 10 | }, 11 | plugins: [new MiniCssExtractPlugin()], 12 | module: { 13 | rules: [ 14 | { 15 | test: /\.js$/, 16 | include: path.resolve(__dirname, 'src'), 17 | exclude: /(node_modules|build)/, 18 | use: { 19 | loader: 'babel-loader' 20 | } 21 | }, 22 | { 23 | test: /\.css$/, 24 | use: [{ loader: MiniCssExtractPlugin.loader }, { loader: 'css-loader' }], 25 | exclude: /node_modules/ 26 | }, 27 | ] 28 | }, 29 | externals: { 30 | react: 'commonjs react' 31 | }, 32 | devServer: { 33 | contentBase: path.join(__dirname, 'build'), 34 | compress: true, 35 | port: 9000 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /webpack.testServer.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | module.exports = { 3 | entry: './srctest/app.js', 4 | output: { 5 | path: path.resolve(__dirname, 'buildtest'), 6 | filename: 'app.js' 7 | }, 8 | module: { 9 | rules: [ 10 | { 11 | test: /\.js$/, 12 | include: [path.resolve(__dirname, 'src'), path.resolve(__dirname, 'srctest')], 13 | exclude: /(node_modules|build)/, 14 | use: { 15 | loader: 'babel-loader' 16 | } 17 | }, 18 | { 19 | test: /\.css$/, 20 | use: [{ loader: 'style-loader'}, { loader: 'css-loader' }], 21 | }, 22 | ] 23 | }, 24 | devServer: { 25 | contentBase: path.join(__dirname, 'buildtest'), 26 | compress: true, 27 | port: 9000 28 | }, 29 | } 30 | --------------------------------------------------------------------------------