[Haik]: and so it begins
@@ -0,0 +1,5 @@
|
||||
*.egg-info/
|
||||
__pycache__
|
||||
.venv/
|
||||
debug_toggles.json
|
||||
needs_resync.txt
|
||||
@@ -0,0 +1,9 @@
|
||||
# Installation
|
||||
|
||||
1. Navigate to this directory:
|
||||
|
||||
|
||||
2. Install the package in development mode:
|
||||
```bash
|
||||
pip install -e .
|
||||
```
|
||||
@@ -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
|
||||
@@ -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__))))
|
||||
@@ -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
|
||||
)
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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()
|
||||
@@ -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
|
||||
@@ -0,0 +1 @@
|
||||
all
|
||||
@@ -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
|
||||
@@ -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*
|
||||
@@ -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)
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
|
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"
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
# https://www.robotstxt.org/robotstxt.html
|
||||
User-agent: *
|
||||
Disallow:
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
|
After Width: | Height: | Size: 7.1 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 57 KiB |
|
After Width: | Height: | Size: 86 KiB |
@@ -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️⃣',
|
||||
'🔟','🔠','🔡','🔢','🔣','🔤','🅰','🆎','🅱','🆑','🆒','🆓','ℹ','🆔','Ⓜ','🆕','🆖',
|
||||
'🅾','🆗','🅿','🆘','🆙','🆚','🈁','🈂','🈷','🈶','🈯','🉐','🈹','🈚','🈲','🉑','🈸',
|
||||
'🈴','🈳','㊗','㊙','🈺','🈵','🔴','🟠','🟡','🟢','🔵','🟣','🟤','⚫','⚪','🟥',
|
||||
'🟧','🟨','🟩','🟦','🟪','🟫','⬛','⬜','◼','◻','◾','◽','▪','▫','🔶','🔷','🔸','🔹',
|
||||
'🔺','🔻','💠','🔘','🔳','🔲','🏁','🚩','🎌','🏴','🏳','🏳️⚧️','🏴☠️',
|
||||
],
|
||||
}
|
||||
|
After Width: | Height: | Size: 7.5 KiB |
|
After Width: | Height: | Size: 19 KiB |
|
After Width: | Height: | Size: 19 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
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;
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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')
|
||||
);
|
||||
@@ -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
|
||||
@@ -0,0 +1,8 @@
|
||||
from setuptools import setup, find_packages
|
||||
|
||||
setup(
|
||||
name="debug",
|
||||
version="0.1",
|
||||
packages=find_packages(),
|
||||
py_modules=["debug"]
|
||||
)
|
||||