import { RGBColor, RGBColorToHexString } from '../engine/model/color.js'; import { CreateObjectUrl } from '../engine/io/bufferutils.js'; import { AddDiv, CreateDiv, AddDomElement } from '../engine/viewer/domutils.js'; import { Loc } from '../engine/core/localization.js'; import { Theme } from './settings.js'; export function GetNameOrDefault (originalName, defaultName) { if (originalName.length > 0) { return originalName; } return defaultName; } export function GetNodeName (originalName) { return GetNameOrDefault (originalName, Loc ('No Name')); } export function GetMeshName (originalNodeName, originalMeshName) { let originalName = (originalNodeName.length > 0 ? originalNodeName : originalMeshName); return GetNameOrDefault (originalName, Loc ('No Name')); } export function GetMaterialName (originalName) { return GetNameOrDefault (originalName, Loc ('No Name')); } export function GetPreferredColorScheme () { return window.matchMedia ('(prefers-color-scheme: dark)').matches ? Theme.Dark : Theme.Light; } export function IsHoverEnabled () { return window.matchMedia ('(hover: hover)').matches; } export function AddSmallWidthChangeEventListener (onChange) { let mediaQuery = window.matchMedia ('(max-width: 800px)'); mediaQuery.addEventListener ('change', onChange); } export function IsSmallWidth () { return window.matchMedia ('(max-width: 800px)').matches; } export function IsSmallHeight () { return window.matchMedia ('(max-height: 800px)').matches; } export function InstallTooltip (element, text) { function CalculateOffset (element, tooltip) { let windowWidth = window.innerWidth; let elementOffset = element.getBoundingClientRect (); let elementWidth = element.offsetWidth; let elementHeight = element.offsetHeight; let tooltipWidth = tooltip.offsetWidth; let tooltipMargin = 10; let left = elementOffset.left + elementWidth / 2 - tooltipWidth / 2; if (left + tooltipWidth > windowWidth - tooltipMargin) { left = windowWidth - tooltipWidth - tooltipMargin; } if (left < tooltipMargin) { left = tooltipMargin; } left = Math.max (left, 0); return { left : left, top : elementOffset.top + elementHeight + tooltipMargin }; } if (!IsHoverEnabled ()) { return; } let tooltip = null; element.addEventListener ('mouseover', () => { tooltip = AddDiv (document.body, 'ov_tooltip', text); let offset = CalculateOffset (element, tooltip); tooltip.style.left = offset.left + 'px'; tooltip.style.top = offset.top + 'px'; }); element.addEventListener ('mouseout', () => { tooltip.remove (); }); } export function CopyToClipboard (text) { let input = document.createElement ('input'); input.style.position = 'absolute'; input.style.left = '0'; input.style.top = '0'; input.setAttribute ('value', text); document.body.appendChild (input); input.select (); document.execCommand ('copy'); document.body.removeChild (input); } export function DownloadUrlAsFile (url, fileName) { let link = document.createElement ('a'); link.href = url; link.download = fileName; document.body.appendChild (link); link.click (); document.body.removeChild (link); } export function DownloadArrayBufferAsFile (arrayBuffer, fileName) { let url = CreateObjectUrl (arrayBuffer); DownloadUrlAsFile (url, fileName); } export function CreateSvgIconElement (iconName, className) { let iconDiv = CreateDiv ('ov_svg_icon'); if (className) { iconDiv.classList.add (className); } AddDomElement (iconDiv, 'i', 'icon icon-' + iconName); return iconDiv; } export function AddSvgIconElement (parentElement, iconName, className) { let iconDiv = CreateSvgIconElement (iconName, className); parentElement.appendChild (iconDiv); return iconDiv; } export function SetSvgIconImageElement (iconElement, iconName) { let iconDiv = iconElement.firstChild; iconDiv.className = 'icon icon-' + iconName; } export function CreateInlineColorCircle (color) { let hexString = '#' + RGBColorToHexString (color); let darkerColor = new RGBColor ( Math.max (0, color.r - 50), Math.max (0, color.g - 50), Math.max (0, color.b - 50) ); let darkerColorHexString = '#' + RGBColorToHexString (darkerColor); let circleDiv = CreateDiv ('ov_color_circle'); circleDiv.style.background = hexString; circleDiv.style.border = '1px solid ' + darkerColorHexString; return circleDiv; } export function IsDarkTextNeededForColor (color) { let intensity = color.r * 0.299 + color.g * 0.587 + color.b * 0.114; return intensity > 186.0; } export function GetFilesFromDataTransfer (dataTransfer, onReady) { async function GetFileEntriesFromDirectory (dirEntry, fileEntries) { let reader = dirEntry.createReader (); return new Promise ((resolve, reject) => { reader.readEntries ( async (dirEntries) => { for (let entry of dirEntries) { if (entry.isFile) { fileEntries.push (entry); } else if (entry.isDirectory) { await GetFileEntriesFromDirectory (entry, fileEntries); } } resolve (); }, (error) => { reject (error); } ); }); } async function GetFileObjectsFromEntries (entries, onReady) { let fileEntries = []; for (let entry of entries) { if (entry.isFile) { fileEntries.push (entry); } else if (entry.isDirectory) { await GetFileEntriesFromDirectory (entry, fileEntries); } } let fileObjects = await Promise.all (fileEntries.map ((fileEntry) => { return new Promise ((resolve, reject) => { fileEntry.file ( (file) => { resolve (file); }, (error) => { reject (error); } ); }); })); onReady (fileObjects); } let getAsEntryFunc = null; if (DataTransferItem) { if (DataTransferItem.prototype.getAsEntry) { getAsEntryFunc = DataTransferItem.prototype.getAsEntry; } else if (DataTransferItem.prototype.webkitGetAsEntry) { getAsEntryFunc = DataTransferItem.prototype.webkitGetAsEntry; } } if (getAsEntryFunc !== null) { let entries = []; for (let item of dataTransfer.items) { let entry = getAsEntryFunc.call (item); if (entry !== null) { entries.push (entry); } } GetFileObjectsFromEntries (entries, (allEntries) => { onReady (allEntries); }); } else { onReady (dataTransfer.files); } } export function AddNumberInput (parentElement, className, onChange) { let numberInput = AddDomElement (parentElement, 'input', className); numberInput.setAttribute ('type', 'text'); let onChangeTimeout = null; numberInput.addEventListener ('input', () => { numberInput.value = numberInput.value.replace (/[^0-9]/g, ''); if (onChange) { if (onChangeTimeout !== null) { clearTimeout (onChangeTimeout); } onChangeTimeout = setTimeout (() => { onChange (numberInput.value); }, 1000); } }); return numberInput; } export function AddCheckbox (parentElement, id, text, isChecked, onChange) { let label = AddDomElement (parentElement, 'label'); label.setAttribute ('for', id); let check = AddDomElement (label, 'input', 'ov_checkbox'); check.setAttribute ('type', 'checkbox'); check.setAttribute ('id', id); check.checked = isChecked; AddDomElement (label, 'span', null, text); if (onChange) { check.addEventListener ('change', onChange); } return check; } export function AddRadioButton (parentElement, id, name, text, isChecked, onChange) { let label = AddDomElement (parentElement, 'label'); label.setAttribute ('for', id); let radio = AddDomElement (label, 'input', 'ov_radio_button'); radio.setAttribute ('type', 'radio'); radio.setAttribute ('id', id); radio.setAttribute ('name', name); radio.checked = isChecked; AddDomElement (label, 'span', null, text); if (onChange) { radio.addEventListener ('change', onChange); } return radio; } export function AddRangeSlider (parentElement, min, max) { let slider = AddDomElement (parentElement, 'input', 'ov_slider'); slider.setAttribute ('type', 'range'); slider.setAttribute ('min', min.toString ()); slider.setAttribute ('max', max.toString ()); return slider; } export function AddSelect (parentElement, options, selectedIndex, onChange) { let container = AddDiv (parentElement, 'ov_select_container'); let select = AddDomElement (container, 'select', 'ov_select'); for (let option of options) { AddDomElement (select, 'option', null, option); } select.selectedIndex = selectedIndex; if (onChange) { select.addEventListener ('change', () => { onChange (select.selectedIndex); }); } return select; } export function AddToggle (parentElement, className) { function UpdateStatus (toggle, status) { if (status) { toggle.classList.add ('on'); } else { toggle.classList.remove ('on'); } } let status = false; let onChange = null; let toggleClassName = 'ov_toggle'; if (className) { toggleClassName += ' ' + className; } let toggle = AddDiv (parentElement, toggleClassName); AddDiv (toggle, 'ov_toggle_slider'); toggle.addEventListener ('click', () => { status = !status; UpdateStatus (toggle, status); if (onChange) { onChange (); } }); return { element : toggle, GetStatus : () => { return status; }, SetStatus : (newStatus) => { status = newStatus; UpdateStatus (toggle, status); }, OnChange : (onChangeHandler) => { onChange = onChangeHandler; } }; }