diff --git a/.gitignore b/.gitignore
index c9e9120..db75471 100644
--- a/.gitignore
+++ b/.gitignore
@@ -14,7 +14,7 @@ develop-eggs/
downloads/
eggs/
.eggs/
-lib/
+# lib/
lib64/
parts/
sdist/
diff --git a/web/lib/nexus.api.js b/web/lib/nexus.api.js
new file mode 100644
index 0000000..93c7da0
--- /dev/null
+++ b/web/lib/nexus.api.js
@@ -0,0 +1,115 @@
+const BASE_URL = 'nexus';
+
+async function fetchWithHandling(url, options = {}) {
+ try {
+// console.info("Calling: ", url)
+ const response = await fetch(url, options);
+ const data = await response.json();
+ if (!response.ok) {
+ throw new Error(data.error || 'Something went wrong');
+ }
+ return data;
+ } catch (error) {
+ console.error(`Fetch error: ${error.message}`);
+ return { error: error.message };
+ }
+}
+
+export async function getUserSpecificWorkflows(userId) {
+ const url = `${BASE_URL}/workflows`;
+ const options = {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ user_id: userId }),
+ };
+ return await fetchWithHandling(url, options);
+}
+
+export async function getUserSpecificWorkflow(userId, workflowId) {
+ const url = `${BASE_URL}/workflows/${workflowId}`;
+ const options = {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ user_id: userId }),
+ };
+ return await fetchWithHandling(url, options);
+}
+
+export async function getUserSpecificWorkflowsMeta(userId) {
+ const url = `${BASE_URL}/workflows/meta`;
+ const options = {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ user_id: userId }),
+ };
+ return await fetchWithHandling(url, options);
+}
+
+export async function postWorkflow(userId, workflowData) {
+ const url = `${BASE_URL}/workflow`;
+ const options = {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ user_id: userId, ...workflowData }),
+ };
+ return await fetchWithHandling(url, options);
+}
+
+export async function postBackupWorkflow(userId, workflowData) {
+ const url = `${BASE_URL}/workflow/backup`;
+ const options = {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ user_id: userId, ...workflowData }),
+ };
+ return await fetchWithHandling(url, options);
+}
+
+export async function getLatestWorkflow() {
+ const url = `${BASE_URL}/workflow`;
+ return await fetchWithHandling(url);
+}
+
+export async function getPermissionById(userId) {
+ const url = `${BASE_URL}/permission/${userId}`;
+ return await fetchWithHandling(url);
+}
+
+export async function postPermissionById(userId, adminId, data, token) {
+ const url = `${BASE_URL}/permission/${userId}`;
+ const options = {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ 'Authorization': token,
+ },
+ body: JSON.stringify({ admin_id: adminId, data }),
+ };
+ return await fetchWithHandling(url, options);
+}
+
+export async function nexusLogin(uuid, account, password) {
+ const url = `${BASE_URL}/login`;
+ const options = {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ uuid, account, password }),
+ };
+ return await fetchWithHandling(url, options);
+}
+
+export async function nexusVerify(token) {
+ const url = `${BASE_URL}/verify`;
+ const options = {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ token }),
+ };
+ return await fetchWithHandling(url, options);
+}
+
+export async function getNameById(userId) {
+ const url = `${BASE_URL}/name/${userId}`;
+ return await fetchWithHandling(url);
+}
+
diff --git a/web/lib/nexus.chat.js b/web/lib/nexus.chat.js
new file mode 100644
index 0000000..cf21bc5
--- /dev/null
+++ b/web/lib/nexus.chat.js
@@ -0,0 +1,160 @@
+import { DynamicElement } from "./nexus.elements.js"
+import { StyleInjector } from "./nexus.styles.js";
+
+let chatMain = new DynamicElement({
+ id: 'nexus-chat-main',
+ tag: 'div',
+ visible: true,
+ styles: {
+ width: '512px',
+ minHeight: '170px',
+ maxHeight: '170px',
+ overflow: 'hidden',
+ padding: '16px',
+ borderRadius: '8px',
+ border: '1px solid rgba(94,94,94,0.5)'
+ },
+ hoverStyles: {
+ overflowY: 'scroll'
+ },
+ afterChildrenAppend: function (childrens, me) {
+ if (childrens.length > 50) {
+ childrens[0].removeElement()
+ childrens.shift()
+ }
+ me.el.scrollTop = me.el.scrollHeight
+ }
+})
+
+let inputMain = new DynamicElement({
+ id: 'nexus-input-main',
+ tag: 'div',
+ visible: true,
+ styles: {
+ width: '100%',
+ height: '32px',
+ display: 'flex',
+ alignItems: 'center',
+ },
+ childrens: [
+ new DynamicElement({
+ id: 'nexus-input-label',
+ tag: 'span',
+ visible: false,
+ styles: {
+ fontFamily: 'Calibri, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif',
+ display: 'block',
+ fontSize: '14px',
+ lineHeight: '14px',
+ fontWeight: 600,
+ color: 'white',
+ padding: '0',
+ margin: '0',
+ textShadow: '1pt 1pt #000',
+ },
+ childrens: ['Say:']
+ }),
+ new DynamicElement({
+ id: 'nexus-input',
+ tag: 'input',
+ visible: false,
+ styles: {
+ fontFamily: 'Calibri, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif',
+ fontSize: '14px',
+ lineHeight: '14px',
+ fontWeight: 600,
+ width: '100%',
+ color: 'white',
+ padding: '0',
+ margin: '0',
+ border: 'none',
+ background: "transparent",
+ flexGrow: 1,
+ outline: 'none',
+ textIndent: '4pt',
+ caretColor: 'transparent',
+ textShadow: '1pt 1pt #000',
+ }
+ })
+ ]
+})
+
+let chatWindow = new DynamicElement({
+ id: 'nexus-chat-window',
+ tag: 'div',
+ styles: {
+ position: 'absolute',
+ left: "16px",
+ top: '100px',
+ display: 'flex',
+ flexDirection: 'column',
+ zIndex: 1000
+ },
+ childrens: [
+ new DynamicElement({
+ id: "nexus-total-players",
+ tag: "div",
+ visible: true,
+ styles: {
+ fontFamily: 'Calibri, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif',
+ fontSize: '14px',
+ lineHeight: '14px',
+ fontWeight: 600,
+ left: "16px",
+ top: '80px',
+ color: "#a5a5a5",
+ width: "100%",
+ paddingBottom: "12px",
+ textAlign: "right"
+ },
+ childrens: ['Users Online: ', new DynamicElement({ id: "nexus-total-players-count", visible: true, tag: "span", childrens:['0'] })]
+ }),
+ chatMain,
+ inputMain,
+ new StyleInjector({
+ styles: {
+ '#nexus-chat-main::-webkit-scrollbar': {
+ 'display': 'none',
+ 'scrollbar-width': 'none',
+ '-ms-overflow-style': 'none',
+ },
+ "#nexus-chat-window" : {
+ // for pointer events
+ }
+ }
+ })
+ ]
+})
+
+let addChatWindowMessage = function (subject, message, subject_color, message_color, merge) {
+ let msg = ""
+ if (merge) {
+ msg = `${subject} ${message}`
+ }
+ else {
+ msg = `${subject}: ${message}`
+ }
+
+ let chatElement = new DynamicElement({
+ tag: 'span',
+ visible: true,
+ styles: {
+ fontFamily: 'Calibri, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif',
+ display: 'block',
+ fontSize: '14px',
+ lineHeight: '14px',
+ fontWeight: 600,
+ width: '100%',
+ color: 'white',
+ padding: '0',
+ margin: '0',
+ textShadow: '1pt 1pt #000',
+ marginBottom: '1pt',
+ lineBreak: 'anywhere'
+ },
+ childrens: [msg],
+ })
+ chatMain.addChild(chatElement)
+}
+
+export { chatMain, inputMain, chatWindow, addChatWindowMessage }
\ No newline at end of file
diff --git a/web/lib/nexus.cmd.js b/web/lib/nexus.cmd.js
new file mode 100644
index 0000000..7d1eeb0
--- /dev/null
+++ b/web/lib/nexus.cmd.js
@@ -0,0 +1,737 @@
+import { nexusLogin, nexusVerify, getPermissionById, postPermissionById, getUserSpecificWorkflowsMeta, getUserSpecificWorkflow } from "./nexus.api.js"
+import { addChatWindowMessage, inputMain, chatWindow } from "./nexus.chat.js"
+import { UserPanel } from "./nexus.panel.js"
+import { WorkflowPanel } from "./nexus.workflow.panel.js"
+
+export class NexusCommands {
+
+ constructor(app, api, nexusSocket, color, mouseManger, workflowManager, promptControl) {
+
+ this.color = color
+
+ this.colors = {
+ "join": "#ff6e4d",
+ "info": "#fba510",
+ "personal": "#fba510",
+ "name": "#00fa00",
+ "none": "#ffffff"
+ }
+
+ this.inputMain = inputMain
+ this.nexusSocket = nexusSocket
+
+ this.app = app
+ this.api = api
+
+ this.workflowManager = workflowManager
+ this.promptControl = promptControl
+ this.mouseManger = mouseManger
+
+ this.nexusSocket.onConnected.push(this.handleConnect.bind(this))
+ this.nexusSocket.onMessageReceive.push(this.handleMessageReceive.bind(this))
+
+ this.inputMain.addEventListener('message', this.handleMessage.bind(this))
+
+ document.addEventListener('keydown', this.handleChatWindow.bind(this));
+
+ this.disableComfyThings = this.disableComfyThings.bind(this);
+ this.enableComfyThings = this.enableComfyThings.bind(this);
+ this.disableGraphSelectionWithExtremePrejudice = this.disableGraphSelectionWithExtremePrejudice.bind(this);
+
+ this.comfyThings = [
+ '#extraOptions',
+ '#queue-button',
+ '#comfy-save-button',
+ '#comfy-file-input',
+ '#comfy-clear-button',
+ '#comfy-load-default-button',
+ '.queue-tab-button.side-bar-button',
+ '.queue-tab-button.side-bar-button',
+ '#comfy-refresh-button',
+ '#queue-front-button',
+ '#comfy-view-queue-button',
+ '#comfy-view-history-button'
+ ];
+
+ chatWindow.childrens[3].softUpdate('#nexus-chat-window', 'pointer-events', 'none')
+ chatWindow.show()
+
+ setInterval((chatWindow) => {
+ chatWindow.childrens[0].childrens[1].el.textContent = Object.keys(this.nexusSocket.userManager.users).length
+ }, 1000, chatWindow);
+
+ this.up = new UserPanel()
+ this.up.onCommandReceived = this.onPanelCommandReceived.bind(this)
+
+ this.wp = new WorkflowPanel()
+ this.wp.onCommandReceived = this.onWFPanelCommandReceived.bind(this)
+
+ this.spectating = null
+ this.lastkey = null
+
+ }
+
+ timeAgo(timeString) {
+ const now = new Date(); // current time
+ const time = new Date(timeString); // input time
+
+ const diff = Math.floor((now - time) / 1000); // difference in seconds
+
+ const minutes = Math.floor(diff / 60);
+ const hours = Math.floor(minutes / 60);
+ const days = Math.floor(hours / 24);
+
+ if (minutes < 1) return "just now";
+ if (minutes === 1) return "1 minute ago";
+ if (minutes < 60) return `${minutes} minutes ago`;
+ if (hours === 1) return "1 hour ago";
+ if (hours < 24) return `${hours} hours ago`;
+ if (days === 1) return "1 day ago";
+
+ return `${days} days ago`;
+ }
+
+ async verifyLogin() {
+
+ this.disableComfyThings()
+
+ let token = localStorage.getItem('nexus-socket-token');
+ if (token) {
+ let user = await nexusVerify(token)
+ if (user && this.nexusSocket.uuid == user.user_id) {
+ this.enableComfyThings()
+ this.promptControl.handleControl(true)
+ this.promptControl.handleEditor(true)
+ this.workflowManager.handleWorkflowEvents(true)
+ this.nexusSocket.admin = true
+ addChatWindowMessage("", "You are now an admin!", this.color.info, this.colors.personal, true)
+ this.workflowManager.saveManager(60)
+ }
+ else {
+ this.promptControl.handleControl(false)
+ this.promptControl.handleEditor(false)
+ this.workflowManager.handleWorkflowEvents(false)
+ this.workflowManager.removeSaveManager()
+ addChatWindowMessage("", "You are a viewer!", this.color.info, this.colors.info, true)
+ }
+ }
+ else {
+
+ let permissions = await getPermissionById(this.nexusSocket.uuid)
+
+ if (permissions) {
+
+ let editor = permissions.editor ? permissions.editor : false
+ let queue = permissions.queue ? permissions.queue : false
+
+ this.workflowManager.handleWorkflowEvents(editor)
+
+ this.promptControl.handleEditor(editor)
+ this.promptControl.handleControl(queue)
+
+ this.nexusSocket.queue = queue
+ this.nexusSocket.editor = editor
+
+ if (editor) {
+ this.workflowManager.saveManager(60)
+ }
+ else {
+ this.workflowManager.removeSaveManager()
+ }
+
+ addChatWindowMessage("", `Permission: Workflow Editor ${editor ? "Yes" : "No"} | Queue Prompt ${queue ? "Yes" : "No"} `, this.color.info, this.colors.personal, true)
+
+ if (queue) {
+ addChatWindowMessage("", `You can now queue prompt press CTRL+Enter`, this.color.info, this.colors.personal, true)
+ }
+
+ }
+
+ }
+
+ this.workflowManager.loadLatestWorkflow()
+ }
+
+ handleConnect() {
+
+ let name = this.nexusSocket.userManager.get(this.nexusSocket.uuid, "name")
+ this.nexusSocket.sendMessage('chat-join', { name }, false)
+ this.verifyLogin()
+
+ }
+
+ findChildrenAfterHr(parentClass) {
+ const parentDiv = document.querySelector(`.${parentClass}`);
+ const childElementsAfterHr = [];
+ if (parentDiv) {
+ const hr = parentDiv.querySelector('hr');
+ if (hr) {
+ let sibling = hr.nextElementSibling;
+ while (sibling) {
+ childElementsAfterHr.push(sibling);
+ sibling = sibling.nextElementSibling;
+ }
+ }
+ }
+ return childElementsAfterHr;
+ }
+
+ disableGraphSelectionWithExtremePrejudice() {
+ if (!this.nexusSocket.admin && !this.nexusSocket.editor) {
+ if (this.app.graph && this.app.graph.list_of_graphcanvas && this.app.graph.list_of_graphcanvas.length > 0) {
+ this.app.graph.list_of_graphcanvas[0].deselectAllNodes()
+ }
+ }
+ }
+
+ disableComfyThings() {
+
+ this.comfyThings.forEach((selector) => {
+ const elements = document.querySelectorAll(selector);
+ elements.forEach((element) => {
+ element.disabled = true;
+ element.style.pointerEvents = 'none';
+ });
+ });
+
+ document.querySelectorAll('.comfy-settings-btn').forEach(button => {
+ button.disabled = true;
+ button.style.pointerEvents = 'none';
+ });
+
+ const children = this.findChildrenAfterHr('comfy-menu');
+
+ children.forEach(element => {
+ if (element.tagName === 'BUTTON' || element.tagName === 'INPUT' || element.tagName === 'SELECT' || element.tagName === 'TEXTAREA') {
+ element.disabled = true;
+ }
+ });
+
+ document.querySelector(`.comfy-menu`).style.display = 'none';
+ document.querySelector(`.comfy-menu`).style.pointerEvents = 'none';
+
+
+ clearInterval(this.disableComfyThingsTimer)
+
+ this.disableComfyThingsTimer = setInterval(() => {
+
+
+ this.comfyThings.forEach((selector) => {
+ const elements = document.querySelectorAll(selector);
+ elements.forEach((element) => {
+ element.disabled = true;
+ element.style.pointerEvents = 'none';
+ });
+ });
+
+ document.querySelectorAll('.comfy-settings-btn').forEach(button => {
+ button.disabled = true;
+ button.style.pointerEvents = 'none';
+ });
+
+ const children = this.findChildrenAfterHr('comfy-menu');
+
+ children.forEach(element => {
+ if (element.tagName === 'BUTTON' || element.tagName === 'INPUT' || element.tagName === 'SELECT' || element.tagName === 'TEXTAREA') {
+ element.disabled = true;
+ }
+ });
+
+ document.querySelector(`.comfy-menu`).style.display = 'none';
+ document.querySelector(`.comfy-menu`).style.pointerEvents = 'none';
+
+ this.promptControl.handleEditor(this.nexusSocket.editor ? this.nexusSocket.editor : false)
+ this.promptControl.handleControl(this.nexusSocket.queue ? this.nexusSocket.queue : false)
+
+ }, 500);
+
+ this.disableGraphSelectionWithExtremePrejudiceTimer = setInterval(this.disableGraphSelectionWithExtremePrejudice, 50)
+
+ }
+
+ enableComfyThings() {
+
+ clearInterval(this.disableComfyThingsTimer)
+ clearInterval(this.disableGraphSelectionWithExtremePrejudiceTimer)
+
+ this.comfyThings.forEach((selector) => {
+ const elements = document.querySelectorAll(selector);
+ elements.forEach((element) => {
+ element.disabled = false;
+ element.style.pointerEvents = 'all';
+ });
+ });
+
+ document.querySelectorAll('.comfy-settings-btn').forEach(button => {
+ button.disabled = false;
+ button.style.pointerEvents = 'all';
+ });
+
+ const children = this.findChildrenAfterHr('comfy-menu');
+
+ children.forEach(element => {
+ if (element.tagName === 'BUTTON' || element.tagName === 'INPUT' || element.tagName === 'SELECT' || element.tagName === 'TEXTAREA') {
+ element.disabled = false;
+ }
+ });
+
+ document.querySelector(`.comfy-menu`).style.display = 'flex';
+ document.querySelector(`.comfy-menu`).style.pointerEvents = 'all';
+
+ }
+
+
+ handleChatWindow(event) {
+
+ let key = event.key
+ let label = inputMain.childrens[0]
+ let input = inputMain.childrens[1]
+
+ let active = {
+ tag: document.activeElement.tagName,
+ el: document.activeElement,
+ chat: document.activeElement == input.el
+ }
+
+ function activateChat() {
+ let allowedTags = ['INPUT', 'TEXTAREA']
+ if (!allowedTags.includes(active.tag) && input.visible == false) {
+ event.preventDefault();
+ label.show()
+ input.show()
+ input.el.focus()
+ input.el.value = ""
+ input.focusTimer = setInterval((parent, input) => {
+ if (document.activeElement != input && parent.visible == true)
+ input.focus()
+ }, 500, input, input.el);
+ chatWindow.childrens[3].softUpdate('#nexus-chat-window', 'pointer-events', 'all')
+ }
+ }
+
+ function deactivateChat() {
+ if (!active.chat) return;
+ label.hide()
+ input.el.blur();
+ input.el.value = ""
+ input.hide()
+ clearTimeout(input.focusTimer)
+ chatWindow.childrens[3].softUpdate('#nexus-chat-window', 'pointer-events', 'none')
+ }
+
+ function sendMessage() {
+ if (!active.chat) return;
+ let message = input.el.value
+ inputMain.dispatchEvent(new CustomEvent('message', { detail: { message } }))
+ deactivateChat()
+ }
+
+ switch (key) {
+ case 't':
+ case 'T':
+ activateChat()
+ this.togglePlayerTab(true)
+ this.toggleWFTab(true)
+ break;
+ case 'Escape':
+ deactivateChat()
+ break;
+ case 'Enter':
+ sendMessage()
+ break;
+ case 'p':
+ case 'P':
+ if (event.altKey && event.shiftKey) {
+ if (!active.chat)
+ this.togglePlayerTab()
+ }
+ break;
+ case 'o':
+ case 'O':
+ if (event.altKey && event.shiftKey) {
+ if (!active.chat)
+ this.toggleWFTab()
+ }
+ break;
+
+ }
+
+ return active
+ }
+
+ toggleWFTab(forceHide, forceReload) {
+ if (this.nexusSocket.admin || this.nexusSocket.editor) {
+
+ if (forceHide) {
+ this.wp.active = 0
+ this.wp.panel.hide()
+ } else if (forceReload) {
+ this.prepareWFPanel()
+ }
+ else {
+ if (this.wp.active == 0) {
+ this.wp.active = 1
+ this.wp.panel.show()
+ this.prepareWFPanel()
+ } else if (this.wp.active == 1) {
+ this.wp.active = 0
+ this.wp.panel.hide()
+ }
+ }
+
+ if (this.wp.active == 1) {
+ this.workflowManager.removeSaveManager()
+ }
+ else {
+ this.workflowManager.saveManager(this.workflowManager.saveTime || 60)
+ }
+
+ }
+ }
+
+ togglePlayerTab(forceHide, forceReload) {
+
+ if (forceHide) {
+ this.up.active = 0
+ this.up.panel.hide()
+ } else if (forceReload) {
+ this.preparePanel(this.nexusSocket.admin)
+ } else {
+ if (this.up.active == 0) {
+ this.up.active = 1
+ this.up.panel.show()
+ this.preparePanel(this.nexusSocket.admin)
+ } else if (this.up.active == 1) {
+ this.up.active = 0
+ this.up.panel.hide()
+ }
+ }
+
+ }
+
+ async onWFPanelCommandReceived(kind, label, data, active) {
+
+ switch (kind) {
+ case 'button':
+ if (label == "load") {
+ let wfid = data // workflowid is data
+ let wf = await getUserSpecificWorkflow(this.nexusSocket.uuid, wfid)
+ if (wf) {
+ wf.clear = true
+ let message = {
+ name: "workflow",
+ from: this.nexusSocket.uuid,
+ data: { workflow: wf }
+ }
+ this.nexusSocket.onMessageReceive.forEach(f => { f(message); });
+ if (this.nexusSocket.admin) {
+ this.nexusSocket.sendMessage('workflow', { workflow: wf }, false)
+ }
+ }
+ }
+ break;
+ }
+
+ }
+
+ async onPanelCommandReceived(kind, label, data, active) {
+
+ switch (kind) {
+ case 'button':
+ if (label == "spectate") {
+ this.nexusSocket.sendMessage("spectate", { on: active, receiver: data }, false)
+ if (active == 1) {
+ this.spectating = data // userid is data
+ }
+ else {
+ this.spectating = null
+ }
+ } else if (label == "editor") {
+ if (this.nexusSocket.admin) {
+ let token = localStorage.getItem('nexus-socket-token');
+ await postPermissionById(data, this.nexusSocket.uuid, { editor: active ? true : false }, token) // userid is data
+ this.nexusSocket.sendMessage("permission", { receiver: data }, false)
+ }
+ } else if (label == "queue") {
+ if (this.nexusSocket.admin) {
+ let token = localStorage.getItem('nexus-socket-token');
+ await postPermissionById(data, this.nexusSocket.uuid, { queue: active ? true : false }, token) // userid is data
+ this.nexusSocket.sendMessage("permission", { receiver: data }, false)
+ }
+ }
+ else if (label == "mouse") {
+ this.nexusSocket.sendMessage("mouse_view_toggle", { on: active, receiver: data }, false)
+ this.nexusSocket.userManager.set(data, 'hide_mouse', active)
+ }
+ break;
+ }
+
+ }
+
+ async prepareWFPanel() {
+
+ const columns = ['Name', 'Created', 'Action'];
+
+ this.wp.addColumnsToPanel(columns);
+
+ let meta = await getUserSpecificWorkflowsMeta(this.nexusSocket.uuid)
+
+ const rows = [];
+ const callbacks = [];
+ const values = [];
+ const data = [];
+
+ for (let id = 0; id < meta.length; id++) {
+
+ const file = meta[id];
+
+ let fn = file.file_name + ""
+
+ let name = (fn.includes('lt') ? 'Long Term ' : 'Short Term ')
+ // let time = file.last_saved.split(':')
+ // time = time.join("|")
+
+ // console.log(time)
+ let time = this.timeAgo(file.last_saved)
+
+ rows.push([name, time, 'button:load'])
+ callbacks.push([null, null, this.wp.onclick])
+ values.push([null, null, '0'])
+ data.push([null, null, file.file_name])
+
+ }
+
+ this.wp.addRowsToPanel(rows, callbacks, values, data);
+ }
+
+ async preparePanel(admin) {
+
+ const columns = ['Name', 'Actions', 'Online'];
+ this.up.addColumnsToPanel(columns);
+
+ const users = this.nexusSocket.userManager.users;
+ const usersIds = Object.keys(users);
+
+ const rows = [];
+ const callbacks = [];
+ const values = [];
+ const data = [];
+
+ for (const id of usersIds) {
+
+ const user = users[id];
+ const name = id === this.nexusSocket.uuid ? `${user.name} (You)` : user.name;
+ const self = id === this.nexusSocket.uuid;
+
+ const status = user.status === "online" || self ? '🟢' : '🔴';
+ const hide_mouse = user.hide_mouse ? 0 : 1;
+ const permissions = await getPermissionById(id);
+ const spectating = id == this.spectating ? 1 : 0
+
+ callbacks.push([null, self ? 'none' : this.up.onclick, null]);
+
+ let powers = 'button:spectate,button:editor,button:queue,button:mouse'
+
+ if (!admin) {
+ powers = 'button:spectate,button:mouse'
+ }
+
+ rows.push([name, self ? 'button:none' : powers, status]);
+
+ let row_values = `${spectating},${permissions.editor ? 1 : 0},${permissions.queue ? 1 : 0},${hide_mouse}`
+ if (!admin) {
+ row_values = `${spectating},${hide_mouse}`
+ }
+
+ values.push([null, row_values, null]);
+
+ let row_data = `${id},${id},${id},${id}`
+ if (!admin) {
+ row_data = `${id},${id}`
+ }
+
+ data.push([null, row_data, null]);
+
+ }
+
+ this.up.addRowsToPanel(rows, callbacks, values, data);
+ }
+
+ handleMessageReceive(message) {
+
+ let name = message.name;
+ let from = message.from;
+ let data = message.data
+
+ // console.log(name, data, from)
+
+ if (name == "chat") {
+
+ let user_name = this.nexusSocket.userManager.get(from, "name") || "User";
+ let mouse = this.nexusSocket.userManager.get(from, "mouse") || "#0f0";
+ let message = data.message
+ addChatWindowMessage(user_name, message, mouse[3], this.colors.none)
+
+ } else if (name == "nick") {
+
+ let old_name = data.old_name
+ let new_name = data.new_name
+
+ addChatWindowMessage(old_name + " is now known as ", new_name, this.colors.personal, this.colors.personal, true)
+ this.nexusSocket.userManager.set(from, "name", new_name)
+
+ } else if (name == "chat-join") {
+
+ let name = data.name
+ addChatWindowMessage(name + " joined the server", "", this.colors.personal, this.colors.personal, true)
+ this.nexusSocket.userManager.set(from, "name", name)
+
+ name = this.nexusSocket.userManager.get(this.nexusSocket.uuid, "name")
+ this.nexusSocket.sendMessage('name', { name }, false)
+
+ if (this.up.active) {
+ this.togglePlayerTab(false, true)
+ }
+
+ } else if (name == "name") {
+
+ let name = data.name
+ this.nexusSocket.userManager.set(from, "name", name)
+ //console.log("Received name:", name)
+
+ if (this.up.active) {
+ this.togglePlayerTab(false, true)
+ }
+
+ } else if (name == 'afk' || name == 'online') {
+
+ this.nexusSocket.userManager.set(from, "status", name)
+
+ if (this.up.active) {
+ this.togglePlayerTab(false, true)
+ }
+
+ } else if (name == 'permission') {
+
+
+ // window.location.reload()
+
+ this.verifyLogin()
+
+ if (this.up.active) {
+ this.togglePlayerTab(false, true)
+ }
+
+ }
+
+ }
+
+ async handleMessage(evt) {
+
+ let message = evt.detail.message
+ message = this.cleanMessage(message)
+
+ if (message) {
+
+ let startsWithCommand = this.startsWithCommand(message)
+
+ if (startsWithCommand) {
+ let parts = this.extractCommandAndValue(message)
+ let cmds = { 'nick': true, 'login': true, 'logout': true }
+ let cmd = parts.command
+ let values = parts.values
+
+ if (cmds[cmd]) {
+ switch (cmd) {
+ case 'nick':
+ let new_name = this.cleanMessage(values[0], 16)
+ let old_name = this.nexusSocket.userManager.get(this.nexusSocket.uuid, "name") || "User"
+ if (!new_name) {
+ addChatWindowMessage("", "Name is invalid", this.colors.info, this.colors.info, true)
+ }
+ else {
+ localStorage.setItem('nexus-socket-name', new_name);
+ this.nexusSocket.sendMessage('nick', { old_name, new_name })
+ }
+ break;
+ case 'login':
+
+ let account = this.cleanMessage(values[0], 16)
+ let password = this.cleanMessage(values[1], 16)
+ let uuid = this.nexusSocket.uuid
+ let token = await nexusLogin(uuid, account, password)
+ if (token) {
+ localStorage.setItem('nexus-socket-token', token.token);
+ // addChatWindowMessage("", "You are now an admin!", this.color.info, this.colors.info, true)
+ this.verifyLogin()
+ // window.location.reload()
+ }
+ else {
+ addChatWindowMessage("", "Admin account/password invalid!", this.color.info, this.colors.info, true)
+ }
+ break;
+ case 'logout':
+ let usertoken = localStorage.getItem('nexus-socket-token')
+ if (this.nexusSocket.admin) {
+ await postPermissionById(this.nexusSocket.uuid, this.nexusSocket.uuid, { admin: false, queue: false, editor: false }, usertoken)
+ localStorage.removeItem('nexus-socket-token');
+ window.location.reload()
+ }
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ }
+ else {
+ addChatWindowMessage("You", message, this.color, this.colors.none)
+ this.nexusSocket.sendMessage('chat', { message }, false)
+ }
+
+ }
+
+ }
+
+ cleanMessage(message, length = 512) {
+ message = message.substr(0, length);
+ if (message.length === 0) {
+ return null;
+ } else {
+ let msg = "";
+ const nonAsciiRe = /[^\x00-\x7F]/;
+ const emojiRe = /[\u{1F600}-\u{1F64F}]|[\u{1F300}-\u{1F5FF}]|[\u{1F680}-\u{1F6FF}]|[\u{1F700}-\u{1F77F}]|[\u{1F780}-\u{1F7FF}]|[\u{1F800}-\u{1F8FF}]|[\u{1F900}-\u{1F9FF}]|[\u{1FA00}-\u{1FA6F}]|[\u{1FA70}-\u{1FAFF}]/u;
+ const htmlTagRe = /<\/?[^>]+(>|$)/g;
+ message = message.replace(htmlTagRe, '');
+ for (let char of message) {
+ if (!(nonAsciiRe.test(char) && !emojiRe.test(char))) {
+ msg += char;
+ }
+ }
+ return msg;
+ }
+ }
+
+ startsWithCommand(str) {
+ const commandPattern = /^\/\w+(\s+\S+)?/;
+ return commandPattern.test(str);
+ }
+
+ extractCommandAndValue(str) {
+ const commandPattern = /^\/(\w+)\s*(.*)/;
+ const match = str.match(commandPattern);
+ if (match) {
+ const command = match[1];
+ const values = match[2].trim() ? match[2].split(/\s+/) : [];
+ return {
+ command,
+ values
+ };
+ } else {
+ return null;
+ }
+ }
+
+
+}
\ No newline at end of file
diff --git a/web/lib/nexus.comfy.js b/web/lib/nexus.comfy.js
new file mode 100644
index 0000000..1a34052
--- /dev/null
+++ b/web/lib/nexus.comfy.js
@@ -0,0 +1,261 @@
+export class NexusPromptControl extends EventTarget {
+
+ constructor(api, app, nexusSocket) {
+
+ super()
+
+ this.app = app
+ this.api = api
+ this.runningNodeId = null
+ this.nexusSocket = nexusSocket
+ this.comfy_events = ['b_preview', 'progress', 'executing', 'executed', 'execution_start', 'execution_error', 'execution_cached'];
+
+ let oqp = api.queuePrompt;
+ this.apiWrapper = {
+ oqp: oqp,
+ enableApi: false,
+ api: api,
+ qp: async function (number, data) {
+ if (!this.enableApi) {
+ return {
+ "node_errors": {}
+ };
+ }
+ return this.oqp.call(this.api, number, { output: data.output, workflow: data.workflow });
+ }
+ };
+
+ this.api.queuePrompt = this.apiWrapper.qp.bind(this.apiWrapper);
+ this.nexusSocket.onMessageReceive.push(this.handleMessageReceive.bind(this))
+
+ this.addEventListeners();
+ this.comfy_events.forEach((event) => {
+ this.api.addEventListener(event, this.handleComfyEvents.bind(this));
+ });
+
+ this.handleProgressDraw()
+ this.handleControl(false)
+ this.handleEditor(false)
+
+ }
+
+ handleProgressDraw() {
+
+ const origDrawNodeShape = LGraphCanvas.prototype.drawNodeShape;
+ const self = this.app;
+ const self2 = this;
+
+ LGraphCanvas.prototype.drawNodeShape = function (node, ctx, size, fgcolor, bgcolor, selected, mouse_over) {
+
+ const res = origDrawNodeShape.apply(this, arguments);
+ const nodeErrors = self.lastNodeErrors?.[node.id];
+
+ let color = null;
+ let lineWidth = 1;
+
+ if (node.id === +self2.runningNodeId) {
+ color = "#0f0";
+ } else if (self.dragOverNode && node.id === self.dragOverNode.id) {
+ color = "dodgerblue";
+ }
+ else if (nodeErrors?.errors) {
+ color = "red";
+ lineWidth = 2;
+ }
+ else if (self.lastExecutionError && self.lastExecutionError.node_id === node.id) {
+ color = "#f0f";
+ lineWidth = 2;
+ }
+
+ if (color) {
+ const shape = node._shape || node.constructor.shape || LiteGraph.ROUND_SHAPE;
+ ctx.lineWidth = lineWidth;
+ ctx.globalAlpha = 0.8;
+ ctx.beginPath();
+ if (shape == LiteGraph.BOX_SHAPE)
+ ctx.rect(-6, -6 - LiteGraph.NODE_TITLE_HEIGHT, 12 + size[0] + 1, 12 + size[1] + LiteGraph.NODE_TITLE_HEIGHT);
+ else if (shape == LiteGraph.ROUND_SHAPE || (shape == LiteGraph.CARD_SHAPE && node.flags.collapsed))
+ ctx.roundRect(
+ -6,
+ -6 - LiteGraph.NODE_TITLE_HEIGHT,
+ 12 + size[0] + 1,
+ 12 + size[1] + LiteGraph.NODE_TITLE_HEIGHT,
+ this.round_radius * 2
+ );
+ else if (shape == LiteGraph.CARD_SHAPE)
+ ctx.roundRect(
+ -6,
+ -6 - LiteGraph.NODE_TITLE_HEIGHT,
+ 12 + size[0] + 1,
+ 12 + size[1] + LiteGraph.NODE_TITLE_HEIGHT,
+ [this.round_radius * 2, this.round_radius * 2, 2, 2]
+ );
+ else if (shape == LiteGraph.CIRCLE_SHAPE)
+ ctx.arc(size[0] * 0.5, size[1] * 0.5, size[0] * 0.5 + 6, 0, Math.PI * 2);
+ ctx.strokeStyle = color;
+ ctx.stroke();
+ ctx.strokeStyle = fgcolor;
+ ctx.globalAlpha = 1;
+ }
+
+ if (self.progress && node.id === +self2.runningNodeId) {
+ ctx.fillStyle = "green";
+ ctx.fillRect(0, 0, size[0] * (self.progress.value / self.progress.max), 6);
+ ctx.fillStyle = bgcolor;
+ }
+
+ // Highlight inputs that failed validation
+ if (nodeErrors) {
+ ctx.lineWidth = 2;
+ ctx.strokeStyle = "red";
+ for (const error of nodeErrors.errors) {
+ if (error.extra_info && error.extra_info.input_name) {
+ const inputIndex = node.findInputSlot(error.extra_info.input_name)
+ if (inputIndex !== -1) {
+ let pos = node.getConnectionPos(true, inputIndex);
+ ctx.beginPath();
+ ctx.arc(pos[0] - node.pos[0], pos[1] - node.pos[1], 12, 0, 2 * Math.PI, false)
+ ctx.stroke();
+ }
+ }
+ }
+ }
+
+ return res;
+ }
+
+ }
+
+ handleMessageReceive(message) {
+ try {
+ this.dispatchEvent(new CustomEvent(message.name, { detail: message }));
+ } catch (e) {
+ console.error('Error handling message:', e);
+ }
+ }
+
+ handleComfyEvents(evt) {
+ if (evt.type === "b_preview") {
+ this.blobToBase64(evt.detail).then((result) => {
+ this.nexusSocket.sendMessage(evt.type, { detail: result }, false)
+ });
+ } else {
+ this.nexusSocket.sendMessage(evt.type, { detail: evt.detail }, false)
+ }
+ }
+
+ addEventListeners() {
+
+ this.addEventListener("progress", ({ detail }) => {
+ detail = detail.data.detail
+ this.app.progress = detail;
+ this.app.graph.setDirtyCanvas(true, false);
+ });
+
+ this.addEventListener("executing", ({ detail }) => {
+ detail = detail.data.detail
+ this.app.progress = null;
+ this.runningNodeId = detail;
+ this.app.graph.setDirtyCanvas(true, false);
+ delete this.app.nodePreviewImages[this.runningNodeId];
+ });
+
+ this.addEventListener("executed", ({ detail }) => {
+ detail = detail.data.detail
+ const output = this.app.nodeOutputs[detail.node];
+ if (detail.merge && output) {
+ for (const k in detail.output ?? {}) {
+ const v = output[k];
+ if (v instanceof Array) {
+ output[k] = v.concat(detail.output[k]);
+ } else {
+ output[k] = detail.output[k];
+ }
+ }
+ } else {
+ this.app.nodeOutputs[detail.node] = detail.output;
+ }
+ const node = this.app.graph.getNodeById(detail.node);
+ if (node && node.onExecuted) {
+ node.onExecuted(detail.output);
+ }
+ });
+
+ this.addEventListener("execution_start", ({ detail }) => {
+ detail = detail.data.detail
+ this.runningNodeId = null;
+ this.app.lastExecutionError = null;
+ this.app.graph._nodes.forEach((node) => {
+ if (node.onExecutionStart) {
+ node.onExecutionStart();
+ }
+ });
+ });
+
+ this.addEventListener("execution_error", ({ detail }) => {
+ detail = detail.data.detail
+ this.app.lastExecutionError = detail;
+ const formattedError = this.formatExecutionError(detail);
+ this.app.ui.dialog.show(formattedError);
+ this.app.canvas.draw(true, true);
+ });
+
+ this.addEventListener("b_preview", ({ detail }) => {
+ detail = detail.data.detail
+ const id = this.runningNodeId;
+ if (id == null) return;
+ const blob = this.base64ToBlob(detail);
+ const blobUrl = URL.createObjectURL(blob);
+ this.app.nodePreviewImages[id] = [blobUrl];
+ });
+ }
+
+ blobToBase64(blob) {
+ return new Promise((resolve, reject) => {
+ const reader = new FileReader();
+ reader.onloadend = () => {
+ resolve(reader.result);
+ };
+ reader.onerror = reject;
+ reader.readAsDataURL(blob);
+ });
+ }
+
+ base64ToBlob(dataUrl) {
+ const [header, base64] = dataUrl.split(',');
+ const mime = header.match(/:(.*?);/)[1];
+ const byteCharacters = atob(base64);
+ const byteNumbers = new Array(byteCharacters.length);
+ for (let i = 0; i < byteCharacters.length; i++) {
+ byteNumbers[i] = byteCharacters.charCodeAt(i);
+ }
+ const byteArray = new Uint8Array(byteNumbers);
+ return new Blob([byteArray], { type: mime });
+ }
+
+ formatExecutionError(error) {
+ if (error == null) {
+ return "(unknown error)";
+ }
+
+ const traceback = error.traceback.join("");
+ const nodeId = error.node_id;
+ const nodeType = error.node_type;
+
+ return `Error occurred when executing ${nodeType}:\n\n${error.exception_message}\n\n${traceback}`;
+ }
+
+ handleEditor(oN) {
+
+ this.app.canvas.allow_dragnodes = oN;
+ this.app.canvas.allow_interaction = oN;
+ this.app.canvas.allow_reconnect_links = oN;
+ this.app.canvas.allow_searchbox = oN;
+
+ }
+
+ handleControl(oN) {
+ this.apiWrapper.enableApi = oN;
+ }
+
+}
\ No newline at end of file
diff --git a/web/lib/nexus.elements.js b/web/lib/nexus.elements.js
new file mode 100644
index 0000000..05294c3
--- /dev/null
+++ b/web/lib/nexus.elements.js
@@ -0,0 +1,217 @@
+import { StyleInjector } from "./nexus.styles.js";
+
+export class DynamicElement extends EventTarget {
+ id = '';
+ tag = '';
+ styles = {};
+ hoverStyles = {};
+ focusStyles = {};
+ activeStyles = {};
+ attrs = {};
+ childrens = [];
+ events = {};
+ appendTo = document.body;
+ visible = false;
+ afterChildrenAppend = null;
+ afterCreated = null;
+ defaultDisplay = 'initial';
+ el = null;
+
+ constructor({
+ id,
+ tag,
+ styles = {},
+ hoverStyles = {},
+ focusStyles = {},
+ activeStyles = {},
+ attrs = {},
+ childrens = [],
+ events = {},
+ appendTo = document.body,
+ afterChildrenAppend = null,
+ afterCreated = null,
+ visible = false
+ } = {}) {
+
+ super()
+
+ this.id = id;
+ this.tag = tag;
+ this.styles = styles;
+ this.hoverStyles = hoverStyles;
+ this.focusStyles = focusStyles;
+ this.activeStyles = activeStyles;
+ this.attrs = attrs;
+ this.childrens = childrens;
+ this.events = events;
+ this.appendTo = appendTo;
+ this.afterChildrenAppend = afterChildrenAppend;
+ this.afterCreated = afterCreated;
+ this.visible = visible
+
+ this.createElement();
+ }
+
+ createElement() {
+ if (!this.tag) {
+ throw new Error('Tag is required to create an element.');
+ }
+
+ this.el = document.createElement(this.tag);
+ if (this.id) this.el.id = this.id;
+ this.appendTo.appendChild(this.el);
+
+ this.addStyles();
+ this.addAttrs();
+ this.addChildrens();
+ this.addEvents();
+ this.addStateStyles();
+
+ if (!this.visible) {
+ this.hide()
+ }
+
+ if (this.afterCreated) {
+ this.afterCreated(this)
+ }
+ }
+
+ removeElement() {
+ if (!this.el) return;
+ this.removeAttrs();
+ this.removeEvents();
+ this.removeStyles();
+ this.removeChildrens();
+ this.el.remove();
+ this.el = null;
+ }
+
+ updateStyles(styles = {}) {
+ this.styles = { ...this.styles, ...styles };
+ this.addStyles();
+ }
+
+ addStyles() {
+ if (!this.el) return;
+ Object.entries(this.styles).forEach(([style, value]) => {
+ if (style === 'display') {
+ this.defaultDisplay = value;
+ }
+ this.el.style[style] = value;
+ });
+ }
+
+ removeStyles() {
+ if (!this.el) return;
+ Object.keys(this.styles).forEach(style => {
+ this.el.style[style] = '';
+ });
+ this.styles = {};
+ }
+
+ updateAttrs(attrs = {}) {
+ this.attrs = { ...this.attrs, ...attrs };
+ this.addAttrs();
+ }
+
+ addAttrs() {
+ if (!this.el) return;
+ Object.entries(this.attrs).forEach(([attr, value]) => {
+ this.el.setAttribute(attr, value);
+ });
+ }
+
+ removeAttrs() {
+ if (!this.el) return;
+ Object.keys(this.attrs).forEach(attr => {
+ this.el.removeAttribute(attr);
+ });
+ this.attrs = {};
+ }
+
+ addChildrens() {
+ if (!this.el) return;
+ this.childrens.forEach(child => {
+ if (typeof child === 'string') {
+ this.el.insertAdjacentHTML('beforeend', child);
+ } else if (child instanceof Node) {
+ this.el.appendChild(child);
+ } else if (child instanceof StyleInjector) {
+ this.el.appendChild(child.el);
+ } else if (child instanceof DynamicElement) {
+ this.el.appendChild(child.el);
+ }
+ });
+ if (this.afterChildrenAppend) {
+ requestAnimationFrame(() => this.afterChildrenAppend(this.childrens, this));
+ }
+ }
+
+ addChild(child) {
+ if (!this.el) return;
+ if (typeof child === 'string') {
+ this.el.insertAdjacentHTML('beforeend', child);
+ } else if (child instanceof Node) {
+ this.el.appendChild(child);
+ } else if (child instanceof DynamicElement) {
+ this.el.appendChild(child.el);
+ }
+ if(!this.childrens.includes(child)) {
+ this.childrens.push(child)
+ }
+ if (this.afterChildrenAppend) {
+ this.afterChildrenAppend(this.childrens, this);
+ }
+ }
+
+ removeChildrens() {
+ if (!this.el) return;
+ this.el.innerHTML = '';
+ this.childrens = [];
+ }
+
+ addEvents() {
+ if (!this.el) return;
+ Object.entries(this.events).forEach(([event, handler]) => {
+ this.el.addEventListener(event, (e) => { handler(e, this.parent) });
+ });
+ }
+
+ removeEvents() {
+ if (!this.el) return;
+ Object.entries(this.events).forEach(([event, handler]) => {
+ this.el.removeEventListener(event, handler);
+ });
+ this.events = {};
+ }
+
+ show() {
+ if (!this.el) return;
+ this.el.style.display = this.defaultDisplay;
+ this.visible = true;
+ }
+
+ hide() {
+ if (!this.el) return;
+ this.el.style.display = 'none';
+ this.visible = false;
+ }
+
+ addStateStyles() {
+ if (!this.el) return;
+
+ this.el.addEventListener('mouseenter', () => this.applyStyles(this.hoverStyles));
+ this.el.addEventListener('mouseleave', () => this.applyStyles(this.styles));
+ this.el.addEventListener('focus', () => this.applyStyles(this.focusStyles), true);
+ this.el.addEventListener('blur', () => this.applyStyles(this.styles), true);
+ this.el.addEventListener('mousedown', () => this.applyStyles(this.activeStyles));
+ this.el.addEventListener('mouseup', () => this.applyStyles(this.styles));
+ }
+
+ applyStyles(stateStyles) {
+ if (!this.visible) return
+ Object.entries(stateStyles).forEach(([style, value]) => {
+ this.el.style[style] = value;
+ });
+ }
+}
diff --git a/web/lib/nexus.litegraph.js b/web/lib/nexus.litegraph.js
new file mode 100644
index 0000000..6231b41
--- /dev/null
+++ b/web/lib/nexus.litegraph.js
@@ -0,0 +1,693 @@
+LiteGraph.use_uuids = true
+LiteGraph.uuidv4 = function() { return Date.now() }
+
+LGraph.prototype.getGroupByUUID = function (uuid) {
+ let groups = this._groups
+ let group = null
+ for (let index = 0; index < groups.length; index++) {
+ let tgroup = groups[index];
+ if (tgroup.uuid && tgroup.uuid == uuid) {
+ group = tgroup
+ break
+ }
+ }
+ return group
+}
+
+LGraph.prototype.getGroupIndexByUUID = function (uuid) {
+ let groups = this._groups
+ let at = -1
+ for (let index = 0; index < groups.length; index++) {
+ let tgroup = groups[index];
+ if (tgroup.uuid && tgroup.uuid == uuid) {
+ at = index
+ break
+ }
+ }
+ return at
+}
+
+
+LGraph.prototype.add = function (node, skip_compute_order) {
+
+ if (!node) {
+ return;
+ }
+
+ //groups
+ if (node.constructor === LGraphGroup) {
+ this._groups.push(node);
+ this.setDirtyCanvas(true);
+ this.change();
+ if(!node.uuid) {
+ node.uuid = LiteGraph.uuidv4()
+ }
+ node.graph = this;
+ this._version++;
+ if (this.onGroupAdded)
+ this.onGroupAdded(node)
+ return;
+ }
+
+ //nodes
+ if (node.id != -1 && this._nodes_by_id[node.id] != null) {
+ console.warn(
+ "Nexus LiteGraph: there is already a node with this ID, will reuse it"
+ );
+ if (LiteGraph.use_uuids) {
+ node.id = LiteGraph.uuidv4()
+ }
+ else {
+ node.id = ++this.last_node_id;
+ }
+ }
+
+ if (this._nodes.length >= LiteGraph.MAX_NUMBER_OF_NODES) {
+ throw "LiteGraph: max number of nodes in a graph reached";
+ }
+
+ //give him an id
+ if (LiteGraph.use_uuids) {
+ if (node.id == null || node.id == -1)
+ node.id = LiteGraph.uuidv4();
+
+ let uuid = localStorage.getItem('nexus-socket-uuid');
+ if (uuid) {
+ node.properties.owner = uuid
+ }
+ this.last_node_id = node.id;
+ }
+ else {
+ if (node.id == null || node.id == -1) {
+ node.id = ++this.last_node_id;
+ } else if (this.last_node_id < node.id) {
+ this.last_node_id = node.id;
+ }
+ }
+
+ node.graph = this;
+ this._version++;
+
+ this._nodes.push(node);
+ this._nodes_by_id[node.id] = node;
+
+ if (node.onAdded) {
+ node.onAdded(this);
+ }
+
+ if (this.config.align_to_grid) {
+ node.alignToGrid();
+ }
+
+ if (!skip_compute_order) {
+ this.updateExecutionOrder();
+ }
+
+ if (this.onNodeAdded) {
+ this.onNodeAdded(node);
+ }
+
+ this.setDirtyCanvas(true);
+ this.change();
+
+ return node; //to chain actions
+};
+
+LGraph.prototype.addNexus = function (node, skip_compute_order) {
+
+ if (!node) {
+ return;
+ }
+
+ //groups
+ if (node.constructor === LGraphGroup) {
+ this._groups.push(node);
+ this.setDirtyCanvas(true);
+ this.change();
+ // node.id = LiteGraph.uuidv4();
+ node.graph = this;
+ this._version++;
+ return;
+ }
+
+ // //nodes
+ // if (node.id != -1 && this._nodes_by_id[node.id] != null) {
+ // console.warn(
+ // "Nexus LiteGraph: there is already a node with this ID, will reuse it"
+ // );
+ // if (LiteGraph.use_uuids) {
+ // node.id = LiteGraph.uuidv4();
+ // }
+ // else {
+ // node.id = ++this.last_node_id;
+ // }
+ // }
+
+ if (this._nodes.length >= LiteGraph.MAX_NUMBER_OF_NODES) {
+ throw "LiteGraph: max number of nodes in a graph reached";
+ }
+
+ //give him an id
+ // if (LiteGraph.use_uuids) {
+ // if (node.id == null || node.id == -1)
+ // let uuid = localStorage.getItem('nexus-socket-uuid');
+ // if (uuid) {
+ // node.properties.owner = uuid
+ // }
+ // node.id = LiteGraph.uuidv4();
+ // }
+ // else {
+ // if (node.id == null || node.id == -1) {
+ // node.id = ++this.last_node_id;
+ // } else if (this.last_node_id < node.id) {
+ this.last_node_id = node.id;
+ // }
+ // }
+
+ node.graph = this;
+ this._version++;
+
+ this._nodes.push(node);
+ this._nodes_by_id[node.id] = node;
+
+ if (this.config.align_to_grid) {
+ node.alignToGrid();
+ }
+
+ if (!skip_compute_order) {
+ this.updateExecutionOrder();
+ }
+
+ if (this.onNodeAddedNexus) {
+ this.onNodeAddedNexus(node);
+ }
+
+ this.setDirtyCanvas(true);
+ this.change();
+
+ return node; //to chain actions
+};
+
+LGraph.prototype.remove = function (node) {
+ if (node.constructor === LiteGraph.LGraphGroup) {
+ var index = this._groups.indexOf(node);
+ if (index != -1) {
+ this._groups.splice(index, 1);
+ }
+ node.graph = null;
+ this._version++;
+ this.setDirtyCanvas(true, true);
+ this.change();
+ if (this.onGroupRemoved)
+ this.onGroupRemoved(node)
+ return;
+ }
+
+ if (this._nodes_by_id[node.id] == null) {
+ return;
+ } //not found
+
+ if (node.ignore_remove) {
+ return;
+ } //cannot be removed
+
+ this.beforeChange(); //sure? - almost sure is wrong
+
+ //disconnect inputs
+ if (node.inputs) {
+ for (var i = 0; i < node.inputs.length; i++) {
+ var slot = node.inputs[i];
+ if (slot.link != null) {
+ node.disconnectInput(i);
+ }
+ }
+ }
+
+ //disconnect outputs
+ if (node.outputs) {
+ for (var i = 0; i < node.outputs.length; i++) {
+ var slot = node.outputs[i];
+ if (slot.links != null && slot.links.length) {
+ node.disconnectOutput(i);
+ }
+ }
+ }
+
+ //node.id = -1; //why?
+
+ //callback
+ if (node.onRemoved) {
+ node.onRemoved();
+ }
+
+ node.graph = null;
+ this._version++;
+
+ //remove from canvas render
+ if (this.list_of_graphcanvas) {
+ for (var i = 0; i < this.list_of_graphcanvas.length; ++i) {
+ var canvas = this.list_of_graphcanvas[i];
+ if (canvas.selected_nodes[node.id]) {
+ delete canvas.selected_nodes[node.id];
+ }
+ if (canvas.node_dragged == node) {
+ canvas.node_dragged = null;
+ }
+ }
+ }
+
+ //remove from containers
+ var pos = this._nodes.indexOf(node);
+ if (pos != -1) {
+ this._nodes.splice(pos, 1);
+ }
+ delete this._nodes_by_id[node.id];
+
+ if (this.onNodeRemoved) {
+ this.onNodeRemoved(node);
+ }
+
+ //close panels
+ this.sendActionToCanvas("checkPanels");
+
+ this.setDirtyCanvas(true, true);
+ this.afterChange(); //sure? - almost sure is wrong
+ this.change();
+
+ this.updateExecutionOrder();
+};
+
+LGraph.prototype.removeNexus = function (node) {
+
+ if (node.constructor === LiteGraph.LGraphGroup) {
+ var index = this._groups.indexOf(node);
+ if (index != -1) {
+ this._groups.splice(index, 1);
+ }
+ node.graph = null;
+ this._version++;
+ this.setDirtyCanvas(true, true);
+ this.change();
+ return;
+ }
+
+ if (this._nodes_by_id[node.id] == null) {
+ return;
+ } //not found
+
+ if (node.ignore_remove) {
+ return;
+ } //cannot be removed
+
+ this.beforeChange(); //sure? - almost sure is wrong
+
+ //disconnect inputs
+ if (node.inputs) {
+ for (var i = 0; i < node.inputs.length; i++) {
+ var slot = node.inputs[i];
+ if (slot.link != null) {
+ node.disconnectInput(i);
+ }
+ }
+ }
+
+ //disconnect outputs
+ if (node.outputs) {
+ for (var i = 0; i < node.outputs.length; i++) {
+ var slot = node.outputs[i];
+ if (slot.links != null && slot.links.length) {
+ node.disconnectOutput(i);
+ }
+ }
+ }
+
+ //node.id = -1; //why?
+
+ //callback
+ if (node.onRemoved) {
+ node.onRemoved();
+ }
+
+ node.graph = null;
+ this._version++;
+
+ //remove from canvas render
+ if (this.list_of_graphcanvas) {
+ for (var i = 0; i < this.list_of_graphcanvas.length; ++i) {
+ var canvas = this.list_of_graphcanvas[i];
+ if (canvas.selected_nodes[node.id]) {
+ delete canvas.selected_nodes[node.id];
+ }
+ if (canvas.node_dragged == node) {
+ canvas.node_dragged = null;
+ }
+ }
+ }
+
+ //remove from containers
+ var pos = this._nodes.indexOf(node);
+ if (pos != -1) {
+ this._nodes.splice(pos, 1);
+ }
+ delete this._nodes_by_id[node.id];
+
+ //close panels
+ this.sendActionToCanvas("checkPanels");
+
+ this.setDirtyCanvas(true, true);
+ this.afterChange(); //sure? - almost sure is wrong
+ this.change();
+
+ this.updateExecutionOrder();
+};
+
+LGraphNode.prototype.configureNexus = function (info) {
+ if (this.graph) {
+ this.graph._version++;
+ }
+ for (var j in info) {
+ if (j == "properties") {
+ //i don't want to clone properties, I want to reuse the old container
+ for (var k in info.properties) {
+ this.properties[k] = info.properties[k];
+ }
+ continue;
+ }
+
+ if (info[j] == null) {
+ continue;
+ } else if (typeof info[j] == "object") {
+ //object
+ if (this[j] && this[j].configure) {
+ this[j].configure(info[j]);
+ } else {
+ this[j] = LiteGraph.cloneObject(info[j], this[j]);
+ }
+ } //value
+ else {
+ this[j] = info[j];
+ }
+ }
+
+ if (!info.title) {
+ this.title = this.constructor.title;
+ }
+
+ if (this.inputs) {
+ for (var i = 0; i < this.inputs.length; ++i) {
+ var input = this.inputs[i];
+ if (this.onInputAdded)
+ this.onInputAdded(input);
+ }
+ }
+
+ if (this.outputs) {
+ for (var i = 0; i < this.outputs.length; ++i) {
+ var output = this.outputs[i];
+ if (!output.links) {
+ continue;
+ }
+ if (this.onOutputAdded)
+ this.onOutputAdded(output);
+ }
+ }
+
+ if (this.widgets) {
+ for (var i = 0; i < this.widgets.length; ++i) {
+ var w = this.widgets[i];
+ if (!w)
+ continue;
+ if (w.options && w.options.property && (this.properties[w.options.property] != undefined))
+ w.value = JSON.parse(JSON.stringify(this.properties[w.options.property]));
+ }
+ if (info.widgets_values) {
+ for (var i = 0; i < info.widgets_values.length; ++i) {
+ if (this.widgets[i]) {
+ this.widgets[i].value = info.widgets_values[i];
+ }
+ }
+ }
+ }
+
+ if (this.onConfigure) {
+ this.onConfigure(info);
+ }
+};
+
+LGraphCanvas.prototype.selectNodesNexus = function (nodes, add_to_current_selection) {
+ if (!add_to_current_selection) {
+ this.deselectAllNodes();
+ }
+
+ nodes = nodes || this.graph._nodes;
+ if (typeof nodes == "string") nodes = [nodes];
+ for (var i in nodes) {
+ var node = nodes[i];
+ if (node.is_selected) {
+ this.deselectNode(node);
+ continue;
+ }
+
+ if (!node.is_selected && node.onSelected) {
+ node.onSelected();
+ }
+ node.is_selected = true;
+ this.selected_nodes[node.id] = node;
+
+ if (node.inputs) {
+ for (var j = 0; j < node.inputs.length; ++j) {
+ this.highlighted_links[node.inputs[j].link] = true;
+ }
+ }
+ if (node.outputs) {
+ for (var j = 0; j < node.outputs.length; ++j) {
+ var out = node.outputs[j];
+ if (out.links) {
+ for (var k = 0; k < out.links.length; ++k) {
+ this.highlighted_links[out.links[k]] = true;
+ }
+ }
+ }
+ }
+ }
+
+ this.setDirty(true);
+};
+
+LGraphCanvas.prototype.deselectNodeNexus = function(node) {
+
+ if (!node.is_selected) {
+ return;
+ }
+
+ if (node.onDeselected) {
+ node.onDeselected();
+ }
+
+ node.is_selected = false;
+
+ //remove highlighted
+ if (node.inputs) {
+ for (var i = 0; i < node.inputs.length; ++i) {
+ delete this.highlighted_links[node.inputs[i].link];
+ }
+ }
+ if (node.outputs) {
+ for (var i = 0; i < node.outputs.length; ++i) {
+ var out = node.outputs[i];
+ if (out.links) {
+ for (var j = 0; j < out.links.length; ++j) {
+ delete this.highlighted_links[out.links[j]];
+ }
+ }
+ }
+ }
+};
+
+LGraphGroup.prototype.move = function (deltax, deltay, ignore_nodes) {
+ this._pos[0] += deltax;
+ this._pos[1] += deltay;
+ if (ignore_nodes) {
+ return;
+ }
+ for (var i = 0; i < this._nodes.length; ++i) {
+ var node = this._nodes[i];
+ node.pos[0] += deltax;
+ node.pos[1] += deltay;
+ if (this.onGroupNodeMoved)
+ this.onGroupNodeMoved(node)
+ }
+ if (this.onGroupMoved)
+ this.onGroupMoved(this)
+};
+
+LGraphGroup.prototype.configure = function (o) {
+ this.title = o.title;
+ this.owner = o.owner;
+ this.uuid = o.uuid;
+ this._bounding.set(o.bounding);
+ this.color = o.color;
+ if (o.font_size) {
+ this.font_size = o.font_size;
+ }
+};
+
+LGraphGroup.prototype.serialize = function () {
+ var b = this._bounding;
+ return {
+ title: this.title,
+ bounding: [
+ Math.round(b[0]),
+ Math.round(b[1]),
+ Math.round(b[2]),
+ Math.round(b[3])
+ ],
+ uuid: this.uuid,
+ color: this.color,
+ font_size: this.font_size
+ };
+};
+
+LGraph.prototype.addNode = function (nodeData) {
+ let node = LiteGraph.createNode(nodeData.type, nodeData.title);
+ if (!node) {
+ console.warn("Node type not found: " + nodeData.type);
+ return false;
+ }
+ node.id = nodeData.id;
+ node.configureNexus(nodeData);
+ this.addNexus(node);
+ return true;
+};
+
+LGraph.prototype.removeNode = function (data) {
+ let node = this.getNodeById(data.id);
+ if (!node) {
+ console.warn("Node ID not found: " + data);
+ return false;
+ }
+ this.removeNexus(node);
+ return true;
+};
+
+LGraph.prototype.addLink = function (linkData) {
+ let link = new LiteGraph.LLink();
+ link.configure(linkData);
+ this.links[link.id] = link;
+ return;
+};
+
+LGraph.prototype.addGroup = function (data) {
+ var group = new LiteGraph.LGraphGroup();
+ group.configure(data);
+ this._groups.push(group);
+ this.setDirtyCanvas(true);
+ this.change();
+ group.graph = this;
+ this._version++;
+ if (this.onGroupAdded)
+ this.onGroupAdded(group)
+ return;
+};
+
+LGraph.prototype.removeGroup = function (group) {
+ let existingGroupIndex = this.getGroupIndexByUUID(group.uuid);
+ if (existingGroupIndex != -1) {
+ this._groups.splice(existingGroupIndex, 1);
+ }
+ group.graph = null;
+ this._version++;
+ this.setDirtyCanvas(true, true);
+ this.change();
+ return;
+};
+
+LGraph.prototype.updateNode = function (nodeData, type = "all") {
+ let existingNode = this.getNodeById(nodeData.id);
+ if (existingNode) {
+ if (type == "all") {
+ existingNode.configureNexus(nodeData);
+ } else if (type == "widgets") {
+ if (existingNode.widgets) {
+ for (var i = 0; i < existingNode.widgets.length; ++i) {
+ var w = existingNode.widgets[i];
+ if (!w)
+ continue;
+ if (w.options && w.options.property && (existingNode.properties[w.options.property] != undefined))
+ w.value = JSON.parse(JSON.stringify(existingNode.properties[w.options.property]));
+ }
+ if (nodeData.widgets_values) {
+ for (var i = 0; i < nodeData.widgets_values.length; ++i) {
+ if (existingNode.widgets[i]) {
+ existingNode.widgets[i].value = nodeData.widgets_values[i];
+ }
+ }
+ }
+ }
+ }
+ } else {
+ this.addNode(nodeData);
+ }
+};
+
+LGraph.prototype.updateLink = function (linkData) {
+
+ if (linkData) {
+
+ let id
+
+ if (linkData instanceof Object) {
+ id = linkData.id
+ } else {
+ id = linkData[0]
+ }
+
+ let existingLink = this.links[id];
+ if (existingLink) {
+ existingLink.configure(linkData);
+ } else {
+ this.addLink(linkData);
+ }
+
+ }
+
+};
+
+LGraph.prototype.updateLinkManual = function (change, linkData, node, graph) {
+ if (change == "add") {
+ this.updateLink(linkData)
+ } else if (change == "remove") {
+ let existingLink = this.links[linkData[0]];
+ if (existingLink) {
+ this.removeLink(linkData[0])
+ delete this.links[linkData[0]]
+ }
+ }
+ if (node) {
+ this.updateNode(node)
+ }
+ graph.updateExecutionOrder();
+ graph._version++;
+ graph.setDirtyCanvas(true, true);
+};
+
+LGraph.prototype.updateGroup = function (groupData) {
+ let existingGroup = this.getGroupByUUID(groupData.uuid);
+ if (existingGroup) {
+ existingGroup.configure(groupData);
+ } else {
+ this.addGroup(groupData);
+ }
+};
+
+LGraph.prototype.updateNodes = function (newNodes) {
+ newNodes.forEach(nodeData => this.updateNode(nodeData));
+};
+
+LGraph.prototype.updateLinks = function (newLinks) {
+ newLinks.forEach(linkData => this.updateLink(linkData));
+};
+
+LGraph.prototype.updateGroups = function (newGroups) {
+ newGroups.forEach(groupData => this.updateGroup(groupData));
+};
\ No newline at end of file
diff --git a/web/lib/nexus.mouse.js b/web/lib/nexus.mouse.js
new file mode 100644
index 0000000..148747d
--- /dev/null
+++ b/web/lib/nexus.mouse.js
@@ -0,0 +1,190 @@
+export class NexusMouseManager {
+
+ hexColors = ["#1abc9c","#2ecc71","#3498db","#9b59b6","#f1c40f","#e67e22","#e74c3c","#ecf0f1"]
+
+ createMouseSvg(color) {
+ return ``
+ }
+
+ svgStringToImage(svgString) {
+ const blob = new Blob([svgString], { type: 'image/svg+xml' });
+ const url = URL.createObjectURL(blob);
+ const image = new Image();
+ image.src = url;
+ return image;
+ }
+
+ hex2rgb(hex) {
+ const r = parseInt(hex.slice(1, 3), 16);
+ const g = parseInt(hex.slice(3, 5), 16);
+ const b = parseInt(hex.slice(5, 7), 16);
+ return { r, g, b };
+ }
+
+ getRandomHexColor() {
+ return this.hexColors[Math.floor(Math.random() * this.hexColors.length)];
+ }
+
+ constructor(nexusSocket, app, fps) {
+
+ this.color = this.getRandomHexColor()
+
+ this.app = app
+ this.fps = fps
+
+ this.canSendMouse = false
+
+ this.nexusSocket = nexusSocket
+ this.nexusSocket.onConnected.push(this.handleConnect.bind(this))
+ this.nexusSocket.onMessageReceive.push(this.handleMessageReceive.bind(this))
+
+ document.addEventListener("visibilitychange", this.handleVisiblityChange.bind(this))
+ document.addEventListener('mousemove', this.handleSendMouseControl.bind(this));
+ this.app.graph.list_of_graphcanvas[0].onDrawForeground = this.handlePointerDraw.bind(this)
+
+ setInterval(this.handleSendMouse.bind(this), 1000 / this.fps)
+
+ }
+
+ throttle(func, limit) {
+ let lastFunc;
+ let lastRan;
+ return function (...args) {
+ const context = this;
+ if (!lastRan) {
+ func.apply(context, args);
+ lastRan = Date.now();
+ } else {
+ clearTimeout(lastFunc);
+ lastFunc = setTimeout(function () {
+ if ((Date.now() - lastRan) >= limit) {
+ func.apply(context, args);
+ lastRan = Date.now();
+ }
+ }, limit - (Date.now() - lastRan));
+ }
+ };
+ }
+
+ handleCanvasDraw() {
+ if (this.app.graph && this.app.graph.list_of_graphcanvas && this.app.graph.list_of_graphcanvas.length > 0) {
+ this.app.graph.list_of_graphcanvas[0].draw(true, true);
+ }
+ }
+
+ handleConnect() {
+ this.drawTimer = setInterval(this.handleCanvasDraw.bind(this), 1000 / this.fps)
+ }
+
+ handleMessageReceive(message) {
+
+ let name = message.name;
+ let from = message.from;
+ let data = message.data
+
+ if (name == "mouse") {
+ let mouse = this.nexusSocket.userManager.get(from, "mouse");
+ let pointer = this.nexusSocket.userManager.get(from, "pointer");
+ if (!pointer) {
+ this.nexusSocket.userManager.set(from, "pointer", this.svgStringToImage(this.createMouseSvg(data.mouse[3])));
+ } else if (pointer && mouse && mouse[3] != data.mouse[3]) {
+ this.nexusSocket.userManager.set(from, "pointer", this.svgStringToImage(this.createMouseSvg(data.mouse[3])));
+ }
+ this.nexusSocket.userManager.set(from, "mouse", data.mouse)
+ } else if (name == "hide_mouse") {
+ this.nexusSocket.userManager.set(from, "mouse", null)
+ } else if (name== "mouse_view_toggle") {
+ this.nexusSocket.userManager.set(from, "mouse_view", data.on)
+ if(data.on == 0){
+ this.nexusSocket.sendMessage("hide_mouse", { receiver: from }, false)
+ }
+ }
+
+ }
+
+ handleVisiblityChange() {
+ if (document.visibilityState === "hidden") {
+ clearInterval(this.drawTimer)
+ this.nexusSocket.sendMessage("hide_mouse", {}, false)
+ this.nexusSocket.sendMessage("afk", {}, false)
+ } else {
+ this.handleConnect()
+ this.nexusSocket.sendMessage("online", {}, false)
+ }
+ }
+
+ handleSendMouse = function () {
+ if (this.app.graph && this.app.graph.list_of_graphcanvas && this.app.graph.list_of_graphcanvas.length > 0) {
+ this.mouse = this.app.graph.list_of_graphcanvas[0].canvas_mouse
+ this.scale = this.app.graph.list_of_graphcanvas[0].ds.scale
+ }
+ this.mouse[3] = this.color
+ this.mouse[4] = this.scale / 10
+
+ if (this.canSendMouse) {
+ let users = Object.keys(this.nexusSocket.userManager.users)
+ for (let index = 0; index < users.length; index++) {
+ let user_id = users[index]
+ let view = this.nexusSocket.userManager.get(user_id, "mouse_view");
+ if(view != null) {
+ if(view == 1) {
+ this.nexusSocket.sendMessage('mouse', { mouse: this.mouse, receiver: user_id }, false)
+ }
+ }
+ else {
+ this.nexusSocket.sendMessage('mouse', { mouse: this.mouse, receiver: user_id }, false)
+ }
+ }
+ this.canSendMouse = false
+ }
+
+ }
+
+ handleSendMouseControl() {
+ this.canSendMouse = true
+ }
+
+ handlePointerDraw = function (ctx) {
+ Object.keys(this.nexusSocket.userManager.users).forEach(id => {
+ if (id != this.nexusSocket.uuid) {
+
+ let mouse = this.nexusSocket.userManager.get(id, "mouse");
+ let name = this.nexusSocket.userManager.get(id, "name") || "User";
+ let pointerImage = this.nexusSocket.userManager.get(id, "pointer");
+ let hidden = this.nexusSocket.userManager.get(id, "mouse_hidden");
+
+ if(hidden == 0) {
+ mouse = null
+ }
+
+ if (mouse && mouse.length > 0) {
+
+ let mouseX = mouse[0] - 4;
+ let mouseY = mouse[1] - 4;
+ let scale = 1 - mouse[4];
+
+ ctx.drawImage(pointerImage, mouseX, mouseY, 18, 18);
+ ctx.font = '14px Arial';
+ ctx.fillStyle = mouse[3];
+ ctx.fillText(name, mouseX + 22, mouseY + 16);
+
+ let color = this.hex2rgb(mouse[3])
+
+ ctx.beginPath();
+ ctx.arc(mouseX, mouseY, scale * 600, 0, 2 * Math.PI, false);
+ ctx.strokeStyle = `rgba(${color.r},${color.g},${color.b},0.1)`;
+ ctx.lineWidth = 2;
+ ctx.stroke();
+
+ }
+ }
+
+ });
+ }
+}
\ No newline at end of file
diff --git a/web/lib/nexus.panel.js b/web/lib/nexus.panel.js
new file mode 100644
index 0000000..41f433d
--- /dev/null
+++ b/web/lib/nexus.panel.js
@@ -0,0 +1,258 @@
+import { DynamicElement } from "./nexus.elements.js"
+import { StyleInjector } from "./nexus.styles.js";
+
+export class UserPanel {
+
+ constructor() {
+
+ this.active = 0
+
+ this.panel = new DynamicElement({
+ id: 'nexus-player-panel',
+ tag: 'div',
+ visible: false,
+ styles: {
+ "width": "100%",
+ "height": "100vh",
+ "display": "flex",
+ "align-items": "center",
+ "justify-content": "center",
+ "box-sizing": "border-box",
+ "position": "absolute",
+ "left": "0",
+ "top": "0",
+ "z-index": "10000"
+ },
+ childrens: [
+ new DynamicElement({
+ id: 'nexus-player-panel-table',
+ tag: 'div',
+ visible: true,
+ styles: {
+ "width": "832px",
+ "display": "flex",
+ "flex-direction": "column",
+ "border-radius": "6px",
+ "box-sizing": "border-box"
+ },
+ childrens: [
+ new DynamicElement({
+ id: 'nexus-player-panel-table-body',
+ tag: 'div',
+ visible: true,
+ styles: {
+ "width": "100%",
+ "display": "flex",
+ "flex-direction": "column",
+ "align-items": "center",
+ "justify-content": "flex-start",
+ "color": "#a3a3a3",
+ "font-family": "Calibri",
+ "font-weight": "600",
+ "gap": "12px",
+ "padding-bottom": "12px",
+ "box-sizing": "border-box",
+ "background": "rgba(54,54,54,0.8)",
+ "border-radius": "6px",
+ "box-shadow": "4px 8px 8px rgba(0,0,0,0.5)",
+ "max-height": "540px",
+ "overflowY": "auto"
+ }
+ }),
+ new StyleInjector({
+ styles: {
+ "#nexus-player-panel .table-row": {
+ "width": "100%",
+ "display": "grid",
+ "align-items": "center",
+ "grid-template-columns": "20% 60% 20%",
+ "justify-content": "space-between",
+ },
+ "#nexus-player-panel .table-row:first-child": {
+ "width": "100%",
+ "color": "#a3a3a3",
+ "padding": "16px 0",
+ "background": "rgb(0,0,0,0.6)",
+ "border-top-left-radius": "6px",
+ "border-top-right-radius": "6px",
+ "position": "sticky",
+ "top": "0",
+ },
+ "#nexus-player-panel .table-row .table-row-child": {
+ "display": "flex",
+ "justify-content": "flex-start",
+ "gap": "12px",
+ "padding-left": "12px",
+ "width": "100%"
+ },
+ "#nexus-player-panel .table-row-child button": {
+ "border": "none",
+ "padding": "6px 12px",
+ "border-radius": "3px",
+ "cursor": "pointer",
+ "background": "rgba(94,94,94)",
+ "color": "#a1a1a1",
+ },
+ "#nexus-player-panel .table-row-child button:active": {
+ "opacity": "0.8"
+ },
+ "#nexus-player-panel .table-row-child button[active='1'][label='spectate']": {
+ "background": "#218c74",
+ "color": "#fafafa"
+ },
+ "#nexus-player-panel .table-row-child button[active='1'][label='editor']": {
+ "background": "#474787",
+ "color": "#fafafa"
+ },
+ "#nexus-player-panel .table-row-child button[active='1'][label='mouse']": {
+ "background": "#cd6133",
+ "color": "#fafafa"
+ },
+ "#nexus-player-panel .table-row-child button[active='1'][label='queue']": {
+ "background": "#6F1E51",
+ "color": "#fafafa"
+ }
+ }
+ })
+ ]
+ })
+ ],
+ })
+ this.onCommandReceived = null
+ this.buttons = []
+ this.columns = []
+ }
+
+ addColumnsToPanel(columns) {
+
+ this.columns = columns
+
+ }
+
+ addRowsToPanel(rows, callbacks, values, data) {
+
+ let tbody = this.panel.childrens[0].childrens[0];
+ tbody.removeChildrens();
+ this.buttons = []
+
+ const tr = document.createElement('div');
+ tr.className = "table-row";
+
+ this.columns.forEach(heading => {
+ const th = document.createElement('span');
+ th.className = "table-row-child"
+ th.textContent = heading;
+ tr.appendChild(th)
+ });
+
+ tbody.addChild(tr)
+
+ rows.forEach((row, i) => {
+ const tr = document.createElement('div');
+ tr.className = "table-row";
+
+ row.forEach((cell, j) => {
+
+ let td = document.createElement('span');
+ td.className = "table-row-child"
+
+ let hascomma = cell.indexOf(",") > -1
+ let hascolon = cell.indexOf(":") > -1
+
+ let content = []
+
+ if (hascomma) {
+ content = cell.split(',')
+ content.forEach((elm, k) => {
+ let elmcontent = elm.split(':')
+ this.addChildToParent(elmcontent, td, callbacks, values, data, i, j, k)
+ })
+ }
+ else if (hascolon) {
+ content = cell.split(':')
+ this.addChildToParent(content, td, callbacks, values, data, i, j, -1)
+ } else {
+ td.textContent = cell
+ }
+ tr.appendChild(td);
+
+ });
+ tbody.addChild(tr)
+ });
+ }
+
+ addChildToParent(content, td, callbacks, values, data, i, j, k) {
+ //console.log(content, td, callbacks, values, data, i, j, k)
+ switch (content[0]) {
+ case 'button':
+ let btn = document.createElement('button');
+ if (content[1] == 'none') {
+ btn.className = "none"
+ }
+ if (!this.buttons.includes(btn)) {
+ this.buttons.push(btn)
+ }
+ btn.textContent = content[1]
+ if (callbacks[i] && callbacks[i][j] && values[i][j] && data[i][j]) {
+ let inner_values = values[i][j].split(',')
+ let inner_data = data[i][j].split(',')
+ if (k != -1) {
+ btn.setAttribute('data', inner_data[k]);
+ btn.setAttribute('active', inner_values[k]);
+ } else {
+ btn.setAttribute('data', data[i][j]);
+ btn.setAttribute('active', values[i][j]);
+ }
+ btn.setAttribute('kind', content[0]);
+ btn.setAttribute('label', content[1]);
+ if (content[1] != 'none') {
+ btn.onclick = callbacks[i][j].bind(this);
+ }
+ }
+ td.appendChild(btn)
+ break;
+ }
+
+ }
+
+ disableButtons(label) {
+ this.buttons.forEach(btn => {
+ let btn_kind = btn.getAttribute('kind')
+ let btn_data = btn.getAttribute('data')
+ let btn_active = parseInt(btn.getAttribute('active'))
+ let btn_label = btn.getAttribute('label')
+ if (btn_label == label) {
+ if (btn_active == 1) {
+ btn.setAttribute('active', 0)
+ //console.log(btn_kind, btn_label, btn_data, 0)
+ if (this.onCommandReceived) {
+ this.onCommandReceived(btn_kind, btn_label, btn_data, 0)
+ }
+ }
+ }
+ })
+ }
+
+ onclick(e) {
+
+ let kind = e.target.getAttribute('kind')
+ let data = e.target.getAttribute('data')
+ let label = e.target.getAttribute('label')
+ let active = parseInt(e.target.getAttribute('active'))
+ if (kind == "button") {
+ if (label == "spectate") {
+ this.disableButtons(label)
+ }
+ if (active == 0) {
+ e.target.setAttribute('active', 1)
+ } else if (active == 1) {
+ e.target.setAttribute('active', 0)
+ }
+ }
+ active = parseInt(e.target.getAttribute('active'))
+ //console.log(kind, label, data, active)
+ if (this.onCommandReceived) {
+ this.onCommandReceived(kind, label, data, active)
+ }
+ }
+}
\ No newline at end of file
diff --git a/web/lib/nexus.sockets.js b/web/lib/nexus.sockets.js
new file mode 100644
index 0000000..a2271a9
--- /dev/null
+++ b/web/lib/nexus.sockets.js
@@ -0,0 +1,135 @@
+import { UserManager } from "./nexus.user.manager.js";
+
+export class NexusSocket extends EventTarget {
+
+ getName() {
+ let name = localStorage.getItem('nexus-socket-name');
+ if (!name) {
+ localStorage.setItem('nexus-socket-name', "User");
+ name = "User"
+ }
+ this.userManager.set(this.uuid, "name", name)
+ return name
+ }
+
+ getUUID() {
+ let uuid = localStorage.getItem('nexus-socket-uuid');
+ if (!uuid) {
+ uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c =>
+ (Math.random() * 16 | 0).toString(16)
+ );
+ localStorage.setItem('nexus-socket-uuid', uuid);
+ }
+ return uuid;
+ }
+
+ constructor() {
+
+ super();
+ this.ws = null;
+ this.userManager = new UserManager();
+ this.uuid = this.getUUID();
+ this.admin = false
+ this.wsUrl = `ws${window.location.protocol === "https:" ? "s" : ""}://${location.host}/nexus?id=${this.uuid}`;
+
+ this.onConnected = [this.onConnectedHandler.bind(this)];
+ this.onDisconnected = [];
+ this.onMessageReceive = [this.onMessageReceiveHandler.bind(this)];
+ this.onMessageSend = [];
+ this.onError = [];
+ this.onClose = [];
+
+ this.getName()
+
+ }
+
+ onConnectedHandler() {
+ this.userManager.create(this.uuid);
+ this.sendMessage('join', { name: this.getName() }, false);
+ this.sendMessage("online", {}, false)
+ }
+
+ onMessageReceiveHandler(message) {
+ let name = message.name;
+ let from = message.from;
+ switch (name) {
+ case "join":
+ this.userManager.create(from);
+ this.sendMessage('hi', {}, false);
+ break;
+ case "hi":
+ this.userManager.create(from);
+ break;
+ }
+ }
+
+ connect() {
+ try {
+ this.ws = new WebSocket(this.wsUrl);
+ this.ws.onopen = () => {
+ if (this.onConnected) {
+ this.onConnected.forEach(f => { f(); });
+ }
+ };
+ this.ws.onmessage = (msg) => {
+ try {
+ const message = JSON.parse(msg.data);
+ if (this.onMessageReceive) {
+ this.onMessageReceive.forEach(f => { f(message); });
+ }
+ } catch (e) {
+ console.error('Error handling message:', e);
+ }
+ };
+ this.ws.onerror = (error) => {
+ if (this.onError) {
+ this.onError.forEach(f => { f(error); });
+ }
+ console.error('WebSocket error:', error);
+ };
+ this.ws.onclose = () => {
+ if (this.onDisconnected) {
+ this.onDisconnected.forEach(f => { f(); });
+ }
+ setTimeout(() => {
+ this.connect();
+ }, 5000);
+ };
+ } catch (e) {
+ console.error('Error connecting to WebSocket:', e);
+ }
+ }
+
+ closeWebSocket() {
+ if (this.ws && this.ws.readyState === WebSocket.OPEN) {
+ this.ws.close();
+ } else {
+ console.log('WebSocket is not open.');
+ }
+ }
+
+ sendMessage(event_name, event_data, all = true) {
+ try {
+ if (this.ws && this.ws.readyState === WebSocket.OPEN) {
+ let data = {}
+ if(event_data.receiver) {
+ data.receiver = event_data.receiver
+ delete event_data.receiver
+ } else if (event_data.exclude) {
+ data.exclude = event_data.exclude
+ delete event_data.exclude
+ }
+ data.from = this.uuid
+ data.name = event_name
+ data.data = event_data
+ data.all = all
+ this.ws.send(JSON.stringify(data));
+ if (this.onMessageSend) {
+ this.onMessageSend.forEach(f => { f(message); });
+ }
+ }
+ } catch (e) {
+ console.error(`Error sending ${event_name} message:`, e);
+ }
+ }
+}
diff --git a/web/lib/nexus.styles.js b/web/lib/nexus.styles.js
new file mode 100644
index 0000000..a46bc3b
--- /dev/null
+++ b/web/lib/nexus.styles.js
@@ -0,0 +1,37 @@
+export class StyleInjector {
+
+ constructor({ styles }) {
+ this.el = null
+ this.styles = styles;
+ this.create()
+ }
+
+ create() {
+ this.el = document.createElement('style');
+ let cssString = "";
+ for (const selector in this.styles) {
+ cssString += `${selector} { `;
+ for (const property in this.styles[selector]) {
+ cssString += `${property}: ${this.styles[selector][property]}; `;
+ }
+ cssString += `} `;
+ }
+ this.el.textContent = cssString;
+ }
+
+ softUpdate(selector, property, value) {
+ let cssString = "";
+ for (const key in this.styles) {
+ cssString += `${key} { `;
+ for (const property in this.styles[key]) {
+ cssString += `${property}: ${this.styles[key][property]}; `;
+ }
+ if(key == selector) {
+ cssString += `${property}: ${value}; `;
+ }
+ cssString += `} `;
+ }
+ this.el.textContent = cssString;
+ }
+
+}
\ No newline at end of file
diff --git a/web/lib/nexus.user.manager.js b/web/lib/nexus.user.manager.js
new file mode 100644
index 0000000..263e8cb
--- /dev/null
+++ b/web/lib/nexus.user.manager.js
@@ -0,0 +1,27 @@
+export class UserManager {
+ constructor() {
+ this.users = {}
+ }
+
+ create(id) {
+ if (!this.users[id]) {
+ this.users[id] = {}
+ }
+ }
+
+ set(id, key, data) {
+ this.create(id)
+ this.users[id][key] = data
+ }
+
+ get(id, key) {
+ this.create(id)
+ return this.users[id][key]
+ }
+
+ remove(id) {
+ if(this.users[id])
+ delete this.users[id];
+ return true
+ }
+}
diff --git a/web/lib/nexus.workflow.js b/web/lib/nexus.workflow.js
new file mode 100644
index 0000000..9c0827f
--- /dev/null
+++ b/web/lib/nexus.workflow.js
@@ -0,0 +1,306 @@
+import "./nexus.litegraph.js"
+import { postWorkflow, postBackupWorkflow, getLatestWorkflow } from "./nexus.api.js";
+
+export class NexusWorkflowManager {
+
+ constructor(app, api, nexusSocket) {
+
+ this.app = app;
+ this.api = api;
+
+ this.nexusSocket = nexusSocket;
+ this.nexusSocket.onMessageReceive.push(this.handleMessageReceive.bind(this))
+
+ this.init()
+
+ this.spectators = []
+ this.saveTime = 0
+ setInterval(this.handleSpectators.bind(this), 1000 / 24) // 24 fps
+
+ }
+
+ async init() {
+ let workflow = (await this.app.graphToPrompt()).workflow;
+ await postBackupWorkflow(this.nexusSocket.uuid, workflow)
+ this.app.graph.clear()
+ }
+
+ removeSaveManager() {
+ if (this.saveTimer) {
+ clearInterval(this.saveTimer)
+ }
+ // document.removeEventListener("visibilitychange", this.handleSaveWorkflowManager.bind(this))
+ }
+
+ async saveManager(time) {
+ if (this.nexusSocket.admin) {
+ this.nexusSocket.sendMessage('workflow:timer', { time: time - 2 }, false)
+ }
+ this.removeSaveManager()
+ this.saveTime = time
+ this.saveTimer = setInterval(this.sendWorkFlow.bind(this), time * 1000)
+ console.log("Auto Backup Timer Setup:",time)
+ // document.addEventListener("visibilitychange", this.handleSaveWorkflowManager.bind(this))
+ }
+
+ async sendWorkFlow() {
+ let workflow = (await this.app.graphToPrompt()).workflow;
+ await postWorkflow(this.nexusSocket.uuid, workflow)
+ }
+
+ async loadLatestWorkflow() {
+ let workflow = await getLatestWorkflow()
+ let links = workflow.links || [];
+ let nodes = workflow.nodes || [];
+ let groups = workflow.groups || [];
+ this.app.graph.updateNodes(nodes);
+ this.app.graph.updateLinks(links);
+ this.app.graph.updateGroups(groups);
+ this.app.graph.updateExecutionOrder();
+ this.app.graph.setDirtyCanvas(true, true);
+ }
+
+ async handleSaveWorkflowManager() {
+ this.sendWorkFlow()
+ // if (document.visibilityState === "hidden") {
+ // if (this.saveTimer) {
+ // clearInterval(this.saveTimer)
+ // }
+ // }
+ // else {
+ // this.saveTimer = setInterval(this.sendWorkFlow.bind(this), this.saveTime * 1000)
+ // }
+ }
+
+ async handleSpectators() {
+ if (this.spectators.length > 0) {
+ let workflow = (await this.app.graphToPrompt()).workflow;
+ let wfe = workflow.extra
+ for (let index = 0; index < this.spectators.length; index++) {
+ const id = this.spectators[index];
+ this.nexusSocket.sendMessage('workflow:spectate', { workflow: wfe, receiver: id }, false)
+ }
+ }
+ }
+
+ async handleMessageReceive(message) {
+
+ let from = message.from;
+ let data = message.data
+ let name = message.name;
+
+ switch (name) {
+ case "spectate":
+ if (data.on == 0) {
+ if (this.spectators.includes(from)) {
+ this.spectators.splice(this.spectators.indexOf(from), 1)
+ }
+ } else if (data.on == 1) {
+ if (!this.spectators.includes(from)) {
+ this.spectators.push(from)
+ }
+ }
+ case "join":
+ // this.sendWorkFlow()
+ this.saveManager(this.saveTime || 60)
+ break;
+ case "workflow:spectate":
+ let wfe = data.workflow
+ if (wfe?.ds) {
+ this.app.canvas.ds.offset = wfe.ds.offset;
+ this.app.canvas.ds.scale = wfe.ds.scale;
+ }
+ break;
+ case "workflow:timer":
+ let time = data.time
+ this.saveManager(time)
+ break;
+ case "workflow:update":
+ this.handleWorkflowUpdate(from, data)
+ break;
+ case "workflow":
+ let workflow = data.workflow
+ if (workflow.clear) {
+ await this.app.graph.clear()
+ }
+ let links = workflow.links || [];
+ let nodes = workflow.nodes || [];
+ let groups = workflow.groups || [];
+ this.app.graph.updateNodes(nodes);
+ this.app.graph.updateLinks(links);
+ this.app.graph.updateGroups(groups);
+ this.app.graph.updateExecutionOrder();
+ this.app.graph.setDirtyCanvas(true, true);
+ }
+
+ }
+
+ handleWorkflowEvents(oN) {
+ if (oN) {
+ this.app.graph.onNodeAdded = this.onNodeAdded.bind(this);
+ this.app.graph.onNodeAddedNexus = this.onNodeAddedNexus.bind(this);
+ this.app.graph.onNodeRemoved = this.onNodeRemoved.bind(this);
+ this.app.canvas.onSelectionChange = this.onSelectionChange.bind(this);
+ this.app.canvas.onNodeDeselected = this.onNodeDeselected.bind(this);
+ this.app.canvas.onNodeMoved = this.onNodeMoved.bind(this);
+ this.app.graph.onGroupAdded = this.onGroupAdded.bind(this);
+ this.app.graph.onGroupRemoved = this.onGroupRemoved.bind(this);
+ this.app.graph._groups.forEach(group => {
+ group.onGroupMoved = this.onGroupMoved.bind(this, group);
+ group.onGroupNodeMoved = this.onNodeMoved.bind(this);
+ });
+ this.app.graph._nodes.forEach(node => {
+ node.onWidgetChanged = this.onWidgetChanged.bind(this, node);
+ node.onConnectionsChange = this.onNodeConnectionChange.bind(this, node);
+ });
+ }
+ else {
+ this.app.graph.onNodeAddedNexus = null;
+ this.app.graph.onNodeAdded = null;
+ this.app.graph.onNodeRemoved = null;
+ this.app.canvas.onSelectionChange = null;
+ this.app.canvas.onNodeDeselected = null;
+ this.app.canvas.onNodeMoved = null;
+ this.app.graph.onGroupAdded = null;
+ this.app.graph.onGroupRemoved = null;
+ this.app.graph._groups.forEach(group => {
+ group.onGroupMoved = null;
+ group.onGroupNodeMoved = null;
+ });
+ this.app.graph._nodes.forEach(node => {
+ node.onWidgetChanged = null;
+ node.onConnectionsChange = null;
+ });
+ }
+ }
+
+ handleWorkflowUpdate(from, detail) {
+
+ let graph = this.app.graph;
+ let canvas = this.app.canvas;
+ let update = detail.update;
+ let data = detail.data;
+
+ let existingNode;
+
+ switch (update) {
+ case "node-added":
+ graph.updateNode(data);
+ break;
+ case "node-removed":
+ graph.removeNode(data);
+ break;
+ case "node-moved":
+ graph.updateNode(data);
+ break;
+ case "node-widget-changed":
+ graph.updateNode(data);
+ break;
+ case "node-selection":
+ let nodes = []
+ data.forEach(node => {
+ existingNode = graph.getNodeById(node.id);
+ if (existingNode) {
+ nodes.push(existingNode)
+ }
+ })
+ canvas.selectNodesNexus(nodes, true)
+ break;
+ case "node-deselection":
+ existingNode = graph.getNodeById(data.id);
+ if (existingNode)
+ canvas.deselectNodeNexus(existingNode)
+ break;
+ case "group-added":
+ graph.updateGroup(data);
+ break;
+ case "group-moved":
+ graph.updateGroup(data);
+ break;
+ case "group-removed":
+ graph.removeGroup(data);
+ break;
+ case "node-connection":
+ graph.updateLinkManual(detail.change, detail.link, detail.node, graph);
+ break;
+ default:
+ //console.log(update);
+ break;
+ }
+
+ }
+
+ onNodeAddedNexus(node) {
+ node.onWidgetChanged = this.onWidgetChanged.bind(this, node);
+ node.onConnectionsChange = this.onNodeConnectionChange.bind(this, node);
+ }
+
+ onNodeAdded(node) {
+ let data = node.serialize();
+ node.onWidgetChanged = this.onWidgetChanged.bind(this, node);
+ node.onConnectionsChange = this.onNodeConnectionChange.bind(this, node);
+ this.nexusSocket.sendMessage('workflow:update', { update: "node-added", data }, false);
+ }
+
+ onWidgetChanged(node) {
+ let data = node.serialize();
+ this.nexusSocket.sendMessage('workflow:update', { update: "node-widget-changed", data }, false);
+ }
+
+ onNodeRemoved(node) {
+ let data = node.serialize();
+ this.nexusSocket.sendMessage('workflow:update', { update: "node-removed", data }, false);
+ }
+
+ async onNodeConnectionChange(node, a, b, c, d, e) {
+ let change = c ? "add" : "remove";
+ let link = d;
+ this.graphData = (await this.app.graphToPrompt()).workflow;
+ let data = this.graphData.links;
+ //console.log(data)
+ node = this.app.graph.getNodeById(node.id);
+ node = node ? node.serialize() : null;
+ this.nexusSocket.sendMessage('workflow:update', { node, update: "node-connection", link, change, data }, false);
+ }
+
+ onSelectionChange(nodes) {
+ let data = []
+ Object.keys(nodes).forEach(node_id => {
+ data.push(nodes[node_id].serialize())
+ })
+ this.nexusSocket.sendMessage('workflow:update', { update: "node-selection", data }, false);
+ }
+
+ onNodeDeselected(node) {
+ let data = node.serialize()
+ this.nexusSocket.sendMessage('workflow:update', { update: "node-deselection", data }, false);
+ }
+
+ onNodeMoved(node) {
+ let data = node.serialize();
+ this.nexusSocket.sendMessage('workflow:update', { update: "node-moved", data }, false);
+ }
+
+ onGroupAdded(group) {
+ let data = group.serialize();
+ let uuid = localStorage.getItem('nexus-socket-uuid');
+ if (uuid) {
+ data.owner = uuid
+ group.configure(data);
+ }
+ group.onGroupMoved = this.onGroupMoved.bind(this, group);
+ group.onGroupNodeMoved = this.onNodeMoved.bind(this);
+ this.nexusSocket.sendMessage('workflow:update', { update: "group-added", data }, false);
+ }
+
+ onGroupRemoved(group) {
+ let data = group.serialize();
+ this.nexusSocket.sendMessage('workflow:update', { update: "group-removed", data }, false);
+ }
+
+ onGroupMoved(group) {
+ let data = group.serialize();
+ this.nexusSocket.sendMessage('workflow:update', { update: "group-moved", data }, false);
+ }
+
+}
diff --git a/web/lib/nexus.workflow.panel.js b/web/lib/nexus.workflow.panel.js
new file mode 100644
index 0000000..b8a4f71
--- /dev/null
+++ b/web/lib/nexus.workflow.panel.js
@@ -0,0 +1,236 @@
+import { DynamicElement } from "./nexus.elements.js"
+import { StyleInjector } from "./nexus.styles.js";
+
+export class WorkflowPanel {
+
+ constructor() {
+
+ this.active = 0
+
+ this.panel = new DynamicElement({
+ id: 'nexus-workflow-panel',
+ tag: 'div',
+ visible: false,
+ styles: {
+ "width": "100%",
+ "height": "100vh",
+ "display": "flex",
+ "align-items": "center",
+ "justify-content": "center",
+ "box-sizing": "border-box",
+ "position": "absolute",
+ "left": "0",
+ "top": "0",
+ "z-index": "10000"
+ },
+ childrens: [
+ new DynamicElement({
+ id: 'nexus-workflow-panel-table',
+ tag: 'div',
+ visible: true,
+ styles: {
+ "width": "512px",
+ "display": "flex",
+ "flex-direction": "column",
+ "border-radius": "6px",
+ "box-sizing": "border-box"
+ },
+ childrens: [
+ new DynamicElement({
+ id: 'nexus-workflow-panel-table-body',
+ tag: 'div',
+ visible: true,
+ styles: {
+ "width": "100%",
+ "display": "flex",
+ "flex-direction": "column",
+ "align-items": "center",
+ "justify-content": "flex-start",
+ "color": "#a3a3a3",
+ "font-family": "Calibri",
+ "font-weight": "600",
+ "gap": "12px",
+ "padding-bottom": "12px",
+ "box-sizing": "border-box",
+ "background": "rgba(54,54,54,0.8)",
+ "border-radius": "6px",
+ "box-shadow": "4px 8px 8px rgba(0,0,0,0.5)",
+ "max-height": "540px",
+ "overflowY": "auto"
+ }
+ }),
+ new StyleInjector({
+ styles: {
+ "#nexus-workflow-panel .table-row": {
+ "width": "100%",
+ "display": "flex",
+ "align-items": "center",
+ "justify-content": "center",
+ },
+ "#nexus-workflow-panel .table-row:first-child": {
+ "width": "100%",
+ "color": "#a3a3a3",
+ "padding": "16px 0",
+ "background": "rgb(0,0,0,0.6)",
+ "border-top-left-radius": "6px",
+ "border-top-right-radius": "6px",
+ "position": "sticky",
+ "top": "0",
+ },
+ "#nexus-workflow-panel .table-row .table-row-child": {
+ "display": "flex",
+ "justify-content": "flex-start",
+ "gap": "12px",
+ "padding-left": "12px",
+ "width": "100%"
+ },
+ "#nexus-workflow-panel .table-row-child button": {
+ "border": "none",
+ "padding": "6px 12px",
+ "border-radius": "3px",
+ "cursor": "pointer",
+ "background": "rgba(94,94,94)",
+ "color": "#a1a1a1",
+ "width": "100%"
+ },
+ "#nexus-workflow-panel .table-row-child:first-child": {
+ "width":"50%"
+ },
+ "#nexus-workflow-panel .table-row-child:last-child": {
+ "padding-right":"12px"
+ },
+ "#nexus-workflow-panel .table-row-child button:active": {
+ "opacity": "0.8"
+ },
+ "#nexus-workflow-panel .table-row-child button[label='load']:active": {
+ "background": "#218c74",
+ "color": "#fafafa"
+ },
+ "#nexus-workflow-panel .table-row-child button[label='loadforall']:active": {
+ "background": "#474787",
+ "color": "#fafafa"
+ }
+ }
+ })
+ ]
+ })
+ ],
+ })
+ this.onCommandReceived = null
+ this.buttons = []
+ this.columns = []
+ }
+
+ addColumnsToPanel(columns) {
+
+ this.columns = columns
+
+ }
+
+ addRowsToPanel(rows, callbacks, values, data) {
+
+ let tbody = this.panel.childrens[0].childrens[0];
+ tbody.removeChildrens();
+ this.buttons = []
+
+ const tr = document.createElement('div');
+ tr.className = "table-row";
+
+ this.columns.forEach(heading => {
+ const th = document.createElement('span');
+ th.className = "table-row-child"
+ th.textContent = heading;
+ tr.appendChild(th)
+ });
+
+ tbody.addChild(tr)
+
+ rows.forEach((row, i) => {
+ const tr = document.createElement('div');
+ tr.className = "table-row";
+
+ row.forEach((cell, j) => {
+
+ let td = document.createElement('span');
+ td.className = "table-row-child"
+
+ let hascomma = cell.indexOf(",") > -1
+ let hascolon = cell.indexOf(":") > -1
+
+ let content = []
+
+ if (hascomma) {
+ content = cell.split(',')
+ content.forEach((elm, k) => {
+ let elmcontent = elm.split(':')
+ this.addChildToParent(elmcontent, td, callbacks, values, data, i, j, k)
+ })
+ }
+ else if (hascolon) {
+ content = cell.split(':')
+ this.addChildToParent(content, td, callbacks, values, data, i, j, -1)
+ } else {
+ cell = cell.split("|")
+ cell = cell.join(":")
+ td.textContent = cell
+ }
+ tr.appendChild(td);
+
+ });
+ tbody.addChild(tr)
+ });
+ }
+
+ addChildToParent(content, td, callbacks, values, data, i, j, k) {
+ switch (content[0]) {
+ case 'button':
+ let btn = document.createElement('button');
+ if (content[1] == 'none') {
+ btn.className = "none"
+ }
+ if (!this.buttons.includes(btn)) {
+ this.buttons.push(btn)
+ }
+ btn.textContent = content[1]
+ if (callbacks[i] && callbacks[i][j] && values[i][j] && data[i][j]) {
+ let inner_values = values[i][j].split(',')
+ let inner_data = data[i][j].split(',')
+ if (k != -1) {
+ btn.setAttribute('data', inner_data[k]);
+ btn.setAttribute('active', inner_values[k]);
+ } else {
+ btn.setAttribute('data', data[i][j]);
+ btn.setAttribute('active', values[i][j]);
+ }
+ btn.setAttribute('kind', content[0]);
+ btn.setAttribute('label', content[1]);
+ if (content[1] != 'none') {
+ btn.onclick = callbacks[i][j].bind(this);
+ }
+ }
+ td.appendChild(btn)
+ break;
+ }
+
+ }
+
+ onclick(e) {
+
+ let kind = e.target.getAttribute('kind')
+ let data = e.target.getAttribute('data')
+ let label = e.target.getAttribute('label')
+ let active = parseInt(e.target.getAttribute('active'))
+ if (kind == "button") {
+ if (active == 0) {
+ e.target.setAttribute('active', 1)
+ } else if (active == 1) {
+ e.target.setAttribute('active', 0)
+ }
+ }
+ active = parseInt(e.target.getAttribute('active'))
+ //console.log(kind, label, data, active)
+ if (this.onCommandReceived) {
+ this.onCommandReceived(kind, label, data, active)
+ }
+ }
+}
diff --git a/web/util.js b/web/util.js
index 919bf83..15edb9e 100644
--- a/web/util.js
+++ b/web/util.js
@@ -1,11 +1,25 @@
-(()=>{var M=class{constructor(){this.users={}}create(e){this.users[e]||(this.users[e]={})}set(e,t,s){this.create(e),this.users[e][t]=s}get(e,t){return this.create(e),this.users[e][t]}remove(e){return this.users[e]&&delete this.users[e],!0}};var N=class extends EventTarget{getName(){let e=localStorage.getItem("nexus-socket-name");return e||(localStorage.setItem("nexus-socket-name","User"),e="User"),this.userManager.set(this.uuid,"name",e),e}getUUID(){let e=localStorage.getItem("nexus-socket-uuid");return e||(e="xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,t=>(Math.random()*16|0).toString(16)),localStorage.setItem("nexus-socket-uuid",e)),e}constructor(){super(),this.ws=null,this.userManager=new M,this.uuid=this.getUUID(),this.admin=!1,this.wsUrl=`ws${window.location.protocol==="https:"?"s":""}://${location.host}/nexus?id=${this.uuid}`,this.onConnected=[this.onConnectedHandler.bind(this)],this.onDisconnected=[],this.onMessageReceive=[this.onMessageReceiveHandler.bind(this)],this.onMessageSend=[],this.onError=[],this.onClose=[],this.getName()}onConnectedHandler(){this.userManager.create(this.uuid),this.sendMessage("join",{name:this.getName()},!1),this.sendMessage("online",{},!1)}onMessageReceiveHandler(e){let t=e.name,s=e.from;switch(t){case"join":this.userManager.create(s),this.sendMessage("hi",{},!1);break;case"hi":this.userManager.create(s);break}}connect(){try{this.ws=new WebSocket(this.wsUrl),this.ws.onopen=()=>{this.onConnected&&this.onConnected.forEach(e=>{e()})},this.ws.onmessage=e=>{try{let t=JSON.parse(e.data);this.onMessageReceive&&this.onMessageReceive.forEach(s=>{s(t)})}catch(t){console.error("Error handling message:",t)}},this.ws.onerror=e=>{this.onError&&this.onError.forEach(t=>{t(e)}),console.error("WebSocket error:",e)},this.ws.onclose=()=>{this.onDisconnected&&this.onDisconnected.forEach(e=>{e()}),setTimeout(()=>{this.connect()},5e3)}}catch(e){console.error("Error connecting to WebSocket:",e)}}closeWebSocket(){this.ws&&this.ws.readyState===WebSocket.OPEN?this.ws.close():console.log("WebSocket is not open.")}sendMessage(e,t,s=!0){try{if(this.ws&&this.ws.readyState===WebSocket.OPEN){let n={};t.receiver?(n.receiver=t.receiver,delete t.receiver):t.exclude&&(n.exclude=t.exclude,delete t.exclude),n.from=this.uuid,n.name=e,n.data=t,n.all=s,this.ws.send(JSON.stringify(n)),this.onMessageSend&&this.onMessageSend.forEach(o=>{o(message)})}}catch(n){console.error(`Error sending ${e} message:`,n)}}};var T=class{hexColors=["#1abc9c","#2ecc71","#3498db","#9b59b6","#f1c40f","#e67e22","#e74c3c","#ecf0f1"];createMouseSvg(e){return``}svgStringToImage(e){let t=new Blob([e],{type:"image/svg+xml"}),s=URL.createObjectURL(t),n=new Image;return n.src=s,n}hex2rgb(e){let t=parseInt(e.slice(1,3),16),s=parseInt(e.slice(3,5),16),n=parseInt(e.slice(5,7),16);return{r:t,g:s,b:n}}getRandomHexColor(){return this.hexColors[Math.floor(Math.random()*this.hexColors.length)]}constructor(e,t,s){this.color=this.getRandomHexColor(),this.app=t,this.fps=s,this.canSendMouse=!1,this.nexusSocket=e,this.nexusSocket.onConnected.push(this.handleConnect.bind(this)),this.nexusSocket.onMessageReceive.push(this.handleMessageReceive.bind(this)),document.addEventListener("visibilitychange",this.handleVisiblityChange.bind(this)),document.addEventListener("mousemove",this.handleSendMouseControl.bind(this)),this.app.graph.list_of_graphcanvas[0].onDrawForeground=this.handlePointerDraw.bind(this),setInterval(this.handleSendMouse.bind(this),1e3/this.fps)}throttle(e,t){let s,n;return function(...o){let a=this;n?(clearTimeout(s),s=setTimeout(function(){Date.now()-n>=t&&(e.apply(a,o),n=Date.now())},t-(Date.now()-n))):(e.apply(a,o),n=Date.now())}}handleCanvasDraw(){this.app.graph&&this.app.graph.list_of_graphcanvas&&this.app.graph.list_of_graphcanvas.length>0&&this.app.graph.list_of_graphcanvas[0].draw(!0,!0)}handleConnect(){this.drawTimer=setInterval(this.handleCanvasDraw.bind(this),1e3/this.fps)}handleMessageReceive(e){let t=e.name,s=e.from,n=e.data;if(t=="mouse"){let o=this.nexusSocket.userManager.get(s,"mouse"),a=this.nexusSocket.userManager.get(s,"pointer");a?a&&o&&o[3]!=n.mouse[3]&&this.nexusSocket.userManager.set(s,"pointer",this.svgStringToImage(this.createMouseSvg(n.mouse[3]))):this.nexusSocket.userManager.set(s,"pointer",this.svgStringToImage(this.createMouseSvg(n.mouse[3]))),this.nexusSocket.userManager.set(s,"mouse",n.mouse)}else t=="hide_mouse"?this.nexusSocket.userManager.set(s,"mouse",null):t=="mouse_view_toggle"&&(this.nexusSocket.userManager.set(s,"mouse_view",n.on),n.on==0&&this.nexusSocket.sendMessage("hide_mouse",{receiver:s},!1))}handleVisiblityChange(){document.visibilityState==="hidden"?(clearInterval(this.drawTimer),this.nexusSocket.sendMessage("hide_mouse",{},!1),this.nexusSocket.sendMessage("afk",{},!1)):(this.handleConnect(),this.nexusSocket.sendMessage("online",{},!1))}handleSendMouse=function(){if(this.app.graph&&this.app.graph.list_of_graphcanvas&&this.app.graph.list_of_graphcanvas.length>0&&(this.mouse=this.app.graph.list_of_graphcanvas[0].canvas_mouse,this.scale=this.app.graph.list_of_graphcanvas[0].ds.scale),this.mouse[3]=this.color,this.mouse[4]=this.scale/10,this.canSendMouse){let e=Object.keys(this.nexusSocket.userManager.users);for(let t=0;t{if(t!=this.nexusSocket.uuid){let s=this.nexusSocket.userManager.get(t,"mouse"),n=this.nexusSocket.userManager.get(t,"name")||"User",o=this.nexusSocket.userManager.get(t,"pointer");if(this.nexusSocket.userManager.get(t,"mouse_hidden")==0&&(s=null),s&&s.length>0){let r=s[0]-4,h=s[1]-4,l=1-s[4];e.drawImage(o,r,h,18,18),e.font="14px Arial",e.fillStyle=s[3],e.fillText(n,r+22,h+16);let d=this.hex2rgb(s[3]);e.beginPath(),e.arc(r,h,l*600,0,2*Math.PI,!1),e.strokeStyle=`rgba(${d.r},${d.g},${d.b},0.1)`,e.lineWidth=2,e.stroke()}}})}};LiteGraph.use_uuids=!0;LiteGraph.uuidv4=function(){return Date.now()};LGraph.prototype.getGroupByUUID=function(i){let e=this._groups,t=null;for(let s=0;s=LiteGraph.MAX_NUMBER_OF_NODES)throw"LiteGraph: max number of nodes in a graph reached";if(LiteGraph.use_uuids){(i.id==null||i.id==-1)&&(i.id=LiteGraph.uuidv4());let t=localStorage.getItem("nexus-socket-uuid");t&&(i.properties.owner=t),this.last_node_id=i.id}else i.id==null||i.id==-1?i.id=++this.last_node_id:this.last_node_id=LiteGraph.MAX_NUMBER_OF_NODES)throw"LiteGraph: max number of nodes in a graph reached";return this.last_node_id=i.id,i.graph=this,this._version++,this._nodes.push(i),this._nodes_by_id[i.id]=i,this.config.align_to_grid&&i.alignToGrid(),e||this.updateExecutionOrder(),this.onNodeAddedNexus&&this.onNodeAddedNexus(i),this.setDirtyCanvas(!0),this.change(),i}};LGraph.prototype.remove=function(i){if(i.constructor===LiteGraph.LGraphGroup){var e=this._groups.indexOf(i);e!=-1&&this._groups.splice(e,1),i.graph=null,this._version++,this.setDirtyCanvas(!0,!0),this.change(),this.onGroupRemoved&&this.onGroupRemoved(i);return}if(this._nodes_by_id[i.id]!=null&&!i.ignore_remove){if(this.beforeChange(),i.inputs)for(var t=0;tthis.updateNode(e))};LGraph.prototype.updateLinks=function(i){i.forEach(e=>this.updateLink(e))};LGraph.prototype.updateGroups=function(i){i.forEach(e=>this.updateGroup(e))};var y="nexus";async function x(i,e={}){try{let t=await fetch(i,e),s=await t.json();if(!t.ok)throw new Error(s.error||"Something went wrong");return s}catch(t){return console.error(`Fetch error: ${t.message}`),{error:t.message}}}async function F(i,e){let t=`${y}/workflows/${e}`,s={method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({user_id:i})};return await x(t,s)}async function U(i){let e=`${y}/workflows/meta`,t={method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({user_id:i})};return await x(e,t)}async function D(i,e){let t=`${y}/workflow`,s={method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({user_id:i,...e})};return await x(t,s)}async function H(i,e){let t=`${y}/workflow/backup`,s={method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({user_id:i,...e})};return await x(t,s)}async function q(){let i=`${y}/workflow`;return await x(i)}async function O(i){let e=`${y}/permission/${i}`;return await x(e)}async function A(i,e,t,s){let n=`${y}/permission/${i}`,o={method:"POST",headers:{"Content-Type":"application/json",Authorization:s},body:JSON.stringify({admin_id:e,data:t})};return await x(n,o)}async function B(i,e,t){let s=`${y}/login`,n={method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({uuid:i,account:e,password:t})};return await x(s,n)}async function j(i){let e=`${y}/verify`,t={method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({token:i})};return await x(e,t)}var L=class{constructor(e,t,s){this.app=e,this.api=t,this.nexusSocket=s,this.nexusSocket.onMessageReceive.push(this.handleMessageReceive.bind(this)),this.init(),this.spectators=[],this.saveTime=0,setInterval(this.handleSpectators.bind(this),1e3/24)}async init(){let e=(await this.app.graphToPrompt()).workflow;await H(this.nexusSocket.uuid,e),this.app.graph.clear()}removeSaveManager(){this.saveTimer&&clearInterval(this.saveTimer)}async saveManager(e){this.nexusSocket.admin&&this.nexusSocket.sendMessage("workflow:timer",{time:e-2},!1),this.removeSaveManager(),this.saveTime=e,this.saveTimer=setInterval(this.sendWorkFlow.bind(this),e*1e3),console.log("Auto Backup Timer Setup:",e)}async sendWorkFlow(){let e=(await this.app.graphToPrompt()).workflow;await D(this.nexusSocket.uuid,e)}async loadLatestWorkflow(){let e=await q(),t=e.links||[],s=e.nodes||[],n=e.groups||[];this.app.graph.updateNodes(s),this.app.graph.updateLinks(t),this.app.graph.updateGroups(n),this.app.graph.updateExecutionOrder(),this.app.graph.setDirtyCanvas(!0,!0)}async handleSaveWorkflowManager(){this.sendWorkFlow()}async handleSpectators(){if(this.spectators.length>0){let t=(await this.app.graphToPrompt()).workflow.extra;for(let s=0;s{t.onGroupMoved=this.onGroupMoved.bind(this,t),t.onGroupNodeMoved=this.onNodeMoved.bind(this)}),this.app.graph._nodes.forEach(t=>{t.onWidgetChanged=this.onWidgetChanged.bind(this,t),t.onConnectionsChange=this.onNodeConnectionChange.bind(this,t)})):(this.app.graph.onNodeAddedNexus=null,this.app.graph.onNodeAdded=null,this.app.graph.onNodeRemoved=null,this.app.canvas.onSelectionChange=null,this.app.canvas.onNodeDeselected=null,this.app.canvas.onNodeMoved=null,this.app.graph.onGroupAdded=null,this.app.graph.onGroupRemoved=null,this.app.graph._groups.forEach(t=>{t.onGroupMoved=null,t.onGroupNodeMoved=null}),this.app.graph._nodes.forEach(t=>{t.onWidgetChanged=null,t.onConnectionsChange=null}))}handleWorkflowUpdate(e,t){let s=this.app.graph,n=this.app.canvas,o=t.update,a=t.data,r;switch(o){case"node-added":s.updateNode(a);break;case"node-removed":s.removeNode(a);break;case"node-moved":s.updateNode(a);break;case"node-widget-changed":s.updateNode(a);break;case"node-selection":let h=[];a.forEach(l=>{r=s.getNodeById(l.id),r&&h.push(r)}),n.selectNodesNexus(h,!0);break;case"node-deselection":r=s.getNodeById(a.id),r&&n.deselectNodeNexus(r);break;case"group-added":s.updateGroup(a);break;case"group-moved":s.updateGroup(a);break;case"group-removed":s.removeGroup(a);break;case"node-connection":s.updateLinkManual(t.change,t.link,t.node,s);break;default:break}}onNodeAddedNexus(e){e.onWidgetChanged=this.onWidgetChanged.bind(this,e),e.onConnectionsChange=this.onNodeConnectionChange.bind(this,e)}onNodeAdded(e){let t=e.serialize();e.onWidgetChanged=this.onWidgetChanged.bind(this,e),e.onConnectionsChange=this.onNodeConnectionChange.bind(this,e),this.nexusSocket.sendMessage("workflow:update",{update:"node-added",data:t},!1)}onWidgetChanged(e){let t=e.serialize();this.nexusSocket.sendMessage("workflow:update",{update:"node-widget-changed",data:t},!1)}onNodeRemoved(e){let t=e.serialize();this.nexusSocket.sendMessage("workflow:update",{update:"node-removed",data:t},!1)}async onNodeConnectionChange(e,t,s,n,o,a){let r=n?"add":"remove",h=o;this.graphData=(await this.app.graphToPrompt()).workflow;let l=this.graphData.links;e=this.app.graph.getNodeById(e.id),e=e?e.serialize():null,this.nexusSocket.sendMessage("workflow:update",{node:e,update:"node-connection",link:h,change:r,data:l},!1)}onSelectionChange(e){let t=[];Object.keys(e).forEach(s=>{t.push(e[s].serialize())}),this.nexusSocket.sendMessage("workflow:update",{update:"node-selection",data:t},!1)}onNodeDeselected(e){let t=e.serialize();this.nexusSocket.sendMessage("workflow:update",{update:"node-deselection",data:t},!1)}onNodeMoved(e){let t=e.serialize();this.nexusSocket.sendMessage("workflow:update",{update:"node-moved",data:t},!1)}onGroupAdded(e){let t=e.serialize(),s=localStorage.getItem("nexus-socket-uuid");s&&(t.owner=s,e.configure(t)),e.onGroupMoved=this.onGroupMoved.bind(this,e),e.onGroupNodeMoved=this.onNodeMoved.bind(this),this.nexusSocket.sendMessage("workflow:update",{update:"group-added",data:t},!1)}onGroupRemoved(e){let t=e.serialize();this.nexusSocket.sendMessage("workflow:update",{update:"group-removed",data:t},!1)}onGroupMoved(e){let t=e.serialize();this.nexusSocket.sendMessage("workflow:update",{update:"group-moved",data:t},!1)}};var G=class extends EventTarget{constructor(e,t,s){super(),this.app=t,this.api=e,this.nexusSocket=s,this.comfy_events=["b_preview","progress","executing","executed","execution_start","execution_error","execution_cached"];let n=e.queuePrompt;this.apiWrapper={oqp:n,enableApi:!1,api:e,qp:async function(o,a){return this.enableApi?this.oqp.call(this.api,o,{output:a.output,workflow:a.workflow}):{node_errors:{}}}},this.api.queuePrompt=this.apiWrapper.qp.bind(this.apiWrapper),this.nexusSocket.onMessageReceive.push(this.handleMessageReceive.bind(this)),this.addEventListeners(),this.comfy_events.forEach(o=>{this.api.addEventListener(o,this.handleComfyEvents.bind(this))}),this.handleProgressDraw(),this.handleControl(!1),this.handleEditor(!1)}handleProgressDraw(){let e=LGraphCanvas.prototype.drawNodeShape,t=this.app;LGraphCanvas.prototype.drawNodeShape=function(s,n,o,a,r,h,l){let d=e.apply(this,arguments),p=t.lastNodeErrors?.[s.id],u=null,m=1;if(s.id===t.runningNodeId?u="#0f0":t.dragOverNode&&s.id===t.dragOverNode.id?u="dodgerblue":p?.errors?(u="red",m=2):t.lastExecutionError&&t.lastExecutionError.node_id===s.id&&(u="#f0f",m=2),u){let f=s._shape||s.constructor.shape||LiteGraph.ROUND_SHAPE;n.lineWidth=m,n.globalAlpha=.8,n.beginPath(),f==LiteGraph.BOX_SHAPE?n.rect(-6,-6-LiteGraph.NODE_TITLE_HEIGHT,12+o[0]+1,12+o[1]+LiteGraph.NODE_TITLE_HEIGHT):f==LiteGraph.ROUND_SHAPE||f==LiteGraph.CARD_SHAPE&&s.flags.collapsed?n.roundRect(-6,-6-LiteGraph.NODE_TITLE_HEIGHT,12+o[0]+1,12+o[1]+LiteGraph.NODE_TITLE_HEIGHT,this.round_radius*2):f==LiteGraph.CARD_SHAPE?n.roundRect(-6,-6-LiteGraph.NODE_TITLE_HEIGHT,12+o[0]+1,12+o[1]+LiteGraph.NODE_TITLE_HEIGHT,[this.round_radius*2,this.round_radius*2,2,2]):f==LiteGraph.CIRCLE_SHAPE&&n.arc(o[0]*.5,o[1]*.5,o[0]*.5+6,0,Math.PI*2),n.strokeStyle=u,n.stroke(),n.strokeStyle=a,n.globalAlpha=1}if(t.progress&&s.id===t.runningNodeId&&(n.fillStyle="green",n.fillRect(0,0,o[0]*(t.progress.value/t.progress.max),6),n.fillStyle=r),p){n.lineWidth=2,n.strokeStyle="red";for(let f of p.errors)if(f.extra_info&&f.extra_info.input_name){let g=s.findInputSlot(f.extra_info.input_name);if(g!==-1){let b=s.getConnectionPos(!0,g);n.beginPath(),n.arc(b[0]-s.pos[0],b[1]-s.pos[1],12,0,2*Math.PI,!1),n.stroke()}}}return d}}handleMessageReceive(e){try{this.dispatchEvent(new CustomEvent(e.name,{detail:e}))}catch(t){console.error("Error handling message:",t)}}handleComfyEvents(e){e.type==="b_preview"?this.blobToBase64(e.detail).then(t=>{this.nexusSocket.sendMessage(e.type,{detail:t},!1)}):this.nexusSocket.sendMessage(e.type,{detail:e.detail},!1)}addEventListeners(){this.addEventListener("progress",({detail:e})=>{e=e.data.detail,this.app.progress=e,this.app.graph.setDirtyCanvas(!0,!1)}),this.addEventListener("executing",({detail:e})=>{e=e.data.detail,this.app.progress=null,this.app.runningNodeId=e,this.app.graph.setDirtyCanvas(!0,!1),delete this.app.nodePreviewImages[this.app.runningNodeId]}),this.addEventListener("executed",({detail:e})=>{e=e.data.detail;let t=this.app.nodeOutputs[e.node];if(e.merge&&t)for(let n in e.output??{}){let o=t[n];o instanceof Array?t[n]=o.concat(e.output[n]):t[n]=e.output[n]}else this.app.nodeOutputs[e.node]=e.output;let s=this.app.graph.getNodeById(e.node);s&&s.onExecuted&&s.onExecuted(e.output)}),this.addEventListener("execution_start",({detail:e})=>{e=e.data.detail,this.app.runningNodeId=null,this.app.lastExecutionError=null,this.app.graph._nodes.forEach(t=>{t.onExecutionStart&&t.onExecutionStart()})}),this.addEventListener("execution_error",({detail:e})=>{e=e.data.detail,this.app.lastExecutionError=e;let t=this.formatExecutionError(e);this.app.ui.dialog.show(t),this.app.canvas.draw(!0,!0)}),this.addEventListener("b_preview",({detail:e})=>{e=e.data.detail;let t=this.app.runningNodeId;if(t==null)return;let s=this.base64ToBlob(e),n=URL.createObjectURL(s);this.app.nodePreviewImages[t]=[n]})}blobToBase64(e){return new Promise((t,s)=>{let n=new FileReader;n.onloadend=()=>{t(n.result)},n.onerror=s,n.readAsDataURL(e)})}base64ToBlob(e){let[t,s]=e.split(","),n=t.match(/:(.*?);/)[1],o=atob(s),a=new Array(o.length);for(let h=0;h{e==="display"&&(this.defaultDisplay=t),this.el.style[e]=t})}removeStyles(){this.el&&(Object.keys(this.styles).forEach(e=>{this.el.style[e]=""}),this.styles={})}updateAttrs(e={}){this.attrs={...this.attrs,...e},this.addAttrs()}addAttrs(){this.el&&Object.entries(this.attrs).forEach(([e,t])=>{this.el.setAttribute(e,t)})}removeAttrs(){this.el&&(Object.keys(this.attrs).forEach(e=>{this.el.removeAttribute(e)}),this.attrs={})}addChildrens(){this.el&&(this.childrens.forEach(e=>{typeof e=="string"?this.el.insertAdjacentHTML("beforeend",e):e instanceof Node?this.el.appendChild(e):e instanceof v?this.el.appendChild(e.el):e instanceof i&&this.el.appendChild(e.el)}),this.afterChildrenAppend&&requestAnimationFrame(()=>this.afterChildrenAppend(this.childrens,this)))}addChild(e){this.el&&(typeof e=="string"?this.el.insertAdjacentHTML("beforeend",e):e instanceof Node?this.el.appendChild(e):e instanceof i&&this.el.appendChild(e.el),this.childrens.includes(e)||this.childrens.push(e),this.afterChildrenAppend&&this.afterChildrenAppend(this.childrens,this))}removeChildrens(){this.el&&(this.el.innerHTML="",this.childrens=[])}addEvents(){this.el&&Object.entries(this.events).forEach(([e,t])=>{this.el.addEventListener(e,s=>{t(s,this.parent)})})}removeEvents(){this.el&&(Object.entries(this.events).forEach(([e,t])=>{this.el.removeEventListener(e,t)}),this.events={})}show(){this.el&&(this.el.style.display=this.defaultDisplay,this.visible=!0)}hide(){this.el&&(this.el.style.display="none",this.visible=!1)}addStateStyles(){this.el&&(this.el.addEventListener("mouseenter",()=>this.applyStyles(this.hoverStyles)),this.el.addEventListener("mouseleave",()=>this.applyStyles(this.styles)),this.el.addEventListener("focus",()=>this.applyStyles(this.focusStyles),!0),this.el.addEventListener("blur",()=>this.applyStyles(this.styles),!0),this.el.addEventListener("mousedown",()=>this.applyStyles(this.activeStyles)),this.el.addEventListener("mouseup",()=>this.applyStyles(this.styles)))}applyStyles(e){this.visible&&Object.entries(e).forEach(([t,s])=>{this.el.style[t]=s})}};var z=new c({id:"nexus-chat-main",tag:"div",visible:!0,styles:{width:"512px",minHeight:"170px",maxHeight:"170px",overflow:"hidden",padding:"16px",borderRadius:"8px",border:"1px solid rgba(94,94,94,0.5)"},hoverStyles:{overflowY:"scroll"},afterChildrenAppend:function(i,e){i.length>50&&(i[0].removeElement(),i.shift()),e.el.scrollTop=e.el.scrollHeight}}),_=new c({id:"nexus-input-main",tag:"div",visible:!0,styles:{width:"100%",height:"32px",display:"flex",alignItems:"center"},childrens:[new c({id:"nexus-input-label",tag:"span",visible:!1,styles:{fontFamily:'Calibri, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif',display:"block",fontSize:"14px",lineHeight:"14px",fontWeight:600,color:"white",padding:"0",margin:"0",textShadow:"1pt 1pt #000"},childrens:["Say:"]}),new c({id:"nexus-input",tag:"input",visible:!1,styles:{fontFamily:'Calibri, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif',fontSize:"14px",lineHeight:"14px",fontWeight:600,width:"100%",color:"white",padding:"0",margin:"0",border:"none",background:"transparent",flexGrow:1,outline:"none",textIndent:"4pt",caretColor:"transparent",textShadow:"1pt 1pt #000"}})]}),C=new c({id:"nexus-chat-window",tag:"div",styles:{position:"absolute",left:"16px",top:"100px",display:"flex",flexDirection:"column",zIndex:1e3},childrens:[new c({id:"nexus-total-players",tag:"div",visible:!0,styles:{fontFamily:'Calibri, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif',fontSize:"14px",lineHeight:"14px",fontWeight:600,left:"16px",top:"80px",color:"#a5a5a5",width:"100%",paddingBottom:"12px",textAlign:"right"},childrens:["Users Online: ",new c({id:"nexus-total-players-count",visible:!0,tag:"span",childrens:["0"]})]}),z,_,new v({styles:{"#nexus-chat-main::-webkit-scrollbar":{display:"none","scrollbar-width":"none","-ms-overflow-style":"none"},"#nexus-chat-window":{}}})]}),w=function(i,e,t,s,n){let o="";n?o=`${i} ${e}`:o=`${i}: ${e}`;let a=new c({tag:"span",visible:!0,styles:{fontFamily:'Calibri, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif',display:"block",fontSize:"14px",lineHeight:"14px",fontWeight:600,width:"100%",color:"white",padding:"0",margin:"0",textShadow:"1pt 1pt #000",marginBottom:"1pt",lineBreak:"anywhere"},childrens:[o]});z.addChild(a)};var I=class{constructor(){this.active=0,this.panel=new c({id:"nexus-player-panel",tag:"div",visible:!1,styles:{width:"100%",height:"100vh",display:"flex","align-items":"center","justify-content":"center","box-sizing":"border-box",position:"absolute",left:"0",top:"0","z-index":"10000"},childrens:[new c({id:"nexus-player-panel-table",tag:"div",visible:!0,styles:{width:"832px",display:"flex","flex-direction":"column","border-radius":"6px","box-sizing":"border-box"},childrens:[new c({id:"nexus-player-panel-table-body",tag:"div",visible:!0,styles:{width:"100%",display:"flex","flex-direction":"column","align-items":"center","justify-content":"flex-start",color:"#a3a3a3","font-family":"Calibri","font-weight":"600",gap:"12px","padding-bottom":"12px","box-sizing":"border-box",background:"rgba(54,54,54,0.8)","border-radius":"6px","box-shadow":"4px 8px 8px rgba(0,0,0,0.5)","max-height":"540px",overflowY:"auto"}}),new v({styles:{"#nexus-player-panel .table-row":{width:"100%",display:"grid","align-items":"center","grid-template-columns":"20% 60% 20%","justify-content":"space-between"},"#nexus-player-panel .table-row:first-child":{width:"100%",color:"#a3a3a3",padding:"16px 0",background:"rgb(0,0,0,0.6)","border-top-left-radius":"6px","border-top-right-radius":"6px",position:"sticky",top:"0"},"#nexus-player-panel .table-row .table-row-child":{display:"flex","justify-content":"flex-start",gap:"12px","padding-left":"12px",width:"100%"},"#nexus-player-panel .table-row-child button":{border:"none",padding:"6px 12px","border-radius":"3px",cursor:"pointer",background:"rgba(94,94,94)",color:"#a1a1a1"},"#nexus-player-panel .table-row-child button:active":{opacity:"0.8"},"#nexus-player-panel .table-row-child button[active='1'][label='spectate']":{background:"#218c74",color:"#fafafa"},"#nexus-player-panel .table-row-child button[active='1'][label='editor']":{background:"#474787",color:"#fafafa"},"#nexus-player-panel .table-row-child button[active='1'][label='mouse']":{background:"#cd6133",color:"#fafafa"},"#nexus-player-panel .table-row-child button[active='1'][label='queue']":{background:"#6F1E51",color:"#fafafa"}}})]})]}),this.onCommandReceived=null,this.buttons=[],this.columns=[]}addColumnsToPanel(e){this.columns=e}addRowsToPanel(e,t,s,n){let o=this.panel.childrens[0].childrens[0];o.removeChildrens(),this.buttons=[];let a=document.createElement("div");a.className="table-row",this.columns.forEach(r=>{let h=document.createElement("span");h.className="table-row-child",h.textContent=r,a.appendChild(h)}),o.addChild(a),e.forEach((r,h)=>{let l=document.createElement("div");l.className="table-row",r.forEach((d,p)=>{let u=document.createElement("span");u.className="table-row-child";let m=d.indexOf(",")>-1,f=d.indexOf(":")>-1,g=[];m?(g=d.split(","),g.forEach((b,k)=>{let S=b.split(":");this.addChildToParent(S,u,t,s,n,h,p,k)})):f?(g=d.split(":"),this.addChildToParent(g,u,t,s,n,h,p,-1)):u.textContent=d,l.appendChild(u)}),o.addChild(l)})}addChildToParent(e,t,s,n,o,a,r,h){switch(e[0]){case"button":let l=document.createElement("button");if(e[1]=="none"&&(l.className="none"),this.buttons.includes(l)||this.buttons.push(l),l.textContent=e[1],s[a]&&s[a][r]&&n[a][r]&&o[a][r]){let d=n[a][r].split(","),p=o[a][r].split(",");h!=-1?(l.setAttribute("data",p[h]),l.setAttribute("active",d[h])):(l.setAttribute("data",o[a][r]),l.setAttribute("active",n[a][r])),l.setAttribute("kind",e[0]),l.setAttribute("label",e[1]),e[1]!="none"&&(l.onclick=s[a][r].bind(this))}t.appendChild(l);break}}disableButtons(e){this.buttons.forEach(t=>{let s=t.getAttribute("kind"),n=t.getAttribute("data"),o=parseInt(t.getAttribute("active")),a=t.getAttribute("label");a==e&&o==1&&(t.setAttribute("active",0),this.onCommandReceived&&this.onCommandReceived(s,a,n,0))})}onclick(e){let t=e.target.getAttribute("kind"),s=e.target.getAttribute("data"),n=e.target.getAttribute("label"),o=parseInt(e.target.getAttribute("active"));t=="button"&&(n=="spectate"&&this.disableButtons(n),o==0?e.target.setAttribute("active",1):o==1&&e.target.setAttribute("active",0)),o=parseInt(e.target.getAttribute("active")),this.onCommandReceived&&this.onCommandReceived(t,n,s,o)}};var P=class{constructor(){this.active=0,this.panel=new c({id:"nexus-workflow-panel",tag:"div",visible:!1,styles:{width:"100%",height:"100vh",display:"flex","align-items":"center","justify-content":"center","box-sizing":"border-box",position:"absolute",left:"0",top:"0","z-index":"10000"},childrens:[new c({id:"nexus-workflow-panel-table",tag:"div",visible:!0,styles:{width:"512px",display:"flex","flex-direction":"column","border-radius":"6px","box-sizing":"border-box"},childrens:[new c({id:"nexus-workflow-panel-table-body",tag:"div",visible:!0,styles:{width:"100%",display:"flex","flex-direction":"column","align-items":"center","justify-content":"flex-start",color:"#a3a3a3","font-family":"Calibri","font-weight":"600",gap:"12px","padding-bottom":"12px","box-sizing":"border-box",background:"rgba(54,54,54,0.8)","border-radius":"6px","box-shadow":"4px 8px 8px rgba(0,0,0,0.5)","max-height":"540px",overflowY:"auto"}}),new v({styles:{"#nexus-workflow-panel .table-row":{width:"100%",display:"flex","align-items":"center","justify-content":"center"},"#nexus-workflow-panel .table-row:first-child":{width:"100%",color:"#a3a3a3",padding:"16px 0",background:"rgb(0,0,0,0.6)","border-top-left-radius":"6px","border-top-right-radius":"6px",position:"sticky",top:"0"},"#nexus-workflow-panel .table-row .table-row-child":{display:"flex","justify-content":"flex-start",gap:"12px","padding-left":"12px",width:"100%"},"#nexus-workflow-panel .table-row-child button":{border:"none",padding:"6px 12px","border-radius":"3px",cursor:"pointer",background:"rgba(94,94,94)",color:"#a1a1a1",width:"100%"},"#nexus-workflow-panel .table-row-child:first-child":{width:"50%"},"#nexus-workflow-panel .table-row-child:last-child":{"padding-right":"12px"},"#nexus-workflow-panel .table-row-child button:active":{opacity:"0.8"},"#nexus-workflow-panel .table-row-child button[label='load']:active":{background:"#218c74",color:"#fafafa"},"#nexus-workflow-panel .table-row-child button[label='loadforall']:active":{background:"#474787",color:"#fafafa"}}})]})]}),this.onCommandReceived=null,this.buttons=[],this.columns=[]}addColumnsToPanel(e){this.columns=e}addRowsToPanel(e,t,s,n){let o=this.panel.childrens[0].childrens[0];o.removeChildrens(),this.buttons=[];let a=document.createElement("div");a.className="table-row",this.columns.forEach(r=>{let h=document.createElement("span");h.className="table-row-child",h.textContent=r,a.appendChild(h)}),o.addChild(a),e.forEach((r,h)=>{let l=document.createElement("div");l.className="table-row",r.forEach((d,p)=>{let u=document.createElement("span");u.className="table-row-child";let m=d.indexOf(",")>-1,f=d.indexOf(":")>-1,g=[];m?(g=d.split(","),g.forEach((b,k)=>{let S=b.split(":");this.addChildToParent(S,u,t,s,n,h,p,k)})):f?(g=d.split(":"),this.addChildToParent(g,u,t,s,n,h,p,-1)):(d=d.split("|"),d=d.join(":"),u.textContent=d),l.appendChild(u)}),o.addChild(l)})}addChildToParent(e,t,s,n,o,a,r,h){switch(e[0]){case"button":let l=document.createElement("button");if(e[1]=="none"&&(l.className="none"),this.buttons.includes(l)||this.buttons.push(l),l.textContent=e[1],s[a]&&s[a][r]&&n[a][r]&&o[a][r]){let d=n[a][r].split(","),p=o[a][r].split(",");h!=-1?(l.setAttribute("data",p[h]),l.setAttribute("active",d[h])):(l.setAttribute("data",o[a][r]),l.setAttribute("active",n[a][r])),l.setAttribute("kind",e[0]),l.setAttribute("label",e[1]),e[1]!="none"&&(l.onclick=s[a][r].bind(this))}t.appendChild(l);break}}onclick(e){let t=e.target.getAttribute("kind"),s=e.target.getAttribute("data"),n=e.target.getAttribute("label"),o=parseInt(e.target.getAttribute("active"));t=="button"&&(o==0?e.target.setAttribute("active",1):o==1&&e.target.setAttribute("active",0)),o=parseInt(e.target.getAttribute("active")),this.onCommandReceived&&this.onCommandReceived(t,n,s,o)}};var R=class{constructor(e,t,s,n,o,a,r){this.color=n,this.colors={join:"#ff6e4d",info:"#fba510",personal:"#fba510",name:"#00fa00",none:"#ffffff"},this.inputMain=_,this.nexusSocket=s,this.app=e,this.api=t,this.workflowManager=a,this.promptControl=r,this.mouseManger=o,this.nexusSocket.onConnected.push(this.handleConnect.bind(this)),this.nexusSocket.onMessageReceive.push(this.handleMessageReceive.bind(this)),this.inputMain.addEventListener("message",this.handleMessage.bind(this)),document.addEventListener("keydown",this.handleChatWindow.bind(this)),this.disableComfyThings=this.disableComfyThings.bind(this),this.enableComfyThings=this.enableComfyThings.bind(this),this.disableGraphSelectionWithExtremePrejudice=this.disableGraphSelectionWithExtremePrejudice.bind(this),this.comfyThings=["#extraOptions","#queue-button","#comfy-save-button","#comfy-file-input","#comfy-clear-button","#comfy-load-default-button",".queue-tab-button.side-bar-button",".queue-tab-button.side-bar-button","#comfy-refresh-button","#queue-front-button","#comfy-view-queue-button","#comfy-view-history-button"],C.childrens[3].softUpdate("#nexus-chat-window","pointer-events","none"),C.show(),setInterval(h=>{h.childrens[0].childrens[1].el.textContent=Object.keys(this.nexusSocket.userManager.users).length},1e3,C),this.up=new I,this.up.onCommandReceived=this.onPanelCommandReceived.bind(this),this.wp=new P,this.wp.onCommandReceived=this.onWFPanelCommandReceived.bind(this),this.spectating=null,this.lastkey=null}timeAgo(e){let t=new Date,s=new Date(e),n=Math.floor((t-s)/1e3),o=Math.floor(n/60),a=Math.floor(o/60),r=Math.floor(a/24);return o<1?"just now":o===1?"1 minute ago":o<60?`${o} minutes ago`:a===1?"1 hour ago":a<24?`${a} hours ago`:r===1?"1 day ago":`${r} days ago`}async verifyLogin(){this.disableComfyThings();let e=localStorage.getItem("nexus-socket-token");if(e){let t=await j(e);t&&this.nexusSocket.uuid==t.user_id?(this.enableComfyThings(),this.promptControl.handleControl(!0),this.promptControl.handleEditor(!0),this.workflowManager.handleWorkflowEvents(!0),this.nexusSocket.admin=!0,w("","You are now an admin!",this.color.info,this.colors.personal,!0),this.workflowManager.saveManager(60)):(this.promptControl.handleControl(!1),this.promptControl.handleEditor(!1),this.workflowManager.handleWorkflowEvents(!1),this.workflowManager.removeSaveManager(),w("","You are a viewer!",this.color.info,this.colors.info,!0))}else{let t=await O(this.nexusSocket.uuid);if(t){let s=t.editor?t.editor:!1,n=t.queue?t.queue:!1;this.workflowManager.handleWorkflowEvents(s),this.promptControl.handleEditor(s),this.promptControl.handleControl(n),this.nexusSocket.queue=n,this.nexusSocket.editor=s,s?this.workflowManager.saveManager(60):this.workflowManager.removeSaveManager(),w("",`Permission: Workflow Editor ${s?"Yes":"No"} | Queue Prompt ${n?"Yes":"No"} `,this.color.info,this.colors.personal,!0),n&&w("","You can now queue prompt press CTRL+Enter",this.color.info,this.colors.personal,!0)}}this.workflowManager.loadLatestWorkflow()}handleConnect(){let e=this.nexusSocket.userManager.get(this.nexusSocket.uuid,"name");this.nexusSocket.sendMessage("chat-join",{name:e},!1),this.verifyLogin()}findChildrenAfterHr(e){let t=document.querySelector(`.${e}`),s=[];if(t){let n=t.querySelector("hr");if(n){let o=n.nextElementSibling;for(;o;)s.push(o),o=o.nextElementSibling}}return s}disableGraphSelectionWithExtremePrejudice(){!this.nexusSocket.admin&&!this.nexusSocket.editor&&this.app.graph&&this.app.graph.list_of_graphcanvas&&this.app.graph.list_of_graphcanvas.length>0&&this.app.graph.list_of_graphcanvas[0].deselectAllNodes()}disableComfyThings(){this.comfyThings.forEach(t=>{document.querySelectorAll(t).forEach(n=>{n.disabled=!0,n.style.pointerEvents="none"})}),document.querySelectorAll(".comfy-settings-btn").forEach(t=>{t.disabled=!0,t.style.pointerEvents="none"}),this.findChildrenAfterHr("comfy-menu").forEach(t=>{(t.tagName==="BUTTON"||t.tagName==="INPUT"||t.tagName==="SELECT"||t.tagName==="TEXTAREA")&&(t.disabled=!0)}),document.querySelector(".comfy-menu").style.display="none",document.querySelector(".comfy-menu").style.pointerEvents="none",clearInterval(this.disableComfyThingsTimer),this.disableComfyThingsTimer=setInterval(()=>{this.comfyThings.forEach(s=>{document.querySelectorAll(s).forEach(o=>{o.disabled=!0,o.style.pointerEvents="none"})}),document.querySelectorAll(".comfy-settings-btn").forEach(s=>{s.disabled=!0,s.style.pointerEvents="none"}),this.findChildrenAfterHr("comfy-menu").forEach(s=>{(s.tagName==="BUTTON"||s.tagName==="INPUT"||s.tagName==="SELECT"||s.tagName==="TEXTAREA")&&(s.disabled=!0)}),document.querySelector(".comfy-menu").style.display="none",document.querySelector(".comfy-menu").style.pointerEvents="none",this.promptControl.handleEditor(this.nexusSocket.editor?this.nexusSocket.editor:!1),this.promptControl.handleControl(this.nexusSocket.queue?this.nexusSocket.queue:!1)},500),this.disableGraphSelectionWithExtremePrejudiceTimer=setInterval(this.disableGraphSelectionWithExtremePrejudice,50)}enableComfyThings(){clearInterval(this.disableComfyThingsTimer),clearInterval(this.disableGraphSelectionWithExtremePrejudiceTimer),this.comfyThings.forEach(t=>{document.querySelectorAll(t).forEach(n=>{n.disabled=!1,n.style.pointerEvents="all"})}),document.querySelectorAll(".comfy-settings-btn").forEach(t=>{t.disabled=!1,t.style.pointerEvents="all"}),this.findChildrenAfterHr("comfy-menu").forEach(t=>{(t.tagName==="BUTTON"||t.tagName==="INPUT"||t.tagName==="SELECT"||t.tagName==="TEXTAREA")&&(t.disabled=!1)}),document.querySelector(".comfy-menu").style.display="flex",document.querySelector(".comfy-menu").style.pointerEvents="all"}handleChatWindow(e){let t=e.key,s=_.childrens[0],n=_.childrens[1],o={tag:document.activeElement.tagName,el:document.activeElement,chat:document.activeElement==n.el};function a(){!["INPUT","TEXTAREA"].includes(o.tag)&&n.visible==!1&&(e.preventDefault(),s.show(),n.show(),n.el.focus(),n.el.value="",n.focusTimer=setInterval((d,p)=>{document.activeElement!=p&&d.visible==!0&&p.focus()},500,n,n.el),C.childrens[3].softUpdate("#nexus-chat-window","pointer-events","all"))}function r(){o.chat&&(s.hide(),n.el.blur(),n.el.value="",n.hide(),clearTimeout(n.focusTimer),C.childrens[3].softUpdate("#nexus-chat-window","pointer-events","none"))}function h(){if(!o.chat)return;let l=n.el.value;_.dispatchEvent(new CustomEvent("message",{detail:{message:l}})),r()}switch(t){case"t":case"T":a(),this.togglePlayerTab(!0),this.toggleWFTab(!0);break;case"Escape":r();break;case"Enter":h();break;case"p":case"P":e.altKey&&e.shiftKey&&(o.chat||this.togglePlayerTab());break;case"o":case"O":e.altKey&&e.shiftKey&&(o.chat||this.toggleWFTab());break}return o}toggleWFTab(e,t){(this.nexusSocket.admin||this.nexusSocket.editor)&&(e?(this.wp.active=0,this.wp.panel.hide()):t?this.prepareWFPanel():this.wp.active==0?(this.wp.active=1,this.wp.panel.show(),this.prepareWFPanel()):this.wp.active==1&&(this.wp.active=0,this.wp.panel.hide()),this.wp.active==1?this.workflowManager.removeSaveManager():this.workflowManager.saveManager(this.workflowManager.saveTime||60))}togglePlayerTab(e,t){e?(this.up.active=0,this.up.panel.hide()):t?this.preparePanel(this.nexusSocket.admin):this.up.active==0?(this.up.active=1,this.up.panel.show(),this.preparePanel(this.nexusSocket.admin)):this.up.active==1&&(this.up.active=0,this.up.panel.hide())}async onWFPanelCommandReceived(e,t,s,n){switch(e){case"button":if(t=="load"){let o=s,a=await F(this.nexusSocket.uuid,o);if(a){a.clear=!0;let r={name:"workflow",from:this.nexusSocket.uuid,data:{workflow:a}};this.nexusSocket.onMessageReceive.forEach(h=>{h(r)}),this.nexusSocket.admin&&this.nexusSocket.sendMessage("workflow",{workflow:a},!1)}}break}}async onPanelCommandReceived(e,t,s,n){switch(e){case"button":if(t=="spectate")this.nexusSocket.sendMessage("spectate",{on:n,receiver:s},!1),n==1?this.spectating=s:this.spectating=null;else if(t=="editor"){if(this.nexusSocket.admin){let o=localStorage.getItem("nexus-socket-token");await A(s,this.nexusSocket.uuid,{editor:!!n},o),this.nexusSocket.sendMessage("permission",{receiver:s},!1)}}else if(t=="queue"){if(this.nexusSocket.admin){let o=localStorage.getItem("nexus-socket-token");await A(s,this.nexusSocket.uuid,{queue:!!n},o),this.nexusSocket.sendMessage("permission",{receiver:s},!1)}}else t=="mouse"&&(this.nexusSocket.sendMessage("mouse_view_toggle",{on:n,receiver:s},!1),this.nexusSocket.userManager.set(s,"hide_mouse",n));break}}async prepareWFPanel(){let e=["Name","Created","Action"];this.wp.addColumnsToPanel(e);let t=await U(this.nexusSocket.uuid),s=[],n=[],o=[],a=[];for(let r=0;r]+(>|$)/g;e=e.replace(a,"");for(let r of e)n.test(r)&&!o.test(r)||(s+=r);return s}}startsWithCommand(e){return/^\/\w+(\s+\S+)?/.test(e)}extractCommandAndValue(e){let t=/^\/(\w+)\s*(.*)/,s=e.match(t);if(s){let n=s[1],o=s[2].trim()?s[2].split(/\s+/):[];return{command:n,values:o}}else return null}};var J=window.comfyAPI.app.app,W=window.comfyAPI.api.api,E=new N,Y={name:"ComfyUI-Nexus",async setup(i){i=i;let e=new T(E,i,24),t=new L(i,W,E),s=new G(W,i,E);new R(i,W,E,e.color,e,t,s),E.connect()},async beforeConfigureGraph(){}};J.registerExtension(Y);})();
+let nexus = {
+ name: "ComfyUI-Nexus",
+ async setup(app) {
+ app = app
+ let mouseManager = new NexusMouseManager(nexusSocket, app, 10)
+ let workflowManager = new NexusWorkflowManager(app, api, nexusSocket)
+ let nexusPromptControl = new NexusPromptControl(api, app, nexusSocket)
+ new NexusCommands(app, api, nexusSocket, mouseManager.color, mouseManager, workflowManager, nexusPromptControl)
+ nexusSocket.connect()
+ },
+ async beforeConfigureGraph() {
+ }
+}
+
+app.registerExtension(nexus);
\ No newline at end of file