├── .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 |
11 |
29 | {selected && (
30 |
67 | )}
68 |
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 |
--------------------------------------------------------------------------------