diff --git a/frontend/src/assets/previews/regional_success.png b/frontend/src/assets/previews/regional_success.png new file mode 100644 index 0000000000000000000000000000000000000000..e3043d4839b49bfb996b3ee58a29be7fb0830b3f Binary files /dev/null and b/frontend/src/assets/previews/regional_success.png differ diff --git a/frontend/src/assets/template/regional_success/arrows_bg.png b/frontend/src/assets/template/regional_success/arrows_bg.png new file mode 100644 index 0000000000000000000000000000000000000000..dfab8a153a0d6bbe86b95ccf29fe15c6a986a657 Binary files /dev/null and b/frontend/src/assets/template/regional_success/arrows_bg.png differ diff --git a/frontend/src/assets/template/regional_success/bg_bottom.png b/frontend/src/assets/template/regional_success/bg_bottom.png new file mode 100644 index 0000000000000000000000000000000000000000..f5736557764348de93ef6ab00a19af1159ec9e56 Binary files /dev/null and b/frontend/src/assets/template/regional_success/bg_bottom.png differ diff --git a/frontend/src/assets/template/regional_success/bg_top.png b/frontend/src/assets/template/regional_success/bg_top.png new file mode 100644 index 0000000000000000000000000000000000000000..165f7fd50c16dd85f91471ee428bf3c80b3805f6 Binary files /dev/null and b/frontend/src/assets/template/regional_success/bg_top.png differ diff --git a/frontend/src/templates.js b/frontend/src/templates.js index 68c76003d8dc84a3521e0fe24c647987337f8985..66f1f0610fe005e0e75770e3c594ec4a92f12725 100644 --- a/frontend/src/templates.js +++ b/frontend/src/templates.js @@ -7,6 +7,7 @@ import newspaperQuoteMiddleImage from './assets/previews/newspaper_quote_middle. import facebookSurveyImage from './assets/previews/facebook_survey.png' import twitterBannerImage from './assets/previews/twitter_banner.png' import posterImage from './assets/previews/poster.png' +import regionalSuccessImage from './assets/previews/regional_success.png' const TEMPLATES = { @@ -90,6 +91,15 @@ const TEMPLATES = { meta: { title: 'Twitter banner' } + }, + regional_success: { + name: 'Povedlo se v kraji', + image: regionalSuccessImage, + path: '/regional-success', + component: () => import('./views/regional_success/RegionalSuccess.vue'), + meta: { + title: 'Povedlo se v kraji' + } } } diff --git a/frontend/src/views/regional_success/RegionalSuccess.vue b/frontend/src/views/regional_success/RegionalSuccess.vue new file mode 100644 index 0000000000000000000000000000000000000000..5d553b7a46debb56cfbe044d263a0b07db8452a8 --- /dev/null +++ b/frontend/src/views/regional_success/RegionalSuccess.vue @@ -0,0 +1,311 @@ +<script setup> +import { watch, ref } from 'vue' + +import COLORS from '../../colors' +import PEOPLE from '../../people' +import TEMPLATES from '../../templates' +import DEFAULT_CONTRACTOR from '../../contractors' +import { loadFonts, loadCanvasStorage, setCanvasStorage, updateAutoRedrawStorage } from '../../utils' +import { generateDefaultLogos, LOGO_POSITIONS, generateLogoPositions } from '../../logos' + +import Canvas from '../../components/canvas/Canvas.vue' +import redraw from './canvas' + +import Navbar from '../../components/Navbar.vue' +import MainContainer from '../../components/MainContainer.vue' +import ImageInput from '../../components/inputs/ImageInput.vue' +import LongTextInput from '../../components/inputs/text/LongTextInput.vue' +import ShortTextInput from '../../components/inputs/text/ShortTextInput.vue' +import EmojiInput from '../../components/inputs/EmojiInput.vue' +import InputSeparator from '../../components/inputs/InputSeparator.vue' +import SelectInput from '../../components/inputs/SelectInput.vue' +import MultipleColorPicker from '../../components/inputs/colors/MultipleColorPicker.vue' +import ReloadButton from '../../components/reload/ReloadButton.vue' +import AutoReloadCheckbox from '../../components/reload/AutoReloadCheckbox.vue' +</script> + +<script> +await loadFonts([ + '12px Bebas Neue', + '12px Roboto Condensed', + 'bold 12px Roboto Condensed' +]) + +export default { + components: { + Canvas, + Navbar, + MainContainer, + ImageInput, + LongTextInput, + ShortTextInput, + EmojiInput, + SelectInput, + InputSeparator, + MultipleColorPicker + }, + data () { + const predefinedColors = { + base: { + name: 'Základní barvy', + colors: { + background: COLORS.white, + baseText: COLORS.black, + highlight: COLORS.yellow1, + highlightedText: COLORS.black, + contractedByText: COLORS.gray1, + arrow: COLORS.yellow1 + } + } + } + + return { + mainText: null, + + mainImage: null, + + firstRow: null, + firstEmoji: null, + secondRow: null, + secondEmoji: null, + thirdRow: null, + thirdEmoji: null, + fourthRow: null, + fourthEmoji: null, + + //contractedBy: DEFAULT_CONTRACTOR, + + logoImage: null, + logoPosition: LOGO_POSITIONS.top_right, + logoOptions: generateLogoPositions( + [ + "top_right", + "top_left", + ] + ), + predefinedLogoImages: generateDefaultLogos('defaultLight'), + + nameText: null, + nameTextDesc: null, + + colorLabels: { + background: 'Pozadí', + baseText: 'Text', + highlight: 'Pozadí zvýrazněného textu', + highlightedText: 'Zvýrazněný text', + arrow: 'Šipka' + }, + + predefinedColors: predefinedColors, + colors: predefinedColors.base.colors, + autoRedraw: false + } + }, + async created () { + await loadCanvasStorage(this) + }, + methods: { + async reloadCanvasProperties () { + const canvasProperties = { + mainImage: this.mainImage, + mainText: this.mainText, + firstRow: this.firstRow, + firstEmoji: this.firstEmoji, + secondRow: this.secondRow, + secondEmoji: this.secondEmoji, + thirdRow: this.thirdRow, + thirdEmoji: this.thirdEmoji, + fourthRow: this.fourthRow, + fourthEmoji: this.fourthEmoji, + logoPosition: this.logoPosition, + logoImage: this.logoImage, + nameText: this.nameText, + nameTextDesc: this.nameTextDesc, + colors: this.colors + } + + await this.$refs.canvas.redraw(canvasProperties) + + // delete canvasProperties.colors + setCanvasStorage(canvasProperties) + } + }, + mounted () { + this.$watch( + vm => [ + vm.mainText, + vm.mainImage, + vm.firstRow, + vm.firstEmoji, + vm.secondRow, + vm.secondEmoji, + vm.thirdRow, + vm.thirdEmoji, + vm.fourthRow, + vm.fourthEmoji, + vm.logoPosition, + vm.logoImage, + vm.nameText, + vm.nameTextDesc, + vm.colors + ], + async (value) => { + if (this.autoRedraw) { + await this.reloadCanvasProperties() + } + }, + { + immediate: true, + deep: true + } + ) + + this.$watch( + vm => [vm.autoRedraw], + async (value) => { + updateAutoRedrawStorage(this.autoRedraw) + + if (this.autoRedraw) { + await this.reloadCanvasProperties() + } + } + ) + }, +} +</script> + +<template> + <header> + <Navbar + :defaultTemplate="TEMPLATES.regional_success" + ></Navbar> + </header> + <main> + <MainContainer> + <template v-slot:left> + <Canvas + ref="canvas" + :redrawFunction="redraw" + width="1080" + height="1350" + /> + </template> + + <template v-slot:right> + <ReloadButton + :parentRefs="$refs" + @click="reloadCanvasProperties" + /> + <AutoReloadCheckbox + v-model="autoRedraw" + /> + + <ImageInput + name="Obrázek" + v-model="mainImage" + :important="true" + zIndex="11" + /> + + <LongTextInput + name="Nadpis" + v-model="mainText" + :highlightable="true" + :important="true" + zIndex="10" + /> + + <LongTextInput + name="Řádek 1" + v-model="firstRow" + :important="true" + zIndex="7" + /> + <EmojiInput + name="Emoji 1" + v-model="firstEmoji" + :important="true" + zIndex="10" + /> + + <LongTextInput + name="Řádek 2" + v-model="secondRow" + :important="true" + zIndex="6" + /> + <EmojiInput + name="Emoji 2" + v-model="secondEmoji" + :important="true" + zIndex="9" + /> + + <LongTextInput + name="Řádek 3" + v-model="thirdRow" + :important="true" + zIndex="6" + /> + <EmojiInput + name="Emoji 3" + v-model="thirdEmoji" + :important="true" + zIndex="8" + /> + + <LongTextInput + name="Řádek 4" + v-model="fourthRow" + :important="true" + zIndex="6" + /> + <EmojiInput + name="Emoji 4" + v-model="fourthEmoji" + :important="true" + zIndex="7" + /> + + <InputSeparator /> + + <LongTextInput + name="Jméno / název" + v-model="nameText" + :highlightable="true" + zIndex="6" + /> + + <LongTextInput + name="Popis" + v-model="nameTextDesc" + zIndex="6" + /> + + <InputSeparator /> + + <ImageInput + name="Obrázek loga" + v-model="logoImage" + :important="false" + :predefinedImages="predefinedLogoImages" + :mustSelectPredefinedImage="true" + :disableImageInput="true" + zIndex="7" + /> + + <SelectInput + name="Pozice loga" + :options="logoOptions" + v-model="logoPosition" + zIndex="6" + /> + + </template> + </MainContainer> + </main> +</template> + +<style> +@import "vue-select/dist/vue-select.css"; +</style> diff --git a/frontend/src/views/regional_success/canvas.js b/frontend/src/views/regional_success/canvas.js new file mode 100644 index 0000000000000000000000000000000000000000..0ba1c2784d9fb61303e70200e4d2ba3baa02b422 --- /dev/null +++ b/frontend/src/views/regional_success/canvas.js @@ -0,0 +1,550 @@ +import { fabric } from 'fabric' +import { clearObjects, sortObjects, transformHighlightedText, checkTextBoxHeight } from '../../components/canvas/utils' +import { PaddedHighlightingTextbox } from '../../components/canvas/textbox' +import backgroundTopImageURL from '../../assets/template/regional_success/bg_top.png' +import backgroundBottomImageURL from '../../assets/template/regional_success/bg_bottom.png' +import backgroundArrowsImageURL from '../../assets/template/regional_success/arrows_bg.png' +//import overlayURL from '../../assets/template/poster/overlay.png' + +let mainTextBox = null + +let logoImage = null +let mainImage = null +let mainImageSource = null +let pointerDownEventAssigned = false +let backgroundTopImage = null +let backgroundBottomImage = null +let backgroundArrowsImage = null +let mainText = null +let firstRow = null +let firstEmoji = null +let secondRow = null +let secondEmoji = null +let thirdRow = null +let thirdEmoji = null +let fourthRow = null +let fourthEmoji = null +let nameText = null +let nameTextDesc = null + +let previousLogoPosition = null + +const removeDownEventListener = () => { + document.getElementsByClassName("upper-canvas")[0].removeEventListener("pointerdown", canvasPointerDownEvent) +} + +let upEventFunction = null +let canvasPointerDownEvent = null + +const redraw = async (canvas, options) => { + clearObjects( + [ + mainText, + firstRow, + firstEmoji, + secondRow, + secondEmoji, + thirdRow, + thirdEmoji, + fourthRow, + fourthEmoji, + nameText, + nameTextDesc + ], + canvas + ) + + const headerHeight = 391 + const mainTextSize = 59 + + const logoWidth = Math.ceil(canvas.width * 0.2) + const logoSideMargin = Math.ceil(canvas.width * 0.04) + + const rowHeightBg = 187 + const rowBottomMargin = 10 + const rowHeight = rowHeightBg + rowBottomMargin + + const emojiHeight = 80 + const emojiLeftMargin = 490 + const emojiTopMargin = Math.ceil(headerHeight+((rowHeightBg-emojiHeight)/2)) + + const rowsMaxWidth = 425 + const rowsTextSize = 38 + const rowsLineHeight = 0.9 + const rowsLeftMargin = 55 + const rowsFontFamily = 'Bebas Neue' + const rowsTextColor = '#fff' + + document.getElementsByClassName("upper-canvas")[0].removeEventListener("pointerup", upEventFunction) + document.getElementsByClassName("upper-canvas")[0].removeEventListener("pointerout", upEventFunction) + document.getElementsByClassName("upper-canvas")[0].removeEventListener("pointercancel", upEventFunction + ) + + canvas.preserveObjectStacking = true + + /* BEGIN Background render */ + + if (backgroundTopImage === null) { + backgroundTopImage = new Image() + + await new Promise(resolve => { + backgroundTopImage.onload = () => { + resolve() + } + + backgroundTopImage.src = backgroundTopImageURL + }) + + backgroundTopImage = new fabric.Image( + backgroundTopImage, + { + top: 0, + left: 0, + selectable: false, + zIndex: 5 + } + ) + backgroundTopImage.scaleToWidth(canvas.width) + + canvas.add(backgroundTopImage) + } + + if (backgroundBottomImage === null) { + backgroundBottomImage = new Image() + + await new Promise(resolve => { + backgroundBottomImage.onload = () => { + resolve() + } + + backgroundBottomImage.src = backgroundBottomImageURL + }) + + backgroundBottomImage = new fabric.Image( + backgroundBottomImage, + { + top: 1169, + left: 0, + zIndex: 10, + selectable: false + } + ) + backgroundBottomImage.scaleToWidth(canvas.width) + + canvas.add(backgroundBottomImage) + } + + /* END Background render */ + + + /* BEGIN Logo render */ + + // A logo is provided, and it either hasn't been rendered yet or is a new one. + const createNewLogo = ( + ( + options.logoImage !== null + && ( + logoImage === null + || ( + options.logoImage !== null + && options.logoImage !== logoImage._element + ) + ) + ) || previousLogoPosition != options.logoPosition.id + ) + + previousLogoPosition = options.logoPosition.id + + if (createNewLogo) { + canvas.remove(logoImage) + + logoImage = new fabric.Image(options.logoImage, {selectable: false}) + logoImage.scaleToWidth(logoWidth) + + if (options.logoPosition.id == "top-right") { + logoImage.set({ + left: ( + canvas.width + - logoWidth + - logoSideMargin + ), + top: logoSideMargin, + zIndex: 11, + }) + } else { + logoImage.set({ + left: logoSideMargin, + top: logoSideMargin, + zIndex: 11, + }) + } + + canvas.add(logoImage) + } + + /* END Logo render */ + + + /* BEGIN Main image render */ + var gradientRect = new fabric.Rect({ + left: 0, + top: 385, + width: 684, + height: 780, + zIndex: 5, + fill: new fabric.Gradient({ + type: 'linear', + coords: { x1: 0, y1: 0, x2: 684, y2: 0 }, + colorStops: [ + { offset: 0, color: 'rgba(255, 255, 255, 1)' }, + { offset: 1, color: 'rgba(255, 255, 255, 0)' } + ] + }), + + selectable: false + }); + canvas.add(gradientRect); + + if ( + options.mainImage !== null + && ( + !canvas.contains(mainImage) + || ( + mainImage === null + || options.mainImage.src !== mainImageSource + ) + ) + ) { + if (mainImage !== null) { + canvas.remove(mainImage) + } + + mainImage = new fabric.Image( + options.mainImage, + { + top: 391, + maxHeight: 782, + right: 0, + zIndex: 3 + } + ) + + mainImage.controls = { + ...fabric.Image.prototype.controls, + mtr: new fabric.Control({ visible: false }) + } + + canvas.add(mainImage) + mainImageSource = options.mainImage.src + // canvas.centerObject(mainImage) + + removeDownEventListener() + pointerDownEventAssigned = false + } else if ( + mainImage !== null + && options.mainImage === null + ) { + canvas.remove(mainImage) + + removeDownEventListener() + pointerDownEventAssigned = false + } + + /* END Main image render */ + + + /* BEGIN Arrow background render */ + + if (backgroundArrowsImage === null) { + backgroundArrowsImage = new Image() + + await new Promise(resolve => { + backgroundArrowsImage.onload = () => { + resolve() + } + + backgroundArrowsImage.src = backgroundArrowsImageURL + }) + + backgroundArrowsImage = new fabric.Image( + backgroundArrowsImage, + { + top: 391, + left: 0, + selectable: false, + zIndex: 6 + } + ) + + canvas.add(backgroundArrowsImage) + } + + /* END Arrows background render */ + + /* BEGIN Main text render */ + + + if (options.mainText !== null) { + const highlightedData = transformHighlightedText( + options.mainText, + mainTextSize, + 'Bebas Neue', + '#000', + '#fec900', + {padWhenDiacritics: true} + ) + mainText = new PaddedHighlightingTextbox( + highlightedData.text, + { + width: canvas.width-(37*2), + left: 37, + top: 155, + textAlign: 'left', + fontFamily: 'Bebas Neue', + fontSize: 80, + lineHeight: 1, + zIndex: 10, + fill: options.colors.baseText.value, + styles: highlightedData.styles, + selectable: false, + highlightPadding: 5 + } + ) + + canvas.add(mainText) + } + /* END Main text render */ + + /* BEGIN Row text render */ + if (options.firstRow !== null) { + firstRow = new fabric.Textbox( + options.firstRow, + { + top: 403, + left: rowsLeftMargin, + width: rowsMaxWidth, + fontFamily: rowsFontFamily, + fontSize: rowsTextSize, + lineHeight: rowsLineHeight, + fill: '#fff', + selectable: false, + zIndex: 40 + } + ) + + canvas.add(firstRow) + } + if (options.firstEmoji !== null) { + firstEmoji = new fabric.Image( + options.firstEmoji, + { + selectable: false, + zIndex: 40, + } + ) + firstEmoji.scaleToHeight(emojiHeight) + firstEmoji.set({ + left: emojiLeftMargin, + top: emojiTopMargin + }) + + canvas.add(firstEmoji) + } + + if (options.secondRow !== null) { + secondRow = new fabric.Textbox( + options.secondRow, + { + top: 600, + left: rowsLeftMargin, + width: rowsMaxWidth, + fontFamily: rowsFontFamily, + fontSize: rowsTextSize, + lineHeight: rowsLineHeight, + fill: rowsTextColor, + selectable: false, + zIndex: 40 + } + ) + + canvas.add(secondRow) + } + if (options.secondEmoji !== null) { + secondEmoji = new fabric.Image( + options.secondEmoji, + { + selectable: false, + zIndex: 40, + } + ) + secondEmoji.scaleToHeight(emojiHeight) + secondEmoji.set({ + left: emojiLeftMargin, + top: emojiTopMargin + rowHeight + }) + + canvas.add(secondEmoji) + } + + if (options.thirdRow !== null) { + thirdRow = new fabric.Textbox( + options.thirdRow, + { + top: 797, + left: rowsLeftMargin, + width: rowsMaxWidth, + fontFamily: rowsFontFamily, + fontSize: rowsTextSize, + lineHeight: rowsLineHeight, + fill: rowsTextColor, + selectable: false, + zIndex: 40 + } + ) + + canvas.add(thirdRow) + } + if (options.thirdEmoji !== null) { + thirdEmoji = new fabric.Image( + options.thirdEmoji, + { + selectable: false, + zIndex: 40, + } + ) + thirdEmoji.scaleToHeight(emojiHeight) + thirdEmoji.set({ + left: emojiLeftMargin, + top: emojiTopMargin + (rowHeight*2) + }) + + canvas.add(thirdEmoji) + } + + if (options.fourthRow !== null) { + fourthRow = new fabric.Textbox( + options.fourthRow, + { + top: 994, + left: rowsLeftMargin, + width: rowsMaxWidth, + fontFamily: rowsFontFamily, + fontSize: rowsTextSize, + lineHeight: rowsLineHeight, + fill: rowsTextColor, + selectable: false, + zIndex: 40 + } + ) + + canvas.add(fourthRow) + } + if (options.fourthEmoji !== null) { + fourthEmoji = new fabric.Image( + options.fourthEmoji, + { + selectable: false, + zIndex: 40, + } + ) + fourthEmoji.scaleToHeight(emojiHeight) + fourthEmoji.set({ + left: emojiLeftMargin, + top: emojiTopMargin + (rowHeight*3) + }) + + canvas.add(fourthEmoji) + } + + + /* END Rows text render */ + + /* BEGIN Name text render */ + + if (options.nameText !== null) { + const highlightedNameData = transformHighlightedText( + options.nameText, + mainTextSize, + 'Bebas Neue', + '#000', + '#fec900', + {padWhenDiacritics: true} + ) + + nameText = new PaddedHighlightingTextbox( + highlightedNameData.text, + { + width: canvas.width-(37*2), + right: 60, + top: 1200, + textAlign: 'right', + fontFamily: 'Bebas Neue', + fontSize: 50, + lineHeight: 1, + zIndex: 12, + fill: options.colors.baseText.value, + styles: highlightedNameData.styles, + selectable: false, + highlightPadding: 5 + } + ) + + canvas.add(nameText) + } + /* END Name text render */ + + + /* BEGIN Name text description render */ + if (options.nameTextDesc !== null) { + nameTextDesc = new fabric.Textbox( + options.nameTextDesc, + { + width: canvas.width-(37*2), + right: 60, + top: 1265, + textAlign: 'right', + fontFamily: 'Roboto Condensed', + fontSize: 20, + fill: "#000", + selectable: false, + zIndex: 12 + } + ) + + canvas.add(nameTextDesc) + } + /* END Name text description render */ + + + sortObjects(canvas) + + if (!pointerDownEventAssigned) { + document.getElementsByClassName("upper-canvas")[0].addEventListener( + "pointerdown", + canvasPointerDownEvent + ) + + pointerDownEventAssigned = true + } + + upEventFunction = (event) => { + redraw(canvas, options) + } + + document.getElementsByClassName("upper-canvas")[0].addEventListener( + "pointerup", + upEventFunction + ) + + document.getElementsByClassName("upper-canvas")[0].addEventListener( + "pointerout", + upEventFunction + ) + + document.getElementsByClassName("upper-canvas")[0].addEventListener( + "pointercancel", + upEventFunction + ) +} + +export default redraw