diff --git a/ui_helpers.js b/ui_helpers.js index 7e72178..873e236 100644 --- a/ui_helpers.js +++ b/ui_helpers.js @@ -1,15 +1,16 @@ import {app} from "../../../scripts/app.js"; import {api} from "../../../scripts/api.js"; -export function setupButtons(menu, app, api) { +export function setupButtons(gdeState, app, api) { // Save To Git Button + const menu = document.querySelector(".comfy-menu"); const saveButton = document.createElement("button"); - saveButton.textContent = "Save To Git"; + saveButton.textContent = "Save To Cloud"; 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; + const userNameValue = gdeState.user; + const graphSelected = gdeState.graph; await api.fetchApi("/gde/git/save", { method: "POST", @@ -18,7 +19,7 @@ export function setupButtons(menu, app, api) { }, body: JSON.stringify({ data: json, - file_name: dropDownSelected, + file_name: graphSelected, user_name: userNameValue }), }); @@ -30,9 +31,11 @@ export function setupButtons(menu, app, api) { // Load From Git Button const loadButton = document.createElement("button"); - loadButton.textContent = "Load From Git"; + loadButton.textContent = "Load From Cloud"; loadButton.onclick = async () => { try { + const userNameValue = gdeState.user; + const graphSelected = gdeState.graph; const response = await api.fetchApi("/gde/git/load", { // needs user_name file_name method: "POST", @@ -40,8 +43,8 @@ export function setupButtons(menu, app, api) { "Content-Type": "application/json", }, body: JSON.stringify({ - user_name: menu.querySelector("#branch-name-field").value, - file_name: menu.querySelector("#graph-name-field").value + user_name: userNameValue, + file_name: graphSelected } ), @@ -56,9 +59,10 @@ export function setupButtons(menu, app, api) { } -export function setupModal() { +export function setupModal(gdeState) { // Modal const modal = document.createElement("div"); + modal.id = "graph-name-modal"; modal.style.display = "none"; // Initially hidden modal.style.position = "fixed"; modal.style.top = "50%"; @@ -80,7 +84,10 @@ export function setupModal() { const submitButton = document.createElement("button"); submitButton.textContent = "Submit"; let targetDropdownId = ''; // This will store the ID of the dropdown we're targeting - submitButton.onclick = () => { + + modal.submit = () => { + // this is the function that will be called when the submit button is clicked + // it will add the new option to the dropdown and select it const dropdown = document.querySelector(`#${targetDropdownId}`); const newOption = document.createElement("option"); newOption.value = modalInput.value; @@ -89,6 +96,10 @@ export function setupModal() { dropdown.value = modalInput.value; console.log("Submitted value:", modalInput.value); modal.style.display = "none"; // Hide the modal + return modalInput.value; + } + submitButton.onclick = () => { + modal.submit() }; modal.append(submitButton); @@ -99,13 +110,37 @@ export function setupModal() { }; } +HTMLSelectElement.prototype.clearDropDown = function (placeHolder = "") { + // remove everything + while (this.options.length > 0) { + this.remove(0); + } + // only add if there is a placeholder + if (placeHolder != "") { + //add select+type + const defaultOption = document.createElement("option"); + defaultOption.value = ""; + defaultOption.textContent = placeHolder + this.append(defaultOption); -export function setupDropdown(menu, api, placeholder, apiEndpoint, dropdownId, user = null, graph_name = null, onCreateNew = null) { + // Add "Create New" option + const createNewOption = document.createElement("option"); + createNewOption.value = "create_new"; + createNewOption.textContent = "Create New"; + this.append(createNewOption); + } + +}; + + +export function setupDropdown(menu, api, placeholder, apiEndpoint, dropdownId, + user = null, onCreateNew = null, + onChanged = null) { let dropdown = document.getElementById(dropdownId); // If dropdown already exists, clear its options first if (dropdown) { - dropdown.innerHTML = ''; + dropdown.clearDropDown(placeholder) } else { // Create new dropdown if it doesn't exist dropdown = document.createElement("select"); @@ -118,55 +153,49 @@ export function setupDropdown(menu, api, placeholder, apiEndpoint, dropdownId, u dropdown.id = dropdownId; //use comfy-list style - + dropdown.clearDropDown(placeholder) menu.append(dropdown); + + // Add event listener to the dropdown + dropdown.addEventListener("change", (event) => { + // Check if "Create New" option was selected + if (event.target.value === "create_new" && onCreateNew) { + onCreateNew(event, dropdown); + } + if (onChanged) { + onChanged(event, 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) - }) - } - }); + // dropdown.addEventListener("change", (event) => { + // // Check if "Create New" option was selected + // if (event.target.value === "create_new" && onCreateNew) { + // onCreateNew(event, dropdown); + // } + // + // }); setTimeout(async () => { - await someFunction(dropdownId, api, apiEndpoint, placeholder, user, graph_name) - }) + dropdown.clearDropDown(placeholder) + await someFunction(dropdownId, api, apiEndpoint, placeholder, user) + }, 50) + return dropdown; } -export async function someFunction(dropdownId, api, apiEndpoint, placeholder, user = null, graph_name = null) { +export async function someFunction(dropdownId, api, apiEndpoint, placeholder, user = null) { + // this is the function that will be called to fetch the options for the dropdown try { const dropdown = document.getElementById(dropdownId); dropdown.disabled = true; // Disable the file dropdown - + const clientId = api.socket.url.split("=")[1] const queryParams = { - user: user || "" + user: user || "", + client_id: clientId }; - if (graph_name) { - queryParams.graph_name = graph_name; - } const response = await api.fetchApi(apiEndpoint, { method: "POST", @@ -177,6 +206,10 @@ export async function someFunction(dropdownId, api, apiEndpoint, placeholder, us }); const optionsData = await response.json(); optionsData.forEach(optionItem => { + // if the option already exists, don't add it again + if (dropdown.querySelector(`option[value="${optionItem.value}"]`)) { + return; + } const optionElement = document.createElement("option"); optionElement.value = optionItem.value; optionElement.textContent = optionItem.label; diff --git a/ui_modifications.js b/ui_modifications.js index df0c379..c762596 100644 --- a/ui_modifications.js +++ b/ui_modifications.js @@ -2,153 +2,233 @@ import {app} from "../../../scripts/app.js"; import {api} from "../../../scripts/api.js"; import {setupButtons, setupModal, setupDropdown} from './ui_helpers.js'; + +let loggedInEvent = null; +let loggedOutEvent = new Event('loggedOutEvent'); +export let selectedGraph = null; + +//const graphCreateNewEvent = new Event('graphCreateNew'); // 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, - }) - } - ); +class GDELogin { + constructor(api) { + this.api = api; + this.security_token = null; + this.initElements(); } -); -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, - } - ) - } - ) - ; + + initElements() { + this.login_div = this.createElement("div", {id: "gde-login"}); + this.usernameField = this.createInputField("username-field", "Username"); + this.passwordField = this.createInputField("password-field", "Password", "password"); + this.loginButton = this.createButton("login-button", "Login", this.handleLogin.bind(this)); + this.logoutButton = this.createButton("logout-button", "Logout", this.handleLogout.bind(this)); + } + + createInputField(id, placeholder, type = "text") { + return this.createElement("input", { + id: id, + placeholder: placeholder, + type: type, + }); + } + + createButton(id, text, clickHandler) { + const button = this.createElement("button", { + id: id, + innerText: text, + }); + button.addEventListener("click", clickHandler); + return button; + } + + createElement(tag, attributes) { + const element = document.createElement(tag); + for (let attr in attributes) { + element[attr] = attributes[attr]; + } + return element; + } + + async handleLogin() { + const username = this.usernameField.value; + const password = this.passwordField.value; + + gdeState.user = username; + + const res = await this.api.fetchApi("/gde/login", { + method: "POST", + body: JSON.stringify({ + username: username, + password: password, + client_id: this.api.clientId, + }), + }); + if (res.status !== 200) { - alert("Login failed"); + //if res.logged_in is false then the login failed but only need to alert if this.api.clientId is set + if (res.logged_in === false && this.api.clientId) { + alert("Login failed. Please check your username and password and try again."); + } + if (res.logged_in === true) { + this.username = res.user; + gdeState.user = username; + } + } else { - //read the json response, token will be under security - const json = await res.json(); + const security_json = await res.json(); + this.security_token = security_json.security; + this.switchToLogoutView(); - //set the token it is somewhere in the json under security - gde.security_token = json.security; + if (username) { + const event = new CustomEvent('loggedInEvent', + {detail: {'user': username}}); + //loggedInEvent.target = {value:json.user}; + document.dispatchEvent(event); + } else { // client was refreshed user was lost but client_id was not + this.usernameField.value = security_json.user; + loggedInEvent.target.value = security_json.user; + document.dispatchEvent(loggedInEvent); - //now that we are logged in remove the login button and fields - gde.usernameField.remove(); - gde.passwordField.remove(); - gde.loginButton.remove(); + } + const welcomeMessage = this.createElement("div", { + id: "welcome-message", + innerText: `Welcome ${this.usernameField.value}` + }); + this.login_div.append(welcomeMessage); + } - gde.login_div.append(gde.logoutButton); + } + async handleLogout() { + await this.api.fetchApi("/gde/logout", { + method: "POST", + body: JSON.stringify({ + client_id: this.api.clientId, + }), + }); + document.dispatchEvent(loggedOutEvent); + this.clearDropdowns(); + this.switchToLoginView(); + const welcomeMessage = document.getElementById("welcome-message"); + if (welcomeMessage) welcomeMessage.remove(); + + } + + switchToLogoutView() { + this.usernameField.remove(); + this.passwordField.remove(); + this.loginButton.remove(); + this.login_div.append(this.logoutButton); + } + + switchToLoginView() { + this.logoutButton.remove(); + this.login_div.append(this.usernameField, this.passwordField, this.loginButton); + } + + clearDropdowns() { + this.clearDropdown("graph-name-field"); + //this.clearDropdown("branch-name-field"); + } + + clearDropdown(id) { + const dropdown = document.getElementById(id); + if (dropdown) { + while (dropdown.options.length > 2) { + dropdown.remove(2); + } } } -); -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; + + async getServerState(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; + } + + async setupLoginForm() { + 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 this.getServerState(cid).then((server_state) => { + const logged_in = server_state.logged_in; + gdeState.user = server_state.user; + + const separator = document.createElement("hr"); + separator.style.margin = "20px 0"; + separator.style.width = "100%"; + menu.append(separator); + menu.append(this.login_div); + + if (logged_in) { + this.login_div.append(this.logoutButton); + + } else { + this.login_div.append(this.usernameField); + this.login_div.append(this.passwordField); + this.login_div.append(this.loginButton); + + } + }); + } +} + +const gdeLogin = new GDELogin(api); +let gdeState = { + // this is for holding the state of the ui like what is selected etc + graph: null, + branch: null, + user: null, } // 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? -} +// 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) { @@ -161,60 +241,152 @@ app.registerExtension({ name: "GDE.UI.Login", async setup() { setTimeout(async () => { - await gde.setup_login_form(); - await gde.hijack_queueprompt(); + await gdeLogin.setupLoginForm(); + //await gde.hijack_queueprompt(); }, 10); } }); +function SetupGraphDropDown(graphDropdown, + event, + menu, + handleGraphCreateNew, + handleGraphSelect) { + // event may come in as null, so we need to populate it with default values + if (!event) { + event = { + detail: { + user: "main", + graph: "default.json", + branch: "main" + }, + target: { + value: "main" + } + } + gdeState.user = "main" + } + // graphDropdown.clearDropDown(); + graphDropdown.clearDropDown("Select Graph"); + const selectedGraph = null + graphDropdown = setupDropdown(menu, api, + "Select Graph", + "/gde/git/user_graphs", + "graph-name-field", + gdeState.user, + handleGraphCreateNew, + handleGraphSelect); + // modify graphDropdown to change gdeState.graph when it is changed + graphDropdown.addEventListener("change", (event) => { + const selectedGraph = event.target.value; + if (selectedGraph && selectedGraph !== gdeState.graph) { + gdeState.graph = selectedGraph; + const graphChangeEvent = new CustomEvent('graphChangeEvent', { + detail: { + graph: selectedGraph + } + }); + document.dispatchEvent(graphChangeEvent); + } + }); + return graphDropdown; +} + app.registerExtension({ name: "GDE.UI.Git", async setup() { setupModal(); + setupButtons(gdeState, app, api); + + 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"); + // 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"); + // + // + // }; - 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); - } + document.addEventListener('loggedInEvent', async (event) => { + gdeState.user = event.detail.user; + //gdeState.graph = event.detail.graph; + graphDropdown = SetupGraphDropDown( + graphDropdown, + event, + menu, + handleGraphCreateNew, + handleGraphSelect); }); + // no lets call clear dropdown when there is a loggout + document.addEventListener('loggedOutEvent', async (event) => { + graphDropdown.clearDropDown(); + } + ); - setupDropdown(menu, api, "Select Graph", "/gde/git/user_graphs", "graph-name-field", null, null, handleGraphCreateNew); + const handleGraphCreateNew = (event, dropdown) => { + // Custom logic for when "Create New" is selected in the branch dropdown + + showModal('graph-name-field') + dropdown.disabled = true; + + const modal = document.getElementById("graph-name-modal"); + // hijack its onsubmit function + const prev_submit = modal.submit; + //submitButton.onclick + modal.submit = () => { + let graphName = prev_submit(); + gdeState.graph = graphName; + dropdown.disabled = false; + + } + }; + const handleGraphSelect = (event, dropdown) => { + // change gdeState.graph when it is changed + const selectedGraph = event.target.value; + gdeState.graph = selectedGraph; + + }; + + + //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); + // } + // + // }); + + + let tmp = setupDropdown(menu, + api, + "Select Graph", + "/gde/git/user_graphs", + "graph-name-field", + gdeState.user, + handleGraphCreateNew, + handleGraphSelect); + let graphDropdown = SetupGraphDropDown(tmp, + null, + menu, + handleGraphCreateNew, + handleGraphSelect); } });