|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export function addNamedStyleSheet(name, css, force = false) { |
|
const existingStyleSheet = document.getElementById(name) |
|
|
|
if (existingStyleSheet && !force) { |
|
console.debug( |
|
`Stylesheet with name "${name}" already exists. Skipping addition.`, |
|
) |
|
return |
|
} |
|
|
|
if (existingStyleSheet && force) { |
|
console.debug(`Stylesheet with name "${name}" exists. Replacing...`) |
|
existingStyleSheet.remove() |
|
} |
|
|
|
const styleElement = document.createElement('style') |
|
styleElement.id = name |
|
styleElement.type = 'text/css' |
|
|
|
styleElement.appendChild(document.createTextNode(css)) |
|
document.head.appendChild(styleElement) |
|
|
|
console.debug(`Stylesheet with name "${name}" added.`) |
|
} |
|
|
|
export const ensureMTBStyles = () => { |
|
const S = { |
|
fg: 'var(--fg-color)', |
|
bgi: 'var(--comfy-input-bg)', |
|
bgm: 'var(--comfy-menu-bg)', |
|
border: 'var(--comfy-border)', |
|
borderHover: 'var(--comfy-border-hover)', |
|
box: 'var(--comfy-box)', |
|
accent: 'var(--p-button-text-primary-color)', |
|
} |
|
const common = ` |
|
.mtb_sidebar { |
|
display: flex; |
|
flex-direction: column; |
|
background: ${S.bgm}; |
|
} |
|
.mtb_img_grid { |
|
display: flex; |
|
flex-wrap: wrap; |
|
overflow: scroll; |
|
gap: 1em; |
|
align-items: center; |
|
justify-content: center; |
|
height: 100%; |
|
width: 100%; |
|
} |
|
.mtb_tools { |
|
display: flex; |
|
flex-direction: row; |
|
align-items: center; |
|
justify-content: space-between; |
|
width: 100%; |
|
} |
|
` |
|
const inputs = ` |
|
/* SELECT */ |
|
.mtb_select { |
|
appearance: none; |
|
display: grid; |
|
grid-template-areas: "select"; |
|
padding: 10px; |
|
background-color: ${S.bgi}; |
|
border: none; |
|
border-radius: 5px; |
|
font-size: 14px; |
|
color: ${S.fg}; |
|
cursor: pointer; |
|
width: 100%; |
|
} |
|
|
|
@supports (-moz-appearance:none) { |
|
.mtb_select{ |
|
grid-area: select; |
|
background: ${S.bgi} url('data:image/gif;base64,R0lGODlhBgAGAKEDAFVVVX9/f9TU1CgmNyH5BAEKAAMALAAAAAAGAAYAAAIODA4hCDKWxlhNvmCnGwUAOw==') right center no-repeat !important; |
|
background-position: calc(100% - 5px) center !important; |
|
-moz-appearance:none !important; |
|
} |
|
|
|
/* styling the dropdown arrow for browsers that support it */ |
|
.mtb_select:after { |
|
content: ""; |
|
width: 0.8em; |
|
height: 0.5em; |
|
background-color: ${S.fg}; |
|
clip-path: polygon(100% 0%, 0 0%, 50% 100%); |
|
} |
|
|
|
.mtb_select:focus { |
|
outline: none; |
|
border-color: #0056b3; |
|
} |
|
|
|
.mtb_select > option { |
|
padding: 10px; |
|
background-color: ${S.bgi}; |
|
border:none; |
|
color: ${S.fg}; |
|
} |
|
|
|
.mtb_select > option:hover { |
|
background-color: red; |
|
color: ${S.fg}; |
|
} |
|
|
|
/* SLIDER */ |
|
.mtb_slider[type="range"] { |
|
-webkit-appearance: none; |
|
appearance: none; |
|
width: 100%; |
|
height: 10px; |
|
background: ${S.bgm}; |
|
border-radius: 5px; |
|
outline: none; |
|
opacity: 0.7; |
|
transition: opacity .2s; |
|
padding: 1em; |
|
} |
|
|
|
/* slider track */ |
|
.mtb_slider[type="range"]::-webkit-slider-runnable-track, |
|
.mtb_slider[type="range"]::-moz-range-track { |
|
width: 100%; |
|
height: 10px; |
|
background: ${S.bgi}; |
|
border-radius: 5px; |
|
} |
|
|
|
|
|
/* progress */ |
|
.mtb_slider[type="range"]::-moz-range-progress { |
|
background-color: ${S.accent}; |
|
height:10px; |
|
border-radius: 5px; |
|
} |
|
|
|
/* slider thumb (the handle) */ |
|
.mtb_slider[type="range"]::-webkit-slider-thumb, |
|
.mtb_slider[type="range"]::-moz-range-thumb |
|
{ |
|
-webkit-appearance: none; |
|
appearance: none; |
|
width: 15px; |
|
height: 15px; |
|
border-radius: 50%; |
|
background: ${S.fg}; |
|
border: none; |
|
cursor: pointer; |
|
filter: drop-shadow(1px 1px 4px black); |
|
} |
|
|
|
.mtb_slider[type="range"]:focus { |
|
opacity: 1; |
|
} |
|
|
|
.mtb_slider[type=range]:-moz-focusring{ |
|
outline: 1px solid red; |
|
outline-offset: -1px; |
|
} |
|
|
|
.mtb_slider[type="range"]:hover::-webkit-slider-thumb, |
|
.mtb_slider[type="range"]:active::-webkit-slider-thumb { |
|
background-color: ${S.accent}; |
|
} |
|
` |
|
addNamedStyleSheet( |
|
'mtb_ui', |
|
` |
|
${common} |
|
${inputs} |
|
`, |
|
) |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export const makeElement = (kind, style) => { |
|
let [real_kind, className] = kind.split('.') |
|
let id |
|
|
|
if (className?.includes('#')) { |
|
;[className, id] = className.split('#') |
|
} |
|
|
|
const el = document.createElement(real_kind) |
|
|
|
if (style) { |
|
Object.assign(el.style, style) |
|
} |
|
|
|
if (className) { |
|
el.classList.add(...className.split(' ')) |
|
} |
|
|
|
if (id) { |
|
el.id = id |
|
} |
|
|
|
return el |
|
} |
|
|
|
|
|
|
|
|
|
|
|
export const clearElement = (el) => { |
|
while (el.firstChild) { |
|
el.removeChild(el.firstChild) |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export const makeLabeledElement = (el, labelText) => { |
|
const wrapper = makeElement('div.mtb_labeled_element', { |
|
marginBottom: '1em', |
|
}) |
|
const label = makeElement('label', { |
|
display: 'block', |
|
marginBottom: '0.5em', |
|
}) |
|
label.textContent = labelText |
|
wrapper.appendChild(label) |
|
wrapper.appendChild(el) |
|
return wrapper |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const camelToKebab = (prop) => |
|
prop.replace(/[A-Z]/g, (match) => `-${match.toLowerCase()}`) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const parseStyleString = (styleString) => { |
|
const styleObj = {} |
|
for (const rule of styleString.split(';')) { |
|
const [property, value] = rule.split(':').map((item) => item.trim()) |
|
if (property && value) { |
|
const camelProp = property.replace(/-([a-z])/g, (g) => g[1].toUpperCase()) |
|
styleObj[camelProp] = value |
|
} |
|
} |
|
return styleObj |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export function defineCSSClass(className, classStyles) { |
|
const styleSheets = document.styleSheets |
|
let classExists = false |
|
let existingStyleString = '' |
|
const classExistsInStyleSheet = (styleSheet) => { |
|
const rules = styleSheet.rules || styleSheet.cssRules |
|
for (const rule of rules) { |
|
if (rule.selectorText === `.${className}`) { |
|
classExists = true |
|
existingStyleString = rule.style.cssText |
|
return true |
|
} |
|
} |
|
return false |
|
} |
|
|
|
for (const styleSheet of styleSheets) { |
|
if (classExistsInStyleSheet(styleSheet)) { |
|
console.debug(`Class ${className} already exists, merging styles...`) |
|
break |
|
} |
|
} |
|
const existingStyles = classExists |
|
? parseStyleString(existingStyleString) |
|
: {} |
|
const mergedStyles = { ...existingStyles, ...classStyles } |
|
|
|
const stylesString = Object.entries(mergedStyles) |
|
.map(([key, value]) => `${camelToKebab(key)}: ${value};`) |
|
.join(' ') |
|
|
|
if (!classExists) { |
|
console.debug(`Defining new class ${className}...`) |
|
if (styleSheets[0].insertRule) { |
|
styleSheets[0].insertRule(`.${className} { ${stylesString} }`, 0) |
|
} else if (styleSheets[0].addRule) { |
|
styleSheets[0].addRule(`.${className}`, stylesString, 0) |
|
} |
|
} else { |
|
console.debug(`Updating existing class ${className} with merged styles...`) |
|
for (const styleSheet of styleSheets) { |
|
const rules = styleSheet.rules || styleSheet.cssRules |
|
for (const rule of rules) { |
|
if (rule.selectorText === `.${className}`) { |
|
rule.style.cssText = stylesString |
|
} |
|
} |
|
} |
|
} |
|
|
|
console.debug( |
|
`Class ${className} has been defined/updated with styles:`, |
|
mergedStyles, |
|
) |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export const renderSidebar = (el, cont, elems) => { |
|
el.appendChild(cont) |
|
|
|
if (!el.parentNode) { |
|
return |
|
} |
|
el.parentNode.style.overflowY = 'clip' |
|
cont.style.height = `${el.parentNode.offsetHeight}px` |
|
|
|
const resizeHandler = () => { |
|
cont.style.height = `${el.parentNode.offsetHeight}px` |
|
} |
|
window.addEventListener('resize', resizeHandler) |
|
|
|
for (const elem of elems) { |
|
cont.appendChild(elem) |
|
} |
|
|
|
return { |
|
unregister: () => { |
|
window.removeEventListener('resize', resizeHandler) |
|
}, |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export const makeSelect = (options, current = undefined) => { |
|
const selector = makeElement('select.mtb_select', { |
|
width: 'auto', |
|
margin: '1em', |
|
}) |
|
|
|
for (const option of options) { |
|
const opt = makeElement('option') |
|
opt.value = option |
|
opt.innerHTML = option |
|
selector.appendChild(opt) |
|
} |
|
|
|
if (current !== undefined) { |
|
if (options.includes(current)) { |
|
selector.value = current |
|
} else { |
|
console.error( |
|
`You tried to select an option that doesn't exist (${current}). Options: ${options}`, |
|
) |
|
} |
|
} |
|
|
|
return selector |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export const makeSlider = (min, max, value = undefined, step = undefined) => { |
|
const slider = makeElement('input.mtb_slider', { |
|
width: '100%', |
|
}) |
|
|
|
slider.type = 'range' |
|
slider.min = min || 0 |
|
slider.max = max || 100 |
|
slider.value = value || slider.min |
|
slider.step = step || 1 |
|
|
|
return slider |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export const makeButton = (label, style = {}, onClick = undefined) => { |
|
const button = makeElement('button.mtb_button', style) |
|
button.textContent = label |
|
|
|
if (onClick) { |
|
button.addEventListener('click', onClick) |
|
} |
|
|
|
return button |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export const makeSplitter = ( |
|
el1, |
|
el2, |
|
direction = 'vertical', |
|
mode = 'normal', |
|
) => { |
|
const container = makeElement('div.mtb_splitter_container', { |
|
display: mode === 'absolute' ? 'block' : 'flex', |
|
flexDirection: direction === 'vertical' ? 'row' : 'column', |
|
position: mode === 'absolute' ? 'relative' : 'static', |
|
height: '100%', |
|
width: '100%', |
|
}) |
|
|
|
const handle = makeElement('div.mtb_splitter_handle', { |
|
backgroundColor: '#ccc', |
|
cursor: direction === 'vertical' ? 'col-resize' : 'row-resize', |
|
width: direction === 'vertical' ? '5px' : '100%', |
|
height: direction === 'horizontal' ? '5px' : '100%', |
|
}) |
|
|
|
let isResizing = false |
|
|
|
handle.addEventListener('mousedown', () => { |
|
isResizing = true |
|
}) |
|
|
|
window.addEventListener('mouseup', () => { |
|
isResizing = false |
|
}) |
|
|
|
window.addEventListener('mousemove', (e) => { |
|
if (!isResizing) return |
|
if (direction === 'vertical') { |
|
const newWidth = e.clientX - container.offsetLeft |
|
el1.style.width = `${newWidth}px` |
|
el2.style.width = `${container.offsetWidth - newWidth}px` |
|
} else { |
|
const newHeight = e.clientY - container.offsetTop |
|
el1.style.height = `${newHeight}px` |
|
el2.style.height = `${container.offsetHeight - newHeight}px` |
|
} |
|
}) |
|
|
|
container.appendChild(el1) |
|
container.appendChild(handle) |
|
container.appendChild(el2) |
|
|
|
return container |
|
} |
|
|