diff --git a/VERSION b/VERSION index 1cc9c180e266a183d615bbbb1b28f7ca520254d5..dc1e644a1014338ad0ca67b5c0bfbd2402e761ee 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.5.8 +1.6.0 diff --git a/generator/static/images/examples/komise.png b/generator/static/images/examples/komise.png new file mode 100644 index 0000000000000000000000000000000000000000..2f326baac3e309320c6047be4ae4ff1ef02749ef Binary files /dev/null and b/generator/static/images/examples/komise.png differ diff --git a/generator/static/images/examples/komise_story.png b/generator/static/images/examples/komise_story.png new file mode 100644 index 0000000000000000000000000000000000000000..150bc6a2ce6edfe88bd6a2a8cc1fd665a9def447 Binary files /dev/null and b/generator/static/images/examples/komise_story.png differ diff --git a/generator/static/js/templates/komise-story.js b/generator/static/js/templates/komise-story.js new file mode 100644 index 0000000000000000000000000000000000000000..51d838ce12b56bcf6f59a0d3c39a487bca40808b --- /dev/null +++ b/generator/static/js/templates/komise-story.js @@ -0,0 +1,823 @@ +class KomiseStory extends Template { + description = "Určeno pro story na sociální sítě."; + + changeableAttributes = [ + "logoImage", + "primaryImage", + "primaryText", + "secondaryText", + "primaryColorScheme", + "primaryImagePosition", + "iconImage" + ]; + + primaryColorSchemes = [ + "black-on-white", + "white-on-black" + ]; + + primaryTextHighlightColorSchemes = [ + "black-on-gold" + ]; + + changeableColors = [ + "primaryTextColor", + "foregroundColor", + "primaryTextHighlightColor", + "requesterTextColor" + ]; + + lightLogoDefaultSource = "/static/images/badges/default-dark.png"; + darkLogoDefaultSource = "/static/images/badges/default-light.png"; + + iconImage = null; + iconSource = ""; + + aspectRatio = 0.5625; + defaultResolution = 1920; + + secondaryText = ""; + + // Canvas + async redrawCanvas() { + if (this.redrawing) { + return; + } + + this.redrawing = true; + + const primaryRectangleAngle = Math.ceil(this.canvas.height * 0.015); + const primaryRectanglePaddingBottom = Math.ceil(this.canvas.height * 0.04); + const primaryRectanglePaddingTop = 0; + const primaryRectangleAdditionalPaddingWithDiacritics = Math.ceil(this.canvas.height * 0.01); + const primaryRectanglePaddingSides = Math.ceil(this.canvas.width * 0.05); + + const highlightPaddingSides = Math.ceil(this.canvas.width * 0.01); + const highlightPaddingBottom = Math.ceil(this.canvas.height * 0.0125); + const highlightPaddingTop = Math.ceil(this.canvas.height * -0.01); // It is, it's how Roboto works. + + const logoHeight = Math.ceil(this.canvas.height * 0.075) * this.logoImageZoom; + const logoBottomOffset = Math.ceil(this.canvas.height * 0.1) - ( + logoHeight / this.logoImageZoom * (this.logoImageZoom - 1) / 2 + ); + + let primaryFontSize = Math.ceil(this.canvas.height * 0.075); + const primaryFontLinePadding = 0; + const primaryTextMaxLines = 3; + const primaryTextPaddingBottom = Math.ceil(this.canvas.height * 0.17); + + let secondaryFontSize = Math.ceil(this.canvas.height * 0.06); + const secondaryTextPaddingBottom = Math.ceil(this.canvas.height * 0.04); + + const iconImageOffsetBottom = Math.ceil(this.canvas.height * 0.1); + const iconImageOffsetTop = Math.ceil(this.canvas.height * 0.03); + const iconOpacity = 0.125; + + // Get primary text split into lines, no more than ``primaryTextMaxLines`` of them + + let primaryTextLines = null; + + do { + this.context.font = `${this.primaryFontStyle} ${primaryFontSize}px ${this.primaryFont}`; + + primaryTextLines = splitStringIntoLines( + this.context, + this.primaryText, + this.canvas.width - primaryRectanglePaddingSides * 2, + primaryTextMaxLines, + true + ).reverse(); + + if (primaryTextLines.length > primaryTextMaxLines) { + primaryFontSize -= 2; + } + } while (primaryTextLines.length > primaryTextMaxLines); + + // Clear the canvas + this.context.clearRect( + 0, 0, + this.canvas.width, this.canvas.height + ); + + // Set image + if (this.primaryImage !== null) { + // https://github.com/DonkeyDushan/piratilol/blob/main/src/js/index.js + // Thanks to DonkeyDushan, the guy who made the joke 2021 campaign generator :D + + const imageScaleX = this.canvas.width / this.primaryImage.width; + const imageScaleY = this.canvas.height / this.primaryImage.height; + + const imageScale = Math.max(imageScaleX, imageScaleY) * this.primaryImageZoom; + + // https://stackoverflow.com/a/8529655 + // Thanks to alex! + this.context.setTransform( + imageScale, + 0, 0, + imageScale, + (this.canvas.width - this.primaryImage.width * imageScale) / 2 + this.primaryImageX * this.primaryImageZoom, + (this.canvas.height - this.primaryImage.height * imageScale) / 2 + this.primaryImageY * this.primaryImageZoom + ); + this.context.drawImage( + this.primaryImage, + 0, 0 + ); + this.context.setTransform(); // Reset transformation + } + + const firstPrimaryLine = primaryTextLines[primaryTextLines.length - 1].join(" "); + + // Create rectangle behind the primary text + const primaryRectangleHeight = ( + primaryTextLines.length * (primaryFontSize + primaryFontLinePadding) + + primaryRectanglePaddingTop + + primaryRectanglePaddingBottom + + primaryTextPaddingBottom + + ( + ( + firstPrimaryLine.replace(/[a-zA-Z0-9À-ž]+/g, "").length + !== firstPrimaryLine.replace(/[a-zA-Z0-9]+/g, "").length + ) ? + primaryRectangleAdditionalPaddingWithDiacritics : + 0 + ) + ); + + const classRef = this; + + // Create background gradient + const gradientLoadPromise = new Promise( + resolve => { + const gradientImage = new Image(); + + gradientImage.onload = function() { + classRef.context.drawImage( + this, + 0, 0, + classRef.canvas.width, classRef.canvas.height - primaryRectangleHeight + ); + + resolve(); + } + + gradientImage.src = "static/images/gradient.png"; + } + ); + + await gradientLoadPromise; + + if (this.secondaryText !== "") { + this.context.textAlign = "center"; + this.context.font = `${secondaryFontSize}px ${this.primaryFont}`; + + while ( + this.context.measureText(this.secondaryText).width + > (this.canvas.width - 2 * primaryRectanglePaddingSides) + ) { + secondaryFontSize -= 2; + this.context.font = `${secondaryFontSize}px ${this.primaryFont}`; + } + + this.context.fillStyle = this.secondaryTextColor; + + this.context.fillText( + this.secondaryText, + this.canvas.width / 2, this.canvas.height - primaryRectangleHeight - secondaryTextPaddingBottom + ); + } + + this.context.font = `${this.primaryFontStyle} ${primaryFontSize}px ${this.primaryFont}`; + + const foregroundRGB = hexToRgb(this.foregroundColor); + const foregroundLightness = ( + 0.2126 * foregroundRGB.r + + 0.7152 * foregroundRGB.g + + 0.0722 * foregroundRGB.b + ) + + this.context.beginPath(); + + const primaryRectangleStartingX = 0; + const primaryRectangleEndingX = this.canvas.width; + + this.context.fillStyle = this.foregroundColor; + + this.context.moveTo( + 0, + this.canvas.height + ); + this.context.lineTo( + this.canvas.width, + this.canvas.height + ); + this.context.lineTo( + this.canvas.width, + this.canvas.height - primaryRectangleHeight - primaryRectangleAngle + ); + this.context.lineTo( + 0, + this.canvas.height - primaryRectangleHeight + ); + + this.context.closePath(); + + this.context.fill(); + + function drawIconImage(image) { + const iconHeight = primaryRectangleHeight - iconImageOffsetBottom - iconImageOffsetTop; + const iconWidth = (image.width * (iconHeight / image.height)); + + classRef.context.globalAlpha = iconOpacity; + + const primaryTextRGB = hexToRgb(classRef.primaryTextColor); + + classRef.context.drawImage( + colorizeImage( + image, + iconWidth, iconHeight, + primaryTextRGB.r, + primaryTextRGB.g, + primaryTextRGB.b + ), + (classRef.canvas.width - iconWidth) / 2, classRef.canvas.height - iconImageOffsetBottom - iconHeight, + iconWidth, iconHeight + ); + + classRef.context.globalAlpha = 1; + } + + // Create icon, if there is one + if (this.iconImage !== null) { + drawIconImage(this.iconImage); + } else if (this.iconSource !== null) { + const iconImageLoadPromise = new Promise( + resolve => { + const iconImage = new Image(); + + iconImage.onload = function() { + drawIconImage(this); + + resolve(); + } + + iconImage.src = this.iconSource; + } + ); + + await iconImageLoadPromise; + } + + // Create primary text + this.context.textAlign = "left"; + + const useLightHighlightAndUseDarkLogo = (foregroundLightness > 207); + + const primaryLineX = this.canvas.width / 2; + let currentPrimaryLineY = ( + this.canvas.height + - primaryRectanglePaddingBottom + - primaryFontLinePadding + - primaryTextPaddingBottom + ); + + let primaryTextHighlightedColor = null; + + const lowercasePrimaryTextHighlightColor = this.primaryTextHighlightColor.toLowerCase(); + const hasColorOverride = ( + lowercasePrimaryTextHighlightColor === "#209a37" || + lowercasePrimaryTextHighlightColor === "#e63812" + ); + + if (hasColorOverride) { + if (useLightHighlightAndUseDarkLogo) { + primaryTextHighlightedColor = this.foregroundColor; + } else { + primaryTextHighlightedColor = this.primaryTextColor; + } + } else if (!useLightHighlightAndUseDarkLogo) { + primaryTextHighlightedColor = this.foregroundColor; + } else { + primaryTextHighlightedColor = this.primaryTextColor; + } + + this.context.fillStyle = this.primaryTextColor; + + for (let line of primaryTextLines) { + let wordPosition = 0; + + for (let word of line) { + const previousWords = line.slice(0, wordPosition).join(" "); + const previousWordsWidth = this.context.measureText( + previousWords + + ( + (previousWords.length !== 0) ? + " " : "" + ) + ).width; + + const nextWords = line.slice(wordPosition + 1, line.length).join(" ") + const nextWordsWidth = this.context.measureText( + nextWords + + ( + (nextWords.length !== 0) ? + " " : "" + ) + ).width; + + let currentWordWidth = this.context.measureText(word).width; + + for (const word of line.slice(wordPosition + 1, line.length)) { + if (word.isHighlighted) { + currentWordWidth += this.context.measureText(word.toString() + " ").width; + } else { + break; + } + } + + if (word.isHighlighted) { + if ( + wordPosition === 0 || + !line[wordPosition - 1].isHighlighted + ) { + this.context.fillStyle = this.primaryTextHighlightColor; + this.context.beginPath(); + + const startingHighlightLineX = ( + primaryLineX + + Math.ceil(previousWordsWidth / 2) + - Math.ceil(nextWordsWidth / 2) + - Math.ceil(this.context.measureText(word).width / 2) + ); + + this.context.moveTo( + startingHighlightLineX - highlightPaddingSides, + currentPrimaryLineY + highlightPaddingBottom + ); + this.context.lineTo( + ( + startingHighlightLineX + + currentWordWidth + + highlightPaddingSides + ), + ( + currentPrimaryLineY + + highlightPaddingBottom + - Math.max( + (currentWordWidth * primaryRectangleAngle) + / (this.canvas.width - 2 * primaryRectanglePaddingSides) + ) + ) + ); + this.context.lineTo( + ( + startingHighlightLineX + + currentWordWidth + + highlightPaddingSides + ), + ( + currentPrimaryLineY + - primaryFontSize + - highlightPaddingTop + - Math.max( + (currentWordWidth * primaryRectangleAngle) + / (this.canvas.width - 2 * primaryRectanglePaddingSides) + ) + ) + ); + this.context.lineTo( + startingHighlightLineX - highlightPaddingSides, + ( + currentPrimaryLineY + - primaryFontSize + - highlightPaddingTop + ) + ); + + this.context.closePath(); + + this.context.fill(); + } + + this.context.fillStyle = primaryTextHighlightedColor; + } + + this.context.fillText( + word + " ", + ( + primaryLineX + + Math.ceil(previousWordsWidth / 2) + - Math.ceil(nextWordsWidth / 2) + - Math.ceil(this.context.measureText(word).width / 2) + ), + currentPrimaryLineY + ); + + wordPosition++; + + this.context.fillStyle = this.primaryTextColor; + } + + currentPrimaryLineY -= (primaryFontSize + primaryFontLinePadding); + } + + function drawLogoImage(image) { + const logoWidth = Math.ceil(image.width * (logoHeight / image.height)); + + classRef.context.drawImage( + image, + (classRef.canvas.width - logoWidth) / 2, classRef.canvas.height - logoHeight - logoBottomOffset, + logoWidth, logoHeight + ); + } + + if (this.logoImage === null) { + const logoImageLoadPromise = new Promise( + resolve => { + let logoImage = new Image(); + + logoImage.onload = function() { + drawLogoImage(this); + + resolve(); + } + + if (!useLightHighlightAndUseDarkLogo) { + logoImage.src = classRef.lightLogoDefaultSource; + } else { + logoImage.src = classRef.darkLogoDefaultSource; + } + } + ); + + await logoImageLoadPromise; + } else { + drawLogoImage(this.logoImage); + } + + if (this.requesterText !== "") { + // https://newspaint.wordpress.com/2014/05/22/writing-rotated-text-on-a-javascript-canvas/ + // Thanks to newspaint! + + this.context.save(); + + this.context.translate(this.canvas.width - 1, 0); + + this.context.rotate(3 * Math.PI / 2); + + let requesterFontSize = Math.ceil(this.canvas.width * 0.0175); + + do { + this.context.font = `${this.primaryFontStyle} ${requesterFontSize}px ${this.primaryFont}`; + + if ( + this.context.measureText(this.requesterText).width + > primaryRectangleHeight - (this.canvas.height * 0.03) + ) { + requesterFontSize -= 2; + this.context.font = `${this.primaryFontStyle} ${requesterFontSize}px ${this.primaryFont}`; + } + } while ( + this.context.measureText(this.requesterText).width + > primaryRectangleHeight - (this.canvas.height * 0.03) + ); + + this.context.fillStyle = this.requesterTextColor; + + this.context.textAlign = "left"; + + this.context.globalAlpha = 0.6; + this.context.fillText( + this.requesterText, + -this.canvas.height * 0.985, -this.canvas.width * 0.99 + requesterFontSize + ); + this.context.globalAlpha = 1; + + this.context.restore(); + } + this.finalDrawHook(); + this.stickerDrawHook(); + + this.redrawing = false; + } + + // Text + async setSecondaryText(text, skipRedraw = false) { + this.secondaryText = text; + + if (!skipRedraw) { + await this.redrawCanvas(); + } + } + + // Icon + async setIconSource(url, skipRedraw = false) { + this.iconSource = url; + + if (!skipRedraw) { + await this.redrawCanvas(); + } + } + + async setIconImageFromInput(imageInput, skipRedraw = false) { + if (imageInput.files.length == 0) { + return; + } + + const readPromise = new Promise( + resolve => { + const fileReader = new FileReader(); + + let classRef = this; + + fileReader.onloadend = function(event) { + classRef.iconImage = new Image(); + + classRef.iconImage.onload = function() { + if (!skipRedraw) { + classRef.redrawCanvas(); + } + + resolve(); + } + + classRef.iconImage.src = event.target.result; + } + + fileReader.readAsDataURL(imageInput.files[0]); + } + ); + + await readPromise; + } + + async resetIconImage(skipRedraw = false) { + this.iconImage = null; + + if (!skipRedraw) { + await this.redrawCanvas(); + } + } + + // Color schemes + async setPrimaryColorScheme(scheme, skipRedraw = false) { + switch (scheme) { + case "black-on-white": + this.primaryTextColor = "#000000"; + this.secondaryTextColor = "#ffffff"; + this.foregroundColor = "#ffffff"; + + this.setPrimaryTextHighlightColorScheme("gold", true); + + break; + case "white-on-black": + this.primaryTextColor = "#ffffff"; + this.secondaryTextColor = "#ffffff"; + this.foregroundColor ="#000000"; + + this.setPrimaryTextHighlightColorScheme("gold", true); + + break; + case "forum-black-on-white": + this.primaryTextColor = "#000000"; + this.secondaryTextColor = "#ffffff"; + this.foregroundColor = "#ffffff"; + + this.setPrimaryTextHighlightColorScheme("gold", true); + + break; + case "forum-white-on-purple": + this.primaryTextColor = "#ffffff"; + this.secondaryTextColor = "#ffffff"; + this.foregroundColor = "#962a51"; + + this.setPrimaryTextHighlightColorScheme("gold", true); + + break; + case "zeleni-volary-bystrc-most-black-on-white": + this.primaryTextColor = "#000000"; + this.secondaryTextColor = "#ffffff"; + this.foregroundColor = "#ffffff"; + + this.setPrimaryTextHighlightColorScheme("gold", true); + + break; + case "zeleni-volary-bystrc-most-white-on-green": + this.primaryTextColor = "#ffffff"; + this.secondaryTextColor = "#ffffff"; + this.foregroundColor = "#00ad43"; + + this.setPrimaryTextHighlightColorScheme("gold", true); + + break; + case "spolecne-s-piraty-black-on-white": + this.primaryTextColor = "#000000"; + this.secondaryTextColor = "#ffffff"; + this.foregroundColor = "#ffffff"; + + this.setPrimaryTextHighlightColorScheme("gold", true); + + break; + case "spolecne-s-piraty-white-on-blue": + this.primaryTextColor = "#ffffff"; + this.secondaryTextColor = "#ffffff"; + this.foregroundColor = "#21274e"; + + this.setPrimaryTextHighlightColorScheme("gold", true); + + break; + case "louny-spolecne-black-on-white": + this.primaryTextColor = "#000000"; + this.secondaryTextColor = "#ffffff"; + this.foregroundColor = "#ffffff"; + + this.setPrimaryTextHighlightColorScheme("gold", true); + + break; + case "louny-spolecne-white-on-purple": + this.primaryTextColor = "#ffffff"; + this.secondaryTextColor = "#ffffff"; + this.foregroundColor = "#3e2a5b"; + + this.setPrimaryTextHighlightColorScheme("gold", true); + + break; + case "litomerice-blue-on-white": + this.primaryTextColor = "#123172"; + this.secondaryTextColor = "#ffffff"; + this.foregroundColor = "#ffffff"; + + this.setPrimaryTextHighlightColorScheme("litomerice", true); + + break; + case "litomerice-white-on-blue": + this.primaryTextColor = "#ffffff"; + this.secondaryTextColor = "#ffffff"; + this.foregroundColor = "#123172"; + + this.setPrimaryTextHighlightColorScheme("litomerice", true); + + break; + case "stranane-gray-on-yellow": + this.primaryTextColor = "#4d4d4d"; + this.secondaryTextColor = "#ffffff"; + this.foregroundColor = "#ffd500"; + + break; + case "stranane-yellow-on-white": + this.primaryTextColor = "#4d4d4d"; + this.secondaryTextColor = "#ffffff"; + this.foregroundColor = "#ffd500"; + this.primaryTextHighlightColor = "#4d4d4d"; + + break; + case "stranane-white-on-yellow": + this.primaryTextColor = "#4d4d4d"; + this.secondaryTextColor = "#ffffff"; + this.foregroundColor = "#ffffff"; + this.primaryTextHighlightColor = "#ffd500"; + + break; + case "prusanky-black-on-yellow": + this.primaryTextColor = "#000000"; + this.secondaryTextColor = "#ffffff"; + this.foregroundColor = "#ffd500"; + + break; + case "prusanky-yellow-on-white": + this.primaryTextColor = "#000000"; + this.secondaryTextColor = "#ffffff"; + this.foregroundColor = "#ffd500"; + this.primaryTextHighlightColor = "#000000"; + + break; + case "prusanky-white-on-yellow": + this.primaryTextColor = "#000000"; + this.secondaryTextColor = "#ffffff"; + this.foregroundColor = "#ffffff"; + this.primaryTextHighlightColor = "#ffd500"; + + break; + case "ujezd-green-on-white": + this.primaryTextColor = "#000000"; + this.secondaryTextColor = "#ffffff"; + this.foregroundColor = "#8ed4a3"; + this.primaryTextHighlightColor = "#ffdd55"; + + break; + case "ujezd-white-on-green": + this.primaryTextColor = "#000000"; + this.secondaryTextColor = "#ffffff"; + this.foregroundColor = "#ffffff"; + this.primaryTextHighlightColor = "#8ed4a3"; + + break; + case "cssd-red-on-black": + this.primaryTextColor = "#ffffff"; + this.secondaryTextColor = "#ffffff"; + this.foregroundColor = "#e63812"; + + break; + case "cssd-black-on-red": + this.primaryTextColor = "#ffffff"; + this.secondaryTextColor = "#ffffff"; + this.foregroundColor = "#000000"; + + break; + case "jilemnice-purple-on-black": + this.primaryTextColor = "#ffffff"; + this.secondaryTextColor = "#ffffff"; + this.foregroundColor = "#6e1646"; + + break; + case "jilemnice-black-on-purple": + this.primaryTextColor = "#ffffff"; + this.secondaryTextColor = "#ffffff"; + this.foregroundColor = "#000000"; + + break; + case "novarole-white-on-green": + this.primaryTextColor = "#000000"; + this.secondaryTextColor = "#ffffff"; + this.foregroundColor = "#ffffff"; + this.primaryTextHighlightColor = "#a9ce2d"; + + break; + case "novarole-green-on-white": + this.primaryTextColor = "#000000"; + this.secondaryTextColor = "#ffffff"; + this.foregroundColor = "#a9ce2d"; + + this.primaryTextHighlightColor = "#ffeda5"; + + break; + case "novarole-green-on-black": + this.primaryTextColor = "#000000"; + this.secondaryTextColor = "#ffffff"; + this.foregroundColor = "#a9ce2d"; + + this.primaryTextHighlightColor = "#ffeda5"; + + break; + case "zeleni-melnik-yellow-name-rect": + await this.setPrimaryColorScheme("white-on-black", true); + + break; + default: + throw new Error("This scheme does not exist."); + break; + } + + this.requesterTextColor = this.primaryTextColor; + + if (!skipRedraw) { + await this.redrawCanvas(); + } + } + + async setPrimaryTextHighlightColorScheme(scheme, skipRedraw = false) { + switch(scheme) { + case "gold": + this.primaryTextHighlightColor = "#ffeda5"; + break; + case "litomerice": + this.primaryTextHighlightColor = "#afe87e"; + break; + default: + throw new Error("This scheme does not exist."); + break; + } + + if (!skipRedraw) { + await this.redrawCanvas(); + } + } + + async setPrimaryTextHighlightColor(color, skipRedraw = false) { + this.primaryTextHighlightColor = color; + + if (!skipRedraw) { + await this.redrawCanvas(); + } + } + + async loadData( + primaryImageInput = null, + primaryText = "", + nameText = "", + primaryColorScheme = "", + resultion = 2000, + skipRedraw = false + ) { + await super.loadData( + primaryImageInput, + primaryText, + nameText, + primaryColorScheme, + resultion, + skipRedraw + ); + + $("#icon-image-selection").val("Otevřená radnice 1"); + $("#icon-image-selection").trigger("change"); + } +} diff --git a/generator/static/js/templates/komise.js b/generator/static/js/templates/komise.js new file mode 100644 index 0000000000000000000000000000000000000000..cecbdbd6f7262ebc8976497ed915d99061089655 --- /dev/null +++ b/generator/static/js/templates/komise.js @@ -0,0 +1,889 @@ +class Komise extends Template { + description = "Určeno pro sociální sítě."; + + changeableAttributes = [ + "logoImage", + "logoIsCenter", + "primaryImage", + "primaryText", + "secondaryText", + "nameText", + "underNameText", + "primaryColorScheme", + "primaryImagePosition", + "iconImage" + ]; + + primaryColorSchemes = [ + "black-on-white", + "white-on-black" + ]; + + primaryTextHighlightColorSchemes = [ + "black-on-gold" + ]; + + changeableColors = [ + "primaryTextColor", + "foregroundColor", + "primaryTextHighlightColor", + "requesterTextColor" + ]; + + iconImage = null; + iconSource = ""; + + logoIsCenter = false; + + aspectRatio = 1; + defaultResolution = 2000; + + secondaryText = ""; + underNameText = ""; + + // Canvas + async redrawCanvas() { + if (this.redrawing) { + return; + } + + this.redrawing = true; + + const primaryRectangleAngle = Math.ceil(this.canvas.height * 0.015); + const primaryRectanglePaddingBottom = Math.ceil(this.canvas.height * 0.01); + const primaryRectanglePaddingTop = 0; + const primaryRectangleAdditionalPaddingWithDiacritics = Math.ceil(this.canvas.height * 0.01); + const primaryRectanglePaddingSides = Math.ceil(this.canvas.width * 0.1); + + const highlightPaddingSides = Math.ceil(this.canvas.width * 0.01); + const highlightPaddingBottom = Math.ceil(this.canvas.height * 0.0125); + const highlightPaddingTop = Math.ceil(this.canvas.height * -0.01); // It is, it's how Roboto works. + + const logoHeight = Math.ceil(this.canvas.height * 0.055) * this.logoImageZoom; + const logoBottomOffset = Math.ceil(this.canvas.height * 0.06) - ( + logoHeight / this.logoImageZoom * (this.logoImageZoom - 1) / 2 + ); + + const logoTextOffsetSide = Math.ceil(this.canvas.width * 0.1); + + const nameTextOffsetBottom = Math.ceil(this.canvas.height * 0.08); + + let primaryFontSize = Math.ceil(this.canvas.height * 0.13); + const primaryFontLinePadding = 0; + const primaryTextMaxLines = 3; + const primaryTextPaddingBottom = Math.ceil(this.canvas.height * 0.17); + + let secondaryFontSize = Math.ceil(this.canvas.height * 0.06); + const secondaryTextPaddingBottom = Math.ceil(this.canvas.height * 0.04); + + let nameFontSize = Math.ceil(this.canvas.height * 0.028); + let underNameFontSize = Math.ceil(this.canvas.height * 0.018); + const nameMaxArea = Math.ceil(this.canvas.width * 0.24); + + const iconImageOffset = Math.ceil(this.canvas.height * 0.03); + const iconOpacity = 0.125; + + // Get primary text split into lines, no more than ``primaryTextMaxLines`` of them + + let primaryTextLines = null; + + do { + this.context.font = `${this.primaryFontStyle} ${primaryFontSize}px ${this.primaryFont}`; + + primaryTextLines = splitStringIntoLines( + this.context, + this.primaryText, + this.canvas.width - primaryRectanglePaddingSides * 2, + primaryTextMaxLines, + true + ).reverse(); + + if (primaryTextLines.length > primaryTextMaxLines) { + primaryFontSize -= 2; + } + } while (primaryTextLines.length > primaryTextMaxLines); + + // Clear the canvas + this.context.clearRect( + 0, 0, + this.canvas.width, this.canvas.height + ); + + // Set image + if (this.primaryImage !== null) { + // https://github.com/DonkeyDushan/piratilol/blob/main/src/js/index.js + // Thanks to DonkeyDushan, the guy who made the joke 2021 campaign generator :D + + const imageScaleX = this.canvas.width / this.primaryImage.width; + const imageScaleY = this.canvas.height / this.primaryImage.height; + + const imageScale = Math.max(imageScaleX, imageScaleY) * this.primaryImageZoom; + + // https://stackoverflow.com/a/8529655 + // Thanks to alex! + this.context.setTransform( + imageScale, + 0, 0, + imageScale, + (this.canvas.width - this.primaryImage.width * imageScale) / 2 + this.primaryImageX * this.primaryImageZoom, + (this.canvas.height - this.primaryImage.height * imageScale) / 2 + this.primaryImageY * this.primaryImageZoom + ); + this.context.drawImage( + this.primaryImage, + 0, 0 + ); + this.context.setTransform(); // Reset transformation + } + + const firstPrimaryLine = primaryTextLines[primaryTextLines.length - 1].join(" "); + + // Create rectangle behind the primary text + const primaryRectangleHeight = ( + primaryTextLines.length * (primaryFontSize + primaryFontLinePadding) + + primaryRectanglePaddingTop + + primaryRectanglePaddingBottom + + primaryTextPaddingBottom + + ( + ( + firstPrimaryLine.replace(/[a-zA-Z0-9À-ž]+/g, "").length + !== firstPrimaryLine.replace(/[a-zA-Z0-9]+/g, "").length + ) ? + primaryRectangleAdditionalPaddingWithDiacritics : + 0 + ) + ); + + const classRef = this; + + // Create background gradient + const gradientLoadPromise = new Promise( + resolve => { + const gradientImage = new Image(); + + gradientImage.onload = function() { + classRef.context.drawImage( + this, + 0, 0, + classRef.canvas.width, classRef.canvas.height - primaryRectangleHeight + ); + + resolve(); + } + + gradientImage.src = "static/images/gradient.png"; + } + ); + + await gradientLoadPromise; + + if (this.secondaryText !== "") { + this.context.textAlign = "center"; + this.context.font = `${secondaryFontSize}px ${this.primaryFont}`; + + while ( + this.context.measureText(this.secondaryText).width + > (this.canvas.width - 2 * primaryRectanglePaddingSides) + ) { + secondaryFontSize -= 2; + this.context.font = `${secondaryFontSize}px ${this.primaryFont}`; + } + + this.context.fillStyle = this.secondaryTextColor; + + this.context.fillText( + this.secondaryText, + this.canvas.width / 2, this.canvas.height - primaryRectangleHeight - secondaryTextPaddingBottom + ); + } + + this.context.font = `${this.primaryFontStyle} ${primaryFontSize}px ${this.primaryFont}`; + + const foregroundRGB = hexToRgb(this.foregroundColor); + const foregroundLightness = ( + 0.2126 * foregroundRGB.r + + 0.7152 * foregroundRGB.g + + 0.0722 * foregroundRGB.b + ) + + this.context.beginPath(); + + const primaryRectangleStartingX = 0; + const primaryRectangleEndingX = this.canvas.width; + + this.context.fillStyle = this.foregroundColor; + + this.context.moveTo( + 0, + this.canvas.height + ); + this.context.lineTo( + this.canvas.width, + this.canvas.height + ); + this.context.lineTo( + this.canvas.width, + this.canvas.height - primaryRectangleHeight - primaryRectangleAngle + ); + this.context.lineTo( + 0, + this.canvas.height - primaryRectangleHeight + ); + + this.context.closePath(); + + this.context.fill(); + + function drawIconImage(image) { + const iconHeight = primaryRectangleHeight - 2 * iconImageOffset; + const iconWidth = (image.width * (iconHeight / image.height)); + + classRef.context.globalAlpha = iconOpacity; + + const primaryTextRGB = hexToRgb(classRef.primaryTextColor); + + classRef.context.drawImage( + colorizeImage( + image, + iconWidth, iconHeight, + primaryTextRGB.r, + primaryTextRGB.g, + primaryTextRGB.b + ), + iconImageOffset, classRef.canvas.height - iconImageOffset - iconHeight, + iconWidth, iconHeight + ); + + classRef.context.globalAlpha = 1; + } + + // Create icon, if there is one + if (this.iconImage !== null) { + drawIconImage(this.iconImage); + } else if (this.iconSource !== null) { + const iconImageLoadPromise = new Promise( + resolve => { + const iconImage = new Image(); + + iconImage.onload = function() { + drawIconImage(this); + + resolve(); + } + + iconImage.src = this.iconSource; + } + ); + + await iconImageLoadPromise; + } + + // Create primary text + this.context.textAlign = "left"; + + const useLightHighlightAndUseDarkLogo = (foregroundLightness > 207); + + const primaryLineX = this.canvas.width / 2; + let currentPrimaryLineY = ( + this.canvas.height + - primaryRectanglePaddingBottom + - primaryFontLinePadding + - primaryTextPaddingBottom + ); + + let primaryTextHighlightedColor = null; + + const lowercasePrimaryTextHighlightColor = this.primaryTextHighlightColor.toLowerCase(); + const hasColorOverride = ( + lowercasePrimaryTextHighlightColor === "#209a37" || + lowercasePrimaryTextHighlightColor === "#e63812" + ); + + if (hasColorOverride) { + if (useLightHighlightAndUseDarkLogo) { + primaryTextHighlightedColor = this.foregroundColor; + } else { + primaryTextHighlightedColor = this.primaryTextColor; + } + } else if (!useLightHighlightAndUseDarkLogo) { + primaryTextHighlightedColor = this.foregroundColor; + } else { + primaryTextHighlightedColor = this.primaryTextColor; + } + + this.context.fillStyle = this.primaryTextColor; + + for (let line of primaryTextLines) { + let wordPosition = 0; + + for (let word of line) { + const previousWords = line.slice(0, wordPosition).join(" "); + const previousWordsWidth = this.context.measureText( + previousWords + + ( + (previousWords.length !== 0) ? + " " : "" + ) + ).width; + + const nextWords = line.slice(wordPosition + 1, line.length).join(" ") + const nextWordsWidth = this.context.measureText( + nextWords + + ( + (nextWords.length !== 0) ? + " " : "" + ) + ).width; + + let currentWordWidth = this.context.measureText(word).width; + + for (const word of line.slice(wordPosition + 1, line.length)) { + if (word.isHighlighted) { + currentWordWidth += this.context.measureText(word.toString() + " ").width; + } else { + break; + } + } + + if (word.isHighlighted) { + if ( + wordPosition === 0 || + !line[wordPosition - 1].isHighlighted + ) { + this.context.fillStyle = this.primaryTextHighlightColor; + this.context.beginPath(); + + const startingHighlightLineX = ( + primaryLineX + + Math.ceil(previousWordsWidth / 2) + - Math.ceil(nextWordsWidth / 2) + - Math.ceil(this.context.measureText(word).width / 2) + ); + + this.context.moveTo( + startingHighlightLineX - highlightPaddingSides, + currentPrimaryLineY + highlightPaddingBottom + ); + this.context.lineTo( + ( + startingHighlightLineX + + currentWordWidth + + highlightPaddingSides + ), + ( + currentPrimaryLineY + + highlightPaddingBottom + - Math.max( + (currentWordWidth * primaryRectangleAngle) + / (this.canvas.width - 2 * primaryRectanglePaddingSides) + ) + ) + ); + this.context.lineTo( + ( + startingHighlightLineX + + currentWordWidth + + highlightPaddingSides + ), + ( + currentPrimaryLineY + - primaryFontSize + - highlightPaddingTop + - Math.max( + (currentWordWidth * primaryRectangleAngle) + / (this.canvas.width - 2 * primaryRectanglePaddingSides) + ) + ) + ); + this.context.lineTo( + startingHighlightLineX - highlightPaddingSides, + ( + currentPrimaryLineY + - primaryFontSize + - highlightPaddingTop + ) + ); + + this.context.closePath(); + + this.context.fill(); + } + + this.context.fillStyle = primaryTextHighlightedColor; + } + + this.context.fillText( + word + " ", + ( + primaryLineX + + Math.ceil(previousWordsWidth / 2) + - Math.ceil(nextWordsWidth / 2) + - Math.ceil(this.context.measureText(word).width / 2) + ), + currentPrimaryLineY + ); + + wordPosition++; + + this.context.fillStyle = this.primaryTextColor; + } + + currentPrimaryLineY -= (primaryFontSize + primaryFontLinePadding); + } + + this.context.textAlign = "center"; + + // Create name, if not empty + if (this.nameText !== "") { + // Create rectangle for name text + this.context.font = `bold ${nameFontSize}px 'Roboto Condensed'`; + + while (this.context.measureText(this.nameText).width > nameMaxArea) { + nameFontSize -= 2; + this.context.font = `bold ${nameFontSize}px 'Roboto Condensed'`; + } + + this.context.textAlign = "left"; + + // Create name text itself + this.context.fillStyle = this.primaryTextColor; + this.context.fillText( + this.nameText, + logoTextOffsetSide, this.canvas.height - nameTextOffsetBottom + ); + + if (this.underNameText !== "") { + this.context.font = `${underNameFontSize}px 'Roboto Condensed'`; + + while (this.context.measureText(this.underNameText).width > nameMaxArea) { + underNameFontSize -= 2; + this.context.font = `${underNameFontSize}px 'Roboto Condensed'`; + } + + this.context.fillText( + this.underNameText, + logoTextOffsetSide, this.canvas.height - nameTextOffsetBottom + nameFontSize + ); + } + } + + function drawLogoImage(image) { + const logoWidth = Math.ceil(image.width * (logoHeight / image.height)); + + classRef.context.drawImage( + image, + ( + (!classRef.logoIsCenter) ? + classRef.canvas.width - logoWidth - logoTextOffsetSide : + (classRef.canvas.width - logoWidth) / 2 + ), classRef.canvas.height - logoHeight - logoBottomOffset, + logoWidth, logoHeight + ); + } + + if (this.logoImage === null) { + const logoImageLoadPromise = new Promise( + resolve => { + let logoImage = new Image(); + + logoImage.onload = function() { + drawLogoImage(this); + + resolve(); + } + + if (!useLightHighlightAndUseDarkLogo) { + logoImage.src = classRef.lightLogoDefaultSource; + } else { + logoImage.src = classRef.darkLogoDefaultSource; + } + } + ); + + await logoImageLoadPromise; + } else { + drawLogoImage(this.logoImage); + } + + if (this.requesterText !== "") { + // https://newspaint.wordpress.com/2014/05/22/writing-rotated-text-on-a-javascript-canvas/ + // Thanks to newspaint! + + this.context.save(); + + this.context.translate(this.canvas.width - 1, 0); + + this.context.rotate(3 * Math.PI / 2); + + let requesterFontSize = Math.ceil(this.canvas.width * 0.0175); + + do { + this.context.font = `${this.primaryFontStyle} ${requesterFontSize}px ${this.primaryFont}`; + + if ( + this.context.measureText(this.requesterText).width + > primaryRectangleHeight - (this.canvas.height * 0.03) + ) { + requesterFontSize -= 2; + this.context.font = `${this.primaryFontStyle} ${requesterFontSize}px ${this.primaryFont}`; + } + } while ( + this.context.measureText(this.requesterText).width + > primaryRectangleHeight - (this.canvas.height * 0.03) + ); + + this.context.fillStyle = this.requesterTextColor; + + this.context.textAlign = "left"; + + this.context.globalAlpha = 0.6; + this.context.fillText( + this.requesterText, + -this.canvas.height * 0.985, -this.canvas.width * 0.99 + requesterFontSize + ); + this.context.globalAlpha = 1; + + this.context.restore(); + } + this.finalDrawHook(); + this.stickerDrawHook(); + + this.redrawing = false; + } + + // Text + async setSecondaryText(text, skipRedraw = false) { + this.secondaryText = text; + + if (!skipRedraw) { + await this.redrawCanvas(); + } + } + + async setUnderNameText(text, skipRedraw = false) { + this.underNameText = text; + + if (!skipRedraw) { + await this.redrawCanvas(); + } + } + + // Icon + async setIconSource(url, skipRedraw = false) { + this.iconSource = url; + + if (!skipRedraw) { + await this.redrawCanvas(); + } + } + + async setIconImageFromInput(imageInput, skipRedraw = false) { + if (imageInput.files.length == 0) { + return; + } + + const readPromise = new Promise( + resolve => { + const fileReader = new FileReader(); + + let classRef = this; + + fileReader.onloadend = function(event) { + classRef.iconImage = new Image(); + + classRef.iconImage.onload = function() { + if (!skipRedraw) { + classRef.redrawCanvas(); + } + + resolve(); + } + + classRef.iconImage.src = event.target.result; + } + + fileReader.readAsDataURL(imageInput.files[0]); + } + ); + + await readPromise; + } + + async resetIconImage(skipRedraw = false) { + this.iconImage = null; + + if (!skipRedraw) { + await this.redrawCanvas(); + } + } + + // Color schemes + async setPrimaryColorScheme(scheme, skipRedraw = false) { + switch (scheme) { + case "black-on-white": + this.primaryTextColor = "#000000"; + this.secondaryTextColor = "#ffffff"; + this.foregroundColor = "#ffffff"; + + this.setPrimaryTextHighlightColorScheme("gold", true); + + break; + case "white-on-black": + this.primaryTextColor = "#ffffff"; + this.secondaryTextColor = "#ffffff"; + this.foregroundColor ="#000000"; + + this.setPrimaryTextHighlightColorScheme("gold", true); + + break; + case "forum-black-on-white": + this.primaryTextColor = "#000000"; + this.secondaryTextColor = "#ffffff"; + this.foregroundColor = "#ffffff"; + + this.setPrimaryTextHighlightColorScheme("gold", true); + + break; + case "forum-white-on-purple": + this.primaryTextColor = "#ffffff"; + this.secondaryTextColor = "#ffffff"; + this.foregroundColor = "#962a51"; + + this.setPrimaryTextHighlightColorScheme("gold", true); + + break; + case "zeleni-volary-bystrc-most-black-on-white": + this.primaryTextColor = "#000000"; + this.secondaryTextColor = "#ffffff"; + this.foregroundColor = "#ffffff"; + + this.setPrimaryTextHighlightColorScheme("gold", true); + + break; + case "zeleni-volary-bystrc-most-white-on-green": + this.primaryTextColor = "#ffffff"; + this.secondaryTextColor = "#ffffff"; + this.foregroundColor = "#00ad43"; + + this.setPrimaryTextHighlightColorScheme("gold", true); + + break; + case "spolecne-s-piraty-black-on-white": + this.primaryTextColor = "#000000"; + this.secondaryTextColor = "#ffffff"; + this.foregroundColor = "#ffffff"; + + this.setPrimaryTextHighlightColorScheme("gold", true); + + break; + case "spolecne-s-piraty-white-on-blue": + this.primaryTextColor = "#ffffff"; + this.secondaryTextColor = "#ffffff"; + this.foregroundColor = "#21274e"; + + this.setPrimaryTextHighlightColorScheme("gold", true); + + break; + case "louny-spolecne-black-on-white": + this.primaryTextColor = "#000000"; + this.secondaryTextColor = "#ffffff"; + this.foregroundColor = "#ffffff"; + + this.setPrimaryTextHighlightColorScheme("gold", true); + + break; + case "louny-spolecne-white-on-purple": + this.primaryTextColor = "#ffffff"; + this.secondaryTextColor = "#ffffff"; + this.foregroundColor = "#3e2a5b"; + + this.setPrimaryTextHighlightColorScheme("gold", true); + + break; + case "litomerice-blue-on-white": + this.primaryTextColor = "#123172"; + this.secondaryTextColor = "#ffffff"; + this.foregroundColor = "#ffffff"; + + this.setPrimaryTextHighlightColorScheme("litomerice", true); + + break; + case "litomerice-white-on-blue": + this.primaryTextColor = "#ffffff"; + this.secondaryTextColor = "#ffffff"; + this.foregroundColor = "#123172"; + + this.setPrimaryTextHighlightColorScheme("litomerice", true); + + break; + case "stranane-gray-on-yellow": + this.primaryTextColor = "#4d4d4d"; + this.secondaryTextColor = "#ffffff"; + this.foregroundColor = "#ffd500"; + + break; + case "stranane-yellow-on-white": + this.primaryTextColor = "#4d4d4d"; + this.secondaryTextColor = "#ffffff"; + this.foregroundColor = "#ffd500"; + this.primaryTextHighlightColor = "#4d4d4d"; + + break; + case "stranane-white-on-yellow": + this.primaryTextColor = "#4d4d4d"; + this.secondaryTextColor = "#ffffff"; + this.foregroundColor = "#ffffff"; + this.primaryTextHighlightColor = "#ffd500"; + + break; + case "prusanky-black-on-yellow": + this.primaryTextColor = "#000000"; + this.secondaryTextColor = "#ffffff"; + this.foregroundColor = "#ffd500"; + + break; + case "prusanky-yellow-on-white": + this.primaryTextColor = "#000000"; + this.secondaryTextColor = "#ffffff"; + this.foregroundColor = "#ffd500"; + this.primaryTextHighlightColor = "#000000"; + + break; + case "prusanky-white-on-yellow": + this.primaryTextColor = "#000000"; + this.secondaryTextColor = "#ffffff"; + this.foregroundColor = "#ffffff"; + this.primaryTextHighlightColor = "#ffd500"; + + break; + case "ujezd-green-on-white": + this.primaryTextColor = "#000000"; + this.secondaryTextColor = "#ffffff"; + this.foregroundColor = "#8ed4a3"; + this.primaryTextHighlightColor = "#ffdd55"; + + break; + case "ujezd-white-on-green": + this.primaryTextColor = "#000000"; + this.secondaryTextColor = "#ffffff"; + this.foregroundColor = "#ffffff"; + this.primaryTextHighlightColor = "#8ed4a3"; + + break; + case "cssd-red-on-black": + this.primaryTextColor = "#ffffff"; + this.secondaryTextColor = "#ffffff"; + this.foregroundColor = "#e63812"; + + break; + case "cssd-black-on-red": + this.primaryTextColor = "#ffffff"; + this.secondaryTextColor = "#ffffff"; + this.foregroundColor = "#000000"; + + break; + case "jilemnice-purple-on-black": + this.primaryTextColor = "#ffffff"; + this.secondaryTextColor = "#ffffff"; + this.foregroundColor = "#6e1646"; + + break; + case "jilemnice-black-on-purple": + this.primaryTextColor = "#ffffff"; + this.secondaryTextColor = "#ffffff"; + this.foregroundColor = "#000000"; + + break; + case "novarole-white-on-green": + this.primaryTextColor = "#000000"; + this.secondaryTextColor = "#ffffff"; + this.foregroundColor = "#ffffff"; + this.primaryTextHighlightColor = "#a9ce2d"; + + break; + case "novarole-green-on-white": + this.primaryTextColor = "#000000"; + this.secondaryTextColor = "#ffffff"; + this.foregroundColor = "#a9ce2d"; + + this.primaryTextHighlightColor = "#ffeda5"; + + break; + case "novarole-green-on-black": + this.primaryTextColor = "#000000"; + this.secondaryTextColor = "#ffffff"; + this.foregroundColor = "#a9ce2d"; + + this.primaryTextHighlightColor = "#ffeda5"; + + break; + case "zeleni-melnik-yellow-name-rect": + await this.setPrimaryColorScheme("white-on-black", true); + + break; + default: + throw new Error("This scheme does not exist."); + break; + } + + this.requesterTextColor = this.primaryTextColor; + + if (!skipRedraw) { + await this.redrawCanvas(); + } + } + + async setPrimaryTextHighlightColorScheme(scheme, skipRedraw = false) { + switch(scheme) { + case "gold": + this.primaryTextHighlightColor = "#ffeda5"; + break; + case "litomerice": + this.primaryTextHighlightColor = "#afe87e"; + break; + default: + throw new Error("This scheme does not exist."); + break; + } + + if (!skipRedraw) { + await this.redrawCanvas(); + } + } + + async setPrimaryTextHighlightColor(color, skipRedraw = false) { + this.primaryTextHighlightColor = color; + + if (!skipRedraw) { + await this.redrawCanvas(); + } + } + + async setLogoIsCenter(isCenter, skipRedraw = false) { + this.logoIsCenter = isCenter; + + if (!skipRedraw) { + await this.redrawCanvas(); + } + } + + async loadData( + primaryImageInput = null, + primaryText = "", + nameText = "", + primaryColorScheme = "", + resultion = 2000, + skipRedraw = false + ) { + await super.loadData( + primaryImageInput, + primaryText, + nameText, + primaryColorScheme, + resultion, + skipRedraw + ); + + $("#icon-image-selection").val("Otevřená radnice 1"); + $("#icon-image-selection").trigger("change"); + } +} diff --git a/generator/static/js/ui.js b/generator/static/js/ui.js index 7b898a5d3b086d5accd4f6703d5a555ddbacacf8..ca3e5506c06fd2633c57950639c85fcb1eec3451 100644 --- a/generator/static/js/ui.js +++ b/generator/static/js/ui.js @@ -43,6 +43,8 @@ const templateTypes = { "eu-icons-image": EuIconsImage, "nalodeni": Nalodeni, "nalodeni-story": NalodeniStory, + "komise": Komise, + "komise-story": KomiseStory, "rollup-big-logo-short-text": RollupBigLogoShortText, "rollup-points": RollupPoints, "rollup-short-and-long-text": RollupShortAndLongText, diff --git a/generator/templates/generator.html b/generator/templates/generator.html index e48ccbf5e57466864f8dc811d51aefcb16b60ad3..9336e97866240f4c32ea9ceadd328d0da2ba8665 100644 --- a/generator/templates/generator.html +++ b/generator/templates/generator.html @@ -200,10 +200,18 @@ type="text/javascript" src="{{ url_for('static', filename='js/templates/nalodeni.js') }}" ></script> + <script + type="text/javascript" + src="{{ url_for('static', filename='js/templates/komise.js') }}" + ></script> <script type="text/javascript" src="{{ url_for('static', filename='js/templates/nalodeni-story.js') }}" ></script> + <script + type="text/javascript" + src="{{ url_for('static', filename='js/templates/komise-story.js') }}" + ></script> <script type="text/javascript" src="{{ url_for('static', filename='js/templates/rollup-big-logo-short-text.js') }}" @@ -610,6 +618,15 @@ data-template-type="nalodeni-story" >Nalodění - story</option> + <option + data-image-source="static/images/examples/komise.png" + data-template-type="komise" + >Komise</option> + <option + data-image-source="static/images/examples/komise_story.png" + data-template-type="komise-story" + >Komise - story</option> + <option data-image-source="static/images/examples/rollup_big_logo_short_text.png" data-template-type="rollup-big-logo-short-text"