diff --git a/git.py b/git.py index fe04be8..1ce5a3f 100644 --- a/git.py +++ b/git.py @@ -2,21 +2,38 @@ from git import Repo, InvalidGitRepositoryError import os import copy import re -from custom_nodes.EternalKernelLiteGraphNodes.shared import ETK_PATH +from custom_nodes.EternalKernelLiteGraphNodes.shared import ETK_PATH, GDE_PATH NODE_CLASS_MAPPINGS = {} NODE_DISPLAY_NAME_MAPPINGS = {} def check_for_js_extension(): - # here we check to see that the js extension is installed at ../../web/extensions/etk/ui_modifications.js + # here we check to see that the js extension is installed at {GDE_PATH}/web/extensions/etk/ui_modifications.js # if it is not, we will copy it there from the ETK_PATH # check if the file exists + import shutil - check_full_path = os.path.normpath(os.path.join(ETK_PATH, "../../web/extensions/etk/ui_modifications.js")) - if not os.path.exists(check_full_path): - # copy the file - shutil.copy(os.path.join(ETK_PATH, "ui_modifications.js"), check_full_path) + for fn in ["ui_modifications.js", "ui_helpers.js"]: + check_full_path = os.path.normpath(os.path.join(ETK_PATH, f"{GDE_PATH}/web/extensions/etk/{fn}")) + # now lets check the modified date of the file + + if os.path.exists(check_full_path): + if os.path.getmtime(check_full_path) < os.path.getmtime(os.path.join(ETK_PATH, fn)): + # check_target_newer = True + should_copy_this_file = True + else: + # check_target_newer = False + should_copy_this_file = False + + else: + should_copy_this_file = True + + if should_copy_this_file: + print(f"Copying {fn} to {check_full_path}") + shutil.copy(os.path.join(ETK_PATH, fn), check_full_path) + else: + print(f"Skipping {fn} as it is already up to date") check_for_js_extension() diff --git a/server_endpoints.py b/server_endpoints.py index a2b6841..96dd0f5 100644 --- a/server_endpoints.py +++ b/server_endpoints.py @@ -5,24 +5,391 @@ from importlib import reload import logging import os from folder_paths import output_directory, input_directory, temp_directory +from custom_nodes.EternalKernelLiteGraphNodes.shared import GDE_PATH +import custom_nodes.EternalKernelLiteGraphNodes.shared as shared fake_git = None +real_git_url = "https://github.com/story-squad/GDE_Graph_IO.git" # is this actually an uri? + + +class GitIO: + def __init__(self, i_real_git_url: str, i_gde_path: str): + self.gde_path = i_gde_path + self.real_git_io_path = self.setup_git_io(i_real_git_url) + + def setup_git_io(self, l_real_git_url: str): + real_git_path = os.path.join(self.gde_path, "data") + real_git_path = os.path.join(real_git_path, "GDE_Graph_IO") + + # check to see if it exists and is a git repo, if not then clone it + if os.path.exists(real_git_path): + # check to see if it is a git repo + if os.path.exists(os.path.join(real_git_path, ".git")): + print("git io found") + else: + # not a git repo, delete it and clone it + print("git io error, not a git repo") + else: + # clone it + print("git io not found") + print("cloning git io repo") + os.system("git clone " + l_real_git_url + " " + real_git_path) + + return real_git_path + + def ensure_git_state(self, user: str): + """works in windows or linux, ensures that the git repo is in the correct state""" + if not os.path.exists(self.real_git_io_path): + raise Exception("git io path not found") + if not os.path.exists(os.path.join(self.real_git_io_path, ".git")): + raise Exception("git io error, not a git repo") + # get the current branch + current_branch = os.popen("git branch --show-current").read() + # if the current branch is not the user's branch, then switch to it + if current_branch != user: + # also check if the branch exists, if not then create it + branches = os.popen("git branch").read() + if user not in branches: + os.system("git branch " + user) + os.system("git push --set-upstream origin " + user) + # switch to the user's branch + os.system("git checkout " + user) + + # pull the latest from the user's branch + os.system("git pull") + + def git_save_version_1(self, user: str, file_name: str, file_data: str): + """ + for git save version 1 each user will have their own branch, + and the data will be saved to a file in the user's branch + version > 1 may include other strategies + """ + self.ensure_git_state(user) + # change to correct directory + os.chdir(self.real_git_io_path) + # switch to the users branch + os.system("git checkout " + user) + # save the file data as the file to the disk + with open(os.path.join(self.real_git_io_path, file_name), "w", encoding="utf-8") as f: + f.write(file_data) + # add the file to the git repo + os.system("git add " + file_name) + # commit the file to the git repo + os.system("git commit -m \"saving file " + file_name + "\"") + # push the file to the git repo + os.system("git push") + + def git_load_version_1(self, user_name, file_name): + """ + for git load version 1 each user will have their own branch, + and the data will be loaded from a file in the user's branch + version > 1 may include other strategies + """ + self.ensure_git_state(user_name) + # change to correct directory + os.chdir(self.real_git_io_path) + # switch to the users branch + os.system("git checkout " + user_name) + # pull the latest from the user's branch + os.system("git pull") + # read the file from the disk + with open(os.path.join(self.real_git_io_path, file_name), "r", encoding="utf-8") as f: + file_data = f.read() + return file_data + + +git_io = GitIO(real_git_url, GDE_PATH) + +user_data = { + "user1": {"password": "pass1", + "security": None, + "client_id": None}, + "user2": {"password": "pass2", + "security": None, + "client_id": None}, + +} + + +def get_user_by_client_id(client_id): + for user in user_data: + if user_data[user]["client_id"] == client_id: + return user + return None + + +def get_user_by_security(security): + for user in user_data: + if user_data[user]["security"] == security: + return user + return None + + +@PromptServer.instance.routes.post("/gde/logout") +async def logout(request): + """ + This function is called when the user logs out + """ + print("logout request") + + # get the user and pass from the json + data = await request.json() + client_id = data["client_id"] + + # find the user with the client id + user = get_user_by_client_id(client_id) + + if user is None: + # use json to send the data back + return web.json_response({"error": "invalid client_id"}, status=401) + + # clear the security and client id + user_data[user]["security"] = None + user_data[user]["client_id"] = None + + # use json to send the data back + return web.json_response({"success": True}) + + +@PromptServer.instance.routes.post("/gde/login") +async def login(request): + """ + This function is called when the attemnpts to log in: + - validates login + - sets up security + - user and pass are in the json + """ + print("login request") + + # get the user and pass from the json + data = await request.json() + try: + client_id = data["client_id"] + except: + # this means the client id is not set yet so return logged_in False + # use json to send the data back + return web.json_response({"logged_in": False}) + try: + user = data["username"] + password = data["password"] + except: + # this means the client is just checking to see if it is already logged into the server + # so check + user = get_user_by_client_id(client_id) + if user is None: + # use json to send the data back + print("user is none") + return web.json_response({"logged_in": False}) + else: + # use json to send the data back + print("user is not none") + return web.json_response({"logged_in": True}) + + # validate the user and pass + # for now the users and passes are right here in the code + + if user not in user_data: + # use json to send the data back + return web.json_response({"error": "invalid user"}, status=401) + + if password != user_data[user]["password"]: + # use json to send the data back + return web.json_response({"error": "invalid password"}, status=401) + + # if the user and pass are valid, then set up security + # for now the security is just a random string that will be sent back to the user + # in the future this will be a token that will be used to authenticate the user + import uuid + security = str(uuid.uuid4()) + + # there is no middleware installed + # so we will just store the security in memory + # this is not secure, but it is good enough for now + # store the security in memory + user_data[user]["security"] = security + user_data[user]["client_id"] = client_id + + # return json with the security + return web.json_response({"security": security}) + + +def hijack_prompt_server(): + """ + This function is called by the GDE when it starts up, it hijacks the prompt server + for added security. + """ + # remove the existing prompt route + q = [i for i, s in enumerate(PromptServer.instance.routes) if ("/prompt" in str(s))] + print(f"found {len(q)} routes to remove: {q}") + for adj, i in enumerate(q): + route = PromptServer.instance.routes._items.pop(i - adj) + + method = route.method + path = route.path + kwargs = route.kwargs + + if method == "GET": + print(f"removing route {method} {path} {kwargs}") + get_handler = route.handler + + @PromptServer.instance.routes.get(path) + async def get_prompt(request): + print("hijacked prompt get /prompt") + print("headers:", request.headers) + modified_func_response = await get_handler(request) + return modified_func_response + + elif method == "POST": + print(f"removing route {method} {path} {kwargs}") + post_handler = route.handler + + @PromptServer.instance.routes.post(path) + async def post_prompt(request: web.Request): + # print("hijacked prompt post /prompt") + # print("headers:", request.headers) + + json_data = await request.json() + client_id = json_data.get("client_id", None) + if client_id is None: + print("client_id is None") + return web.Response(text="client_id is None", status=400) + else: + print("client_id:", client_id) + # find in users by client_id + try: + user = get_user_by_client_id(client_id) + except KeyError: + user = None + + if user is None: + print("user not found") + return web.json_response({"error": "user not found"}, status=401) + else: + print(f"minimal security check passed for user {user}") + + modified_func_response: web.Response = await post_handler(request) + + print(modified_func_response.headers) + + return modified_func_response + + # return await post_handler(request) + # return post_handler(request) + + +@PromptServer.instance.routes.post("/gde/git/user_graphs") +async def get_user_graphs(request): + """ + Returns a list of available user graphs (dropdown options). + """ + import os + import subprocess + data = await request.json() + user = data.get("user", None) + + print("getting user graphs for user:", user) + + # Ensure that the git repo is in the correct state + + # Directory where the user graphs are stored + user_graphs_dir = git_io.real_git_io_path + + # Ensure that the git repo is in the correct state + git_io.ensure_git_state(user) + + # Check if the branch exists + branch_exists = subprocess.call(["git", "rev-parse", "--verify", "--quiet", user]) + + # If the branch does not exist, create it + if branch_exists != 0: + subprocess.call(["git", "checkout", "-b", user]) + + # Pull the latest from the user's branch + subprocess.call(["git", "pull"]) + + # Get the list of user graph files + user_graph_files = [f for f in os.listdir(user_graphs_dir) if os.path.isfile(os.path.join(user_graphs_dir, f))] + + # Convert the list of files to the desired format + options_data = [{"value": f, "label": f} for f in user_graph_files if f != ""] + + return web.json_response(options_data) + + +@PromptServer.instance.routes.post("/gde/git/users") +async def get_users(request): + """ the list of users is just the list of branches in the git repo""" + import subprocess + print("getting users") + + # Ensure that the git repo is in the correct state + git_io.ensure_git_state("main") + + # Change to correct directory + os.chdir(git_io.real_git_io_path) + + # Fetch updates from the remote + subprocess.call(["git", "fetch", "--prune"]) + + # List all remote branches + remote_branches_raw = subprocess.check_output(["git", "branch", "-r"]).decode("utf-8").split("\n") + remote_branches = [b.replace("origin/", "").strip() for b in remote_branches_raw] + + # List all local branches + local_branches_raw = subprocess.check_output(["git", "branch"]).decode("utf-8").split("\n") + local_branches = [b.replace("*", "").strip() for b in local_branches_raw] + + # Identify local branches not present in the remote branches + branches_to_delete = [b for b in local_branches if b not in remote_branches] + + # Delete these local branches + for branch in branches_to_delete: + subprocess.call(["git", "branch", "-D", branch]) + + # Now, get the updated list of local branches + branches = subprocess.check_output(["git", "branch"]).decode("utf-8").split("\n") + branches = [b.replace("*", "").strip() for b in branches] + + # Convert the list of branches to the desired format + options_data = [{"value": b, "label": b} for b in branches if b != ""] + + return web.json_response(options_data) + @PromptServer.instance.routes.post("/gde/git/save") async def save_to_git(request): + """ + for git save version 1 each user will have their own branch, + and the data will be saved to a file in the user's branch + version > 1 may include other strategies + """ + + git_save_version = 1 + req_json = await request.json() print("saving to git") global fake_git - fake_git = await request.json() - print(fake_git) + req_json = await request.json() + data = req_json.get("data", None) + user_name = req_json.get("user_name", None) + file_name = req_json.get("file_name", None) + + git_io.git_save_version_1(user_name, file_name, data) # Implement your actual Git saving logic here return web.Response(text="Saved to Git successfully.") + @PromptServer.instance.routes.post("/gde/git/load") async def load_from_git(request): + """use git_io to load the file from git""" print("loading from git") - global fake_git + req_json = await request.json() + user_name = req_json.get("user_name", None) + file_name = req_json.get("file_name", None) + # Implement your actual Git loading logic here - if fake_git is not None: - return web.json_response(fake_git) - else: - return web.Response(text="No data found.", status=404) + file_data = git_io.git_load_version_1(user_name, file_name) + return web.json_response({"data": file_data}) + + +if __name__ != 'EternalKernelLiteGraphNodes.server_endpoints': + hijack_prompt_server() diff --git a/ui_helpers.js b/ui_helpers.js new file mode 100644 index 0000000..7e72178 --- /dev/null +++ b/ui_helpers.js @@ -0,0 +1,192 @@ +import {app} from "../../../scripts/app.js"; +import {api} from "../../../scripts/api.js"; + +export function setupButtons(menu, app, api) { + // Save To Git Button + const saveButton = document.createElement("button"); + saveButton.textContent = "Save To Git"; + saveButton.onclick = async () => { + try { + const json = JSON.stringify(app.graph.serialize(), null, 2); + const userNameValue = menu.querySelector("#branch-name-field").value; + const dropDownSelected = menu.querySelector("#graph-name-field").value; + + await api.fetchApi("/gde/git/save", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + data: json, + file_name: dropDownSelected, + user_name: userNameValue + }), + }); + } catch (error) { + console.error(error); + } + }; + menu.append(saveButton); + + // Load From Git Button + const loadButton = document.createElement("button"); + loadButton.textContent = "Load From Git"; + loadButton.onclick = async () => { + try { + const response = await api.fetchApi("/gde/git/load", { + // needs user_name file_name + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + user_name: menu.querySelector("#branch-name-field").value, + file_name: menu.querySelector("#graph-name-field").value + } + ), + + }); + const data = await response.json(); + app.loadGraphData(JSON.parse(data.data)); + } catch (error) { + console.error(error); + } + }; + menu.append(loadButton); +} + + +export function setupModal() { + // Modal + const modal = document.createElement("div"); + modal.style.display = "none"; // Initially hidden + modal.style.position = "fixed"; + modal.style.top = "50%"; + modal.style.left = "50%"; + modal.style.transform = "translate(-50%, -50%)"; + modal.style.padding = "20px"; + modal.style.backgroundColor = "#fff"; + modal.style.border = "1px solid #000"; + modal.style.zIndex = "1000"; // Ensure it's on top of other elements + document.body.append(modal); + + // Input field inside the modal + const modalInput = document.createElement("input"); + modalInput.type = "text"; + modalInput.placeholder = "Enter graph name"; + modal.append(modalInput); + + // Submit button inside the modal + const submitButton = document.createElement("button"); + submitButton.textContent = "Submit"; + let targetDropdownId = ''; // This will store the ID of the dropdown we're targeting + submitButton.onclick = () => { + const dropdown = document.querySelector(`#${targetDropdownId}`); + const newOption = document.createElement("option"); + newOption.value = modalInput.value; + newOption.textContent = modalInput.value; + dropdown.append(newOption); + dropdown.value = modalInput.value; + console.log("Submitted value:", modalInput.value); + modal.style.display = "none"; // Hide the modal + }; + modal.append(submitButton); + + // Function to show the modal (this can be used outside as well) + window.showModal = function (dropdownId) { + targetDropdownId = dropdownId; // Store the passed dropdown ID + modal.style.display = "block"; + }; +} + + +export function setupDropdown(menu, api, placeholder, apiEndpoint, dropdownId, user = null, graph_name = null, onCreateNew = null) { + let dropdown = document.getElementById(dropdownId); + + // If dropdown already exists, clear its options first + if (dropdown) { + dropdown.innerHTML = ''; + } else { + // Create new dropdown if it doesn't exist + dropdown = document.createElement("select"); + dropdown.style.marginTop = "2px"; + dropdown.style.width = "100%"; + dropdown.style.borderRadius = "8px"; + dropdown.style.fontSize = "18px"; + dropdown.style.backgroundColor = "#222222"; + dropdown.style.color = "#EEEEEE"; + + dropdown.id = dropdownId; + //use comfy-list style + + menu.append(dropdown); + } + + // Add default option + const defaultOption = document.createElement("option"); + defaultOption.value = ""; + defaultOption.textContent = placeholder; + dropdown.append(defaultOption); + + // Add "Create New" option + const createNewOption = document.createElement("option"); + createNewOption.value = "create_new"; + createNewOption.textContent = "Create New"; + dropdown.append(createNewOption); + + dropdown.addEventListener("change", (event) => { + // Check if "Create New" option was selected + if (event.target.value === "create_new" && onCreateNew) { + onCreateNew(event, dropdown); + } + + // Check if branch dropdown is the one being changed + if (dropdownId === 'branch-name-field') { + while (dropdown.options.length > 2) { + dropdown.remove(2); + } + setTimeout(async () => { + await someFunction(dropdownId, api, apiEndpoint, placeholder, user, graph_name) + }) + } + }); + + setTimeout(async () => { + await someFunction(dropdownId, api, apiEndpoint, placeholder, user, graph_name) + }) +} + +export async function someFunction(dropdownId, api, apiEndpoint, placeholder, user = null, graph_name = null) { + try { + const dropdown = document.getElementById(dropdownId); + dropdown.disabled = true; // Disable the file dropdown + + const queryParams = { + user: user || "" + }; + if (graph_name) { + queryParams.graph_name = graph_name; + } + + const response = await api.fetchApi(apiEndpoint, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(queryParams) + }); + const optionsData = await response.json(); + optionsData.forEach(optionItem => { + const optionElement = document.createElement("option"); + optionElement.value = optionItem.value; + optionElement.textContent = optionItem.label; + dropdown.append(optionElement); + }); + + dropdown.disabled = false; // Enable the file dropdown + + } catch (error) { + console.error(`Error fetching options for ${placeholder}:`, error); + } +} + diff --git a/ui_modifications.js b/ui_modifications.js index 5d93eef..df0c379 100644 --- a/ui_modifications.js +++ b/ui_modifications.js @@ -1,60 +1,220 @@ -import { app } from "../../../scripts/app.js"; -import { api } from "../../../scripts/api.js"; +import {app} from "../../../scripts/app.js"; +import {api} from "../../../scripts/api.js"; +import {setupButtons, setupModal, setupDropdown} from './ui_helpers.js'; +// create a place to store some variables, including security_token +const gde = { + security_token: null, +} +gde.login_div = document.createElement("div"); +gde.login_div.id = "gde-login"; + +gde.usernameField = document.createElement("input"); +gde.usernameField.id = "username-field"; +gde.usernameField.placeholder = "Username"; +//add a logout button +gde.logoutButton = document.createElement("button"); +gde.logoutButton.id = "logout-button"; +gde.logoutButton.innerText = "Logout"; +gde.logoutButton.addEventListener("click", async () => { + //clear the token + gde.security_token = null; + //remove the logout button + gde.logoutButton.remove(); + //add the login button and fields + gde.login_div.append(gde.usernameField); + gde.login_div.append(gde.passwordField); + gde.login_div.append(gde.loginButton); + // clear the graph dropdown + const graphDropdown = document.getElementById("graph-name-field"); + if (graphDropdown) { + // remove all options without removing the first two + while (graphDropdown.options.length > 2) { + graphDropdown.remove(2); + } + } + // clear the branch dropdown + const branchDropdown = document.getElementById("branch-name-field"); + if (branchDropdown) { + // remove all options without removing the first two + while (branchDropdown.options.length > 2) { + branchDropdown.remove(2); + } + } + + // tell the server we logged out + await api.fetchApi("/gde/logout", { + method: "POST", + body: JSON.stringify({ + client_id: api.clientId, + }) + } + ); + } +); +gde.passwordField = document.createElement("input"); +gde.passwordField.id = "password-field"; +gde.passwordField.placeholder = "Password"; +gde.passwordField.type = "password"; +gde.loginButton = document.createElement("button"); +gde.loginButton.id = "login-button"; +gde.loginButton.style.color = "var(--input-text)"; +gde.loginButton.style.backgroundColor = "var(--comfy-input-bg)"; +gde.loginButton.style.borderRadius = "8px"; +gde.loginButton.style.borderColor = "var(--border-color)"; +gde.loginButton.style.borderStyle = "solid"; +gde.loginButton.style.marginTop = "2px"; +gde.loginButton.style.width = "100%"; +gde.loginButton.innerText = "Login"; +gde.loginButton.addEventListener("click", async () => { + const username = document.getElementById("username-field").value; + const password = document.getElementById("password-field").value; + const res = await api.fetchApi("/gde/login", { + method: "POST", + body: JSON.stringify({ + username: username, + password: password, + client_id: api.clientId, + } + ) + } + ) + ; + if (res.status !== 200) { + alert("Login failed"); + } else { + //read the json response, token will be under security + const json = await res.json(); + + //set the token it is somewhere in the json under security + gde.security_token = json.security; + + //now that we are logged in remove the login button and fields + gde.usernameField.remove(); + gde.passwordField.remove(); + gde.loginButton.remove(); + + + gde.login_div.append(gde.logoutButton); + + + } + } +); + +gde.get_server_state = async (client_id) => { + // check with the server to see if we have a valid client_id + const response = await api.fetchApi("/gde/login", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + client_id: client_id, + }), + }); + const data = await response.json(); + return data; +} + +// create a dummy function +gde.setup_login_form = async () => { + const menu = document.querySelector(".comfy-menu"); + // create a div in the menu for this extension + + + //need to check with server for the status of our current api.clientId + const cid = api.clientId || api.socket.url.split("=")[1]; + const server_state = await gde.get_server_state(cid).then((server_state) => { + const logged_in = server_state.logged_in; + + const separator = document.createElement("hr"); + separator.style.margin = "20px 0"; + separator.style.width = "100%"; + menu.append(separator); + menu.append(gde.login_div); + + if (logged_in) { + gde.login_div.append(gde.logoutButton); + + } else { + gde.login_div.append(gde.usernameField); + gde.login_div.append(gde.passwordField); + gde.login_div.append(gde.loginButton); + + } + }); + +} +gde.hijack_queueprompt = async () => { + // is this actually needed? +} app.registerExtension({ - name: "GDE.UI.RemoveDefaultButtons", - async setup(app) { - document.getElementById("comfy-load-default-button").remove(); - document.getElementById("comfy-clipspace-button").remove(); - }, + name: "GDE.UI.RemoveDefaultButtons", + async setup(app) { + document.getElementById("comfy-load-default-button").remove(); + document.getElementById("comfy-clipspace-button").remove(); + }, }); app.registerExtension({ - name: "GDE.UI", - async setup() { - const menu = document.querySelector(".comfy-menu"); - const separator = document.createElement("hr"); + name: "GDE.UI.Login", + async setup() { + setTimeout(async () => { + await gde.setup_login_form(); + await gde.hijack_queueprompt(); + }, 10); + } - separator.style.margin = "20px 0"; - separator.style.width = "100%"; - menu.append(separator); - // Save To Git Button - const saveButton = document.createElement("button"); - saveButton.textContent = "Save To Git"; - saveButton.onclick = async () => { - try { - const json = JSON.stringify(app.graph.serialize(), null, 2); - await api.fetchApi("/gde/git/save", { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ data: json }), - }); - } catch (error) { - console.error(error); - } - }; - menu.append(saveButton); +}); - // Load From Git Button - const loadButton = document.createElement("button"); - loadButton.textContent = "Load From Git"; - loadButton.onclick = async () => { - try { - const response = await api.fetchApi("/gde/git/load", { - method: "POST", - headers: { - "Content-Type": "application/json", - } - }); - const data = await response.json(); - app.loadGraphData(JSON.parse(data.data)); - } catch (error) { - console.error(error); - } - }; - menu.append(loadButton); - } -}); \ No newline at end of file +app.registerExtension({ + name: "GDE.UI.Git", + async setup() { + setupModal(); + const separator = document.createElement("hr"); + const menu = document.querySelector(".comfy-menu"); + separator.style.margin = "20px 0"; + separator.style.width = "100%"; + menu.append(separator); + setupButtons(menu, app, api); + const handleBranchCreateNew = (event, dropdown) => { + // Custom logic for when "Create New" is selected in the branch dropdown + showModal('branch-name-field'); // Show the modal for creating a new branch + dropdown.value = ""; // Reset the dropdown value + // Clear the file dropdown + const fileDropdown = document.getElementById("graph-name-field"); + if (fileDropdown) { + // remove all options without removing the first two + while (fileDropdown.options.length > 2) { + fileDropdown.remove(2); + } + } + }; + + const handleGraphCreateNew = (event, dropdown) => { + // Custom logic for when "Create New" is selected in the branch dropdown + showModal('graph-name-field'); // Show the modal for creating a new branch + dropdown.value = ""; // Reset the dropdown value + // Clear the file dropdown + const fileDropdown = document.getElementById("graph-name-field"); + }; + + setupDropdown(menu, api, "Select Branch", "/gde/git/users", "branch-name-field", null, null, handleBranchCreateNew); + const branchDropdown = document.getElementById("branch-name-field"); + + branchDropdown.addEventListener("change", (event) => { + const selectedBranch = event.target.value; + if (selectedBranch && selectedBranch !== "Create New") { + // Repopulate the file/graph dropdown based on the selected branch + setupDropdown(menu, api, "Select Graph", "/gde/git/user_graphs", "graph-name-field", selectedBranch, null, handleGraphCreateNew); + } + + }); + + + setupDropdown(menu, api, "Select Graph", "/gde/git/user_graphs", "graph-name-field", null, null, handleGraphCreateNew); + + } +});