RobinsAIWorld commited on
Commit
47cb9f7
·
verified ·
1 Parent(s): 0fe86ea

🐳 10/02 - 14:10 - Now, a new feature should be:  - I want to grab a line and drag it to another location, and automatically snap it to an allowable indentation layer

Browse files
Files changed (2) hide show
  1. script.js +284 -0
  2. style.css +73 -0
script.js CHANGED
@@ -53,6 +53,12 @@ document.addEventListener('DOMContentLoaded', function() {
53
  let editableFields = [];
54
  let currentFieldIndex = -1;
55
 
 
 
 
 
 
 
56
  // Layer colors for visual distinction
57
  const layerColors = [
58
  '#ef4444', '#f97316', '#eab308', '#22c55e',
@@ -262,6 +268,23 @@ document.addEventListener('DOMContentLoaded', function() {
262
  wrapper.dataset.parent = parentKey;
263
  wrapper.dataset.depth = depth;
264
  wrapper.dataset.layer = Math.min(depth, 7);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
265
 
266
  // Layer indicator line (LCARS style)
267
  const layerIndicator = document.createElement('div');
@@ -380,6 +403,23 @@ document.addEventListener('DOMContentLoaded', function() {
380
  wrapper.dataset.parent = parentKey;
381
  wrapper.dataset.depth = depth;
382
  wrapper.dataset.layer = Math.min(depth, 7);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
383
 
384
  // Layer indicator line
385
  const layerIndicator = document.createElement('div');
@@ -850,4 +890,248 @@ document.addEventListener('DOMContentLoaded', function() {
850
  // Re-initialize with sample data
851
  loadJSON(sampleJSON);
852
  });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
853
  });
 
53
  let editableFields = [];
54
  let currentFieldIndex = -1;
55
 
56
+ // Drag and drop state
57
+ let draggedElement = null;
58
+ let draggedData = null;
59
+ let dropTarget = null;
60
+ let dropPosition = null; // 'above', 'below', or 'inside'
61
+
62
  // Layer colors for visual distinction
63
  const layerColors = [
64
  '#ef4444', '#f97316', '#eab308', '#22c55e',
 
268
  wrapper.dataset.parent = parentKey;
269
  wrapper.dataset.depth = depth;
270
  wrapper.dataset.layer = Math.min(depth, 7);
271
+ wrapper.dataset.type = Array.isArray(data) ? 'array' : 'object';
272
+
273
+ // Make primitive items draggable (not opening/closing brackets)
274
+ if (key !== 'root' && key !== 'closing') {
275
+ wrapper.classList.add('draggable');
276
+ wrapper.draggable = true;
277
+
278
+ // Add drag handle
279
+ const dragHandle = document.createElement('div');
280
+ dragHandle.className = 'drag-handle';
281
+ dragHandle.innerHTML = '<i class="fas fa-grip-vertical"></i>';
282
+ dragHandle.style.left = `${depth * 24 + 2}px`;
283
+ wrapper.appendChild(dragHandle);
284
+
285
+ // Add drag event listeners
286
+ addDragListeners(wrapper, key, parentKey, depth);
287
+ }
288
 
289
  // Layer indicator line (LCARS style)
290
  const layerIndicator = document.createElement('div');
 
403
  wrapper.dataset.parent = parentKey;
404
  wrapper.dataset.depth = depth;
405
  wrapper.dataset.layer = Math.min(depth, 7);
406
+ wrapper.dataset.type = 'primitive';
407
+
408
+ // Make draggable
409
+ if (key !== 'root') {
410
+ wrapper.classList.add('draggable');
411
+ wrapper.draggable = true;
412
+
413
+ // Add drag handle
414
+ const dragHandle = document.createElement('div');
415
+ dragHandle.className = 'drag-handle';
416
+ dragHandle.innerHTML = '<i class="fas fa-grip-vertical"></i>';
417
+ dragHandle.style.left = `${depth * 24 + 2}px`;
418
+ wrapper.appendChild(dragHandle);
419
+
420
+ // Add drag event listeners
421
+ addDragListeners(wrapper, key, parentKey, depth);
422
+ }
423
 
424
  // Layer indicator line
425
  const layerIndicator = document.createElement('div');
 
890
  // Re-initialize with sample data
891
  loadJSON(sampleJSON);
892
  });
893
+
894
+ // ==================== DRAG AND DROP FUNCTIONS ====================
895
+
896
+ // Add drag event listeners to an element
897
+ function addDragListeners(element, key, parentKey, depth) {
898
+ element.addEventListener('dragstart', (e) => {
899
+ draggedElement = element;
900
+ draggedData = {
901
+ key: key,
902
+ parentKey: parentKey,
903
+ depth: depth,
904
+ element: element
905
+ };
906
+ element.classList.add('dragging');
907
+ e.dataTransfer.effectAllowed = 'move';
908
+ e.dataTransfer.setData('text/plain', JSON.stringify({ key, parentKey }));
909
+ });
910
+
911
+ element.addEventListener('dragend', (e) => {
912
+ element.classList.remove('dragging');
913
+ clearDropIndicators();
914
+ draggedElement = null;
915
+ draggedData = null;
916
+ dropTarget = null;
917
+ dropPosition = null;
918
+ });
919
+
920
+ element.addEventListener('dragover', (e) => {
921
+ e.preventDefault();
922
+ if (draggedElement === element) return;
923
+
924
+ const rect = element.getBoundingClientRect();
925
+ const y = e.clientY - rect.top;
926
+ const height = rect.height;
927
+
928
+ // Determine drop position based on mouse position
929
+ if (y < height * 0.25) {
930
+ dropPosition = 'above';
931
+ } else if (y > height * 0.75) {
932
+ dropPosition = 'below';
933
+ } else {
934
+ dropPosition = 'inside';
935
+ }
936
+
937
+ dropTarget = element;
938
+ updateDropIndicators();
939
+ });
940
+
941
+ element.addEventListener('dragleave', (e) => {
942
+ element.classList.remove('drag-over', 'drag-over-above', 'drag-over-below', 'drag-over-inside');
943
+ });
944
+
945
+ element.addEventListener('drop', (e) => {
946
+ e.preventDefault();
947
+ if (draggedElement === element) return;
948
+
949
+ handleDrop(draggedData, dropTarget, dropPosition);
950
+ });
951
+ }
952
+
953
+ // Update visual drop indicators
954
+ function updateDropIndicators() {
955
+ // Clear all indicators first
956
+ clearDropIndicators();
957
+
958
+ if (!dropTarget) return;
959
+
960
+ // Add appropriate indicator class
961
+ if (dropPosition === 'above') {
962
+ dropTarget.classList.add('drag-over-above');
963
+ } else if (dropPosition === 'below') {
964
+ dropTarget.classList.add('drag-over-below');
965
+ } else if (dropPosition === 'inside') {
966
+ dropTarget.classList.add('drag-over-inside');
967
+ }
968
+ }
969
+
970
+ // Clear all drop indicators
971
+ function clearDropIndicators() {
972
+ document.querySelectorAll('.drag-over, .drag-over-above, .drag-over-below, .drag-over-inside').forEach(el => {
973
+ el.classList.remove('drag-over', 'drag-over-above', 'drag-over-below', 'drag-over-inside');
974
+ });
975
+ }
976
+
977
+ // Handle the drop operation
978
+ function handleDrop(draggedData, dropTarget, dropPosition) {
979
+ const { key: draggedKey, parentKey: draggedParentKey } = draggedData;
980
+ const dropKey = dropTarget.dataset.key;
981
+ const dropParentKey = dropTarget.dataset.parent;
982
+ const dropDepth = parseInt(dropTarget.dataset.depth);
983
+ const dropType = dropTarget.dataset.type;
984
+
985
+ // Get the value being moved
986
+ let movedValue = getValue(draggedParentKey, draggedKey);
987
+
988
+ // Remove from original location
989
+ removeFromParent(draggedParentKey, draggedKey);
990
+
991
+ // Determine new parent and position
992
+ let newParentKey = dropParentKey;
993
+ let insertPosition = 'after';
994
+ let insertKey = dropKey;
995
+
996
+ if (dropPosition === 'above') {
997
+ // Insert before the drop target
998
+ insertPosition = 'before';
999
+ newParentKey = dropParentKey;
1000
+ insertKey = dropKey;
1001
+ } else if (dropPosition === 'below') {
1002
+ // Insert after the drop target
1003
+ insertPosition = 'after';
1004
+ newParentKey = dropParentKey;
1005
+ insertKey = dropKey;
1006
+ } else if (dropPosition === 'inside') {
1007
+ // Insert inside the drop target (as a child)
1008
+ newParentKey = dropKey;
1009
+ insertPosition = 'append';
1010
+ insertKey = null;
1011
+
1012
+ // For primitives, we can't insert inside - change to below
1013
+ if (dropType === 'primitive') {
1014
+ newParentKey = dropParentKey;
1015
+ insertPosition = 'after';
1016
+ insertKey = dropKey;
1017
+ }
1018
+ }
1019
+
1020
+ // Insert at new location
1021
+ insertToParent(newParentKey, movedValue, insertPosition, insertKey);
1022
+
1023
+ // Re-render and sync
1024
+ renderEditor();
1025
+ updateOutput();
1026
+ saveToHistory();
1027
+ showSavedIndicator();
1028
+ showNotification('Item moved successfully!', false);
1029
+ }
1030
+
1031
+ // Remove an element from its parent
1032
+ function removeFromParent(parentKey, key) {
1033
+ if (parentKey === 'root') {
1034
+ delete jsonData[key];
1035
+ } else {
1036
+ const parent = findElementByKey(jsonData, parentKey);
1037
+ if (parent && typeof parent === 'object') {
1038
+ if (Array.isArray(parent)) {
1039
+ const index = parseInt(key);
1040
+ parent.splice(index, 1);
1041
+ } else {
1042
+ delete parent[key];
1043
+ }
1044
+ }
1045
+ }
1046
+ }
1047
+
1048
+ // Insert a value into a parent at a specific position
1049
+ function insertToParent(parentKey, value, position = 'append', afterKey = null) {
1050
+ if (parentKey === 'root') {
1051
+ if (Array.isArray(jsonData)) {
1052
+ if (position === 'append') {
1053
+ jsonData.push(value);
1054
+ } else if (position === 'before' || position === 'after') {
1055
+ const index = Object.keys(jsonData).indexOf(afterKey);
1056
+ if (position === 'before') {
1057
+ jsonData.splice(index, 0, value);
1058
+ } else {
1059
+ jsonData.splice(index + 1, 0, value);
1060
+ }
1061
+ }
1062
+ } else {
1063
+ // Object - need a key for the new value
1064
+ let newKey = 'newField';
1065
+ let counter = 1;
1066
+ while (jsonData[newKey] !== undefined) {
1067
+ newKey = `newField${counter++}`;
1068
+ }
1069
+
1070
+ if (position === 'append') {
1071
+ jsonData[newKey] = value;
1072
+ } else if (position === 'before' || position === 'after') {
1073
+ const keys = Object.keys(jsonData);
1074
+ const index = keys.indexOf(afterKey);
1075
+ const newData = {};
1076
+
1077
+ keys.forEach((k, i) => {
1078
+ if (position === 'before' && i === index) {
1079
+ newData[newKey] = value;
1080
+ }
1081
+ newData[k] = jsonData[k];
1082
+ if (position === 'after' && i === index) {
1083
+ newData[newKey] = value;
1084
+ }
1085
+ });
1086
+
1087
+ Object.keys(jsonData).forEach(k => delete jsonData[k]);
1088
+ Object.assign(jsonData, newData);
1089
+ }
1090
+ }
1091
+ } else {
1092
+ const parent = findElementByKey(jsonData, parentKey);
1093
+ if (parent && typeof parent === 'object') {
1094
+ if (Array.isArray(parent)) {
1095
+ if (position === 'append') {
1096
+ parent.push(value);
1097
+ } else if (position === 'before' || position === 'after') {
1098
+ const index = parseInt(afterKey);
1099
+ if (position === 'before') {
1100
+ parent.splice(index, 0, value);
1101
+ } else {
1102
+ parent.splice(index + 1, 0, value);
1103
+ }
1104
+ }
1105
+ } else {
1106
+ // Object - need a key for the new value
1107
+ let newKey = 'newField';
1108
+ let counter = 1;
1109
+ while (parent[newKey] !== undefined) {
1110
+ newKey = `newField${counter++}`;
1111
+ }
1112
+
1113
+ if (position === 'append') {
1114
+ parent[newKey] = value;
1115
+ } else if (position === 'before' || position === 'after') {
1116
+ const keys = Object.keys(parent);
1117
+ const index = keys.indexOf(afterKey);
1118
+ const newData = {};
1119
+
1120
+ keys.forEach((k, i) => {
1121
+ if (position === 'before' && i === index) {
1122
+ newData[newKey] = value;
1123
+ }
1124
+ newData[k] = parent[k];
1125
+ if (position === 'after' && i === index) {
1126
+ newData[newKey] = value;
1127
+ }
1128
+ });
1129
+
1130
+ Object.keys(parent).forEach(k => delete parent[k]);
1131
+ Object.assign(parent, newData);
1132
+ }
1133
+ }
1134
+ }
1135
+ }
1136
+ }
1137
  });
style.css CHANGED
@@ -321,4 +321,77 @@ p {
321
  @keyframes pulse-green {
322
  0%, 100% { background-color: rgba(34, 197, 94, 0.2); }
323
  50% { background-color: rgba(34, 197, 94, 0.4); }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
324
  }
 
321
  @keyframes pulse-green {
322
  0%, 100% { background-color: rgba(34, 197, 94, 0.2); }
323
  50% { background-color: rgba(34, 197, 94, 0.4); }
324
+ }
325
+
326
+ /* Drag and drop styles */
327
+ .draggable {
328
+ cursor: grab;
329
+ }
330
+
331
+ .draggable:active {
332
+ cursor: grabbing;
333
+ }
334
+
335
+ .draggable.dragging {
336
+ opacity: 0.5;
337
+ background-color: rgba(255, 255, 255, 0.1);
338
+ }
339
+
340
+ .drag-over {
341
+ border-top: 2px solid var(--layer-color, #3b82f6);
342
+ background-color: rgba(59, 130, 246, 0.1);
343
+ margin-top: 2px;
344
+ margin-bottom: 2px;
345
+ }
346
+
347
+ .drag-over-above {
348
+ border-top: 3px solid var(--layer-color, #3b82f6);
349
+ }
350
+
351
+ .drag-over-below {
352
+ border-bottom: 3px solid var(--layer-color, #3b82f6);
353
+ }
354
+
355
+ .drag-over-inside {
356
+ border-left: 4px solid var(--layer-color, #3b82f6);
357
+ background-color: rgba(59, 130, 246, 0.15);
358
+ }
359
+
360
+ .drop-indicator {
361
+ position: absolute;
362
+ left: 0;
363
+ right: 0;
364
+ height: 2px;
365
+ background: linear-gradient(90deg, transparent, var(--layer-color, #3b82f6), transparent);
366
+ z-index: 100;
367
+ pointer-events: none;
368
+ opacity: 0;
369
+ transition: opacity 0.2s;
370
+ }
371
+
372
+ .drop-indicator.show {
373
+ opacity: 1;
374
+ }
375
+
376
+ /* Drag handle icon */
377
+ .drag-handle {
378
+ position: absolute;
379
+ left: 4px;
380
+ top: 50%;
381
+ transform: translateY(-50%);
382
+ color: rgba(255, 255, 255, 0.3);
383
+ cursor: grab;
384
+ padding: 4px;
385
+ opacity: 0;
386
+ transition: opacity 0.2s;
387
+ z-index: 10;
388
+ }
389
+
390
+ .json-item:hover .drag-handle {
391
+ opacity: 1;
392
+ }
393
+
394
+ .drag-handle:active {
395
+ cursor: grabbing;
396
+ color: rgba(255, 255, 255, 0.6);
397
  }