Files
nathannlu-ComfyUI-Cloud/js/lib/van-ui.js
T
2024-03-23 14:58:46 -07:00

439 lines
19 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import van from "./van.js";
// Quote all tag names so that they're not mangled by minifier
const { "button": button, "div": div, "header": header, "input": input, "label": label, "span": span, "style": style } = van.tags;
const toStyleStr = (style) => Object.entries(style).map(([k, v]) => `${k}: ${v};`).join("");
const toStyleSheet = (styles) => {
return Object.entries(styles)
.map(([selector, props]) => `${selector} { ${toStyleStr(props)} }`)
.join("\n");
};
const stateProto = Object.getPrototypeOf(van.state(null));
const stateOf = (v) => (Object.getPrototypeOf(v ?? 0) === stateProto ? v : van.state(v));
export const Await = ({ value, container = div, Loading, Error }, children) => {
const data = van.state({ status: "pending" });
value
.then(result => data.val = { status: "fulfilled", value: result })
.catch(err => data.val = { status: "rejected", value: err });
return container(() => data.val.status === "pending" ? Loading?.() ?? "" :
data.val.status === "rejected" ? Error?.(data.val.value) :
children(data.val.value));
};
export const Modal = ({ closed, backgroundColor = "rgba(0,0,0,.5)", blurBackground = false, backgroundClass = "", backgroundStyleOverrides = {}, modalClass = "", modalStyleOverrides = {}, }, ...children) => {
const backgroundStyle = {
display: "flex",
"align-items": "center",
"justify-content": "center",
left: 0,
right: 0,
top: 0,
bottom: 0,
position: "fixed",
"z-index": 10000,
"background-color": backgroundColor,
"backdrop-filter": blurBackground ? "blur(0.25rem)" : "none",
...backgroundStyleOverrides,
};
const modalStyle = {
"border-radius": "0.5rem",
padding: "1rem",
display: "block",
"background-color": "white",
...modalStyleOverrides,
};
return () => closed.val ? null : div({ class: backgroundClass, style: toStyleStr(backgroundStyle) }, div({ class: modalClass, style: toStyleStr(modalStyle) }, children));
};
let tabsId = 0;
export const Tabs = ({ activeTab, resultClass = "", style = "", tabButtonRowColor = "#f1f1f1", tabButtonBorderStyle = "1px solid #000", tabButtonHoverColor = "#ddd", tabButtonActiveColor = "#ccc", transitionSec = 0.3, tabButtonRowClass = "", tabButtonRowStyleOverrides = {}, tabButtonClass = "", tabButtonStyleOverrides = {}, tabContentClass = "", tabContentStyleOverrides = {}, }, contents) => {
const activeTabState = activeTab ?? van.state(Object.keys(contents)[0]);
const tabButtonRowStylesStr = toStyleStr({
overflow: "hidden",
"background-color": tabButtonRowColor,
...tabButtonRowStyleOverrides,
});
const tabButtonStylesStr = toStyleStr({
float: "left",
border: "none",
"border-right": tabButtonBorderStyle,
outline: "none",
cursor: "pointer",
padding: "8px 16px",
transition: `background-color ${transitionSec}s`,
...tabButtonStyleOverrides,
});
const tabContentStylesStr = toStyleStr({
padding: "6px 12px",
"border-top": "none",
...tabContentStyleOverrides,
});
const id = "vanui-tabs-" + (++tabsId);
document.head.appendChild(van.tags["style"](`#${id} .vanui-tab-button { background-color: inherit }
#${id} .vanui-tab-button:hover { background-color: ${tabButtonHoverColor} }
#${id} .vanui-tab-button.active { background-color: ${tabButtonActiveColor} }`));
return div({ id, class: resultClass, style }, div({ class: tabButtonRowClass, style: tabButtonRowStylesStr }, Object.keys(contents).map(k => button({
class: () => ["vanui-tab-button"].concat(tabButtonClass ? tabButtonClass : [], k === activeTabState.val ? "active" : []).join(" "),
style: tabButtonStylesStr,
onclick: () => activeTabState.val = k,
}, k))), Object.entries(contents).map(([k, v]) => div({
class: tabContentClass,
style: () => `display: ${k === activeTabState.val ? "block" : "none"}; ${tabContentStylesStr}`,
}, v)));
};
export const Toggle = ({ on = false, size = 1, cursor = "pointer", offColor = "#ccc", onColor = "#2196F3", circleColor = "white", toggleClass = "", toggleStyleOverrides = {}, sliderClass = "", sliderStyleOverrides = {}, circleClass = "", circleStyleOverrides = {}, circleWhenOnStyleOverrides = {}, }) => {
const onState = stateOf(on);
const toggleStylesStr = toStyleStr({
position: "relative",
display: "inline-block",
width: 1.76 * size + "rem",
height: size + "rem",
cursor,
...toggleStyleOverrides,
});
const inputStylesStr = toStyleStr({
opacity: 0,
width: 0,
height: 0,
position: "absolute",
"z-index": 10000, // Ensures the toggle clickable
});
const sliderStylesStr = toStyleStr({
position: "absolute",
top: 0,
left: 0,
right: 0,
bottom: 0,
transition: ".4s",
"border-radius": size + "rem",
...sliderStyleOverrides,
});
const circleStylesStr = toStyleStr({
position: "absolute",
height: 0.76 * size + "rem",
width: 0.76 * size + "rem",
left: 0.12 * size + "rem",
bottom: 0.12 * size + "rem",
"background-color": circleColor,
transition: ".4s",
"border-radius": "50%",
...circleStyleOverrides,
});
const circleStylesWhenOnStr = toStyleStr({
transform: `translateX(${0.76 * size}rem)`,
...circleWhenOnStyleOverrides,
});
return label({ class: toggleClass, style: toggleStylesStr }, input({ type: "checkbox", style: inputStylesStr, oninput: e => onState.val = e.target.checked }), span({
class: sliderClass,
style: () => `${sliderStylesStr}; background-color: ${onState.val ? onColor : offColor};`,
}, span({
class: circleClass,
style: () => circleStylesStr + (onState.val ? circleStylesWhenOnStr : ""),
})));
};
export class MessageBoard {
_fadeOutSec;
_messageClass;
_messageStylesStr;
_closerClass;
_closerStylesStr;
_dom;
constructor({ top = "unset", bottom = "unset", backgroundColor = "#333D", fontColor = "white", fadeOutSec = 0.3, boardClass = "", boardStyleOverrides = {}, messageClass = "", messageStyleOverrides = {}, closerClass = "", closerStyleOverrides = {}, }, parentDom = document.body) {
const boardStylesStr = toStyleStr({
display: "flex",
"flex-direction": "column",
"align-items": "center",
position: "fixed",
top,
bottom,
left: "50%",
transform: "translateX(-50%)",
"z-index": 10000,
...boardStyleOverrides,
});
this._fadeOutSec = fadeOutSec;
this._messageClass = messageClass;
this._messageStylesStr = toStyleStr({
display: "flex",
"background-color": backgroundColor,
color: fontColor,
padding: "15px",
"margin-bottom": "10px",
"border-radius": "5px",
transition: `opacity ${fadeOutSec}s, transform ${fadeOutSec}s`,
...messageStyleOverrides,
});
this._closerClass = closerClass;
this._closerStylesStr = toStyleStr({
display: "flex",
"align-items": "center",
"margin-left": "10px",
cursor: "pointer",
...closerStyleOverrides
});
parentDom.appendChild(this._dom = div({ class: boardClass, style: boardStylesStr }));
}
show({ message, closer, durationSec, closed = van.state(false), }) {
const removed = van.state(false);
van.derive(() => setTimeout((v) => removed.val = v, this._fadeOutSec * 1000, closed.val));
const msgDom = div({ class: this._messageClass, style: this._messageStylesStr }, div(message), closer ? div({ class: this._closerClass, style: this._closerStylesStr, onclick: () => closed.val = true }, closer) : null);
van.derive(() => closed.val && (msgDom.style.opacity = "0", msgDom.style.transform = "translateY(-20px)"));
if (durationSec)
setTimeout(() => closed.val = true, durationSec * 1000);
van.add(this._dom, () => removed.val ? null : msgDom);
return msgDom;
}
remove() { this._dom.remove(); }
}
export const Tooltip = ({ text, show, width = "200px", backgroundColor = "#333D", fontColor = "white", fadeInSec = 0.3, tooltipClass = "", tooltipStyleOverrides = {}, triangleClass = "", triangleStyleOverrides = {}, }) => {
const tooltipStylesStr = toStyleStr({
width,
visibility: "hidden",
"background-color": backgroundColor,
color: fontColor,
"text-align": "center",
padding: "5px",
"border-radius": "5px",
position: "absolute",
"z-index": 1,
bottom: "125%",
left: "50%",
transform: "translateX(-50%)",
opacity: 0,
transition: `opacity ${fadeInSec}s`,
...tooltipStyleOverrides,
});
const triangleStylesStr = toStyleStr({
width: 0,
height: 0,
"margin-left": "-5px",
"border-left": "5px solid transparent",
"border-right": "5px solid transparent",
"border-top": "5px solid #333",
position: "absolute",
bottom: "-5px",
left: "50%",
...triangleStyleOverrides,
});
const dom = span({ class: tooltipClass, style: tooltipStylesStr }, text, div({ class: triangleClass, style: triangleStylesStr }));
van.derive(() => show.val ?
(dom.style.opacity = "1", dom.style.visibility = "visible") :
(dom.style.opacity = "0", dom.style.visibility = "hidden"));
return dom;
};
let optionGroupId = 0;
export const OptionGroup = ({ selected, normalColor = "#e2eef7", hoverColor = "#c1d4e9", selectedColor = "#90b6d9", selectedHoverColor = "#7fa5c8", fontColor = "black", transitionSec = 0.3, optionGroupClass = "", optionGroupStyleOverrides = {}, optionClass = "", optionStyleOverrides = {}, }, options) => {
const buttonGroupStylesStr = toStyleStr({
display: "flex",
...optionGroupStyleOverrides,
});
const buttonStylesStr = toStyleStr({
padding: "10px 20px",
border: "none",
color: fontColor,
cursor: "pointer",
outline: "none",
transition: `background-color ${transitionSec}s`,
...optionStyleOverrides,
});
const id = "vanui-option-group-" + (++optionGroupId);
document.head.appendChild(van.tags["style"](`#${id} .vanui-button { background-color: ${normalColor} }
#${id} .vanui-button:hover { background-color: ${hoverColor} }
#${id} .vanui-button.selected { background-color: ${selectedColor} }
#${id} .vanui-button.selected:hover { background-color: ${selectedHoverColor} }`));
return div({ id, class: optionGroupClass, style: buttonGroupStylesStr }, options.map(o => button({
class: () => ["vanui-button"].concat(optionClass ? optionClass : [], o === selected.val ? "selected" : []).join(" "),
style: buttonStylesStr,
onclick: () => selected.val = o,
}, o)));
};
export const Banner = ({ backgroundColor = "#fff1a8", fontColor = "currentcolor", sticky = false, bannerClass = "", bannerStyleOverrides = {}, }, ...children) => {
const bannerStyleStr = toStyleStr({
"background-color": backgroundColor,
color: fontColor,
top: 0,
position: sticky ? "sticky" : "static",
"z-index": 10,
...bannerStyleOverrides,
});
return header({ class: bannerClass, style: bannerStyleStr }, children);
};
let curWindowZIndex = 0;
export const topMostZIndex = () => ++curWindowZIndex;
export const FloatingWindow = ({ title, closed = van.state(false), x = 100, y = 100, width = 300, height = 200, closeCross = "×", customStacking = false, zIndex = 1, disableMove = false, disableResize = false, headerColor = "lightgray", windowClass = "", windowStyleOverrides = {}, headerClass = "", headerStyleOverrides = {}, childrenContainerClass = "", childrenContainerStyleOverrides = {}, crossClass = "", crossStyleOverrides = {}, crossHoverClass = "", crossHoverStyleOverrides = {}, }, ...children) => {
const xState = stateOf(x), yState = stateOf(y);
const widthState = stateOf(width), heightState = stateOf(height);
const zIndexState = stateOf(zIndex);
if (!customStacking)
zIndexState.val = topMostZIndex();
const dragging = van.state(false), resizingDirection = van.state(null);
const startX = van.state(0), startY = van.state(0);
const startWidth = van.state(0), startHeight = van.state(0);
const crossHover = crossHoverClass || Object.keys(crossHoverStyleOverrides) ?
van.state(false) : null;
const onmousedown = (e) => {
if (e.button !== 0)
return;
dragging.val = true;
startX.val = e.clientX;
startY.val = e.clientY;
document.body.style.userSelect = "none";
};
const onResizeMouseDown = (direction) => (e) => {
resizingDirection.val = direction;
startX.val = e.clientX;
startY.val = e.clientY;
startWidth.val = widthState.val;
startHeight.val = heightState.val;
document.body.style.userSelect = "none";
};
const onMouseMove = (e) => {
if (dragging.val) {
xState.val += e.clientX - startX.val;
yState.val += e.clientY - startY.val;
startX.val = e.clientX;
startY.val = e.clientY;
}
else if (resizingDirection.val) {
const deltaX = e.clientX - startX.val;
const deltaY = e.clientY - startY.val;
if (resizingDirection.val.includes("right"))
widthState.val = startWidth.val + deltaX;
if (resizingDirection.val.includes("bottom"))
heightState.val = startHeight.val + deltaY;
}
};
const onMouseUp = () => {
dragging.val = false;
resizingDirection.val = null;
document.body.style.userSelect = "";
};
document.addEventListener("mousemove", onMouseMove);
document.addEventListener("mouseup", onMouseUp);
const grabAreaBgColor = "transparent";
if (!document.getElementById("vanui-window-style")) {
const staticStyles = style({ type: "text/css", id: "vanui-window-style" }, toStyleSheet({
".vanui-window": {
position: "fixed",
"background-color": "white",
border: "1px solid black",
"border-radius": "0.5rem",
overflow: "hidden",
},
".vanui-window-dragarea": {
cursor: "move",
position: "absolute",
left: "0",
top: "0",
width: "100%",
height: "1rem",
},
".vanui-window-resize-right": {
cursor: "e-resize",
position: "absolute",
right: "0",
top: "0",
width: "10px",
height: "100%",
"background-color": grabAreaBgColor,
},
".vanui-window-resize-bottom": {
cursor: "s-resize",
position: "absolute",
left: "0",
bottom: "0",
width: "100%",
height: "10px",
"background-color": grabAreaBgColor,
},
".vanui-window-resize-rightbottom": {
cursor: "se-resize",
position: "absolute",
right: "0",
bottom: "0",
width: "10px",
height: "10px",
"background-color": grabAreaBgColor,
},
".vanui-window-header": {
cursor: "move",
"user-select": "none",
display: "flex",
"justify-content": "space-between",
"align-items": "center",
padding: "0.5rem",
},
".vanui-window-cross": {
cursor: "pointer",
"font-family": "Arial",
transition: "background-color 0.3s, color 0.3s",
"border-radius": "50%",
width: "24px",
height: "24px",
display: "flex",
"align-items": "center",
"justify-content": "center",
},
".vanui-window-cross:hover": {
"background-color": "red",
color: "white",
},
".vanui-window-children": {
padding: "0.5rem",
}
}));
document.head.appendChild(staticStyles);
}
return () => closed.val ? null : div({
class: ["vanui-window"].concat(windowClass ? windowClass : []).join(" "),
style: () => toStyleStr({
left: `${xState.val}px`,
top: `${yState.val}px`,
width: `${widthState.val}px`,
height: `${heightState.val}px`,
"z-index": zIndexState.val,
...windowStyleOverrides,
}),
...(customStacking ? {} : { onmousedown: () => zIndexState.val = topMostZIndex() }),
}, title ? header({
class: ["vanui-window-header"].concat(headerClass ? headerClass : []).join(" "),
style: toStyleStr({
"background-color": headerColor,
...(disableMove ? { cursor: "auto" } : {}),
...headerStyleOverrides,
}),
...(disableMove ? {} : { onmousedown }),
}, title, closeCross ? span({
class: () => ["vanui-window-cross"]
.concat(crossClass ? crossClass : [])
.concat(crossHoverClass && crossHover.val ? crossHoverClass : [])
.join(" "),
style: () => toStyleStr({
...crossStyleOverrides,
...(Object.keys(crossHoverStyleOverrides).length && crossHover.val ?
crossHoverStyleOverrides : {}),
}),
onclick: () => closed.val = true,
...(crossHover ? {
onmouseenter: () => crossHover.val = true,
onmouseleave: () => crossHover.val = false,
} : {})
}, closeCross) : null) : disableMove ? null : div({
class: "vanui-window-dragarea",
onmousedown,
}), disableResize ? [] : [
div({
class: "vanui-window-resize-right",
onmousedown: onResizeMouseDown("right"),
}),
div({
class: "vanui-window-resize-bottom",
onmousedown: onResizeMouseDown("bottom"),
}),
div({
class: "vanui-window-resize-rightbottom",
onmousedown: onResizeMouseDown("rightbottom"),
}),
], div({
class: ["vanui-window-children"].concat(childrenContainerClass ? childrenContainerClass : []).join(" "),
style: toStyleStr(childrenContainerStyleOverrides)
}, children));
};