refactor!: extract js scripts

This commit is contained in:
BennyKok
2023-09-08 16:23:25 +08:00
parent 4fc778cc21
commit 6abac9e1c2
9 changed files with 402 additions and 368 deletions
+31
View File
@@ -0,0 +1,31 @@
import { iframeSrc, showEditor } from './state.js';
import { van } from './van.js';
const { button, iframe, div, img } = van.tags;
export function AvatarEditor() {
return div(
{
class: 'w-full h-full',
},
button(
{
class: () =>
'px-4 py-2 rounded-md absolute left-0 top-0 z-[100] pointer-events-auto ' +
(showEditor.val ? '' : 'hidden'),
onclick: () => {
console.log('close');
showEditor.val = false;
},
},
'back',
),
iframe({
id: 'avatech-editor-iframe',
title: 'avatech-editor-iframe',
name: 'avatech-editor-iframe',
class: () =>
'w-full h-full pointer-events-auto ' + (showEditor.val ? '' : 'hidden'),
src: iframeSrc,
}),
);
}
+15
View File
@@ -0,0 +1,15 @@
import { ImageEditor } from './ImageEditor.js';
import { AvatarEditor } from './AvatarEditor.js';
import { van } from './van.js';
const { button, iframe, div, img } = van.tags;
export function Container() {
return div(
{
class: 'fixed left-0 top-0 w-full h-full z-[200] pointer-events-none',
id: 'avatech-editor',
},
AvatarEditor(),
ImageEditor()
);
}
+128
View File
@@ -0,0 +1,128 @@
import {
showImageEditor,
point_label,
imageUrl,
imageContainerSize,
imagePrompts,
targetNode,
imageSize
} from './state.js';
import { van } from './van.js';
const { button, iframe, div, img } = van.tags;
export function ImageEditor() {
return div(
{
class: () => 'absolute flex bg-gray-900 bg-opacity-50 top-0 w-full h-full pointer-events-auto ' +
(showImageEditor.val ? '' : 'hidden'),
},
button(
{
class: () => 'absolute px-4 py-2 rounded-md left-0 top-0 z-[200] ',
onclick: () => {
console.log('close');
showImageEditor.val = false;
},
},
'back'
),
div(
{
class: 'w-full flex justify-center absolute top-0 left-0 right-0 items-center',
},
button(
{
class: () => ' px-4 py-2 rounded-md left-0 top-0 z-[200] ',
onclick: () => {
point_label.val = 1;
},
},
'Positive'
),
button(
{
class: () => ' px-4 py-2 rounded-md left-0 top-0 z-[200] ',
onclick: () => {
point_label.val = 0;
},
},
'Negative'
)
),
div(
{
class: 'flex items-center justify-center absolute h-full left-0 right-0 bottom-0 top-0 mx-auto my-auto max-h-96 ',
},
img({
class: 'w-fit h-full',
src: imageUrl,
onload: (e) => {
imageSize.val = {
width: e.target.naturalWidth,
height: e.target.naturalHeight,
};
imageContainerSize.val = {
width: e.target.offsetWidth,
height: e.target.offsetHeight,
};
},
onclick: (e) => {
const rect = e.target.getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
const relativeX = Math.trunc(
(x / e.target.offsetWidth) * imageSize.val.width
);
const relativeY = Math.trunc(
(y / e.target.offsetHeight) * imageSize.val.height
);
imagePrompts.val = [
...imagePrompts.val,
{ x: relativeX, y: relativeY, label: point_label.val },
];
// targetWidget.widgets.find((x) => x.name === 'x').value = relativeX;
// targetWidget.widgets.find((x) => x.name === 'y').value = relativeY;
targetNode.val.widgets.find(
(x) => x.name === 'image_prompts_json'
).value = JSON.stringify(imagePrompts.val);
targetNode.val.graph.change();
// showImageEditor.val = false;
},
}),
() => {
return div(
{
class: 'absolute w-full h-full pointer-events-none',
style: () => `width: ${imageContainerSize.val.width}px; height: ${imageContainerSize.val.height}px;`,
},
...imagePrompts.val.map((point) => {
return div(
{
class: 'absolute',
style: () => `left: ${(point.x / imageSize.val.width) *
imageContainerSize.val.width}px; top: ${(point.y / imageSize.val.height) *
imageContainerSize.val.height}px; transform: translate(-50%, -50%);`,
},
button({
class: () => `w-4 h-4 rounded-full pointer-events-auto ${point.label === 1 ? 'bg-green-500' : 'bg-red-500'}`,
onclick: (e) => {
e.preventDefault();
imagePrompts.val = imagePrompts.val.filter(
(x) => x.x !== point.x && x.y !== point.y
);
targetNode.val.widgets.find(
(x) => x.name === 'image_prompts_json'
).value = JSON.stringify(imagePrompts.val);
targetNode.val.graph.change();
},
})
);
})
);
}
)
);
}
+4
View File
@@ -0,0 +1,4 @@
/** @typedef {import('../../../web/scripts/api.js').api} API*/
import { api as _api } from '../../scripts/api.js';
/** @type {API} */
export const api = _api;
+4
View File
@@ -0,0 +1,4 @@
/** @typedef {import('../../../web/scripts/app.js').ComfyApp} ComfyApp*/
import { app as _app } from '../../scripts/app.js';
/** @type {ComfyApp} */
export const app = _app;
-368
View File
@@ -1,368 +0,0 @@
/** @typedef {import('../../../web/types/comfy.js').ComfyExtension} ComfyExtension*/
/** @typedef {import('../../../web/scripts/app.js').ComfyApp} ComfyApp*/
/** @typedef {import('../../../web/scripts/api.js').api} API*/
import { app as _app } from '../../scripts/app.js';
import { api as _api } from '../../scripts/api.js';
/** @type {ComfyApp} */
const app = _app;
/** @type {API} */
const api = _api;
import * as _van from 'https://cdn.jsdelivr.net/gh/vanjs-org/van/public/van-1.1.3.min.js';
/** @type {import('./van-1.1.3.min.js').Van} */
const van = _van.default;
function addMenuHandler(nodeType, cb) {
const getOpts = nodeType.prototype.getExtraMenuOptions;
nodeType.prototype.getExtraMenuOptions = function () {
const r = getOpts.apply(this, arguments);
cb.apply(this, arguments);
return r;
};
}
const iframeSrc = van.state('https://editor.avatech.ai');
const showEditor = van.state(false);
const showImageEditor = van.state(false);
const imageUrl = van.state('');
const point_label = van.state(1);
const imageContainerSize = van.state({
width: 0,
height: 0,
});
/**
* @typedef {Object} Point
* @property {number} x - The x coordinate
* @property {number} y - The y coordinate
* @property {number} label - The label
*/
/** @type {import('./van-1.1.3.min.js').State<Point[]>} */
const imagePrompts = van.state([]);
let targetWidget;
let imageSize = { width: 0, height: 0 };
function openInAvatechEditor(url, fileName) {
let editor = document.getElementById('avatech-editor-iframe');
iframeSrc.val = url;
showEditor.val = true;
editor.contentWindow.postMessage(
{
key: 'key',
value: fileName,
method: 'store',
},
'*',
);
}
/** @type {ComfyExtension} */
const ext = {
getCustomWidgets(app) {
return {
SAM_PROMPTS(node, inputName, inputData, app) {
const btn = node.addWidget('button', 'Edit prompt','', () => {
showMyImageEditor(node);
});
btn.serialize = false;
return {
widget: btn,
};
},
};
},
name: 'Avatech.Avatar.BlendshapeEditor',
init(app) {},
async setup() {
const graphCanvas = document.getElementById('graph-canvas');
window.addEventListener('keydown', (event) => {
console.log(event.key);
if (event.key === 'b') {
event.preventDefault();
const currentGraph = app.graph.list_of_graphcanvas[0];
if (currentGraph.selected_nodes.length !== 1) {
Object.values(currentGraph.selected_nodes).forEach((targetNode) => {
if (targetNode.mode === 4) targetNode.mode = 0;
else targetNode.mode = 4;
});
} else {
const targetNode = currentGraph.current_node;
if (targetNode.mode === 4) targetNode.mode = 0;
else targetNode.mode = 4;
}
app.graph.change();
}
if (event.key === 'Escape') {
event.preventDefault();
showImageEditor.val = false;
}
// if enter
if (event.key === 'Enter') {
event.preventDefault();
document.getElementById('queue-button').click();
}
}, {
capture: true,
});
const { button, iframe, div, img } = van.tags;
const AvatarEditor = () => {
return div(
{
class: 'w-full h-full',
},
button(
{
class: () =>
'px-4 py-2 rounded-md absolute left-0 top-0 z-[100] pointer-events-auto ' +
(showEditor.val ? '' : 'hidden'),
onclick: () => {
console.log('close');
showEditor.val = false;
},
},
'back',
),
iframe({
id: 'avatech-editor-iframe',
title: 'avatech-editor-iframe',
name: 'avatech-editor-iframe',
class: () =>
'w-full h-full pointer-events-auto ' +
(showEditor.val ? '' : 'hidden'),
src: iframeSrc,
}),
);
};
const ImageEditor = () => {
return div(
{
class: () =>
'absolute flex bg-gray-900 bg-opacity-50 top-0 w-full h-full pointer-events-auto ' +
(showImageEditor.val ? '' : 'hidden'),
},
button(
{
class: () => 'absolute px-4 py-2 rounded-md left-0 top-0 z-[200] ',
onclick: () => {
console.log('close');
showImageEditor.val = false;
},
},
'back',
),
div(
{
class:
'w-full flex justify-center absolute top-0 left-0 right-0 items-center',
},
button(
{
class: () => ' px-4 py-2 rounded-md left-0 top-0 z-[200] ',
onclick: () => {
point_label.val = 1;
},
},
'Positive',
),
button(
{
class: () => ' px-4 py-2 rounded-md left-0 top-0 z-[200] ',
onclick: () => {
point_label.val = 0;
},
},
'Negative',
),
),
div(
{
class:
'flex items-center justify-center absolute h-full left-0 right-0 bottom-0 top-0 mx-auto my-auto max-h-96 ',
},
img({
class: 'w-fit h-full',
src: imageUrl,
onload: (e) => {
imageSize = {
width: e.target.naturalWidth,
height: e.target.naturalHeight,
};
imageContainerSize.val = {
width: e.target.offsetWidth,
height: e.target.offsetHeight,
};
},
onclick: (e) => {
const rect = e.target.getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
const relativeX = Math.trunc(
(x / e.target.offsetWidth) * imageSize.width,
);
const relativeY = Math.trunc(
(y / e.target.offsetHeight) * imageSize.height,
);
imagePrompts.val = [
...imagePrompts.val,
{ x: relativeX, y: relativeY, label: point_label.val },
];
// targetWidget.widgets.find((x) => x.name === 'x').value = relativeX;
// targetWidget.widgets.find((x) => x.name === 'y').value = relativeY;
targetWidget.widgets.find(
(x) => x.name === 'image_prompts_json',
).value = JSON.stringify(imagePrompts.val);
targetWidget.graph.change();
// showImageEditor.val = false;
},
}),
() => {
return div(
{
class: 'absolute w-full h-full pointer-events-none',
style: () =>
`width: ${imageContainerSize.val.width}px; height: ${imageContainerSize.val.height}px;`,
},
...imagePrompts.val.map((point) => {
return div(
{
class: 'absolute',
style: () =>
`left: ${
(point.x / imageSize.width) *
imageContainerSize.val.width
}px; top: ${
(point.y / imageSize.height) *
imageContainerSize.val.height
}px; transform: translate(-50%, -50%);`,
},
button({
class: () =>
`w-4 h-4 rounded-full pointer-events-auto ${
point.label === 1 ? 'bg-green-500' : 'bg-red-500'
}`,
onclick: (e) => {
e.preventDefault();
imagePrompts.val = imagePrompts.val.filter(
(x) => x.x !== point.x && x.y !== point.y,
);
targetWidget.widgets.find(
(x) => x.name === 'image_prompts_json',
).value = JSON.stringify(imagePrompts.val);
targetWidget.graph.change();
},
}),
);
}),
);
},
),
);
};
const Container = () => {
return div(
{
class: 'fixed left-0 top-0 w-full h-full z-[200] pointer-events-none',
id: 'avatech-editor',
},
AvatarEditor(),
ImageEditor(),
);
};
van.add(document.body, Container());
},
async beforeRegisterNodeDef(nodeType, nodeData, app) {
if (nodeData.name === 'ExportGLTF') {
addMenuHandler(nodeType, function (_, options) {
const output = app.nodeOutputs[this.id + ''];
if (!output || !output.gltfFilename) return;
const gltfFilename =
window.location.protocol +
'//' +
api.api_host +
api.api_base +
`/view?filename=${output.gltfFilename[0]}`;
options.unshift({
content: 'Save file',
callback: () => {
const a = document.createElement('a');
let url = new URL(gltfFilename);
url.searchParams.delete('preview');
a.href = url;
a.setAttribute(
'download',
new URLSearchParams(url.search).get('filename'),
);
document.body.append(a);
a.click();
requestAnimationFrame(() => a.remove());
},
});
options.unshift({
content: 'Open In Avatech Editor (Local)',
callback: () => {
openInAvatechEditor('http://localhost:3006', gltfFilename);
},
});
options.unshift({
content: 'Open In Avatech Editor',
callback: () => {
openInAvatechEditor('https://editor.avatech.ai', gltfFilename);
},
});
});
} else if (nodeData.name === 'SAM_Prompt_Image') {
nodeData.input.required.sam = ['SAM_PROMPTS'];
nodeData.input.required.upload = ['IMAGEUPLOAD'];
// nodeData.input.required.prompts_points = ["IMAGEUPLOAD"];
addMenuHandler(nodeType, function (_, options) {
options.unshift({
content: 'Open In Points Editor (Local)',
callback: () => {
showMyImageEditor(this)
},
});
});
}
},
};
function showMyImageEditor(node) {
showImageEditor.val = true;
imagePrompts.val = JSON.parse(
node.widgets.find((x) => x.name === 'image_prompts_json').value,
);
imageUrl.val = api.apiURL(
`/view?filename=${encodeURIComponent(
node.widgets.find((x) => x.name === 'image').value,
)}&type=input&subfolder=`,
);
targetWidget = node;
// console.log(imageUrl.val, api.api_base);
// console.log(this);
}
app.registerExtension(ext);
+183
View File
@@ -0,0 +1,183 @@
import {
iframeSrc,
showEditor,
showImageEditor,
imageUrl,
imagePrompts,
targetNode,
} from './state.js';
import { van } from './van.js';
import { app } from './app.js';
import { api } from './api.js';
import { Container } from './Container.js';
/**
* Adds a menu handler to a node type.
* @param {Object} nodeType - The type of the node.
* @param {Function} cb - The callback function to handle the menu.
*/
function addMenuHandler(nodeType, cb) {
const getOpts = nodeType.prototype.getExtraMenuOptions;
nodeType.prototype.getExtraMenuOptions = function () {
const r = getOpts.apply(this, arguments);
cb.apply(this, arguments);
return r;
};
}
function openInAvatechEditor(url, fileName) {
let editor = document.getElementById('avatech-editor-iframe');
iframeSrc.val = url;
showEditor.val = true;
editor.contentWindow.postMessage(
{
key: 'key',
value: fileName,
method: 'store',
},
'*',
);
}
/** @typedef {import('../../../web/types/comfy.js').ComfyExtension} ComfyExtension*/
/** @type {ComfyExtension} */
const ext = {
getCustomWidgets(app) {
return {
SAM_PROMPTS(node, inputName, inputData, app) {
const btn = node.addWidget('button', 'Edit prompt', '', () => {
showMyImageEditor(node);
});
btn.serialize = false;
return {
widget: btn,
};
},
};
},
name: 'Avatech.Avatar.BlendshapeEditor',
init(app) {},
async setup() {
const graphCanvas = document.getElementById('graph-canvas');
window.addEventListener(
'keydown',
(event) => {
if (event.key === 'b') {
event.preventDefault();
const currentGraph = app.graph.list_of_graphcanvas[0];
if (currentGraph.selected_nodes.length !== 1) {
Object.values(currentGraph.selected_nodes).forEach((targetNode) => {
if (targetNode.mode === 4) targetNode.mode = 0;
else targetNode.mode = 4;
});
} else {
const targetNode = currentGraph.current_node;
if (targetNode.mode === 4) targetNode.mode = 0;
else targetNode.mode = 4;
}
app.graph.change();
}
if (event.key === 'Escape') {
event.preventDefault();
showImageEditor.val = false;
}
},
{
capture: true,
},
);
window.addEventListener('keydown', (event) => {
// if enter
if (event.key === 'Enter') {
event.preventDefault();
document.getElementById('queue-button').click();
}
});
van.add(document.body, Container());
},
async beforeRegisterNodeDef(nodeType, nodeData, app) {
if (nodeData.name === 'ExportGLTF') {
addMenuHandler(nodeType, function (_, options) {
const output = app.nodeOutputs[this.id + ''];
if (!output || !output.gltfFilename) return;
const gltfFilename =
window.location.protocol +
'//' +
api.api_host +
api.api_base +
`/view?filename=${output.gltfFilename[0]}`;
options.unshift({
content: 'Save file',
callback: () => {
const a = document.createElement('a');
let url = new URL(gltfFilename);
url.searchParams.delete('preview');
a.href = url;
a.setAttribute(
'download',
new URLSearchParams(url.search).get('filename'),
);
document.body.append(a);
a.click();
requestAnimationFrame(() => a.remove());
},
});
options.unshift({
content: 'Open In Avatech Editor (Local)',
callback: () => {
openInAvatechEditor('http://localhost:3006', gltfFilename);
},
});
options.unshift({
content: 'Open In Avatech Editor',
callback: () => {
openInAvatechEditor('https://editor.avatech.ai', gltfFilename);
},
});
});
} else if (nodeData.name === 'SAM_Prompt_Image') {
nodeData.input.required.sam = ['SAM_PROMPTS'];
nodeData.input.required.upload = ['IMAGEUPLOAD'];
// nodeData.input.required.prompts_points = ["IMAGEUPLOAD"];
addMenuHandler(nodeType, function (_, options) {
options.unshift({
content: 'Open In Points Editor (Local)',
callback: () => {
showMyImageEditor(this);
},
});
});
}
},
};
function showMyImageEditor(node) {
showImageEditor.val = true;
imagePrompts.val = JSON.parse(
node.widgets.find((x) => x.name === 'image_prompts_json').value,
);
imageUrl.val = api.apiURL(
`/view?filename=${encodeURIComponent(
node.widgets.find((x) => x.name === 'image').value,
)}&type=input&subfolder=`,
);
targetNode.val = node;
// console.log(imageUrl.val, api.api_base);
// console.log(this);
}
app.registerExtension(ext);
+33
View File
@@ -0,0 +1,33 @@
/**
* @typedef {import('./van-1.1.3.min.js').State<T>} State<T>
* @template T
*/
/**
* @typedef {import('../../../web/types/litegraph.js').LGraphNode} LGraphNode
*
* @typedef {Object} Point
* @property {number} x - The x coordinate
* @property {number} y - The y coordinate
* @property {number} label - The label
*/
import { van } from './van.js';
export const iframeSrc = van.state('https://editor.avatech.ai');
export const showEditor = van.state(false);
export const showImageEditor = van.state(false);
export const imageUrl = van.state('');
export const point_label = van.state(1);
export const imageContainerSize = van.state({
width: 0,
height: 0,
});
/** @type {State<Point[]>} */
export const imagePrompts = van.state([]);
/** @type {State<LGraphNode>} */
export const targetNode = van.state();
export const imageSize = van.state({ width: 0, height: 0 });
+4
View File
@@ -0,0 +1,4 @@
import * as _van from 'https://cdn.jsdelivr.net/gh/vanjs-org/van/public/van-1.1.3.min.js';
/** @type {import('./van-1.1.3.min.js').Van} */
export const van = _van.default;