[Haik]: and so it begins

This commit is contained in:
haikdc
2026-03-13 12:09:25 -07:00
commit 23621306a1
190 changed files with 51641 additions and 0 deletions
+5
View File
@@ -0,0 +1,5 @@
*.egg-info/
__pycache__
.venv/
debug_toggles.json
needs_resync.txt
+9
View File
@@ -0,0 +1,9 @@
# Installation
1. Navigate to this directory:
2. Install the package in development mode:
```bash
pip install -e .
```
+57
View File
@@ -0,0 +1,57 @@
import re
import inspect
import os
from debugger_backend.log_config import log_config
from debugger_backend.Debugleton import Debugleton
from debugger_backend.color_adjuster import rgb_to_ansi, bold_and_italicize_text, hex_to_rgb
from debugger_backend.debug_arg_parser import is_text, is_error
def debug(*args, mode:str='debug', override_max_chars:bool=False):
frame = inspect.currentframe().f_back
code = frame.f_code
line_no = frame.f_lineno
calling_function_name = frame.f_code.co_name
calling_file_name = os.path.basename(code.co_filename)
if calling_function_name == "<module>":
calling_function_name = calling_file_name
# Retrieve the file path of the calling function
file_path = os.path.abspath(code.co_filename)
# print(f"FILE PATH: {file_path}")
t_color, t_is_on, t_emoji = Debugleton().find_file_info(file_path)
# print(f"DEBUGGING: {t_color}, {t_is_on}")
max_chars = 3000
with open(code.co_filename, 'r', encoding='utf-8') as f:
lines = f.readlines()
line = lines[line_no - 1]
leading_spaces = len(line) - len(line.lstrip(' '))
indent = leading_spaces // 4
arg_names = re.findall(r'debug\((.*?)\)', line)[0].split(', ')
for arg_name, arg_value in zip(arg_names, args):
indent_str = ' |\t' * indent
if indent > 0:
indent_str = indent_str[:-3] + ' |-- '
arg_is_error = is_error(arg_value, arg_name)
arg_is_text = is_text(arg_value, arg_name)
if arg_is_error:
t_color = "#FE3F3F"
t_emoji = ""
t_is_on = True
arg_len = len(str(arg_value))
if arg_len > max_chars and not override_max_chars:
if not arg_is_text: arg_value = str(arg_value)
arg_value = arg_value[:int(max_chars/2)] + "...\n..." + arg_value[arg_len-int(max_chars/2):]
function_print_str = calling_function_name if 'self' not in frame.f_locals else f'{frame.f_locals["self"].__class__.__name__}.{calling_function_name}'
# color = COLORS.get(function_print_str, white)
color = hex_to_rgb(t_color)
if arg_is_text:
print_str = f"{t_emoji}{rgb_to_ansi(color)}{indent_str}[{function_print_str}] : {bold_and_italicize_text(arg_value)}\033[0m"
else:
print_str = f"{t_emoji}{rgb_to_ansi(color)}{indent_str}[{function_print_str}] : {arg_name} = {arg_value}\033[0m"
if t_is_on: log_config.debug_custom(print_str, mode)
# Assign the function to the module's __call__ attribute
import sys
sys.modules[__name__] = debug
+7
View File
@@ -0,0 +1,7 @@
import os
TOGGLE_FILE = os.path.join(os.path.dirname(__file__), 'debug_toggles.json')
DEFAULT_COLOR = '#ffffff'
DEFAULT_TOGGLED = False
DEFAULT_SET_MANUALLY = False
DEFAULT_EMOJI = ''
ROOT_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
+39
View File
@@ -0,0 +1,39 @@
import os
from debugger_backend.File import File
from debugger_backend.DEFAULTS import DEFAULT_COLOR, DEFAULT_TOGGLED, DEFAULT_SET_MANUALLY, DEFAULT_EMOJI
class DebugFile(File):
def __init__(self, filename, path, color=DEFAULT_COLOR, is_toggled=DEFAULT_TOGGLED,
set_manually=DEFAULT_SET_MANUALLY, emoji=DEFAULT_EMOJI, directory=None):
super().__init__(filename, path)
self.color = color
self.is_toggled = is_toggled
self.set_manually = set_manually
self.emoji = emoji
self.directory = directory # Reference to parent directory
def to_dict(self):
"""
Converts the DebugFile object to a dictionary format.
"""
return {
"name": os.path.basename(self.filename),
"color": self.color,
"is_toggled": self.is_toggled,
"set_manually": self.set_manually,
"emoji": self.emoji
}
@classmethod
def from_dict(cls, file_dict, directory):
"""
Creates a DebugFile object from a dictionary loaded from JSON.
"""
filename = os.path.join(directory.path, file_dict["name"])
return cls(
filename=filename,
color=file_dict.get("color", DEFAULT_COLOR),
is_toggled=file_dict.get("is_toggled", DEFAULT_TOGGLED),
set_manually=file_dict.get("set_manually", DEFAULT_SET_MANUALLY),
directory=directory
)
+88
View File
@@ -0,0 +1,88 @@
# Haik: sorry bout the filename
import threading
from debugger_backend.project_scanner import update_debug_toggles
from debugger_backend.Directory import Directory
from debugger_backend.DebugFile import DebugFile
from debugger_backend.DEFAULTS import DEFAULT_COLOR, DEFAULT_TOGGLED, DEFAULT_EMOJI
import os
import time
NEEDS_RESYNC_FILE = os.path.join(os.path.dirname(__file__), 'needs_resync.txt')
class Debugleton:
_instance = None
_lock = threading.Lock() # Lock for thread-safe singleton creation
sync_lock: threading.Lock
def __new__(cls, *args, **kwargs):
# Double-checked locking for thread-safe singleton creation
if cls._instance is None:
with cls._lock:
if cls._instance is None:
cls._instance = super(Debugleton, cls).__new__(cls)
print("\033[38;5;120m\n---------------------------------\033[0m")
print("\033[38;5;120m|\tDEBUGLETON INIT \t|\033[0m")
cls._instance.dir = None
print("\033[38;5;120m|\tScanning Project...\t|\033[0m")
cls._instance.sync_lock = threading.Lock()
cls._instance.sync_lock.acquire(blocking=False)
cls._instance.sync_to_saved(is_first_sync=True)
cls._instance.sync_lock.release()
print("\033[38;5;120m|\t...Project Scanned\t|\033[0m")
print("\033[38;5;120m|\tDEBUGLETON INIT DONE\t|\033[0m")
print("\033[38;5;120m---------------------------------\n\033[0m")
# else: print("DEBUGLETON Already initialized INNER")
# else: print("DEBUGLETON Already initialized OUTER")
return cls._instance
def sync_to_saved(self, is_first_sync=False):
# print(f"[sync_to_saved]: START")
if not is_first_sync: self.sync_lock.acquire()
# print(f"[sync_to_saved]: Acquired sync lock")
self.dir = update_debug_toggles(save_to_file=False)
# print(f"Synced to saved dir: {self.dir}")
self.abspaths, self.instances = self.dir.get_ordered_abspaths_and_instances()
# print(f"Synced to abspaths: {self.abspaths}")
with open(NEEDS_RESYNC_FILE, 'w') as f:
f.write('0')
if not is_first_sync: self.sync_lock.release()
# print(f"[sync_to_saved]: Released sync lock")
# print(f"[sync_to_saved]: END")
def needs_resync(self):
# print(f"[needs_resync]: START")
num_tries = 0
while self.is_syncing():
print(f"Waiting for Debugleton to sync... ({num_tries})")
time.sleep(5)
num_tries += 1
if num_tries > 10:
print(f"""
NOTE: Debugleton is taking a long time, there's one scenario where it breaks:
\n\t- If running in docker, and you deleted one of the root dirs in the volumes of docker compose,
\n\t then the debugger will not be able to find the project and will get stuck in an infinite loop.
\n\t- In this case, you can restart the docker container and delete the volume in the docker compose file and it will resync.
""")
with open(NEEDS_RESYNC_FILE, 'r') as f:
does_need_resync = True if f.read().strip() == '1' else False
# if does_need_resync: print("Resyncing Debugleton...")
# print(f"[needs_resync]: END")
return does_need_resync
def is_syncing(self):
return self.sync_lock.locked()
def find_file_info(self, filepath: str):
filepath = filepath.lower()
# print(f"Finding file info for {filepath}")
if self.needs_resync():
self.sync_to_saved()
try:
filepath_id = self.abspaths.index(filepath)
match = self.instances[filepath_id]
return match.color, match.is_toggled, match.emoji
except ValueError:
print(f"Filepath not found: {filepath}")
return DEFAULT_COLOR, DEFAULT_TOGGLED, DEFAULT_EMOJI
+200
View File
@@ -0,0 +1,200 @@
import os
import json
import colorsys
from pathlib import Path
from debugger_backend.DebugFile import DebugFile
from debugger_backend.DEFAULTS import DEFAULT_COLOR, DEFAULT_TOGGLED, DEFAULT_SET_MANUALLY, DEFAULT_EMOJI
from debugger_backend.path_mngr import get_abspath, get_root_rel_path
class Directory:
def __init__(self, path, color=DEFAULT_COLOR, is_toggled=DEFAULT_TOGGLED,
set_manually=DEFAULT_SET_MANUALLY, emoji=DEFAULT_EMOJI):
self.path = path
# print(f"Directory init: {self.path}")
self.children = [] # Can contain DebugFile or other Directory objects
self.color = color
self.is_toggled = is_toggled
self.set_manually = set_manually
self.emoji = emoji
def __str__(self):
return f"Directory: {self.path}\nNum Children: {len(self.children)}\nColor: {self.color}\nToggled: {self.is_toggled}\nSet Manually: {self.set_manually}"
def get_abspath(self):
return get_abspath(self.path)
def add_child(self, child):
"""
Adds a child to the directory (either a DebugFile or another Directory).
"""
self.children.append(child)
def get_ordered_abspaths_and_instances(self):
# print("[get_ordered_abspaths]: START")
curr_file_path = os.path.abspath(__file__)
root_dir = os.path.dirname(os.path.dirname(os.path.dirname(curr_file_path)))
# print(f"[get_ordered_abspaths]: Curr path: {curr_file_path}")
# print(f"[get_ordered_abspaths]: Dir path: {root_dir}")
def construct_ordered_abspaths(dir: Directory, ordered_abspaths: list):
dir_path = dir.path
full_path = os.path.join(root_dir, dir_path)
ordered_abspaths.append({"abspath": full_path, "instance": dir})
# print(f"\t[construct_ordered_abspaths]: Full path: {full_path}")
for child in dir.children:
child_abspath = os.path.join(root_dir, child.path).lower()
if os.path.isdir(child_abspath):
construct_ordered_abspaths(child, ordered_abspaths)
elif os.path.isfile(child_abspath):
# print(f"\t[construct_ordered_abspaths]: Child is file: {child_abspath}")
ordered_abspaths.append({"abspath": child_abspath, "instance": child})
else:
print(f"\033[38;5;120mEntry is non existent: {child_abspath}\033[0m")
# print(f"\t[construct_ordered_abspaths]: Finished for dir: {full_path}")
# print(f"\t[construct_ordered_abspaths]: RETURNING FROM DIR: {full_path}")
return ordered_abspaths
ordered_abspaths_and_instances = construct_ordered_abspaths(self, [])
# print("[get_ordered_abspaths]: Finished getting ordered abspaths and instances")
# for abspath_and_instance in ordered_abspaths_and_instances:
# abspath = abspath_and_instance["abspath"]
# print(f"\t[get_ordered_abspaths]: Abspath: {abspath}")
ordered_abspaths = [abspath_and_instance["abspath"] for abspath_and_instance in ordered_abspaths_and_instances]
ordered_instances = [abspath_and_instance["instance"] for abspath_and_instance in ordered_abspaths_and_instances]
return ordered_abspaths, ordered_instances
def build_structure(self):
print("[build_structure]: START")
root_dir = self.get_abspath()
# print(f"[build_structure]: Root dir: {root_dir}")
excluded_dirs = [".venv", "debugger", "node_modules", ".git", "__pycache__"]
project_structure = []
def construct_project_structure(dir_path: str, parent_dir: Directory):
# print(f"[build_structure]: Scanning dir: {dir_path}")
with os.scandir(dir_path) as it:
for entry in it:
# print(f"[build_structure]: Entry: {entry.path}")
if any(excluded_dir in entry.path for excluded_dir in excluded_dirs):
# print(f"[build_structure]: Excluding {entry.path}")
continue
root_rel_path = get_root_rel_path(entry.path)
if entry.is_dir():
subdir = Directory(root_rel_path)
construct_project_structure(entry.path, subdir)
parent_dir.add_child(subdir)
elif entry.is_file():
debug_file = DebugFile(filename=entry.name, path=root_rel_path)
if debug_file.calls_debug_function():
parent_dir.add_child(debug_file)
else:
raise Exception(f"[build_structure]: Entry is not dir or file: {entry.path}")
construct_project_structure(root_dir, self)
# [print(f"[build_structure]: {file}") for file in project_structure]
# print(f"[build_structure]: END")
return
def to_dict(self):
"""
Converts the Directory object to a dictionary format, recursively.
"""
return {
"name": os.path.basename(self.path),
"color": self.color,
"is_toggled": self.is_toggled,
"set_manually": self.set_manually,
"emoji": self.emoji,
"children": [child.to_dict() if isinstance(child, DebugFile) else child.to_dict() for child in self.children]
}
def prune_empty(self):
# Recursively prune empty directories
# Base case) if the current directory has no children, return
# Recursive case) for each of the directories in the current directory, call prune_empty
# then remove the directory from the children of the current directory if it has no children
for child in self.children[:]:
if isinstance(child, Directory):
# Recursively prune empty subdirectories
child.prune_empty()
# If the subdirectory is empty after pruning, remove it
if len(child.children) == 0:
self.children.remove(child)
def propagate_toggled_state(self):
"""
Propagates the toggled state down the hierarchy.
"""
for child in self.children:
if isinstance(child, DebugFile) and not child.set_manually:
child.is_toggled = self.is_toggled
elif isinstance(child, Directory) and not child.set_manually:
child.is_toggled = self.is_toggled
child.propagate_toggled_state()
def propagate_color(self, parent_color=DEFAULT_COLOR):
"""
Propagates the color from parent to children.
"""
if self.color == DEFAULT_COLOR:
self.color = lighten_color(parent_color)
for child in self.children:
if isinstance(child, DebugFile) and child.color == DEFAULT_COLOR:
child.color = lighten_color(self.color)
elif isinstance(child, Directory):
child.propagate_color(self.color)
def load_from_json(self, json_data):
"""
Loads a directory structure from a JSON file into this Directory instance.
"""
for item in json_data:
if 'children' in item:
subdir = Directory(
path=os.path.join(self.path, item['name']),
color=item.get('color', DEFAULT_COLOR),
is_toggled=item.get('is_toggled', DEFAULT_TOGGLED),
set_manually=item.get('set_manually', DEFAULT_SET_MANUALLY),
emoji=item.get('emoji', DEFAULT_EMOJI)
)
subdir.load_from_json(item['children'])
self.add_child(subdir)
else:
# debug_file = DebugFile.from_dict(item, self)
debug_file = DebugFile(
filename=item['name'],
path=os.path.join(self.path, item['name']),
color=item.get('color', DEFAULT_COLOR),
is_toggled=item.get('is_toggled', DEFAULT_TOGGLED),
set_manually=item.get('set_manually', DEFAULT_SET_MANUALLY),
emoji=item.get('emoji', DEFAULT_EMOJI),
directory=self
)
self.add_child(debug_file)
def reset_colors(self):
"""
Resets the color of all DebugFile and Directory objects in this directory structure to the default color.
"""
self.color = DEFAULT_COLOR
for child in self.children:
if isinstance(child, DebugFile):
child.color = DEFAULT_COLOR
elif isinstance(child, Directory):
child.reset_colors()
def lighten_color(color, amount=0.1):
"""
Lightens the given color by the specified amount.
"""
try:
color = color.lstrip('#')
r, g, b = int(color[:2], 16), int(color[2:4], 16), int(color[4:6], 16)
h, l, s = colorsys.rgb_to_hls(r / 255.0, g / 255.0, b / 255.0)
l = min(1, l + amount)
r, g, b = colorsys.hls_to_rgb(h, l, s)
return '#{:02x}{:02x}{:02x}'.format(int(r * 255), int(g * 255), int(b * 255))
except Exception as e:
print(f"Error lightening color {color}: {e}")
return color
+29
View File
@@ -0,0 +1,29 @@
import os
from debugger_backend.path_mngr import get_abspath
class File:
def __init__(self, filename, path):
self.filename = filename
self.path = path
def get_abspath(self):
return get_abspath(self.path)
def calls_debug_function(self):
"""
Checks if the file calls the debug function.
"""
full_path = self.get_abspath()
if not full_path.endswith('.py') or full_path.endswith('.pyc'):
result = False
else:
try:
with open(full_path, 'r', encoding='utf-8') as file:
content = file.read()
result = 'debug(' in content
except (UnicodeDecodeError, FileNotFoundError) as e:
print(f"Error reading file {full_path}")
result = False
# print(f"??calls_debug_function?? {result}")
return result
@@ -0,0 +1,21 @@
import colorsys
def adjust_brightness(color, brightness_factor):
hls = colorsys.rgb_to_hls(*[x/255.0 for x in color]) # Convert RGB to HLS
hls = (hls[0], max(0, min(1, hls[1] + brightness_factor)), hls[2]) # Adjust lightness
rgb = [int(x*255.0) for x in colorsys.hls_to_rgb(*hls)] # Convert back to RGB
return rgb
def rgb_to_ansi(rgb):
return '\033[38;2;{};{};{}m'.format(*rgb)
def bold_and_italicize_text(text):
return f"\033[1m\033[3m{text}\033[0m"
def hex_to_rgb(hex_code):
# Remove the '#' symbol if it exists
hex_code = hex_code.lstrip('#')
# Convert the hex code to RGB
return tuple(int(hex_code[i:i+2], 16) for i in (0, 2, 4))
@@ -0,0 +1,21 @@
def is_fstring(arg_name):
if not isinstance(arg_name, str):
return False
# print(f"arg_name: {arg_name}")
fstring_start_values = ["f'", "f\""]
num_start_matches = sum(arg_name.startswith(start_value) for start_value in fstring_start_values)
conditions = [num_start_matches == 1]
return all(conditions)
def is_text(arg_value, arg_name):
arg_is_text = isinstance(arg_value, str) and len(arg_name) > 2 and arg_name[1:len(arg_name)-1] == arg_value and not arg_name.endswith(")")
if not arg_is_text:
arg_is_text = is_fstring(arg_name)
# print(f"is_text: {arg_is_text}")
return arg_is_text
def is_error(arg_value, arg_name):
arg_is_error = isinstance(arg_value, Exception) or "error" in str(arg_value).lower() or "error" in str(arg_name).lower()
return arg_is_error
@@ -0,0 +1,49 @@
from flask import Flask, request, jsonify, Response
from flask_cors import CORS
from debugger_backend.project_scanner import update_debug_toggles, dir_to_output_format
import json
import os
NEEDS_RESYNC_FILE = os.path.join(os.path.dirname(__file__), 'needs_resync.txt')
DEBUG_TOGGLE_FILE = os.path.join(os.path.dirname(__file__), 'debug_toggles.json')
app = Flask(__name__)
CORS(app)
@app.route('/pull_structure', methods=['GET'])
def api_get_structure():
print("GET /get_structure")
scanned_dir=update_debug_toggles(save_to_file=True)
# print("\n\nPS scanned_dir: ", scanned_dir)
output = dir_to_output_format(scanned_dir)
output = json.dumps(output, ensure_ascii=False, indent=4)
# print("output: ", output)
return Response(output, mimetype='application/json')
@app.route('/push_structure', methods=['POST'])
def api_push_structure():
print("POST /push_structure")
data = request.get_json()
data = data['projectStructure']
# print(data)
with open(DEBUG_TOGGLE_FILE, 'w', encoding='utf-8') as file:
json.dump(data, file, indent=4)
with open(NEEDS_RESYNC_FILE, 'w') as f:
f.write('1')
return jsonify({"status": "success"})
@app.route('/reset_color', methods=['POST'])
def api_reset_color():
print("POST /reset_color")
scanned_dir=update_debug_toggles(save_to_file=False)
scanned_dir.reset_colors()
# print("RS: scanned_dir: ", scanned_dir)
output = dir_to_output_format(scanned_dir)
output = json.dumps(output, ensure_ascii=False, indent=4)
# print("RS: output: ", output)
return Response(output, mimetype='application/json')
if __name__ == '__main__':
app.run(host='0.0.0.0', port=6969, debug=False)
+50
View File
@@ -0,0 +1,50 @@
import logging
from debugger_backend.log_mode import get_log_mode, set_log_mode
class LogConfig:
_instance = None
MODES = {
"all": 1,
"debug": 10,
"test": 20,
}
def __new__(cls):
if cls._instance is None:
cls._instance = super(LogConfig, cls).__new__(cls)
cls._instance._initialize_logger()
return cls._instance
def _initialize_logger(self):
for name, level in self.MODES.items():
logging.addLevelName(level, name.upper())
self.logger = logging.getLogger('custom_logger')
self.logger.propagate = False # Prevent log propagation
handler = logging.StreamHandler()
formatter = logging.Formatter('%(message)s')
handler.setFormatter(formatter)
# Remove existing handlers to prevent duplicate logging
if self.logger.hasHandlers():
self.logger.handlers.clear()
self.logger.addHandler(handler)
self.set_debug_mode(get_log_mode())
def debug_custom(self, message, mode = None, *args, **kwargs):
if mode is None:
mode = get_log_mode()
if self.logger.isEnabledFor(self.MODES[mode]):
self.logger._log(self.MODES[mode], message, args, **kwargs)
def set_debug_mode(self, mode):
current_mode = get_log_mode()
# print(f"Setting debug mode from {current_mode} -> to {mode}")
if mode not in self.MODES: raise ValueError(f"Invalid mode: {mode}")
set_log_mode(mode)
self.logger.setLevel(self.MODES[mode])
def get_debug_mode(self):
return get_log_mode()
log_config = LogConfig()
+13
View File
@@ -0,0 +1,13 @@
import os
# LOG_MODE_FILE = 'debugger/log_mode.txt'
LOG_MODE_FILE = os.path.join(os.path.dirname(__file__), 'log_mode.txt')
def set_log_mode(mode):
with open(LOG_MODE_FILE, 'w') as f:
f.write(mode)
def get_log_mode():
if os.path.exists(LOG_MODE_FILE):
with open(LOG_MODE_FILE, 'r') as f:
return f.read().strip()
return 'all' # Default to 'all' if the file doesn't exist
+1
View File
@@ -0,0 +1 @@
all
+12
View File
@@ -0,0 +1,12 @@
import os
from debugger_backend.DEFAULTS import ROOT_DIR
def get_abspath(path: str):
return os.path.join(ROOT_DIR, path)
def get_root_rel_path(path: str):
assert path.startswith(ROOT_DIR)
path = path[len(ROOT_DIR):]
while path.startswith(os.sep):
path = path[1:]
return path
@@ -0,0 +1,149 @@
import os
import json
import colorsys
from typing import Union
from debugger_backend.Directory import Directory
from debugger_backend.DEFAULTS import DEFAULT_COLOR, DEFAULT_TOGGLED, DEFAULT_SET_MANUALLY, TOGGLE_FILE, DEFAULT_EMOJI, ROOT_DIR
from debugger_backend.DebugFile import DebugFile
from collections import OrderedDict
def merge_directories(json_dir: Directory, scanned_dir: Directory):
"""
Merges two Directory instances: one loaded from JSON (json_dir) and one built from scanning (scanned_dir).
The values from json_dir take precedence where attributes overlap.
It matches based on full directory and file structure, not just file names.
"""
# print(f"Merging JSON_DIR: {json_dir.path}\n with SCAN_DIR: {scanned_dir.path}")
json_abspaths, json_instances = json_dir.get_ordered_abspaths_and_instances()
# print(f"json_abspaths: {json_abspaths}")
scanned_abspaths, scanned_instances = scanned_dir.get_ordered_abspaths_and_instances()
# print(f"scanned_abspaths: {scanned_abspaths}")
def find_matching_in_structure(scanned_child: Union[DebugFile, Directory], json_dir: Directory):
assert json_dir in json_instances, f"JSON_DIR: {json_dir.path} not in json_instances"
assert scanned_child in scanned_instances, f"SCANNED_CHILD: {scanned_child.path} not in scanned_instances"
scanned_id = scanned_instances.index(scanned_child)
scanned_abspath = scanned_abspaths[scanned_id]
json_instance = None
try:
json_id = json_abspaths.index(scanned_abspath)
json_instance = json_instances[json_id]
# print(f"Match found: {scanned_child.path} == {json_instance.path}")
except ValueError:
# print(f"SCANNED_ABSPATH: {scanned_abspath} not in JSON_ABSPATHS")
pass
return json_instance
def construct_merged_dir(json_dir: Directory, scanned_dir: Directory):
for scanned_child in scanned_dir.children:
# Use the new recursive function to find the corresponding child in the JSON directory structure
matching_json_child = find_matching_in_structure(scanned_child, json_dir)
if isinstance(scanned_child, DebugFile) and matching_json_child:
# Merge attributes from the JSON-loaded structure
scanned_child.color = matching_json_child.color
scanned_child.is_toggled = matching_json_child.is_toggled
scanned_child.set_manually = matching_json_child.set_manually
scanned_child.emoji = matching_json_child.emoji
elif isinstance(scanned_child, Directory) and matching_json_child:
# Merge directory attributes
scanned_child.color = matching_json_child.color
scanned_child.is_toggled = matching_json_child.is_toggled
scanned_child.set_manually = matching_json_child.set_manually
scanned_child.emoji = matching_json_child.emoji
# Recursively merge the subdirectories
construct_merged_dir(matching_json_child, scanned_child)
else:
scanned_child.color = DEFAULT_COLOR
scanned_child.is_toggled = DEFAULT_TOGGLED
scanned_child.set_manually = DEFAULT_SET_MANUALLY
scanned_child.emoji = DEFAULT_EMOJI
construct_merged_dir(json_dir, scanned_dir)
def update_debug_toggles(save_to_file=True) -> Directory:
# print(f"[update_debug_toggles]: START")
json_loaded_dir = None
if os.path.exists(TOGGLE_FILE):
with open(TOGGLE_FILE, 'r', encoding='utf-8') as file:
try:
json_data = json.load(file)
json_loaded_dir = Directory("")
json_loaded_dir = Directory(path="",
color=json_data[0].get('color', DEFAULT_COLOR),
is_toggled=json_data[0].get('is_toggled', DEFAULT_TOGGLED),
set_manually=json_data[0].get('set_manually', DEFAULT_SET_MANUALLY),
emoji=json_data[0].get('emoji', DEFAULT_EMOJI)
)
# print(f"Root: {json_loaded_dir}")
# print("Json Children 1:")
# [print(child.path) for child in json_loaded_dir.children]
json_loaded_dir.load_from_json(json_data[0]['children']) # Assuming the root is in json_data[0]
# print("Json Children 2:")
# [print(child.path) for child in json_loaded_dir.children]
except json.JSONDecodeError:
ValueError("Error: JSON file could not be decoded.")
else:
print("No JSON file found")
# 1. Create a directory structure from the filesystem scan
# print("Scanning directory...")
scanned_dir = Directory(path="",
color=json_loaded_dir.color if json_loaded_dir else DEFAULT_COLOR,
is_toggled=json_loaded_dir.is_toggled if json_loaded_dir else DEFAULT_TOGGLED,
set_manually=json_loaded_dir.set_manually if json_loaded_dir else DEFAULT_SET_MANUALLY,
emoji=json_loaded_dir.emoji if json_loaded_dir else DEFAULT_EMOJI
)
# print(f"\n\nNum Children 1: {len(scanned_dir.children)}")
# [print(child.path) for child in scanned_dir.children]
scanned_dir.build_structure()
# print(f"\n\nNum Children 2: {len(scanned_dir.children)}")
# [print(child.path) for child in scanned_dir.children]
scanned_dir.prune_empty()
# print(f"\n\nNum Children 3: {len(scanned_dir.children)}")
# [print(child.path) for child in scanned_dir.children]
# print("1.1 Merged Dir First Child: ", scanned_dir.children[0])
# 4. Propagate the toggled state and color through the merged structure
scanned_dir.propagate_toggled_state()
# print(f"\n\nNum Children 4: {len(scanned_dir.children)}")
# [print(child.path) for child in scanned_dir.children]
# 3. Merge the two directory structures
if json_loaded_dir:
merge_directories(json_loaded_dir, scanned_dir)
# print(f"\n\nNum Children 5: {len(scanned_dir.children)}")
scanned_dir.propagate_color()
output = dir_to_output_format(scanned_dir)
# print(f"\n\nNum Children 6: {len(scanned_dir.children)}")
# 5. Write the updated structure back to the JSON file
if save_to_file:
with open(TOGGLE_FILE, 'w', encoding='utf-8') as file:
json.dump(output, file, ensure_ascii=False, indent=4)
# print(f"[update_debug_toggles]: END")
return scanned_dir
def dir_to_output_format(input_dir):
root_node = {
"name": "root",
"color": input_dir.color, # Use input_dir's color
"is_toggled": input_dir.is_toggled, # Use input_dir's toggled state
"set_manually": input_dir.set_manually, # Use input_dir's set_manually
"emoji": input_dir.emoji, # Use input_dir's emoji
"children": input_dir.to_dict()["children"]
}
return [ordered(root_node)]
def ordered(obj):
if isinstance(obj, dict):
return OrderedDict((k, ordered(v)) for k, v in obj.items())
if isinstance(obj, list):
return [ordered(x) for x in obj]
return obj
@@ -0,0 +1,3 @@
Flask==2.0.1
Flask-Cors==4.0.1
Werkzeug==2.0.3
+23
View File
@@ -0,0 +1,23 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# production
/build
# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*
+70
View File
@@ -0,0 +1,70 @@
# Getting Started with Create React App
This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
## Available Scripts
In the project directory, you can run:
### `npm start`
Runs the app in the development mode.\
Open [http://localhost:3000](http://localhost:3000) to view it in your browser.
The page will reload when you make changes.\
You may also see any lint errors in the console.
### `npm test`
Launches the test runner in the interactive watch mode.\
See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
### `npm run build`
Builds the app for production to the `build` folder.\
It correctly bundles React in production mode and optimizes the build for the best performance.
The build is minified and the filenames include the hashes.\
Your app is ready to be deployed!
See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
### `npm run eject`
**Note: this is a one-way operation. Once you `eject`, you can't go back!**
If you aren't satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you're on your own.
You don't have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn't feel obligated to use this feature. However we understand that this tool wouldn't be useful if you couldn't customize it when you are ready for it.
## Learn More
You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
To learn React, check out the [React documentation](https://reactjs.org/).
### Code Splitting
This section has moved here: [https://facebook.github.io/create-react-app/docs/code-splitting](https://facebook.github.io/create-react-app/docs/code-splitting)
### Analyzing the Bundle Size
This section has moved here: [https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size](https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size)
### Making a Progressive Web App
This section has moved here: [https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app](https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app)
### Advanced Configuration
This section has moved here: [https://facebook.github.io/create-react-app/docs/advanced-configuration](https://facebook.github.io/create-react-app/docs/advanced-configuration)
### Deployment
This section has moved here: [https://facebook.github.io/create-react-app/docs/deployment](https://facebook.github.io/create-react-app/docs/deployment)
### `npm run build` fails to minify
This section has moved here: [https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify](https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify)
File diff suppressed because it is too large Load Diff
+47
View File
@@ -0,0 +1,47 @@
{
"name": "debugger-gui",
"version": "0.1.0",
"private": true,
"dependencies": {
"@emotion/react": "^11.11.4",
"@emotion/styled": "^11.11.5",
"@mui/icons-material": "^5.16.0",
"@mui/material": "^5.16.0",
"@testing-library/jest-dom": "^5.17.0",
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0",
"axios": "^1.7.2",
"emoji-mart": "^5.6.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-scripts": "5.0.1",
"web-vitals": "^2.1.4"
},
"scripts": {
"start": "cross-env PORT=6970 react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
]
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
},
"devDependencies": {
"cross-env": "^7.0.3"
}
}
+43
View File
@@ -0,0 +1,43 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/logo.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Web site created using create-react-app"
/>
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo.png" />
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>CL-Debug</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>
Binary file not shown.

After

Width:  |  Height:  |  Size: 171 KiB

@@ -0,0 +1,25 @@
{
"short_name": "React App",
"name": "Create React App Sample",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
},
{
"src": "logo192.png",
"type": "image/png",
"sizes": "192x192"
},
{
"src": "logo512.png",
"type": "image/png",
"sizes": "512x512"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}
+3
View File
@@ -0,0 +1,3 @@
# https://www.robotstxt.org/robotstxt.html
User-agent: *
Disallow:
+29
View File
@@ -0,0 +1,29 @@
.app-container {
display: flex;
flex-direction: column;
width: 90vw;
height: 100%;
padding-left: 5vw;
padding-right: 5vw;
padding-bottom: 5vh;
align-items: center;
}
.app-header {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 100%;
height: 10vh;
font-family: 'Consolas', monospace;
font-size: 3.45rem;
font-weight: bold; /* Changed from 900 to bold */
color: #d8d8d8;
text-align: center;
margin-bottom: 2vh;
margin-top: 2vh;
}
+223
View File
@@ -0,0 +1,223 @@
import React, { useState } from 'react';
import { Container } from '@mui/material';
// import PullButton from './components/pull-button/PullButton'; // Import the FetchButton component
import SyncSection from './components/sync-section/SyncSection'; // Import the FetchButton component
import Tree from './components/tree/Tree'; // Import the Tree component
import './App.css';
const App = () => {
const [projectStructure, setProjectStructure] = useState(null);
const [expanded, setExpanded] = useState({});
const handleExpandClick = (id) => {
setExpanded((prevExpanded) => ({ ...prevExpanded, [id]: !prevExpanded[id] }));
};
const handleCheckboxChange = (nodeId, checked) => {
console.log("Handle checkbox change on node: ", nodeId, " with checked: ", checked); // Log the checkbox change
const updateNode = (nodes, pathParts, checked, forceCheck = false) => {
return nodes.map((node) => {
if (node.name === pathParts[0]) {
// Check if the node should be force-checked due to a child being checked
const shouldCheck = checked || forceCheck;
if (pathParts.length === 1) {
console.log("Node found: ", node.name, " With current checked: ", node.is_toggled);
return { ...node, is_toggled: shouldCheck, children: updateChildren(node.children, shouldCheck) };
}
// Recursively update children
if (node.children) {
const updatedChildren = updateNode(node.children, pathParts.slice(1), checked, shouldCheck);
return { ...node, is_toggled: shouldCheck, children: updatedChildren };
}
return { ...node, is_toggled: shouldCheck };
}
return node;
});
};
// Function to update the state of child nodes
const updateChildren = (children, checked) => {
if (!children) return [];
return children.map((child) => ({
...child,
is_toggled: checked,
children: updateChildren(child.children, checked)
}));
};
setProjectStructure((prevStructure) => {
if (!prevStructure) return prevStructure;
const pathParts = nodeId.split('/');
const updatedStructure = updateNode(prevStructure, pathParts, checked);
return updatedStructure;
});
};
const handleEmojiChange = (nodeId, emoji) => {
console.log("Handle emoji change on node: ", nodeId, " with emoji: ", emoji);
// Function to recursively propagate the emoji to all children but not update the ancestors
const propagateEmojiToChildren = (node) => {
if (!node.children) return node; // If no children, return the node as is
const updatedChildren = node.children.map((child) => ({
...child,
emoji, // Set the new emoji to the child node
children: propagateEmojiToChildren(child).children, // Recursively propagate to deeper children
}));
return { ...node, children: updatedChildren }; // Update the node's children but not the node itself
};
const updateNode = (nodes, pathParts, emoji) => {
return nodes.map((node) => {
if (node.name === pathParts[0]) {
// Update the emoji of the node itself if this is the target node
let updatedNode = { ...node };
if (pathParts.length === 1) {
// This is the target node, update its emoji
updatedNode.emoji = emoji;
}
// If this node has children and we haven't reached the target node yet
if (node.children && pathParts.length > 1) {
const updatedChildren = updateNode(node.children, pathParts.slice(1), emoji);
updatedNode = { ...updatedNode, children: updatedChildren };
}
// If we've reached the target node, propagate the emoji to its children
if (pathParts.length === 1 && node.children) {
updatedNode.children = propagateEmojiToChildren(node).children;
}
return updatedNode;
}
return node; // No match, return the node as is
});
};
setProjectStructure((prevStructure) => {
if (!prevStructure) return prevStructure;
const pathParts = nodeId.split('/');
const updatedStructure = updateNode(prevStructure, pathParts, emoji);
return updatedStructure;
});
};
const defaultColor = '#ff0000'; // Define the default color
const handleColorChange = (nodeId, color) => {
console.log("Handle color change on node: ", nodeId, " with color: ", color); // Log the color change
const amount = 50; // Define the amount to lighten the color
const updateNode = (nodes, pathParts, color, isOriginalParent = false) => {
console.log("HIT updateNode with color: ", color); // Log the color
return nodes.map((node) => {
if (node.name === pathParts[0]) {
let name = node.name;
let newColor = node.color;
let is_toggled = node.is_toggled;
let set_manually = node.set_manually;
// If the node is the target node
if (pathParts.length === 1) {
console.log("Node found: ", node.name, " With current color: ", node.color);
console.log("Setting color to: ", color);
console.log("Is original parent: ", isOriginalParent, " Set manually: ", set_manually);
if (isOriginalParent) {
set_manually = true;
}
newColor = color;
}
if (node.children) {
// Update children nodes
const updatedChildren = updateNode(node.children, pathParts.slice(1), color, isOriginalParent);
// Check and propagate the color to children if needed
const propagatedChildren = updatedChildren.map((child) => {
console.log("Checking child: ", child.name);
console.log("Child set manually: ", child.set_manually);
if (!child.set_manually) {
console.log("Propagating color to child: ", child.name);
const newPath = [...pathParts.slice(1), child.name];
return updateNode([child], newPath, lightenColor(color, amount), false)[0];
}
return child;
});
console.log("RETURN 1 node: ", node.name, " with color: ", newColor);
return {
...node,
name: name,
color: newColor,
is_toggled: is_toggled,
set_manually: set_manually,
children: propagatedChildren
};
}
console.log("RETURN 2 node: ", node.name, " with color: ", newColor);
return { ...node, name: name, color: newColor, is_toggled, set_manually};
}
return node;
});
};
// Helper function to lighten a color (example implementation)
const lightenColor = (color, amount = 50) => {
if (!color || typeof color !== 'string' || !color.startsWith('#') || color.length !== 7) {
throw new Error('Invalid color format. Expected format is #RRGGBB. But got: ' + color);
}
const colorInt = parseInt(color.slice(1), 16);
const r = Math.min(255, (colorInt >> 16) + amount);
const g = Math.min(255, ((colorInt >> 8) & 0x00FF) + amount);
const b = Math.min(255, (colorInt & 0x0000FF) + amount);
const newColorInt = (r << 16) + (g << 8) + b;
// Corrected console.log statement
console.log(`Old color: ${color}, New color: #${newColorInt.toString(16).padStart(6, '0')}`); // Log the old and new color
return `#${newColorInt.toString(16).padStart(6, '0')}`;
};
setProjectStructure((prevStructure) => {
if (!prevStructure) return prevStructure;
const pathParts = nodeId.split('/');
const updatedStructure = updateNode(prevStructure, pathParts, color, true);
console.log('Updated structure:', updatedStructure);
return updatedStructure;
});
};
return (
<div className='app-container'>
<div className='app-header'>
Cluster Labs Debugger v0
</div>
{/* <PullButton setProjectStructure={setProjectStructure} /> */}
{/* <SyncSection setProjectStructure={setProjectStructure} /> */}
<SyncSection projectStructure={projectStructure} setProjectStructure={setProjectStructure} />
<Tree
projectStructure={projectStructure}
expanded={expanded}
handleExpandClick={handleExpandClick}
handleCheckboxChange={handleCheckboxChange}
handleColorChange={handleColorChange}
handleEmojiChange={handleEmojiChange}
/>
</div>
);
};
export default App;
Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

+128
View File
@@ -0,0 +1,128 @@
// Description: List of emojis for the debugger
export const emojiList = {
"R": [
'👽','👾','🤖','🦠','🚀','🛸',
],
"S": [
'😀','😃','😄','😁','😆','😅','🤣','😂','🙂','🙃','😉','😊',
'😇','🥰','😍','🤩','😘','😗','☺','😚','😙','😋','😛','😜',
'🤪','😝','🤑','🤗','🤭','🤫','🤔','🤐','🤨','😐',
'😑','😶','😏','😒','🙄','😬','🤥',
'😌','😔','😪','🤤','😴','😷','🤒','🤕','🤢','🤮','🤧',
'🥵','🥶','🥴','😵','🤯','🤠','🥳','😎','🤓','🧐',
'😕','😟','🙁','☹','😮','😯','😲','😳','🥺','😦','😧',
'😨','😰','😥','😢','😭','😱','😖','😣','😞','😓','😩','😫',
'🥱','😤','😡','😠','🤬','😈','👿','💀','☠','💩','🤡','👹',
'👺','👻','👋','🤚','🖐','✋','🖖','👌','🤏','✌','🤞','🤟','🤘','🤙','👈','👉',
'👆','🖕','👇','☝','👍','👎','✊','👊','🤛','🤜','👏','🙌','👐','🤲','🤝','🙏','✍','💅','🤳',
'💪','🦾','🦿','🦵','🦶','👂','🦻','👃','🧠','🦷','🦴','👀','👁',
'👅','👄','👶','🧒','👦','👧','🧑','👱','👨','🧔','👨‍🦰',
'👨‍🦱','👨‍🦳','👨‍🦲','👩','👩‍🦰','👩‍🦱','👩‍🦳','👩‍🦲','👱‍♀️',
'👱‍♂️','🧓','👴','👵','🙍','🙍‍♂️','🙍‍♀️','🙎','🙎‍♂️','🙎‍♀️','🙅','🙅‍♂️','🙅‍♀️','🙆','🙆‍♂️',
'🙆‍♀️','💁','💁‍♂️','💁‍♀️','🙋','🙋‍♂️','🙋‍♀️','🧏','🧏‍♂️','🧏‍♀️','🙇','🙇‍♂️','🙇‍♀️','🤦','🤦‍♂️',
'🤦‍♀️','🤷','🤷‍♂️','🤷‍♀️','👨‍⚕️','👩‍⚕️','👨‍🎓','👩‍🎓','👨‍🏫','👩‍🏫',
'👨‍⚖️','👩‍⚖️','👨‍🌾','👩‍🌾','👨‍🍳','👩‍🍳','👨‍🔧','👩‍🔧','👨‍🏭','👩‍🏭',
'👨‍💼','👩‍💼',,'👨‍🔬','👩‍🔬','👨‍💻','👩‍💻','👨‍🎤','👩‍🎤','👨‍🎨',
'👩‍🎨','👨‍✈️','👩‍✈️','👨‍🚀','👩‍🚀','👨‍🚒','👩‍🚒','👮','👮‍♂️','👮‍♀️','🕵','🕵️‍♂️',
'🕵️‍♀️','💂','💂‍♂️','💂‍♀️','👷','👷‍♂️','👷‍♀️','🤴','👸','👳','👳‍♂️','👳‍♀️','👲','🧕','🤵',
'👰','🤰','🤱','👼','🎅',
'🤶','🦸','🦸‍♂️','🦸‍♀️','🦹','🦹‍♂️','🦹‍♀️','🧙','🧙‍♂️','🧙‍♀️','🧚','🧚‍♂️','🧚‍♀️','🧛',
'🧛‍♂️','🧛‍♀️','🧜','🧜‍♂️','🧜‍♀️','🧝','🧝‍♂️','🧝‍♀️','🧞','🧞‍♂️','🧞‍♀️','🧟','🧟‍♂️','🧟‍♀️','💆',
'💆‍♂️','💆‍♀️','💇','💇‍♂️','💇‍♀️','🚶','🚶‍♂️','🚶‍♀️','🧍','🧍‍♂️','🧍‍♀️','🧎',
'🧎‍♂️','🧎‍♀️','👨‍🦯','👩‍🦯',
'👨‍🦼','👩‍🦼','👨‍🦽','👩‍🦽','🏃',
'🏃‍♂️','🏃‍♀️','💃','🕺','🕴','👯','👯‍♂️','👯‍♀️','🧖','🧖‍♂️','🧖‍♀️','🧗',
'🧗‍♂️','🧗‍♀️','🤺','🏇','⛷','🏂','🏌','🏌️‍♂️','🏌️‍♀️','🏄','🏄‍♂️','🏄‍♀️','🚣','🚣‍♂️','🚣‍♀️','🏊',
'🏊‍♂️','🏊‍♀️','⛹','⛹️‍♂️','⛹️‍♀️','🏋','🏋️‍♂️','🏋️‍♀️','🚴','🚴‍♂️','🚴‍♀️','🚵','🚵‍♂️','🚵‍♀️','🤸','🤸‍♂️',
'🤸‍♀️','🤼','🤼‍♂️','🤼‍♀️','🤽','🤽‍♂️','🤽‍♀️','🤾','🤾‍♂️','🤾‍♀️','🤹','🤹‍♂️','🤹‍♀️','🧘','🧘‍♂️','🧘‍♀️',
'🛀','🛌','🗣','👤','👥','👪','👣','🦰','🦱','🦳','🦲',
],
"A": [
'🐕','🦮','🐕‍🦺','🐩','🐺','🦊','🦝','🐱','😺','😸','😹','😻','😼','😽','🙀','😿','😾','🙈','🙉','🙊','🐵','🐒','🦍','🦧',
'🐈','🦁','🐯','🐅','🐆','🐴','🐎','🦄','🦓','🦌',
'🐮','🐂','🐃','🐄','🐷','🐖','🐗','🐽','🐏','🐑','🐐','🐪','🐫','🦙',
'🦒','🐘','🦏','🦛','🐭','🐁','🐀','🐹','🐰','🐇','🐿','🦔','🦇',
'🐻','🐨','🐼','🦥','🦦','🦨','🦘','🦡','🐾','🦃','🐔','🐓','🐣',
'🐤','🐥','🐦','🐧','🕊','🦅','🦆','🦢','🦉','🦩','🦚','🦜',
'🐸','🐊','🐢','🦎','🐍','🐲','🐉','🦕','🦖','🐳','🐋','🐬','🐟',
'🐠','🐡','🦈','🐙','🐚','🐌','🦋','🐛','🐜','🐝','🐞','🦗','🕷',
'🕸','🦂','🦟','🍇','🍈',
'🛎','💐','🌸','💮','🏵','🌹','🥀','🌺','🌻','🌼','🌷',
'🌱','🌲','🌳','🌴','🌵','🌾','🌿','☘','🍀','🍁','🍂','🍃','🍄','🎃','🎄','🎋','🎍',
],
"F": [
'🍉','🍊','🍋','🍌','🍍','🥭','🍎','🍏','🍐','🍑','🍒','🍓','🥝','🍅',
'🥥','🥑','🍆','🥔','🥕','🌽','🌶','🥒','🥬','🥦','🧄','🧅','🥜','🌰',
'🍞','🥐','🥖','🥨','🥯','🥞','🧇','🧀','🍖','🍗','🥩','🥓','🍔','🍟','🍕',
'🌭','🥪','🌮','🌯','🥙','🧆','🥚','🍳','🥘','🍲','🥣','🥗','🍿','🧈','🧂','🥫',
'🍱','🍘','🍙','🍚','🍛','🍜','🍝','🍠','🍢','🍣','🍤','🍥','🥮','🍡','🥟','🥠','🥡',
'🦀','🦞','🦐','🦑','🦪','🍦','🍧','🍨','🍩','🍪','🎂','🍰','🧁','🥧','🍫','🍬','🍭','🍮',
'🍯','🍼','🥛','☕','🍵','🍶','🍾','🍷','🍸','🍹','🍺','🍻','🥂','🥃','🥤','🧃',
'🧉','🧊','🥢','🍽','🍴','🥄','🔪',
],
"G": [
'⚽','⚾','🥎','🏀','🏐','🏈','🏉','🎾','🥏',
'🎳','🏏','🏑','🏒','🥍','🏓','🏸','🥊','🥋','🥅','⛳','⛸','🎣','🤿','🎽','🎿','🛷','🥌',
'♟','🃏','🀄','🎴','🎭','🖼','🎨','🧵','🎖','🏆','🏅','🥇','🥈','🥉',
'🎯','🪀','🪁','🔫','🎱','🔮','🎮','🕹','🎰','🎲','🧩','🧸','♠','♥','♦','♣',
],
"W": [
'🌍','🌎','🌏','🌐','🗺','🗾','🧭','🏔','⛰','🌋',
'🗻','🏕','🏖','🏜','🏝','🏞','🏟','🏛','🏗','🧱','🏘','🏚','🏠','🏡','🏢','🏣','🏤',
'🏥','🏦','🏨','🏩','🏪','🏫','🏬','🏭','🏯','🏰','💒','🗼','🗽','⛪','🕌','🛕','🕍','⛩',
'🕋','⛲','⛺','🌁','🌃','🏙','🌄','🌅','🌆','🌇','🌉','♨','🎠','🎡','🎢','💈','🎪','🚂',
'🚃','🚄','🚅','🚆','🚇','🚈','🚉','🚊','🚝','🚞','🚋','🚌','🚍','🚎','🚐','🚑','🚒','🚓',
'🚔','🚕','🚖','🚗','🚘','🚙','🚚','🚛','🚜','🏎','🏍','🛵','🦽','🦼','🛺','🚲','🛴','🛹',
'🚏','🛣','🛤','🛢','⛽','🚨','🚥','🚦','🛑','🚧','⚓','⛵','🛶','🚤','🛳','⛴','🛥',
'🚢','✈','🛩','🛫','🛬','🪂','💺','🚁','🚟','🚠','🚡','🛰','🧳','🎎','🎏','🎐','🎑','🧧','🎀','🎁',
'🎗','🎟','🎫','🏺',
],
"T": [
'⌛','⏳','⌚',
'⏰','⏱','⏲','🕰','🕛','🕧','🕐','🕜','🕑','🕝','🕒','🕞','🕓','🕟','🕔','🕠','🕕','🕡',
'🕖','🕢','🕗','🕣','🕘','🕤','🕙','🕥','🕚','🕦','🌑','🌒','🌓','🌔','🌕','🌖','🌗','🌘',
'🌙','🌚','🌛','🌜','🌡','☀','🌝','🌞','🪐','⭐','🌟','🌠','🌌','☁','⛅','⛈','🌤','🌥','🌦',
'🌧','🌨','🌩','🌪','🌫','🌬','🌀','🌈','🌂','☂','☔','⛱','⚡','❄','☃','⛄','☄','🔥','💧','🌊',
'🎆','🎇','🧨','✨','🎈','🎉','🎊',
],
"X": [
'💻','🖥','🖨','⌨','🖱','🖲','💽','💾','💿','📀','🧮','🎥','🎞','📽',
'🎬','📺','📷','📸','📹','📼','🔍','🔎','🕯','💡','🔦',
'🎙','🎚','🎛','🎤','🎧','📻','🎷','🎸',
'🎹','🎺','🎻','🪕','🥁','📱','📲','☎','📞','📟','📠','🔋',
'🔌','🏮','🪔','📔','📕','💌',
'📖','📗','📘','📙','📚','📓','📒','📃','📜','📄','📰','🗞','📑','🔖','🏷',
'💰','💴','💵','💶','💷','💸','💳','🧾','💹','✉','📧','📨','📩','📤',
'📥','📦','📫','📪','📬','📭','📮','🗳','✏','✒','🖋','🖊','🖌','🖍','📝',
'💼','📁','📂','🗂','📅','📆','🗒','🗓','📇','📈','📉','📊','📋','📌','📍',
'📎','🖇','📏','📐','✂','🗃','🗄','🗑','🔒','🔓','🔏','🔐','🔑','🗝','🔨','🪓',
'⛏','⚒','🛠','🗡','⚔','💣','🏹','🛡','🔧','🔩','⚙','🗜','⚖','🦯',
'🔗','⛓','🧰','🧲','⚗','🧪','🧫','🧬','🔬','🔭','📡','💉','🩸',
'💊','🩹','🩺','🚪','🛏','🛋','🪑','🚽','🚿','🛁','🪒',
'🧴','🧷','🧹','🧺','🧻','🧼','🧽','🧯','🛒','🚬','⚰','⚱','🧿',
],
"O": [
'💘','💝','💖','💗','💓','💞','💕','💟','❣','💔',
'❤','🧡','💛','💚','💙','💜','🤎','🖤','🤍',
'💋','💯','💢','💥','💫','💦','💨','🕳','💬','👁️‍🗨️','🗨',
'🗯','💭','💤','🧶','👓','🕶','🥽','🥼','🦺','👔','👕','👖','🧣','🧤','🧥',
'🧦','👗','👘','🥻','🩱','🩲','🩳','👙','👚','👛','👜','👝','🛍',
'🎒','👞','👟','🥾','🥿','👠','👡','🩰','👢','👑','👒','🎩',
'🎓','🧢','⛑','📿','💄','💍','💎','🔇','🔈','🔉','🔊','📢','📣',
'📯','🔔','🔕','🎼','🎵','🎶',
'🗿','🏧','🚮','🚰','♿','🚹','🚺','🚻','🚼','🚾','🛂','🛃','🛄',
'🛅','⚠','🚸','⛔','🚫','🚳','🚭','🚯','🚱','🚷','📵','🔞','☢','☣','⬆','↗',
'➡','↘','⬇','↙','⬅','↖','↕','↔','↩','↪','⤴','⤵','🔃','🔄','🔙','🔚','🔛','🔜',
'🔝','🛐','⚛','🕉','✡','☸','☯','✝','☦','☪','☮','🕎','🔯','♈','♉','♊',
'♋','♌','♍','♎','♏','♐','♑','♒','♓','⛎','🔀','🔁','🔂','▶','⏩','⏭',
'⏯','◀','⏪','⏮','🔼','⏫','🔽','⏬','⏸','⏹','⏺','⏏','🎦','🔅','🔆','📶',
'📳','📴','♀','♂','⚧','✖','','','➗','♾','‼','⁉','❓','❔','❕','❗','〰',
'💱','💲','⚕','♻','⚜','🔱','📛','🔰','⭕','✅','☑','✔','❌','❎','➰','➿','〽','✳',
'✴','❇','©','®','™','#️⃣','*️⃣','0️⃣','1️⃣','2️⃣','3️⃣','4️⃣','5️⃣','6️⃣','7️⃣','8️⃣','9️⃣',
'🔟','🔠','🔡','🔢','🔣','🔤','🅰','🆎','🅱','🆑','🆒','🆓','','🆔','Ⓜ','🆕','🆖',
'🅾','🆗','🅿','🆘','🆙','🆚','🈁','🈂','🈷','🈶','🈯','🉐','🈹','🈚','🈲','🉑','🈸',
'🈴','🈳','㊗','㊙','🈺','🈵','🔴','🟠','🟡','🟢','🔵','🟣','🟤','⚫','⚪','🟥',
'🟧','🟨','🟩','🟦','🟪','🟫','⬛','⬜','◼','◻','◾','◽','▪','▫','🔶','🔷','🔸','🔹',
'🔺','🔻','💠','🔘','🔳','🔲','🏁','🚩','🎌','🏴','🏳','🏳️‍⚧️','🏴‍☠️',
],
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

@@ -0,0 +1,57 @@
.color-button-container {
display: flex;
flex-direction: row;
align-items: center;
justify-content: flex-start;
padding: 0;
margin: 0;
}
.color-button-img {
height: 100px;
}
.color-button-content {
display: flex;
flex-direction: column;
justify-content: center;
}
.color-button {
background-color: rgba(255, 255, 255, 0);
color: black;
border: none;
font-size: 1.45rem;
font-weight: bold; /* Changed from 900 to bold */
font-family: 'Consolas', monospace;
cursor: pointer;
border-radius: 400px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 10px;
}
.color-button:hover {
background-color: #9292926c;
}
.color-button:active {
background-color: #929292ce;
}
.color-button:focus {
outline: none;
}
.color-button-container p {
color: #D9D9D9;
font-family: 'Consolas', monospace;
margin: 0;
text-align: center;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
@@ -0,0 +1,30 @@
import React from 'react';
import axios from 'axios';
import pushImg from '../../assets/color-reset.png'; // Adjust the path according to your project structure
import './ColorReset.css'; // Import the CSS file
const ColorReset = ({ projectStructure, setProjectStructure }) => {
const pushStructure = async () => {
try {
console.log("Resetting colors");
const response = await axios.post('http://127.0.0.1:6969/reset_color');
console.log('Project structure:', response.data);
setProjectStructure(response.data);
} catch (error) {
console.error('Error pushing project structure:', error);
}
};
return (
<div className="color-button-container">
<div className="color-button-content">
<button className="color-button" onClick={pushStructure}>
<img src={pushImg} alt="Project Structure" className="color-button-img" />
</button>
<p>Wipe colors</p>
</div>
</div>
);
};
export default ColorReset;
@@ -0,0 +1,129 @@
/* EmojiPicker.css */
.picker-wrapper {
position: relative;
display: inline-block;
}
.picker-button {
font-size: 1.5rem;
background: none;
border: none;
cursor: pointer;
font-family: "Apple Color Emoji", "Segoe UI Emoji", "Noto Color Emoji", sans-serif;
}
.emoji-popup {
position: absolute;
top: 2.5rem;
left: 0;
width: fit-content;
padding: 10px;
border: 7px solid #cccccc46;
border-radius: 35px;
background-color: #181818ef;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
z-index: 1000;
}
.folder-section {
display: flex;
flex-direction: row;
justify-content: space-around;
height: fit-content;
width: 100%;
}
.folder-item {
cursor: pointer;
padding: 10px;
text-align: left;
background-color: #00000000;
margin: 5px 0;
border-radius: 5px;
color: #ffffff;
}
.folder-item:hover {
background-color: #ffffff35;
}
.folder-item.active {
background-color: #ffffff15; /* Active color for the selected folder */
}
.folder-item.active:hover {
background-color: #ffffff35; /* Active color for the selected folder */
}
.emoji-grid {
display: grid;
grid-template-columns: repeat(6, 1fr);
gap: 10px;
height: 260px;
width: 330px;
font-family: "Apple Color Emoji", "Segoe UI Emoji", "Noto Color Emoji", sans-serif;
}
.emoji-item {
cursor: pointer;
padding: 5px;
text-align: center;
height: fit-content;
border-radius: 5px;
font-size: 1.5rem;
font-family: "Apple Color Emoji", "Segoe UI Emoji", "Noto Color Emoji", sans-serif;
}
.emoji-item:hover {
background-color: #ffffff35;
}
.pagination {
margin-top: 10px;
text-align: center;
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
gap: 5px;
}
.pagination button {
background-color: #00000000;
border: 0;
gap: 0;
width: 18px;
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
padding: 0 16px;
border-radius: 50px;
color: #ffffff;
height: fit-content;
}
.pagination button img {
height: 100px;
width: 30px;
object-fit: cover;
}
.pagination button:disabled {
cursor: default;
}
.pagination button:hover {
background-color: #ffffff15;
}
.selected-emoji-display {
margin-top: 20px;
font-family: "Apple Color Emoji", "Segoe UI Emoji", "Noto Color Emoji", sans-serif;
}
@@ -0,0 +1,141 @@
import React, { useState, useEffect } from "react";
import { emojiList } from '../../assets/emojis';
import PrevPage from '../../assets/prev-page.png';
import NextPage from '../../assets/next-page.png';
import './EmojiPicker.css'; // Import CSS file
const EMOJIS_PER_PAGE = 30;
const EmojiPicker = ({ defaultEmoji, handleEmojiChange }) => {
const folderNames = Object.keys(emojiList); // Get all folder names
const firstFolder = folderNames[0]; // Get the first folder name
const [showPicker, setShowPicker] = useState(false);
const [selectedEmoji, setSelectedEmoji] = useState(defaultEmoji || "😀"); // Initialize with the default emoji from props or fallback to smiley
const [currentPage, setCurrentPage] = useState(0);
const [currentFolder, setCurrentFolder] = useState(firstFolder); // Initialize with first folder
// When defaultEmoji changes (e.g., from backend data), update selectedEmoji
useEffect(() => {
if (defaultEmoji) {
setSelectedEmoji(defaultEmoji);
}
}, [defaultEmoji]);
// Handle folder click
const handleFolderClick = (folderName) => {
setCurrentFolder(folderName); // Set the current folder
setCurrentPage(0); // Reset page to 0 when switching folders
};
// Get the emojis for the selected folder
const emojis = currentFolder ? emojiList[currentFolder] : [];
const totalPages = Math.ceil(emojis.length / EMOJIS_PER_PAGE);
const currentEmojis = emojis.slice(
currentPage * EMOJIS_PER_PAGE,
(currentPage + 1) * EMOJIS_PER_PAGE
);
const handleEmojiClick = (emoji) => {
setSelectedEmoji(emoji);
setShowPicker(false); // Close the picker when an emoji is selected
handleEmojiChange(emoji); // Call the parent handler with the selected emoji
};
const togglePicker = () => {
setShowPicker((prev) => !prev); // Toggle the picker visibility
};
const goToNextFolder = () => {
const currentIndex = folderNames.indexOf(currentFolder);
const nextFolderIndex = currentIndex + 1;
if (nextFolderIndex < folderNames.length) {
setCurrentFolder(folderNames[nextFolderIndex]);
setCurrentPage(0); // Reset to the first page of the next folder
}
};
const goToPreviousFolder = () => {
const currentIndex = folderNames.indexOf(currentFolder);
const previousFolderIndex = currentIndex - 1;
if (previousFolderIndex >= 0) {
const previousFolder = folderNames[previousFolderIndex];
setCurrentFolder(previousFolder);
const lastPageOfPreviousFolder = Math.ceil(emojiList[previousFolder].length / EMOJIS_PER_PAGE) - 1;
setCurrentPage(lastPageOfPreviousFolder); // Set to the last page of the previous folder
}
};
const goToNextPage = () => {
if (currentPage < totalPages - 1) {
setCurrentPage(currentPage + 1);
} else {
goToNextFolder();
}
};
const goToPreviousPage = () => {
if (currentPage > 0) {
setCurrentPage(currentPage - 1);
} else {
goToPreviousFolder();
}
};
const isLastFolder = currentFolder === folderNames[folderNames.length - 1];
const isLastPageOfLastFolder = isLastFolder && currentPage === totalPages - 1;
const isFirstFolder = currentFolder === folderNames[0];
const isFirstPageOfFirstFolder = isFirstFolder && currentPage === 0;
return (
<div className="picker-wrapper">
{/* The emoji picker icon that changes when an emoji is selected */}
<button onClick={togglePicker} className="picker-button">
{selectedEmoji}
</button>
{/* Hoverable emoji picker */}
{showPicker && (
<div className="emoji-popup">
<div className="emoji-section">
<div className="pagination">
<button onClick={goToPreviousPage} disabled={isFirstPageOfFirstFolder}>
<img src={PrevPage} alt="Previous Page" />
</button>
<div className="emoji-grid">
{currentEmojis.map((emoji, index) => (
<span
key={index}
className="emoji-item"
onClick={() => handleEmojiClick(emoji)}
>
{emoji}
</span>
))}
</div>
<button
onClick={goToNextPage}
disabled={isLastPageOfLastFolder}
>
<img src={NextPage} alt="Next Page" />
</button>
</div>
</div>
<div className="folder-section">
{folderNames.map((folderName, index) => (
<div
key={index}
className={`folder-item ${folderName === currentFolder ? 'active' : ''}`}
onClick={() => handleFolderClick(folderName)}
>
{emojiList[folderName][0]}
</div>
))}
</div>
</div>
)}
</div>
);
};
export default EmojiPicker;
@@ -0,0 +1,49 @@
.pull-button-container {
display: flex;
flex-direction: row;
align-items: center;
justify-content: flex-start;
width: 40vw;
}
.pull-button-img {
margin-right: 20px;
height: 100px;
}
.pull-button-content {
display: flex;
flex-direction: column;
align-items: flex-start;
}
.pull-button {
background-color: #A3FFA9;
color: black;
border: none;
padding: 10px 20px;
font-size: 1.45rem;
font-weight: bold; /* Changed from 900 to bold */
font-family: 'Consolas', monospace;
cursor: pointer;
margin: 20px 0;
border-radius: 4px;
}
.pull-button:hover {
background-color: #84d488;
}
.pull-button:active {
background-color: #65a568;
}
.pull-button:focus {
outline: none;
}
.pull-button-container p {
color: #A3FFA9;
font-family: 'Consolas', monospace;
margin: 0;
}
@@ -0,0 +1,32 @@
import React from 'react';
import { Button } from '@mui/material';
import axios from 'axios';
import pullImg from '../../assets/pull.png'; // Adjust the path according to your project structure
import './PullButton.css';
const PullButton = ({ setProjectStructure }) => {
const pullStructure = async () => {
try {
const response = await axios.get('http://127.0.0.1:6969/pull_structure');
console.log('Project structure:', response.data);
setProjectStructure(response.data);
} catch (error) {
console.error('Error fetching project structure:', error);
}
};
return (
<div className="pull-button-container">
<img src={pullImg} alt="Project Structure" className="pull-button-img" />
<div className="pull-button-content">
<button className="pull-button" onClick={pullStructure}>
Pull
</button>
<p>Pull debugger config from backend</p>
</div>
</div>
);
};
export default PullButton;
@@ -0,0 +1,50 @@
.push-button-container {
display: flex;
flex-direction: row;
align-items: center;
justify-content: flex-start;
width: 40vw;
text-align: right;
}
.push-button-img {
margin-left: 20px;
height: 100px;
}
.push-button-content {
display: flex;
flex-direction: column;
align-items: flex-end;
}
.push-button {
background-color: #FFA3A4;
color: black;
border: none;
padding: 10px 20px;
font-size: 1.45rem;
font-weight: bold; /* Changed from 900 to bold */
font-family: 'Consolas', monospace;
cursor: pointer;
margin: 20px 0;
border-radius: 4px;
}
.push-button:hover {
background-color: #cc8182;
}
.push-button:active {
background-color: #a86768;
}
.push-button:focus {
outline: none;
}
.push-button-container p {
color: #FFA3A4;
font-family: 'Consolas', monospace;
margin: 0;
}
@@ -0,0 +1,33 @@
import React from 'react';
import axios from 'axios';
import pushImg from '../../assets/push.png'; // Adjust the path according to your project structure
import './PushButton.css'; // Import the CSS file
const PushButton = ({ projectStructure, setProjectStructure }) => {
const pushStructure = async () => {
try {
console.log("Pushing project structure to backend: ", projectStructure);
const response = await axios.post('http://127.0.0.1:6969/push_structure', {
projectStructure // Include the projectStructure in the POST request body
});
console.log('Project structure pushed:', response.data);
setProjectStructure(response.data);
} catch (error) {
console.error('Error pushing project structure:', error);
}
};
return (
<div className="push-button-container">
<div className="push-button-content">
<button className="push-button" onClick={pushStructure}>
Push
</button>
<p>Push debugger config to backend</p>
</div>
<img src={pushImg} alt="Project Structure" className="push-button-img" />
</div>
);
};
export default PushButton;
@@ -0,0 +1,16 @@
.sync-section-container {
display: flex;
flex-direction: row;
justify-content: space-between;
width: 100vw;
align-items: center;
padding: 20px;
box-sizing: border-box;
gap: 0
}
.sync-section-container > div {
flex: 1;
display: flex;
justify-content: center;
}
@@ -0,0 +1,17 @@
import React from 'react';
import PullButton from '../pull-button/PullButton';
import PushButton from '../push-button/PushButton';
import ColorReset from '../color-reset/ColorReset';
import './SyncSection.css'; // Import the CSS file
const SyncSection = ({ projectStructure, setProjectStructure }) => {
return (
<div className="sync-section-container">
<PullButton setProjectStructure={setProjectStructure} />
<ColorReset setProjectStructure={setProjectStructure} />
<PushButton projectStructure={projectStructure} setProjectStructure={setProjectStructure} />
</div>
);
};
export default SyncSection;
@@ -0,0 +1,7 @@
.tree-container {
display: flex;
flex-direction: column;
width: 100%;
height: 100%;
align-items: center;
}
@@ -0,0 +1,33 @@
import React from 'react';
import TreeNode from '../tree_node/TreeNode';
import './Tree.css';
const Tree = ({ projectStructure, expanded, handleExpandClick, handleCheckboxChange, handleColorChange, handleEmojiChange}) => {
const renderTree = (node, parentId = '') => {
const nodeId = parentId ? `${parentId}/${node.name}` : node.name;
return (
<TreeNode
key={nodeId}
node={node}
nodeId={nodeId}
expanded={expanded}
handleExpandClick={handleExpandClick}
handleCheckboxChange={handleCheckboxChange}
handleColorChange={handleColorChange}
handleEmojiChange={handleEmojiChange}
renderTree={renderTree}
/>
);
};
if (!Array.isArray(projectStructure)) return null; // Ensure projectStructure is an array
return (
<div className='tree-container'>
{projectStructure.map((node) => renderTree(node))}
</div>
);
};
export default Tree;
@@ -0,0 +1,88 @@
.tree-node {
border: 1px solid #ccc;
margin-top: 10px;
margin-bottom: 10px;
padding: 10px;
border-radius: 4px;
width: 100%;
background-color: #181818;
}
.tree-node-content {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
width: 100%;
}
.tree-node-content-main {
display: flex;
align-items: center;
width: fit-content;
}
.expand-button {
background: none;
border: none;
cursor: pointer;
margin-right: 10px;
padding: 0; /* Ensure no padding around the button */
display: flex;
align-items: center;
justify-content: center;
}
.expand-button img {
width: 16px; /* Adjust the width as needed */
height: 16px; /* Adjust the height as needed */
max-width: 100%; /* Ensure it doesn't exceed button size */
max-height: 100%; /* Ensure it doesn't exceed button size */
}
.tree-node-text {
margin-left: 10px;
font-size: 1.45rem;
font-weight: 600;
font-family: 'Consolas', monospace; /* Specify fallback font */
color: #ffffff; /* Adjust text color to ensure it's visible on the dark background */
}
.tree-node-children {
margin-left: 20px;
margin-right: 20px;
margin-bottom: 10px;
padding-left: 20px;
padding-right: 20px;
gap: 10px;
}
.tree-node-content-secondary {
display: flex;
align-items: center;
}
.color-picker-wrapper {
position: relative;
}
.color-picker-icon {
display: inline-block;
width: 24px;
height: 24px;
cursor: pointer;
border: 1px solid #000;
border-radius: 4px;
overflow: hidden;
background-size: 100% 100%; /* Ensure the background color scales to fit */
background-clip: content-box;
background-color: transparent; /* Ensure the background is transparent */
-webkit-mask: url('../../assets/color-picker.png') center no-repeat;
mask: url('../../assets/color-picker.png') center no-repeat;
-webkit-mask-size: 20px; /* Adjust this value to fit your icon */
mask-size: 20px; /* Adjust this value to fit your icon */
}
.color-picker-icon img {
display: none;
}
@@ -0,0 +1,52 @@
import './TreeNode.css';
import React from 'react';
import plusIcon from '../../assets/collapsed.png';
import minusIcon from '../../assets/expanded.png';
import colorIcon from '../../assets/color-picker.png'; // Import your custom color icon
import EmojiPicker from '../emoji-picker/EmojiPicker';
const TreeNode = ({ node, nodeId, expanded, handleExpandClick, handleCheckboxChange, handleColorChange, handleEmojiChange, renderTree }) => (
<div className="tree-node">
<div className="tree-node-content">
<div className="tree-node-content-main">
{node.children && node.children.length > 0 && (
<button className="expand-button" onClick={() => handleExpandClick(nodeId)}>
<img src={expanded[nodeId] ? minusIcon : plusIcon} alt="Expand/Collapse Icon" />
</button>
)}
<input
type="checkbox"
checked={node.is_toggled}
onChange={(e) => handleCheckboxChange(nodeId, e.target.checked)}
/>
{/* Pass node.emoji along with handleEmojiChange */}
<EmojiPicker
defaultEmoji={node.emoji}
handleEmojiChange={(emoji) => handleEmojiChange(nodeId, emoji)}
/>
<span className="tree-node-text" style={{ color: node.color || '#000000' }}>{node.name}</span>
</div>
<div className="tree-node-content-secondary">
<div className="color-picker-wrapper">
<input
type="color"
value={node.color || '#000000'}
onChange={(e) => handleColorChange(nodeId, e.target.value)}
style={{ display: 'none' }} // Hide the default color input
id={`color-picker-${nodeId}`}
/>
<label htmlFor={`color-picker-${nodeId}`} className="color-picker-icon" style={{ backgroundColor: node.color || '#000000' }}>
<img src={colorIcon} alt="Color Picker Icon" />
</label>
</div>
</div>
</div>
{node.children && expanded[nodeId] && (
<div className="tree-node-children">
{node.children.map((childNode) => renderTree(childNode, nodeId))}
</div>
)}
</div>
);
export default TreeNode;
+14
View File
@@ -0,0 +1,14 @@
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
background-color: #242121;
}
code {
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
monospace;
}
+9
View File
@@ -0,0 +1,9 @@
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import './index.css';
ReactDOM.render(
<App />,
document.getElementById('root')
);
+50
View File
@@ -0,0 +1,50 @@
#!/bin/bash
# The comment above is functional DO NOT REMOVE
DEBUGGER_ABSPATH="$(readlink -f "${BASH_SOURCE[0]}")"
DEBUGGER_DIR_ABSPATH="$(dirname "$DEBUGGER_ABSPATH")"
if [[ "$OSTYPE" == "darwin"* ]]; then
sed -i '' 's/\r//g' "$DEBUGGER_ABSPATH"
else
sed -i 's/\r//g' "$DEBUGGER_ABSPATH"
fi
chmod +x "$DEBUGGER_ABSPATH"
cleanup() {
printf "\033[36mCleaning up debugger...\033[0m\n"
wait $!
}
# Set trap to call cleanup on script exit
trap cleanup EXIT
cd "$DEBUGGER_DIR_ABSPATH/debugger_gui"
npm install
npm start & until curl -s http://localhost:6970 > /dev/null; do
sleep 1
done
cd "$DEBUGGER_DIR_ABSPATH/debugger_backend"
# colorize print to cyan
printf "\033[36mSetting up virtual environment...\033[0m\n"
python -m venv .venv
# Activate the Python virtual environment
if [[ "$OSTYPE" == "msys" || "$OSTYPE" == "win32" ]]; then
# Windows
source .venv/Scripts/activate
else
# macOS/Linux
source .venv/bin/activate
fi
# colorize print to cyan
printf "\033[36mInstalling Python dependencies...\033[0m\n"
pip install -r requirements.txt
cd "$DEBUGGER_DIR_ABSPATH"
printf "\033[36mStarting debugger server...\033[0m\n"
python -m debugger_backend.debugger_server
printf "\033[36mDeactivating Python virtual environment...\033[0m\n"
deactivate
+8
View File
@@ -0,0 +1,8 @@
from setuptools import setup, find_packages
setup(
name="debug",
version="0.1",
packages=find_packages(),
py_modules=["debug"]
)