mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-02-05 07:30:11 +00:00
Rework user selection (#1728)
* Move to new route * Convert to tailwind * Basic style * Add userStore * nit * nit * nit * Remove app.#setUser * Route to user-select view * Mock login * Use primevue UI components * handle create new user * Remove legacy user selection * Add logout button on side toolbar * Add username to logout button tooltip * Add playwright tests * hide logout button in single user server * nit
This commit is contained in:
@@ -42,6 +42,9 @@ export class ComfyApi extends EventTarget {
|
||||
* The current client id from websocket status updates.
|
||||
*/
|
||||
clientId?: string
|
||||
/**
|
||||
* The current user id.
|
||||
*/
|
||||
user: string
|
||||
socket: WebSocket | null = null
|
||||
|
||||
@@ -49,7 +52,6 @@ export class ComfyApi extends EventTarget {
|
||||
|
||||
constructor() {
|
||||
super()
|
||||
// api.user is set by ComfyApp.setup()
|
||||
this.user = ''
|
||||
this.api_host = location.host
|
||||
this.api_base = location.pathname.split('/').slice(0, -1).join('/')
|
||||
|
||||
@@ -1711,67 +1711,6 @@ export class ComfyApp {
|
||||
)
|
||||
}
|
||||
|
||||
async #setUser() {
|
||||
const userConfig = await api.getUserConfig()
|
||||
// Return in single user mode.
|
||||
if (userConfig.users === undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
let user = localStorage['Comfy.userId']
|
||||
const users = userConfig.users ?? {}
|
||||
if (!user || !users[user]) {
|
||||
// Lift spinner / BlockUI for user selection.
|
||||
if (this.vueAppReady) useWorkspaceStore().spinner = false
|
||||
|
||||
// This will rarely be hit so move the loading to on demand
|
||||
const { UserSelectionScreen } = await import('./ui/userSelection')
|
||||
|
||||
this.ui.menuContainer.style.display = 'none'
|
||||
const { userId, username } = await new UserSelectionScreen().show(
|
||||
users,
|
||||
user
|
||||
)
|
||||
this.ui.menuContainer.style.display = ''
|
||||
|
||||
user = userId
|
||||
localStorage['Comfy.userName'] = username
|
||||
localStorage['Comfy.userId'] = user
|
||||
}
|
||||
|
||||
api.user = user
|
||||
|
||||
this.ui.settings.addSetting({
|
||||
id: 'Comfy.SwitchUser',
|
||||
name: 'Switch User',
|
||||
type: (name) => {
|
||||
let currentUser = localStorage['Comfy.userName']
|
||||
if (currentUser) {
|
||||
currentUser = ` (${currentUser})`
|
||||
}
|
||||
return $el('tr', [
|
||||
$el('td', [
|
||||
$el('label', {
|
||||
textContent: name
|
||||
})
|
||||
]),
|
||||
$el('td', [
|
||||
$el('button', {
|
||||
textContent: name + (currentUser ?? ''),
|
||||
onclick: () => {
|
||||
delete localStorage['Comfy.userId']
|
||||
delete localStorage['Comfy.userName']
|
||||
window.location.reload()
|
||||
}
|
||||
})
|
||||
])
|
||||
])
|
||||
},
|
||||
// TODO: Is that the correct default value?
|
||||
defaultValue: undefined
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Set up the app on the page
|
||||
*/
|
||||
@@ -1779,7 +1718,6 @@ export class ComfyApp {
|
||||
this.canvasEl = canvasEl
|
||||
// Show menu container for GraphView.
|
||||
this.ui.menuContainer.style.display = 'block'
|
||||
await this.#setUser()
|
||||
|
||||
this.resizeCanvas()
|
||||
|
||||
|
||||
@@ -1,135 +0,0 @@
|
||||
.comfy-user-selection {
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 999;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-family: sans-serif;
|
||||
background: linear-gradient(var(--tr-even-bg-color), var(--tr-odd-bg-color));
|
||||
}
|
||||
|
||||
.comfy-user-selection-inner {
|
||||
background: var(--comfy-menu-bg);
|
||||
margin-top: -30vh;
|
||||
padding: 20px 40px;
|
||||
border-radius: 10px;
|
||||
min-width: 365px;
|
||||
position: relative;
|
||||
box-shadow: 0 0 20px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.comfy-user-selection-inner form {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.comfy-user-selection-inner h1 {
|
||||
margin: 10px 0 30px 0;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.comfy-user-selection-inner label {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.comfy-user-selection input,
|
||||
.comfy-user-selection select {
|
||||
background-color: var(--comfy-input-bg);
|
||||
color: var(--input-text);
|
||||
border: 0;
|
||||
border-radius: 5px;
|
||||
padding: 5px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.comfy-user-selection input::placeholder {
|
||||
color: var(--descrip-text);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.comfy-user-existing {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.no-users .comfy-user-existing {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.comfy-user-selection-inner .or-separator {
|
||||
margin: 10px 0;
|
||||
padding: 10px;
|
||||
display: block;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
color: var(--descrip-text);
|
||||
}
|
||||
|
||||
.comfy-user-selection-inner .or-separator {
|
||||
overflow: hidden;
|
||||
text-align: center;
|
||||
margin-left: -10px;
|
||||
}
|
||||
|
||||
.comfy-user-selection-inner .or-separator::before,
|
||||
.comfy-user-selection-inner .or-separator::after {
|
||||
content: "";
|
||||
background-color: var(--border-color);
|
||||
position: relative;
|
||||
height: 1px;
|
||||
vertical-align: middle;
|
||||
display: inline-block;
|
||||
width: calc(50% - 20px);
|
||||
top: -1px;
|
||||
}
|
||||
|
||||
.comfy-user-selection-inner .or-separator::before {
|
||||
right: 10px;
|
||||
margin-left: -50%;
|
||||
}
|
||||
|
||||
.comfy-user-selection-inner .or-separator::after {
|
||||
left: 10px;
|
||||
margin-right: -50%;
|
||||
}
|
||||
|
||||
.comfy-user-selection-inner section {
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
margin: -10px;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
.comfy-user-selection-inner section.selected {
|
||||
background: var(--border-color);
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.comfy-user-selection-inner footer {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.comfy-user-selection-inner .comfy-user-error {
|
||||
color: var(--error-text);
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.comfy-user-button-next {
|
||||
font-size: 16px;
|
||||
padding: 6px 10px;
|
||||
width: 100px;
|
||||
display: flex;
|
||||
gap: 5px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
@@ -1,146 +0,0 @@
|
||||
// @ts-strict-ignore
|
||||
import { api } from '../api'
|
||||
import { $el } from '../ui'
|
||||
import { createSpinner } from './spinner'
|
||||
import './userSelection.css'
|
||||
|
||||
interface SelectedUser {
|
||||
username: string
|
||||
userId: string
|
||||
created: boolean
|
||||
}
|
||||
|
||||
export class UserSelectionScreen {
|
||||
async show(users, user): Promise<SelectedUser> {
|
||||
const userSelection = document.getElementById('comfy-user-selection')
|
||||
userSelection.style.display = ''
|
||||
return new Promise((resolve) => {
|
||||
const input = userSelection.getElementsByTagName('input')[0]
|
||||
const select = userSelection.getElementsByTagName('select')[0]
|
||||
const inputSection = input.closest('section')
|
||||
const selectSection = select.closest('section')
|
||||
const form = userSelection.getElementsByTagName('form')[0]
|
||||
const error = userSelection.getElementsByClassName('comfy-user-error')[0]
|
||||
const button = userSelection.getElementsByClassName(
|
||||
'comfy-user-button-next'
|
||||
)[0]
|
||||
|
||||
let inputActive = null
|
||||
input.addEventListener('focus', () => {
|
||||
inputSection.classList.add('selected')
|
||||
selectSection.classList.remove('selected')
|
||||
inputActive = true
|
||||
})
|
||||
select.addEventListener('focus', () => {
|
||||
inputSection.classList.remove('selected')
|
||||
selectSection.classList.add('selected')
|
||||
inputActive = false
|
||||
select.style.color = ''
|
||||
})
|
||||
select.addEventListener('blur', () => {
|
||||
if (!select.value) {
|
||||
select.style.color = 'var(--descrip-text)'
|
||||
}
|
||||
})
|
||||
|
||||
form.addEventListener('submit', async (e) => {
|
||||
e.preventDefault()
|
||||
if (inputActive == null) {
|
||||
error.textContent =
|
||||
'Please enter a username or select an existing user.'
|
||||
} else if (inputActive) {
|
||||
const username = input.value.trim()
|
||||
if (!username) {
|
||||
error.textContent = 'Please enter a username.'
|
||||
return
|
||||
}
|
||||
|
||||
// Create new user
|
||||
// Property 'readonly' does not exist on type 'HTMLSelectElement'.ts(2339)
|
||||
// Property 'readonly' does not exist on type 'HTMLInputElement'. Did you mean 'readOnly'?ts(2551)
|
||||
input.disabled =
|
||||
select.disabled =
|
||||
// @ts-expect-error
|
||||
input.readonly =
|
||||
// @ts-expect-error
|
||||
select.readonly =
|
||||
true
|
||||
const spinner = createSpinner()
|
||||
button.prepend(spinner)
|
||||
try {
|
||||
const resp = await api.createUser(username)
|
||||
if (resp.status >= 300) {
|
||||
let message =
|
||||
'Error creating user: ' + resp.status + ' ' + resp.statusText
|
||||
try {
|
||||
const res = await resp.json()
|
||||
if (res.error) {
|
||||
message = res.error
|
||||
}
|
||||
} catch (error) {}
|
||||
throw new Error(message)
|
||||
}
|
||||
|
||||
resolve({ username, userId: await resp.json(), created: true })
|
||||
} catch (err) {
|
||||
spinner.remove()
|
||||
error.textContent =
|
||||
err.message ??
|
||||
err.statusText ??
|
||||
err ??
|
||||
'An unknown error occurred.'
|
||||
// Property 'readonly' does not exist on type 'HTMLSelectElement'.ts(2339)
|
||||
// Property 'readonly' does not exist on type 'HTMLInputElement'. Did you mean 'readOnly'?ts(2551)
|
||||
input.disabled =
|
||||
select.disabled =
|
||||
// @ts-expect-error
|
||||
input.readonly =
|
||||
// @ts-expect-error
|
||||
select.readonly =
|
||||
false
|
||||
return
|
||||
}
|
||||
} else if (!select.value) {
|
||||
error.textContent = 'Please select an existing user.'
|
||||
return
|
||||
} else {
|
||||
resolve({
|
||||
username: users[select.value],
|
||||
userId: select.value,
|
||||
created: false
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
if (user) {
|
||||
const name = localStorage['Comfy.userName']
|
||||
if (name) {
|
||||
input.value = name
|
||||
}
|
||||
}
|
||||
if (input.value) {
|
||||
// Focus the input, do this separately as sometimes browsers like to fill in the value
|
||||
input.focus()
|
||||
}
|
||||
|
||||
const userIds = Object.keys(users ?? {})
|
||||
if (userIds.length) {
|
||||
for (const u of userIds) {
|
||||
$el('option', { textContent: users[u], value: u, parent: select })
|
||||
}
|
||||
select.style.color = 'var(--descrip-text)'
|
||||
|
||||
if (select.value) {
|
||||
// Focus the select, do this separately as sometimes browsers like to fill in the value
|
||||
select.focus()
|
||||
}
|
||||
} else {
|
||||
userSelection.classList.add('no-users')
|
||||
input.focus()
|
||||
}
|
||||
}).then((r: SelectedUser) => {
|
||||
userSelection.remove()
|
||||
return r
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user