let bgMap = new Map(); let iconElement = Element.createWithStyle("i", "bi-x-square", "padding:1%;"); let layerElement = document.createElement("div"); layerElement.appendChild(iconElement); function highlight(element) { bgMap.set(element, element.style.border); element.style.border = "thick solid #ffff00"; //obj.style.backgroundColor = "yellow"; } function edit(element){ var rect = element.getBoundingClientRect(); var left = rect.x-200; var top = rect.y; console.log(rect); layerElement.setAttribute("style", 'width:' + 200 + 'px;' + "height:100px" + ';position:absolute;left:'+left+'px;top:'+top+'px;overflow:hidden;' + 'z-index:' + 10000 + ';' + "padding:10px;"); element.appendChild(layerElement); } function stopEdit(element) { element.style.border = bgMap.get(element); element.removeChild(layerElement); } let textVariables = new Map(); function isElement(element) { return ( typeof HTMLElement === "object" ? element instanceof HTMLElement : //DOM2 element && typeof element === "object" && element !== null && element.nodeType === 1 && typeof element.nodeName==="string" ); } function resolveShortcodes(text) { if (text.indexOf("[$") === -1) return text;//no shortcode saves loop! for (let [key, value] of textVariables) text = text.replaceAll(key, value); return text; } function getLanguageText(text) { let result = "no text '" + name + "' for lang " + ModuleSite.currentLanguage; let languages = text.split(";;"); if (languages.length === 1)//not multilang? result = text; else { for (let lang of languages) { lang = lang.trim(); if (lang.startsWith(ModuleSite.currentLanguage)) result = lang.substring(3); } } result = resolveShortcodes(result); return result; } class Module { children = []; mandatoryAttributes = []; contentLines = ""; widthPx = getCanvasWidth(); heightPx = 0; parent = null; paddingPx = 0; id = ""; static index = 0; elementToAppend = null; editMode = false; applyEditAttributes(element) { element.setAttribute("onmouseover","event.stopPropagation();highlight(this);"); element.setAttribute("onclick","event.stopPropagation();edit(this);"); //element.setAttribute("onmouseout","stopEdit(this)"); element.setAttribute("data-bs-toggle","tooltip"); element.setAttribute("data-bs-placement","top"); element.setAttribute("title",this.constructor.name); } getWidthPx(itemHeight) { let sum = 0; //alert("getWidthPx() in " + this.name + " with " + this.children.length + " children"); for (let child of this.children) { let w = child.getWidthPx(itemHeight); //alert(child.name + " returned a width of " + w); sum += w; } return sum; } registerMandatoryAttributes(mandatoryAttributes) { for (let attr of mandatoryAttributes) this.mandatoryAttributes.push(attr); } collectIds(ids) { if (this.attributes) { let id = this.getOrDefaultAttribute("id", ""); if (id !== "") ids.push(id); } for (let child of this.children) { child.collectIds(ids); } } checkIds() { let ids = []; this.collectIds(ids); let count = ids.length; for (let i = 0; i < count; ++i) { for (let j = i + 1; j < count; ++j) { if (ids[i] === ids[j]) { return "id " + ids[i] + " is not unique! " + i + " and " + j + " are equal"; } } } return false; } findById(id) { if (this.attributes && this.getOrDefaultAttribute("id", "") === id && id !== "") return this; for (let child of this.children) { let module = child.findById(id); if (module) return module; } return false; } findByType(type) { //console.log("findByType(" + type + ") this=" + this.constructor.name); if (this.constructor.name === type) return this; for (let child of this.children) { let module = child.findByType(type); if (module) return module; } } addContentLine(line) { this.contentLines += line + "\n"; } constructor(attributes, name) { this.attributes = attributes; if (name) this.name = name; else this.name = this.constructor.name; //if(attributes) this.id = this.getOrDefaultAttribute("id", this.constructor.name + Module.index++); console.log("constructed module " + this.name + " with id " + this.id); } create() { return document.body; } renderId() { return ' id="' + this.id + '"'; } init() { } getBooleanAttribute(name, defaultValue) { if (this.attributes.has(name)) return this.attributes.get(name) == "true"; return defaultValue; } getOrDefaultAttribute(name, defaultValue) { if (!this.attributes) alert("ERROR: Module " + this.name + " does not have attributes!"); return this.attributes.has(name) ? this.attributes.get(name).replace("#_#", " ") : defaultValue; } getLabel(defaultValue) { return this.getMultilangAttribute("label", defaultValue); } getTitle(defaultValue) { return this.getMultilangAttribute("title", defaultValue); } getMultilangText() { return getLanguageText(this.contentLines); } scanFolder(folder, filter) { const folderEncoded = encodeURIComponent(he.encode(folder)); const filterEncoded = encodeURIComponent(he.encode(filter)); let pathsString = ajaxCallSync("getFilesInFolder.php?folder=" + folderEncoded + "&filter=" + filterEncoded); //alert(pathsString); let pathsArray = pathsString.split(","); if (!pathsArray[pathsArray.length - 1]) pathsArray.pop(); return pathsArray; } getMultilangAttribute(name, defaultValue) { if (!this.attributes.has(name)) return defaultValue; let text = this.attributes.get(name); let languages = text.split(";;"); if (languages.length === 1)//not multilang? return text.replaceAll("#_#", " "); for (let lang of languages) { if (lang.startsWith(ModuleSite.currentLanguage)) return lang.substring(3).replaceAll("#_#", " "); } return "no text '" + name + "' for lang " + ModuleSite.currentLanguage; } attribute(name) { let attr = ""; if (this.attributes.has(name)) attr = ' ' + name + '="' + this.attributes.get(name).replace("#_#", " ") + '"'; return attr; } addChild(child) { child.parent = this; this.children.push(child); } hasChildren() { return this.children.length > 0; } start() { for (let child of this.children) { child.start(); } } applyTooltipDefault(element, tooltip) { element.setAttribute("data-bs-toggle", "tooltip"); element.setAttribute("data-bs-html", "true"); element.setAttribute("data-bs-placement", "auto"); element.setAttribute("title", tooltip); } languageTest() { //console.log(this); let lang = this.getOrDefaultAttribute("language", false); if (!lang) return true; return lang === ModuleSite.currentLanguage; } appendToStyle(style) { this.attributes.set("style", this.getOrDefaultAttribute("style", "") + style); } renderStyle() { let html = ""; if (this.attributes.has("style")) html += ' style="' + this.attributes.get("style") + '"'; return html; } getHtmlDimensions(html) { let test = document.getElementById("dim"); test.style.display = "block"; test.innerHTML = html; let result = test.getBoundingClientRect(); test.style.display = "none"; test.innerHTML = ""; return result; } getElementDimensions(element) { let test = document.getElementById("dim"); test.appendChild(element); test.style.display = "block"; let result = test.getBoundingClientRect(); test.style.display = "none"; test.removeChild(element); return result; } useAlign() { let style = ""; let vAlign = this.getOrDefaultAttribute("valign", false); let translateY = false; switch (vAlign) { default: break; case "center": translateY = "-50%"; style += "top:50%;"; break; case "bottom": style += "bottom:0px;"; break; } let hAlign = this.getOrDefaultAttribute("halign", false); let translateX = false; switch (hAlign) { default: break; case "center": translateX = "-50%"; style += "left:50%;"; break; case "right": style += "right:0px;"; break; } if (translateX) { style += "position:absolute;"; if (translateY) { style += "transform:translate(" + translateX + "," + translateY + ");" } else { style += "transform:translateX(" + translateX + ");"; } } else if (translateY) { style += "position:absolute;"; style += "transform:translateY(" + translateY + ");"; } return style; } usePadding(defaultPct) { let padding = this.getOrDefaultAttribute("padding", defaultPct); if (padding) { let pxValue = this.parent.widthPx * padding / 100; console.log(this.name +" padding:" + this.paddingPx + " / " + pxValue); if (this.paddingPx != pxValue) { this.paddingPx = pxValue; this.appendToStyle("padding:" + pxValue + "px;"); } return "padding:" + pxValue + "px;"; } return ""; } append(parentElement) { let newModule = this.create(); newModule.id = this.id; if(this.editMode) this.applyEditAttributes(newModule) parentElement.appendChild(newModule); } appendChildrenRange(parent, from, to) { for (let i = from; i < to; ++i) { let child = this.children[i]; child.widthPx = this.widthPx - this.paddingPx * 2; if (child.languageTest()) child.append(parent); } } appendChildren(parent) { for (let child of this.children) { child.widthPx = this.widthPx - this.paddingPx * 2; if (child.languageTest()) child.append(parent); } } }class ModuleCard extends Module { create() { let header = this.getMultilangAttribute("header", ""); let footer = this.getMultilangAttribute("footer", ""); let imagePath = this.getOrDefaultAttribute("image", ""); let color = this.getOrDefaultAttribute("bordercolor", "var(--textcolor)"); let borderColor = this.getOrDefaultAttribute("bordercolor", "var(--textcolor)"); let backgroundColor = this.getOrDefaultAttribute("backgroundcolor", "transparent"); let div = Element.createWithStyle("div", "card", 'height:100%;color:' + color + ';border-color: ' + borderColor + ';background-color:' + backgroundColor + ';'); if (header) { let headerDiv = Element.createWithContent("div", "card-header", header); div.appendChild(headerDiv); } let image = Element.create("img", "card-img-top"); image.setAttribute("src", imagePath); div.appendChild(image); let body = this.createBody(); div.appendChild(body); if (footer) { let footerDiv = Element.createWithContent("div", "card-footer", footer); div.appendChild(footerDiv); } return div; } createBody() { let title = this.getMultilangAttribute("title", ""); let subtitle = this.getMultilangAttribute("subtitle", false); let text = this.getMultilangText(); let body = Element.create("div", "card-body"); let cardTitle = Element.createWithContent("h3", "card-title", title); body.appendChild(cardTitle); if (subtitle) { let cardSubtitle = Element.createWithContent("h4", "card-subtitle", subtitle); body.appendChild(cardTitle); } let cardText = Element.createWithContent("p", "card-text", text); body.appendChild(cardText); return body; } }class ModuleContent extends Module { scaleFont = false; maxWordWidth = 0; biggestWord = ""; create() { let responsiveBehavior = this.getOrDefaultAttribute("responsive", "break"); let div = document.createElement("div"); let style = this.usePadding(); switch (responsiveBehavior) { case "break": style += "word-break: break-word;"; break; case "scale": this.scaleFont = true; break; } style += this.useAlign(); let htmlText = this.getMultilangText(); div.innerHTML = htmlText; if (this.scaleFont) { const temp = document.createElement("div"); temp.setAttribute("class", "scalefonts"); document.body.appendChild(temp); temp.innerHTML = htmlText; this.getMaxWordWidth(temp); document.body.removeChild(temp); //alert(this.maxWordWidth + " "+ this.widthPx); let factor = 1; if (this.maxWordWidth > this.widthPx) factor = this.widthPx / this.maxWordWidth; let scaleStyle = document.createElement('style'); scaleStyle.type = 'text/css'; let styleclass = this.id+"Style"; scaleStyle.innerHTML = '.' + styleclass + " h1 { font-size: calc(" + factor + " * 6vw) }" + '.' + styleclass + " h2 { font-size: calc(" + factor + " * 5vw) }" + '.' + styleclass + " h3 { font-size: calc(" + factor + " * 4vw) }" + '.' + styleclass + " h4 { font-size: calc(" + factor + " * 3vw) }" + '.' + styleclass + " h5 { font-size: calc(" + factor + " * 2vw) }"; div.prepend(scaleStyle) div.setAttribute("class", styleclass); } div.setAttribute("style", style); return div; } getMaxWordWidth(element) { if (!isElement(element)) { //alert("node "+element + " is not an element"); return; } for (let child of element.childNodes) { this.getMaxWordWidth(child); } //if(element.nodeType === 3) { //alert("found node=" + element + " of type "+element.nodeType); const text = element.innerText || ""; if (text.length > 0) { //alert("text=" + text + " in node with type " + element.nodeType); let words = text.split(/\s/); for (let word of words) { let font = getCanvasFont(element); let width = getTextWidth(word, font); //alert("word " + word + " in element " + element.tagName + element +" with font "+ font +" has a width of " + width); if (width > this.maxWordWidth) { this.maxWordWidth = width; this.biggestWord = word; } } } // } } }class ModuleDbTable extends Module { create() { let dbTableName = this.getOrDefaultAttribute("id", ""); let fields = this.getOrDefaultAttribute("fields", ""); const encoded = encodeURIComponent(he.encode(fields)); let rowsString = ajaxCallSync("query.php?action=readall&table=" + dbTableName + "&fields=" + encoded); let rows = rowsString.split(";;").filter(Boolean); let table = Element.create("table","table table-dark table-striped table-hover"); for (let row of rows) { let tr = document.createElement("tr"); let items = row.split(",,"); for (let item of items) { let td = document.createElement("td"); td.innerHTML = item; tr.appendChild(td); } table.appendChild(tr); } return table; } }class ModuleFeature extends Module { create(){ let icon = this.getOrDefaultAttribute("icon", ""); let text = this.getMultilangText(); let tr = document.createElement("tr"); let td1 = document.createElement("td"); let iconI = Element.createWithStyle("i", icon, "padding:1%;"); td1.appendChild(iconI); let td2 = document.createElement("td"); td2.setAttribute("style","padding:1%;"); let textI = document.createElement("i"); textI.innerHTML = text; td2.appendChild(textI); tr.appendChild(td1); tr.appendChild(td2); return tr; } }class ModuleFeatures extends Module { create() { let table = document.createElement("table"); this.appendChildren(table); return table; } }class ModuleFile extends Module { isImage() { let extension = this.getExtension().toLowerCase(); //console.log("ext="+extension); switch (extension) { case "jpg": case "png": case "svg": case "gif": case "webp": return true; } return false; } getTooltip() { if (this.isImage()) return ""; return this.getTitle(this.getPath()); } getExtension() { let index = this.getPath().lastIndexOf("."); return this.getPath().substring(index + 1); } getFileIcon() { //TODO get mapping from system settings or moduleFileGrid sitescript let iconMapping = new Map(); iconMapping.set("doc", "media/icons/files/file-earmark-word.svg"); let extension = this.getExtension(); if (!iconMapping.has(extension)) return "media/icons/files/file-earmark.svg"; return iconMapping.get(extension); } getPath() { return this.attributes.get('path').replace("#_#", " "); } getPreviewPath() { if (this.isImage()) return this.getPath(); return this.getFileIcon(); } getThumbPath() { if (this.isImage()) return this.getPath(); return this.getFileIcon(); } create() { let thumbSize = this.getOrDefaultAttribute("thumbSize", "30"); let paddingMm = this.getOrDefaultAttribute("paddingMm", "1"); let div = Element.createWithStyle("div", "col text-center", "padding:" + paddingMm + "mm;flex:0 0 " + thumbSize + "mm;"); let image = Element.createWithStyle("img", "figure-img img-fluid rounded", "width:" + thumbSize + "mm; height: " + thumbSize + "mm; object-fit: cover;"); this.applyTooltipDefault(image, this.getTooltip()); image.setAttribute("src", this.getThumbPath()); image.setAttribute("alt", this.getTitle()); image.setAttribute("onmouseup","applySettingsToModal('Image','" + this.id + "',dummy,0);"); div.appendChild(image); return div; } }class ModuleFileGrid extends Module { create() { let numCols = this.getFiles(); if (this.children.length > 0) {//got files? //console.log(alignment); let justifyContent = this.getAlignment(); let div = Element.create("div", "container-fluid"); //alert(canvasWidth + " / " + elementWidth + " = " + numCols + " (" + mm2px + ")"); let rowCount = Math.ceil(this.children.length / numCols); //alert(numCols + " / " + rowCount + " / " + this.children.length); for (let r = 0; r < rowCount; ++r) { let row = Element.createWithStyle("div", "row", "justify-content: " + justifyContent + ";"); let from = r * numCols; //alert("appending media files "+from + " to " + Math.min(this.children.length, from + numCols)); this.appendChildrenRange(row, from, Math.min(this.children.length, from + numCols)); div.appendChild(row); } return div; } else { let h2 = document.createElement("h2"); h2.innerHTML = this.getMultilangAttribute("empty", "No files found"); return h2; } } getAlignment() { let alignment = this.getOrDefaultAttribute("align", false); let justifyContent = "flex-start";//default? if (alignment) { switch (alignment) { default: case "start": justifyContent = "flex-start"; break; case "end": justifyContent = "flex-end"; break; case "center": justifyContent = "center"; break; case "evenly": justifyContent = "space-evenly"; break; } } return justifyContent; } getFiles() { let folder = this.getOrDefaultAttribute("folder", false); let filter = this.getOrDefaultAttribute("filter", false); let thumbSize = this.getOrDefaultAttribute("thumbSize_mm", "30"); let padding = this.getOrDefaultAttribute("thumbPadding", "2"); let paddingMm = parseFloat(thumbSize) * parseFloat(padding) / 100.0; let elementWidth = thumbSize * mm2px; let numCols = Math.floor(this.widthPx / elementWidth); console.log("file grid: " + this.widthPx + " / " + elementWidth + " = " + numCols + " (" + mm2px + ")"); if (folder) { let paths = this.scanFolder(folder, filter); for (let path of paths) { let attribs = new Map(); attribs.set("paddingMm", paddingMm.toString()); attribs.set("path", path); attribs.set("thumbSize", thumbSize); let moduleFile = new ModuleFile(attribs, false); moduleFile.widthPx = elementWidth; this.addChild(moduleFile); } } return numCols; } }class ModuleFontSelector extends Module { start() { if (!getFontList().length) document.addEventListener("FontListAvailable", (e) => { this.fontListAvailable(); }); else this.fontListAvailable(); } fontListAvailable() { let fonts = getFontList(); let fontOptions = ""; for (let font of fonts) { fontOptions += ""; } if (this.elementToAppend) { this.elementToAppend.innerHTML = fontOptions; } else { let select = document.getElementById(this.id); select.innerHTML = fontOptions; } } create() { let title = this.getTitle(""); let action = this.getOrDefaultAttribute("action", ""); let select = document.createElement("select"); select.setAttribute("onselect", action); select.setAttribute("title", getLanguageText(title)); return select; } }class ModuleFooter extends Module { create() { let classes = "footer"; let overlay = this.getBooleanAttribute("overlay", false); let sticky = this.getBooleanAttribute("sticky", false); let opacity = this.getOrDefaultAttribute("opacity", ""); if (opacity) opacity = "opacity: " + opacity + ";"; let heightStyle = "height:" + this.getOrDefaultAttribute("size", 5) + "vh;"; console.log("footerAttribs=%o", this.attributes); let root = document.createElement("div"); if (sticky) { let stickyClass = " fixed-bottom"; classes += stickyClass; if (!overlay) { let spacer = document.createElement("div"); spacer.setAttribute("style", heightStyle); root.appendChild(spacer); } } let footer = Element.createWithStyle("footer", classes,heightStyle + opacity); this.appendChildren(footer); root.appendChild(footer); return root; } } class ModuleForm extends Module { create(){ let task = this.getOrDefaultAttribute("task", ""); let form = document.createElement("form"); form.setAttribute("method", "post"); let input = document.createElement("input"); input.setAttribute("type", "hidden"); input.setAttribute("name", "_task_"); input.setAttribute("value", task); form.appendChild(input); this.appendChildren(form); return form; } }class ModuleInputField extends Module { static helpTextCounter = 0; create() { let additionalInputAttributes = new Map(); let id = this.getOrDefaultAttribute("id", ""); let name = this.getOrDefaultAttribute("name", ""); let data = this.getOrDefaultAttribute("data", ""); let value = this.getOrDefaultAttribute("value", ""); if (name) additionalInputAttributes.set('name', name); if (!value && data) { //console.log(data); const queryComp = data.split("."); let rowString = ajaxCallSync("query.php?action=readkeyset&table=" + queryComp[0] + "&key=" + queryComp[1]); //console.log(rowString); //here we should only get one field of one row //extract it //TODO: make DBResult.getValue(rowsString,0,0); let fields = rowString.split(",,"); let assoc = []; for (let field of fields) { let splitter = field.split("="); assoc[splitter[0]] = splitter[1]; } value = assoc[queryComp[2]];//2 is the field name //console.log("assoc =" + assoc); let separator = queryComp[3]; if (separator) {//3rd component can be an index of a semicolon separated string //console.log(separator); let values = value.split(separator); if (queryComp.length < 5) alert("query component 3 needs to be a separator and 4 needs to be an index"); let index = queryComp[4]; //console.log(index); value = values[index]; } } additionalInputAttributes.set("value", value); let placeholder = this.getMultilangAttribute("placeholder", ""); let type = this.getOrDefaultAttribute("type", ""); let helpText = this.getMultilangAttribute("help", ""); let helpTextId = ""; if (helpText) { helpTextId = "help" + ModuleInputField.helpTextCounter; ++ModuleInputField.helpTextCounter; additionalInputAttributes.set("aria-describedby", helpTextId); } let root = Element.create("div", "form-group"); this.appendLabel(root); this.appendPrepend(root); switch (type) { default: let input = Element.create("input", "form-control"); input.id = id; input.setAttribute("type", type); input.setAttribute("placeholder", placeholder); for (let [key, value] of additionalInputAttributes) { input.setAttribute(key, value); } root.appendChild(input); break; case "select": let select = Element.create("select", "form-control"); select.id = id; this.appendChildren(select) root.appendChild(select); break; } if (helpText) { let help = Element.create("small", "form-text text-muted"); help.id = helpTextId; help.innerHTML = helpText; root.appendChild(help); } return root; } appendPrepend(root) { let prepend = this.getMultilangAttribute("prepend", ""); if (prepend) { let prep = Element.create("div", "input-group-prepend"); let text = Element.create("span", "input-group-text"); text.innerHTML = prepend; prep.appendChild(text); root.appendChild(prep); } } appendLabel(root) { let label = this.getLabel(""); let labelElement = document.createElement("label"); labelElement.setAttribute("for", this.id); labelElement.innerHTML = label; root.appendChild(labelElement); } }class ModuleLayer extends Module { static layerIndex = 0; getResponsibleHeight() { let heightResponsible = this.getOrDefaultAttribute("height-responsible", false); if (heightResponsible) { let info = null; info = this.getElementDimensions(this.createByPosition("relative")); //alert(info.width + "x" + info.height); let result = info.height; return result; } return false; } create() { let element = this.createByPosition("absolute"); ++ModuleLayer.layerIndex; return element; } createByPosition(position) { let padding = this.usePadding(); console.log(position + " padding of layer = " + padding); console.log(this); let height = ""; if (position === "absolute") height = "height:100%"; let div = document.createElement("div"); div.setAttribute("style", 'width:' + this.parent.widthPx + 'px;' + height + ';position:' + position + ';overflow:hidden;' + 'z-index:' + ModuleLayer.layerIndex + ';' + padding); this.appendChildren(div); return div; } }class ModuleMap extends Module { static index = 0; center = [48.837398661225926, 13.34104422351722]; level = 15; markers = []; icon = false; start() { const map = L.map(this.id).setView(this.center, this.level); L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { 'attribution': 'Kartendaten © OpenStreetMap Mitwirkende', 'useCache': true }).addTo(map); map.scrollWheelZoom.disable(); map.dragging.disable(); let options = false; if (this.icon) { let splits = this.icon.split(";"); let url = splits[0]; let size = [16, 16]; if (splits[1]) size = splits[1].split(",").map(Number); let anchor = [size[0] / 2, size[1] / 2]; if (splits[2]) anchor = splits[2].split(",").map(Number); let myIcon = L.icon({ iconUrl: url, iconSize: size, // size of the icon iconAnchor: anchor, // point of the icon which will correspond to marker's location }); options = {icon: myIcon}; } for (let marker of this.markers) { console.log(marker); if (options) L.marker(marker, options).addTo(map); else L.marker(marker).addTo(map); } } create(){ this.applyMembers(); let height = this.getOrDefaultAttribute("height", "600"); let div = document.createElement("div"); div.id = this.id; div.setAttribute("style","height:"+height+"px;width:100%;"); return div; } applyMembers() { let center = this.getOrDefaultAttribute("center", "48.837398661225926,13.34104422351722"); center = center.split(","); this.center[0] = Number(center[0]); this.center[1] = Number(center[1]); this.level = parseInt(this.getOrDefaultAttribute("level", "15"), 10); this.icon = this.getOrDefaultAttribute("icon", false); let markers = this.getOrDefaultAttribute("marker", "48.837398661225926,13.34104422351722"); markers = markers.split(","); for (let i = 0; i < markers.length; i += 2) { this.markers[i / 2] = [Number(markers[i]), Number(markers[i + 1])]; } } }class ModuleOption extends Module { init() { this.registerMandatoryAttributes(["value", "label"]); } create() { let val = this.getOrDefaultAttribute("value",""); let label = this.getLabel("label"); let option = document.createElement("option"); option.setAttribute("value",val); option.innerHTML = label; } } class ModulePostEditor extends Module { languageCount = 1; editors = []; start() { for (let editor of this.editors) { editor.setup(); } } create() { let condition = encodeURIComponent("PostKey=" + ModulePage.current.param); let request = "query.php?action=read&table=contents&fields=*&condition=" + condition; let rowsString = ajaxCallSync(request); //alert("ModulePostEditor + " + ModulePage.current.param + " " + request + " res=" + rowsString); //every row contains the post in one language let rows = rowsString.split(";;").filter(Boolean); this.languageCount = rows.length; let root = document.createElement("div"); let heading = document.createElement("h3"); heading.innerHTML = "Post Editor"; root.appendChild(heading); for (let row of rows) { let fields = row.split(",,"); let editor = new CodeEditor(new HtmlParser()); editor.setKey(ModulePage.current.param+"_"+fields[2]); editor.setCode(fields[3]); let htmlEditor = new HtmlEditor(editor); let div = document.createElement("div"); div.setAttribute("style",'display: inline-block; padding: 1%;border: 1px solid white;'); div.innerHTML = editor.render(); let div2 = div.cloneNode(false); div2.innerHTML= htmlEditor.render(); root.appendChild(div); root.appendChild(div2); root.appendChild(document.createElement("p")); this.editors.push(editor); this.editors.push(htmlEditor); } return root; } render() { let condition = encodeURIComponent("PostKey=" + ModulePage.current.param); let request = "query.php?action=read&table=contents&fields=*&condition=" + condition; let rowsString = ajaxCallSync(request); //alert("ModulePostEditor + " + ModulePage.current.param + " " + request + " res=" + rowsString); //every row contains the post in one language let rows = rowsString.split(";;").filter(Boolean); this.languageCount = rows.length; let html = '

Post Editor

'; let i = 0; for (let row of rows) { let fields = row.split(",,"); let editor = new CodeEditor(new HtmlParser()); editor.setKey(ModulePage.current.param+"_"+fields[2]); editor.setCode(fields[3]); let htmlEditor = new HtmlEditor(editor); html += "
"; html += editor.render(); html+= "
"; html += "
"; html += htmlEditor.render(); html+= "

"; this.editors.push(editor); this.editors.push(htmlEditor); } return html; } }class ModuleProduct extends Module { folderUrl = ""; readTextFile(file, callback, param) { var rawFile = new XMLHttpRequest(); rawFile.overrideMimeType("application/json"); rawFile.open("GET", file, false); rawFile.send(); //rawFile.onreadystatechange = function() { if (rawFile.readyState === 4 && rawFile.status == "200") { return callback(rawFile.responseText, param); } //} } formatMoney(number, decPlaces, decSep, thouSep) { decPlaces = isNaN(decPlaces = Math.abs(decPlaces)) ? 2 : decPlaces, decSep = typeof decSep === "undefined" ? "." : decSep; thouSep = typeof thouSep === "undefined" ? "," : thouSep; var sign = number < 0 ? "-" : ""; var i = String(parseInt(number = Math.abs(Number(number) || 0).toFixed(decPlaces))); var j = (j = i.length) > 3 ? j % 3 : 0; return sign + (j ? i.substr(0, j) + thouSep : "") + i.substr(j).replace(/(\decSep{3})(?=\decSep)/g, "$1" + thouSep) + (decPlaces ? decSep + Math.abs(number - i).toFixed(decPlaces).slice(2) : ""); } createCard(productdata) { console.log("product json data = %o", productdata); let cardAttributes = new Map(); let imagePath = this.folderUrl + "thumb" + productdata.mainImageIndex + ".jpg"; cardAttributes.set("image", imagePath); let hint = ' '; if (productdata.hint) hint = '' + productdata.hint + ''; let title = hint + '' + productdata.brand + '
' + productdata.title + ''; cardAttributes.set("title", title); if (productdata.subtitle) cardAttributes.set("subtitle", productdata.subtitle); let borderColor = this.getOrDefaultAttribute("bordercolor", "var(--textcolor)"); cardAttributes.set("bordercolor", borderColor); let backgroundColor = this.getOrDefaultAttribute("backgroundcolor", "transparent"); cardAttributes.set("backgroundcolor", backgroundColor); let moduleCard = new ModuleCard(cardAttributes, "product"); if (productdata.rating) { let emptyStars = 5; let fullStars = Math.floor(productdata.rating); let halfStar = false; let fraction = productdata.rating - fullStars; if (fraction > 0.75) { fullStars++; } else if (fraction > 0.25) halfStar = true; emptyStars -= fullStars; let rating = ''; for (let i = 0; i < fullStars; ++i) { rating += ''; } if (halfStar) { rating += ''; --emptyStars; } for (let i = 0; i < emptyStars; ++i) { rating += ''; } moduleCard.addContentLine('

' + rating + "

"); } let priceInfo = '' + this.makePrice(productdata.price) + ''; if (productdata.rrp && productdata.rrp !== productdata.price) { let discountPct = 100 * (1 - productdata.price / productdata.rrp); priceInfo = '

' + '-' + discountPct + '% ' + priceInfo + ' ' + this.makePrice(productdata.rrp) + '' + '

'; } moduleCard.addContentLine(priceInfo); if (productdata.shippingInfo) moduleCard.addContentLine(productdata.shippingInfo); return moduleCard.create(); } makePrice(value) { let currency = "$";//TODO: use global currency return this.formatMoney(this.convertToCurrency(value), 2, ",", ".") + currency; } convertToCurrency(value) { console.log("TODO: implement ModuleProduct::convertToCurrency"); return value; } create() { let folder = this.getOrDefaultAttribute("folder", ""); let url = window.location.protocol + "//" + window.location.host; this.folderUrl = url + "/products/" + folder + "/"; console.log("productUrl = " + this.folderUrl); let jsonPath = this.folderUrl + "product.json"; let container = Element.createStyled("div", "max-width:7cm;"); let div = this.readTextFile(jsonPath, function (text, thiz) { let data = JSON.parse(text); return thiz.createCard(data); }, this); container.appendChild(div); return container; } } class ModuleSection extends Module { create(){ let section = Element.createStyled("section",'width:'+getCanvasWidth()+'px;height:'+window.innerHeight+'px;'); this.appendChildren(section); return section; } render() { this.appendToStyle('width:'+getCanvasWidth()+'px;height:'+window.innerHeight+'px;'); let html = '
1) factor = 1; let w = this.width * factor; //alert("this.width=" + this.width + " w=" + w + " "+this.name); return w; } getDimensions(path) { let condition = encodeURIComponent("path LIKE '%" + path + "'"); let rowString = ajaxCallSync("query.php?action=readset&table=media&fields=width,height&condition=" + condition); return rowString; } calculateLeft(xPct) { let xOffset = this.parent.widthPx * xPct / 100; switch (this.anchor) { case "center": return xOffset - this.width / 2; case "topleft": return xOffset; case "bottomright": return xOffset - this.width; } } calculateTop(yPct) { let yOffset = window.innerHeight * yPct / 100; switch (this.anchor) { case "center": return yOffset - this.height / 2; case "topleft": return yOffset; case "bottomright": return yOffset - this.height; } } create() { let effect = this.getOrDefaultAttribute("effect", ""); let effectTiming = this.getOrDefaultAttribute("effecttiming", "ease"); let offset = this.getOrDefaultAttribute("offset", ""); let style = this.getOrDefaultAttribute("style", ""); this.anchor = this.getOrDefaultAttribute("anchor", "center"); let fill = this.getOrDefaultAttribute("fill", false); let alt = super.attribute("alt"); let path = this.attributes.get("src").replace("#_#", " "); if (fill) { if (fill === "cover") style += "width:100%;height:100%;object-fit:cover;"; else if (fill.startsWith("wvmin")) { let pct = fill.substring(5); style += "width:" + pct + "vmin;"; } else if (fill.startsWith("hvmin")) { let pct = fill.substring(5); style += "height:" + pct + "vmin;"; } else if (fill === "height") style += "max-height:100%;width:auto;"; else style += "max-width:100%;max-height:100%;"; } if (offset) { //get percentage offset let xy = offset.split(","); style += "position:absolute;left:" + xy[0] + "px; top:" + xy[1] + "px;"; } let root = null; if (effect) { root = document.createElement("span"); let effectVars = effect.split(";"); let effectType = effectVars[0]; let keyframes = document.createElement("style"); ModuleImage.imageIndex++; switch (effectType) { case "zoom": // noinspection CssInvalidFunction keyframes.innerHTML = "@keyframes zoom" + ModuleImage.imageIndex + " {\n" + " from {transform: scale(1.0);}\n" + " to {transform: scale(" + effectVars[1] + ");}\n" + " }"; style += " width:100%;height:100%;" + "object-fit:cover; " + "animation-fill-mode:forwards;" + "animation-timing-function:" + effectTiming + ";" + "animation-name:zoom" + ModuleImage.imageIndex + ";" + "animation-duration:" + effectVars[2] + "s;"; break; case "move": // noinspection CssInvalidFunction let coordinates = effectVars[1].split(","); let coordinatePairCount = coordinates.length / 2; let kf = "@keyframes move" + ModuleImage.imageIndex + " {\n"; for (let i = 0; i < coordinatePairCount; ++i) { let percentage = 100 * i / (coordinatePairCount - 1); kf += percentage + "% {left: " + coordinates[i * 2] + "%; top: " + coordinates[i * 2 + 1] + "%;} \n"; } kf += "}\n"; keyframes.innerHTML = kf; if (this.anchor === "center") style += "transform:translate(-50%,-50%);"; style += "position: relative;" + "animation-fill-mode: forwards;" + "animation-timing-function:" + effectTiming + ";" + "animation-name:move" + ModuleImage.imageIndex + ";" + "animation-duration:" + effectVars[2] + "s;"; if (effectVars[3]) style += "animation-iteration-count:" + effectVars[3] + ";"; if (effectVars[4]) style += "animation-direction:" + effectVars[4] + ";"; break; default: break; } root.appendChild(keyframes); } let filter = this.getOrDefaultAttribute("filter", ""); if (filter) { filter = "filter:"+filter.replaceAll(";", " ")+";"; style += filter; } let image = Element.createWithStyle("img", "img-fluid", style); image.setAttribute("src",path); if(alt) image.setAttribute("alt",alt); if (root) root.appendChild(image); else root = image; return root; } writeDimensions(path) { const img = new Image(); img.onload = function () { const h = img.height; const w = img.width; let condition = encodeURIComponent("path LIKE '%" + path + "'"); let rowString = ajaxCallSync("query.php?action=update&table=media&fields=width,height&values=" + w + "," + h + "&condition=" + condition); //alert("rowString=" + rowString); // code here to use the dimensions } img.src = path; } }class ModuleMenu extends Module { calculateLodHorizontal(spacing) { const totalLodsizes = [0, 0, 0]; console.log("spacing:" + spacing + " tot:" + totalLodsizes + " " + this.children[0].getLodSizes().length + " " + this.widthPx); for (let child of this.children) { child.setSpacing(spacing); let lodSizes = child.getLodSizes(); console.log("lodSizes=" + lodSizes); for (let i = 0; i < lodSizes.length; ++i) { totalLodsizes[i] += lodSizes[i] + spacing * 2; } } console.log("tot:" + totalLodsizes + " w:" + this.widthPx); for (let i = 0; i < totalLodsizes.length; ++i) { if (totalLodsizes[i] < this.widthPx) { //console.log(this.widthPx + " lod: " + i); return i; } } return totalLodsizes.length - 1; } calculateLodVertical(spacing) { const maxLodsizes = [0, 0, 0]; console.log("spacing:" + spacing + " tot:" + maxLodsizes + " " + this.children[0].getLodSizes().length + " " + this.widthPx); for (let child of this.children) { child.setSpacing(spacing); let lodSizes = child.getLodSizes(); console.log(lodSizes); for (let i = 0; i < lodSizes.length; ++i) { maxLodsizes[i] = Math.max(maxLodsizes[i], lodSizes[i] + spacing * 2); } } console.log("tot:" + maxLodsizes + " w:" + this.widthPx); for (let i = 0; i < maxLodsizes.length; ++i) { if (maxLodsizes[i] < this.widthPx) { //console.log(this.widthPx + " lod: " + i); return i; } } } top() { let html = '"; return html; } left() { let additionalClasses = this.sticky(); let html = '"; return html; } createTop() { let div = Element.createStyled("div", "margin:auto"); let ul = Element.createWithStyle("ul", "navbar-nav", "flex-direction: row;align-items: center;justify-content: space-evenly"); this.appendChildren(ul); div.appendChild(ul); return div; } createLeft() { let additionalClasses = this.sticky(); let ul = Element.create("ul", "nav nav-pills flex-column mb-auto" + additionalClasses); this.appendChildren(ul); return ul; } sticky() { let additionalClasses = ""; if (ModuleNavbar.sticky) { additionalClasses += " sticky-top"; } return additionalClasses; } setChildSizes() { let spacingPx = Number(this.getOrDefaultAttribute("spacing", "1")) / 100 * this.parent.widthPx; let lod; if (ModuleNavbar.horizontal) { this.widthPx = this.parent.widthPx * 0.5;//TODO: at the moment we have hardcoded 50%-> find better solution lod = this.calculateLodHorizontal(spacingPx); } else { lod = this.calculateLodVertical(spacingPx); } //alert(spacing * mm2px()); console.log("lod=" + lod); if (lod < 0) lod = 0; for (let child of this.children) { child.setLod(lod); child.setSpacing(spacingPx); } } create() { this.setChildSizes(); if (ModuleNavbar.horizontal) return this.createTop(); return this.createLeft(); } }class ModuleBrand extends Module { create() { let filter = this.getOrDefaultAttribute("filter", ""); if (filter) { filter = "filter:"+filter.replaceAll(";", " ")+";"; } let link = document.createElement("a"); link.setAttribute("class", "navbar-brand"); link.setAttribute("href", "#"); if (!ModuleNavbar.horizontal) { link.setAttribute("style", "margin-left:auto;margin-right:auto;text-align:center;"); } else { link.setAttribute("style", "margin:auto;"); } let logo = this.getOrDefaultAttribute("logo", false); if (logo) { let alt = this.getOrDefaultAttribute("alt", ""); let logoStyle = ""; if (ModuleNavbar.horizontal) logoStyle += ModuleNavbar.logoSize; else logoStyle += 'max-width:100%;'; let image = document.createElement("img"); image.setAttribute("src", logo.replace("#_#", " ")); if (alt) image.setAttribute("alt", alt); image.setAttribute("style", logoStyle + filter); link.appendChild(image); } let name = this.getLabel(""); let html = ""; if (name) { if (!ModuleNavbar.horizontal) { if (getTextWidth(name, getCanvasFont()) * 1.25 > this.widthPx) name = name.substring(0, 5);//shorten name if it is too wide if (getTextWidth(name, getCanvasFont()) * 1.25 < this.widthPx) html += '

' + name.toUpperCase() + '

'; } else html += name; let span = document.createElement("span"); span.innerHTML = html; link.appendChild(span); } return link; } }class ModuleNavItem extends Module { static navItemCounter = 0; static navLinkFont = 0; hasParentNavItem = false; horizontal = true; lod = 0; spacing = 0; setLod(lod) { this.lod = lod; } setSpacing(spacing) { this.spacing = spacing; } getLodSizes() { if (ModuleNavItem.navLinkFont === 0) { let test = document.createElement("div"); test.classList.add("nav-link"); document.body.appendChild(test); ModuleNavItem.navLinkFont = getCanvasFont(test); console.log("font=" + ModuleNavItem.navLinkFont); document.body.removeChild(test); } let fontSize = ModuleNavItem.navLinkFont.split(" ")[1]; let iconSize = getTextWidth("SS", ModuleNavItem.navLinkFont); let textWidth = getTextWidth(this.getLabel(), ModuleNavItem.navLinkFont) + this.spacing;//8 is space between icon and text let shortLabel = this.getLabel().substring(0, 5).trim(); let shortTextWidth = getTextWidth(shortLabel, ModuleNavItem.navLinkFont) + this.spacing; // noinspection UnnecessaryLocalVariableJS let dropdownSpace = 0; if (this.hasChildren()) dropdownSpace += 14 + 0.255 * parseInt(fontSize); console.log("iconSize="+iconSize+"textWidth="+textWidth+"dropdownSpace="+dropdownSpace); let sizes = [iconSize + textWidth + dropdownSpace, iconSize + shortTextWidth + dropdownSpace, iconSize + dropdownSpace]; return sizes; } setHasParentNavItem() { this.hasParentNavItem = true; } create() { ModuleNavItem.navItemCounter++; if (this.hasParentNavItem) { this.setSpacing(this.parent.spacing); this.lod = this.parent.lod; } let liAdditionalClass = ""; let aAdditionalClass = ""; let li = document.createElement("li"); let a = document.createElement("a"); a.setAttribute("aria-expanded", "false"); if (this.hasChildren()) { for (let child of this.children) { try { child.setHasParentNavItem(); } catch (e) { //console.log("nav item child " + child.name + " does not have a setHasParentNavItem method!"); } } if (ModuleNavbar.horizontal) { liAdditionalClass += " dropdown"; aAdditionalClass += " dropdown-toggle"; a.setAttribute("data-bs-toggle", "dropdown"); } else { aAdditionalClass += " dropdown-toggle"; a.setAttribute("data-bs-toggle", "collapse"); } } //console.log("navItem lod=" + this.lod + " label=" + label); let href = this.getOrDefaultAttribute("target", ""); let padding = this.spacing; let {iconMargin, label} = this.setupLabel(); let icon = this.getOrDefaultAttribute("icon", ""); if (icon) { icon = Element.createWithStyle("i", icon, "margin-right:" + iconMargin + "px;"); icon.setAttribute("role", "img"); icon.setAttribute("aria-label", label); a.appendChild(icon); } if (this.hasParentNavItem) { let align = "center"; if (!ModuleNavbar.horizontal) align = "left"; li.setAttribute("style", "text-align:" + align); a.setAttribute("class", "dropdown-item nav-link"); a.setAttribute("href", href); } else { if (!ModuleNavbar.horizontal) { if (this.hasChildren()) { href = "#child" + ModuleNavItem.navItemCounter; } } if (ModuleNavbar.horizontal) { liAdditionalClass += " text-center"; } if (padding) a.setAttribute("style", "padding:" + padding + "px"); else li.setAttribute("style", "flex-grow:1"); li.setAttribute("class", "nav-item" + liAdditionalClass); a.setAttribute("class", "nav-link" + aAdditionalClass); a.setAttribute("href", href); } a.appendChild(document.createTextNode(label)); li.appendChild(a); if (this.hasChildren()) { let style; let classAttr = "dropdown-menu" if (!ModuleNavbar.horizontal) { classAttr = "collapse"; style = "list-style:none;"; } else { style = "position:absolute;right:0"; } let ul = Element.createWithStyle("ul", classAttr, style); ul.setAttribute("aria-labelledby","dropdown07XL"); ul.id = "child" + ModuleNavItem.navItemCounter; this.appendChildren(ul); li.appendChild(ul); } return li; } setupLabel() { let iconMargin = this.spacing * 0.5; let label = this.getLabel("navitem"); switch (this.lod) { case 1: label = label.substring(0, 5); break; case 2: label = ""; iconMargin = 0; break; default: break; } return {iconMargin, label}; } }class ModuleLanguageSelector extends Module { create(){ let horizontalStyle = ""; let verticalStyle = "width:100%;text-align:center;" let style = verticalStyle; if (ModuleNavbar.horizontal) style = horizontalStyle; let root = document.createElement("span"); root.setAttribute("style","margin:auto;" + style); for (let lang of ModuleSite.supportedLanguages) { let languageStyle = ""; if (lang === ModuleSite.currentLanguage) languageStyle = " border: 1px solid white;"; else languageStyle = " filter:opacity(50%);"; let link = document.createElement("a"); link.setAttribute("onclick","switchLanguage('" + lang + "')"); let icon = Element.createWithStyle("img","rounded-1","padding:2px;height:calc(7px*var(--mm2px));" + languageStyle); icon.setAttribute("src","language-icons/" + lang + ".svg"); icon.setAttribute("alt",lang); link.appendChild(icon); root.appendChild(link) } return root; } }class ModuleNavbar extends Module { static height = 0; static logoSize = 0; static sticky = false; static overlay = false; static backgroundColorRGBA = ""; static horizontal = true; remainingWidth = getCanvasWidth(); top(create) { this.widthPx = this.parent.widthPx; let size = this.getOrDefaultAttribute("size", 1); let logoPadding = this.getOrDefaultAttribute("logo-padding", 1); ModuleMenu.textColorRGBA = this.getOrDefaultAttribute("text-color", ""); ModuleNavbar.backgroundColorRGBA = this.getOrDefaultAttribute("background-color", ""); let opacity = this.getOrDefaultAttribute("opacity", ""); ModuleNavbar.minSize = this.attributes.get("min-size"); ModuleNavbar.height = window.innerHeight * size / 100; let logoHeight = ModuleNavbar.height - window.innerHeight * logoPadding / 100;//minus logoPadding% ModuleNavbar.logoSize = "height:" + logoHeight + "px; min-height:" + ModuleNavbar.minSize + "cm;"; let barSize = /*"height:" + ModuleNavbar.height + "px;*/" min-height:" + ModuleNavbar.minSize + "cm;"; // noinspection UnnecessaryLocalVariableJS let stickyClass = "sticky-top"; if (ModuleNavbar.overlay) { stickyClass = "fixed-top"; } else { barSize += "width:100%;"; } let additionalClasses = ""; if (ModuleNavbar.sticky) { additionalClasses += " " + stickyClass; } let colors = ""; if (ModuleNavbar.textColorRGBA) colors += "color:" + ModuleNavbar.textColorRGBA + ";"; //if (ModuleNavbar.backgroundColorRGBA) colors += "background-color:rgba(var(--bgcolorRGB), " + opacity + ");" // else if (opacity) // colors += "opacity:" + opacity + ";"; if (create) { let elem = Element.createWithStyle("nav", "navbar" + additionalClasses, barSize + "padding-top:0;padding-bottom:0;" + colors); elem.setAttribute("aria-label", "navbar"); this.appendChildren(elem); return elem; } else { let html = ""; //add spacer // if (ModuleMenu.sticky) { // html += "
"; // } let dims = this.getHtmlDimensions(html); console.log("navbar dims=" + dims.width + " x " + dims.height); return html; } } getPadding() { return this.paddingPx; } left(create) { ModuleNavbar.horizontal = false; let size = this.attributes.get("size").replace("%", ""); let minSizeCm = this.getOrDefaultAttribute("min-size", "1"); let paddingPct = this.getOrDefaultAttribute("padding", "0.5"); let minSizePx = minSizeCm * 10 * mm2px; //alert(this.parent.widthPx + " / " + this.parent.widthPx * size / 100 + " / " + minSizePx); this.widthPx = Math.max(this.parent.widthPx * size / 100, minSizePx); this.paddingPx = getCanvasWidth() * paddingPct / 100;//padding = paddingPct of full canvas this.remainingWidth = this.parent.widthPx - this.widthPx - this.paddingPx * 2; console.log("navbar left: " + this.paddingPx + " / " + this.remainingWidth + " width: "+this.widthPx); let additionalClasses = ""; if (ModuleNavbar.sticky) { additionalClasses += " sticky-top"; } let height = window.innerHeight + "px"; for (let child of this.children) { if (child.languageTest()) child.widthPx = this.widthPx - this.paddingPx * 2; } if (create) { let root = Element.createWithStyle("div", "d-flex flex-column" + additionalClasses, "height:" + height + ";flex:0 0 auto;width:" + this.widthPx + "px;padding:" + this.paddingPx + "px;margin:0;"); this.appendChildren(root); return root; } else { let html = '
'; html += super.renderChildren(); html += '
'; return html; } } getRemainingWidth() { return this.remainingWidth; } create() { let position = this.attributes.get("position"); switch (position) { default: case "top": { ModuleNavbar.sticky = this.getBooleanAttribute("sticky", true); ModuleNavbar.overlay = this.getBooleanAttribute("overlay", true); return this.top(true); } case "left": ModuleNavbar.sticky = this.getBooleanAttribute("sticky", false); ModuleNavbar.overlay = this.getBooleanAttribute("overlay", false); return this.left(true); } } }class ModuleRangeBar extends Module { createInternal(create) { let thickness = this.getOrDefaultAttribute("thickness", "10"); let range = this.getOrDefaultAttribute("range", "0;100"); let value = this.getOrDefaultAttribute("value", "0"); let colorVar = this.getOrDefaultAttribute("color", "--color1"); let border = this.getOrDefaultAttribute("border", "0,#000000"); let barMargin = this.getOrDefaultAttribute("barMargin", "8"); let padding = this.usePadding(); let label = this.getLabel(""); border = border.split(";"); let borderSize = border[0]; let borderColor = border[1]; let color = "background-color:var(" + colorVar + ");" let borderStyle = ""; if (borderSize > 0) { borderStyle = "border:" + borderSize + "px solid " + borderColor + ";"; } let minmax = range.split(";"); let min = minmax[0]; let max = minmax[1]; let width = 100 * value / (max - min); if (create) { let root = document.createElement("div"); root.setAttribute("style", padding); let labelAndValueContainer = Element.createStyled("div", "position:relative;overflow:hidden;"); let labelAndPercentage = Element.createStyled("div", "position:relative;overflow:hidden;"); let labelSpan = Element.createStyled("span", "float:left"); labelSpan.innerHTML = label; labelAndPercentage.appendChild(labelSpan); let valueElem = Element.createStyled("span", "float:right"); valueElem.innerHTML = value + '%'; labelAndPercentage.appendChild(valueElem); labelAndValueContainer.appendChild(labelAndPercentage); let barBorder = Element.createWithStyle("div", "progress", 'margin-top:' + barMargin + 'px;margin-bottom:' + barMargin + 'px;height:' + thickness + 'px;' + borderStyle) let barInner = Element.createWithStyle("div", "progress-bar", 'width:' + width + '%;' + color); barInner.setAttribute("aria-valuenow", value); barInner.setAttribute("aria-valuemin", min); barInner.setAttribute("aria-valuemax", max); barBorder.appendChild(barInner); root.appendChild(labelAndValueContainer); root.appendChild(barBorder); return root; } else { let html = '
' + '
' + label + '' + value + '%
' + '
' + '
' + '
' + '
'; return html; } } create() { return this.createInternal(true); } render() { return this.createInternal(false); } } class ModuleSiteEditor extends Module { _interval = 0; _parseResult = {}; _lastContent = 0; editor = new CodeEditor(new SiteScriptParser()); start() { this.editor.init(); super.start(); } create(){ this.editor.setKey(ModulePage.current.param); let root = document.createElement("div"); let heading = document.createElement("h3"); heading.innerHTML = this.getTitle("Site Editor"); root.appendChild(heading); let editor = document.createElement("div"); editor.innerHTML = this.editor.render(); root.appendChild(editor); return root; } render() { this.editor.setKey(ModulePage.current.param); let html = `

`+this.getTitle("Site Editor")+`

`; html += this.editor.render(); return html; } }class ModuleSlider extends Module { init() { this.registerMandatoryAttributes(["value", "label"]); } createInternal(create) { let height = this.getOrDefaultAttribute("height", "50"); let itemHeight = height * mm2px; let startslide = parseInt(this.getOrDefaultAttribute("startslide", "0")); let distribute = this.getOrDefaultAttribute("distribute", false); let arrows = this.getOrDefaultAttribute("arrows", false); let indicators = this.getOrDefaultAttribute("indicators", "true"); let interval = this.getOrDefaultAttribute("interval", "3000"); let slides = []; if (distribute && distribute !== "false") { let sum = 0; //check how many items fit in one slide for (let i = 0; i < this.children.length; ++i) { let paddingPct = 1; let child = this.children[i]; this.children[i].attributes.set("padding", paddingPct.toString()); let paddingPx = paddingPct / 100 * this.widthPx; let w = child.getWidthPx(itemHeight) + paddingPx * 2; sum = sum + w; //alert(i + " sum=" + sum + " w=" + w + " this.widthPx=" + this.widthPx + " " + child.name); if (sum > this.widthPx) { slides.push(i); sum = w; } } slides.push(this.children.length); if (startslide > slides.length) startslide = 0; } else { for (let i = 0; i < this.children.length; ++i) slides.push(i); } let indicatorsId = "indicators_" + this.id; if (create) { let root = Element.create("div", "carousel slide"); root.setAttribute("data-bs-ride", "carousel"); root.setAttribute("data-bs-interval", interval); this.appendInner(slides, startslide, itemHeight, root); this.appendIndicators(indicators, slides, root, indicatorsId, startslide); if (arrows && slides.length > 1) { let arrowPrev = Element.create("a", "carousel-control-prev"); arrowPrev.setAttribute("data-bs-target", "#" + indicatorsId); arrowPrev.setAttribute("role", "button"); arrowPrev.setAttribute("data-bs-slide", "prev"); let prevIcon = Element.create("span", "carousel-control-prev-icon"); prevIcon.setAttribute("aria-hidden", "true"); arrowPrev.appendChild(prevIcon); root.appendChild(arrowPrev); let arrowNext = Element.create("a", "carousel-control-next"); arrowNext.setAttribute("data-bs-target", "#" + indicatorsId); arrowNext.setAttribute("role", "button"); arrowNext.setAttribute("data-bs-slide", "next"); let nextIcon = Element.create("span", "carousel-control-next-icon"); nextIcon.setAttribute("aria-hidden", "true"); arrowNext.appendChild(nextIcon); root.appendChild(arrowNext); } return root; } else { let html = ''; return html; } } appendInner(slides, startslide, itemHeight, root) { let inner = Element.create("div", "carousel-inner"); //alert(slides); let start = 0; for (let b = 0; b < slides.length; ++b) { let active = ""; if (b === startslide) active = " active"; let item = Element.createWithStyle("div", "carousel-item" + active, "height:" + itemHeight + "px"); for (let i = start; i < slides[b]; ++i) item.appendChild(this.children[i].create()); start = slides[b]; inner.appendChild(item); } root.appendChild(inner); } appendIndicators(indicators, slides, root, indicatorsId, startslide) { if (indicators && slides.length > 1) { let indicators = Element.create("div", "carousel-indicators"); root.appendChild(indicators); for (let i = 0; i < slides.length; ++i) { let button = document.createElement("button"); button.setAttribute("type", "button"); button.setAttribute("data-bs-target", "#" + indicatorsId); button.setAttribute("data-bs-slide-to", i.toString()); if (i === startslide) { button.setAttribute("class", "active"); button.setAttribute("aria-current", "true"); } indicators.appendChild(button); } } } create() { return this.createInternal(true); } render() { return this.createInternal(false); } }class ModuleSliderItem extends Module { create() { let padding = this.usePadding(); let item = document.createElement("span"); item.setAttribute("style", padding); this.appendChildren(item); return item; } render() { this.usePadding(); let html = '' + this.renderChildren() + ''; return html; } } class ModuleSubmit extends Module { create() { return this.createInternal(true); } render() { return this.createInternal(false); } createInternal(create) { let label = this.getLabel("Submit"); let fill = this.getOrDefaultAttribute("fill", "width"); if (create) { let input = Element.create("input", "button"); input.setAttribute("type", "submit"); input.setAttribute("value", label); if (fill === "width") input.setAttribute("style", "width:100%;"); return input; } else { let style = ""; if (fill === "width") style = 'style="width:100%;"'; return ''; } } }class ModuleTabs extends Module { static index = 0; create() { let root = document.createElement("span"); let navTab = Element.create("ul", "nav nav-tabs"); navTab.id = "tabs" + ModuleTabs.index; navTab.setAttribute("role", "tablist"); let content = Element.create("div", "tab-content"); content.id = "tabContent" + ModuleTabs.index; //all children must be of type ModuleTab let panes = ""; let i = 0; for (let child of this.children) { if (i == 0) child.setActive(true); child.widthPx = this.widthPx; navTab.appendChild(child.create()); content.appendChild(child.createContent()); ++i; } root.appendChild(navTab); root.appendChild(content); ++ModuleTabs.index; return root; } render() { let html = `"; html += `
`; html += panes; html += "
"; ++ModuleTabs.index; return html; } }class ModuleTab extends Module { active = false; buttonId = ""; buttonTarget = ""; label = ""; setActive(active) { this.active = ""; if (active) this.active = "active"; } createContent() { let paneActive = ""; if (this.active) paneActive = this.active + " show"; let pane = Element.create("div","tab-pane fade " + paneActive); pane.id = this.tabId; pane.setAttribute("role","tabpanel"); pane.setAttribute("aria-labelledby",this.buttonId); this.appendChildren(pane); return pane; } getContent() { let paneActive = ""; if (this.active) paneActive = this.active + " show"; let pane = `
`; pane += this.renderChildren(); pane += "
"; return pane; } create() { this.tabId = "tab" + this.id; this.label = this.getLabel(this.tabId); this.buttonId = this.tabId + '-tab'; let li = Element.create("li", "nav-item"); li.setAttribute("role", "presentation"); let button = Element.create("button", "nav-link " + this.active); button.id = this.buttonId; button.setAttribute("data-bs-toggle", "tab"); button.setAttribute("data-bs-target", "#" + this.tabId); button.setAttribute("type", "button"); button.setAttribute("role", "tab"); button.setAttribute("aria-controls", this.tabId); button.setAttribute("aria-selected", "true"); button.innerHTML = this.label; li.appendChild(button); return li; } render() { this.tabId = "tab" + this.id; this.label = this.getLabel(this.tabId); this.buttonId = this.tabId + '-tab'; let html = ``; return html; } }class ModuleTask extends Module { create(){ let root = document.createElement("span"); root.innerHTML = this.render(); return root; } render() { let elem = document.getElementById(this.id); if (elem) return elem.innerHTML; return ""; } }class ModuleTitle extends Module { create() { let title = this.getLabel(""); let subtitle = this.getMultilangText(); let root = document.createElement("span"); let heading = document.createElement("h1"); let dot = this.getOrDefaultAttribute("dot", ""); if (dot) { let dotStyle = this.createDotStyle(dot); let dotElement = Element.createStyled("span",dotStyle); heading.appendChild(dotElement); let span = document.createElement("span"); span.innerHTML = title; heading.appendChild(document.createTextNode(title)); } root.appendChild(heading); let subHeading = Element.createStyled("i","color:#888888;"); subHeading.innerHTML = subtitle; root.appendChild(subHeading); return root; } createDotStyle(dot) { let dotVars = dot.split(";"); let size = dotVars[0] * mm2px; let radius = ""; if (dotVars[1]) radius = "border-radius:" + dotVars[1] * mm2px + "px;"; let colorVar = "var(--color1)"; if (dotVars[2]) colorVar = "var(" + dotVars[2] + ")"; let bottom = size - 2; let dotStyle = 'border-style: solid;' + radius + ' border-bottom-width: ' + bottom + 'px;' + ' border-color: ' + colorVar + ';' + ' width: ' + size + 'px;' + ' margin: 0 10px;' + ' display:inline-block;'; return dotStyle; } }class ModuleToolbar extends Module { create() { let div = Element.createWithStyle("div", "toolbar", "position:absolute;display:none;"); this.appendChildren(div); return div; } render() { let html = ''; return html; } }class ModuleToolbarButton extends Module { createInternal(create){ let title = this.getTitle(""); let icon = this.getOrDefaultAttribute("icon", "bi-exclamation-triangle"); let action = this.getOrDefaultAttribute("action", ""); if(create){ let i = Element.create("i", icon); i.setAttribute("title",getLanguageText(title)); i.setAttribute("onclick",action); return i; }else { let html = ''; return html; } } create(){ return this.createInternal(true); } render() { return this.createInternal(false); } }class ModuleUploadForm extends Module { create() { let label = this.getLabel("Upload"); let target = this.getOrDefaultAttribute("target",""); let form = document.createElement("form"); form.setAttribute("method","post"); form.setAttribute("encType","multipart/form-data"); let fields = document.createElement("div","mb-3"); let labelElem = Element.create("label","form-label"); labelElem.setAttribute("htmlFor","formFile") ; labelElem.innerHTML = label; fields.appendChild(labelElem); let fileField = Element.create("input","form-control"); fileField.setAttribute("type","file"); fileField.setAttribute("name","fileToUpload"); fileField.setAttribute("multiple","multiple"); fileField.id = "fileToUpload"; let accept = this.getOrDefaultAttribute("accept",false); if(accept) fileField.setAttribute("accept",accept); fields.appendChild(fileField); form.appendChild(fields); let hidden = Element.create("input","form-control"); hidden.setAttribute("type","hidden"); hidden.setAttribute("name","target"); hidden.setAttribute("value",target); form.appendChild(hidden); let butCol = document.createElement("div","col-auto"); let button = Element.create("button","btn btn-primary mb-3"); button.setAttribute("type","submit"); button.setAttribute("name","submit"); button.innerHTML = label; butCol.appendChild(button); form.appendChild(butCol); return form; } render() { let accept = this.getOrDefaultAttribute("accept",false); if(accept) accept = 'accept="'+accept+'"'; let label = this.getLabel("Upload"); let target = this.getOrDefaultAttribute("target",""); let html = `
`; return html; } }class ModuleUserThumb extends Module { static username = ""; static imageUrl = "ewok.svg"; create() { let root = Element.createStyled("div", "font-size: calc(7px * var(--mm2px));"); let a = Element.create("a", "text-decoration-none"); a.setAttribute("href", "?user"); a.id = "dropdownUser1"; let b = document.createElement("b"); b.innerHTML = ModuleUserThumb.username+ " "; a.appendChild(b); let img = Element.createWithStyle("img", "rounded-circle", "object-fit:cover;"); img.setAttribute("src", ModuleUserThumb.imageUrl); img.setAttribute("alt", ModuleUserThumb.username); img.setAttribute("width", "32"); img.setAttribute("height", "32"); a.appendChild(img); root.appendChild(a); return root; } render() { return `
` + ModuleUserThumb.username + ` user image
`; } }class ModuleVideo extends Module { create() { let src = this.getOrDefaultAttribute("src", ""); let video = document.createElement("video"); video.src = src; video.controls = true; video.autoplay = true; video.muted = true; video.loop = true; video.preload = "auto"; video.innerHTML = 'Cannot play html5 video ' + src; video.setAttribute("width","100%"); return video; } render() { let src = this.getOrDefaultAttribute("src", ""); let html = '' + 'Cannot play html5 video' + ''; return html; } }class ModulePage extends Module { static current = 0; menu = 0; footer = 0; param = 0; site = 0; setParam(pageParam) { this.param = pageParam; } resolve(rootModule) { //getMenu by menuid this.site = rootModule.findByType("ModuleSite");//contains title, favicon... this.menu = rootModule.findById(this.getOrDefaultAttribute("menuid", "")); this.footer = rootModule.findById(this.getOrDefaultAttribute("footerid", "")); } create() { ModulePage.current = this; let root = document.createElement("div"); if (this.site) this.site.create(); let pageParent = root; let container = Element.createStyled("div","display: flex; height:100%"); root.appendChild(container); if (this.menu) { let navbar = this.getOrDefaultAttribute("navbar", false); if(navbar) { //console.log("custom navbar attribs = "+navbar); let navbarAttributes = navbar.split(";"); for (let attrib of navbarAttributes) { let keyValue = attrib.split(":"); this.menu.attributes.set(keyValue[0], keyValue[1]); //console.log("custom menu attribs = %o",this.menu.attributes); } } container.appendChild(this.menu.create()); if(!ModuleNavbar.horizontal) { let rem = this.menu.getRemainingWidth(); let paddingPx = this.menu.getPadding(); let contentColumn = Element.createWithStyle("div","col", "width:" + rem + "px;margin-right:0;padding:" + paddingPx + "px;overflow:auto;max-height:100%"); container.appendChild(contentColumn); pageParent = contentColumn; } } this.widthPx = this.menu.getRemainingWidth(); let page = document.createElement("main"); this.file = this.getOrDefaultAttribute("file", ""); if (this.file) { if (this.param) this.file += "?" + this.param; let fileContent = document.createElement("div"); fileContent.setAttribute("style","height:900px");//TODO: why 900? make dynamic? fileContent.id = "fileContent"; page.appendChild(fileContent); ajaxCallFunc(this.file, "fileContent", activateTooltips);//TODO: can this work? as the fileContent div is not rendered yet } else { this.appendChildren(page); } pageParent.appendChild(page); if (this.footer) pageParent.appendChild(this.footer.create()); return root; } } class ModuleSite extends Module { static supportedLanguages = 0; static currentLanguage = "de"; static darkTheme = true; page = null; init(){ this.widthPx = getCanvasWidth(); } start(){ if(this.page) this.page.start(); } createPage(pageId, pageParam){ this.page = this.findById(pageId);//should deliver ModulePage object let root = document.createElement("h1"); if(!document.body) document.body = document.createElement("body"); root.innerHTML = "ERROR: Cannot render page " + pageId; if (this.page) { console.log("pageId: " + pageId + " page: " + this.page.name + " param: " + pageParam); this.page.setParam(pageParam); this.page.resolve(this); root = this.page.create(); } return root; } renderPage(pageId, pageParam){ this.page = this.findById(pageId);//should deliver ModulePage object let html = "

ERROR: Cannot render page " + pageId + "

"; if (this.page) { console.log("pageId: " + pageId + " page: " + this.page.name + " param: " + pageParam); this.page.setParam(pageParam); this.page.resolve(this); html = this.page.render(); } return html; } create() { return this.render(); } render() { let title = "" + this.getMultilangAttribute("title", "Ewok CMS") + ""; let favicon = ''; ModuleSite.supportedLanguages = this.getOrDefaultAttribute("languages","").split(","); let html = title + favicon; document.head.innerHTML = document.head.innerHTML + html; return ""; } }/** * Created by roulio on 13.12.2022. */ String.prototype.insertAt = function (index, string) { return this.substring(0, index) + string + this.substring(index); } String.prototype.splice = function (start, end, replacement) { return this.substring(0, start) + replacement + this.substring(end); } const classesMapping = new Map([ ["brand", ModuleBrand], ["card", ModuleCard], ["col", ModuleColumn], ["content", ModuleContent], ["dbTable", ModuleDbTable], ["feature", ModuleFeature], ["features", ModuleFeatures], ["file", ModuleFile], ["fileGrid", ModuleFileGrid], ["fontSelector", ModuleFontSelector], ["footer", ModuleFooter], ["form", ModuleForm], ["image", ModuleImage], ["input", ModuleInputField], ["map", ModuleMap], ["option", ModuleOption], ["languageSelector", ModuleLanguageSelector], ["layer", ModuleLayer], ["menu", ModuleMenu], ["navbar", ModuleNavbar], ["navItem", ModuleNavItem], ["page", ModulePage], ["postEditor", ModulePostEditor], ["product", ModuleProduct], ["range", ModuleRangeBar], ["row", ModuleRow], ["section",ModuleSection], ["site", ModuleSite], ["siteEditor", ModuleSiteEditor], ["slider", ModuleSlider], ["sliderItem", ModuleSliderItem], ["submit", ModuleSubmit], ["tabs", ModuleTabs], ["tab", ModuleTab], ["task", ModuleTask], ["title", ModuleTitle], ["toolbar", ModuleToolbar], ["toolbarButton", ModuleToolbarButton], ["uploadForm", ModuleUploadForm], ["userThumb", ModuleUserThumb], ["video", ModuleVideo] ]);