—'
252 | );
253 | tt.appendChild(tplusbtnLW );
254 | tt.appendChild(tminusbtnLW );
255 |
256 | // fill?
257 | const tbutton = _ce('button'
258 | ,'className', "np-n-t-btn"
259 | );
260 |
261 | const tlabel = _ce('label'
262 | ,'innerHTML', node.style.fill=='none'?'
'
263 | ,'ondblclick', function (e) {
264 | applyAction({
265 | type: 'E',
266 | node_ids: [node.id],
267 | oldValues: [{'style.fill': this.dataset['oldValue']}],
268 | newValues: [{'style.fill': 'none'}]
269 | });
270 | e.stopPropagation();
271 | }
272 | );
273 | tlabel.setAttribute('for','inputfill_'+node.id);
274 | if(node.style.fill!=='none'){
275 | tlabel.style.color = node.style.fill;
276 | }
277 |
278 |
279 | const tfillinput = _ce('input'
280 | ,'type','color'
281 | ,'value', node.style.fill=='none'?'#ffffff':node.style.fill
282 | ,'id','inputfill_'+node.id
283 | ,'style','width:0px;height:0px;'
284 | // ,'hidden','hidden'
285 | ,'oninput', function (e) {
286 | tlabel.style.color = this.value;
287 | node.style.fill = this.value;
288 | node.path_dom.setAttribute('fill', this.value);
289 | tlabel.innerHTML = '
';
290 | }
291 | ,'onchange', function (e) {
292 | applyAction({
293 | type: 'E',
294 | node_ids: [node.id],
295 | oldValues: [{'style.fill': this.dataset['oldValue']}],
296 | newValues: [{'style.fill': this.value}],
297 | });
298 | }
299 |
300 | )
301 | tfillinput.dataset['oldValue'] = node.style.fill;
302 | tbutton.appendChild(tlabel);
303 | tbutton.appendChild(tfillinput);
304 |
305 | // tt.appendChild(tfillinput);
306 | tt.appendChild(tbutton);
307 | }
308 |
309 |
310 | tt.addEventListener('click', function(e) {
311 | e.stopPropagation();
312 | })
313 | tt.addEventListener('mousedown', function(e) {
314 | e.stopPropagation();
315 | })
316 | tt.addEventListener('mouseup', function(e) {
317 | e.stopPropagation();
318 | })
319 | tt.addEventListener('dblclick', function (e) {
320 | e.stopPropagation();
321 | });
322 |
323 | tdom.appendChild(tt);
324 |
325 | for (const p of Object.keys(node.style)) {
326 | tcontent.style[p] = node.style[p];
327 | }
328 |
329 | tdom.appendChild(tcontent);
330 |
331 | node.dom = tdom;
332 | node.content_dom = tcontent;
333 |
334 | if (!('className' in node)) {
335 | node_container.appendChild(tdom);
336 | }
337 |
338 |
339 |
340 | tdom.getElementsByTagName('a').forEach( (elt) => {
341 | if (elt.href) {
342 | elt.onclick = (e) => {
343 | e.stopPropagation();
344 | };
345 | elt.onmousedown = (e) => {
346 | e.stopPropagation();
347 | };
348 | }
349 | });
350 |
351 | if (redraw) {
352 | redrawNode(node);
353 | } else {
354 | updateNode(node);
355 | }
356 |
357 | if(tdom.classList.contains('selected')){
358 | // deselectOneDOM(tdom);
359 | try{$(tdom).rotatable('destroy');}catch(e){}
360 | setTimeout(function(){selectOneDOM(tdom);},1);
361 | }
362 |
363 | return tdom;
364 | }
365 |
366 | function fillMissingNodeFields(node){
367 | if ((!('id' in node)) || (node.id === undefined) || (idNode(node.id))) {
368 | node.id = newNodeID();
369 | }
370 | if (!('rotate' in node)) {
371 | node.rotate = 0;
372 | }
373 | if (!('x' in node)) {
374 | let mousePos = _Mouse.pos;
375 | if ('mousePos' in node) {
376 | mousePos = node.mousePos;
377 | }
378 | const mouseXY = _View.clientToPos(mousePos);
379 | node.x = mouseXY[0];
380 | node.y = mouseXY[1];
381 | }
382 | if (!('fontSize' in node)) {
383 | node.fontSize = 20 / _View.state.S;
384 | }
385 | if ((!node.hasOwnProperty('style'))||
386 | (node.style === undefined)) {
387 | node.style = Object.assign({}, default_node_style);
388 | } else {
389 | node.style = Object.assign({}, default_node_style, node.style);
390 | }
391 | }
392 |
393 |
--------------------------------------------------------------------------------
/style.css:
--------------------------------------------------------------------------------
1 | html, body {
2 | /* margin:0px;
3 | height:100%;
4 | width:100%;
5 | position:fixed; */
6 | overflow: hidden; /* Hide scrollbars */
7 | font-family: 'Open Sans';
8 | touch-action: none;
9 | }
10 |
11 | #wrapper {
12 | /* overflow-x: hidden; */
13 | width: 100%;
14 | }
15 |
16 | #navbar{
17 | z-index: 900;
18 | }
19 |
20 | #sidebar-wrapper {
21 | min-height: 100vh;
22 | margin-left: -15rem;
23 | -webkit-transition: margin .25s ease-out;
24 | -moz-transition: margin .25s ease-out;
25 | -o-transition: margin .25s ease-out;
26 | transition: margin .25s ease-out;
27 | z-index: 500;
28 | }
29 |
30 | #sidebar-wrapper .sidebar-heading {
31 | padding: 0.875rem 1.25rem;
32 | font-size: 1.2rem;
33 | }
34 |
35 | #sidebar-wrapper ul {
36 | width: 15rem;
37 | }
38 |
39 | #page-content-wrapper {
40 | min-width: 100vw;
41 | overflow: hidden;
42 | }
43 |
44 | #wrapper.toggled #sidebar-wrapper {
45 | margin-left: 0;
46 | }
47 |
48 | @media (min-width: 768px) {
49 | #sidebar-wrapper {
50 | margin-left: 0;
51 | }
52 |
53 | #page-content-wrapper {
54 | min-width: 0;
55 | width: 100%;
56 | }
57 |
58 | #wrapper.toggled #sidebar-wrapper {
59 | margin-left: -15rem;
60 | }
61 | }
62 |
63 |
64 | div.node{
65 | display: inline-block;
66 | width:auto;
67 |
68 | }
69 |
70 | p{
71 | margin:0px;
72 | }
73 |
74 | .dot circle {
75 | fill: lightsteelblue;
76 | stroke: steelblue;
77 | stroke-width: 1.5px;
78 | }
79 |
80 | .dot circle.dragging {
81 | fill: red;
82 | stroke: brown;
83 | }
84 |
85 | .axis line {
86 | fill: none;
87 | stroke: #ddd;
88 | shape-rendering: crispEdges;
89 | vector-effect: non-scaling-stroke;
90 | }
91 |
92 | .node{
93 | /* width: 100%;
94 | height: 100%;
95 | text-align: left; */
96 | position: absolute;
97 | width:auto;
98 | /* text-align: left; */
99 | /* overflow: hidden; */
100 | white-space:nowrap;
101 | /* -webkit-user-modify: read-write;
102 | overflow-wrap: break-word;
103 | -webkit-line-break: after-white-space; */
104 |
105 | font-size: inherit;
106 | /* transition-timing-function: linear; */
107 | }
108 |
109 |
110 | .node svg{
111 | position: absolute;
112 | left:0px;
113 | top:0px;
114 | /* transition-timing-function: linear; */
115 | /* transition-duration: inherit; */
116 | }
117 |
118 | .node svg path {
119 | cursor: pointer;
120 | }
121 | .node .np-n-c.img{
122 | transition-duration: inherit;
123 | }
124 | /* .node img{
125 | transition-duration: inherit;
126 |
127 | } */
128 |
129 | .node h1{
130 | font-size: 1.5em;
131 | font-weight: bold;
132 | }
133 | .node h2{
134 | font-size: 1.4em;
135 | font-weight: bold;
136 | }
137 | .node h3{
138 | font-size: 1.35em;
139 | font-weight: bold;
140 | }
141 | .node h4{
142 | font-size: 1.3em;
143 | }
144 | .node h5{
145 | font-size: 1.25em;
146 | font-style:italic;
147 | }
148 | .node h6{
149 | font-size: 1.1em;
150 | font-style:oblique;
151 | }
152 |
153 |
154 | .node.selected{
155 | /* fill:red;
156 | color:red; */
157 | border:1px solid black;
158 | z-index: 100;
159 | }
160 |
161 | .node blockquote{
162 | margin-inline-start: 1em;
163 | margin-inline-end: 0px;
164 | }
165 | .node ol,ul{
166 | padding-inline-start: 1em;
167 | padding-inline-start: 1em;
168 | }
169 |
170 | /* .inside_node{
171 |
172 | } */
173 | #container{
174 |
175 | width: 100%;
176 | height:100%;
177 | }
178 | #container.drag-hover{
179 | background-color: #aaa;
180 | }
181 |
182 | #text{
183 | resize: none;
184 | /* overflow: hidden; */
185 | min-height: 50px;
186 | max-height: 100px;
187 | }
188 |
189 | .custom-file-upload {
190 | border: 1px solid #ccc;
191 | display: inline-block;
192 | padding: 6px 12px;
193 | cursor: pointer;
194 | }
195 | input[type="file"] {
196 | display: none;
197 | }
198 |
199 |
200 | /*
201 | ===================================================================§§
202 | */
203 |
204 | .notification-top {
205 | position: fixed;
206 | width: 100%;
207 | top: 0;
208 | left: 0;
209 | /* Rest of your styling */
210 | border: 1px solid black;
211 | background-color: white;
212 | z-index: 1000;
213 | }
214 | .notification-bottom {
215 | position: fixed;
216 | width: 100%;
217 | bottom: 0;
218 | left: 0;
219 | /* Rest of your styling */
220 | border: 1px solid black;
221 | background-color: white;
222 | z-index: 1000;
223 | }
224 |
225 | #node_container{
226 | /* transition-duration: 0.2s; */
227 | display: block;
228 | position: absolute;
229 | }
230 |
231 | .node{
232 | /* display: block; */
233 | /* white-space: pre; */
234 | position: absolute;
235 | /* transition-duration: 0.2s; */
236 | user-select: none; /* CSS3 (little to no support) */
237 | -ms-user-select: none; /* IE 10+ */
238 | -moz-user-select: none; /* Gecko (Firefox) */
239 | -webkit-user-select: none; /* Webkit (Safari, Chrome) */
240 | }
241 | .node.zoom{
242 | transition-duration: 0.2s;
243 | }
244 |
245 | a.node_a{
246 | color: inherit; /* blue colors for links too */
247 | text-decoration: inherit; /* no underline */
248 | }
249 |
250 | a.local{
251 | cursor:pointer;
252 | }
253 |
254 | .ui-rotatable-handle{
255 | width: 20px;
256 | height: 20px;
257 | position: absolute;
258 | left:-20px;
259 | bottom:-20px;
260 | /* border-radius: 6px; */
261 | /* border-width: 1.5px; */
262 | /* border-style: solid; */
263 | /* border-color: rgb(0,0,0); */
264 |
265 | content: url(img/r.svg);
266 |
267 | z-index: 10000;
268 |
269 | cursor:grab;
270 | }
271 | .ui-rotatable-disabled .ui-rotatable-handle{
272 | display: none;
273 | }
274 |
275 | .node img{
276 | transition-duration:inherit;
277 | -webkit-user-drag: none;
278 | -khtml-user-drag: none;
279 | -moz-user-drag: none;
280 | -o-user-drag: none;
281 | user-drag: none;
282 | }
283 |
284 | #select-box{
285 | position: absolute;
286 | border: 1px solid black;
287 | z-index: 10000;
288 | display: none;
289 | }
290 |
291 | #copydiv{
292 | position: absolute;
293 | left:-10000px;
294 | top:-100000px;
295 | opacity: 0;
296 | }
297 |
298 |
299 | .horizontal-scrollable > .row {
300 | overflow-x: auto;
301 | white-space: nowrap;
302 | -webkit-overflow-scrolling: touch;
303 | }
304 |
305 | .horizontal-scrollable > .row > .col {
306 | display: inline-block;
307 | float: none;
308 | }
309 |
310 |
311 |
312 | /* https://getbootstrap.com/docs/5.0/examples/sidebars/# */
313 | body {
314 | display: flex;
315 | flex-wrap: nowrap;
316 | height: 100vh;
317 | height: -webkit-fill-available;
318 | overflow-x: hidden;
319 | overflow-y: hidden;
320 | }
321 | body > * {
322 | flex-shrink: 0;
323 | min-height: -webkit-fill-available;
324 | }
325 |
326 | .b-example-divider {
327 | width: 1.5rem;
328 | height: 100%;
329 | background-color: rgba(0, 0, 0, .1);
330 | border: solid rgba(0, 0, 0, .15);
331 | border-width: 1px 0;
332 | box-shadow: inset 0 .5em 1.5em rgba(0, 0, 0, .1), inset 0 .125em .5em rgba(0, 0, 0, .15);
333 | }
334 |
335 | .bi {
336 | vertical-align: -.125em;
337 | pointer-events: none;
338 | fill: currentColor;
339 | }
340 |
341 | .dropdown-toggle { outline: 0; }
342 |
343 | .nav-flush .nav-link {
344 | border-radius: 0;
345 | }
346 |
347 | .btn-toggle {
348 | display: inline-flex;
349 | align-items: center;
350 | padding: .25rem .5rem;
351 | font-weight: 600;
352 | color: rgba(0, 0, 0, .65);
353 | background-color: transparent;
354 | border: 0;
355 | }
356 | .btn-toggle:hover,
357 | .btn-toggle:focus {
358 | color: rgba(0, 0, 0, .85);
359 | background-color: #d2f4ea;
360 | }
361 |
362 | .btn-toggle::before {
363 | width: 1.25em;
364 | line-height: 0;
365 | content: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='rgba%280,0,0,.5%29' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M5 14l6-6-6-6'/%3e%3c/svg%3e");
366 | transition: transform .35s ease;
367 | transform-origin: .5em 50%;
368 | }
369 |
370 | .btn-toggle[aria-expanded="true"] {
371 | color: rgba(0, 0, 0, .85);
372 | }
373 | .btn-toggle[aria-expanded="true"]::before {
374 | transform: rotate(90deg);
375 | }
376 |
377 | .btn-toggle-nav a {
378 | display: inline-flex;
379 | padding: .375rem .5rem;
380 | margin-top: 0px;
381 | margin-left: 1.25rem;
382 | text-decoration: none;
383 | }
384 | .btn-toggle-nav a:hover,
385 | .btn-toggle-nav a:focus {
386 | background-color: #d2f4ea;
387 | }
388 |
389 | .scrollarea {
390 | overflow-y: auto;
391 | }
392 |
393 | .fw-semibold { font-weight: 600; }
394 | .lh-tight { line-height: 1.25; }
395 |
396 | /* ----- */
397 |
398 | #modal-list{
399 | max-height: 60vh;
400 | overflow-y: scroll;
401 | }
402 |
403 | /* ------------------------------- */
404 | #np-places-header button{
405 | padding: 0.4rem;
406 | }
407 |
408 | .places-place{
409 | max-width: 8em;
410 | overflow: hidden;
411 | }
412 | .places-folder{
413 | max-width: 8em;
414 | overflow: hidden;
415 | }
416 |
417 |
418 | /* -------------- */
419 |
420 | #searchSideBar{
421 | width: 18rem;
422 | transition-duration: 0.25s;
423 | z-index: 900;
424 | }
425 |
426 | #searchSideBar.toggled{
427 | margin-right:-18rem;
428 | }
429 |
430 | #search-toggle{
431 | position: relative;
432 | margin-left: -32px;
433 | width:32px;
434 | border-radius: 48%;
435 | }
436 |
437 | #searchResultContainer{
438 | overflow-y: auto;
439 | }
440 |
441 | .np-sr-row .col{
442 | text-overflow: ellipsis;
443 | white-space:nowrap;
444 | overflow: hidden;
445 | max-height: 3rem;
446 | }
447 |
448 | .np-sr-row .bi-folder-symlink{
449 | font-size: 180%;
450 | border-radius: 0.4rem;
451 | padding: 0.2rem;
452 | }
453 |
454 | /* .np-sr-row .bi-folder-symlink:hover{
455 | background-color: black;
456 | color:white;
457 | } */
458 |
459 | .node.np-search-preview{
460 | border: 2px dotted orange;
461 | }
462 |
463 | .np-search-place-preview{
464 | background-color: orange;
465 | }
466 |
467 | /*----------------*/
468 |
469 | /*
470 | .np-n-c{
471 | }
472 | */
473 |
474 | .np-n-tooltip{
475 | display: none;
476 | height:3rem;
477 | font-size: 1rem;
478 | margin-top: -3rem;
479 | border-radius: 0.3rem;
480 | /* border:1px solid black; */
481 | vertical-align: middle;
482 | text-align: center;
483 | padding: 0.125rem;
484 |
485 | }
486 | .selected .np-n-tooltip{
487 | display:flex;
488 | align-items: center;
489 | /* transition-duration: 0.5s; */
490 | }
491 |
492 | .np-n-t-btn{
493 | /*--*/
494 | background-color: #fff;
495 | height: 2em;
496 | width: 2em;
497 | border: 2px solid #ccf;
498 | border-radius: 999px;
499 | position: relative;
500 | vertical-align: middle;
501 | margin-right: 0.33em;
502 | }
503 | .np-n-t-btn:hover,.np-n-btn:focus{
504 | /* background-color:#ccc */
505 | background-color: #eef;
506 | }
507 |
508 | .np-n-tooltip input{
509 | height: 1.3em;
510 | width: 1.3em;
511 | margin: 0.1em;
512 | }
513 |
514 | .np-n-t-ta:hover{
515 | background-color: #eef;
516 | }
517 |
518 | .np-n-t-ta-selected{
519 | /* */
520 | background-color: #ccf;
521 | }
522 |
523 |
524 | input[type="color"]{
525 | background-color:white;
526 | border:none;
527 | margin-right: 0.2em;
528 | width:1.8em;
529 | height:1.8em;
530 | padding:0;
531 | margin-right: 0.33em;
532 | }
533 | /* -webkit */
534 | input[type="color"]::-webkit-color-swatch-wrapper {
535 | padding: 0;
536 | }
537 |
538 | input[type="color"]::-webkit-color-swatch {
539 | border: none;
540 | border-radius: 500%;
541 | }
542 | /* firefox */
543 | input[type=color]::-moz-focus-inner {
544 | border: none;
545 | padding: 0;
546 | border-radius: 500%;
547 | }
548 |
549 | input[type=color]::-moz-color-swatch {
550 | border: none;
551 | border-radius: 500%;
552 | }
553 |
554 |
555 | /* ----------- */
556 | #np-history-header button{
557 | padding: 0.4rem;
558 | }
559 |
560 | #historyContainer{
561 | max-height: 60vh;
562 | overflow-y: scroll;
563 | display: flex;
564 | flex-direction: column;
565 | }
566 |
567 | #historyContainer div{
568 | /* width: 100%; */
569 | width:15rem;
570 | /* max-height: 10vh;
571 | overflow-y: hidden; */
572 | }
573 |
574 | .np-h-r-c{
575 | /* width: 100%; */
576 | max-height: 3rem;
577 | overflow: hidden;
578 | }
579 |
580 |
581 | /* ----------------------- */
582 |
583 | .grid-align-line{
584 | position: absolute;
585 | z-index: 900;
586 | border: 1px dashed lightgreen;
587 | }
588 |
589 |
590 |
591 | #freehandField {
592 | /* opacity: 0.1; */
593 | /* background-color: #000; */
594 | background-color: transparent;
595 | z-index: 1000;
596 | position: absolute;
597 | left: 0px;
598 | top:0px;
599 |
600 | cursor: crosshair;
601 | }
602 |
603 | #freehandField svg{
604 | opacity: 1;
605 | position: absolute;
606 | left: 0px;
607 | top: 0px;
608 | width: 100%;
609 | height: 100%;
610 |
611 | /* z-index: -100; */
612 | }
613 |
614 | /*
615 | #freehandField svg path{
616 | fill:none;
617 |
618 | width:auto;
619 | }
620 | */
621 | .svg{
622 | pointer-events: none;
623 | }
624 |
625 | .svg .ui-rotatable-handle, .svg .ui-icon{
626 | pointer-events: all;
627 | }
628 |
629 | .svg .np-n-tooltip{
630 | pointer-events:all;
631 | }
632 |
633 | .svg path{
634 | pointer-events: visiblePainted;
635 | }
636 |
637 |
638 | /* -------- */
639 | #scale-box{
640 | margin-left: -7vh;
641 | width: 5vh;
642 | bottom: 2vh;
643 | }
644 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
21 |
104 |
105 |
106 |
107 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 | FontSize:
176 |
177 |
178 |
179 |
180 |
181 |
182 |
195 |
196 |
197 |
198 |
199 |
200 |
201 |
202 |
203 |
204 |
205 |
206 |
212 |
213 |
214 |
215 |
216 |
217 |
218 |
219 |
220 |
221 |
225 |
226 |
227 |
228 |
229 |
230 |
231 |
232 |
238 |
239 | Are you sure ??
240 |
241 |
245 |
246 |
247 |
248 |
249 |
250 |
251 |
252 | Hello, world! This is a toast message.
253 |
254 |
255 |
256 |
257 |
258 |
259 |
260 |
261 |
262 |
263 |
264 |
265 |
266 |
267 |
268 |
269 |
270 |
271 |
272 |
273 |
274 |
275 |
276 |
277 |
278 |
279 |
280 |
281 |
282 |
283 |
290 |
291 |
--------------------------------------------------------------------------------
/history.js:
--------------------------------------------------------------------------------
1 | /*
2 |
3 | properties of a history object:
4 |
5 | id : unique identifier, generates via newHistID
6 | type
7 | A : ADD
8 | D : DELETE
9 | M : MOVE, ?
10 | E : EDIT, ?
11 |
12 | state: {T:T,S:S} of a view where it happened
13 |
14 | timestamp: timestamp of that action
15 |
16 | node_ids: ids of node objects under said action in history
17 |
18 | oldValues : for MOVE and EDIT events old values of specified parameters
19 | ( are stored in _HISTORY )
20 | newValues : for reverting aand going forward again
21 |
22 | nodes : for Actions, nodes to be created
23 |
24 | newValues : for Actions, new Values of MOVE/EDIT events
25 | (get converted to _HISTORY notation of oldValues via processAction)
26 |
27 | */
28 |
29 | const btnUndo = _('#btnUndo');
30 | const btnRedo = _('#btnRedo');
31 | const btnHistoryStatus = _('#btnHistoryStatus');
32 | const historyContainer = _('#historyContainer');
33 |
34 | let _HISTORY = null;
35 | let _HISTORY_Map = new Map();
36 | let _HISTORY_j_Map = new Map();
37 | let _HISTORY_CURRENT_ID = null; // ID of the last applied history action
38 |
39 | function genHistIDMap () {
40 | _HISTORY_Map = new Map(_HISTORY.map(h => [h.id, h]));
41 | _HISTORY_Map.set(null, null);
42 | _HISTORY_j_Map = new Map([...Array(_HISTORY.length).keys()].map(j => [_HISTORY[j].id, j]));
43 | _HISTORY_j_Map.set(null, -1);
44 | }
45 |
46 | function getHistory (id) {
47 | return _HISTORY_Map.get(id);
48 | }
49 |
50 | function lastHistoryID () {
51 | return _HISTORY.length > 0 ? _HISTORY[_HISTORY.length - 1].id : null;
52 | }
53 |
54 | function newHistID (id = null) {
55 | return newID(id || 'h', getHistory);
56 | }
57 |
58 | function getAllHistoryProps(h){
59 | const R = [];
60 | h.oldValues.forEach( vs => {
61 | Object.keys(vs).forEach( prop => {
62 | R.push(prop);
63 | })
64 | })
65 | return R;
66 | }
67 |
68 | function processAction (A) {
69 | // h = resulting history event
70 | const h = { type: A.type };
71 | switch (h.type) {
72 | case 'A':
73 | // ADD
74 | let doms = [];
75 | if('node_ids' in A){
76 | // just de-delete them, ok?
77 | h.node_ids = A.node_ids.slice();
78 |
79 | h.node_ids.forEach( id => {
80 | idNode(id).deleted = false;
81 | })
82 | }else{
83 | h.node_ids = A.nodes.map(function (node) {
84 | doms.push(newNode(node, false));
85 | return node.id;
86 | });
87 | }
88 | // selectNode(doms);
89 | break;
90 | case 'D':
91 | // delete
92 | h.node_ids = A.node_ids.slice();
93 | h.node_ids.forEach(n_id => {
94 | idNode(n_id).deleted = true;
95 | });
96 | break;
97 | case 'M':
98 | // move and edit are basically the same
99 | case 'E':
100 | // edit
101 | h.node_ids = A.node_ids.slice();
102 | h.oldValues = [];
103 | // h.newValues = [];
104 | let anythingChanged = false;
105 | const allProps = [];
106 | for (let j = 0; j < h.node_ids.length; j++) {
107 | const tnode = idNode(h.node_ids[j]);
108 | const oldValues = {};
109 | const newValues = {};
110 | Object.keys(A.newValues[j]).forEach(prop => {
111 | newValues[prop] = A.newValues[j][prop];
112 | if(prop.indexOf('.')>0){
113 | oldValues[prop] = ('oldValues' in A)?A.oldValues[j][prop]:dotProp(tnode,prop);
114 | setDotProp(tnode, prop, newValues[prop]);
115 |
116 | }else{
117 | oldValues[prop] = ('oldValues' in A)?A.oldValues[j][prop]:tnode[prop];
118 | tnode[prop] = newValues[prop];
119 | }
120 | if(oldValues[prop] != newValues[prop]){
121 | anythingChanged = true;
122 | }
123 |
124 | allProps.push(prop);
125 | });
126 | h.oldValues.push(oldValues);
127 | // h.newValues.push(newValues);
128 | }
129 | if (h.type === 'E'){
130 | // really redraw nodes
131 | h.node_ids.forEach( id => { newNode(idNode(id).dom, false); } );
132 | }
133 |
134 | if(!anythingChanged){
135 | return null;
136 | }
137 |
138 | if (_HISTORY.length > 0) {
139 | const lh = _HISTORY[_HISTORY.length - 1];
140 |
141 | if (lh.type == h.type) {
142 | if (h.node_ids.length == lh.node_ids.length) {
143 | if(equalSetsOfItems(h.node_ids, lh.node_ids)) {
144 | if (equalSetsOfItems(allProps, getAllHistoryProps(lh))) {
145 | // same action basically,
146 | // +old Values stay the same, new ones - already applied.
147 | return null;
148 | }
149 | }
150 | }
151 | }
152 | }
153 |
154 |
155 | break;
156 | default:
157 | throw Error('processAction error: What type of history is [' + h.type + '] ??!');
158 | break;
159 | }
160 | return h;
161 | }
162 |
163 | function applyAction (A) {
164 | /*
165 | * Applies Action A and saves it in _HISTORY
166 | *
167 | *
168 | */
169 | log('applyAction');
170 | log(JSON.stringify(A));
171 |
172 | // if we are not at the end of _HISTORY, clear all after
173 | if (_HISTORY_CURRENT_ID !== lastHistoryID()) {
174 | _HISTORY = _HISTORY.slice(0, _HISTORY_j_Map.get(_HISTORY_CURRENT_ID) + 1);
175 | }
176 |
177 | // do the actual thing, make proper _HISTORY event
178 | const h = processAction(A);
179 | if(h !== null){
180 | redraw();
181 |
182 | h.id = newHistID();
183 | h.timestamp = now();
184 | // TODO: probably calculate state based on action itself?
185 | h.state = currentState();
186 |
187 | // add to _HISTORY and to indices
188 | _HISTORY.push(h);
189 | _HISTORY_CURRENT_ID = h.id;
190 | _HISTORY_Map.set(h.id, h);
191 | _HISTORY_j_Map.set(h.id, _HISTORY.length - 1);
192 |
193 | // save?
194 | _localStorage.save(h.node_ids);
195 |
196 | fillHistoryList();
197 |
198 | updateUndoRedoEnabled();
199 | }else{
200 | // nothing changed!
201 | log('no changes')
202 | }
203 |
204 | return h;
205 | }
206 |
207 | function revertHistory (id) {
208 | /*
209 | *
210 | * Reverts back specified history object
211 | * situation is supposed to be congruent with history
212 | *
213 | */
214 |
215 | // get history object
216 | let h = id.hasOwnProperty('id') ? id:_HISTORY_Map.get(id);
217 |
218 | switch (h.type) {
219 | case 'A':
220 | // ADD
221 | // => delete
222 | h.node_ids.forEach(n_id => {
223 | idNode(n_id).deleted = true;
224 | });
225 | break;
226 | case 'D':
227 | // delete
228 | // => un-delete ;)
229 | h.node_ids.forEach(n_id => {
230 | idNode(n_id).deleted = false;
231 | });
232 | break;
233 | case 'M':
234 | // move
235 | // edit but with a fancy name hence no break
236 | case 'E':
237 | // edit
238 | log('reverting EDIT');
239 | h.newValues = [];
240 | for (let j = 0; j < h.node_ids.length; j++) {
241 | const tnode = idNode(h.node_ids[j]);
242 | const newValues = {};
243 | log(' node ' + idNode(h.node_ids[j]).id);
244 | for (let prop of Object.keys(h.oldValues[j])) {
245 | log(' prop ' + prop);
246 | if(prop.indexOf('.')>0){
247 | // like style.color
248 | newValues[prop] = dotProp(tnode,prop);
249 | setDotProp(tnode, prop, h.oldValues[j][prop]);
250 | }else{
251 | newValues[prop] = tnode[prop];
252 | tnode[prop] = h.oldValues[j][prop];
253 | }
254 | log(' -> ' + newValues[prop]);
255 | }
256 | h.newValues.push(newValues);
257 | }
258 | // sometimes an Update just won't cut it
259 | h.node_ids.forEach( id => { newNode(idNode(id).dom); } );
260 | break;
261 | default:
262 | throw Error('revertHistory error: What type of history is [' + h.type + '] ??!');
263 | break;
264 | }
265 | }
266 |
267 | function goBackInHistory () {
268 | log('goBackInHstory');
269 |
270 | let nowj = _HISTORY_j_Map.get(_HISTORY_CURRENT_ID);
271 | log('current nowj=' + nowj);
272 | if (nowj === -1) {
273 | // before the first one : impossible!
274 | return 0;
275 | }
276 |
277 | revertHistory(_HISTORY[nowj]);
278 |
279 | gotoState(_HISTORY[nowj].state);
280 |
281 | nowj--;
282 | _HISTORY_CURRENT_ID = nowj >= 0 ? _HISTORY[nowj].id:null;
283 | log('nowj=' + nowj);
284 | log('_HISTORY_CURRENT_ID=' + _HISTORY_CURRENT_ID);
285 | }
286 |
287 | function goForwardInHistory () {
288 | log('goForwardInHistory');
289 |
290 | let nowj = _HISTORY_j_Map.get(_HISTORY_CURRENT_ID);
291 | log('current nowj=' + nowj);
292 | if (nowj === _HISTORY.length - 1) {
293 | // after the last one : impossible!
294 | return 0;
295 | }
296 |
297 | nowj++;
298 | processAction(_HISTORY[nowj]);
299 |
300 | gotoState(_HISTORY[nowj].state);
301 |
302 | _HISTORY_CURRENT_ID = _HISTORY[nowj].id;
303 | log('nowj=' + nowj);
304 | log('_HISTORY_CURRENT_ID=' + _HISTORY_CURRENT_ID);
305 | }
306 |
307 | function clearAllHistory() {
308 | _HISTORY = [];
309 | _HISTORY_CURRENT_ID = null;
310 | // delete the deleted nodes ;)
311 | _NODES = _NODES.filter(n => ((!('deleted' in n)) || (n.deleted == false)))
312 |
313 | genHistIDMap();
314 | fillHistoryList();
315 | }
316 |
317 | // :::::::: ::::::::::: ::: ::::::::: ::::::::::: ::: ::: :::::::::
318 | // :+: :+: :+: :+: :+: :+: :+: :+: :+: :+: :+: :+:
319 | // +:+ +:+ +:+ +:+ +:+ +:+ +:+ +:+ +:+ +:+ +:+
320 | // +#++:++#++ +#+ +#++:++#++: +#++:++#: +#+ +#+ +:+ +#++:++#+
321 | // +#+ +#+ +#+ +#+ +#+ +#+ +#+ +#+ +#+ +#+
322 | // #+# #+# #+# #+# #+# #+# #+# #+# #+# #+# #+#
323 | // ######## ### ### ### ### ### ### ######## ###
324 |
325 | if (localStorage['noteplace.history'] !== undefined) {
326 | try {
327 | _HISTORY = JSON.parse(localStorage['noteplace.history']);
328 | }catch (e) {
329 | _HISTORY = null;
330 | }
331 | }
332 |
333 | _HISTORY = _HISTORY || [
334 | // { id: '0', type: 'A', state: { T: [0, 0], S: 1 }, timestamp: 1620040025793, node_ids: ['0', '1'] },
335 | // { id: '1', type: 'D', state: { T: [0, 0], S: 1 }, timestamp: 1620040305793, node_ids: ['1'] },
336 | // { id: '2', type: 'M', state: { T: [0, 0], S: 1 }, timestamp: 1620044005793, node_ids: ['0'], oldValues: [{ x: -100, y: -100 }] },
337 | // { id: '3', type: 'E', state: { T: [-200, -200], S: 0.6 }, timestamp: 1620050025793, node_ids: ['0'], oldValues: [{ rotate: -0.3 }] }
338 | ];
339 |
340 | genHistIDMap();
341 |
342 | if (localStorage['noteplace.history_current'] !== undefined) {
343 | if (getHistory(localStorage['noteplace.history_current'])) {
344 | _HISTORY_CURRENT_ID = localStorage['noteplace.history_current'];
345 | }else {
346 | _HISTORY_CURRENT_ID = null;
347 | }
348 | }
349 | _HISTORY_CURRENT_ID = _HISTORY_CURRENT_ID || lastHistoryID();
350 |
351 | function updateUndoRedoEnabled(){
352 | btnUndo.disabled = (_HISTORY_CURRENT_ID === null);
353 | btnRedo.disabled = (_HISTORY_CURRENT_ID === lastHistoryID());
354 | }
355 |
356 | btnUndo.onclick = function(e) {
357 | goBackInHistory();
358 | updateUndoRedoEnabled();
359 | fillHistoryList();
360 | }
361 |
362 | btnRedo.onclick = function(e) {
363 | goForwardInHistory();
364 | updateUndoRedoEnabled();
365 | fillHistoryList();
366 | }
367 |
368 | function fillHistoryList () {
369 | console.log("idNode");
370 | console.log(idNode);
371 | historyContainer.innerHTML = '';
372 |
373 | const nowDate = new Date();
374 | for( let j=_HISTORY.length-1 ; j>=0 ; j-- ){
375 | const h = _HISTORY[j];
376 |
377 | const date = new Date(h.timestamp);
378 |
379 | const isToday = nowDate.toLocaleDateString() == date.toLocaleDateString();
380 | // no need for date if it was today
381 | let datestr = isToday ? date.toLocaleTimeString() : date.toLocaleString();
382 | datestr = datestr.replaceAll(' ',' ');
383 |
384 | let actionStr = '';
385 |
386 | if (h.type === 'A') {
387 | actionStr = '
';
388 | }else if(h.type === 'D'){
389 | actionStr = '
';
390 | }else if(h.type === 'M'){
391 | actionStr = '
';
392 | }else if(h.type === 'E'){
393 | actionStr = '
';
394 | }
395 |
396 | let nodeStr = '';
397 | if(h.node_ids.length > 1) {
398 | nodeStr = h.node_ids.length + ' node' + (h.node_ids.length>1?'s':'') ;
399 | } else {
400 | var temp = idNode(h.node_ids[0]);
401 | if(temp) {
402 | nodeStr = temp.text.split('\n')[0];
403 | }else{
404 | nodeStr = '???';
405 | console.error('No node with id=',h.node_ids[0])
406 | }
407 | }
408 |
409 | let allstr = datestr + ' ' + actionStr + ' ' + nodeStr;
410 |
411 | const nodePreview = _ce('div'
412 | ,'className', 'np-h-r-c'
413 | ,'innerHTML', allstr
414 | )
415 |
416 | const row = _ce('div'
417 | ,'className','row btn btn-outline-' + ( h.id == _HISTORY_CURRENT_ID ? 'primary' : 'secondary')
418 | ,'onmouseenter', function (e) {
419 | const _h_id = this.dataset['histodyID'];
420 | previewState(getHistory(_h_id).state);
421 | }
422 | ,'onmouseleave', function (e) {
423 | exitPreview();
424 | }
425 | ,'onclick', function (e) {
426 | const _h_id = this.dataset['histodyID'];
427 | const clickJ = _HISTORY_j_Map.get(_h_id);
428 | const nowJ = _HISTORY_j_Map.get(_HISTORY_CURRENT_ID);
429 |
430 | let goBtn = btnUndo;
431 | if(nowJ < clickJ){
432 | goBtn = btnRedo;
433 | }
434 |
435 | while(_HISTORY_CURRENT_ID !== _h_id){
436 | goBtn.click();
437 | }
438 | }
439 | );
440 | row.dataset['histodyID'] = h.id;
441 |
442 | row.appendChild(nodePreview);
443 | // log(allstr);
444 |
445 | historyContainer.appendChild(row);
446 | }
447 |
448 | // start row
449 |
450 | const row = _ce('div'
451 | ,'className','row btn btn-outline-' + ( null == _HISTORY_CURRENT_ID ? 'primary' : 'secondary')
452 | ,'innerHTML','T = 0 , all STARTED'
453 | ,'onmouseenter', function (e) {
454 | _HISTORY.length > 0 ? previewState(_HISTORY[0].state) : '';
455 | }
456 | ,'onmouseleave', function (e) {
457 | _HISTORY.length > 0 ? exitPreview() : '';
458 | }
459 | ,'onclick', function (e) {
460 | while(_HISTORY_CURRENT_ID !== null){
461 | btnUndo.click();
462 | }
463 | }
464 | );
465 | historyContainer.appendChild(row);
466 |
467 | // history status : how many changes, when it all started, ..
468 |
469 | let html = '
';
470 | let title = 'No history';
471 | if(_HISTORY.length > 0) {
472 | let sameDay = false;
473 | if( (new Date(_HISTORY[0].timestamp)).toLocaleDateString
474 | == (new Date()).toLocaleDateString ) {
475 | // same day
476 | sameDay = true;
477 | html = '
';
478 | } else if ( ( now() - _HISTORY[0].timestamp ) < 24 * 3600 * 7 * 1000 ) {
479 | // same week
480 | html = '
';
481 | } else {
482 | html = '
';
483 | }
484 | const firstDate = (new Date(_HISTORY[0].timestamp));
485 | const lastHistDate = new Date(_HISTORY[_HISTORY.length - 1].timestamp);
486 |
487 | title = _HISTORY.length + ' event'
488 | + ( (_HISTORY.length > 1) ? 's' : '')
489 | + ', ' + ( sameDay
490 | ? firstDate.toLocaleDateString() + ', from ' + firstDate.toLocaleTimeString()
491 | : 'from ' + firstDate.toLocaleString()
492 | )
493 | + ' to ' + ( sameDay ? lastHistDate.toLocaleTimeString() : lastHistDate.toLocaleString() )
494 | }
495 |
496 | btnHistoryStatus.innerHTML = html;
497 | btnHistoryStatus.title = title;
498 | }
499 |
500 | _('#btnHistoryClear').onclick = function () {
501 | showModalYesNo(
502 | 'Really?',
503 | 'Are you sure you want to delete all
History?',
504 | function () {
505 | clearAllHistory();
506 | }
507 | )
508 | }
509 |
--------------------------------------------------------------------------------
/places.js:
--------------------------------------------------------------------------------
1 | // _PLACES = {}
2 |
3 | _PLACES_dragContent = null;
4 |
5 | const _PLACES_default = {
6 | name: 'Places',
7 | items: [
8 | { name: 'Home?', state: { T: [0, 0], S: 1 } }
9 | // {name:'Test folder', items:[
10 | // {name:'test 1', state:{T:[-4000,-2000], S:0.1}},
11 | // {name:'Test sub-folder..', items:[
12 | // {name:'test 2', state:{T:[-400000,-200000], S:0.001}},
13 | // {name:'test 3', state:{T:[-400000,-250000], S:0.001}},
14 | // ]}
15 | // ]}
16 | ]
17 | };
18 |
19 | function stripPlace (place = null) {
20 | if (place === null) {
21 | place = _PLACES;
22 | }
23 |
24 | if ('items' in place) {
25 | return {
26 | name: place.name,
27 | items: place.items.map(stripPlace)
28 | };
29 | } else {
30 | return {
31 | name: place.name,
32 | state: {
33 | T: place.state.T.slice(),
34 | S: place.state.S
35 | }
36 | };
37 | }
38 | }
39 |
40 | function placeOKNewName (path, new_name) {
41 | console.log('placeOKNewName path=' + path + ' new_name=' + new_name);
42 | if (!Array.isArray(path)) {
43 | path = JSON.parse(path);
44 | }
45 |
46 | return (pathPlace(path)// .slice(0,-1))
47 | .items
48 | .map(s => s.name)
49 | .indexOf(new_name) === -1
50 | );
51 | }
52 |
53 | // returns _PLACES for path
54 | function pathPlace (path) {
55 | let p = _PLACES;
56 | path = ((typeof (path) === 'string') ? JSON.parse(path) : path).forEach((e) => {
57 | p = p.items[p.items.map(jp => jp.name).indexOf(e)];
58 | });
59 | return p;
60 | }
61 |
62 | function placesUpdatePaths (places = null, _path = []) {
63 | if (!places) {
64 | places = _PLACES.items;
65 | }
66 | for (const place of places) {
67 | const path = _path.slice();
68 | path.push(place.name);
69 | if ('items' in place) {
70 | place.dom.hBtn.dataset.path = JSON.stringify(path);
71 |
72 | placesUpdatePaths(place.items, path);
73 | } else {
74 | place.dom.a.dataset.path = JSON.stringify(path);
75 | }
76 | }
77 | }
78 |
79 | function placeNewName (path, folder = false) {
80 | let _name = 'New ' + (folder ? 'Folder':'Place');
81 | let j = 0;
82 | while (!placeOKNewName(path, _name)) {
83 | j++;
84 | _name = 'New ' + (folder ? 'Folder':'Place') + ' ' + j;
85 | }
86 | return _name;
87 | }
88 |
89 | function addPlaceFolder (path) {
90 | console.log('addPlaceFolder path=' + path);
91 | const place = { name: placeNewName(path, true), items: [] };
92 | const hpath = JSON.parse(path);
93 | hpath.push(place.name);
94 |
95 | const rli = createPlaceFolderDOM(place, JSON.stringify(hpath));
96 |
97 | pathPlace(path).dom.ul.appendChild(rli);
98 | pathPlace(path).items.push(place);
99 |
100 | _localStorage.places.save();
101 |
102 | place.dom.btnEdit.click();
103 | }
104 |
105 | function addPlace (path) {
106 | console.log('addPlace path=' + path);
107 |
108 | const place = { name: placeNewName(path), state: _View.getState()};
109 | const hpath = JSON.parse(path);
110 | hpath.push(place.name);
111 |
112 | const rli = createPlaceDOM(place, JSON.stringify(hpath));
113 |
114 | pathPlace(path).dom.ul.appendChild(rli);
115 | pathPlace(path).items.push(place);
116 |
117 | _localStorage.places.save();
118 |
119 | place.dom.btnEdit.click();
120 | }
121 |
122 | function fillPlaces () {
123 | createPlaceFolderDOM.N = 0;
124 | _('#places-root').innerHTML = '';
125 | _PLACES.dom = {
126 | hBtn: _('#btnPlaces'),
127 | ul: _('#places-root')
128 | };
129 | rec_fillPlace(_PLACES.items, _('#places-root'));
130 | }
131 |
132 | function showPlacePath(path) {
133 | if (!Array.isArray(path)) {
134 | path = JSON.parse(path);
135 | }
136 |
137 | showMenu();
138 |
139 | const btns2check = [_('#btnPlaces')];
140 | for(var j=0 ; j<=path.length-1 ; j++){
141 | const btn = pathPlace(path.slice(0, j)).dom.hBtn;
142 | if(btn.classList.contains('collapsed')){
143 | btn.click();
144 | }
145 | }
146 | }
147 |
148 | function previewPlacePath(path){
149 | const place = pathPlace(path);
150 | const dom = 'items' in place ? place.dom.hBtn : place.dom.a;
151 | dom.classList.add('np-search-place-preview');
152 | }
153 |
154 | function depreviewPlace(){
155 | $('.np-search-place-preview').removeClass('np-search-place-preview');
156 | }
157 |
158 | function rec_fillPlace (places, root_dom, _P_path = '') {
159 | console.log('rec_fillPlace path=' + _P_path);
160 | for (let place of places) {
161 | let path = _P_path ? JSON.parse(_P_path) : [];
162 | path.push(place.name);
163 | path = JSON.stringify(path);
164 |
165 | if ('items' in place) {
166 | // Folder
167 | const rli = createPlaceFolderDOM(place, path);
168 |
169 | console.log(' >rec_fillPlace with path=' + path);
170 | rec_fillPlace(place.items, place.dom.ul, path);
171 |
172 | root_dom.appendChild(rli);
173 | } else {
174 | // place, not a Folder
175 | root_dom.appendChild(createPlaceDOM(place, path));
176 | }
177 | }
178 | }
179 |
180 | function createPlaceFolderDOM (place, path) {
181 | createPlaceFolderDOM.N++;
182 |
183 | const rli = document.createElement('li');
184 | const hid = 'places-collapse-' + createPlaceFolderDOM.N;
185 |
186 | const hdiv = _ce('div');
187 |
188 | const hBtn = _ce('span'
189 | , 'className', 'btn btn-toggle align-items-center collapsed places-folder places-name'
190 | , 'ariaExpanded', 'false'
191 | , 'innerHTML', place.name
192 | , 'title', 'Edit name'
193 | , 'onmouseenter', function (e) {
194 | console.log('mouseenter');
195 | console.log(e);
196 | }
197 | , 'onmouseleave', function (e) {
198 | console.log('onmouseleave');
199 | console.log(e);
200 | }
201 | );
202 | hBtn.dataset.bsToggle = 'collapse';
203 | hBtn.dataset.bsTarget = '#' + hid;
204 | hBtn.dataset.path = path;
205 |
206 | const btnEdit = _ce('button'
207 | , 'className', 'btn p-1'
208 | , 'innerHTML', '
'
209 | , 'onclick', function (e) {
210 | const elt = this.parentNode.getElementsByClassName('places-name')[0];
211 | elt.contentEditable = 'true';
212 | elt.dataset.originalName = elt.innerHTML;
213 | elt.focus();
214 | elt.addEventListener('focusout', function () {
215 | console.log('focusout1!');
216 | console.log(this);
217 | console.log(this.innerHTML);
218 | this.contentEditable = 'false';
219 | if (this.innerHTML != this.dataset.originalName) {
220 | const thisplace = pathPlace(this.dataset.path);
221 | const tpath = JSON.parse(this.dataset.path).slice(0, -1);
222 | if (placeOKNewName(tpath, this.innerHTML)) {
223 | tpath.push(this.innerHTML);
224 | this.dataset.path = JSON.stringify(tpath);
225 | thisplace.name = this.innerHTML;
226 | placesUpdatePaths(thisplace.items, tpath);
227 | save('places');
228 | } else {
229 | // return to original
230 | this.innerHTML = this.dataset.originalName;
231 | }
232 | }
233 | });
234 | elt.addEventListener('keydown', function (e) {
235 | console.log(e);
236 | if (e.key === 'Enter') {
237 | // end editing
238 | this.contentEditable = 'false';
239 | } else if (e.key === ' ') {
240 | // e.preventDefault();
241 | // this.innerHTML+=' ';
242 | // e.stopPropagation();
243 | }
244 | });
245 | }
246 | );
247 |
248 | const btnDel = _ce('button'
249 | , 'className', 'btn text-danger p-1'
250 | , 'innerHTML', '
'
251 | , 'title', 'Delete folder'
252 | , 'onclick', function (e) {
253 | showModalYesNo(
254 | 'Really?'
255 | , 'Are you sure you want to delete folder
' + place.name + ' with
all it\'s contents???'
256 | , function () {
257 | // find parent place
258 | const path = place.dom.hBtn.dataset.path;
259 | let parent_place = pathPlace(JSON.parse(path).slice(0, -1));
260 |
261 | if ('items' in parent_place) {
262 | parent_place = parent_place.items;
263 | }
264 | // remove place
265 | parent_place.splice(parent_place.indexOf(place), 1);
266 |
267 | // remove dom
268 | place.dom.hBtn.parentNode.parentNode.parentNode.removeChild(place.dom.hBtn.parentNode.parentNode);
269 |
270 | save('places');
271 | }
272 | );
273 | }
274 | );
275 |
276 | const btnAddFolder = _ce('button'
277 | , 'className', 'btn p-1'
278 | , 'innerHTML', '
'
279 | , 'title', 'Add sub-folder'
280 | , 'onclick', function (e) {
281 | if (hBtn.ariaExpanded == 'false') {
282 | hBtn.click();
283 | }
284 | addPlaceFolder(place.dom.hBtn.dataset.path);
285 | }
286 | );
287 |
288 | const btnAddPlace = _ce('button'
289 | , 'className', 'btn p-1'
290 | , 'innerHTML', '
'
291 | , 'title', 'Add place to folder'
292 | , 'onclick', function (e) {
293 | if (hBtn.ariaExpanded == 'false') {
294 | hBtn.click();
295 | }
296 | addPlace(place.dom.hBtn.dataset.path);
297 | }
298 | );
299 |
300 | const bdiv = _ce('div'
301 | , 'className', 'collapse'
302 | , 'id', hid
303 | );
304 |
305 | const tul = _ce('ul'
306 | , 'className', 'btn-toggle-nav list-unstyled fw-normal pb-1 small'
307 | );
308 | bdiv.appendChild(tul);
309 |
310 | hdiv.appendChild(hBtn);
311 | hdiv.appendChild(btnEdit);
312 | hdiv.appendChild(btnDel);
313 | hdiv.appendChild(btnAddFolder);
314 | hdiv.appendChild(btnAddPlace);
315 |
316 | place.dom = {
317 | hBtn: hBtn,
318 | btnEdit: btnEdit,
319 | btnDel: btnDel,
320 | btnAddFolder: btnAddFolder,
321 | btnAddPlace: btnAddPlace,
322 | ul: tul
323 | };
324 | rli.appendChild(hdiv);
325 | rli.appendChild(bdiv);
326 |
327 | return rli;
328 | }
329 |
330 | function createPlaceDOM (place, path) {
331 | const rli = _ce('li'
332 | );
333 |
334 | const ta = _ce('a'
335 | , 'className', 'btn col align-self-start align-items-center places-place places-name'
336 | , 'innerHTML', place.name
337 | , 'onclick', function (e) {
338 | gotoState(pathPlace(this.dataset.path).state, false, true);
339 | }
340 | , 'onmouseenter', function (e) {
341 | console.log('enter');
342 | previewState(pathPlace(this.dataset.path).state);
343 | }
344 | , 'onmouseleave', function (e) {
345 | console.log('leave');
346 | exitPreview();
347 | }
348 | );
349 | ta.dataset.path = path;
350 |
351 | const btnEdit = _ce('button'
352 | , 'className', 'btn p-1'
353 | , 'innerHTML', '
'
354 | , 'title', 'Edit name'
355 | , 'onclick', function (e) {
356 | const elt = this.parentNode.getElementsByClassName('places-name')[0];
357 | elt.contentEditable = 'true';
358 | // save for probable collision check
359 | elt.dataset.originalName = elt.innerHTML;
360 | elt.focus();
361 | elt.addEventListener('focusout', function () {
362 | console.log('focusout1!');
363 | console.log(this);
364 | this.contentEditable = 'false';
365 | if (this.innerHTML != this.dataset.originalName) {
366 | console.log(this.dataset.path);
367 | const thisplace = pathPlace(this.dataset.path);
368 | const tpath = JSON.parse(this.dataset.path).slice(0, -1);
369 | if (placeOKNewName(tpath, this.innerHTML)) {
370 | console.log('OK NAME!');
371 | console.log(this.dataset.path);
372 | tpath.push(this.innerHTML);
373 | this.dataset.path = JSON.stringify(tpath);
374 | thisplace.name = this.innerHTML;
375 | console.log(this.dataset.path);
376 | save('places');
377 | } else {
378 | this.innerHTML = this.dataset.originalName;
379 | }
380 | }
381 | });
382 | elt.addEventListener('keydown', function (e) {
383 | if (e.key == 'Enter') {
384 | // end editing
385 | this.contentEditable = 'false';
386 | e.preventDefault();
387 | e.stopPropagation();
388 | }
389 | });
390 | }
391 | );
392 |
393 | const btnDel = _ce('button'
394 | , 'className', 'btn text-danger p-1'
395 | , 'innerHTML', '
'
396 | , 'title', 'Delete place'
397 | , 'onclick', function (e) {
398 | showModalYesNo(
399 | 'Really?'
400 | , 'Are you sure you want to delete
' + place.name + '?'
401 | , function () {
402 | // find parent place
403 | let path = place.dom.a.dataset.path;
404 | parent_place = pathPlace(JSON.parse(path).slice(0, -1));
405 |
406 | if ('items' in parent_place) {
407 | parent_place = parent_place.items;
408 | }
409 | // remove place
410 | parent_place.splice(parent_place.indexOf(place), 1);
411 |
412 | // remove dom
413 | place.dom.a.parentNode.parentNode.removeChild(place.dom.a.parentNode);
414 |
415 | save('places');
416 | }
417 | );
418 | }
419 | );
420 |
421 | const btnSubs = _ce('button'
422 | , 'className', 'btn p-1'
423 | , 'innerHTML', '
'
424 | , 'title', 'Save current here'
425 | , 'onclick', function (e) {
426 | showModalYesNo(
427 | 'Rewrite?'
428 | , 'Are you sure you want to save current position as
' + place.name + '?'
429 | , function () {
430 | place.state = { T: T, S: S };
431 | save('places');
432 | }
433 | );
434 | }
435 | );
436 |
437 | const btnPlace = _ce('button'
438 | , 'className', 'btn p-1 col align-self-end'
439 | , 'innerHTML', '
'
440 | , 'title', 'Drag to place link'
441 | , 'draggable', true
442 | , 'ondragstart', function (e) {
443 | console.log('place drag start');
444 | console.log(e);
445 | e.dataTransfer.effectAllowed = 'all';
446 | _PLACES_dragContent = 'Tap to [' + place.name + '](' + getStateURL(place.state) + ' "Goto ' + place.name + '")';
447 | e.dataTransfer.setData('text', _PLACES_dragContent);
448 | console.log(e);
449 | }
450 | , 'ondragend', function (e) {
451 | _PLACES_dragContent = null;
452 | }
453 | , 'ondrop', function (e) {
454 | _PLACES_dragContent = null;
455 | }
456 | );
457 |
458 | rli.appendChild(ta);
459 | rli.appendChild(btnEdit);
460 | rli.appendChild(btnDel);
461 | rli.appendChild(btnSubs);
462 | rli.appendChild(btnPlace);
463 |
464 | place.dom = {
465 | a: ta,
466 | btnEdit: btnEdit,
467 | btnDel: btnDel,
468 | btnSubs: btnSubs
469 | };
470 |
471 | return rli;
472 | }
473 |
474 | // :::::::: ::::::::::: ::: ::::::::: ::::::::::: ::: ::: :::::::::
475 | // :+: :+: :+: :+: :+: :+: :+: :+: :+: :+: :+: :+:
476 | // +:+ +:+ +:+ +:+ +:+ +:+ +:+ +:+ +:+ +:+ +:+
477 | // +#++:++#++ +#+ +#++:++#++: +#++:++#: +#+ +#+ +:+ +#++:++#+
478 | // +#+ +#+ +#+ +#+ +#+ +#+ +#+ +#+ +#+ +#+
479 | // #+# #+# #+# #+# #+# #+# #+# #+# #+# #+# #+#
480 | // ######## ### ### ### ### ### ### ######## ###
481 |
482 | // :::::::::: ::: ::: :::::::::: :::: ::: ::::::::::: ::::::::
483 | // :+: :+: :+: :+: :+:+: :+: :+: :+: :+:
484 | // +:+ +:+ +:+ +:+ :+:+:+ +:+ +:+ +:+
485 | // +#++:++# +#+ +:+ +#++:++# +#+ +:+ +#+ +#+ +#++:++#++
486 | // +#+ +#+ +#+ +#+ +#+ +#+#+# +#+ +#+
487 | // #+# #+#+#+# #+# #+# #+#+# #+# #+# #+#
488 | // ########## ### ########## ### #### ### ########
489 |
490 | _('#btnNewHomeSubFolder').addEventListener('click', (e) => {
491 | const domwpath = _('#btnPlaces');
492 | if (domwpath.ariaExpanded == 'false') {
493 | domwpath.click();
494 | }
495 |
496 | addPlaceFolder('[]');
497 | });
498 |
499 | _('#btnNewHomeSubPlace').addEventListener('click', (e) => {
500 | const domwpath = _('#btnPlaces');
501 | if (domwpath.ariaExpanded == 'false') {
502 | domwpath.click();
503 | }
504 | addPlace('[]');
505 | });
506 |
507 |
508 |
509 | _('#btnSaveView').dataset.view = null;
510 | _('#btnSaveView').onclick = function () {
511 | const btn = _('#btnSaveView');
512 | btn.dataset.view = getStateURL();
513 |
514 | btn.innerHTML = '
 ';
515 | // btn.getElementsByTagName('i')[0].className='bi-stickies-fill';
516 | // btn.classList.remove('btn-secondary');
517 | // btn.classList.add('btn-success');
518 |
519 | btn.title = 'Saved View ' + btoa(Math.random()).slice(10, 13) + ' ';
520 |
521 | const telt = _ce('i'
522 | , 'className', 'bi-fullscreen'
523 | , 'title', 'Preview'
524 | );
525 | telt.addEventListener('mouseenter', e => {
526 | previewState(_('#btnSaveView').dataset.view);
527 | });
528 | telt.addEventListener('mouseleave', e => {
529 | exitPreview();
530 | });
531 | telt.addEventListener('click', (e) => {
532 | e.stopPropagation();
533 | __previewOldState = { T: T, S: S };
534 | // gotoState(_('#btnSaveView').dataset['view'], false, true);
535 | }, false);
536 |
537 | btn.appendChild(telt);
538 |
539 | btn.title = btn.dataset.view;
540 | btn.draggable = true;
541 |
542 | btn.ondragstart = function (e) {
543 | console.log('btn drag start');
544 | console.log(e);
545 | e.dataTransfer.effectAllowed = 'all';
546 | _PLACES_dragContent = 'Tap to [View](' + this.dataset.view + ' "Saved View")';
547 | e.dataTransfer.setData('text', _PLACES_dragContent);
548 | console.log(e);
549 | };
550 |
551 | btn.ondragend = function (e) {
552 | console.log('btn ondragend');
553 | _PLACES_dragContent = null;
554 | console.log(e);
555 | };
556 |
557 | btn.ondrop = function (e) {
558 | console.log('btn drop');
559 | _PLACES_dragContent = null;
560 | console.log(e);
561 | };
562 | };
563 |
--------------------------------------------------------------------------------
/main.js:
--------------------------------------------------------------------------------
1 | let M = 0;
2 |
3 | let _selected_DOM = [];
4 |
5 | let width = (window.innerWidth || document.documentElement.clientWidth || BODY.clientWidth);
6 | let height = window.innerHeight || document.documentElement.clientHeight || BODY.clientHeight;
7 |
8 | let resizeWatchTimeout = null;
9 | window.addEventListener('resize', function (event) {
10 | // do stuff here
11 | clearTimeout(resizeWatchTimeout);
12 | resizeWatchTimeout = setTimeout(function () {
13 | width = (window.innerWidth || document.documentElement.clientWidth || BODY.clientWidth);
14 | height = window.innerHeight || document.documentElement.clientHeight || BODY.clientHeight;
15 |
16 | }, 500); // run update only every 100ms
17 | });
18 |
19 | const _FreeHand = new FreeHand()
20 |
21 | node_container.ondblclick = function (e) {
22 | console.log('dblclick on empty field at [' + e.clientX + ',' + e.clientY + ']');
23 | console.log(_View.state);
24 | applyAction({
25 | type: 'A',
26 | nodes: [
27 | { text: 'test' + _NODES.length }
28 | ]
29 | });
30 |
31 | e.preventDefault();
32 | e.stopPropagation();
33 |
34 | setTimeout(function () {
35 | onNodeDblClick(_NODES[_NODES.length - 1].dom);
36 | }, 10);
37 | };
38 |
39 | function haveNodesSelection(){
40 | return _selected_DOM.length > 0;
41 | }
42 |
43 | function zoomInOut (inDegree, clientPos = null) {
44 | let centerX = 0;
45 | let centerY = 0;
46 | if (clientPos == null) {
47 | // basically - we pressed a +/- button
48 | if (haveNodesSelection()) {
49 | centerPos = calcCenterPos(_selected_DOM.map(domNode))
50 |
51 | clientPos = _View.posToClient(centerPos)
52 | } else {
53 | // just center
54 | clientPos = [width / 2, height / 2];
55 | }
56 | }
57 | _View.changeZoom(
58 | Math.pow(zoomK, inDegree),
59 | clientPos
60 | )
61 | }
62 |
63 | function fitInBorders(x, minX, maxX){
64 | return ( x > maxX ) ? maxX : ( x < minX ? minX : x);
65 | }
66 |
67 | const wheelZoom_minInterval_ms = 10;
68 |
69 | function onMouseWheel (e) {
70 | // console.log(e);
71 | e.preventDefault();
72 | e.stopPropagation();
73 | // console.log(e.deltaX, e.deltaY, e.deltaFactor);
74 | let hdelta = e.deltaY < 0 ? 1 : -1;
75 | if ((e.ctrlKey) && (_selected_DOM.length > 0)) {
76 | editFontSize(hdelta);
77 | }else {
78 | if (now() - onMouseWheel.lastZoom > wheelZoom_minInterval_ms) {
79 | if (e.ctrlKey) {
80 | hdelta = -e.deltaY / 10;
81 | }
82 | zoomInOut(hdelta, [e.clientX, e.clientY]);
83 | onMouseWheel.lastZoom = now();
84 | }
85 | }
86 | }
87 | onMouseWheel.lastZoom = null;
88 |
89 | // $(container).on('mousewheel', onMouseWheel );
90 | // _('#container').addEventListener("wheel", onMouseWheel);
91 | // _('#node_container').addEventListener("wheel", onMouseWheel);
92 |
93 | // $('#container').bind('mousewheel DOMMouseScroll', onMouseWheel);
94 | // $('#container').scroll(onMouseWheel)
95 |
96 | // safari?
97 | // window.onwheel = onMouseWheel;
98 | // window.addEventListener("wheel",onMouseWheel);
99 | _('#wrapper').onwheel = onMouseWheel;
100 |
101 | // Safari zoomes on pinch no matter what
102 | // all of these do not work =(
103 | window.addEventListener('gestureend', e => {
104 | console.log('gestureend');
105 | console.log(e);
106 | e.preventDefault();
107 | });
108 | window.addEventListener('gesturestart', e => {
109 | console.log('gesturestart');
110 | console.log(e);
111 | e.preventDefault();
112 | });
113 | window.addEventListener('gesturechange', e => {
114 | console.log('gesturechange');
115 | console.log(e);
116 | e.preventDefault();
117 | });
118 |
119 | function setTransitionDur (s) {
120 | $('.node').css('transition-duration', s + 's');
121 | // $('.np-n-c').css('transition-duration', s + 's');
122 | $('#container img').css('transition-duration', s + 's');
123 | $('#container svg').css('transition-duration', s + 's');
124 | // $('#container path').css('transition-duration', s + 's');
125 | // $('#container .ui-wrapper').css('transition-duration', s + 's');
126 | }
127 |
128 | let _Mouse = {
129 | is: {
130 | down: false,
131 | downContentEdit: false,
132 | downPath: false,
133 | dragging: false,
134 | dragSelecting: false,
135 | resizing: false,
136 | },
137 | pos: [0, 0],
138 | clipboard: [], // stores nodes with relative coordinates (x-centerX)*S
139 | dragSelected: [],
140 | rotatingNode: null,
141 | down:{
142 | pos: [0,0],
143 | T: [0,0],
144 | node: null,
145 | },
146 | drag:{
147 | start: [0, 0],
148 | pos: [0, 0],
149 | node: null,
150 | },
151 | }
152 |
153 | node_container.onmousedown = function (e) {
154 | console.log('container.onmousedown');
155 | console.log('T=' + _View.state.T + ' S=' + _View.state.S);
156 |
157 | if (_Mouse.is.resizing) {
158 | console.log('resizing..');
159 | } else {
160 | if (_Mouse.is.downContentEdit) {
161 | log('_Mouse.is.downContentEdit')
162 | _Mouse.is.downContentEdit = false;
163 | } else {
164 | log('!_Mouse.is.downContentEdit')
165 | _Mouse.down.pos = [e.clientX, e.clientY];
166 | _Mouse.down.T = [_View.state.T[0],_View.state.T[1]];
167 | _Mouse.is.down = true;
168 |
169 | if (_ContentEditing.textarea) {
170 | log('_ContentEditing.textarea')
171 | _ContentEditing.stop();
172 | }
173 | }
174 |
175 | if (e.shiftKey) {
176 | _Mouse.is.dragSelecting = true;
177 |
178 | _('#select-box').style.display = 'block';
179 | _('#select-box').style.width = 0;
180 | _('#select-box').style.height = 0;
181 | _('#select-box').style.top = e.clientX.toPx();
182 | _('#select-box').style.left = e.clientY.toPx();
183 | } else {
184 | if (_Mouse.down.node) {
185 | // pass
186 | log('_Mouse.down.node');
187 | } else {
188 | log('!_Mouse.down.node');
189 | // throw Error('aaa');
190 | setTimeout(function(){selectNode(null);},50);
191 | }
192 | }
193 | }
194 | };
195 |
196 | function hideSelectBox(){
197 | _('#select-box').style.display = 'none';
198 | _('#select-box').style.width = 0;
199 | _('#select-box').style.height = 0;
200 | _('#select-box').style.top = 0;
201 | _('#select-box').style.left = 0;
202 | }
203 |
204 | window.addEventListener('mouseup', function (e) {
205 | console.log('window onmouseup');
206 | // console.log('T=' + T + ' S=' + S);
207 |
208 | _Mouse.down.node = null;
209 |
210 | if (_Mouse.drag.node) {
211 | // stop dragging
212 | const A = { type: 'M' }
213 |
214 | if (_selected_DOM.contains(_Mouse.drag.node.dom)) {
215 | // moving all selected
216 | A.node_ids = _selected_DOM.map( dom => domNode(dom).id );
217 | } else {
218 | // moving just the one node
219 | A.node_ids = [ _Mouse.drag.node.id ];
220 | }
221 | A.oldValues = A.node_ids.map( id => idNode(id).startPos );
222 | A.newValues = A.node_ids.map( id => {
223 | const node = idNode(id);
224 | return { x: node.x, y: node.y}
225 | });
226 |
227 | applyAction(A);
228 | // save(_Mouse.drag.node);
229 |
230 | _Mouse.drag.node = false;
231 | } else if (_Mouse.is.dragSelecting) {
232 | hideSelectBox();
233 |
234 | _Mouse.is.dragSelecting = false;
235 |
236 | console.log('updating _Mouse.dragSelected..');
237 | updateDragSelect();
238 | console.log('now ' + _Mouse.dragSelected.length + ' _Mouse.dragSelected');
239 |
240 | console.log('pushing _Mouse.dragSelected doms to _selected_DOM');
241 | _Mouse.dragSelected.forEach((node) => {
242 | console.log(node.dom.id);
243 | _selected_DOM.push(node.dom);
244 | });
245 | _Mouse.dragSelected = [];
246 | } else if (_Mouse.rotatingNode) {
247 |
248 | rotateStop({}, {angle: { current: 0 } } );
249 |
250 | } else if (_Mouse.is.down) {
251 | // stop moving
252 | _View.state.T[0] = _Mouse.down.T[0] - 1 * node_container.dataset.x / _View.state.S;
253 | _View.state.T[1] = _Mouse.down.T[1] - 1 * node_container.dataset.y / _View.state.S;
254 |
255 | node_container.dataset.x = 0;
256 | node_container.dataset.y = 0;
257 |
258 | node_container.style.left = 0;
259 | node_container.style.top = 0;
260 |
261 | replaceHistoryState();
262 |
263 | redraw();
264 | }
265 |
266 | hideGridLine();
267 | _Mouse.is.resizing = false;
268 | _Mouse.is.down = false;
269 | _Mouse.is.downPath = false;
270 | });
271 |
272 | function tempSelect (node) {
273 | if (_selected_DOM.indexOf(node.dom) >= 0) {
274 | // already really selected
275 | } else {
276 | if (_Mouse.dragSelected.indexOf(node) < 0) {
277 | node.dom.classList.add('selected');
278 |
279 | _Mouse.dragSelected.push(node);
280 | }
281 | }
282 | }
283 |
284 | function tempDeselect (node) {
285 | console.log('deselecting: ' + node.dom.id);
286 | if (_selected_DOM.indexOf(node.dom) >= 0) {
287 | // selected previously..
288 | } else {
289 | node.dom.classList.remove('selected');
290 | _Mouse.dragSelected.splice(_Mouse.dragSelected.indexOf(node), 1);
291 | }
292 | }
293 |
294 | function updateDragSelect () {
295 | // so the function os not run two times simultaneously
296 | if (!('on' in updateDragSelect)) {
297 | updateDragSelect.on = true;
298 | } else {
299 | if (updateDragSelect.on) {
300 | setTimeout(updateDragSelect, 100);
301 | return 0;
302 | }
303 | }
304 | //
305 | _Mouse.dragSelected.forEach((node) => {
306 | node.stillSelected = false;
307 | });
308 | _NODES.forEach((node) => {
309 | // if(node.vis){
310 | if (!node.deleted) {
311 | if (nodeIsInClientBox(node,
312 | [_Mouse.pos, _Mouse.down.pos]
313 | )) {
314 | tempSelect(node);
315 | node.stillSelected = true;
316 | }
317 | }
318 | });
319 | //
320 | _Mouse.dragSelected.forEach((node) => {
321 | if (node.stillSelected === false) {
322 | tempDeselect(node);
323 | }
324 | });
325 | //
326 | updateDragSelect.on = false;
327 | }
328 |
329 | function isNodeInBox (node, bxMin, bxMax, byMin, byMax) {
330 | return isInBox(
331 | node.x, node.xMax, node.y, node.yMax,
332 | bxMin, bxMax, byMin, byMax
333 | );
334 | }
335 |
336 | function nodeBox(node){
337 | return [[node.x, node.y], [node.xMax, node.yMax]];
338 | }
339 |
340 | function nodeIsInBox(node, box){
341 | return isInBox( nodeBox(node), box );
342 | }
343 |
344 | function nodeIsInClientBox(node, cBox){
345 | return nodeIsInBox(node, cBox.map((pos)=>clientToPos(pos)));
346 | }
347 |
348 | function allVisibleNodesProps(prop='x', except=[]){
349 | const except_ids = except.map( n => n.id )
350 | const R = new Map();
351 | for( let j=0 ; j<_NODES.length ; j++){
352 | const node = _NODES[j];
353 | if ((!node.vis)||(node.deleted))
354 | continue;
355 | if (except_ids.indexOf(node.id) >= 0)
356 | continue;
357 |
358 | R.set(node.id, node[prop]);
359 | }
360 | return R;
361 | }
362 |
363 | _theGridAlignLines = { x: null, y:null };
364 |
365 | function gridAlignLine(node1, node2, prop){
366 | if(_theGridAlignLines[prop] == null){
367 | _theGridAlignLines[prop] = _ce('div'
368 | ,'className','grid-align-line'
369 | );
370 | node_container.appendChild(_theGridAlignLines[prop]);
371 | }
372 | _theGridAlignLines[prop].style.display = '';
373 | const prop2 = 'y';
374 |
375 | let widthprop = 'width';
376 | let heightprop = 'height';
377 | let domProp = 'left';
378 | let domProp2 = 'top';
379 | if(prop == 'y'){
380 | widthprop = 'height';
381 | heightprop = 'width';
382 | domProp = 'top';
383 | domProp2 = 'left';
384 | }
385 |
386 | const delta = 0;
387 |
388 | node1prop = node1.dom.style[domProp].pxToFloat();
389 | node2prop = node2.dom.style[domProp].pxToFloat();
390 | node1prop2 = node1.dom.style[domProp2].pxToFloat();
391 | node2prop2 = node2.dom.style[domProp2].pxToFloat();
392 |
393 | _theGridAlignLines[prop].style[widthprop] = 1;
394 | _theGridAlignLines[prop].style[domProp] = Math.min( node1prop , node2prop ).toPx();
395 |
396 | let tx = Math.min( node1prop2 , node2prop2) - delta;
397 | // log('tx=');
398 | // log(tx);
399 |
400 | _theGridAlignLines[prop].style[domProp2] = tx.toPx();
401 | // log(_theGridAlignLines[prop].style[domProp2])
402 | _theGridAlignLines[prop].style[heightprop] = (Math.max( node1prop2 , node2prop2) - tx + 2 * delta ).toPx();
403 |
404 | }
405 |
406 | function hideGridLine(prop){
407 | // log(prop);
408 | if(prop == null){
409 | Object.keys(_theGridAlignLines).forEach( hideGridLine );
410 | return 0;
411 | }
412 | if(_theGridAlignLines[prop] !== null) {
413 | _theGridAlignLines[prop].style.display = 'none';
414 | }
415 | }
416 |
417 | container.onmousemove = function (e) {
418 | _Mouse.pos = [e.clientX, e.clientY];
419 | if (_Mouse.is.down) {
420 | if (_Mouse.drag.node) {
421 | const deltaMove = {
422 | x: (e.clientX - _Mouse.drag.start[0]) / _View.state.S ,
423 | y: (e.clientY - _Mouse.drag.start[1]) / _View.state.S
424 | }
425 | const sizeScreen = {
426 | x: width,
427 | y: height
428 | }
429 | let updatePosNodes = [_Mouse.drag.node];
430 |
431 | if (_Mouse.drag.node.dom.classList.contains('selected')) {
432 | // move all selected
433 | updatePosNodes = _selected_DOM.map(domNode);
434 | }
435 |
436 | for(let prop of ['x','y']){
437 | _Mouse.drag.node[prop] = _Mouse.drag.node.startPos[prop] + deltaMove[prop];
438 |
439 | allPropsMap = allVisibleNodesProps(prop, [_Mouse.drag.node]);
440 |
441 | allPropsAbs = [...allPropsMap.values()].map( v => Math.abs( v - _Mouse.drag.node[prop] ));
442 | minAbs = Math.min(...allPropsAbs)
443 | minJ = allPropsAbs.indexOf(minAbs);
444 |
445 | minDvh = minAbs * _View.state.S / sizeScreen[prop];
446 |
447 | if(minDvh < 0.01){
448 | //
449 | // log('seems like node '+[...allPropsMap.keys()][minJ]+' is OK, huh?');
450 | // _Mouse.drag.node[prop] = [...allPropsMap.values()][minJ];
451 | deltaMove[prop] = [...allPropsMap.values()][minJ] - _Mouse.drag.node.startPos[prop];
452 |
453 | gridAlignLine(_Mouse.drag.node, idNode([...allPropsMap.keys()][minJ]), prop);
454 | } else {
455 | hideGridLine(prop);
456 | }
457 | }
458 | updatePosNodes.forEach(function (node) {
459 | node.x = node.startPos.x + deltaMove.x;
460 | node.y = node.startPos.y + deltaMove.y;
461 | calcBox(node);
462 | updateNode(node);
463 | });
464 | // move the node under the cursor
465 | // _Mouse.drag.node.x = _Mouse.drag.node.startPos.x + deltaMove.x;
466 | // _Mouse.drag.node.y = _Mouse.drag.node.startPos.y + deltaMove.y;
467 | // calcBox(_Mouse.drag.node);
468 | // updateNode(_Mouse.drag.node);
469 | // }
470 | } else if (_Mouse.is.resizing) {
471 | // pass
472 | } else if (_Mouse.is.dragSelecting) {
473 | _('#select-box').style.left = Math.min(e.clientX, _Mouse.down.pos[0]).toPx();
474 | _('#select-box').style.width = Math.abs(e.clientX - _Mouse.down.pos[0]).toPx();
475 | _('#select-box').style.top = Math.min(e.clientY, _Mouse.down.pos[1]).toPx();
476 | _('#select-box').style.height = Math.abs(e.clientY - _Mouse.down.pos[1]).toPx();
477 |
478 | updateDragSelect();
479 | // clearTimeout(_dragSelectingTimeout);
480 | // _dragSelectingTimeout = setTimeout(updateDragSelect, 500);
481 | } else {
482 | // T[0] = _Mouse.down.T[0] - (e.clientX - _Mouse.down.pos[0])/S;
483 | // T[1] = _Mouse.down.T[1] - (e.clientY - _Mouse.down.pos[1])/S;
484 | node_container.dataset.x = (e.clientX - _Mouse.down.pos[0]);
485 | node_container.dataset.y = (e.clientY - _Mouse.down.pos[1]);
486 | node_container.style.left = node_container.dataset.x.toPx();
487 | node_container.style.top = node_container.dataset.y.toPx();
488 |
489 | // redraw()
490 | }
491 | }
492 |
493 | if(_previewNode){
494 | _previewNode.style.left = e.clientX;
495 | _previewNode.style.top = e.clientY;
496 | }
497 | };
498 |
499 | _previewNode = null;
500 |
501 | // ::: ::: :::::::::: ::: ::: ::::::::: :::::::: ::: ::: :::: :::
502 | // :+: :+: :+: :+: :+: :+: :+: :+: :+: :+: :+: :+:+: :+:
503 | // +:+ +:+ +:+ +:+ +:+ +:+ +:+ +:+ +:+ +:+ +:+ :+:+:+ +:+
504 | // +#++:++ +#++:++# +#++: +#+ +:+ +#+ +:+ +#+ +:+ +#+ +#+ +:+ +#+
505 | // +#+ +#+ +#+ +#+ +#+ +#+ +#+ +#+ +#+ +#+#+ +#+ +#+ +#+#+#
506 | // #+# #+# #+# #+# #+# #+# #+# #+# #+#+# #+#+# #+# #+#+#
507 | // ### ### ########## ### ######### ######## ### ### ### ####
508 |
509 |
510 | function calcCenterPos (nodes) {
511 | const R = [0, 0];
512 | nodes.forEach(function (node) {
513 | R[0] += node.x;
514 | R[1] += node.y;
515 | });
516 | return [R[0] / nodes.length, R[1] / nodes.length];
517 | }
518 |
519 | function copySelectedNodesToClipboard (de_id = false) {
520 | if (('on' in copySelectedNodesToClipboard) && (copySelectedNodesToClipboard.on)) {
521 | // pass
522 | } else {
523 | copySelectedNodesToClipboard.on = true;
524 | _Mouse.clipboard = _selected_DOM.map(domNode).map(stripNode);
525 | const centerPos = calcCenterPos(_Mouse.clipboard);
526 | _Mouse.clipboard.forEach((node) => {
527 | // yeah, my definition of S is counterintuitive here..
528 | node.x = (node.x - centerPos[0]) * _View.state.S;
529 | node.y = (node.y - centerPos[1]) * _View.state.S;
530 | node.fontSize *= _View.state.S;
531 | });
532 |
533 | copyToClipboard(JSON.stringify(_Mouse.clipboard));
534 |
535 | copySelectedNodesToClipboard.on = false;
536 | }
537 | }
538 |
539 | window.addEventListener('keydown', (e) => {
540 | console.log('keydown');
541 | console.log(e);
542 | if (e.key === 'Delete') {
543 | if (_ContentEditing.textarea) {
544 | // pass
545 | }else {
546 | if (_selected_DOM.length > 0) {
547 | applyAction({
548 | type: 'D',
549 | node_ids: _selected_DOM.map(dom => domNode(dom).id)
550 | });
551 | // _selected_DOM.forEach(deleteNode);
552 | // save('ids');
553 | }
554 | }
555 | } else if (e.key === 'Enter') {
556 | if (_ContentEditing.textarea) {
557 | e.stopPropagation();
558 | return 0;
559 | } else {
560 | if (_selected_DOM.length > 0) {
561 | // select, start editing
562 | onNodeDblClick(_selected_DOM[0]);
563 | // stop so that Enter does not overwrite the node content
564 | e.stopPropagation();
565 | e.preventDefault();
566 | }
567 | }
568 | } else if (e.key === 'c') {
569 | if (e.ctrlKey) {
570 | // Ctrl-C !
571 | // moved to copy event
572 | // copySelectedNodesToClipboard();
573 | }
574 | } else if (e.key === 'x') {
575 | if (e.ctrlKey) {
576 | // Ctrl-X
577 | // moved to cut event
578 | // copySelectedNodesToClipboard();
579 | // _selected_DOM.forEach(deleteNode);
580 | // _selected_DOM = [];
581 | }
582 | } else if (e.key === 'v') {
583 | if (e.ctrlKey) {
584 | // Ctrl-V !
585 | if (_ContentEditing.textarea) {
586 | // pass
587 | } else {
588 | // moved to window paste
589 | }
590 | }
591 | } else if (e.code === 'F3' || ((e.ctrlKey || e.metaKey) && e.code === 'KeyF')) {
592 | _('#search-toggle').click();
593 | e.preventDefault();
594 | } else if ((e.code == "KeyZ") && ( (e.ctrlKey) || (e.metaKey) )) {
595 | if (_ContentEditing.textarea){
596 | // pass
597 | }else{
598 | if (e.shiftKey){
599 | // Shift - Meta - Z = Re-do on Macs
600 | _('#btnRedo').click();
601 | }else{
602 | // Ctrl/Meta - Z
603 | _('#btnUndo').click();
604 | }
605 | }
606 | } else if ((e.code == "KeyY") && ( (e.ctrlKey) || (e.metaKey) )) {
607 | // Ctrl-Y was Re-do, right?
608 | _('#btnRedo').click();
609 | }
610 | });
611 |
612 | let __IMG = null;
613 | let __X = null;
614 |
615 |
616 | let _oldValues = null;
617 |
618 | function rotateStop(e, ui) {
619 | console.log('rotate stop');
620 | console.log(e);
621 | console.log(ui);
622 |
623 |
624 |
625 | // ui.angle.start = ui.angle.current;
626 | applyAction({
627 | type: 'E',
628 | node_ids: [ _Mouse.rotatingNode.id ],
629 | oldValues: _oldValues,
630 | newValues: [{ rotate: ui.angle.current }]
631 | });
632 |
633 | _Mouse.rotatingNode.dom.style.transform = 'rotate('+ui.angle.current+'rad)';
634 | save(_Mouse.rotatingNode);
635 | _Mouse.rotatingNode = null;
636 | }
637 |
638 | class Set{
639 | constructor(){
640 | this.arr = [];
641 | }
642 |
643 | }
644 |
645 | class NodeSelection{
646 | constructor() {
647 | this.nodes = [];
648 | this.control = null;
649 | }
650 |
651 | add(node){
652 | if (this.empty) {
653 | this.createControl();
654 | }
655 | this.nodes.push(node);
656 | }
657 |
658 | contains(node){
659 | return this.nodes.indexOf(node)>=0;
660 | }
661 |
662 | clear(){
663 | this.deleteControl();
664 |
665 | this.nodes = [];
666 | }
667 |
668 | get length() { return this.nodes.length; }
669 | get empty() { return this.nodes.length > 0; }
670 |
671 | createControl() {
672 | console.log('createControl placeholder');
673 | }
674 |
675 | deleteControl() {
676 | console.log('deleteControl placeholder');
677 | }
678 | }
679 |
680 | _Selection = new NodeSelection();
681 |
682 |
683 |
684 | function selectNode (n) {
685 | console.log('select')
686 | if ( n == null) {
687 | console.log('[null]')
688 | _selected_DOM.forEach(deselectOneDOM);
689 | _selected_DOM = [];
690 | return 0;
691 | }
692 | if (!Array.isArray(n)) {
693 | log(`[${n.id}]`);
694 | n = [n];
695 | } else {
696 | log( n.map( dom => dom.id) );
697 | }
698 | if (_selected_DOM.length > 0) {
699 | // see how many are new ones
700 | if (n.every((dom) => dom.classList.contains('selected'))) {
701 | // if all are already selected - deselect them!
702 | n.forEach(deselectOneDOM);
703 | n.forEach((dom) => {
704 | _selected_DOM.splice(_selected_DOM.indexOf(dom));
705 | });
706 | }else {
707 | // if only some are already selected - add all new ones
708 | for (let dom of n) {
709 | if (dom.classList.contains('selected')) {
710 | continue;
711 | }else {
712 | selectOneDOM(dom);
713 | _selected_DOM.push(dom);
714 | }
715 | }
716 | }
717 | }else {
718 | _selected_DOM = n.slice();
719 | _selected_DOM.forEach(selectOneDOM);
720 | }
721 | return 0;
722 | }
723 |
724 |
725 | function deselectOneDOM (dom) {
726 | dom.classList.remove('selected');
727 | try {
728 | $(dom).rotatable('destroy');
729 | } catch(e) {
730 | // log('error in rotatable destroy..');
731 | // log(e);
732 | }
733 |
734 | try{
735 | $(domNode(dom).content_dom).resizable('destroy');
736 | } catch (e) {
737 | // console.log('some error in resizable destroy');
738 | // console.log(e);
739 | }
740 | }
741 |
742 |
743 | function selectOneDOM (dom) {
744 | dom.classList.add('selected');
745 |
746 | const node = domNode(dom);
747 | node.startPos = {x: node.x, y: node.y};
748 |
749 | // https://jsfiddle.net/Twisty/7zc36sug/
750 | // https://stackoverflow.com/a/62379454/2624911
751 | // https://jsfiddle.net/Twisty/cdLn56f1/
752 | $(function () {
753 | console.log('init rotation, rotate node_'+node.id+'='+node.rotate)
754 | const params = {
755 | radians: node.rotate,
756 | angle: node.rotate,
757 | start: function (e, ui) {
758 | console.log('rotate start');
759 | console.log(e);
760 | log(ui);
761 | _oldValues = [{ rotate: node.rotate }];
762 | _Mouse.rotatingNode = node;
763 | },
764 | stop: rotateStop
765 | };
766 | log(params);
767 |
768 | if($(dom)
769 | .find('.ui-rotatable-handle').length>0){
770 | }else
771 | $(node.dom).rotatable(params);
772 |
773 | $(dom)
774 | .find('.ui-rotatable-handle')
775 | .on('mouseup', function (e) {
776 | log('rotatable mouse up');
777 | log(e);
778 | // e.stopPropagation();
779 | })
780 | .on('click', function(e) {
781 | log('rotatable click');
782 | e.stopPropagation();
783 | })
784 | .on('dblclick', function (e) {
785 | log('rotatable dblclick');
786 | _Mouse.rotatingNode = node;
787 | rotateStop({}, { angle: { current: 0 }});
788 | e.stopPropagation();
789 | })
790 |
791 | let resizeParams = {
792 | // autoHide: true,
793 | start: function (e, ui) {
794 | console.log('resize start');
795 | console.log(e);
796 | console.log(ui);
797 | _oldValues = [{ fontSize: node.fontSize }];
798 | },
799 | stop: function (e, ui) {
800 | console.log('resize stop');
801 | console.log(e);
802 | console.log(ui);
803 | applyAction({
804 | type:'E',
805 | node_ids:[ node.id ],
806 | oldValues: _oldValues,
807 | newValues: [{ fontSize: node.fontSize }]
808 | })
809 | },
810 | resize: function (e, ui) {
811 | node.fontSize = ui.size.height * _oldValues[0].fontSize / ui.originalSize.height;
812 | updateNode(node);
813 | }
814 | };
815 |
816 | if (node.is_img){
817 | resizeParams.aspectRatio = true;
818 | }else if(node.is_svg) {
819 | resizeParams.aspectRatio = true;
820 | }
821 |
822 | $(node.content_dom).resizable(resizeParams);
823 | $(dom)
824 | .find('.ui-resizable-handle')
825 | .on('mousedown', function (e) {
826 | console.log('resize mouse down');
827 | // e.stopPropagation();
828 | _Mouse.is.resizing = true;
829 | })
830 | .on('mouseup', function (e) {
831 | console.log('resize mouse up');
832 | // e.stopPropagation();
833 | });
834 | });
835 | }
836 |
837 | function isMenuShown(){
838 | return !_('#wrapper').classList.contains('toggled');
839 | }
840 |
841 | function showMenu(){
842 | if(!isMenuShown()){
843 | _('#menu-toggle').click();
844 | }
845 | }
846 |
847 | // :::: ::: :::::::: ::::::::: :::::::::: :::::::::: ::: ::: :::: ::: :::::::: ::::::::
848 | // :+:+: :+: :+: :+: :+: :+: :+: :+: :+: :+: :+:+: :+: :+: :+: :+: :+:
849 | // :+:+:+ +:+ +:+ +:+ +:+ +:+ +:+ +:+ +:+ +:+ :+:+:+ +:+ +:+ +:+
850 | // +#+ +:+ +#+ +#+ +:+ +#+ +:+ +#++:++# :#::+::# +#+ +:+ +#+ +:+ +#+ +#+ +#++:++#++
851 | // +#+ +#+#+# +#+ +#+ +#+ +#+ +#+ +#+ +#+ +#+ +#+ +#+#+# +#+ +#+
852 | // #+# #+#+# #+# #+# #+# #+# #+# #+# #+# #+# #+# #+#+# #+# #+# #+# #+#
853 | // ### #### ######## ######### ########## ### ######## ### #### ######## ########
854 |
855 | let _ContentEditing = {
856 | dom: null,
857 | textarea: null,
858 | stop: function(){
859 | log('_ContentEditing.stop');
860 | let tdom = _ContentEditing.textarea.parentElement.parentElement;
861 | _ContentEditing.textarea.parentElement.removeChild(_ContentEditing.textarea);
862 | const A = { type: 'D', node_ids: [ domNode(tdom).id ] };
863 | if (_ContentEditing.textarea.value === '') {
864 | //deleteNode(tdom);
865 | _ContentEditing.dom = null;
866 | }else {
867 | A.type = 'E';
868 | A.oldValues = [ { text: domNode(tdom).startText } ];
869 | A.newValues = [ { text: domNode(tdom).text } ];
870 | // A.nodes = [ domNode(tdom) ];
871 | // newNode(tdom);
872 | }
873 | applyAction(A);
874 | _ContentEditing.textarea = null;
875 | },
876 | start: function(node){
877 |
878 | },
879 | isInProgress: function nodeEditInProgress() {
880 | return _ContentEditing.dom;
881 | },
882 | }
883 |
884 | // let _ContentEditing.dom = null;
885 | // let _ContentEditing.textarea = null;
886 |
887 | function textareaAutoResize (e) {
888 | var target = ('style' in e) ? e : this;
889 | target.style.height = 'auto';
890 | target.style.height = target.scrollHeight.toPx();
891 | }
892 |
893 | function textareaBtnDown (e) {
894 | log('textareaBtnDown');
895 | log(e);
896 | if ((e.ctrlKey) && ((e.keyCode === 0xA) || (e.keyCode === 0xD))) {
897 | log('Ctrl+Enter!');
898 | _ContentEditing.stop();
899 | e.stopPropagation();
900 |
901 | }
902 | if ((e.key === 'Enter') && (e.shiftKey)) {
903 | log('Shift-enter');
904 | const tnode_orig = domNode(_ContentEditing.dom.parentElement);
905 | const tnode = stripNode(tnode_orig);
906 | let th = _ContentEditing.dom.getBoundingClientRect().height;
907 |
908 | _ContentEditing.stop();
909 |
910 | if (_ContentEditing.isInProgress()) {
911 | // if the node has not been deleted (as empty)
912 | // , get actual height (not height of markdown textarea)
913 | th = tnode_orig.content_dom.getBoundingClientRect().height;
914 | }
915 |
916 | applyAction({
917 | type:'A',
918 | nodes: [{
919 | x: 1 * tnode.x,
920 | y: 1 * tnode.y +
921 | // + 1*tnode['fontSize']
922 | (th / _View.state.S),
923 | text: '',
924 | fontSize: tnode.fontSize
925 | }]
926 | })
927 |
928 | setTimeout(function(){
929 | selectNode(null);
930 |
931 | console.log(_NODES[_NODES.length - 1].dom)
932 | selectNode(_NODES[_NODES.length - 1].dom);
933 |
934 | onNodeDblClick(_NODES[_NODES.length - 1].dom);
935 |
936 | // neither preventDefault nor stopPropagation
937 | // stoped newline from appearing
938 | setTimeout(function () {
939 | _ContentEditing.textarea.value = '';
940 | _ContentEditing.textarea.focus();
941 | }, 10);
942 | },10);
943 |
944 | e.stopPropagation();
945 |
946 | }
947 |
948 | if (e.key === 'Tab') {
949 | // Tab
950 |
951 | // no jump-to-next-field
952 | e.preventDefault();
953 | }
954 |
955 | // https://stackoverflow.com/a/3369624/2624911
956 | if (_ContentEditing.isInProgress()) {
957 | if (e.key === 'Escape') { // escape key maps to keycode `27`
958 | log('Escape!');
959 |
960 | selectNode(_ContentEditing.dom.parentElement);
961 |
962 | _ContentEditing.stop();
963 |
964 | e.stopPropagation();
965 | }
966 | }
967 | }
968 |
969 | function onNodeDblClick (e) {
970 | if ('preventDefault' in e) {
971 | _ContentEditing.dom = this;
972 | } else {
973 | _ContentEditing.dom = e;
974 | }
975 |
976 | if ( (!domNode(_ContentEditing.dom).is_svg)
977 | ||((domNode(_ContentEditing.dom).is_svg)
978 | &&(onNodeDblClick.path_ok))) {
979 |
980 | if ('preventDefault' in e) {
981 | e.preventDefault();
982 | e.stopPropagation();
983 | }
984 | console.log('double-clicked on [' + _ContentEditing.dom.id + '] : ' + _ContentEditing.dom.innerText);
985 | console.log(_ContentEditing.dom);
986 |
987 | const node = domNode(_ContentEditing.dom);
988 | node.startText = node.text;
989 |
990 | _ContentEditing.dom = _ContentEditing.dom.getElementsByClassName('np-n-c')[0];
991 |
992 | ta = document.createElement('textarea');
993 | ta.id = 'ta';
994 | ta.value = node.text;
995 |
996 | ta.dataset.initS = _View.state.S;
997 | ta.dataset.initWidth = Math.max(width / 3, _ContentEditing.dom.getBoundingClientRect().width + 20);
998 | ta.dataset.initHeight = _ContentEditing.dom.getBoundingClientRect().height;
999 | ta.style.width = ta.dataset.initWidth.toPx();
1000 | ta.style.height = ta.dataset['initHeight'].toPx();
1001 |
1002 | ta.onkeydown = textareaBtnDown;
1003 | ta.onkeyup = textareaAutoResize;
1004 | ta.oninput = function (e) {
1005 | console.log('ta input');
1006 | domNode(this.parentElement.parentElement).text = this.value;
1007 | };
1008 |
1009 | ta.onmousedown = (e) => {
1010 | if (e.button === 1) {
1011 | // drag on middle button => just pass the event
1012 | } else {
1013 | _Mouse.is.downContentEdit = true;
1014 | }
1015 | };
1016 |
1017 | _ContentEditing.dom.innerHTML = '';
1018 | _ContentEditing.dom.appendChild(ta);
1019 |
1020 | ta.select();
1021 | _ContentEditing.textarea = ta;
1022 |
1023 | }
1024 | onNodeDblClick.path_ok = false;
1025 | }
1026 |
1027 |
1028 | function onNodeMouseDown (e) {
1029 | console.log('onNodeMouseBtn');
1030 | console.log(this.id);
1031 |
1032 | if((!domNode(this).is_svg)||((domNode(this).is_svg)&&(onNodeMouseDown.path_ok))){
1033 | _Mouse.down.node = domNode(this);
1034 | // console.log(e);
1035 | if (e.button === 1) {
1036 | _Mouse.drag.node = _Mouse.down.node;
1037 | _Mouse.drag.start = [e.clientX, e.clientY];
1038 |
1039 | let applyDrag = [ this ];
1040 | if (this.classList.contains('selected')) {
1041 | // save all selected positions
1042 | applyDrag = _selected_DOM;
1043 | }
1044 | applyDrag.forEach((dom) => {
1045 | const node = domNode(dom);
1046 | node.startPos = { x: node.x, y: node.y };
1047 | });
1048 |
1049 | e.preventDefault();
1050 | } else if (e.button === 0) {
1051 | // left mouse button
1052 | console.log(e);
1053 | } else {
1054 | // pass
1055 | }
1056 | }
1057 | if (e.ctrlKey) {
1058 | e.preventDefault();
1059 | }
1060 |
1061 | onNodeMouseDown.path_ok=false;
1062 | }
1063 |
1064 | function onNodeClick (e) {
1065 | console.log('clicked on [' + this.id + '] : ' + this.innerText);
1066 |
1067 | if((!domNode(this).is_svg)||((domNode(this).is_svg)&&(onNodeClick.path_ok))){
1068 |
1069 | if (e.shiftKey) {
1070 | // multiselect!
1071 | // selectNode(this); //already handled via drag-select
1072 | // e.stopPropagation();
1073 | } else {
1074 | selectNode(null);
1075 | selectNode(this);
1076 | }
1077 |
1078 | }
1079 | onNodeClick.path_ok = false;
1080 | }
1081 |
1082 | function changeStrokeWidth(delta=1){
1083 | const node_ids = [];
1084 | const newValues = [];
1085 |
1086 | const k = Math.pow(1.25, delta);
1087 |
1088 | _selected_DOM.forEach((dom) => {
1089 | const node = domNode(dom);
1090 | if (node.is_svg == false) {
1091 | return 0;
1092 | }
1093 |
1094 | node_ids.push(node.id);
1095 | newValues.push( {
1096 | 'style.strokeWidth': (node.style.strokeWidth || 1) * k
1097 | });
1098 | })
1099 |
1100 | applyAction( {
1101 | type: 'E',
1102 | node_ids: node_ids,
1103 | newValues: newValues
1104 | })
1105 | }
1106 |
1107 | function updateNode (node_) {
1108 | let node = node_;
1109 | if (node.hasOwnProperty('dom')) {
1110 | dom = node.dom;
1111 | } else {
1112 | dom = node_;
1113 | node = domNode(dom);
1114 | }
1115 |
1116 | var pos = _View.posToClient(node.x, node.y);
1117 | dom.style.left = pos[0].toPx();
1118 | dom.style.top = pos[1].toPx();
1119 | dom.style.fontSize = (node.fontSize * _View.state.S).toPx();
1120 |
1121 | dom.style.zIndex = Math.floor(200 - 10 * Math.log((node.fontSize) * _View.state.S ));
1122 |
1123 | let k = ( _View.state.S * node.fontSize / 20);
1124 |
1125 | if(node.is_svg){
1126 | node.content_dom.style.position = 'relative';
1127 | node.content_dom.style.left = '0px';
1128 | node.content_dom.style.top = '0px';
1129 |
1130 | let ow = node.svg_dom.getAttribute('width')*1;
1131 | let oh = node.svg_dom.getAttribute('height')*1
1132 | node.content_dom.style.width = (ow * k).toPx();
1133 | node.content_dom.style.height = (oh * k).toPx();
1134 | node.dom.style.height = (oh * k).toPx();
1135 | node.dom.style.width = (ow * k).toPx();
1136 | node.svg_dom.style.transform = 'translate(-'+ow/2+'px,-'+oh/2+'px) scale(' + k + ')' +' translate('+ow/2+'px,'+oh/2+'px)';// + ' rotate('+node.rotate+'rad) ' ;
1137 | }
1138 |
1139 | if(node.is_img){
1140 | if(node.size){
1141 | const h = 5 * (node.fontSize) * _View.state.S;
1142 | const w = h * node.size[0] / node.size[1];
1143 | node.content_dom.style.width = w.toPx();
1144 | node.content_dom.style.height = h.toPx();
1145 | node.img_dom.style.width = w.toPx();
1146 | node.img_dom.style.height = h.toPx();
1147 | }
1148 | }else{
1149 | dom.getElementsByTagName('img').forEach( (e) => {
1150 | e.style.width = 'auto';
1151 | e.style.height = (100 * k).toPx();
1152 | });
1153 | }
1154 |
1155 | if (node.rotate !== 0) {
1156 | if(dom.classList.contains('selected')){
1157 |
1158 | }else{
1159 | dom.style.transform = 'rotate(' + node.rotate + 'rad)';
1160 | }
1161 | }
1162 | }
1163 |
1164 | function updateSizes () {
1165 | if (!('state' in updateSizes)) {
1166 | updateSizes.state = '';
1167 | }
1168 | const state = JSON.stringify(_View.state);
1169 | if (state === updateSizes.state) {
1170 | // pass
1171 | } else {
1172 | for (let j = 0; j < _NODES.length; j++) {
1173 | if (_NODES[j].vis) {
1174 | const d = _NODES[j];
1175 | d.xMax = d.x + d.dom.clientWidth / _View.state.S;
1176 | d.yMax = d.y + d.dom.clientHeight / _View.state.S;
1177 | }
1178 | }
1179 | updateSizes.state = state;
1180 | }
1181 |
1182 | setTimeout(updateSizes, 100);
1183 | }
1184 |
1185 | function calcBox (d) {
1186 | if (d.text.indexOf('![') >= 0) {
1187 | d.xMax = d.x + d.fontSize * 10;
1188 | d.yMax = d.y + d.fontSize * 5;
1189 | } else {
1190 | d.xMax = d.x + d.text.length * d.fontSize * 0.5;
1191 | d.yMax = d.y + d.text.split('\n').length * d.fontSize;
1192 | }
1193 | }
1194 |
1195 | function isVisible (d) {
1196 | if (!('xMax' in d)) {
1197 | calcBox(d);
1198 | }
1199 | return (
1200 | (d.fontSize > 0.2 / _View.state.S)
1201 | && _View.isBoxSeen(d.x, d.xMax, d.y, d.yMax)
1202 | );
1203 | }
1204 |
1205 | function calcVisible (d, onhide, onshow) {
1206 | const newVis = isVisible(d);
1207 |
1208 | if (!('vis' in d)) {
1209 | if (newVis) {
1210 | onshow(d);
1211 | } else {
1212 | onhide(d);
1213 | }
1214 | } else {
1215 | if (d.vis) {
1216 | if (newVis) {
1217 | //
1218 | } else {
1219 | // console.log('hiding:');
1220 | // console.log(d);
1221 | onhide(d);
1222 | }
1223 | } else {
1224 | if (newVis) {
1225 | // console.log('show:');
1226 | // console.log(d);
1227 | onshow(d);
1228 | } else {
1229 | // did not show before and not showing now, pass
1230 | }
1231 | }
1232 | }
1233 | d.vis = newVis;
1234 | }
1235 |
1236 | function redrawNode (e) {
1237 | if (!e.hasOwnProperty('deleted')) {
1238 | e.deleted = false;
1239 | }
1240 | if (e.deleted) {
1241 | e.dom.style.display = 'none';
1242 | } else {
1243 | e.dom.style.display = '';
1244 | calcVisible(e
1245 | , function () { // onhide
1246 | e.dom.style.opacity = 0;
1247 | e.dom.style.display = 'none';
1248 | }, function () { // onshow
1249 | let oldTD = 0;
1250 | if ('node' in e) {
1251 | oldTD = e.dom.style.transitionDuration.slice(0, -1) * 1;
1252 | e.dom.style.display = 'none';
1253 | }
1254 | updateNode(e);
1255 | setTimeout(function () {
1256 | e.dom.style.display = '';
1257 | e.dom.style.opacity = 1;
1258 | }, 1 + 1000 * oldTD);
1259 | }
1260 | );
1261 | }
1262 | }
1263 |
1264 | // function redrawAllNodes(){
1265 | // if(redrawAllNodes.running){
1266 | // redrawAllNodes.waiting = true;
1267 | // }else{
1268 | // redrawAllNodes.running = true;
1269 | // _NODES.forEach(redrawNode);
1270 |
1271 | // if(redrawAllNodes.waiting){
1272 | // redrawAllNodes.waiting = false;
1273 | // setTimeout(redrawAllNodes,5);
1274 | // }else{
1275 | // redrawAllNodes.running=false;
1276 | // }
1277 | // }
1278 | // // clearTimeout(redrawAllNodes.timeout);
1279 | // // redrawAllNodes.timeout = setTimeout(function(){
1280 | // // }, 1);
1281 | // }
1282 | // redrawAllNodes.running=false;
1283 | // redrawAllNodes.waiting=false;
1284 |
1285 | function redraw () {
1286 | console.log('redraw');
1287 | _NODES.forEach((e) => {
1288 | if (('vis' in e) && (e.vis)) {
1289 | // console.log('vis => update');
1290 | // console.log(e);
1291 | updateNode(e);
1292 | }
1293 | });
1294 |
1295 | // setTimeout(function(){redrawAllNodes();},10);
1296 | setTimeout(function () {
1297 | _NODES.forEach(redrawNode);
1298 | }, 5);
1299 |
1300 | // _NODES.forEach(redrawNode);
1301 |
1302 | if (_ContentEditing.textarea) {
1303 | console.log('redraw : contextEditTextarea');
1304 | console.log('initWidth:');
1305 | console.log(_ContentEditing.textarea.dataset.initWidth);
1306 | console.log('initS: ' + _ContentEditing.textarea.dataset.initS);
1307 | console.log('_ContentEditing.textarea.style.width = ' + _ContentEditing.textarea.style.width);
1308 | _ContentEditing.textarea.style.width = 1 * _ContentEditing.textarea.dataset.initWidth * _View.state.S / (1 * _ContentEditing.textarea.dataset.initS).toPx();
1309 | console.log('_ContentEditing.textarea.style.width = ' + _ContentEditing.textarea.style.width);
1310 | _ContentEditing.textarea.style.height = _ContentEditing.textarea.dataset.initHeight * _View.state.S / _ContentEditing.textarea.dataset.initS.toPx();
1311 | }
1312 |
1313 | if (_selected_DOM.length > 0) {
1314 | _selected_DOM.forEach(dom => {
1315 | let wrapper = dom.getElementsByClassName('ui-wrapper');
1316 |
1317 | if (wrapper.length > 0) {
1318 | wrapper = wrapper[0];
1319 | const img = wrapper.getElementsByTagName('img')[0];
1320 |
1321 | wrapper.style.width = (wrapper.style.width.pxToFloat() * _View.state.S / img.dataset.origS).toPx();
1322 | wrapper.style.height = (wrapper.style.height.pxToFloat() * _View.state.S / img.dataset.origS).toPx();
1323 | img.dataset.origS = _View.state.S;
1324 | }
1325 | });
1326 | }
1327 | }
1328 |
1329 | function getHTML (node) {
1330 | if((node.text.slice(-4)=='svg>')
1331 | && (node.text.slice(0,4)=='