@underpostnet/underpost
Version:
Underpost Platform — end-to-end CI/CD and application-delivery toolchain CLI. Covers bare metal, Kubernetes, K3s, kubeadm, LXD, container/image orchestration, secrets, databases, cron jobs, monitoring, SSH, runners, PWA + Workbox delivery, and release orc
1,135 lines (1,113 loc) • 34.4 kB
JavaScript
import { getId, newInstance, range, rgbToHex, s4, splitEveryXChar } from './CommonJs.js';
import { CssCoreDark, CssCoreLight } from './CssCore.js';
import { DropDown } from './DropDown.js';
import { Modal } from './Modal.js';
import { Translate } from './Translate.js';
import { append, htmls, s, sa } from './VanillaJs.js';
import { getProxyPath } from './Router.js';
let ThemesScope = [];
// https://css.github.io/csso/csso.html
// https://www.fontspace.com/
// https://www.1001fonts.com/
class Css {
// Menu button container transition styles
static menuButtonContainer = () => css`
.main-btn-menu {
transition: all 0.2s ease-in-out;
position: relative;
}
.main-btn-menu::after {
content: '';
position: absolute;
bottom: 0;
left: 50%;
width: 0;
height: 2px;
background: currentColor;
transition: all 0.2s ease-in-out;
transform: translateX(-50%);
}
.main-btn-menu:hover::after {
width: 60%;
}
.main-btn-menu.active {
background: rgba(255, 255, 255, 0.1);
}
.main-btn-menu.active::after {
width: 80%;
background: currentColor;
}
`;
static async loadThemes(themes = []) {
ThemesScope = [];
for (const themeOptions of themes) addTheme(themeOptions);
// if (!ThemesScope.find((t) => t.dark)) addTheme(CssCoreDark);
// if (!ThemesScope.find((t) => !t.dark)) addTheme(CssCoreLight);
if (ThemesScope.length === 0) {
addTheme(CssCoreLight);
addTheme(CssCoreDark);
}
const localStorageTheme = localStorage.getItem('_theme');
if (localStorageTheme && Themes[localStorageTheme]) {
const themeOption = ThemesScope.find((t) => t.theme === localStorageTheme);
if (themeOption) return await Css.instance(themeOption);
}
await Css.instance();
}
static async instance(options) {
if (!options) options = ThemesScope[0];
const { theme } = options;
// Inject menu button container styles
const styleId = 'menu-btn-container-styles';
if (!document.getElementById(styleId)) {
const style = document.createElement('style');
style.id = styleId;
style.textContent = Css.menuButtonContainer();
document.head.appendChild(style);
}
return await Themes[theme](options);
}
static async RenderSetting() {
return html` <div class="in section-mp">
${await DropDown.instance({
id: 'settings-theme',
value: Css.currentTheme,
label: html`${Translate.instance('theme')}`,
data: ThemesScope.map((themeOption) => {
return {
display: html`<i class="fa-solid fa-brush"></i> ${themeOption.theme}`,
value: themeOption.theme,
onClick: async () => await Themes[themeOption.theme](),
};
}),
})}
</div>`;
}
}
const barLabels = (options) => {
return {
img: {
close: html`<img
class="inl bar-default-modal-icon ${options.iconClass ? options.iconClass : ''}"
src="${getProxyPath()}assets/icons/close.png"
/>`,
maximize: html`<img
class="inl bar-default-modal-icon ${options.iconClass ? options.iconClass : ''}"
src="${getProxyPath()}assets/icons/maximize.png"
/>`,
minimize: html`<img
class="inl bar-default-modal-icon ${options.iconClass ? options.iconClass : ''}"
src="${getProxyPath()}assets/icons/minimize.png"
/>`,
restore: html`<img
class="inl bar-default-modal-icon ${options.iconClass ? options.iconClass : ''}"
src="${getProxyPath()}assets/icons/restore.png"
/>`,
menu: html`<img
class="inl bar-default-modal-icon ${options.iconClass ? options.iconClass : ''}"
src="${getProxyPath()}assets/icons/menu.png"
/>`,
},
fontawesome: {
close: html`<i class="fa-solid fa-xmark ${options.iconClass ? options.iconClass : ''}"></i>`,
maximize: html`<i class="fa-regular fa-square ${options.iconClass ? options.iconClass : ''}"></i>`,
minimize: html`<i class="fa-solid fa-window-minimize ${options.iconClass ? options.iconClass : ''}"></i>`,
restore: html`<i class="fa-regular fa-window-restore ${options.iconClass ? options.iconClass : ''}"></i>`,
menu: html`<i class="fa-solid fa-bars ${options.iconClass ? options.iconClass : ''}"></i>`,
},
default: {
close: html`X`,
maximize: html`▢`,
minimize: html`_`,
restore: html`□`,
menu: html`≡`,
},
};
};
const barConfig = (options) => {
const { barButtonsIconTheme } = options;
return {
buttons: {
close: {
disabled: false,
label: barLabels(options)[barButtonsIconTheme].close,
},
maximize: {
disabled: false,
label: barLabels(options)[barButtonsIconTheme].maximize,
},
minimize: {
disabled: false,
label: barLabels(options)[barButtonsIconTheme].minimize,
},
restore: {
disabled: false,
label: barLabels(options)[barButtonsIconTheme].restore,
},
menu: {
disabled: true,
label: barLabels(options)[barButtonsIconTheme].menu,
},
},
};
};
const renderDefaultWindowsModalButtonContent = (options) => {
const { barButtonsIconTheme, htmlRender } = options;
const barConfigInstance = barConfig(options);
if (htmlRender)
Object.keys(Modal.Data).map((idModal) => {
if (s(`.btn-minimize-${idModal}`)) htmls(`.btn-minimize-${idModal}`, barConfigInstance.buttons.minimize.label);
if (s(`.btn-restore-${idModal}`)) htmls(`.btn-restore-${idModal}`, barConfigInstance.buttons.restore.label);
if (s(`.btn-maximize-${idModal}`)) htmls(`.btn-maximize-${idModal}`, barConfigInstance.buttons.maximize.label);
if (s(`.btn-close-${idModal}`)) htmls(`.btn-close-${idModal}`, barConfigInstance.buttons.close.label);
if (s(`.btn-menu-${idModal}`)) htmls(`.btn-menu-${idModal}`, barConfigInstance.buttons.menu.label);
});
return { barConfig: barConfigInstance };
};
let darkTheme = true;
const ThemeEvents = {};
const TriggerThemeEvents = () => {
localStorage.setItem('_theme', Css.currentTheme);
Object.keys(ThemeEvents).map((keyEvent) => ThemeEvents[keyEvent]());
};
const Themes = {};
const addTheme = (options) => {
ThemesScope.push(options);
Themes[options.theme] = async (barOptions) => {
if (!options.dark) options.dark = false;
if (!options.barButtonsIconTheme) options.barButtonsIconTheme = 'fontawesome';
const htmlRender = Css.currentTheme !== options.theme;
if (htmlRender) {
Css.currentTheme = options.theme;
darkTheme = options.dark;
let render = '';
if (!['core', 'css-core'].includes(options.theme))
render += darkTheme ? await CssCoreDark.render() : await CssCoreLight.render();
render += await options.render();
render += await subThemeManager.render();
htmls('.theme', render);
TriggerThemeEvents();
}
return {
...renderDefaultWindowsModalButtonContent({
barButtonsIconTheme: options.barButtonsIconTheme,
htmlRender,
...barOptions,
}),
};
};
};
const borderChar = (px, color, selectors, hover = false) => {
if (selectors) {
return selectors
.map(
(selector) => html`
<style>
${selector}${hover ? ':hover' : ''} {
text-shadow:
${px}px -${px}px ${px}px ${color},
-${px}px ${px}px ${px}px ${color},
-${px}px -${px}px ${px}px ${color},
${px}px ${px}px ${px}px ${color};
}
</style>
`,
)
.join('');
}
return html`
text-shadow: ${px}px -${px}px ${px}px ${color}, -${px}px ${px}px ${px}px ${color}, -${px}px -${px}px ${px}px
${color}, ${px}px ${px}px ${px}px ${color};
`;
};
const boxShadow = ({ selector }) => html`
${darkTheme
? html`
<style>
${selector} {
box-shadow:
0 4px 8px 0 rgba(255, 255, 255, 0.1),
0 6px 20px 0 rgba(255, 255, 255, 0.08);
}
${selector}:hover {
box-shadow:
0 8px 16px 0 rgba(255, 255, 255, 0.15),
0 10px 30px 0 rgba(255, 255, 255, 0.1);
}
</style>
`
: html`
<style>
${selector} {
box-shadow:
0 4px 8px 0 rgba(0, 0, 0, 0.2),
0 6px 20px 0 rgba(0, 0, 0, 0.19);
}
${selector}:hover {
box-shadow:
0 8px 16px 0 rgba(0, 0, 0, 0.2),
0 10px 30px 0 rgba(0, 0, 0, 0.3);
}
</style>
`}
`;
const renderMediaQuery = (mediaData) => {
// first limit should be '0'
return html`
${mediaData
.map(
(mediaState) => html`
<style>
@media only screen and (min-width: ${mediaState.limit}px) {
${mediaState.css}
}
</style>
`,
)
.join('')}
`;
};
const renderStatus = (status, options) => {
switch (status) {
case 'success':
return html`<div class="${options?.class ? options.class : 'abs center'}">
<i style="color: green" class="fa-solid fa-check"></i>
</div>`;
case 'error':
return html`<div class="${options?.class ? options.class : 'abs center'}">
<i style="color: red" class="fa-solid fa-xmark"></i>
</div>`;
case 'warning':
return html`<div class="${options?.class ? options.class : 'abs center'}">
<i style="color: yellow" class="fa-solid fa-triangle-exclamation"></i>
</div>`;
default:
return html``;
}
};
const dynamicColTokens = {};
const dynamicCol = (options = { containerSelector: '', id: '', type: '', limit: 900 }) => {
const { containerSelector, id } = options;
const limitCol = options?.limit ? options.limit : 900;
if (!(id in dynamicColTokens)) dynamicColTokens[id] = {};
dynamicColTokens[id].options = options;
if (dynamicColTokens[id].observer) dynamicColTokens[id].observer.disconnect();
setTimeout(() => {
dynamicColTokens[id].observer = new ResizeObserver(() => {
if (s(`.${containerSelector}`)) {
switch (options.type) {
case 'a-50-b-50':
if (s(`.${containerSelector}`).offsetWidth < limitCol)
htmls(
`.style-${id}-col`,
css`
.${id}-col-a, .${id}-col-b {
width: 100%;
}
`,
);
else
htmls(
`.style-${id}-col`,
css`
.${id}-col-a {
width: 50%;
}
.${id}-col-b {
width: 50%;
}
`,
);
break;
case 'search-inputs':
if (s(`.${containerSelector}`).offsetWidth < limitCol)
htmls(
`.style-${id}-col`,
css`
.${id}-col-a, .${id}-col-b, .${id}-col-c, .${id}-col-d {
width: 100%;
}
`,
);
else
htmls(
`.style-${id}-col`,
css`
.${id}-col-a {
width: 30%;
}
.${id}-col-b {
width: 30%;
}
.${id}-col-c {
width: 30%;
}
.${id}-col-d {
width: 10%;
}
`,
);
break;
default:
if (s(`.${containerSelector}`).offsetWidth < 900)
htmls(
`.style-${id}-col`,
css`
.${id}-col-a, .${id}-col-b {
width: 100%;
}
`,
);
else
htmls(
`.style-${id}-col`,
css`
.${id}-col-a {
width: 30%;
}
.${id}-col-b {
width: 70%;
}
`,
);
break;
}
} else {
dynamicColTokens[id].observer.disconnect();
delete dynamicColTokens[id];
if (s(`.style-${id}-col`)) s(`.style-${id}-col`).remove();
}
});
dynamicColTokens[id].observer.observe(s(`.${containerSelector}`));
});
return html` <style class="style-${id}-col"></style>`;
};
const renderBubbleDialog = async function (
options = {
id: '',
html: async () => '',
classSelectors,
triangleType: 'down',
trianglePositionCss: '',
triangleCss: '',
triangleDim: 0,
bubbleCss: '',
},
) {
const { id, html } = options;
let cssTrianglePosition = `
bottom: -45px;
left: 5px;
`;
let whiteTriangleStyle = `top: 43%`;
let blackTriangleStyle = ``;
switch (options.triangleType) {
case 'right':
cssTrianglePosition = `
right: -40px;
top: 5px;
`;
blackTriangleStyle = `
top: 43%;
left: 57%;
`;
break;
default:
break;
}
if (options.trianglePositionCss) cssTrianglePosition = options.trianglePositionCss;
return html` <div
class="${options?.classSelectors ? options.classSelectors : 'inl'} bubble-dialog bubble-dialog-${id}"
${options.bubbleCss ? `style='${options.bubbleCss}'` : ''}
>
<style class="style-bubble-dialog-triangle-${id}">
.bubble-dialog-triangle-${id} {
width: ${options.triangleDim ? options.triangleDim : 60}px;
height: ${options.triangleDim ? options.triangleDim : 60}px;
/* border: 2px solid red; */
box-sizing: border-box;
${cssTrianglePosition}
}
</style>
<div class="abs bubble-dialog-triangle bubble-dialog-triangle-${id}">
<div class="abs center" style="${blackTriangleStyle}">
${triangle[options?.triangleType ? options.triangleType : 'down']({
dim: 25,
id: id + '-triangle-black',
color: 'black',
classList: 'inl',
customStyle: options.triangleCss,
})}
</div>
<div class="abs center" style="${whiteTriangleStyle}">
${triangle[options?.triangleType ? options.triangleType : 'down']({
dim: 24,
id: id + '-triangle-white',
color: 'white',
classList: 'inl',
customStyle: options.triangleCss,
})}
</div>
</div>
${await html()}
</div>`;
};
const typeWriter = async function ({ id, html, seconds, endHideBlink, container }) {
if (!seconds) seconds = 2;
return new Promise((resolve) => {
// https://developer.mozilla.org/en-US/docs/Web/CSS/animation-timing-function
// https://www.w3schools.com/cssref/css3_pr_animation-fill-mode.php
const typingAnimationTransitionStyle = [
`1s linear`,
`${seconds}s steps(${html.split(' ').length * 6}, end)`,
`1s forwards`,
];
const render = html`
<style class="style-${id}">
.tw-${id}-typed-out {
overflow: hidden;
border-right: 0.15em solid orange;
white-space: nowrap;
animation:
typing-${id} ${typingAnimationTransitionStyle[1]},
blink-caret-${id} 0.5s step-end infinite;
animation-fill-mode: forwards;
width: 0;
}
</style>
<style>
.tw-${id}-container {
}
@keyframes typing-${id} {
from {
width: 0;
}
to {
width: 100%;
}
}
@keyframes blink-caret-${id} {
from,
to {
border-color: transparent;
}
50% {
border-color: orange;
}
}
</style>
<div class="inl tw-${id}-container">
<div class="tw-${id}-typed-out">${html}</div>
</div>
`;
htmls(`.${container}`, render);
setTimeout(() => {
if (endHideBlink && s(`.style-${id}`)) s(`.style-${id}`).remove();
resolve(render);
}, seconds * 1000);
});
};
const renderCssAttr = (options) =>
`${
options && options.style
? Object.keys(options.style)
.map((keyStyle) => `${keyStyle}: ${options.style[keyStyle]};`)
.join(`\n`)
: ''
}`;
const renderStyleTag = (styleSelector = 'style-abc', selector, options) =>
html`<style class="${styleSelector}">
${selector} { ${renderCssAttr(options)} }
</style>`;
function getTranslate3d(el) {
const values = el.style.transform.split(/\w+\(|\);?/);
if (!values[1] || !values[1].length) {
return [];
}
return values[1].split(/,\s?/g);
}
const dashRange = ({ selector, color }) => {
return html`
<style>
.${selector} {
background:
linear-gradient(90deg, ${color} 50%, transparent 50%), linear-gradient(90deg, ${color} 50%, transparent 50%),
linear-gradient(0deg, ${color} 50%, transparent 50%), linear-gradient(0deg, ${color} 50%, transparent 50%);
background-repeat: repeat-x, repeat-x, repeat-y, repeat-y;
background-size:
16px 4px,
16px 4px,
4px 16px,
4px 16px;
background-position:
0% 0%,
100% 100%,
0% 100%,
100% 0px;
border-radius: 5px;
padding: 10px;
animation: ${selector}_dash_range 5s linear infinite;
}
@keyframes ${selector}_dash_range {
to {
background-position:
100% 0%,
0% 100%,
0% 0%,
100% 100%;
}
}
</style>
`;
};
const triangle = {
up: ({ id, dim, color, classList, customStyle }) => {
return html`<style class="style-${id}">
.arrow-up-${id} {
width: 0;
height: 0;
border-left: ${dim}px solid transparent;
border-right: ${dim}px solid transparent;
border-bottom: ${dim}px solid ${color};
}
</style>
<div
class="arrow-up-${id} ${classList}"
${customStyle ? `style="${customStyle ? customStyle : ''}"` : ''}
></div>`;
},
down: ({ id, dim, color, classList, customStyle }) => {
return html`<style class="style-${id}">
.arrow-down-${id} {
width: 0;
height: 0;
border-left: ${dim}px solid transparent;
border-right: ${dim}px solid transparent;
border-top: ${dim}px solid ${color};
}
</style>
<div
class="arrow-down-${id} ${classList}"
${customStyle ? `style="${customStyle ? customStyle : ''}"` : ''}
></div>`;
},
right: ({ id, dim, color, classList, customStyle }) => {
return html` <style class="style-${id}">
.arrow-right-${id} {
width: 0;
height: 0;
border-top: ${dim}px solid transparent;
border-bottom: ${dim}px solid transparent;
border-left: ${dim}px solid ${color};
}
</style>
<div
class="arrow-right-${id} ${classList}"
${customStyle ? `style="${customStyle ? customStyle : ''}"` : ''}
></div>`;
},
left: ({ id, dim, color, classList, customStyle }) => {
return html`<style class="style-${id}">
.arrow-left-${id} {
width: 0;
height: 0;
border-top: ${dim}px solid transparent;
border-bottom: ${dim}px solid transparent;
border-right: ${dim}px solid ${color};
}
</style>
<div
class="arrow-left-${id} ${classList}"
${customStyle ? `style="${customStyle ? customStyle : ''}"` : ''}
></div>`;
},
};
const getSectionsStringData = (offsetWidth, text) => {
const sectionsIndex = [];
const everyXChar = parseInt(offsetWidth / 4);
const phraseArray = text
.split('.')
.map((t) => splitEveryXChar(t + '.', everyXChar, ['.', ' ']))
.flat()
.filter((p) => p !== '.' && p.trim());
let currentIndex = [0];
let pi = -1;
for (const p of phraseArray) {
pi++;
if (p.indexOf('.') !== -1) {
currentIndex.push(newInstance(pi));
sectionsIndex.push(newInstance(currentIndex));
if (phraseArray[pi + 1]) currentIndex = [newInstance(pi + 1)];
else currentIndex = [];
}
}
if (currentIndex[0] && !currentIndex[1]) {
currentIndex[1] = phraseArray.length - 1;
sectionsIndex.push(newInstance(currentIndex));
}
return { phraseArray, sectionsIndex };
};
const typeWriteSectionsString = ({ container, phraseArray, rangeArraySectionIndex }) =>
new Promise((resolve) => {
let cumulativeSeconds = 0;
for (const index of range(...rangeArraySectionIndex)) {
const subIdSalt = s4() + s4() + s4();
const seconds = phraseArray[index].trim().length * 0.05;
append(`.${container}`, html` <div class="${container}-${subIdSalt}"></div> `);
setTimeout(async () => {
if (s(`.${container}-${subIdSalt}`)) {
append(`.${container}-${subIdSalt}`, html` <div class="render-typeWriter-${container}-${subIdSalt}"></div> `);
await typeWriter({
id: `typeWriter-${index}-${container}`,
html: phraseArray[index].trim(),
endHideBlink: index < rangeArraySectionIndex[1],
seconds,
container: `render-typeWriter-${container}-${subIdSalt}`,
});
}
if (index === rangeArraySectionIndex[1]) resolve();
}, cumulativeSeconds * 1000);
cumulativeSeconds += seconds;
}
});
const cssBrowserCodes = ['webkit', 'moz', 'ms', 'o'];
const scrollBarLightRender = () => {
return cssBrowserCodes
.map(
(b) =>
html`<style>
::-` +
b +
`-scrollbar {
width: 5px;
height: 5px;
}
/* Track */
::-` +
b +
`-scrollbar-track {
background: none !important;
}
/* Handle */
::-` +
b +
`-scrollbar-thumb {
background: #15151557;
border-radius: 3px;
}
/* Handle on hover */
::-` +
b +
`-scrollbar-thumb:hover {
background: #4d4d4dbb;
}
</style>`,
)
.join('');
};
// adjustHex: supports #RGB #RGBA #RRGGBB #RRGGBBAA
// preserves alpha channel if present (does not modify it)
// usage: adjustHex('#24FBFFFF', 0.1)
function adjustHex(hex, factor = 0.1, options = {}) {
if (typeof hex !== 'string') throw new TypeError('hex must be a string');
if (typeof factor !== 'number') throw new TypeError('factor must be a number');
// normalize factor: allow -100..100 or -1..1
if (factor > 1 && factor <= 100) factor = factor / 100;
if (factor < -1 && factor >= -100) factor = factor / 100;
factor = Math.max(-1, Math.min(1, factor));
const mode = options.mode === 'hsl' ? 'hsl' : 'mix';
// normalize hex: accept 3,4,6,8 (with or without #)
let h = hex.replace(/^#/, '').trim();
if (![3, 4, 6, 8].includes(h.length)) {
throw new Error('Invalid hex format — expected 3, 4, 6 or 8 hex digits');
}
// expand shorthand (#RGB or #RGBA -> #RRGGBB or #RRGGBBAA)
if (h.length === 3 || h.length === 4) {
h = h
.split('')
.map((c) => c + c)
.join('');
}
const hasAlpha = h.length === 8;
const r = parseInt(h.slice(0, 2), 16);
const g = parseInt(h.slice(2, 4), 16);
const b = parseInt(h.slice(4, 6), 16);
const a = hasAlpha ? parseInt(h.slice(6, 8), 16) : null; // keep alpha as-is if present
const clamp = (v, a0 = 0, z = 255) => Math.max(a0, Math.min(z, v));
const rgbToHex = (rr, gg, bb, aa = null) => {
const parts = [rr, gg, bb].map((v) => Math.round(v).toString(16).padStart(2, '0'));
if (aa !== null) parts.push(Math.round(aa).toString(16).padStart(2, '0'));
return '#' + parts.join('').toLowerCase();
};
if (mode === 'mix') {
// positive: mix toward white (255); negative: mix toward black (0)
const mixChannel = (c) => {
if (factor >= 0) {
return clamp(Math.round(c + (255 - c) * factor));
} else {
const aFactor = Math.abs(factor);
return clamp(Math.round(c * (1 - aFactor)));
}
};
const rr = mixChannel(r);
const gg = mixChannel(g);
const bb = mixChannel(b);
return rgbToHex(rr, gg, bb, a);
} else {
// HSL mode: convert rgb to hsl, adjust L by factor, convert back
const rgbToHsl = (r, g, b) => {
r /= 255;
g /= 255;
b /= 255;
const max = Math.max(r, g, b),
min = Math.min(r, g, b);
let h = 0,
s = 0,
l = (max + min) / 2;
if (max !== min) {
const d = max - min;
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
switch (max) {
case r:
h = (g - b) / d + (g < b ? 6 : 0);
break;
case g:
h = (b - r) / d + 2;
break;
case b:
h = (r - g) / d + 4;
break;
}
h /= 6;
}
return { h, s, l };
};
const hslToRgb = (h, s, l) => {
let r, g, b;
if (s === 0) {
r = g = b = l;
} else {
const hue2rgb = (p, q, t) => {
if (t < 0) t += 1;
if (t > 1) t -= 1;
if (t < 1 / 6) return p + (q - p) * 6 * t;
if (t < 1 / 2) return q;
if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
return p;
};
const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
const p = 2 * l - q;
r = hue2rgb(p, q, h + 1 / 3);
g = hue2rgb(p, q, h);
b = hue2rgb(p, q, h - 1 / 3);
}
return { r: r * 255, g: g * 255, b: b * 255 };
};
const { h: hh, s: ss, l: ll } = rgbToHsl(r, g, b);
// add factor to lightness (factor already normalized -1..1)
let newL = ll + factor;
newL = Math.max(0, Math.min(1, newL));
const { r: r2, g: g2, b: b2 } = hslToRgb(hh, ss, newL);
return rgbToHex(r2, g2, b2, a);
}
}
// Examples (uncomment to test):
// console.log(adjustHex('#24FBFFFF', 0.1)); // accepts 8-digit input
// console.log(adjustHex('#24FBFF', 0.1)); // 6-digit
// console.log(adjustHex('#4bf', -0.2)); // 3-digit
// console.log(adjustHex('#4bf8', -0.2)); // 4-digit (with alpha)
// Convenience helpers:
function lightenHex(hex, percentOr01 = 0.1, options = {}) {
return adjustHex(hex, Math.abs(percentOr01), options);
}
function darkenHex(hex, percentOr01 = 0.1, options = {}) {
return adjustHex(hex, -Math.abs(percentOr01), options);
}
const subThemeManager = {
render: async function () {
if (darkTheme && this.renderDark) {
return await this.renderDark();
} else if (!darkTheme && this.renderLight) {
return await this.renderLight();
}
return html``;
},
lightColor: null,
setLightTheme: function (color) {
this.lightColor = color;
this.renderLight = async function () {
return html`<style>
button:hover,
.a-btn:hover,
.main-btn-menu-active,
.top-bar-search-box-container:hover {
color: ${this.lightColor};
background-color: ${lightenHex(this.lightColor, 0.8)};
}
.top-bar-search-box-container i {
color: #000;
}
.top-bar-search-box-container:hover i {
color: ${this.lightColor};
}
.main-sub-btn-active {
color: ${this.lightColor};
background-color: rgba(0, 0, 0, 0.3);
}
.main-sub-btn-active:hover {
color: ${this.lightColor};
background-color: rgba(0, 0, 0, 0.2);
}
</style>`;
};
},
darkColor: null,
setDarkTheme: function (color) {
this.darkColor = color;
this.renderDark = async function () {
return html`<style>
button:hover,
.a-btn:hover,
.main-btn-menu-active,
.top-bar-search-box-container:hover {
color: ${lightenHex(this.darkColor, 0.8)};
background-color: ${darkenHex(this.darkColor, 0.75)};
}
.top-bar-search-box-container i {
color: #fff;
}
.top-bar-search-box-container:hover i {
color: ${lightenHex(this.darkColor, 0.8)};
}
.main-sub-btn-active {
color: ${lightenHex(this.darkColor, 0.8)};
background-color: rgba(255, 255, 255, 0.3);
}
.main-sub-btn-active:hover {
color: ${lightenHex(this.darkColor, 0.8)};
background-color: rgba(255, 255, 255, 0.2);
}
</style>`;
};
},
};
const scrollBarDarkRender = () => {
return cssBrowserCodes
.map(
(b) =>
html`<style>
::-` +
b +
`-scrollbar {
width: 8px;
height: 8px;
/* line-height: 1em; */
}
/* Track */
::-` +
b +
`-scrollbar-track {
background: none !important;
}
/* Handle */
::-` +
b +
`-scrollbar-thumb {
background: #74747457;
border-radius: 4px;
}
/* Handle on hover */
::-` +
b +
`-scrollbar-thumb:hover {
background: #98989857;
}
</style>`,
)
.join('');
};
const renderWave = ({ id }) => {
return html`
<style>
.wave-animation-container-${id} {
height: 200px;
}
.wave-animation-container-${id} {
background: linear-gradient(
315deg,
rgba(101, 0, 94, 1) 3%,
rgba(60, 132, 206, 1) 38%,
rgba(48, 238, 226, 1) 68%,
rgba(255, 25, 25, 1) 98%
);
animation: gradient-${id} 15s ease infinite;
background-size: 400% 400%;
overflow: hidden;
}
@keyframes gradient-${id} {
0% {
background-position: 0% 0%;
}
50% {
background-position: 100% 100%;
}
100% {
background-position: 0% 0%;
}
}
.wave-${id} {
background: rgb(255 255 255 / 25%);
border-radius: 1000% 1000% 0 0;
width: 200%;
height: 12em;
animation: wave-${id} 10s -3s linear infinite;
opacity: 0.8;
bottom: 0;
left: 0;
top: 30%;
}
.wave-${id}:nth-of-type(2) {
animation: wave-${id} 18s linear reverse infinite;
opacity: 0.8;
top: 50%;
}
.wave-${id}:nth-of-type(3) {
animation: wave-${id} 20s -1s reverse infinite;
opacity: 0.9;
top: 70%;
}
@keyframes wave-${id} {
2% {
transform: translateX(1);
}
25% {
transform: translateX(-25%);
}
50% {
transform: translateX(-50%);
}
75% {
transform: translateX(-25%);
}
100% {
transform: translateX(1);
}
}
</style>
<div class="in wave-animation-container-${id} ${id}">
<div class="in wave-${id}"></div>
<div class="abs wave-${id}"></div>
<div class="abs wave-${id}"></div>
</div>
`;
};
const cssTokensEffect = {};
const cssTokensContainer = {};
const cssEffect = async (containerSelector, event) => {
// Array.from(event.target.classList)
let offsetX, offsetY;
if (Array.from(event.srcElement.classList).includes('ripple') && cssTokensContainer[containerSelector]) {
offsetX = cssTokensContainer[containerSelector].lastOffsetX;
offsetY = cssTokensContainer[containerSelector].lastOffsetY;
} else {
cssTokensContainer[containerSelector] = { lastOffsetX: event.offsetX, lastOffsetY: event.offsetY };
offsetX = event.offsetX;
offsetY = event.offsetY;
}
const element = s(containerSelector);
// element.style.overflow = 'hidden';
const id = getId(cssTokensEffect, 'btn-effect-');
cssTokensEffect[id] = { containerSelector, event };
append(containerSelector, html`<span class="abs ${id} ripple" style="display: none"></span>`);
const circle = s(`.${id}`);
circle.style.width = circle.style.height = `40px`;
circle.style.left = `${offsetX}px`;
circle.style.top = `${offsetY}px`;
circle.style.display = null;
setTimeout(() => {
circle.remove();
delete cssTokensEffect[id];
}, 600);
};
const imageShimmer = () =>
html`<div
class="abs center ssr-shimmer-search-box"
style="${renderCssAttr({
style: {
width: '95%',
height: '95%',
'border-radius': '10px',
overflow: 'hidden',
},
})}"
>
<div
class="abs center"
style="${renderCssAttr({
style: {
'font-size': '70px',
color: `#bababa`,
},
})}"
>
<i class="fa-solid fa-photo-film"></i>
</div>
</div>`;
const renderChessPattern = (patternSize = 20) =>
`background: repeating-conic-gradient(#808080 0 25%, #0000 0 50%) 50% / ${patternSize}px ${patternSize}px`;
const extractBackgroundImageUrl = (element) => {
const style = window.getComputedStyle(element);
const imageString = style.backgroundImage;
const foundUrlRaw = imageString.match(/^url\(?(.+)\)$/i)[1];
if (!foundUrlRaw) return null;
const foundUrl = foundUrlRaw.replace(/^['|"| ]*/, '').replace(/['" ]*$/, '');
if (!foundUrl) return null;
return foundUrl;
};
const simpleIconsRender = (selector) => {
sa(selector).forEach((el) => {
el.src = `https://cdn.simpleicons.org/coveralls/${rgbToHex(window.getComputedStyle(s('html')).color)}`;
});
};
const styleFactory = (payload, plain = '') => `style="${renderCssAttr({ style: payload })} ${plain}"`;
export {
Css,
Themes,
barLabels,
barConfig,
borderChar,
renderMediaQuery,
renderDefaultWindowsModalButtonContent,
renderStatus,
dynamicCol,
dynamicColTokens,
boxShadow,
addTheme,
darkTheme,
ThemeEvents,
TriggerThemeEvents,
ThemesScope,
renderBubbleDialog,
typeWriter,
renderStyleTag,
renderCssAttr,
getTranslate3d,
dashRange,
triangle,
getSectionsStringData,
typeWriteSectionsString,
cssBrowserCodes,
scrollBarDarkRender,
scrollBarLightRender,
renderWave,
cssEffect,
imageShimmer,
simpleIconsRender,
extractBackgroundImageUrl,
renderChessPattern,
subThemeManager,
lightenHex,
darkenHex,
adjustHex,
styleFactory,
};