Some cleanup and new menu class.

This commit is contained in:
rgthree
2024-05-17 23:21:42 -04:00
parent 2fb14983b1
commit 5f86b99bc3
18 changed files with 572 additions and 97 deletions
+59 -17
View File
@@ -6,12 +6,15 @@ import {
getClosestOrSelf,
queryOne,
query,
setAttributes,
} from "rgthree/common/utils_dom.js";
import { logoCivitai, link, pencilColored, diskColored } from "rgthree/common/media/svgs.js";
import { logoCivitai, link, pencilColored, diskColored, dotdotdot } from "rgthree/common/media/svgs.js";
import { RgthreeModelInfo } from "typings/rgthree.js";
import { SERVICE as MODEL_INFO_SERVICE } from "rgthree/common/model_info_service.js";
import { rgthree } from "./rgthree.js";
import { MenuButton } from "rgthree/common/menu.js";
import { generateId } from "rgthree/common/shared_utils.js";
import { rgthreeApi } from "rgthree/common/rgthree_api.js";
function injectCss(): Promise<void> {
const href = "rgthree/common/css/dialog_model_info.css";
@@ -152,20 +155,20 @@ export class RgthreeInfoDialog extends RgthreeDialog {
const info = this.modelInfo || {};
const civitaiLink = info.links?.find((i) => i.includes("civitai.com/models"));
const html = `
<ul class="rgthree-info-tags">
<li title="Type" class="-type -type-${(info.type || "").toLowerCase()}"><span>${
<ul class="rgthree-info-area">
<li title="Type" class="rgthree-info-tag -type -type-${(info.type || "").toLowerCase()}"><span>${
info.type || ""
}</span></li>
<li title="Base Model" class="-basemodel -basemodel-${(
<li title="Base Model" class="rgthree-info-tag -basemodel -basemodel-${(
info.baseModel || ""
).toLowerCase()}"><span>${info.baseModel || ""}</span></li>
<li stub="menu"></li>
${
!civitaiLink
? ""
: `
<li title="Visit on Civitai" class="-link -civitai"><a href="${civitaiLink}" target="_blank">${logoCivitai} Civitai ${link}</a></li>
`
<li class="rgthree-info-menu" stub="menu"></li>
${''
// !civitaiLink
// ? ""
// : `
// <li title="Visit on Civitai" class="-link -civitai"><a href="${civitaiLink}" target="_blank">Civitai ${link}</a></li>
// `
}
</ul>
@@ -188,14 +191,11 @@ export class RgthreeInfoDialog extends RgthreeDialog {
: !info.raw?.civitai
? infoTableRow(
"Civitai",
`<button data-action="fetch-civitai">Fetch info from civitai</button>`,
`<button class="rgthree-button" data-action="fetch-civitai">Fetch info from civitai</button>`,
)
: ""
}
${infoTableRow(
"Base Model",
(info.baseModel || " ") + (info.baseModelFile ? `(${info.baseModelFile})` : ""),
)}
${infoTableRow(
"Name",
info.name || info.raw?.metadata?.ss_output_name || "",
@@ -203,11 +203,22 @@ export class RgthreeInfoDialog extends RgthreeDialog {
"name",
)}
${infoTableRow(
${!info.baseModelFile && !info.baseModelFile ? '' : infoTableRow(
"Base Model",
(info.baseModel || "") + (info.baseModelFile ? ` (${info.baseModelFile})` : ""),
)}
${!info.trainedWords?.length ? '' : infoTableRow(
"Trained Words",
getTrainedWordsMarkup(info.trainedWords) ?? "",
"Trained words from the metadata and/or civitai. Click to select for copy.",
)}
${!info.raw?.metadata.ss_clip_skip || info.raw?.metadata.ss_clip_skip == 'None' ? '' : infoTableRow(
"Clip Skip",
info.raw?.metadata.ss_clip_skip
)}
${infoTableRow(
"Strength Min",
info.strengthMin ?? "",
@@ -277,6 +288,37 @@ export class RgthreeInfoDialog extends RgthreeDialog {
const div = $el("div", { html });
if (rgthree.isDevMode()) {
setAttributes(queryOne('[stub="menu"]', div)!, {
children: [
new MenuButton({
icon: dotdotdot,
options: [
{ label: "More Actions", type: "title" },
{
label: "Open API JSON",
callback: async (e: PointerEvent) => {
if (this.modelInfo?.file) {
window.open(`rgthree/api/loras/info?file=${encodeURIComponent(this.modelInfo.file)}`);
}
},
},
{
label: "Clear all local info",
callback: async (e: PointerEvent) => {
if (this.modelInfo?.file) {
this.modelInfo = await MODEL_INFO_SERVICE.clearLoraFetchedData(this.modelInfo.file);
this.setContent(this.getInfoContent());
this.setTitle(this.modelInfo?.["name"] || this.modelInfo?.["file"] || "Unknown");
}
},
},
],
}),
],
});
}
return div;
}
}
+1
View File
@@ -115,6 +115,7 @@
@import '../common/css/buttons.scss';
@import '../common/css/dialog.scss';
@import '../common/css/menu.scss';
.rgthree-dialog.-settings {
width: 100%;
+3
View File
@@ -990,6 +990,9 @@ class Rgthree extends EventTarget {
}
isDevMode() {
if (window.location.href.includes("#rgthree-dev=false")) {
return false;
}
return GLOBAL_LOG_LEVEL >= LogLevel.DEBUG || window.location.href.includes("#rgthree-dev");
}
+4 -1
View File
@@ -25,9 +25,12 @@ export async function showLoraChooser(
event: PointerEvent,
callback: ContextMenuEventListener,
parentMenu?: ContextMenu,
loras?: string[],
) {
const canvas = app.canvas as TLGraphCanvas;
const loras = ["None", ...(await rgthreeApi.getLoras())];
if (!loras) {
loras = ["None", ...(await rgthreeApi.getLoras())];
}
new LiteGraph.ContextMenu(loras, {
event: event,
parentMenu,
+35 -17
View File
@@ -4,7 +4,7 @@
width: 90vw;
max-width: 960px;
.rgthree-info-tags {
.rgthree-info-area {
list-style: none;
padding: 0;
margin: 0;
@@ -22,7 +22,8 @@
margin-left: auto;
}
> * {
&.rgthree-info-tag > * {
min-height: 24px;
border-radius: 4px;
line-height: 1;
color: rgba(255,255,255,0.85);
@@ -44,13 +45,8 @@
width: 16px;
height: 16px;
&:first-child {
margin-right: .3em;
display: none;
}
&:last-child {
margin-left: .5em;
fill: currentColor;
}
}
@@ -64,20 +60,35 @@
}
}
&.-civitai > * {
color: #ddd;
background: #1b65aa;
transition: all 0.15s ease-in-out;
&:hover {
color: #fff;
border-color: #1971c2;
background: #1971c2;
}
}
// &.-civitai > * {
// color: #ddd;
// background: #1b65aa;
// transition: all 0.15s ease-in-out;
// &:hover {
// color: #fff;
// border-color: #1971c2;
// background: #1971c2;
// }
// }
&.-type > * {
background: rgb(73, 54, 94);
color: rgb(228, 209, 248);
}
&.rgthree-info-menu {
margin-left: auto;
:not(#fakeid) & .rgthree-button {
margin: 0;
min-height: 24px;
padding: 0 12px;
}
svg {
width: 16px;
height: 16px;
}
}
}
}
@@ -148,6 +159,7 @@
display: block;
padding: 6px 10px;
}
> input, > textarea{
padding: 5px 10px;
border: 0;
@@ -162,6 +174,12 @@
width: 100%;
}
}
:not(#fakeid) & .rgthree-button[data-action="fetch-civitai"] {
font-size: inherit;
padding: 6px 16px;
margin: 2px;
}
}
tr[data-field-name="userNote"] td > span:first-child {
+43
View File
@@ -0,0 +1,43 @@
.rgthree-menu {
list-style: none;
padding: 0;
margin: 0;
position: fixed;
z-index: 999999;
pointer-events: none;
opacity: 0;
transition: opacity 0.08s ease-in-out;
color: #dde;
background-color: #111;
font-size: 12px;
box-shadow: 0 0 10px black !important;
> li {
position: relative;
padding: 4px 6px;
z-index: 9999;
white-space: nowrap;
&[role="button"] {
background-color: var(--comfy-menu-bg) !important;
color: var(--input-text);
cursor: pointer;
&:hover {
filter: brightness(155%);
}
}
}
&[state^="measuring"] {
display: block;
opacity: 0;
}
&[state="open"] {
display: block;
opacity: 1;
pointer-events: all;
}
}
+130
View File
@@ -0,0 +1,130 @@
import { generateId, wait } from "./shared_utils.js";
import { createElement as $el, getClosestOrSelf, setAttributes} from "./utils_dom.js";
/**
* A menu. Mimics the comfy menu.
*/
class Menu {
private element: HTMLMenuElement = $el('menu.rgthree-menu');
private callbacks: Map<string, (e: PointerEvent) => Promise<boolean|void>> = new Map();
private handleWindowPointerDownBound = this.handleWindowPointerDown.bind(this);
constructor(options: MenuOption[]) {
this.setOptions(options);
this.element.addEventListener('pointerup', async (e) => {
const target = getClosestOrSelf(e.target as HTMLElement, "[data-callback],menu");
if (e.which !== 1) {
return;
}
const callback = target?.dataset?.['callback'];
if (callback) {
const halt = await this.callbacks.get(callback)?.(e);
if (halt !== false) {
this.close();
}
}
e.preventDefault();
e.stopPropagation();
e.stopImmediatePropagation();
});
}
setOptions(options: MenuOption[]) {
for (const option of options) {
if (option.type === 'title') {
this.element.appendChild($el(`li`, {
html: option.label
}));
} else {
const id = generateId(8);
this.callbacks.set(id, async (e: PointerEvent) => { return option?.callback?.(e); });
this.element.appendChild($el(`li[role="button"][data-callback="${id}"]`, {
html: option.label
}));
}
}
}
toElement() {
return this.element;
}
async open(e: PointerEvent) {
const parent = (e.target as HTMLElement).closest('div,dialog,body') as HTMLElement
parent.appendChild(this.element);
setAttributes(this.element, {
style: {
left: `${e.clientX + 16}px`,
top: `${e.clientY - 16}px`,
}
});
this.element.setAttribute('state', 'measuring-open');
await wait(16);
const rect = this.element.getBoundingClientRect();
if (rect.right > window.innerWidth) {
this.element.style.left = `${e.clientX - rect.width - 16}px`;
await wait(16);
}
this.element.setAttribute('state', 'open');
setTimeout(() => {
window.addEventListener('pointerdown', this.handleWindowPointerDownBound);
});
}
handleWindowPointerDown(e:PointerEvent) {
if (!this.element.contains(e.target as HTMLElement)) {
this.close();
}
}
async close() {
window.removeEventListener('pointerdown', this.handleWindowPointerDownBound);
this.element.setAttribute('state', 'measuring-closed');
await wait(16);
this.element.setAttribute('state', 'closed');
this.element.remove();
}
isOpen() {
return (this.element.getAttribute('state') || '').includes('open');
}
}
type MenuOption = {
label: string;
type?: 'title'|'item'|'separator';
callback?: (e: PointerEvent) => void;
}
type MenuButtonOptions = {
icon: string;
options: MenuOption[];
}
export class MenuButton {
private options: MenuButtonOptions;
private menu: Menu;
private element: HTMLButtonElement = $el('button.rgthree-button[data-action="open-menu"]')
constructor(options: MenuButtonOptions) {
this.options = options;
this.element.innerHTML = options.icon;
this.menu = new Menu(options.options);
this.element.addEventListener('pointerdown', (e) => {
if (!this.menu.isOpen()) {
this.menu.open(e);
}
});
}
toElement() {
return this.element;
}
}
+7 -1
View File
@@ -19,7 +19,7 @@ class ModelInfoService {
if (!refresh) {
info = await rgthreeApi.getLoraInfo(file, light);
} else {
info = await rgthreeApi.refreshLoraInfo(file);
info = await rgthreeApi.refreshLorasInfo(file);
}
if (!light) {
this.loraToInfo.set(file, info);
@@ -31,6 +31,12 @@ class ModelInfoService {
return this.fetchLora(file, true);
}
async clearLoraFetchedData(file: string) {
await rgthreeApi.clearLorasInfo(file);
this.loraToInfo.delete(file);
return null;
}
async saveLoraPartial(file: string, data: Partial<RgthreeModelInfo>) {
let info = await rgthreeApi.saveLoraInfo(file, data);
this.loraToInfo.set(file, info);
+13 -8
View File
@@ -59,12 +59,6 @@ class RgthreeApi {
);
}
async refreshLoraInfo(lora: string): Promise<RgthreeModelInfo | null> {
return await this.fetchApiJsonOrNull<RgthreeModelInfo>(
`/loras/info/refresh?file=${encodeURIComponent(lora)}`,
);
}
async saveLoraInfo(
lora: string,
data: Partial<RgthreeModelInfo>,
@@ -81,9 +75,20 @@ class RgthreeApi {
return await this.fetchApiJsonOrNull<RgthreeModelInfo[]>(`/loras/info`);
}
async refreshLorasInfo(): Promise<RgthreeModelInfo[] | null> {
return await this.fetchApiJsonOrNull<RgthreeModelInfo[]>(`/loras/info/refresh`);
async refreshLorasInfo(file: string): Promise<RgthreeModelInfo | null>;
async refreshLorasInfo(): Promise<RgthreeModelInfo[] | null>;
async refreshLorasInfo(file?: string) {
const path = `/loras/info/refresh` + (file ? `?file=${encodeURIComponent(file)}` : '');
const infos = await this.fetchApiJsonOrNull<RgthreeModelInfo[]|RgthreeModelInfo>(path);
return infos;
}
async clearLorasInfo(file?: string): Promise<void> {
const path = `/loras/info/clear` + (file ? `?file=${encodeURIComponent(file)}` : '');
await this.fetchApiJsonOrNull<RgthreeModelInfo[]>(path);
return;
}
}
export const rgthreeApi = new RgthreeApi();