Merge branch 'pythongosssss:main' into main
This commit is contained in:
+24
-2
@@ -312,7 +312,7 @@ app.registerExtension({
|
||||
};
|
||||
|
||||
TextAreaAutoComplete.globalSeparator = localStorage.getItem(id + ".AutoSeparate") ?? ", ";
|
||||
app.ui.settings.addSetting({
|
||||
const enabledSetting = app.ui.settings.addSetting({
|
||||
id,
|
||||
name: "🐍 Text Autocomplete",
|
||||
defaultValue: true,
|
||||
@@ -356,7 +356,6 @@ app.registerExtension({
|
||||
},
|
||||
[
|
||||
$el("input", {
|
||||
id: id.replaceAll(".", "-"),
|
||||
type: "checkbox",
|
||||
checked: !!TextAreaAutoComplete.globalSeparator,
|
||||
onchange: (event) => {
|
||||
@@ -367,6 +366,26 @@ app.registerExtension({
|
||||
}),
|
||||
]
|
||||
),
|
||||
$el(
|
||||
"label",
|
||||
{
|
||||
textContent: "Replace _ with space ",
|
||||
style: {
|
||||
display: "block",
|
||||
},
|
||||
},
|
||||
[
|
||||
$el("input", {
|
||||
type: "checkbox",
|
||||
checked: !!TextAreaAutoComplete.replacer,
|
||||
onchange: (event) => {
|
||||
const checked = !!event.target.checked;
|
||||
TextAreaAutoComplete.replacer = checked ? (v) => v.replaceAll("_", " ") : undefined;
|
||||
localStorage.setItem(id + ".ReplaceUnderscore", checked);
|
||||
},
|
||||
}),
|
||||
]
|
||||
),
|
||||
$el("button", {
|
||||
textContent: "Manage Custom Words",
|
||||
onclick: () => {
|
||||
@@ -383,6 +402,9 @@ app.registerExtension({
|
||||
]);
|
||||
},
|
||||
});
|
||||
TextAreaAutoComplete.enabled = enabledSetting.value;
|
||||
TextAreaAutoComplete.replacer = localStorage.getItem(id + ".ReplaceUnderscore") === "true" ? (v) => v.replaceAll("_", " ") : undefined;
|
||||
|
||||
},
|
||||
beforeRegisterNodeDef(_, def) {
|
||||
// Process each input to see if there is a custom word list for
|
||||
|
||||
@@ -27,10 +27,11 @@ app.registerExtension({
|
||||
left: 0;
|
||||
top: 0;
|
||||
transform: translate(-100%, 0);
|
||||
width: 256px;
|
||||
height: 256px;
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
width: 384px;
|
||||
height: 384px;
|
||||
background-size: contain;
|
||||
background-position: top right;
|
||||
background-repeat: no-repeat;
|
||||
filter: brightness(65%);
|
||||
}
|
||||
`,
|
||||
|
||||
@@ -175,8 +175,9 @@ const CHAR_CODE_ZERO = "0".charCodeAt(0);
|
||||
const CHAR_CODE_NINE = "9".charCodeAt(0);
|
||||
|
||||
class TextAreaCaretHelper {
|
||||
constructor(el) {
|
||||
constructor(el, getScale) {
|
||||
this.el = el;
|
||||
this.getScale = getScale;
|
||||
}
|
||||
|
||||
#calculateElementOffset() {
|
||||
@@ -253,11 +254,12 @@ class TextAreaCaretHelper {
|
||||
}
|
||||
|
||||
getCursorOffset() {
|
||||
const scale = this.getScale();
|
||||
const elOffset = this.#calculateElementOffset();
|
||||
const elScroll = this.#getElScroll();
|
||||
const cursorPosition = this.#getCursorPosition();
|
||||
const lineHeight = this.#getLineHeightPx();
|
||||
const top = elOffset.top - elScroll.top + cursorPosition.top + lineHeight;
|
||||
const top = elOffset.top - (elScroll.top * scale) + (cursorPosition.top + lineHeight) * scale;
|
||||
const left = elOffset.left - elScroll.left + cursorPosition.left;
|
||||
const clientTop = this.el.getBoundingClientRect().top;
|
||||
if (this.el.dir !== "rtl") {
|
||||
@@ -289,8 +291,7 @@ class TextAreaCaretHelper {
|
||||
const startPos = this.el.selectionStart;
|
||||
const endPos = this.el.selectionEnd;
|
||||
|
||||
this.el.value =
|
||||
this.el.value.substring(0, startPos + offset) + value + this.el.value.substring(endPos, this.el.value.length);
|
||||
this.el.value = this.el.value.substring(0, startPos + offset) + value + this.el.value.substring(endPos, this.el.value.length);
|
||||
this.el.selectionEnd = this.el.selectionStart = startPos + value.length + offset + (finalOffset ?? 0);
|
||||
} else {
|
||||
this.el.value += value;
|
||||
@@ -313,6 +314,7 @@ class TextAreaCaretHelper {
|
||||
export class TextAreaAutoComplete {
|
||||
static globalSeparator = "";
|
||||
static enabled = true;
|
||||
static replacer = undefined;
|
||||
|
||||
/** @type {Record<string, Record<string, AutoCompleteEntry>>} */
|
||||
static groups = {};
|
||||
@@ -341,7 +343,7 @@ export class TextAreaAutoComplete {
|
||||
*/
|
||||
constructor(el, words = null, separator = null) {
|
||||
this.el = el;
|
||||
this.helper = new TextAreaCaretHelper(el);
|
||||
this.helper = new TextAreaCaretHelper(el, () => app.canvas.ds.scale);
|
||||
this.dropdown = $el("div.pysssss-autocomplete");
|
||||
this.overrideWords = words;
|
||||
this.overrideSeparator = separator;
|
||||
@@ -481,10 +483,7 @@ export class TextAreaAutoComplete {
|
||||
);
|
||||
|
||||
const top = priorityMatches.length * 0.2;
|
||||
return priorityMatches
|
||||
.slice(0, top)
|
||||
.concat(prefixMatches, priorityMatches.slice(top), includesMatches)
|
||||
.slice(0, 20);
|
||||
return priorityMatches.slice(0, top).concat(prefixMatches, priorityMatches.slice(top), includesMatches).slice(0, 20);
|
||||
}
|
||||
|
||||
#update() {
|
||||
@@ -567,11 +566,11 @@ export class TextAreaAutoComplete {
|
||||
{
|
||||
onclick: () => {
|
||||
this.el.focus();
|
||||
this.helper.insertAtCursor(
|
||||
(wordInfo.value ?? wordInfo.text) + this.separator,
|
||||
-before.length,
|
||||
wordInfo.caretOffset
|
||||
);
|
||||
let value = wordInfo.value ?? wordInfo.text;
|
||||
if(TextAreaAutoComplete.replacer) {
|
||||
value = TextAreaAutoComplete.replacer(value);
|
||||
}
|
||||
this.helper.insertAtCursor(value + this.separator, -before.length, wordInfo.caretOffset);
|
||||
setTimeout(() => {
|
||||
this.#update();
|
||||
}, 150);
|
||||
|
||||
@@ -80,6 +80,12 @@ export class Lightbox {
|
||||
}
|
||||
|
||||
async update(shift) {
|
||||
if (shift < 0 && this.index <= 0) {
|
||||
return;
|
||||
}
|
||||
if (shift > 0 && this.index >= this.images.length - 1) {
|
||||
return;
|
||||
}
|
||||
this.index += shift;
|
||||
|
||||
this.prev.style.visibility = this.index ? "unset" : "hidden";
|
||||
@@ -91,7 +97,11 @@ export class Lightbox {
|
||||
this.el.style.opacity = 1;
|
||||
this.img.style.opacity = 0;
|
||||
this.spinner.style.display = "inline-block";
|
||||
await loadImage(img);
|
||||
try {
|
||||
await loadImage(img);
|
||||
} catch (err) {
|
||||
console.error('failed to load image', img, err);
|
||||
}
|
||||
this.spinner.style.display = "none";
|
||||
this.link.href = img;
|
||||
this.img.src = img;
|
||||
@@ -100,3 +110,23 @@ export class Lightbox {
|
||||
}
|
||||
|
||||
export const lightbox = new Lightbox();
|
||||
|
||||
addEventListener('keydown', (event) => {
|
||||
if (lightbox.el.style.display === 'none') {
|
||||
return;
|
||||
}
|
||||
const { key } = event;
|
||||
switch (key) {
|
||||
case 'ArrowLeft':
|
||||
case 'a':
|
||||
lightbox.update(-1);
|
||||
break;
|
||||
case 'ArrowRight':
|
||||
case 'd':
|
||||
lightbox.update(1);
|
||||
break;
|
||||
case 'Escape':
|
||||
lightbox.close();
|
||||
break;
|
||||
}
|
||||
});
|
||||
+1
-1
@@ -410,7 +410,7 @@ app.registerExtension({
|
||||
api.addEventListener("executed", ({ detail }) => {
|
||||
if (visible && detail?.output?.images) {
|
||||
for (const src of detail.output.images) {
|
||||
const href = `/view?filename=${encodeURIComponent(src.filename)}&type=${
|
||||
const href = `./view?filename=${encodeURIComponent(src.filename)}&type=${
|
||||
src.type
|
||||
}&subfolder=${encodeURIComponent(src.subfolder)}&t=${+new Date()}`;
|
||||
|
||||
|
||||
@@ -234,6 +234,8 @@ const infoHandler = {
|
||||
CheckpointLoader: "checkpoints",
|
||||
CheckpointLoaderSimple: "checkpoints",
|
||||
"CheckpointLoader|pysssss": "checkpoints",
|
||||
"Efficient Loader": "checkpoints",
|
||||
"Eff. Loader SDXL": "checkpoints",
|
||||
};
|
||||
|
||||
app.registerExtension({
|
||||
|
||||
+12
-1
@@ -15,7 +15,18 @@ app.registerExtension({
|
||||
return;
|
||||
}
|
||||
}
|
||||
const url = new URL(`assets/notify.mp3`, import.meta.url);
|
||||
let file = this.widgets[2].value;
|
||||
if (!file) {
|
||||
file = "notify.mp3";
|
||||
}
|
||||
if (!file.startsWith("http")) {
|
||||
if (!file.includes("/")) {
|
||||
file = "assets/" + file;
|
||||
}
|
||||
file = new URL(file, import.meta.url)
|
||||
}
|
||||
|
||||
const url = new URL(file);
|
||||
const audio = new Audio(url);
|
||||
audio.volume = this.widgets[1].value;
|
||||
audio.play();
|
||||
|
||||
@@ -154,7 +154,7 @@ app.registerExtension({
|
||||
v = this.widgets_values[0];
|
||||
}
|
||||
let config = [link.type, {}];
|
||||
if (input.widget) {
|
||||
if (input.widget?.config) {
|
||||
config = input.widget.config;
|
||||
}
|
||||
const { widget } = ComfyWidgets[widgetType](this, "value", config, app);
|
||||
|
||||
@@ -49,11 +49,13 @@ const ext = {
|
||||
api.addEventListener("executed", ({ detail }) => {
|
||||
if (!enabled) return;
|
||||
const images = detail?.output?.images;
|
||||
if (!images) return;
|
||||
if (!images || !images.length) return;
|
||||
const format = app.getPreviewFormatParam();
|
||||
const src = `/view?filename=${encodeURIComponent(images[0].filename)}&type=${
|
||||
images[0].type
|
||||
}&subfolder=${encodeURIComponent(images[0].subfolder)}&t=${+new Date()}${format}`;
|
||||
const src = [
|
||||
`./view?filename=${encodeURIComponent(images[0].filename)}`,
|
||||
`type=${images[0].type}`,
|
||||
`subfolder=${encodeURIComponent(images[0].subfolder)}`,
|
||||
`t=${+new Date()}${format}`,].join('&');
|
||||
show(src, detail.node);
|
||||
});
|
||||
|
||||
|
||||
+8
-8
@@ -2,23 +2,23 @@ import { app } from "../../../scripts/app.js";
|
||||
import { ComfyWidgets } from "../../../scripts/widgets.js";
|
||||
|
||||
// Displays input text on a node
|
||||
|
||||
app.registerExtension({
|
||||
name: "pysssss.ShowText",
|
||||
async beforeRegisterNodeDef(nodeType, nodeData, app) {
|
||||
if (nodeData.name === "ShowText|pysssss") {
|
||||
function populate(text) {
|
||||
if (this.widgets) {
|
||||
const pos = this.widgets.findIndex((w) => w.name === "text");
|
||||
if (pos !== -1) {
|
||||
for (let i = pos; i < this.widgets.length; i++) {
|
||||
this.widgets[i].onRemove?.();
|
||||
}
|
||||
this.widgets.length = pos;
|
||||
for (let i = 1; i < this.widgets.length; i++) {
|
||||
this.widgets[i].onRemove?.();
|
||||
}
|
||||
this.widgets.length = 1;
|
||||
}
|
||||
|
||||
for (const list of text) {
|
||||
const v = [...text];
|
||||
if (!v[0]) {
|
||||
v.shift();
|
||||
}
|
||||
for (const list of v) {
|
||||
const w = ComfyWidgets["STRING"](this, "text", ["STRING", { multiline: true }], app).widget;
|
||||
w.inputEl.readOnly = true;
|
||||
w.inputEl.style.opacity = 0.6;
|
||||
|
||||
@@ -386,7 +386,7 @@ class SvgWorkflowImage extends WorkflowImage {
|
||||
return;
|
||||
} else if (file && (file.type === "image/jpeg" || file.name?.endsWith(".jpg") || file.name?.endsWith(".jpeg"))) {
|
||||
if (
|
||||
await new Promise((r) => {
|
||||
await new Promise((resolve) => {
|
||||
try {
|
||||
// This shouldnt go in here but it's easier than refactoring handleFile
|
||||
const reader = new FileReader();
|
||||
|
||||
@@ -81,6 +81,9 @@ async function saveWorkflow(name, workflow, overwrite) {
|
||||
class PysssssWorkflows {
|
||||
async load() {
|
||||
this.workflows = await getWorkflows();
|
||||
if(this.workflows.length) {
|
||||
this.workflows.sort();
|
||||
}
|
||||
this.loadMenu.style.display = this.workflows.length ? "flex" : "none";
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user