diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index b74e7e840abadc33e25289b2cf9cffbac00ad7c7..94579c6aec51e81c18b03253570d2e0b0357f2e2 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -2,7 +2,6 @@ stages: - build - test_deploy - image: docker:24.0.1 variables: diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 479c120f9810b911ed090b3303281fdc9036524c..ec049d38e724e45fb1805b45d3df9bd9a17ca266 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,3 +13,7 @@ repos: args: [--fix=lf] - id: detect-private-key - id: check-merge-conflict + - repo: https://github.com/pre-commit/mirrors-prettier + rev: "" # Use the sha or tag you want to point at + hooks: + - id: prettier diff --git a/frontend/.eslintrc.cjs b/frontend/.eslintrc.cjs index b64731a0fe8c67d765f3a3cce1e401fe57d9d2a0..b2fc3102642ebb2f3f02de3f9cbfcd60b273cebd 100644 --- a/frontend/.eslintrc.cjs +++ b/frontend/.eslintrc.cjs @@ -1,14 +1,14 @@ /* eslint-env node */ -require('@rushstack/eslint-patch/modern-module-resolution') +require("@rushstack/eslint-patch/modern-module-resolution"); module.exports = { root: true, - 'extends': [ - 'plugin:vue/vue3-essential', - 'eslint:recommended', - '@vue/eslint-config-prettier/skip-formatting' + extends: [ + "plugin:vue/vue3-essential", + "eslint:recommended", + "@vue/eslint-config-prettier/skip-formatting", ], parserOptions: { - ecmaVersion: 'latest' - } -} + ecmaVersion: "latest", + }, +}; diff --git a/frontend/index.html b/frontend/index.html index a54c0bea7081ad14da2c06a33908f2beff0cf268..fe40e4aa84d5425d8b6d2449f9604ce7e0ff91b2 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -1,16 +1,23 @@ -<!DOCTYPE html> +<!doctype html> <html lang="en"> <head> - <meta charset="UTF-8"> - <link rel="icon" href="/static/favicon.ico"> - <link rel="stylesheet" href="https://styleguide.pirati.cz/2.12.x/css/styles.css"> - <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <meta charset="UTF-8" /> + <link rel="icon" href="/static/favicon.ico" /> + <link + rel="stylesheet" + href="https://styleguide.pirati.cz/2.12.x/css/styles.css" + /> + <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <script> - ;(function () { - var src = '//cdn.jsdelivr.net/npm/eruda'; - if (!/eruda=true/.test(window.location) && localStorage.getItem('active-eruda') != 'true') return; - document.write('<scr' + 'ipt src="' + src + '"></scr' + 'ipt>'); - document.write('<scr' + 'ipt>eruda.init();</scr' + 'ipt>'); + (function () { + var src = "//cdn.jsdelivr.net/npm/eruda"; + if ( + !/eruda=true/.test(window.location) && + localStorage.getItem("active-eruda") != "true" + ) + return; + document.write("<scr" + 'ipt src="' + src + '"></scr' + "ipt>"); + document.write("<scr" + "ipt>eruda.init();</scr" + "ipt>"); })(); </script> <title>Generátor grafiky</title> diff --git a/frontend/postcss.config.js b/frontend/postcss.config.js index 33ad091d26d8a9dc95ebdf616e217d985ec215b8..12a703d900da8159c30e75acbd2c4d87ae177f62 100644 --- a/frontend/postcss.config.js +++ b/frontend/postcss.config.js @@ -3,4 +3,4 @@ module.exports = { tailwindcss: {}, autoprefixer: {}, }, -} +}; diff --git a/frontend/src/App.vue b/frontend/src/App.vue index 5b90105d99c63abe624a3733b49b3a235b55c02d..dcd008a0b8e17cdcafa1e6254c9cce1206e7ccc7 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -1,7 +1,7 @@ <script setup> -import { RouterView } from 'vue-router' +import { RouterView } from "vue-router"; </script> <template> - <RouterView /> + <RouterView /> </template> diff --git a/frontend/src/assets/fonts/glegoo/style.css b/frontend/src/assets/fonts/glegoo/style.css index 2dc6b2e2f7e97ef3673793c51305c36ac3829b12..2acfe75e1c65af6d1fa2f1910cb9d0e2dfb92c33 100644 --- a/frontend/src/assets/fonts/glegoo/style.css +++ b/frontend/src/assets/fonts/glegoo/style.css @@ -1,19 +1,23 @@ /* glegoo-regular - latin_latin-ext */ @font-face { font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */ - font-family: 'Glegoo'; + font-family: "Glegoo"; font-style: normal; font-weight: 400; - src: url('./glegoo-v14-latin_latin-ext-regular.woff2') format('woff2'), /* Chrome 36+, Opera 23+, Firefox 39+ */ - url('./glegoo-v14-latin_latin-ext-regular.woff') format('woff'); /* Chrome 5+, Firefox 3.6+, IE 9+, Safari 5.1+ */ + src: + url("./glegoo-v14-latin_latin-ext-regular.woff2") format("woff2"), + /* Chrome 36+, Opera 23+, Firefox 39+ */ + url("./glegoo-v14-latin_latin-ext-regular.woff") format("woff"); /* Chrome 5+, Firefox 3.6+, IE 9+, Safari 5.1+ */ } /* glegoo-700 - latin_latin-ext */ @font-face { font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */ - font-family: 'Glegoo'; + font-family: "Glegoo"; font-style: normal; font-weight: 700; - src: url('./glegoo-v14-latin_latin-ext-700.woff2') format('woff2'), /* Chrome 36+, Opera 23+, Firefox 39+ */ - url('./glegoo-v14-latin_latin-ext-700.woff') format('woff'); /* Chrome 5+, Firefox 3.6+, IE 9+, Safari 5.1+ */ + src: + url("./glegoo-v14-latin_latin-ext-700.woff2") format("woff2"), + /* Chrome 36+, Opera 23+, Firefox 39+ */ + url("./glegoo-v14-latin_latin-ext-700.woff") format("woff"); /* Chrome 5+, Firefox 3.6+, IE 9+, Safari 5.1+ */ } diff --git a/frontend/src/assets/previews/base_event.png b/frontend/src/assets/previews/base_event.png new file mode 100644 index 0000000000000000000000000000000000000000..a3e69bf55392851ba9b96b80dce416ded5cbbe1b Binary files /dev/null and b/frontend/src/assets/previews/base_event.png differ diff --git a/frontend/src/assets/template/base_event/bg_black.png b/frontend/src/assets/template/base_event/bg_black.png new file mode 100644 index 0000000000000000000000000000000000000000..878fac36940ab8c06a07b1de34f094a11cc842ba Binary files /dev/null and b/frontend/src/assets/template/base_event/bg_black.png differ diff --git a/frontend/src/assets/template/base_event/bg_white.png b/frontend/src/assets/template/base_event/bg_white.png new file mode 100644 index 0000000000000000000000000000000000000000..0e545d6923a74d0ce92da7d5f9820f8b5536f5e8 Binary files /dev/null and b/frontend/src/assets/template/base_event/bg_white.png differ diff --git a/frontend/src/colors.js b/frontend/src/colors.js index 9b65e35987f619daba8cc8ef3e22220b7ad50ea4..096771ffd620cd0d27c044cc7fcdabfb02c45a26 100644 --- a/frontend/src/colors.js +++ b/frontend/src/colors.js @@ -1,28 +1,28 @@ const COLORS = { - black: { - name: 'Černá', - value: '#000000' - }, - white: { - name: 'Bílá', - value: '#ffffff' - }, - yellow1: { - name: 'Žlutá 1', - value: '#fec900' - }, - gray1: { - name: 'Šedá 1', - value: '#b2b2b2' - }, - gray2: { - name: 'Šedá 2', - value: '#888888' - }, - gray3: { - name: 'Šedá 3', - value: '#404040' - } -} + black: { + name: "Černá", + value: "#000000", + }, + white: { + name: "Bílá", + value: "#ffffff", + }, + yellow1: { + name: "Žlutá 1", + value: "#fec900", + }, + gray1: { + name: "Šedá 1", + value: "#b2b2b2", + }, + gray2: { + name: "Šedá 2", + value: "#888888", + }, + gray3: { + name: "Šedá 3", + value: "#404040", + }, +}; -export default COLORS +export default COLORS; diff --git a/frontend/src/components/MainContainer.vue b/frontend/src/components/MainContainer.vue index 8c3b477e03b4c7deb1dcd1311d02cfee563f67f6..97d930ed6e4882eea0930f12b7d0c248b5f48558 100644 --- a/frontend/src/components/MainContainer.vue +++ b/frontend/src/components/MainContainer.vue @@ -1,14 +1,16 @@ <template> - <div class="container container--default mt-12 flex flex-col gap-8 md:grid md:gap-5 md:grid-cols-2"> - <div class="md:col-span-1"> - <slot name="left"></slot> - </div> - <div class="md:col-span-1 flex flex-col gap-2"> - <div class="h-8 flex gap-3 items-center head-alt-md mb-4"> - <i class="ico--cog"></i> - <h2>Nastavení</h2> - </div> - <slot name="right"></slot> - </div> + <div + class="container container--default mt-12 flex flex-col gap-8 md:grid md:gap-5 md:grid-cols-2" + > + <div class="md:col-span-1"> + <slot name="left"></slot> </div> + <div class="md:col-span-1 flex flex-col gap-2"> + <div class="h-8 flex gap-3 items-center head-alt-md mb-4"> + <i class="ico--cog"></i> + <h2>Nastavení</h2> + </div> + <slot name="right"></slot> + </div> + </div> </template> diff --git a/frontend/src/components/Navbar.vue b/frontend/src/components/Navbar.vue index 20f57f1bdb23c94d24267f87ccb808ef6471c951..d8cd0150d6686b3c553292b7086b30ab7477ff79 100644 --- a/frontend/src/components/Navbar.vue +++ b/frontend/src/components/Navbar.vue @@ -1,85 +1,82 @@ <script setup> -import TEMPLATES from '../templates' -import v2Image from '../assets/v2.png' +import TEMPLATES from "../templates"; +import v2Image from "../assets/v2.png"; -import VueSelect from 'vue-select' -import { RouterLink } from 'vue-router' +import VueSelect from "vue-select"; +import { RouterLink } from "vue-router"; </script> <script> export default { - components: { VueSelect }, - props: ['defaultTemplate'], - data () { - return { - currentTemplate: this.defaultTemplate - } + components: { VueSelect }, + props: ["defaultTemplate"], + data() { + return { + currentTemplate: this.defaultTemplate, + }; + }, + watch: { + currentTemplate(value) { + console.info("Switching template: ", value); + this.$router.push(value.path); }, - watch: { - currentTemplate (value) { - console.info("Switching template: ", value) - this.$router.push(value.path) - } - } -} + }, +}; </script> <template> - <nav class="bg-grey-600 py-7"> - <div class="container container--default flex justify-between flex-col lg:flex-row"> - <div class="mb-5 flex items-center lg:pr-8 lg:mb-0"> - <RouterLink to="/"> - <img src="https://styleguide.pirati.cz/2.12.x/images/logo-round-white.svg" class="w-8"> - </RouterLink> - <RouterLink - to="/" - class="text-white pl-4 font-bold text-xl hover:no-underline lg:border-r lg:border-grey-300 lg:pr-8 flex gap-2 items-center" - > - Generátor grafiky - <img - :src="v2Image" - class="h-5" - alt="Verze 2.0" - /> - </RouterLink> - </div> - <div> - <div class="w-72" v-if="currentTemplate"> - <VueSelect - class="bg-white rounded-md" - :options="Object.values(TEMPLATES)" - :clearable="false" - :searchable="false" - label="name" - v-model="currentTemplate" - > - <!-- BEGIN Hide search (TODO) --> - <template v-slot:search="{ attributes, events }"> - <input - class="h-0 w-0" - v-bind="attributes" - v-on="events" - /> - </template> - <!-- END Hide search --> + <nav class="bg-grey-600 py-7"> + <div + class="container container--default flex justify-between flex-col lg:flex-row" + > + <div class="mb-5 flex items-center lg:pr-8 lg:mb-0"> + <RouterLink to="/"> + <img + src="https://styleguide.pirati.cz/2.12.x/images/logo-round-white.svg" + class="w-8" + /> + </RouterLink> + <RouterLink + to="/" + class="text-white pl-4 font-bold text-xl hover:no-underline lg:border-r lg:border-grey-300 lg:pr-8 flex gap-2 items-center" + > + Generátor grafiky + <img :src="v2Image" class="h-5" alt="Verze 2.0" /> + </RouterLink> + </div> + <div> + <div class="w-72" v-if="currentTemplate"> + <VueSelect + class="bg-white rounded-md" + :options="Object.values(TEMPLATES)" + :clearable="false" + :searchable="false" + label="name" + v-model="currentTemplate" + > + <!-- BEGIN Hide search (TODO) --> + <template v-slot:search="{ attributes, events }"> + <input class="h-0 w-0" v-bind="attributes" v-on="events" /> + </template> + <!-- END Hide search --> - <template v-slot:option="option"> - <div class="flex gap-2 items-center"> - <div class="w-12 h-12 flex justify-end shrink-0"> - <img - class="h-12" - alt="Náhled šablony" - :src="option.image" - /> - </div> - <div class="overflow-x-hidden break-normal whitespace-normal"> - {{ option.name }} - </div> - </div> - </template> - </VueSelect> + <template v-slot:option="option"> + <div class="flex gap-2 items-center"> + <div class="w-12 h-12 flex justify-end shrink-0"> + <img + class="h-12 mx-auto" + alt="Náhled šablony" + :src="option.image" + /> </div> - </div> + <div class="overflow-x-hidden break-normal whitespace-normal"> + {{ option.name }} + </div> + </div> + </template> + </VueSelect> </div> - </nav> + </div> + </div> + </nav> </template> diff --git a/frontend/src/components/canvas/Canvas.vue b/frontend/src/components/canvas/Canvas.vue index 64caaf5c6ca3359e56bd3f09de480ce08a1c64a7..cc21e1366dc40346bb5041699f27209d13818f60 100644 --- a/frontend/src/components/canvas/Canvas.vue +++ b/frontend/src/components/canvas/Canvas.vue @@ -1,105 +1,98 @@ <script> -import { fabric } from 'fabric'; +import { fabric } from "fabric"; export default { - props: [ - 'width', - 'height', - 'redrawFunction', - ], - mounted () { - fabric.Object.prototype.set({ - transparentCorners: false, - borderColor: '#ff00ff', - cornerColor: '#ff0000' - }); + props: ["width", "height", "redrawFunction"], + mounted() { + fabric.Object.prototype.set({ + transparentCorners: false, + borderColor: "#ff00ff", + cornerColor: "#ff0000", + }); - this.canvas = new fabric.Canvas(this.$refs.canvas); - }, - data () { - return { - redrawing: false - } - }, - methods: { - async redraw (options) { - if (this.redrawing) { - console.info("Already drawing, skipping redraw.") - return - } + this.canvas = new fabric.Canvas(this.$refs.canvas); + }, + data() { + return { + redrawing: false, + }; + }, + methods: { + async redraw(options) { + if (this.redrawing) { + console.info("Already drawing, skipping redraw."); + return; + } - this.redrawing = true + this.redrawing = true; - console.info(`Redrawing canvas with options: `, options) + console.info(`Redrawing canvas with options: `, options); - try { - await this.redrawFunction(this.canvas, options) - } catch (exception) { - console.error("Error redrawing: ", exception) - } + try { + await this.redrawFunction(this.canvas, options); + } catch (exception) { + console.error("Error redrawing: ", exception); + } - this.redrawing = false - }, - downloadImage () { - this.canvas.discardActiveObject().renderAll() + this.redrawing = false; + }, + downloadImage() { + this.canvas.discardActiveObject().renderAll(); - let link = document.createElement('a') + let link = document.createElement("a"); - link.download = "Vyhrajem.png" - link.href = this.$refs.canvas.toDataURL() + link.download = "Vyhrajem.png"; + link.href = this.$refs.canvas.toDataURL(); - link.click() - } - } -} + link.click(); + }, + }, +}; </script> <template> - <div class="flex flex-col gap-2"> - <div class="flex justify-between items-center h-8 mb-4"> - <div class="flex gap-3 items-center head-alt-md"> - <i class="ico--eye"></i> - <h2>Náhled</h2> - </div> - <div class="flex gap-2"> - <button - class="btn btn--icon" - @click="downloadImage" - > - <div class="btn__body-wrap"> - <div class="btn__body">Stáhnout</div> - <div class="btn__icon"> - <i class="ico--download"></i> - </div> - </div> - </button> + <div class="flex flex-col gap-2"> + <div class="flex justify-between items-center h-8 mb-4"> + <div class="flex gap-3 items-center head-alt-md"> + <i class="ico--eye"></i> + <h2>Náhled</h2> + </div> + <div class="flex gap-2"> + <button class="btn btn--icon" @click="downloadImage"> + <div class="btn__body-wrap"> + <div class="btn__body">Stáhnout</div> + <div class="btn__icon"> + <i class="ico--download"></i> </div> - </div> - <div class="object-contain h-[115vw] md:h-[unset]"> - <canvas - id="canvas" - ref="canvas" - class="w-full border border-gray-300 drop-shadow-md duration-150" - :class="{'blur': redrawing}" - :width="width" - :height="height" - ></canvas> - </div> + </div> + </button> + </div> + </div> + <div class="object-contain h-[115vw] md:h-[unset]"> + <canvas + id="canvas" + ref="canvas" + class="w-full border border-gray-300 drop-shadow-md duration-150" + :class="{ blur: redrawing }" + :width="width" + :height="height" + ></canvas> </div> + </div> </template> <style> .canvas-container { - width: 100% !important; - height: auto !important; + width: 100% !important; + height: auto !important; } .canvas-container > * { - width: 100% !important; - height: auto !important; + width: 100% !important; + height: auto !important; } .lower-canvas { - position: relative !important; + position: relative !important; } </style> diff --git a/frontend/src/components/canvas/textbox.js b/frontend/src/components/canvas/textbox.js index 5954a3b33d9e53e27ab2b622410fac8874c7706c..6584210b70d4292712772d2de053670bbc3fc0f3 100644 --- a/frontend/src/components/canvas/textbox.js +++ b/frontend/src/components/canvas/textbox.js @@ -1,102 +1,110 @@ -import { fabric } from 'fabric' - +import { fabric } from "fabric"; class PaddedHighlightingTextbox extends fabric.Textbox { - _renderTextLinesBackground (ctx) { - if (!this.textBackgroundColor && !this.styleHas('textBackgroundColor')) { - return; + _renderTextLinesBackground(ctx) { + if (!this.textBackgroundColor && !this.styleHas("textBackgroundColor")) { + return; + } + + var heightOfLine, + lineLeftOffset, + originalFill = ctx.fillStyle, + line, + lastColor, + leftOffset = this._getLeftOffset(), + lineTopOffset = this._getTopOffset(), + boxStart = 0, + boxWidth = 0, + charBox, + currentColor, + path = this.path, + drawStart; + + const highlightPadding = + this.highlightPadding !== undefined ? this.highlightPadding : 1; + + for (var i = 0, len = this._textLines.length; i < len; i++) { + heightOfLine = this.getHeightOfLine(i); + + if ( + !this.textBackgroundColor && + !this.styleHas("textBackgroundColor", i) + ) { + lineTopOffset += heightOfLine; + continue; + } + + line = this._textLines[i]; + lineLeftOffset = this._getLineLeftOffset(i); + boxWidth = 0; + boxStart = 0; + lastColor = this.getValueOfPropertyAt(i, 0, "textBackgroundColor"); + + for (var j = 0, jlen = line.length; j < jlen; j++) { + charBox = this.__charBounds[i][j]; + currentColor = this.getValueOfPropertyAt(i, j, "textBackgroundColor"); + + if (path) { + ctx.save(); + ctx.translate(charBox.renderLeft, charBox.renderTop); + ctx.rotate(charBox.angle); + ctx.fillStyle = currentColor; + currentColor && + ctx.fillRect( + -charBox.width / 2, + (-heightOfLine / this.lineHeight) * (1 - this._fontSizeFraction) - + highlightPadding * 2, + charBox.width, + heightOfLine / this.lineHeight + highlightPadding, + ); + ctx.restore(); + } else if (currentColor !== lastColor) { + drawStart = leftOffset + lineLeftOffset + boxStart; + + if (this.direction === "rtl") { + drawStart = this.width - drawStart - boxWidth; + } + + ctx.fillStyle = lastColor; + lastColor && + ctx.fillRect( + drawStart, + lineTopOffset - highlightPadding, + boxWidth, + heightOfLine / this.lineHeight + highlightPadding * 2, + ); + boxStart = charBox.left; + boxWidth = charBox.width; + lastColor = currentColor; + } else { + boxWidth += charBox.kernedWidth; } + } + + if (currentColor && !path) { + drawStart = leftOffset + lineLeftOffset + boxStart; - var heightOfLine, - lineLeftOffset, originalFill = ctx.fillStyle, - line, lastColor, - leftOffset = this._getLeftOffset(), - lineTopOffset = this._getTopOffset(), - boxStart = 0, boxWidth = 0, charBox, currentColor, path = this.path, - drawStart; - - const highlightPadding = ( - (this.highlightPadding !== undefined) ? - this.highlightPadding : 1 - ) - - for (var i = 0, len = this._textLines.length; i < len; i++) { - heightOfLine = this.getHeightOfLine(i); - - if (!this.textBackgroundColor && !this.styleHas('textBackgroundColor', i)) { - lineTopOffset += heightOfLine; - continue; - } - - line = this._textLines[i]; - lineLeftOffset = this._getLineLeftOffset(i); - boxWidth = 0; - boxStart = 0; - lastColor = this.getValueOfPropertyAt(i, 0, 'textBackgroundColor'); - - for (var j = 0, jlen = line.length; j < jlen; j++) { - charBox = this.__charBounds[i][j]; - currentColor = this.getValueOfPropertyAt(i, j, 'textBackgroundColor'); - - if (path) { - ctx.save(); - ctx.translate(charBox.renderLeft, charBox.renderTop); - ctx.rotate(charBox.angle); - ctx.fillStyle = currentColor; - currentColor && ctx.fillRect( - -charBox.width / 2, - (-heightOfLine / this.lineHeight * (1 - this._fontSizeFraction)) - highlightPadding * 2, - charBox.width, - (heightOfLine / this.lineHeight) + highlightPadding - ); - ctx.restore(); - } else if (currentColor !== lastColor) { - drawStart = leftOffset + lineLeftOffset + boxStart; - - if (this.direction === 'rtl') { - drawStart = this.width - drawStart - boxWidth; - } - - ctx.fillStyle = lastColor; - lastColor && ctx.fillRect( - drawStart, - lineTopOffset - highlightPadding, - boxWidth, - (heightOfLine / this.lineHeight) + highlightPadding * 2 - ); - boxStart = charBox.left; - boxWidth = charBox.width; - lastColor = currentColor; - } else { - boxWidth += charBox.kernedWidth; - } - } - - if (currentColor && !path) { - drawStart = leftOffset + lineLeftOffset + boxStart; - - if (this.direction === 'rtl') { - drawStart = this.width - drawStart - boxWidth; - } - - ctx.fillStyle = currentColor; - ctx.fillRect( - drawStart, - lineTopOffset - highlightPadding, - boxWidth, - (heightOfLine / this.lineHeight) + highlightPadding * 2 - ); - } - - lineTopOffset += heightOfLine; + if (this.direction === "rtl") { + drawStart = this.width - drawStart - boxWidth; } - ctx.fillStyle = originalFill; - // if there is text background color no - // other shadows should be casted - this._removeShadow(ctx); + ctx.fillStyle = currentColor; + ctx.fillRect( + drawStart, + lineTopOffset - highlightPadding, + boxWidth, + heightOfLine / this.lineHeight + highlightPadding * 2, + ); + } + + lineTopOffset += heightOfLine; } -} + ctx.fillStyle = originalFill; + // if there is text background color no + // other shadows should be casted + this._removeShadow(ctx); + } +} -export { PaddedHighlightingTextbox } +export { PaddedHighlightingTextbox }; diff --git a/frontend/src/components/canvas/utils.js b/frontend/src/components/canvas/utils.js index 4645b1e13cfa194dc6a2e0fbb15ac972f3aeda44..49bc18776db46315476fa7b4752dbd66fee3f659 100644 --- a/frontend/src/components/canvas/utils.js +++ b/frontend/src/components/canvas/utils.js @@ -1,293 +1,275 @@ -import alertifyjs from "alertifyjs" -import "alertifyjs/build/css/alertify.css" - -import { fabric } from 'fabric' +import alertifyjs from "alertifyjs"; +import "alertifyjs/build/css/alertify.css"; +import { fabric } from "fabric"; const setCharAt = (str, index, chr) => { - if (index > str.length - 1) return str + if (index > str.length - 1) return str; - return str.substring(0, index) + chr + str.substring(index + 1) -} + return str.substring(0, index) + chr + str.substring(index + 1); +}; const clearObjects = (clearableItems, canvas) => { - for (const clearableItem of clearableItems) { - if (clearableItem !== null) { - canvas.remove(clearableItem); - } + for (const clearableItem of clearableItems) { + if (clearableItem !== null) { + canvas.remove(clearableItem); } -} + } +}; const sortObjects = (canvas) => { - canvas._objects.sort((a, b) => (a.zIndex > b.zIndex) ? 1 : -1) - canvas.renderAll() -} - + canvas._objects.sort((a, b) => (a.zIndex > b.zIndex ? 1 : -1)); + canvas.renderAll(); +}; const transformTextLineBreaks = ( - text, - fontSize, - fontFamily, - maxWidth, - options + text, + fontSize, + fontFamily, + maxWidth, + options, ) => { - if (options === undefined) { - options = {} - } + if (options === undefined) { + options = {}; + } - text = text.replace(/[^\S\r\n]+/g, ' ') - text = text.replace(/\r\n/g, '\n') + text = text.replace(/[^\S\r\n]+/g, " "); + text = text.replace(/\r\n/g, "\n"); - let positionWithinString = -1 - let currentWidth = 0 + let positionWithinString = -1; + let currentWidth = 0; - const splitWords = text.split(" ") - let wordIndexes = {} - const spaceText = new fabric.Text( - " ", - { - fontFamily: fontFamily, - fontSize: fontSize - } - ) + const splitWords = text.split(" "); + let wordIndexes = {}; + const spaceText = new fabric.Text(" ", { + fontFamily: fontFamily, + fontSize: fontSize, + }); - for (let wordPosition = 0; wordPosition < splitWords.length; wordPosition++) { - let currentWord = splitWords[wordPosition] - let skipNewLineGeneration = false + for (let wordPosition = 0; wordPosition < splitWords.length; wordPosition++) { + let currentWord = splitWords[wordPosition]; + let skipNewLineGeneration = false; - if (currentWord.includes("\n")) { - skipNewLineGeneration = true + if (currentWord.includes("\n")) { + skipNewLineGeneration = true; - const breakSplitWord = currentWord.split("\n") + const breakSplitWord = currentWord.split("\n"); - const firstLineWord = breakSplitWord[0] - const secondLineWord = breakSplitWord[1] + const firstLineWord = breakSplitWord[0]; + const secondLineWord = breakSplitWord[1]; - // Word + \n - positionWithinString += firstLineWord.length + 1 + // Word + \n + positionWithinString += firstLineWord.length + 1; - currentWord = secondLineWord - currentWidth = 0 - } + currentWord = secondLineWord; + currentWidth = 0; + } - const wordIsLast = (wordPosition === splitWords.length - 1) + const wordIsLast = wordPosition === splitWords.length - 1; - positionWithinString += currentWord.length + ( - (!wordIsLast) ? - 1 : 0 - ) + positionWithinString += currentWord.length + (!wordIsLast ? 1 : 0); - const wordText = new fabric.Text( - currentWord, - { - fontFamily: fontFamily, - fontSize: fontSize - } - ); + const wordText = new fabric.Text(currentWord, { + fontFamily: fontFamily, + fontSize: fontSize, + }); + + // This is really ugly, I have no idea why Chromium thinks the text is shorter than it really is. + // (Or why Firefox thinks it's longer.) + // But, it works. + currentWidth += wordText.width * (!!window.chrome ? 1.183 : 1); - // This is really ugly, I have no idea why Chromium thinks the text is shorter than it really is. - // (Or why Firefox thinks it's longer.) - // But, it works. - currentWidth += wordText.width * ( - (!!window.chrome) - ? 1.183 - : 1 + if (!skipNewLineGeneration && currentWidth > maxWidth) { + if ( + ["a", "i", "o", "u", "s", "se", "v", "z"].includes( + splitWords[ + wordPosition !== 0 ? wordPosition - 1 : wordPosition + ].replace("*", ""), ) + ) { + // Previous word is not a, i, o, u, s, ... + const lineBreakPosition = + positionWithinString - + (!wordIsLast ? 1 : 0) - + currentWord.length - + 1 - + splitWords[wordPosition !== 0 ? wordPosition - 1 : wordPosition] + .length; + + text = setCharAt(text, lineBreakPosition, "\n"); + } else { + text = setCharAt( + text, + positionWithinString - (!wordIsLast ? 1 : 0) - currentWord.length, + "\n", + ); + } - if (!skipNewLineGeneration && currentWidth > maxWidth) { - if ( - ["a", "i", "o", "u", "s", "se", "v", "z"]. - includes(splitWords[ - (wordPosition !== 0) - ? (wordPosition - 1) - : (wordPosition) - ].replace("*", "")) - ) { // Previous word is not a, i, o, u, s, ... - const lineBreakPosition = ( - positionWithinString - - ( - (!wordIsLast) ? - 1 : 0 - ) - - currentWord.length - - 1 - - splitWords[ - (wordPosition !== 0) - ? (wordPosition - 1) - : (wordPosition) - ].length - ) - - text = setCharAt(text, lineBreakPosition, "\n") - } else { - text = setCharAt( - text, - ( - positionWithinString - - ( - (!wordIsLast) ? - 1 : 0 - ) - - currentWord.length - ), - "\n" - ) - } - - currentWidth = wordText.width - } else if (!wordIsLast) { - currentWidth += spaceText.width - } + currentWidth = wordText.width; + } else if (!wordIsLast) { + currentWidth += spaceText.width; } - - if (options.prependLinesWithSpace) { - let prependedText = "" - let splitLines = text.split("\n") - let linePosition = 0; - - for (let line of splitLines) { - if (linePosition + 1 !== splitLines.length) { - if (line[0] === "*") { - line = `${line}\n` - } else { - line = ` ${line}\n` - } - } - - prependedText += line + } + + if (options.prependLinesWithSpace) { + let prependedText = ""; + let splitLines = text.split("\n"); + let linePosition = 0; + + for (let line of splitLines) { + if (linePosition + 1 !== splitLines.length) { + if (line[0] === "*") { + line = `${line}\n`; + } else { + line = ` ${line}\n`; } + } - text = prependedText + prependedText += line; } - return text -} + text = prependedText; + } + return text; +}; const transformHighlightedText = ( - text, - fontSize, - maxWidth, - fontFamily, - highlightColor, - highlightedTextColor, - options + text, + fontSize, + maxWidth, + fontFamily, + highlightColor, + highlightedTextColor, + options, ) => { - if (options === undefined) { - options = {} - } + if (options === undefined) { + options = {}; + } - text = transformTextLineBreaks( - text, - fontSize, - fontFamily, - maxWidth, - options - ) + text = transformTextLineBreaks(text, fontSize, fontFamily, maxWidth, options); - let positionWithinString = 0 + let positionWithinString = 0; - const textContainsDiacritics = ( - /[áčďéěíňóřšťúůýžÁČĎÉĚÍŇÓŘŠŤÚÝŽ]+/.test(text) - ) + const textContainsDiacritics = /[áčďéěíňóřšťúůýžÁČĎÉĚÍŇÓŘŠŤÚÝŽ]+/.test(text); - const textContainsHighlight = ( - /\*/.test(text) - ) + const textContainsHighlight = /\*/.test(text); - let positionWithinLine = 0 - let linePosition = 0 - let highlightIsActive = false + let positionWithinLine = 0; + let linePosition = 0; + let highlightIsActive = false; - let styles = { - 0: {} - } + let styles = { + 0: {}, + }; - let prependWithSpace = false; + let prependWithSpace = false; - for (const character of text) { - const characterIsStar = (character === "*") + for (const character of text) { + const characterIsStar = character === "*"; - if (characterIsStar) { - highlightIsActive = !highlightIsActive - text = setCharAt(text, positionWithinString, " ") - } - - let style = {} - - if (highlightIsActive || characterIsStar) { - style.textBackgroundColor = highlightColor - style.fill = highlightedTextColor - } - - if (options.padWhenDiacritics && textContainsHighlight && textContainsDiacritics) { - style.deltaY = Math.ceil(fontSize * ( - (options.diacriticsDeltaYOffset !== undefined) ? - options.diacriticsDeltaYOffset : 0.1 - )) - } - - styles[linePosition][positionWithinLine] = style - - positionWithinLine++ - positionWithinString++ - -// TODO -// -// if (positionWithinLine === 1 && character === "*") { -// prependWithSpace = true; -// } - - if (character === "\n") { - styles[linePosition + 1] = {} - linePosition++ + if (characterIsStar) { + highlightIsActive = !highlightIsActive; + text = setCharAt(text, positionWithinString, " "); + } - if (highlightIsActive) { - text = text.slice(0, positionWithinString - 1) + " " + text.slice(positionWithinString - 1) - positionWithinString++ + let style = {}; - text = text.slice(0, positionWithinString) + " " + text.slice(positionWithinString) - positionWithinString++ + if (highlightIsActive || characterIsStar) { + style.textBackgroundColor = highlightColor; + style.fill = highlightedTextColor; + } - positionWithinLine = 1 - styles[linePosition][0] = style - } else { - positionWithinLine = 0 - } - } + if ( + options.padWhenDiacritics && + textContainsHighlight && + textContainsDiacritics + ) { + style.deltaY = Math.ceil( + fontSize * + (options.diacriticsDeltaYOffset !== undefined + ? options.diacriticsDeltaYOffset + : 0.1), + ); } - return { - text: text, - styles: styles, - paddingBottom: ( - (options.padWhenDiacritics && textContainsHighlight && textContainsDiacritics) ? - Math.ceil(fontSize * ( - (options.diacriticsDeltaYOffset !== undefined) ? - options.diacriticsDeltaYOffset : 0.1 - )) : 0 - ) + styles[linePosition][positionWithinLine] = style; + + positionWithinLine++; + positionWithinString++; + + // TODO + // + // if (positionWithinLine === 1 && character === "*") { + // prependWithSpace = true; + // } + + if (character === "\n") { + styles[linePosition + 1] = {}; + linePosition++; + + if (highlightIsActive) { + text = + text.slice(0, positionWithinString - 1) + + " " + + text.slice(positionWithinString - 1); + positionWithinString++; + + text = + text.slice(0, positionWithinString) + + " " + + text.slice(positionWithinString); + positionWithinString++; + + positionWithinLine = 1; + styles[linePosition][0] = style; + } else { + positionWithinLine = 0; + } } -} + } + + return { + text: text, + styles: styles, + paddingBottom: + options.padWhenDiacritics && + textContainsHighlight && + textContainsDiacritics + ? Math.ceil( + fontSize * + (options.diacriticsDeltaYOffset !== undefined + ? options.diacriticsDeltaYOffset + : 0.1), + ) + : 0, + }; +}; const checkTextBoxHeight = (textBox, maxLines) => { - if (textBox.textLines.length > maxLines) { - if (!window.showingMaxLinesWarning) { - window.showingMaxLinesWarning = true - - const errorMessage = alertifyjs.error( - `Text je moc dlouhý a nevejde se do ${maxLines} řádků. Prosím, zkrať ho.`, - ) + if (textBox.textLines.length > maxLines) { + if (!window.showingMaxLinesWarning) { + window.showingMaxLinesWarning = true; - errorMessage.callback = () => { - window.showingMaxLinesWarning = false - } - } + const errorMessage = alertifyjs.error( + `Text je moc dlouhý a nevejde se do ${maxLines} řádků. Prosím, zkrať ho.`, + ); - let textLines = [...textBox.textLines] - textLines.splice(maxLines) - textBox.set("text", textLines.join('\n')) + errorMessage.callback = () => { + window.showingMaxLinesWarning = false; + }; } -} -export { clearObjects, sortObjects, transformHighlightedText, transformTextLineBreaks, checkTextBoxHeight } + let textLines = [...textBox.textLines]; + textLines.splice(maxLines); + textBox.set("text", textLines.join("\n")); + } +}; + +export { + clearObjects, + sortObjects, + transformHighlightedText, + transformTextLineBreaks, + checkTextBoxHeight, +}; diff --git a/frontend/src/components/inputs/EmojiInput.vue b/frontend/src/components/inputs/EmojiInput.vue index 998b8cfcc59f65a969ef8c6b2157d3651206e8ea..1fff04107288ce57a59c5c399814a978d0e0c49a 100644 --- a/frontend/src/components/inputs/EmojiInput.vue +++ b/frontend/src/components/inputs/EmojiInput.vue @@ -1,89 +1,89 @@ <script setup> -import VueSelect from 'vue-select' -import InputHeading from "./InputHeading.vue" +import VueSelect from "vue-select"; +import InputHeading from "./InputHeading.vue"; -import likeImage from '../../assets/reactions/like.png' -import laughImage from '../../assets/reactions/laugh.png' -import heartImage from '../../assets/reactions/heart.png' -import angryImage from '../../assets/reactions/angry.png' -import sadImage from '../../assets/reactions/sad.png' -import surprisedImage from '../../assets/reactions/surprised.png' -import careImage from '../../assets/reactions/care.png' +import likeImage from "../../assets/reactions/like.png"; +import laughImage from "../../assets/reactions/laugh.png"; +import heartImage from "../../assets/reactions/heart.png"; +import angryImage from "../../assets/reactions/angry.png"; +import sadImage from "../../assets/reactions/sad.png"; +import surprisedImage from "../../assets/reactions/surprised.png"; +import careImage from "../../assets/reactions/care.png"; </script> <script> export default { - components: { InputHeading, VueSelect }, - props: ['name', 'important', 'zIndex', 'modelValue'], - emits: ['update:modelValue'], - data () { - return { - emojiOptions: [ - { - title: '👍 Like', - url: likeImage - }, - { - title: '😂 Smích', - url: laughImage - }, - { - title: '♥️ Srdce', - url: heartImage - }, - { - title: '😡 Naštvaný', - url: angryImage - }, - { - title: '☹️ Smutný', - url: sadImage - }, - { - title: '😲 Překvapený', - url: surprisedImage - }, - { - title: '🥰 Péče', - url: careImage - }, - ], - selectedEmoji: this.modelValue - } - }, - watch: { - selectedEmoji (value) { - const image = new Image() + components: { InputHeading, VueSelect }, + props: ["name", "important", "zIndex", "modelValue"], + emits: ["update:modelValue"], + data() { + return { + emojiOptions: [ + { + title: "👍 Like", + url: likeImage, + }, + { + title: "😂 Smích", + url: laughImage, + }, + { + title: "♥️ Srdce", + url: heartImage, + }, + { + title: "😡 Naštvaný", + url: angryImage, + }, + { + title: "☹️ Smutný", + url: sadImage, + }, + { + title: "😲 Překvapený", + url: surprisedImage, + }, + { + title: "🥰 Péče", + url: careImage, + }, + ], + selectedEmoji: this.modelValue, + }; + }, + watch: { + selectedEmoji(value) { + const image = new Image(); - if (value === null || value === undefined) { - this.$emit('update:modelValue', null) - return - } + if (value === null || value === undefined) { + this.$emit("update:modelValue", null); + return; + } - image.onload = () => { - this.$emit('update:modelValue', image) - } + image.onload = () => { + this.$emit("update:modelValue", image); + }; - image.src = value.url - } - } -} + image.src = value.url; + }, + }, +}; </script> <template> - <section - class="flex flex-col gap-2 bg-gray-100 p-4 drop-shadow-md" - :style="{'z-index': zIndex}" - > - <InputHeading - :name="name" - :important="important" - icon="thumbs-up" - ></InputHeading> - <VueSelect - :options="emojiOptions" - v-model="selectedEmoji" - label="title" - ></VueSelect> - </section> + <section + class="flex flex-col gap-2 bg-gray-100 p-4 drop-shadow-md" + :style="{ 'z-index': zIndex }" + > + <InputHeading + :name="name" + :important="important" + icon="thumbs-up" + ></InputHeading> + <VueSelect + :options="emojiOptions" + v-model="selectedEmoji" + label="title" + ></VueSelect> + </section> </template> diff --git a/frontend/src/components/inputs/ImageInput.vue b/frontend/src/components/inputs/ImageInput.vue index d65414000ed24e78853161691a3f3f94878cc877..8d4fc2398f2c47cd66d60ea68c5ad7bdecbe67d7 100644 --- a/frontend/src/components/inputs/ImageInput.vue +++ b/frontend/src/components/inputs/ImageInput.vue @@ -1,190 +1,182 @@ <script setup> -import InputHeading from "./InputHeading.vue" +import InputHeading from "./InputHeading.vue"; -import VueSelect from 'vue-select' +import VueSelect from "vue-select"; </script> <script> export default { - components: { InputHeading, VueSelect }, - props: [ - 'name', - 'important', - 'zIndex', - 'modelValue', - 'predefinedImages', - 'mustSelectPredefinedImage', - 'disableImageInput' - ], - emits: ['update:modelValue'], - data () { - let data = { - hasFile: false, - selectedImage: null + components: { InputHeading, VueSelect }, + props: [ + "name", + "important", + "zIndex", + "modelValue", + "predefinedImages", + "mustSelectPredefinedImage", + "disableImageInput", + ], + emits: ["update:modelValue"], + data() { + let data = { + hasFile: false, + selectedImage: null, + }; + + if (this.predefinedImages) { + for (const image of this.predefinedImages) { + if (!image.defaultSelected) { + continue; } - if (this.predefinedImages) { - for (const image of this.predefinedImages) { - if (!image.defaultSelected) { - continue - } + data.selectedImage = image; + } + } - data.selectedImage = image - } - } + return data; + }, + methods: { + handleFileInput(event) { + if (event.target.files.length !== 0) { + this.loadImageFromFile(event.target.files[0]); + } else { + this.hasFile = false; + } + }, + loadImageFromFile(file) { + this.hasFile = true; + const image = new Image(); + + image.onload = () => { + this.$emit("update:modelValue", image); + }; + + const reader = new FileReader(); + + reader.onloadend = () => { + image.src = reader.result; + }; - return data + reader.readAsDataURL(file); }, - methods: { - handleFileInput (event) { - if (event.target.files.length !== 0) { - this.loadImageFromFile(event.target.files[0]) - } else { - this.hasFile = false - } - }, - loadImageFromFile (file) { - this.hasFile = true - const image = new Image() - - image.onload = () => { - this.$emit('update:modelValue', image) - } - - const reader = new FileReader() - - reader.onloadend = () => { - image.src = reader.result - } - - reader.readAsDataURL(file) - }, - setSelectedImage (value) { - this.selectedImage = value - - if (value !== null) { - const image = new Image() - - image.onload = () => { - this.$emit('update:modelValue', image) - } - - image.src = value.src - } else { - if (this.hasFile) { - this.loadImageFromFile(this.$refs.fileInput.files[0]) - } else { - this.$emit('update:modelValue', null) - } - } - }, - clearFileInput (event) { - this.hasFile = false - this.$refs.fileInput.value = '' - - if (this.selectedImage) { - this.setSelectedImage(this.selectedImage) - } else { - this.$emit('update:modelValue', null) - } + setSelectedImage(value) { + this.selectedImage = value; + + if (value !== null) { + const image = new Image(); + + image.onload = () => { + this.$emit("update:modelValue", image); + }; + + image.src = value.src; + } else { + if (this.hasFile) { + this.loadImageFromFile(this.$refs.fileInput.files[0]); + } else { + this.$emit("update:modelValue", null); } + } }, - mounted () { - if (this.selectedImage && !this.modelValue) { - this.setSelectedImage(this.selectedImage) - } + clearFileInput(event) { + this.hasFile = false; + this.$refs.fileInput.value = ""; + + if (this.selectedImage) { + this.setSelectedImage(this.selectedImage); + } else { + this.$emit("update:modelValue", null); + } + }, + }, + mounted() { + if (this.selectedImage && !this.modelValue) { + this.setSelectedImage(this.selectedImage); } -} + }, +}; </script> <template> - <section - class="flex flex-col gap-2 bg-gray-100 p-4 drop-shadow-md" - :style="{'z-index': zIndex}" + <section + class="flex flex-col gap-2 bg-gray-100 p-4 drop-shadow-md" + :style="{ 'z-index': zIndex }" + > + <InputHeading + :name="name" + :important="important" + icon="file-picture" + ></InputHeading> + + <hr class="hr--unstyled border-t-gray-300" /> + + <div class="flex justify-between gap-2" v-if="!disableImageInput"> + <input + ref="fileInput" + type="file" + accept="image/*" + @change="handleFileInput" + /> + <button + aria-label="Odstranit obrázek" + class="shrink-0 bg-black text-xs text-white duration-150 w-8 hover:bg-grey-800" + v-if="hasFile" + @click="clearFileInput" + > + <i class="ico--cross"></i> + </button> + </div> + + <div + :class="{ 'mt-2': !disableImageInput }" + class="flex flex-col gap-2" + v-if="predefinedImages" > - <InputHeading - :name="name" - :important="important" - icon="file-picture" - ></InputHeading> - - <hr class="hr--unstyled border-t-gray-300"> - - <div class="flex justify-between gap-2" v-if="!disableImageInput"> - <input - ref="fileInput" - type="file" - accept="image/*" - @change="handleFileInput" - > - <button - aria-label="Odstranit obrázek" - class="shrink-0 bg-black text-xs text-white duration-150 w-8 hover:bg-grey-800" - v-if="hasFile" - @click="clearFileInput" - > - <i class="ico--cross"></i> - </button> - </div> - - <div - :class="{ 'mt-2': !disableImageInput }" - class="flex flex-col gap-2" - v-if="predefinedImages" - > + <div> + <small v-if="!disableImageInput">Nebo vyber ze senamu:</small> + <span v-else>Vyber ze seznamu:</span> + </div> + <VueSelect + ref="predefinedImageSelect" + :options="predefinedImages" + :clearable="!mustSelectPredefinedImage" + :searchable="false" + :modelValue="selectedImage ? selectedImage : null" + @update:modelValue="setSelectedImage" + label="name" + > + <!-- BEGIN Hide search (TODO) --> + <template v-slot:search="{ attributes, events }"> + <input class="h-0 w-0" v-bind="attributes" v-on="events" /> + </template> + <!-- END Hide search --> + + <template v-slot:option="option"> + <div class="flex gap-2 items-center"> + <div class="p-2 bg-gray-200 rounded-md"> + <img class="h-8" alt="Náhled možnosti" :src="option.src" /> + </div> + <div> + {{ option.name }} + </div> + </div> + </template> + + <template v-slot:selected-option="option"> + <div class="flex gap-2 items-center"> + <div class="p-2 bg-gray-200 rounded-md"> + <img + class="h-10" + alt="Náhled vybrané možnosti" + :src="option.src" + /> + </div> <div> - <small v-if="!disableImageInput">Nebo vyber ze senamu:</small> - <span v-else>Vyber ze seznamu:</span> + {{ option.name }} </div> - <VueSelect - ref="predefinedImageSelect" - :options="predefinedImages" - :clearable="!mustSelectPredefinedImage" - :searchable="false" - :modelValue="(selectedImage) ? selectedImage : null" - @update:modelValue="setSelectedImage" - label="name" - > - <!-- BEGIN Hide search (TODO) --> - <template v-slot:search="{ attributes, events }"> - <input - class="h-0 w-0" - v-bind="attributes" - v-on="events" - /> - </template> - <!-- END Hide search --> - - <template v-slot:option="option"> - <div class="flex gap-2 items-center"> - <div class="p-2 bg-gray-200 rounded-md"> - <img - class="h-8" - alt="Náhled možnosti" - :src="option.src" - /> - </div> - <div> - {{ option.name }} - </div> - </div> - </template> - - <template v-slot:selected-option="option"> - <div class="flex gap-2 items-center"> - <div class="p-2 bg-gray-200 rounded-md"> - <img - class="h-10" - alt="Náhled vybrané možnosti" - :src="option.src" - /> - </div> - <div> - {{ option.name }} - </div> - </div> - </template> - </VueSelect> - </div> - </section> + </div> + </template> + </VueSelect> + </div> + </section> </template> diff --git a/frontend/src/components/inputs/InputHeading.vue b/frontend/src/components/inputs/InputHeading.vue index d1a3af03eb1ff50b83df45857cbd03ea38bc31a5..7000610f59d6642044cf751bfd74c9f6cba9a34e 100644 --- a/frontend/src/components/inputs/InputHeading.vue +++ b/frontend/src/components/inputs/InputHeading.vue @@ -1,30 +1,20 @@ <script> export default { - props: ['name', 'important', 'zIndex', 'icon'], - methods: { - getClass () { - return { - 'font-bold text-xl': this.important, - 'text-lg': !this.important - } - } - } -} + props: ["name", "important", "zIndex", "icon"], + methods: { + getClass() { + return { + "font-bold text-xl": this.important, + "text-lg": !this.important, + }; + }, + }, +}; </script> <template> - <div - class="flex gap-2 items-center" - :style="{'z-index': zIndex}" - > - <i - :class="'ico--' + icon" - class="text-lg" - v-if="icon" - ></i> - <h2 - class="font-condensed" - :class="getClass()" - >{{ this.name }}</h2> - </div> + <div class="flex gap-2 items-center" :style="{ 'z-index': zIndex }"> + <i :class="'ico--' + icon" class="text-lg" v-if="icon"></i> + <h2 class="font-condensed" :class="getClass()">{{ this.name }}</h2> + </div> </template> diff --git a/frontend/src/components/inputs/InputSeparator.vue b/frontend/src/components/inputs/InputSeparator.vue index 0d4e8d6da56e6bba0dc81d8e9c527b46b214d3d2..d02a45ae250c70bac973218a663ba403af182094 100644 --- a/frontend/src/components/inputs/InputSeparator.vue +++ b/frontend/src/components/inputs/InputSeparator.vue @@ -1,3 +1,3 @@ <template> - <hr class="hr--unstyled my-5 border-t-gray-300"> + <hr class="hr--unstyled my-5 border-t-gray-300" /> </template> diff --git a/frontend/src/components/inputs/RangeInput.vue b/frontend/src/components/inputs/RangeInput.vue index 98c2cfc91612c167c118d4d47c8de0fbe7e66a4f..48906d0f7e81721a0420906e6b0c48b6b8f54db4 100644 --- a/frontend/src/components/inputs/RangeInput.vue +++ b/frontend/src/components/inputs/RangeInput.vue @@ -1,31 +1,31 @@ <script setup> -import InputHeading from './InputHeading.vue' +import InputHeading from "./InputHeading.vue"; </script> <script> export default { - components: { InputHeading }, - props: ['name', 'important', 'zIndex', 'min', 'max', 'modelValue'], - emits: ['update:modelValue'] -} + components: { InputHeading }, + props: ["name", "important", "zIndex", "min", "max", "modelValue"], + emits: ["update:modelValue"], +}; </script> <template> - <section - class="flex flex-col gap-2 bg-gray-100 p-4 drop-shadow-md" - :style="{'z-index': zIndex}" - > - <InputHeading - :name="name" - :important="important" - icon="equalizer" - ></InputHeading> - <input - type="range" - :min="min * 100" - :max="max * 100" - :value="modelValue * 100" - @input="$emit('update:modelValue', $event.target.value / 100)" - > - </section> + <section + class="flex flex-col gap-2 bg-gray-100 p-4 drop-shadow-md" + :style="{ 'z-index': zIndex }" + > + <InputHeading + :name="name" + :important="important" + icon="equalizer" + ></InputHeading> + <input + type="range" + :min="min * 100" + :max="max * 100" + :value="modelValue * 100" + @input="$emit('update:modelValue', $event.target.value / 100)" + /> + </section> </template> diff --git a/frontend/src/components/inputs/SelectInput.vue b/frontend/src/components/inputs/SelectInput.vue index d7b4352854185e794895986dcecc5687097c5cd5..3bb7bcb262024333d6d7b4938c12f7712147a33a 100644 --- a/frontend/src/components/inputs/SelectInput.vue +++ b/frontend/src/components/inputs/SelectInput.vue @@ -1,49 +1,49 @@ <script setup> -import InputHeading from './InputHeading.vue' +import InputHeading from "./InputHeading.vue"; -import VueSelect from 'vue-select' +import VueSelect from "vue-select"; </script> <script> export default { - components: { InputHeading, VueSelect }, - props: ['name', 'important', 'zIndex', 'options', 'modelValue'], - emits: ['update:modelValue'], - data () { - return { - currentOption: this.modelValue, - } + components: { InputHeading, VueSelect }, + props: ["name", "important", "zIndex", "options", "modelValue"], + emits: ["update:modelValue"], + data() { + return { + currentOption: this.modelValue, + }; + }, + methods: { + setPredefinedColors(value) { + this.currentOption = value; }, - methods: { - setPredefinedColors (value) { - this.currentOption = value - } + }, + watch: { + currentOption(value) { + this.$emit("update:modelValue", value); }, - watch: { - currentOption (value) { - this.$emit('update:modelValue', value); - } - }, -} + }, +}; </script> <template> - <section - class="flex flex-col gap-2 bg-gray-100 p-4 drop-shadow-md" - :style="{'z-index': zIndex}" - > - <InputHeading - :name="name" - :important="important" - icon="menu" - ></InputHeading> + <section + class="flex flex-col gap-2 bg-gray-100 p-4 drop-shadow-md" + :style="{ 'z-index': zIndex }" + > + <InputHeading + :name="name" + :important="important" + icon="menu" + ></InputHeading> - <VueSelect - :options="options" - :clearable="false" - :modelValue="modelValue" - @update:modelValue="setPredefinedColors" - label="name" - ></VueSelect> - </section> + <VueSelect + :options="options" + :clearable="false" + :modelValue="modelValue" + @update:modelValue="setPredefinedColors" + label="name" + ></VueSelect> + </section> </template> diff --git a/frontend/src/components/inputs/colors/ColorPicker.vue b/frontend/src/components/inputs/colors/ColorPicker.vue index 0bce0030d5b58508e466730d130b9d3a548a48b4..fc05888e0375ac5f0745b1c89f91cb3cfcf8232d 100644 --- a/frontend/src/components/inputs/colors/ColorPicker.vue +++ b/frontend/src/components/inputs/colors/ColorPicker.vue @@ -1,88 +1,85 @@ <script setup> -import COLORS from '../../../colors' +import COLORS from "../../../colors"; -import VueSelect from 'vue-select' +import VueSelect from "vue-select"; </script> <script> export default { - components: { VueSelect }, - props: ['modelValue', 'colorKey', 'label'], - emits: ['update:modelValue'], - data () { - let colorOptions = [] + components: { VueSelect }, + props: ["modelValue", "colorKey", "label"], + emits: ["update:modelValue"], + data() { + let colorOptions = []; - for (let [identifier, color] of Object.entries(COLORS)) { - colorOptions.push({ - name: color.name, - value: color.value - }) - } + for (let [identifier, color] of Object.entries(COLORS)) { + colorOptions.push({ + name: color.name, + value: color.value, + }); + } - return { - colorOptions: colorOptions - } - }, - computed: { - colorValue: { - get () { - return this.modelValue[this.colorKey] - }, - set (value) { - let copiedModelValue = this.modelValue - copiedModelValue[this.colorKey] = value + return { + colorOptions: colorOptions, + }; + }, + computed: { + colorValue: { + get() { + return this.modelValue[this.colorKey]; + }, + set(value) { + let copiedModelValue = this.modelValue; + copiedModelValue[this.colorKey] = value; - this.$emit( - 'update:modelValue', - copiedModelValue - ) - } - } - } -} + this.$emit("update:modelValue", copiedModelValue); + }, + }, + }, +}; </script> <template> - <!-- Temporarily hidden :( --> + <!-- Temporarily hidden :( --> - <li class="grid grid-cols-2 justify-between items-center gap-4 hidden"> - <span class="font-condensed">{{ label }}</span> + <li class="grid grid-cols-2 justify-between items-center gap-4 hidden"> + <span class="font-condensed">{{ label }}</span> - <div> - <VueSelect - :options="colorOptions" - :clearable="false" - label="name" - v-model="colorValue" - > - <template v-slot:option="option"> - <div class="flex gap-2 items-center"> - <div - class="w-8 h-8 rounded-md border border-gray-200" - aria-label="Náhled barvy" - title="Náhled barvy" - :style="{ background: option.value }" - ></div> - <div> - {{ option.name }} - </div> - </div> - </template> + <div> + <VueSelect + :options="colorOptions" + :clearable="false" + label="name" + v-model="colorValue" + > + <template v-slot:option="option"> + <div class="flex gap-2 items-center"> + <div + class="w-8 h-8 rounded-md border border-gray-200" + aria-label="Náhled barvy" + title="Náhled barvy" + :style="{ background: option.value }" + ></div> + <div> + {{ option.name }} + </div> + </div> + </template> - <template v-slot:selected-option="option"> - <div class="flex gap-2 items-center"> - <div - class="w-8 h-8 rounded-md border border-gray-200" - aria-label="Náhled vybrané barvy" - title="Náhled vybrané barvy" - :style="{ background: option.value }" - ></div> - <div> - {{ option.name }} - </div> - </div> - </template> - </VueSelect> - </div> - </li> + <template v-slot:selected-option="option"> + <div class="flex gap-2 items-center"> + <div + class="w-8 h-8 rounded-md border border-gray-200" + aria-label="Náhled vybrané barvy" + title="Náhled vybrané barvy" + :style="{ background: option.value }" + ></div> + <div> + {{ option.name }} + </div> + </div> + </template> + </VueSelect> + </div> + </li> </template> diff --git a/frontend/src/components/inputs/colors/MultipleColorPicker.vue b/frontend/src/components/inputs/colors/MultipleColorPicker.vue index e7e9a22507aa5eb31ad7f63f0d3fac31fcdbc894..ab634e4dec30dabfcf43535c0598859d03d01c88 100644 --- a/frontend/src/components/inputs/colors/MultipleColorPicker.vue +++ b/frontend/src/components/inputs/colors/MultipleColorPicker.vue @@ -1,71 +1,71 @@ <script setup> -import InputHeading from '../InputHeading.vue' -import ColorPicker from './ColorPicker.vue' +import InputHeading from "../InputHeading.vue"; +import ColorPicker from "./ColorPicker.vue"; -import VueSelect from 'vue-select' +import VueSelect from "vue-select"; </script> <script> export default { - components: { InputHeading, ColorPicker, VueSelect }, - props: [ - 'name', - 'important', - 'zIndex', - 'colorLabels', - 'predefinedColors', - 'defaultPredefinedColors', - 'modelValue' - ], - emits: ['update:modelValue'], - data () { - return { - currentColors: this.modelValue, - currentPredefinedColors: this.defaultPredefinedColors, - predefinedColorOptions: Object.values(this.predefinedColors) - } + components: { InputHeading, ColorPicker, VueSelect }, + props: [ + "name", + "important", + "zIndex", + "colorLabels", + "predefinedColors", + "defaultPredefinedColors", + "modelValue", + ], + emits: ["update:modelValue"], + data() { + return { + currentColors: this.modelValue, + currentPredefinedColors: this.defaultPredefinedColors, + predefinedColorOptions: Object.values(this.predefinedColors), + }; + }, + watch: { + currentColors(value) { + this.$emit("update:modelValue", value); }, - watch: { - currentColors (value) { - this.$emit('update:modelValue', value); - } + }, + methods: { + setPredefinedColors(value) { + this.currentPredefinedColors = value; + this.currentColors = value.colors; }, - methods: { - setPredefinedColors (value) { - this.currentPredefinedColors = value - this.currentColors = value.colors - } - } -} + }, +}; </script> <template> - <section - class="flex flex-col gap-2 bg-gray-100 p-4 drop-shadow-md" - :style="{'z-index': zIndex}" - > - <InputHeading - :name="name" - :important="important" - icon="equalizer" - ></InputHeading> + <section + class="flex flex-col gap-2 bg-gray-100 p-4 drop-shadow-md" + :style="{ 'z-index': zIndex }" + > + <InputHeading + :name="name" + :important="important" + icon="equalizer" + ></InputHeading> - <VueSelect - :options="predefinedColorOptions" - :clearable="false" - :modelValue="currentPredefinedColors" - @update:modelValue="setPredefinedColors" - label="name" - ></VueSelect> + <VueSelect + :options="predefinedColorOptions" + :clearable="false" + :modelValue="currentPredefinedColors" + @update:modelValue="setPredefinedColors" + label="name" + ></VueSelect> - <ul class="flex flex-col gap-2"> - <ColorPicker - v-for="(templateColorIdentifier, index) in Object.keys(currentColors)" - :key="index" - :label="colorLabels[templateColorIdentifier]" - :colorKey="templateColorIdentifier" - v-model="currentColors" - ></ColorPicker> - </ul> - </section> + <ul class="flex flex-col gap-2"> + <ColorPicker + v-for="(templateColorIdentifier, index) in Object.keys(currentColors)" + :key="index" + :label="colorLabels[templateColorIdentifier]" + :colorKey="templateColorIdentifier" + v-model="currentColors" + ></ColorPicker> + </ul> + </section> </template> diff --git a/frontend/src/components/inputs/text/LongTextInput.vue b/frontend/src/components/inputs/text/LongTextInput.vue index 0423cab57d9b2a57382f23c47306d0d589e50c89..338575042b4769ae54855345a267f87738298e57 100644 --- a/frontend/src/components/inputs/text/LongTextInput.vue +++ b/frontend/src/components/inputs/text/LongTextInput.vue @@ -1,34 +1,34 @@ <script setup> -import InputHeading from '../InputHeading.vue' -import { sanitizeValue } from './utils' +import InputHeading from "../InputHeading.vue"; +import { sanitizeValue } from "./utils"; </script> <script> export default { - components: { InputHeading }, - props: ['name', 'important', 'highlightable', 'zIndex', 'modelValue'], - emits: ['update:modelValue'] -} + components: { InputHeading }, + props: ["name", "important", "highlightable", "zIndex", "modelValue"], + emits: ["update:modelValue"], +}; </script> <template> - <section - class="flex flex-col gap-2 bg-gray-100 p-4 drop-shadow-md" - :style="{'z-index': zIndex}" - > - <InputHeading - :name="name" - :important="important" - icon="pencil" - ></InputHeading> - <textarea - class="p-2 font-condensed bg-gray-200 rounded-sm" - :value="modelValue" - @input="$emit('update:modelValue', sanitizeValue($event.target.value))" - ></textarea> - <small v-if="highlightable"> - Pro zvýraznění části textu ho <em>*obal do hvězdiček.*</em><br> - Nezapomeň na druhou hvězdičku. - </small> - </section> + <section + class="flex flex-col gap-2 bg-gray-100 p-4 drop-shadow-md" + :style="{ 'z-index': zIndex }" + > + <InputHeading + :name="name" + :important="important" + icon="pencil" + ></InputHeading> + <textarea + class="p-2 font-condensed bg-gray-200 rounded-sm" + :value="modelValue" + @input="$emit('update:modelValue', sanitizeValue($event.target.value))" + ></textarea> + <small v-if="highlightable"> + Pro zvýraznění části textu ho <em>*obal do hvězdiček.*</em><br /> + Nezapomeň na druhou hvězdičku. + </small> + </section> </template> diff --git a/frontend/src/components/inputs/text/ShortTextInput.vue b/frontend/src/components/inputs/text/ShortTextInput.vue index 94c051b414ee2e185d8d9b12ce1723d909cd37b8..3c967c84d6506d17a1f438f1abb4c367bfd5ddf9 100644 --- a/frontend/src/components/inputs/text/ShortTextInput.vue +++ b/frontend/src/components/inputs/text/ShortTextInput.vue @@ -1,64 +1,69 @@ <script setup> -import InputHeading from '../InputHeading.vue' -import { sanitizeValue } from './utils' +import InputHeading from "../InputHeading.vue"; +import { sanitizeValue } from "./utils"; </script> <script> export default { - components: { InputHeading }, - props: ['name', 'important', 'zIndex', 'relatedModel', 'predefinedValues', 'modelValue', 'defaultValue'], - emits: ['update:modelValue', 'update:relatedModel'], - methods: { - emitChanges (event) { - this.$emit('update:modelValue', sanitizeValue(event.currentTarget.value)) + components: { InputHeading }, + props: [ + "name", + "important", + "zIndex", + "relatedModel", + "predefinedValues", + "modelValue", + "defaultValue", + ], + emits: ["update:modelValue", "update:relatedModel"], + methods: { + emitChanges(event) { + this.$emit("update:modelValue", sanitizeValue(event.currentTarget.value)); - let predefinedValue = null; + let predefinedValue = null; - if (!this.$props.predefinedValues) { - return - } + if (!this.$props.predefinedValues) { + return; + } - for (let value of this.$props.predefinedValues) { - if (value.self === event.currentTarget.value) { - predefinedValue = value - } - } + for (let value of this.$props.predefinedValues) { + if (value.self === event.currentTarget.value) { + predefinedValue = value; + } + } - if (predefinedValue === null) { - return - } + if (predefinedValue === null) { + return; + } - this.$emit('update:relatedModel', predefinedValue.related) - } - } -} + this.$emit("update:relatedModel", predefinedValue.related); + }, + }, +}; </script> <template> - <section - class="flex flex-col gap-2 bg-gray-100 p-4 drop-shadow-md" - :style="{'z-index': zIndex}" - > - <InputHeading - :name="name" - :important="important" - icon="pencil" - ></InputHeading> - <input - class="p-2 font-condensed bg-gray-200 rounded-sm" - type="text" - :list="($props.predefinedValues) ? predefinedValues : ''" - :value="(modelValue !== null) ? modelValue : defaultValue" - @input="emitChanges($event)" - > - <datalist - id="predefinedValues" - v-if="$props.predefinedValues" - > - <option - v-for="predefinedValue in predefinedValues" - :value="predefinedValue.self" - /> - </datalist> - </section> + <section + class="flex flex-col gap-2 bg-gray-100 p-4 drop-shadow-md" + :style="{ 'z-index': zIndex }" + > + <InputHeading + :name="name" + :important="important" + icon="pencil" + ></InputHeading> + <input + class="p-2 font-condensed bg-gray-200 rounded-sm" + type="text" + :list="$props.predefinedValues ? predefinedValues : ''" + :value="modelValue !== null ? modelValue : defaultValue" + @input="emitChanges($event)" + /> + <datalist id="predefinedValues" v-if="$props.predefinedValues"> + <option + v-for="predefinedValue in predefinedValues" + :value="predefinedValue.self" + /> + </datalist> + </section> </template> diff --git a/frontend/src/components/inputs/text/utils.js b/frontend/src/components/inputs/text/utils.js index 2fc05e2544dd186e1d96061cdefc977cc1cc1886..da63c1191d1958f48dddb0a0eb265e5fac88f27e 100644 --- a/frontend/src/components/inputs/text/utils.js +++ b/frontend/src/components/inputs/text/utils.js @@ -1,9 +1,9 @@ const sanitizeValue = (value) => { - if (value == '') { - value = null - } + if (value == "") { + value = null; + } - return value -} + return value; +}; -export { sanitizeValue } +export { sanitizeValue }; diff --git a/frontend/src/components/reload/AutoReloadCheckbox.vue b/frontend/src/components/reload/AutoReloadCheckbox.vue index 464433eb09748bcc9ed9791a49c6d68ec0c8c890..6f117e487dd13f778b99bd9548f4086f578227a5 100644 --- a/frontend/src/components/reload/AutoReloadCheckbox.vue +++ b/frontend/src/components/reload/AutoReloadCheckbox.vue @@ -1,22 +1,22 @@ <script> - export default { - props: ['modelValue'], - emits: ['update:modelValue'] - } +export default { + props: ["modelValue"], + emits: ["update:modelValue"], +}; </script> <template> - <div class="flex gap-3 justify-end mb-3"> - <input - id="auto-reload-checkbox" - name="auto-reload-checkbox" - type="checkbox" - :checked="modelValue" - autocomplete="off" - @input="$emit('update:modelValue', $event.target.checked)" - > - <label - for="auto-reload-checkbox" - >Obnovovat automaticky (může zatížit prohlížeč)</label> - </div> + <div class="flex gap-3 justify-end mb-3"> + <input + id="auto-reload-checkbox" + name="auto-reload-checkbox" + type="checkbox" + :checked="modelValue" + autocomplete="off" + @input="$emit('update:modelValue', $event.target.checked)" + /> + <label for="auto-reload-checkbox" + >Obnovovat automaticky (může zatížit prohlížeč)</label + > + </div> </template> diff --git a/frontend/src/components/reload/ReloadButton.vue b/frontend/src/components/reload/ReloadButton.vue index 49b7d30d44c78996be998ae6b6b9757772d264fa..dbfba1bf23f3afb1728636537db208e196ea26c6 100644 --- a/frontend/src/components/reload/ReloadButton.vue +++ b/frontend/src/components/reload/ReloadButton.vue @@ -1,19 +1,21 @@ <script> - export default { - props: ['parentRefs'] - } +export default { + props: ["parentRefs"], +}; </script> <template> - <button - class="btn btn--icon max-w-[unset]" - :disabled="$props.parentRefs.canvas ? $props.parentRefs.canvas.redrawing : true" - > - <div class="btn__body-wrap"> - <div class="btn__body w-full">Obnovit šablonu</div> - <div class="btn__icon"> - <i class="ico--refresh"></i> - </div> - </div> - </button> + <button + class="btn btn--icon max-w-[unset]" + :disabled=" + $props.parentRefs.canvas ? $props.parentRefs.canvas.redrawing : true + " + > + <div class="btn__body-wrap"> + <div class="btn__body w-full">Obnovit šablonu</div> + <div class="btn__icon"> + <i class="ico--refresh"></i> + </div> + </div> + </button> </template> diff --git a/frontend/src/contractors.js b/frontend/src/contractors.js index 9945933a35d52917da95f43713ce0b4d3ea4d706..c546266aa5a009612e936744f835b3b3f25bf56d 100644 --- a/frontend/src/contractors.js +++ b/frontend/src/contractors.js @@ -1,3 +1,3 @@ -const DEFAULT_CONTRACTOR = "Zadavatel | zpracovatel: Česká pirátská strana" +const DEFAULT_CONTRACTOR = "Zadavatel | zpracovatel: Česká pirátská strana"; -export default DEFAULT_CONTRACTOR +export default DEFAULT_CONTRACTOR; diff --git a/frontend/src/logos.js b/frontend/src/logos.js index 6963cb4980ad2a473e918ef0656bc34c5102b1fa..f3fb2da5439c450064fa0d2a96cb98afbc164102 100644 --- a/frontend/src/logos.js +++ b/frontend/src/logos.js @@ -1,58 +1,58 @@ -import defaultLogoLight from './assets/logos/default-light.png' -import defaultLogoDark from './assets/logos/default-dark.png' +import defaultLogoLight from "./assets/logos/default-light.png"; +import defaultLogoDark from "./assets/logos/default-dark.png"; const LOGOS = { - defaultLight: { - name: 'Základní - světlé', - src: defaultLogoLight, - }, - defaultDark: { - name: 'Základní - tmavé', - src: defaultLogoDark, - } -} + defaultLight: { + name: "Základní - světlé", + src: defaultLogoLight, + }, + defaultDark: { + name: "Základní - tmavé", + src: defaultLogoDark, + }, +}; const LOGO_POSITIONS = { - top_left: { - "id": "top-left", - "name": "Vlevo nahoře" - }, - top_right: { - "id": "top-right", - "name": "Vpravo nahoře" - }, - bottom_left: { - "id": "bottom-left", - "name": "Vlevo dole" - }, - bottom_right: { - "id": "bottom-right", - "name": "Vpravo dole" - } -} + top_left: { + id: "top-left", + name: "Vlevo nahoře", + }, + top_right: { + id: "top-right", + name: "Vpravo nahoře", + }, + bottom_left: { + id: "bottom-left", + name: "Vlevo dole", + }, + bottom_right: { + id: "bottom-right", + name: "Vpravo dole", + }, +}; const generateLogoPositions = (identifiers) => { - let logoPositionsList = [] - - for (const [key, value] of Object.entries(LOGO_POSITIONS)) { - if (!identifiers.includes(key)) { - continue - } + let logoPositionsList = []; - logoPositionsList.push(value) + for (const [key, value] of Object.entries(LOGO_POSITIONS)) { + if (!identifiers.includes(key)) { + continue; } - return logoPositionsList -} + logoPositionsList.push(value); + } + + return logoPositionsList; +}; const generateDefaultLogos = (identifier) => { - let logosCopy = LOGOS + let logosCopy = LOGOS; - for (const [logoIdentifier, logo] of Object.entries(logosCopy)) { - logo.defaultSelected = (logoIdentifier === identifier) - } + for (const [logoIdentifier, logo] of Object.entries(logosCopy)) { + logo.defaultSelected = logoIdentifier === identifier; + } - return Object.values(logosCopy) -} + return Object.values(logosCopy); +}; -export { LOGOS, generateDefaultLogos, LOGO_POSITIONS, generateLogoPositions } +export { LOGOS, generateDefaultLogos, LOGO_POSITIONS, generateLogoPositions }; diff --git a/frontend/src/main.js b/frontend/src/main.js index e30ce3c75e4f40e2812fffed80718db0c5b1dffe..9c552f3a0623c50f87b6b63ca24b113c5d19aa5a 100644 --- a/frontend/src/main.js +++ b/frontend/src/main.js @@ -1,11 +1,11 @@ -import './index.css' +import "./index.css"; -import { createApp } from 'vue' -import router from './router' -import App from './App.vue' +import { createApp } from "vue"; +import router from "./router"; +import App from "./App.vue"; -const app = createApp(App) +const app = createApp(App); -app.use(router) +app.use(router); -app.mount('#app') +app.mount("#app"); diff --git a/frontend/src/people.js b/frontend/src/people.js index e7e08ce3e528262959829b61ab2696da6b3c6489..67ee96c193af13735affb43918f76803818da0a5 100644 --- a/frontend/src/people.js +++ b/frontend/src/people.js @@ -1,28 +1,28 @@ const PEOPLE = [ - { - "self": "Ivan Bartoš", - "related": "ministr pro místní rozvoj" - }, - { - "self": "Olga Richterová", - "related": "místopředsedkyně Poslanecké sněmovny" - }, - { - "self": "Jan Lipavský", - "related": "ministr zahraničních věcí" - }, - { - "self": "Klára Kocmanová", - "related": "pirátská poslankyně" - }, - { - "self": "Jakub Michálek", - "related": "pirátský poslanec" - }, - { - "self": "Michal Šalamoun", - "related": "ministr pro legislativu" - } -] + { + self: "Ivan Bartoš", + related: "ministr pro místní rozvoj", + }, + { + self: "Olga Richterová", + related: "místopředsedkyně Poslanecké sněmovny", + }, + { + self: "Jan Lipavský", + related: "ministr zahraničních věcí", + }, + { + self: "Klára Kocmanová", + related: "pirátská poslankyně", + }, + { + self: "Jakub Michálek", + related: "pirátský poslanec", + }, + { + self: "Michal Šalamoun", + related: "ministr pro legislativu", + }, +]; -export default PEOPLE +export default PEOPLE; diff --git a/frontend/src/router/index.js b/frontend/src/router/index.js index 32a319509b12e65d617a5c174109987b77073a96..5def58f7428d5cb18bfa0b66ccc6e55d819e987e 100644 --- a/frontend/src/router/index.js +++ b/frontend/src/router/index.js @@ -1,57 +1,55 @@ -import { createRouter, createWebHistory } from 'vue-router' -import TEMPLATES from '../templates' +import { createRouter, createWebHistory } from "vue-router"; +import TEMPLATES from "../templates"; -import defaultFavicon from '../assets/favicon.png' +import defaultFavicon from "../assets/favicon.png"; let routes = [ - { - path: '/avatar', - name: 'avatar', - component: () => import('../views/avatar/Avatar.vue'), - meta: { - title: 'Profilové obrázky' - } - } + { + path: "/avatar", + name: "avatar", + component: () => import("../views/avatar/Avatar.vue"), + meta: { + title: "Profilové obrázky", + }, + }, ]; for (let [identifier, templateData] of Object.entries(TEMPLATES)) { - routes.push({ - path: templateData.path, - name: identifier, - component: templateData.component, - meta: templateData.meta - }) + routes.push({ + path: templateData.path, + name: identifier, + component: templateData.component, + meta: templateData.meta, + }); } const router = createRouter({ - history: createWebHistory(import.meta.env.BASE_URL), - routes: routes -}) + history: createWebHistory(import.meta.env.BASE_URL), + routes: routes, +}); -router.beforeEach( - (to, from, next) => { - document.title = 'Generátor grafiky' +router.beforeEach((to, from, next) => { + document.title = "Generátor grafiky"; - if (to.meta.title) { - document.title = `${to.meta.title} | ${document.title}` - } - // END Title + if (to.meta.title) { + document.title = `${to.meta.title} | ${document.title}`; + } + // END Title - // BEGIN Favicon - const link = document.createElement('link') - link.rel = 'icon' + // BEGIN Favicon + const link = document.createElement("link"); + link.rel = "icon"; - if (to.meta.favicon !== undefined) { - link.href = to.meta.favicon - } else { - link.href = defaultFavicon - } + if (to.meta.favicon !== undefined) { + link.href = to.meta.favicon; + } else { + link.href = defaultFavicon; + } - document.head.appendChild(link) - // END Favicon + document.head.appendChild(link); + // END Favicon - next() - } -) + next(); +}); -export default router +export default router; diff --git a/frontend/src/templates.js b/frontend/src/templates.js index 66f1f0610fe005e0e75770e3c594ec4a92f12725..78c2d7ac0608f9a2af8dad8c21ce656ccfa463e2 100644 --- a/frontend/src/templates.js +++ b/frontend/src/templates.js @@ -1,106 +1,118 @@ -import basicPhotoBannerImage from './assets/previews/basic_photo_banner.png' -import urgentBasicPhotoBannerImage from './assets/previews/urgent_basic_photo_banner.png' -import urgentTextBannerImage from './assets/previews/urgent_text_banner.png' -import textBannerImage from './assets/previews/text_banner.png' -import newspaperQuoteBottomImage from './assets/previews/newspaper_quote_bottom.png' -import newspaperQuoteMiddleImage from './assets/previews/newspaper_quote_middle.png' -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' - +import basicPhotoBannerImage from "./assets/previews/basic_photo_banner.png"; +import urgentBasicPhotoBannerImage from "./assets/previews/urgent_basic_photo_banner.png"; +import urgentTextBannerImage from "./assets/previews/urgent_text_banner.png"; +import textBannerImage from "./assets/previews/text_banner.png"; +import newspaperQuoteBottomImage from "./assets/previews/newspaper_quote_bottom.png"; +import newspaperQuoteMiddleImage from "./assets/previews/newspaper_quote_middle.png"; +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"; +import baseEventImage from "./assets/previews/base_event.png"; const TEMPLATES = { - basic_photo_banner: { - name: 'Základní banner s fotkou', - image: basicPhotoBannerImage, - path: '/', - component: () => import('./views/basic_photo_banner/BasicPhotoBanner.vue'), - meta: { - title: 'Základní banner s fotkou', - } + basic_photo_banner: { + name: "Základní banner s fotkou", + image: basicPhotoBannerImage, + path: "/", + component: () => import("./views/basic_photo_banner/BasicPhotoBanner.vue"), + meta: { + title: "Základní banner s fotkou", + }, + }, + urgent_basic_photo_banner: { + name: "Urgentní banner s fotkou", + image: urgentBasicPhotoBannerImage, + path: "/urgent-basic-photo-banner", + component: () => + import("./views/urgent_basic_photo_banner/UrgentBasicPhotoBanner.vue"), + meta: { + title: "Urgentní banner s fotkou", + }, + }, + urgent_text_banner: { + name: "Urgentní banner pouze s textem", + image: urgentTextBannerImage, + path: "/urgent-text-banner", + component: () => import("./views/urgent_text_banner/UrgentTextBanner.vue"), + meta: { + title: "Urgentní banner pouze s textem", }, - urgent_basic_photo_banner: { - name: 'Urgentní banner s fotkou', - image: urgentBasicPhotoBannerImage, - path: '/urgent-basic-photo-banner', - component: () => import('./views/urgent_basic_photo_banner/UrgentBasicPhotoBanner.vue'), - meta: { - title: 'Urgentní banner s fotkou', - } + }, + text_banner: { + name: "Banner pouze s textem", + image: textBannerImage, + path: "/text-banner", + component: () => import("./views/text_banner/TextBanner.vue"), + meta: { + title: "Banner pouze s textem", }, - urgent_text_banner: { - name: 'Urgentní banner pouze s textem', - image: urgentTextBannerImage, - path: '/urgent-text-banner', - component: () => import('./views/urgent_text_banner/UrgentTextBanner.vue'), - meta: { - title: 'Urgentní banner pouze s textem', - } + }, + newspaper_quote_bottom: { + name: "Novinová citace s obrázkem", + image: newspaperQuoteBottomImage, + path: "/newspaper-quote-bottom", + component: () => + import("./views/newspaper_quote_bottom/NewspaperQuoteBottom.vue"), + meta: { + title: "Novinová citace s obrázkem", }, - text_banner: { - name: 'Banner pouze s textem', - image: textBannerImage, - path: '/text-banner', - component: () => import('./views/text_banner/TextBanner.vue'), - meta: { - title: 'Banner pouze s textem', - } + }, + newspaper_quote_middle: { + name: "Novinová citace - pouze text", + image: newspaperQuoteMiddleImage, + path: "/newspaper-quote-middle", + component: () => + import("./views/newspaper_quote_middle/NewspaperQuoteMiddle.vue"), + meta: { + title: "Novinová citace - pouze text", }, - newspaper_quote_bottom: { - name: 'Novinová citace s obrázkem', - image: newspaperQuoteBottomImage, - path: '/newspaper-quote-bottom', - component: () => import('./views/newspaper_quote_bottom/NewspaperQuoteBottom.vue'), - meta: { - title: 'Novinová citace s obrázkem', - } + }, + facebook_survey: { + name: "Facebook anketa", + image: facebookSurveyImage, + path: "/facebook-survey", + component: () => import("./views/facebook_survey/FacebookSurvey.vue"), + meta: { + title: "Facebook anketa", }, - newspaper_quote_middle: { - name: 'Novinová citace - pouze text', - image: newspaperQuoteMiddleImage, - path: '/newspaper-quote-middle', - component: () => import('./views/newspaper_quote_middle/NewspaperQuoteMiddle.vue'), - meta: { - title: 'Novinová citace - pouze text', - } + }, + poster: { + name: "Plakát", + image: posterImage, + path: "/poster", + component: () => import("./views/poster/Poster.vue"), + meta: { + title: "Plakát", }, - facebook_survey: { - name: 'Facebook anketa', - image: facebookSurveyImage, - path: '/facebook-survey', - component: () => import('./views/facebook_survey/FacebookSurvey.vue'), - meta: { - title: 'Facebook anketa', - } + }, + twitter_banner: { + name: "Twitter banner", + image: twitterBannerImage, + path: "/twitter-banner", + component: () => import("./views/twitter_banner/TwitterBanner.vue"), + meta: { + title: "Twitter banner", }, - poster: { - name: 'Plakát', - image: posterImage, - path: '/poster', - component: () => import('./views/poster/Poster.vue'), - meta: { - title: 'Plakát' - } + }, + 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", }, - twitter_banner: { - name: 'Twitter banner', - image: twitterBannerImage, - path: '/twitter-banner', - component: () => import('./views/twitter_banner/TwitterBanner.vue'), - meta: { - title: 'Twitter banner' - } + }, + base_event: { + name: "Událost - pouze text", + image: baseEventImage, + path: "/base-event", + component: () => import("./views/base_event/BaseEvent.vue"), + meta: { + title: "Událost - pouze text", }, - 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' - } - } -} + }, +}; -export default TEMPLATES +export default TEMPLATES; diff --git a/frontend/src/utils.js b/frontend/src/utils.js index fe59f7fbb2e03de52e469fb83c86cf7c77ebd05e..7d701c7bfce5acdc1e8bc05ed61a40db865e4c54 100644 --- a/frontend/src/utils.js +++ b/frontend/src/utils.js @@ -1,88 +1,90 @@ const loadFonts = async (fonts) => { - for (const font of fonts) { - await document.fonts.load( - `${font}`, - 'Příliš žluťoučký kůň úpěl ďábelské ó.' - ) - } -} - + for (const font of fonts) { + await document.fonts.load( + `${font}`, + "Příliš žluťoučký kůň úpěl ďábelské ó.", + ); + } +}; const clearNullsFromArray = (originalArray) => { - for (const originalArrayItem of originalArray) { - if (originalArrayItem !== null) { - continue - } - - originalArray = originalArray.filter( - nestedArrayItem => { - nestedArrayItem !== originalArrayItem - } - ) + for (const originalArrayItem of originalArray) { + if (originalArrayItem !== null) { + continue; } - return originalArray -} + originalArray = originalArray.filter((nestedArrayItem) => { + nestedArrayItem !== originalArrayItem; + }); + } + return originalArray; +}; const updateAutoRedrawStorage = (autoRedraw) => { - window.sessionStorage.setItem("auto_redraw", JSON.stringify(autoRedraw)) -} - + window.sessionStorage.setItem("auto_redraw", JSON.stringify(autoRedraw)); +}; const loadCanvasStorage = async (data) => { - const canvasProperties = JSON.parse(window.sessionStorage.getItem("canvas_properties")) - const autoRedraw = JSON.parse(window.sessionStorage.getItem("auto_redraw")) - - if (canvasProperties) { - for (const [key, value] of Object.entries(canvasProperties)) { - if (!(key in data)) { - continue - } + const canvasProperties = JSON.parse( + window.sessionStorage.getItem("canvas_properties"), + ); + const autoRedraw = JSON.parse(window.sessionStorage.getItem("auto_redraw")); - if (key.toLowerCase().endsWith("image") && value) { - const image = new Image() + if (canvasProperties) { + for (const [key, value] of Object.entries(canvasProperties)) { + if (!(key in data)) { + continue; + } - await new Promise( - resolve => { - image.onload = () => { - resolve() - } + if (key.toLowerCase().endsWith("image") && value) { + const image = new Image(); - image.src = value - } - ) + await new Promise((resolve) => { + image.onload = () => { + resolve(); + }; - data[key] = image + image.src = value; + }); - continue - } + data[key] = image; - data[key] = value - } - } + continue; + } - if (autoRedraw) { - data.autoRedraw = autoRedraw + data[key] = value; } -} + } + if (autoRedraw) { + data.autoRedraw = autoRedraw; + } +}; const setCanvasStorage = (data) => { - let processedData = {} - - for (const [key, value] of Object.entries(data)) { - if (key.toLowerCase().endsWith("image") && value) { - processedData[key] = value.src + let processedData = {}; - continue - } + for (const [key, value] of Object.entries(data)) { + if (key.toLowerCase().endsWith("image") && value) { + processedData[key] = value.src; - processedData[key] = value + continue; } - window.sessionStorage.setItem("canvas_properties", JSON.stringify(processedData)) -} - - -export { loadFonts, clearNullsFromArray, loadCanvasStorage, setCanvasStorage, updateAutoRedrawStorage } + processedData[key] = value; + } + + window.sessionStorage.setItem( + "canvas_properties", + JSON.stringify(processedData), + ); +}; + +export { + loadFonts, + clearNullsFromArray, + loadCanvasStorage, + setCanvasStorage, + updateAutoRedrawStorage, +}; diff --git a/frontend/src/views/avatar/Avatar.vue b/frontend/src/views/avatar/Avatar.vue index 32fa8df81bc97d772fd16a4384011ff2544e1e4a..e516552f984981e8c5c4b8472c44c41279e61040 100644 --- a/frontend/src/views/avatar/Avatar.vue +++ b/frontend/src/views/avatar/Avatar.vue @@ -1,98 +1,97 @@ <script setup> -import { watch, ref } from 'vue' +import { watch, ref } from "vue"; -import Canvas from '../../components/canvas/Canvas.vue' -import redraw from './canvas' +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 Navbar from "../../components/Navbar.vue"; +import MainContainer from "../../components/MainContainer.vue"; +import ImageInput from "../../components/inputs/ImageInput.vue"; </script> <script> export default { - components: { - Canvas, - Navbar, - MainContainer, - ImageInput, - }, - data () { - return { - mainImage: null, - autoRedraw: false - } - }, - methods: { - async reloadCanvasProperties () { - const canvasProperties = { - mainImage: this.mainImage, - } + components: { + Canvas, + Navbar, + MainContainer, + ImageInput, + }, + data() { + return { + mainImage: null, + autoRedraw: false, + }; + }, + methods: { + async reloadCanvasProperties() { + const canvasProperties = { + mainImage: this.mainImage, + }; - await this.$refs.canvas.redraw(canvasProperties) - } - }, - mounted () { - this.$watch( - vm => [ - vm.mainImage, - ], - async (value) => { - if (this.autoRedraw) { - await this.reloadCanvasProperties() - } - }, - { - immediate: true, - deep: true - } - ) + await this.$refs.canvas.redraw(canvasProperties); }, -} + }, + mounted() { + this.$watch( + (vm) => [vm.mainImage], + async (value) => { + if (this.autoRedraw) { + await this.reloadCanvasProperties(); + } + }, + { + immediate: true, + deep: true, + }, + ); + }, +}; </script> <template> - <header> - <Navbar></Navbar> - </header> - <main> - <MainContainer> - <template v-slot:left> - <Canvas - ref="canvas" - :redrawFunction="redraw" - width="2000" - height="2000" - /> - </template> + <header> + <Navbar></Navbar> + </header> + <main> + <MainContainer> + <template v-slot:left> + <Canvas + ref="canvas" + :redrawFunction="redraw" + width="2000" + height="2000" + /> + </template> - <template v-slot:right> - <div class="prose max-w-none"> - <p> - Amet illo et atque et voluptates esse ad magni. Dolor omnis non ad. Qui est aut aperiam rerum atque mollitia ut. Ea aliquam nihil earum vel. Dicta odit quod aliquam molestiae porro ex error. Aut soluta et voluptatibus vel. - </p> + <template v-slot:right> + <div class="prose max-w-none"> + <p> + Amet illo et atque et voluptates esse ad magni. Dolor omnis non ad. + Qui est aut aperiam rerum atque mollitia ut. Ea aliquam nihil earum + vel. Dicta odit quod aliquam molestiae porro ex error. Aut soluta et + voluptatibus vel. + </p> - <p> - Quo quos est eaque ipsa esse. Laboriosam laborum est facilis. Corporis excepturi consequuntur quo dolorem omnis accusantium expedita voluptates. Ut omnis et asperiores consequatur quia voluptatum repudiandae. - </p> - </div> + <p> + Quo quos est eaque ipsa esse. Laboriosam laborum est facilis. + Corporis excepturi consequuntur quo dolorem omnis accusantium + expedita voluptates. Ut omnis et asperiores consequatur quia + voluptatum repudiandae. + </p> + </div> - <ReloadButton - :parentRefs="$refs" - @click="reloadCanvasProperties" - /> - <AutoReloadCheckbox - v-model="autoRedraw" - /> - <ImageInput - name="Obrázek" - v-model="mainImage" - :important="true" - zIndex="10" - /> - </template> - </MainContainer> - </main> + <ReloadButton :parentRefs="$refs" @click="reloadCanvasProperties" /> + <AutoReloadCheckbox v-model="autoRedraw" /> + <ImageInput + name="Obrázek" + v-model="mainImage" + :important="true" + zIndex="10" + /> + </template> + </MainContainer> + </main> </template> <style> diff --git a/frontend/src/views/avatar/canvas.js b/frontend/src/views/avatar/canvas.js index d4b80a9a01f94204995b1de648d26e9a92b164c6..cc505bd8eb060a8526572d52cbabbee31788ed8d 100644 --- a/frontend/src/views/avatar/canvas.js +++ b/frontend/src/views/avatar/canvas.js @@ -1,3 +1,3 @@ -const redraw = async (canvas, options) => { } +const redraw = async (canvas, options) => {}; -export default redraw +export default redraw; diff --git a/frontend/src/views/base_event/BaseEvent.vue b/frontend/src/views/base_event/BaseEvent.vue new file mode 100644 index 0000000000000000000000000000000000000000..2e08d2890301fdff3f99f2960dd86c0f8f347da5 --- /dev/null +++ b/frontend/src/views/base_event/BaseEvent.vue @@ -0,0 +1,171 @@ +<script setup> +import { watch, ref } from "vue"; + +import COLORS from "../../colors"; +import TEMPLATES from "../../templates"; +import DEFAULT_CONTRACTOR from "../../contractors"; +import { + loadFonts, + loadCanvasStorage, + setCanvasStorage, + updateAutoRedrawStorage, +} from "../../utils"; + +import Canvas from "../../components/canvas/Canvas.vue"; +import redraw from "./canvas"; + +import Navbar from "../../components/Navbar.vue"; +import MainContainer from "../../components/MainContainer.vue"; +import LongTextInput from "../../components/inputs/text/LongTextInput.vue"; +import ShortTextInput from "../../components/inputs/text/ShortTextInput.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"]); + +export default { + components: { + Canvas, + Navbar, + MainContainer, + LongTextInput, + ShortTextInput, + MultipleColorPicker, + AutoReloadCheckbox, + ReloadButton, + }, + data() { + const predefinedColors = { + blackBackground: { + name: "Černé pozadí, bílý text", + colors: { + background: COLORS.black, + mainText: COLORS.white, + highlightedText: COLORS.black, + highlight: COLORS.yellow1, + contractedByText: COLORS.gray1, + }, + }, + whiteBackground: { + name: "Bílé pozadí, černý text", + colors: { + background: COLORS.white, + mainText: COLORS.black, + highlightedText: COLORS.black, + highlight: COLORS.yellow1, + contractedByText: COLORS.gray1, + }, + }, + }; + + return { + mainText: null, + contractedBy: DEFAULT_CONTRACTOR, + gradientHeightMultiplier: 1, + colorLabels: { + background: "Pozadí", + }, + predefinedColors: predefinedColors, + colors: predefinedColors.blackBackground.colors, + autoRedraw: false, + }; + }, + async created() { + await loadCanvasStorage(this); + }, + methods: { + async reloadCanvasProperties() { + const canvasProperties = { + mainText: this.mainText, + contractedBy: this.contractedBy, + colors: this.colors, + }; + + await this.$refs.canvas.redraw(canvasProperties); + + delete canvasProperties.colors; + setCanvasStorage(canvasProperties); + }, + }, + mounted() { + this.$watch( + (vm) => [vm.mainText, vm.contractedBy, 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.base_event"></Navbar> + </header> + <main> + <MainContainer> + <template v-slot:left> + <Canvas + ref="canvas" + :redrawFunction="redraw" + width="1702" + height="630" + /> + </template> + + <template v-slot:right> + <ReloadButton :parentRefs="$refs" @click="reloadCanvasProperties" /> + <AutoReloadCheckbox v-model="autoRedraw" /> + <LongTextInput + name="Text" + v-model="mainText" + :important="true" + :highlightable="true" + zIndex="9" + /> + + <MultipleColorPicker + name="Barvy" + v-model="colors" + :important="false" + :colorLabels="colorLabels" + :predefinedColors="predefinedColors" + :defaultPredefinedColors="predefinedColors.base" + zIndex="5" + ></MultipleColorPicker> + + <ShortTextInput + name="Zadavatel a zpracovatel" + v-model="contractedBy" + :defaultValue="DEFAULT_CONTRACTOR" + :important="false" + zIndex="4" + /> + </template> + </MainContainer> + </main> +</template> + +<style> +@import "vue-select/dist/vue-select.css"; +</style> diff --git a/frontend/src/views/base_event/canvas.js b/frontend/src/views/base_event/canvas.js new file mode 100644 index 0000000000000000000000000000000000000000..cf40ff72d14e63a935824483ff28b63276f165b2 --- /dev/null +++ b/frontend/src/views/base_event/canvas.js @@ -0,0 +1,149 @@ +import { fabric } from "fabric"; +import { + clearObjects, + sortObjects, + transformHighlightedText, + checkTextBoxHeight, +} from "../../components/canvas/utils"; +import COLORS from "../../colors"; +import { PaddedHighlightingTextbox } from "../../components/canvas/textbox"; + +import bgImageSourceBlack from "../../assets/template/base_event/bg_black.png"; +import bgImageSourceWhite from "../../assets/template/base_event/bg_white.png"; + +let mainImage = null; +let contractedByTextbox = null; +let previousColor = null; +let mainTextBox = null; + +const redraw = async (canvas, options) => { + clearObjects([contractedByTextbox, mainTextBox], canvas); + + const contractedByTextSize = Math.ceil(canvas.height * 0.035); + const contractedByTextMaxWidth = Math.ceil(canvas.width * 0.9); + const contractedByTextBottomMargin = Math.ceil(canvas.width * 0.02); + const contractedByTextSideMargin = Math.ceil(canvas.width * 0.03); + + const textMarginLeft = Math.ceil(canvas.width * 0.4); + const textMarginRight = Math.ceil(canvas.width * 0.23); + + let mainTextMarginBottom = Math.ceil(canvas.height * 0.36); + const mainTextBackgroundMarginTop = Math.ceil(canvas.height * 0.14); + const mainTextSize = Math.ceil(canvas.height * 0.13); + const mainTextHeightLimit = Math.ceil(mainTextSize * 2.2); + const mainTextLineHeight = 0.85; + + canvas.preserveObjectStacking = true; + + /* BEGIN Main image render */ + + if (previousColor !== options.colors.background) { + if (mainImage !== null) { + canvas.remove(mainImage); + } + + const image = new Image(); + + const imageLoadPromise = new Promise((resolve) => { + image.onload = () => { + resolve(); + }; + + if (options.colors.background.value === COLORS.black.value) { + image.src = bgImageSourceBlack; + } else { + image.src = bgImageSourceWhite; + } + }); + await imageLoadPromise; + + mainImage = new fabric.Image(image, { + left: 0, + top: 0, + zIndex: 0, + selectable: false, + }); + + mainImage.scaleToWidth(canvas.width); + + canvas.add(mainImage); + + previousColor = options.colors.background; + } + + /* END Main image render */ + + if (options.mainText !== null) { + /* BEGIN Name text render */ + + const mainTextWidth = canvas.width - textMarginLeft - textMarginRight; + + const highlightedData = transformHighlightedText( + options.mainText, + mainTextSize, + mainTextWidth, + "Bebas Neue", + options.colors.highlight.value, + options.colors.highlightedText.value, + { padWhenDiacritics: true }, + ); + + mainTextBox = new PaddedHighlightingTextbox(highlightedData.text, { + width: canvas.width, + left: textMarginLeft, + textAlign: "left", + fontFamily: "Bebas Neue", + fontSize: mainTextSize, + lineHeight: mainTextLineHeight, + fill: options.colors.mainText.value, + styles: highlightedData.styles, + selectable: false, + highlightPadding: canvas.height * 0.003, + zIndex: 10, + }); + + checkTextBoxHeight(mainTextBox, 2); + + canvas.add(mainTextBox); + + let mainTextBoxTop = + canvas.height - mainTextBox.height - mainTextMarginBottom; + + if (mainTextBox.textLines.length === 1) { + mainTextBoxTop -= mainTextSize / 2; + } + + mainTextBox.top = mainTextBoxTop - highlightedData.paddingBottom; + + canvas.renderAll(); + + /* END Main text render */ + } + + /* BEGIN Contracted by render */ + + if (options.contractedBy !== null) { + contractedByTextbox = new fabric.Textbox(options.contractedBy, { + left: + canvas.width - contractedByTextMaxWidth - contractedByTextSideMargin, + top: canvas.height - contractedByTextBottomMargin - contractedByTextSize, + width: contractedByTextMaxWidth, + fontFamily: "Roboto Condensed", + fontSize: contractedByTextSize, + textAlign: "right", + fill: options.colors.contractedByText.value, + selectable: false, + zIndex: 10, + }); + + checkTextBoxHeight(contractedByTextbox, 1); + + canvas.add(contractedByTextbox); + } + + /* END Contracted by render */ + + sortObjects(canvas); +}; + +export default redraw; diff --git a/frontend/src/views/basic_photo_banner/BasicPhotoBanner.vue b/frontend/src/views/basic_photo_banner/BasicPhotoBanner.vue index c3ae42f5f292703f273fbfcb3bd6b281f37a9c0c..ec3a3f89520aecf14e5000d64a86f323be2c5d67 100644 --- a/frontend/src/views/basic_photo_banner/BasicPhotoBanner.vue +++ b/frontend/src/views/basic_photo_banner/BasicPhotoBanner.vue @@ -1,253 +1,250 @@ <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 { generateDefaultLogos, LOGO_POSITIONS, generateLogoPositions } from '../../logos' -import { loadFonts, loadCanvasStorage, setCanvasStorage, updateAutoRedrawStorage } from '../../utils' - -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 RangeInput from '../../components/inputs/RangeInput.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' +import { watch, ref } from "vue"; + +import COLORS from "../../colors"; +import PEOPLE from "../../people"; +import TEMPLATES from "../../templates"; +import DEFAULT_CONTRACTOR from "../../contractors"; +import { + generateDefaultLogos, + LOGO_POSITIONS, + generateLogoPositions, +} from "../../logos"; +import { + loadFonts, + loadCanvasStorage, + setCanvasStorage, + updateAutoRedrawStorage, +} from "../../utils"; + +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 RangeInput from "../../components/inputs/RangeInput.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' -]) + "12px Bebas Neue", + "12px Roboto Condensed", + "bold 12px Roboto Condensed", +]); export default { - components: { - Canvas, - Navbar, - MainContainer, - ImageInput, - LongTextInput, - ShortTextInput, - RangeInput, - SelectInput, - InputSeparator, - MultipleColorPicker + components: { + Canvas, + Navbar, + MainContainer, + ImageInput, + LongTextInput, + ShortTextInput, + RangeInput, + SelectInput, + InputSeparator, + MultipleColorPicker, + }, + data() { + const predefinedColors = { + base: { + name: "Základní barvy", + colors: { + background: COLORS.black, + highlight: COLORS.yellow1, + arrow: COLORS.yellow1, + baseText: COLORS.white, + highlightedText: COLORS.black, + contractedByText: COLORS.gray1, + }, + }, + }; + + return { + mainImage: null, + mainText: null, + personName: null, + personPosition: null, + contractedBy: DEFAULT_CONTRACTOR, + logoImage: null, + logoPosition: LOGO_POSITIONS.top_right, + logoOptions: generateLogoPositions(["top_right", "top_left"]), + gradientHeightMultiplier: 1, + colorLabels: { + background: "Pozadí", + highlight: "Zvýraznění", + arrow: "Šipka", + baseText: "Text", + highlightedText: "Zvýrazněný text", + }, + predefinedColors: predefinedColors, + colors: predefinedColors.base.colors, + predefinedLogoImages: generateDefaultLogos("defaultLight"), + autoRedraw: false, + }; + }, + async created() { + await loadCanvasStorage(this); + }, + methods: { + async reloadCanvasProperties() { + const canvasProperties = { + mainImage: this.mainImage, + mainText: this.mainText, + logoPosition: this.logoPosition, + personName: this.personName, + personPosition: this.personPosition, + contractedBy: this.contractedBy, + logoImage: this.logoImage, + gradientHeightMultiplier: this.gradientHeightMultiplier, + colors: this.colors, + }; + + await this.$refs.canvas.redraw(canvasProperties); + + delete canvasProperties.colors; + setCanvasStorage(canvasProperties); }, - data () { - const predefinedColors = { - base: { - name: 'Základní barvy', - colors: { - background: COLORS.black, - highlight: COLORS.yellow1, - arrow: COLORS.yellow1, - baseText: COLORS.white, - highlightedText: COLORS.black, - contractedByText: COLORS.gray1 - } - } + }, + mounted() { + this.$watch( + (vm) => [ + vm.mainImage, + vm.mainText, + vm.logoPosition, + vm.personName, + vm.personPosition, + vm.contractedBy, + vm.logoImage, + vm.gradientHeightMultiplier, + vm.colors, + ], + async (value) => { + if (this.autoRedraw) { + await this.reloadCanvasProperties(); } - - return { - mainImage: null, - mainText: null, - personName: null, - personPosition: null, - contractedBy: DEFAULT_CONTRACTOR, - logoImage: null, - logoPosition: LOGO_POSITIONS.top_right, - logoOptions: generateLogoPositions( - [ - "top_right", - "top_left", - ] - ), - gradientHeightMultiplier: 1, - colorLabels: { - background: 'Pozadí', - highlight: 'Zvýraznění', - arrow: 'Šipka', - baseText: 'Text', - highlightedText: 'Zvýrazněný text' - }, - predefinedColors: predefinedColors, - colors: predefinedColors.base.colors, - predefinedLogoImages: generateDefaultLogos('defaultLight'), - autoRedraw: false - } - }, - async created () { - await loadCanvasStorage(this) - }, - methods: { - async reloadCanvasProperties () { - const canvasProperties = { - mainImage: this.mainImage, - mainText: this.mainText, - logoPosition: this.logoPosition, - personName: this.personName, - personPosition: this.personPosition, - contractedBy: this.contractedBy, - logoImage: this.logoImage, - gradientHeightMultiplier: this.gradientHeightMultiplier, - colors: this.colors - } - - await this.$refs.canvas.redraw(canvasProperties) - - delete canvasProperties.colors - setCanvasStorage(canvasProperties) + }, + { + immediate: true, + deep: true, + }, + ); + + this.$watch( + (vm) => [vm.autoRedraw], + async (value) => { + updateAutoRedrawStorage(this.autoRedraw); + + if (this.autoRedraw) { + await this.reloadCanvasProperties(); } - }, - mounted () { - this.$watch( - vm => [ - vm.mainImage, - vm.mainText, - vm.logoPosition, - vm.personName, - vm.personPosition, - vm.contractedBy, - vm.logoImage, - vm.gradientHeightMultiplier, - 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.basic_photo_banner" - ></Navbar> - </header> - <main> - <MainContainer> - <template v-slot:left> - <Canvas - ref="canvas" - :redrawFunction="redraw" - width="2000" - height="2000" - /> - </template> - - <template v-slot:right> - <ReloadButton - :parentRefs="$refs" - @click="reloadCanvasProperties" - /> - <AutoReloadCheckbox - v-model="autoRedraw" - /> - <ImageInput - name="Obrázek" - v-model="mainImage" - :important="true" - zIndex="10" - /> - <LongTextInput - name="Hlavní text" - v-model="mainText" - :important="true" - :highlightable="true" - zIndex="9" - /> - <ShortTextInput - name="Jméno osoby" - v-model="personName" - v-model:relatedModel="personPosition" - :predefinedValues="PEOPLE" - :important="true" - zIndex="8" - /> - <LongTextInput - ref="refPersonPosition" - name="Pozice osoby" - v-model="personPosition" - :important="false" - zIndex="7" - /> - - <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" - /> - - <RangeInput - name="Výška gradientu" - v-model="gradientHeightMultiplier" - min="0" - max="3" - /> - - <MultipleColorPicker - name="Barvy" - v-model="colors" - :important="false" - :colorLabels="colorLabels" - :predefinedColors="predefinedColors" - :defaultPredefinedColors="predefinedColors.base" - zIndex="5" - ></MultipleColorPicker> - - <ShortTextInput - name="Zadavatel a zpracovatel" - v-model="contractedBy" - :defaultValue="DEFAULT_CONTRACTOR" - :important="false" - zIndex="4" - /> - </template> - </MainContainer> - </main> + <header> + <Navbar :defaultTemplate="TEMPLATES.basic_photo_banner"></Navbar> + </header> + <main> + <MainContainer> + <template v-slot:left> + <Canvas + ref="canvas" + :redrawFunction="redraw" + width="2000" + height="2000" + /> + </template> + + <template v-slot:right> + <ReloadButton :parentRefs="$refs" @click="reloadCanvasProperties" /> + <AutoReloadCheckbox v-model="autoRedraw" /> + <ImageInput + name="Obrázek" + v-model="mainImage" + :important="true" + zIndex="10" + /> + <LongTextInput + name="Hlavní text" + v-model="mainText" + :important="true" + :highlightable="true" + zIndex="9" + /> + <ShortTextInput + name="Jméno osoby" + v-model="personName" + v-model:relatedModel="personPosition" + :predefinedValues="PEOPLE" + :important="true" + zIndex="8" + /> + <LongTextInput + ref="refPersonPosition" + name="Pozice osoby" + v-model="personPosition" + :important="false" + zIndex="7" + /> + + <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" + /> + + <RangeInput + name="Výška gradientu" + v-model="gradientHeightMultiplier" + min="0" + max="3" + /> + + <MultipleColorPicker + name="Barvy" + v-model="colors" + :important="false" + :colorLabels="colorLabels" + :predefinedColors="predefinedColors" + :defaultPredefinedColors="predefinedColors.base" + zIndex="5" + ></MultipleColorPicker> + + <ShortTextInput + name="Zadavatel a zpracovatel" + v-model="contractedBy" + :defaultValue="DEFAULT_CONTRACTOR" + :important="false" + zIndex="4" + /> + </template> + </MainContainer> + </main> </template> <style> diff --git a/frontend/src/views/basic_photo_banner/canvas.js b/frontend/src/views/basic_photo_banner/canvas.js index 7c2e810ab0f788c7c9c3ea37166bd1250497320a..305a61e670ab2ed10589d161641ea76b35a55593 100644 --- a/frontend/src/views/basic_photo_banner/canvas.js +++ b/frontend/src/views/basic_photo_banner/canvas.js @@ -1,425 +1,374 @@ -import { fabric } from 'fabric' -import { clearObjects, sortObjects, transformHighlightedText, checkTextBoxHeight } from '../../components/canvas/utils' -import { PaddedHighlightingTextbox } from '../../components/canvas/textbox' +import { fabric } from "fabric"; +import { + clearObjects, + sortObjects, + transformHighlightedText, + checkTextBoxHeight, +} from "../../components/canvas/utils"; +import { PaddedHighlightingTextbox } from "../../components/canvas/textbox"; -let mainTextBox = null -let mainTextBoxBackground = null +let mainTextBox = null; +let mainTextBoxBackground = null; -let personNameText = null -let personInfoSeparator = null -let personPositionText = null +let personNameText = null; +let personInfoSeparator = null; +let personPositionText = null; -let mainImage = null -let logoImage = null +let mainImage = null; +let logoImage = null; -let contractedByTextbox = null +let contractedByTextbox = null; -let arrow = null +let arrow = null; -let mainImageSource = null -let previousLogoPosition = null +let mainImageSource = null; +let previousLogoPosition = null; const redraw = async (canvas, options) => { - clearObjects( - [ - mainTextBox, - mainTextBoxBackground, - personNameText, - personInfoSeparator, - personPositionText, - contractedByTextbox, - arrow - ], - canvas - ) - - canvas.preserveObjectStacking = true - - const textMarginLeft = Math.ceil(canvas.width * 0.14) - const textMarginRight = Math.ceil(canvas.width * 0.075) - - let mainTextMarginBottom = Math.ceil(canvas.height * 0.06) - const mainTextBackgroundMarginTop = Math.ceil(canvas.height * 0.14) - const mainTextSize = Math.ceil(canvas.height * 0.0725) - const mainTextHeightLimit = Math.ceil(mainTextSize * 3.3) - const mainTextLineHeight = 1 - - const bottomTextSize = Math.ceil(canvas.height * 0.03) - const nameTextMarginTop = Math.ceil(canvas.height * 0.015) - const nameTextExtraBottomMargin = Math.ceil(canvas.height * 0.06) - const positionTextSideGap = Math.ceil(canvas.width * 0.01) - const positionTextSeparatorWidth = Math.ceil(canvas.width * 0.0035) - const positionTextMaxWidth = Math.ceil(canvas.width * 0.4) - - const contractedByTextSize = Math.ceil(canvas.height * 0.02) - const contractedByTextMaxWidth = Math.ceil(canvas.width * 0.9) - const contractedByTextSidesMargin = Math.ceil(canvas.width * 0.03) - - const arrowWidth = Math.ceil(canvas.width * 0.047) - const arrowHeight = Math.ceil(canvas.width * 0.055) - const arrowThickness = Math.ceil(canvas.width * 0.019) - const arrowMarginLeft = Math.ceil(canvas.width * 0.08) - const arrowMarginLeftExtra = Math.ceil(canvas.width * -0.01) - const arrowMarginTop = Math.ceil(canvas.height * 0.011) - - const logoWidth = Math.ceil(canvas.width * 0.2) - const logoSideMargin = Math.ceil(canvas.width * 0.07) - - if (options.mainText !== null) { - /* BEGIN Name text render */ - - if (options.personName !== null) { - mainTextMarginBottom += nameTextExtraBottomMargin - - let styles = { - 0: {} - } - - - for (let position = 0; position < options.personName.length; position++) { - styles[0][position] = { - fontWeight: 'bold' - } - } - - personNameText = new fabric.Text( - options.personName, - { - left: textMarginLeft, - top: ( - canvas.height - - mainTextMarginBottom - + nameTextMarginTop - ), - fontFamily: 'Roboto Condensed', - fontSize: bottomTextSize, - styles: styles, - fill: options.colors.baseText.value, - selectable: false, - zIndex: 10 - } - ) - - if (options.personPosition !== null) { - personInfoSeparator = new fabric.Rect({ - left: personNameText.left + personNameText.width + positionTextSideGap, - width: positionTextSeparatorWidth, - fill: options.colors.baseText.value, - selectable: false, - zIndex: 10 - }) - - personPositionText = new fabric.Textbox( - options.personPosition, - { - left: personInfoSeparator.left + personInfoSeparator.width + positionTextSideGap, - top: personNameText.top, - width: positionTextMaxWidth, - fontFamily: 'Roboto Condensed', - fontSize: bottomTextSize, - fill: options.colors.baseText.value, - selectable: false, - zIndex: 10 - } - ) - - checkTextBoxHeight(personPositionText, 2) - - if (personPositionText._textLines.length === 2) { - mainTextMarginBottom += nameTextExtraBottomMargin - personNameText.set({top: personNameText.top - nameTextExtraBottomMargin}) - personPositionText.set({top: personPositionText.top - nameTextExtraBottomMargin}) - } - - canvas.add(personPositionText) - - personInfoSeparator.set({top: personPositionText.top}) - personInfoSeparator.set({height: personPositionText.height}) - canvas.add(personInfoSeparator) - - canvas.renderAll() - } - - canvas.add(personNameText) + clearObjects( + [ + mainTextBox, + mainTextBoxBackground, + personNameText, + personInfoSeparator, + personPositionText, + contractedByTextbox, + arrow, + ], + canvas, + ); + + canvas.preserveObjectStacking = true; + + const textMarginLeft = Math.ceil(canvas.width * 0.14); + const textMarginRight = Math.ceil(canvas.width * 0.075); + + let mainTextMarginBottom = Math.ceil(canvas.height * 0.06); + const mainTextBackgroundMarginTop = Math.ceil(canvas.height * 0.14); + const mainTextSize = Math.ceil(canvas.height * 0.0725); + const mainTextHeightLimit = Math.ceil(mainTextSize * 3.3); + const mainTextLineHeight = 1; + + const bottomTextSize = Math.ceil(canvas.height * 0.03); + const nameTextMarginTop = Math.ceil(canvas.height * 0.015); + const nameTextExtraBottomMargin = Math.ceil(canvas.height * 0.06); + const positionTextSideGap = Math.ceil(canvas.width * 0.01); + const positionTextSeparatorWidth = Math.ceil(canvas.width * 0.0035); + const positionTextMaxWidth = Math.ceil(canvas.width * 0.4); + + const contractedByTextSize = Math.ceil(canvas.height * 0.02); + const contractedByTextMaxWidth = Math.ceil(canvas.width * 0.9); + const contractedByTextSidesMargin = Math.ceil(canvas.width * 0.03); + + const arrowWidth = Math.ceil(canvas.width * 0.047); + const arrowHeight = Math.ceil(canvas.width * 0.055); + const arrowThickness = Math.ceil(canvas.width * 0.019); + const arrowMarginLeft = Math.ceil(canvas.width * 0.08); + const arrowMarginLeftExtra = Math.ceil(canvas.width * -0.01); + const arrowMarginTop = Math.ceil(canvas.height * 0.011); + + const logoWidth = Math.ceil(canvas.width * 0.2); + const logoSideMargin = Math.ceil(canvas.width * 0.07); + + if (options.mainText !== null) { + /* BEGIN Name text render */ + + if (options.personName !== null) { + mainTextMarginBottom += nameTextExtraBottomMargin; + + let styles = { + 0: {}, + }; + + for (let position = 0; position < options.personName.length; position++) { + styles[0][position] = { + fontWeight: "bold", + }; + } + + personNameText = new fabric.Text(options.personName, { + left: textMarginLeft, + top: canvas.height - mainTextMarginBottom + nameTextMarginTop, + fontFamily: "Roboto Condensed", + fontSize: bottomTextSize, + styles: styles, + fill: options.colors.baseText.value, + selectable: false, + zIndex: 10, + }); + + if (options.personPosition !== null) { + personInfoSeparator = new fabric.Rect({ + left: + personNameText.left + personNameText.width + positionTextSideGap, + width: positionTextSeparatorWidth, + fill: options.colors.baseText.value, + selectable: false, + zIndex: 10, + }); + + personPositionText = new fabric.Textbox(options.personPosition, { + left: + personInfoSeparator.left + + personInfoSeparator.width + + positionTextSideGap, + top: personNameText.top, + width: positionTextMaxWidth, + fontFamily: "Roboto Condensed", + fontSize: bottomTextSize, + fill: options.colors.baseText.value, + selectable: false, + zIndex: 10, + }); + + checkTextBoxHeight(personPositionText, 2); + + if (personPositionText._textLines.length === 2) { + mainTextMarginBottom += nameTextExtraBottomMargin; + personNameText.set({ + top: personNameText.top - nameTextExtraBottomMargin, + }); + personPositionText.set({ + top: personPositionText.top - nameTextExtraBottomMargin, + }); } - /* END Name text render */ - - - /* BEGIN Main text render */ - - const mainTextWidth = (canvas.width - textMarginLeft - textMarginRight) - - const highlightedData = transformHighlightedText( - options.mainText, - mainTextSize, - mainTextWidth, - 'Bebas Neue', - options.colors.highlight.value, - options.colors.highlightedText.value, - {padWhenDiacritics: true} - ) - - mainTextBox = new PaddedHighlightingTextbox( - highlightedData.text, - { - width: canvas.width, - left: textMarginLeft, - textAlign: 'left', - fontFamily: 'Bebas Neue', - fontSize: mainTextSize, - lineHeight: mainTextLineHeight, - fill: options.colors.baseText.value, - styles: highlightedData.styles, - selectable: false, - highlightPadding: (canvas.height * 0.003), - zIndex: 10 - } - ) - - checkTextBoxHeight(mainTextBox, 3) - - canvas.add(mainTextBox) - - const mainTextBoxTop = ( - canvas.height - - mainTextBox.height - - mainTextMarginBottom - ) - - mainTextBox.top = ( - mainTextBoxTop - - highlightedData.paddingBottom - ) - - canvas.renderAll() - - /* END Main text render */ - - - /* BEGIN Arrow render */ - - arrow = new fabric.Polygon( - [ - {x: 0, y: 0}, - {x: arrowThickness, y: 0}, - { - x: arrowWidth, - y: Math.ceil(arrowHeight) / 2 - }, - { - x: arrowThickness, - y: arrowHeight - }, - {x: 0, y: arrowHeight}, - { - x: arrowWidth - arrowThickness, - y: Math.ceil(arrowHeight) / 2 - }, - {x: 0, y: 0} - ], - { - top: ( - mainTextBoxTop - + arrowMarginTop - ), - left: ( - arrowMarginLeft - + ( - (options.mainText[0] === "*") ? - arrowMarginLeftExtra : 0 - ) - ), - fill: options.colors.arrow.value, - selectable: false, - zIndex: 10 - } - ) - - canvas.add(arrow) - - /* END Arrow render */ - - - /* BEGIN Main text background render */ - - const backgroundHeight = ( - canvas.height - - mainTextBoxTop - + mainTextBackgroundMarginTop - ) - - mainTextBoxBackground = new fabric.Rect( - { - width: canvas.width + 30, // FIXME: Whhhhyyyyyy???? - height: backgroundHeight * options.gradientHeightMultiplier, - left: -20, - top: ( - mainTextBoxTop - - mainTextBackgroundMarginTop - - backgroundHeight * (options.gradientHeightMultiplier - 1) - ), - fill: new fabric.Gradient({ - type: 'linear', - gradientUnits: 'pixels', - coords: { - x1: 0, y1: 0, - x2: 0, y2: backgroundHeight * options.gradientHeightMultiplier - }, - colorStops: [ - { - offset: 1, - color: options.colors.background.value - }, - { - offset: 0.45, - color: options.colors.background.value - }, - { - offset: 0, - color: `${options.colors.background.value}00` - } - ] - }), - selectable: false, - zIndex: 9 - } - ) - - canvas.add(mainTextBoxBackground) - - /* END Main text background render */ - } + canvas.add(personPositionText); + personInfoSeparator.set({ top: personPositionText.top }); + personInfoSeparator.set({ height: personPositionText.height }); + canvas.add(personInfoSeparator); - /* 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.renderAll(); + } - canvas.add(logoImage) + canvas.add(personNameText); } - /* END Logo render */ - - - /* BEGIN Contracted by render */ - - if (options.contractedBy !== null) { - contractedByTextbox = new fabric.Textbox( - options.contractedBy, - { - left: canvas.width - contractedByTextMaxWidth - contractedByTextSidesMargin, - top: ( - canvas.height - - contractedByTextSidesMargin - - contractedByTextSize - ), - width: contractedByTextMaxWidth, - fontFamily: 'Roboto Condensed', - fontSize: contractedByTextSize, - textAlign: 'right', - fill: options.colors.contractedByText.value, - selectable: false, - zIndex: 10 - } - ) - - checkTextBoxHeight(contractedByTextbox, 1) - - canvas.add(contractedByTextbox) + /* END Name text render */ + + /* BEGIN Main text render */ + + const mainTextWidth = canvas.width - textMarginLeft - textMarginRight; + + const highlightedData = transformHighlightedText( + options.mainText, + mainTextSize, + mainTextWidth, + "Bebas Neue", + options.colors.highlight.value, + options.colors.highlightedText.value, + { padWhenDiacritics: true }, + ); + + mainTextBox = new PaddedHighlightingTextbox(highlightedData.text, { + width: canvas.width, + left: textMarginLeft, + textAlign: "left", + fontFamily: "Bebas Neue", + fontSize: mainTextSize, + lineHeight: mainTextLineHeight, + fill: options.colors.baseText.value, + styles: highlightedData.styles, + selectable: false, + highlightPadding: canvas.height * 0.003, + zIndex: 10, + }); + + checkTextBoxHeight(mainTextBox, 3); + + canvas.add(mainTextBox); + + const mainTextBoxTop = + canvas.height - mainTextBox.height - mainTextMarginBottom; + + mainTextBox.top = mainTextBoxTop - highlightedData.paddingBottom; + + canvas.renderAll(); + + /* END Main text render */ + + /* BEGIN Arrow render */ + + arrow = new fabric.Polygon( + [ + { x: 0, y: 0 }, + { x: arrowThickness, y: 0 }, + { + x: arrowWidth, + y: Math.ceil(arrowHeight) / 2, + }, + { + x: arrowThickness, + y: arrowHeight, + }, + { x: 0, y: arrowHeight }, + { + x: arrowWidth - arrowThickness, + y: Math.ceil(arrowHeight) / 2, + }, + { x: 0, y: 0 }, + ], + { + top: mainTextBoxTop + arrowMarginTop, + left: + arrowMarginLeft + + (options.mainText[0] === "*" ? arrowMarginLeftExtra : 0), + fill: options.colors.arrow.value, + selectable: false, + zIndex: 10, + }, + ); + + canvas.add(arrow); + + /* END Arrow render */ + + /* BEGIN Main text background render */ + + const backgroundHeight = + canvas.height - mainTextBoxTop + mainTextBackgroundMarginTop; + + mainTextBoxBackground = new fabric.Rect({ + width: canvas.width + 30, // FIXME: Whhhhyyyyyy???? + height: backgroundHeight * options.gradientHeightMultiplier, + left: -20, + top: + mainTextBoxTop - + mainTextBackgroundMarginTop - + backgroundHeight * (options.gradientHeightMultiplier - 1), + fill: new fabric.Gradient({ + type: "linear", + gradientUnits: "pixels", + coords: { + x1: 0, + y1: 0, + x2: 0, + y2: backgroundHeight * options.gradientHeightMultiplier, + }, + colorStops: [ + { + offset: 1, + color: options.colors.background.value, + }, + { + offset: 0.45, + color: options.colors.background.value, + }, + { + offset: 0, + color: `${options.colors.background.value}00`, + }, + ], + }), + selectable: false, + zIndex: 9, + }); + + canvas.add(mainTextBoxBackground); + + /* END Main text 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, + }); } - /* END Contracted by render */ + canvas.add(logoImage); + } + /* END Logo render */ - /* BEGIN Main image render */ + /* BEGIN Contracted by render */ - if ( - options.mainImage !== null - && ( - !canvas.contains(mainImage) - || ( - mainImage === null - || options.mainImage.src !== mainImageSource - ) - ) - ) { - if (mainImage !== null) { - canvas.remove(mainImage) - } + if (options.contractedBy !== null) { + contractedByTextbox = new fabric.Textbox(options.contractedBy, { + left: + canvas.width - contractedByTextMaxWidth - contractedByTextSidesMargin, + top: canvas.height - contractedByTextSidesMargin - contractedByTextSize, + width: contractedByTextMaxWidth, + fontFamily: "Roboto Condensed", + fontSize: contractedByTextSize, + textAlign: "right", + fill: options.colors.contractedByText.value, + selectable: false, + zIndex: 10, + }); - mainImage = new fabric.Image( - options.mainImage, - { - left: 0, - top: 0, - zIndex: 0 - } - ) - - mainImage.controls = { - ...fabric.Image.prototype.controls, - mtr: new fabric.Control({ visible: false }) - } + checkTextBoxHeight(contractedByTextbox, 1); - if (mainImage.width >= mainImage.height) { - mainImage.scaleToHeight(canvas.height) - } else { - mainImage.scaleToWidth(canvas.width) - } + canvas.add(contractedByTextbox); + } + + /* END Contracted by render */ + + /* BEGIN Main image render */ + + if ( + options.mainImage !== null && + (!canvas.contains(mainImage) || + mainImage === null || + options.mainImage.src !== mainImageSource) + ) { + if (mainImage !== null) { + canvas.remove(mainImage); + } - canvas.add(mainImage) - mainImageSource = options.mainImage.src - // canvas.centerObject(mainImage) - } else if ( - mainImage !== null - && options.mainImage === null - ) { - canvas.remove(mainImage) + mainImage = new fabric.Image(options.mainImage, { + left: 0, + top: 0, + zIndex: 0, + }); + + mainImage.controls = { + ...fabric.Image.prototype.controls, + mtr: new fabric.Control({ visible: false }), + }; + + if (mainImage.width >= mainImage.height) { + mainImage.scaleToHeight(canvas.height); + } else { + mainImage.scaleToWidth(canvas.width); } - /* END Main image render */ + canvas.add(mainImage); + mainImageSource = options.mainImage.src; + // canvas.centerObject(mainImage) + } else if (mainImage !== null && options.mainImage === null) { + canvas.remove(mainImage); + } + /* END Main image render */ - sortObjects(canvas) -} + sortObjects(canvas); +}; -export default redraw +export default redraw; diff --git a/frontend/src/views/facebook_survey/FacebookSurvey.vue b/frontend/src/views/facebook_survey/FacebookSurvey.vue index d9042fce8024d9defe634c15fc1e2d29925b4906..f25dfece67a63487055b6a1f28675567143d04c9 100644 --- a/frontend/src/views/facebook_survey/FacebookSurvey.vue +++ b/frontend/src/views/facebook_survey/FacebookSurvey.vue @@ -1,270 +1,263 @@ <script setup> -import { watch, ref } from 'vue' +import { watch, ref } from "vue"; -import COLORS from '../../colors' -import TEMPLATES from '../../templates' -import DEFAULT_CONTRACTOR from '../../contractors' -import { generateDefaultLogos, LOGO_POSITIONS, generateLogoPositions } from '../../logos' -import { loadFonts, loadCanvasStorage, setCanvasStorage, updateAutoRedrawStorage } from '../../utils' +import COLORS from "../../colors"; +import TEMPLATES from "../../templates"; +import DEFAULT_CONTRACTOR from "../../contractors"; +import { + generateDefaultLogos, + LOGO_POSITIONS, + generateLogoPositions, +} from "../../logos"; +import { + loadFonts, + loadCanvasStorage, + setCanvasStorage, + updateAutoRedrawStorage, +} from "../../utils"; -import Canvas from '../../components/canvas/Canvas.vue' -import redraw from './canvas' +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 RangeInput from '../../components/inputs/RangeInput.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' +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 RangeInput from "../../components/inputs/RangeInput.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', -]) +await loadFonts(["12px Bebas Neue", "12px Roboto Condensed"]); export default { - components: { - Canvas, - Navbar, - MainContainer, - ImageInput, - LongTextInput, - ShortTextInput, - EmojiInput, - InputSeparator, - MultipleColorPicker - }, - data () { - const predefinedColors = { - base: { - name: 'Základní barvy', - colors: { - background: COLORS.black, - highlight: COLORS.yellow1, - arrow: COLORS.yellow1, - baseText: COLORS.white, - highlightedText: COLORS.black, - contractedByText: COLORS.gray1 - } - } - } + components: { + Canvas, + Navbar, + MainContainer, + ImageInput, + LongTextInput, + ShortTextInput, + EmojiInput, + InputSeparator, + MultipleColorPicker, + }, + data() { + const predefinedColors = { + base: { + name: "Základní barvy", + colors: { + background: COLORS.black, + highlight: COLORS.yellow1, + arrow: COLORS.yellow1, + baseText: COLORS.white, + highlightedText: COLORS.black, + contractedByText: COLORS.gray1, + }, + }, + }; - return { - mainImage: null, - mainText: null, - logoImage: null, - logoPosition: LOGO_POSITIONS.top_right, - logoOptions: generateLogoPositions( - [ - "top_right", - "top_left", - ] - ), - firstEmojiImage: null, - firstEmojiText: null, - secondEmojiImage: null, - secondEmojiText: null, - contractedBy: DEFAULT_CONTRACTOR, - colorLabels: { - background: 'Pozadí', - highlight: 'Zvýraznění', - arrow: 'Šipka', - baseText: 'Text', - highlightedText: 'Zvýrazněný text' - }, - gradientHeightMultiplier: 1, - predefinedColors: predefinedColors, - colors: predefinedColors.base.colors, - predefinedLogoImages: generateDefaultLogos('defaultDark'), - autoRedraw: false - } - }, - async created () { - await loadCanvasStorage(this) - }, - methods: { - async reloadCanvasProperties () { - const canvasProperties = { - mainImage: this.mainImage, - mainText: this.mainText, - contractedBy: this.contractedBy, - logoImage: this.logoImage, - logoPosition: this.logoPosition, - colors: this.colors, - firstEmojiImage: this.firstEmojiImage, - secondEmojiImage: this.secondEmojiImage, - firstEmojiText: this.firstEmojiText, - secondEmojiText: this.secondEmojiText, - gradientHeightMultiplier: this.gradientHeightMultiplier - } + return { + mainImage: null, + mainText: null, + logoImage: null, + logoPosition: LOGO_POSITIONS.top_right, + logoOptions: generateLogoPositions(["top_right", "top_left"]), + firstEmojiImage: null, + firstEmojiText: null, + secondEmojiImage: null, + secondEmojiText: null, + contractedBy: DEFAULT_CONTRACTOR, + colorLabels: { + background: "Pozadí", + highlight: "Zvýraznění", + arrow: "Šipka", + baseText: "Text", + highlightedText: "Zvýrazněný text", + }, + gradientHeightMultiplier: 1, + predefinedColors: predefinedColors, + colors: predefinedColors.base.colors, + predefinedLogoImages: generateDefaultLogos("defaultDark"), + autoRedraw: false, + }; + }, + async created() { + await loadCanvasStorage(this); + }, + methods: { + async reloadCanvasProperties() { + const canvasProperties = { + mainImage: this.mainImage, + mainText: this.mainText, + contractedBy: this.contractedBy, + logoImage: this.logoImage, + logoPosition: this.logoPosition, + colors: this.colors, + firstEmojiImage: this.firstEmojiImage, + secondEmojiImage: this.secondEmojiImage, + firstEmojiText: this.firstEmojiText, + secondEmojiText: this.secondEmojiText, + gradientHeightMultiplier: this.gradientHeightMultiplier, + }; - await this.$refs.canvas.redraw(canvasProperties) + await this.$refs.canvas.redraw(canvasProperties); - delete canvasProperties.colors - setCanvasStorage(canvasProperties) - } + delete canvasProperties.colors; + setCanvasStorage(canvasProperties); }, - mounted () { - this.$watch( - vm => [ - vm.mainImage, - vm.mainText, - vm.contractedBy, - vm.logoImage, - vm.logoPosition, - vm.colors, - vm.firstEmojiImage, - vm.secondEmojiImage, - vm.firstEmojiText, - vm.secondEmojiText, - vm.gradientHeightMultiplier - ], - async (value) => { - if (this.autoRedraw) { - await this.reloadCanvasProperties() - } - }, - { - immediate: true, - deep: true - } - ) + }, + mounted() { + this.$watch( + (vm) => [ + vm.mainImage, + vm.mainText, + vm.contractedBy, + vm.logoImage, + vm.logoPosition, + vm.colors, + vm.firstEmojiImage, + vm.secondEmojiImage, + vm.firstEmojiText, + vm.secondEmojiText, + vm.gradientHeightMultiplier, + ], + async (value) => { + if (this.autoRedraw) { + await this.reloadCanvasProperties(); + } + }, + { + immediate: true, + deep: true, + }, + ); - this.$watch( - vm => [vm.autoRedraw], - async (value) => { - updateAutoRedrawStorage(this.autoRedraw) + this.$watch( + (vm) => [vm.autoRedraw], + async (value) => { + updateAutoRedrawStorage(this.autoRedraw); - if (this.autoRedraw) { - await this.reloadCanvasProperties() - } - } - ) - } -} + if (this.autoRedraw) { + await this.reloadCanvasProperties(); + } + }, + ); + }, +}; </script> <template> - <header> - <Navbar - :defaultTemplate="TEMPLATES.facebook_survey" - ></Navbar> - </header> - <main> - <MainContainer> - <template v-slot:left> - <Canvas - ref="canvas" - :redrawFunction="redraw" - width="2000" - height="2000" - /> - </template> - - <template v-slot:right> - <ReloadButton - :parentRefs="$refs" - @click="reloadCanvasProperties" - /> - <AutoReloadCheckbox - v-model="autoRedraw" - /> - <ImageInput - name="Obrázek" - v-model="mainImage" - :important="true" - zIndex="10" - /> - <LongTextInput - name="Hlavní text" - v-model="mainText" - :important="true" - :highlightable="true" - zIndex="9" - /> + <header> + <Navbar :defaultTemplate="TEMPLATES.facebook_survey"></Navbar> + </header> + <main> + <MainContainer> + <template v-slot:left> + <Canvas + ref="canvas" + :redrawFunction="redraw" + width="2000" + height="2000" + /> + </template> - <EmojiInput - name="Levé emoji" - v-model="firstEmojiImage" - :important="true" - zIndex="8" - /> + <template v-slot:right> + <ReloadButton :parentRefs="$refs" @click="reloadCanvasProperties" /> + <AutoReloadCheckbox v-model="autoRedraw" /> + <ImageInput + name="Obrázek" + v-model="mainImage" + :important="true" + zIndex="10" + /> + <LongTextInput + name="Hlavní text" + v-model="mainText" + :important="true" + :highlightable="true" + zIndex="9" + /> - <ShortTextInput - name="Levý text" - v-model="firstEmojiText" - :important="true" - zIndex="7" - /> + <EmojiInput + name="Levé emoji" + v-model="firstEmojiImage" + :important="true" + zIndex="8" + /> + <ShortTextInput + name="Levý text" + v-model="firstEmojiText" + :important="true" + zIndex="7" + /> - <EmojiInput - name="Pravé emoji" - v-model="secondEmojiImage" - :important="true" - zIndex="6" - /> + <EmojiInput + name="Pravé emoji" + v-model="secondEmojiImage" + :important="true" + zIndex="6" + /> - <ShortTextInput - name="Pravý text" - v-model="secondEmojiText" - :important="true" - zIndex="5" - /> + <ShortTextInput + name="Pravý text" + v-model="secondEmojiText" + :important="true" + zIndex="5" + /> - <InputSeparator /> + <InputSeparator /> - <RangeInput - name="Výška gradientu" - v-model="gradientHeightMultiplier" - min="0" - max="3" - /> + <RangeInput + name="Výška gradientu" + v-model="gradientHeightMultiplier" + min="0" + max="3" + /> - <ImageInput - name="Obrázek loga" - v-model="logoImage" - :important="false" - :predefinedImages="predefinedLogoImages" - :mustSelectPredefinedImage="true" - :disableImageInput="true" - zIndex="4" - /> - <SelectInput - name="Pozice loga" - :options="logoOptions" - v-model="logoPosition" - zIndex="3" - /> + <ImageInput + name="Obrázek loga" + v-model="logoImage" + :important="false" + :predefinedImages="predefinedLogoImages" + :mustSelectPredefinedImage="true" + :disableImageInput="true" + zIndex="4" + /> + <SelectInput + name="Pozice loga" + :options="logoOptions" + v-model="logoPosition" + zIndex="3" + /> - <MultipleColorPicker - name="Barvy" - v-model="colors" - :important="false" - :colorLabels="colorLabels" - :predefinedColors="predefinedColors" - :defaultPredefinedColors="predefinedColors.base" - zIndex="2" - ></MultipleColorPicker> + <MultipleColorPicker + name="Barvy" + v-model="colors" + :important="false" + :colorLabels="colorLabels" + :predefinedColors="predefinedColors" + :defaultPredefinedColors="predefinedColors.base" + zIndex="2" + ></MultipleColorPicker> - <ShortTextInput - name="Zadavatel a zpracovatel" - v-model="contractedBy" - :defaultValue="DEFAULT_CONTRACTOR" - :important="false" - zIndex="1" - /> - </template> - </MainContainer> - </main> + <ShortTextInput + name="Zadavatel a zpracovatel" + v-model="contractedBy" + :defaultValue="DEFAULT_CONTRACTOR" + :important="false" + zIndex="1" + /> + </template> + </MainContainer> + </main> </template> <style> diff --git a/frontend/src/views/facebook_survey/canvas.js b/frontend/src/views/facebook_survey/canvas.js index ecda2228907dc1b878216989313002ee73fe1fc2..652c0026dd56801785c44968df240025db691e08 100644 --- a/frontend/src/views/facebook_survey/canvas.js +++ b/frontend/src/views/facebook_survey/canvas.js @@ -1,442 +1,389 @@ -import alertifyjs from "alertifyjs" -import "alertifyjs/build/css/alertify.css" +import alertifyjs from "alertifyjs"; +import "alertifyjs/build/css/alertify.css"; -import { fabric } from 'fabric' -import { clearObjects, sortObjects, transformHighlightedText, checkTextBoxHeight } from '../../components/canvas/utils' -import { PaddedHighlightingTextbox } from '../../components/canvas/textbox' +import { fabric } from "fabric"; +import { + clearObjects, + sortObjects, + transformHighlightedText, + checkTextBoxHeight, +} from "../../components/canvas/utils"; +import { PaddedHighlightingTextbox } from "../../components/canvas/textbox"; -let mainTextBox = null -let mainTextBoxBackground = null +let mainTextBox = null; +let mainTextBoxBackground = null; -let contractedByTextbox = null +let contractedByTextbox = null; -let mainImage = null -let logoImage = null +let mainImage = null; +let logoImage = null; -let firstEmojiImage = null -let secondEmojiImage = null +let firstEmojiImage = null; +let secondEmojiImage = null; -let firstEmojiText = null -let secondEmojiText = null +let firstEmojiText = null; +let secondEmojiText = null; -let mainImageSource = null -let previousLogoPosition = null +let mainImageSource = null; +let previousLogoPosition = null; const redraw = async (canvas, options) => { - clearObjects( - [ - mainTextBox, - mainTextBoxBackground, - firstEmojiImage, - firstEmojiText, - secondEmojiImage, - secondEmojiText, - contractedByTextbox, - ], - canvas - ) - - canvas.preserveObjectStacking = true - - const textMarginSides = Math.ceil(canvas.width * 0.14) - - let mainTextMarginBottom = Math.ceil(canvas.height * 0.06) - const mainTextBackgroundMarginTop = Math.ceil(canvas.height * 0.14) - const mainTextSize = Math.ceil(canvas.height * 0.0725) - const mainTextLineHeight = 1 - - const contractedByTextSize = Math.ceil(canvas.height * 0.02) - const contractedByTextMaxWidth = Math.ceil(canvas.width * 0.9) - const contractedByTextSidesMargin = Math.ceil(canvas.width * 0.03) - - const emojiImageHeight = Math.ceil(canvas.height * 0.100) - - const firstEmojiImageMarginSide = Math.ceil(canvas.width * 0.25) - const firstEmojiImageMarginTop = 50 - const secondEmojiImageMarginSide = Math.ceil(canvas.width * 0.65) - const secondEmojiImageMarginTop = 50 - - const logoWidth = Math.ceil(canvas.width * 0.2) - const logoSideMargin = Math.ceil(canvas.width * 0.07) - - if (options.mainText !== null) { - /* BEGIN Main text render */ - - const mainTextWidth = (canvas.width - textMarginSides * 2) - - const highlightedData = transformHighlightedText( - options.mainText, - mainTextSize, - mainTextWidth, - 'Bebas Neue', - options.colors.highlight.value, - options.colors.highlightedText.value - ) - - mainTextBox = new PaddedHighlightingTextbox( - highlightedData.text, - { - width: canvas.width, - left: 0, - textAlign: 'center', - fontFamily: 'Bebas Neue', - fontSize: mainTextSize, - lineHeight: mainTextLineHeight, - fill: options.colors.baseText.value, - styles: highlightedData.styles, - selectable: false, - highlightPadding: (canvas.height * 0.003), - zIndex: 10 - } - ) - - checkTextBoxHeight(mainTextBox, 3) - - canvas.add(mainTextBox) - - mainTextBox.top = ( - canvas.height - - mainTextBox.height - - mainTextMarginBottom - - 200 - ) - - canvas.renderAll() - - /* END Main text render */ - - - /* BEGIN First Emoji render*/ - - if (options.firstEmojiImage !== null) { - firstEmojiImage = new fabric.Image( - options.firstEmojiImage, - { - selectable: false, - zIndex: 10, - } - ) - firstEmojiImage.scaleToHeight(emojiImageHeight) - firstEmojiImage.set({ - left: canvas.width / 2 - 600, - top: mainTextBox.top + mainTextBox.height + firstEmojiImageMarginTop - }) - - canvas.add(firstEmojiImage) - } + clearObjects( + [ + mainTextBox, + mainTextBoxBackground, + firstEmojiImage, + firstEmojiText, + secondEmojiImage, + secondEmojiText, + contractedByTextbox, + ], + canvas, + ); + + canvas.preserveObjectStacking = true; + + const textMarginSides = Math.ceil(canvas.width * 0.14); + + let mainTextMarginBottom = Math.ceil(canvas.height * 0.06); + const mainTextBackgroundMarginTop = Math.ceil(canvas.height * 0.14); + const mainTextSize = Math.ceil(canvas.height * 0.0725); + const mainTextLineHeight = 1; + + const contractedByTextSize = Math.ceil(canvas.height * 0.02); + const contractedByTextMaxWidth = Math.ceil(canvas.width * 0.9); + const contractedByTextSidesMargin = Math.ceil(canvas.width * 0.03); + + const emojiImageHeight = Math.ceil(canvas.height * 0.1); + + const firstEmojiImageMarginSide = Math.ceil(canvas.width * 0.25); + const firstEmojiImageMarginTop = 50; + const secondEmojiImageMarginSide = Math.ceil(canvas.width * 0.65); + const secondEmojiImageMarginTop = 50; + + const logoWidth = Math.ceil(canvas.width * 0.2); + const logoSideMargin = Math.ceil(canvas.width * 0.07); + + if (options.mainText !== null) { + /* BEGIN Main text render */ + + const mainTextWidth = canvas.width - textMarginSides * 2; + + const highlightedData = transformHighlightedText( + options.mainText, + mainTextSize, + mainTextWidth, + "Bebas Neue", + options.colors.highlight.value, + options.colors.highlightedText.value, + ); + + mainTextBox = new PaddedHighlightingTextbox(highlightedData.text, { + width: canvas.width, + left: 0, + textAlign: "center", + fontFamily: "Bebas Neue", + fontSize: mainTextSize, + lineHeight: mainTextLineHeight, + fill: options.colors.baseText.value, + styles: highlightedData.styles, + selectable: false, + highlightPadding: canvas.height * 0.003, + zIndex: 10, + }); + + checkTextBoxHeight(mainTextBox, 3); + + canvas.add(mainTextBox); + + mainTextBox.top = + canvas.height - mainTextBox.height - mainTextMarginBottom - 200; + + canvas.renderAll(); + + /* END Main text render */ + + /* BEGIN First Emoji render*/ + + if (options.firstEmojiImage !== null) { + firstEmojiImage = new fabric.Image(options.firstEmojiImage, { + selectable: false, + zIndex: 10, + }); + firstEmojiImage.scaleToHeight(emojiImageHeight); + firstEmojiImage.set({ + left: canvas.width / 2 - 600, + top: mainTextBox.top + mainTextBox.height + firstEmojiImageMarginTop, + }); + + canvas.add(firstEmojiImage); + } - if (options.firstEmojiText !== null && options.firstEmojiImage !== null) { - firstEmojiText = new fabric.Textbox( - options.firstEmojiText, - { - left: firstEmojiImage.left + 250, - width: 500, - top: ( - mainTextBox.top - + mainTextBox.height - + firstEmojiImageMarginTop - + emojiImageHeight / 8 - ), - fontFamily: 'Bebas Neue', - fontSize: mainTextSize, - fill: options.colors.baseText.value, - selectable: false, - zIndex: 10 - } - ) - - canvas.add(firstEmojiText) - - if (firstEmojiText._textLines.length > 1) { - firstEmojiText.set({ - fontSize: firstEmojiText.fontSize - (mainTextSize / 2), - lineHeight: 0.9 - }) - canvas.renderAll() - } else if (firstEmojiText.width > 500) { - firstEmojiText.set({ - fontSize: firstEmojiText.fontSize - (mainTextSize / 2), - lineHeight: 0.9 - }) - firstEmojiText.top += firstEmojiText.fontSize / 2.5 - canvas.renderAll() - } - - if (firstEmojiText._textLines.length > 2) { - canvas.remove(firstEmojiText) - - if (!window.showingMaxLinesWarning) { - window.showingMaxLinesWarning = true - - const errorMessage = alertifyjs.error( - "Text je moc dlouhý a nevejde se do 2 řádků. Prosím, zkrať ho." - ) - - errorMessage.callback = () => { - window.showingMaxLinesWarning = false - } - } - } + if (options.firstEmojiText !== null && options.firstEmojiImage !== null) { + firstEmojiText = new fabric.Textbox(options.firstEmojiText, { + left: firstEmojiImage.left + 250, + width: 500, + top: + mainTextBox.top + + mainTextBox.height + + firstEmojiImageMarginTop + + emojiImageHeight / 8, + fontFamily: "Bebas Neue", + fontSize: mainTextSize, + fill: options.colors.baseText.value, + selectable: false, + zIndex: 10, + }); + + canvas.add(firstEmojiText); + + if (firstEmojiText._textLines.length > 1) { + firstEmojiText.set({ + fontSize: firstEmojiText.fontSize - mainTextSize / 2, + lineHeight: 0.9, + }); + canvas.renderAll(); + } else if (firstEmojiText.width > 500) { + firstEmojiText.set({ + fontSize: firstEmojiText.fontSize - mainTextSize / 2, + lineHeight: 0.9, + }); + firstEmojiText.top += firstEmojiText.fontSize / 2.5; + canvas.renderAll(); + } + + if (firstEmojiText._textLines.length > 2) { + canvas.remove(firstEmojiText); + + if (!window.showingMaxLinesWarning) { + window.showingMaxLinesWarning = true; + + const errorMessage = alertifyjs.error( + "Text je moc dlouhý a nevejde se do 2 řádků. Prosím, zkrať ho.", + ); + + errorMessage.callback = () => { + window.showingMaxLinesWarning = false; + }; } + } + } - /* END First emoji render */ - + /* END First emoji render */ - /* BEGIN Second Emoji render */ + /* BEGIN Second Emoji render */ - if (options.secondEmojiImage !== null) { - secondEmojiImage = new fabric.Image( - options.secondEmojiImage, - { - selectable: false, - zIndex: 10 - } - ) - secondEmojiImage.scaleToHeight(emojiImageHeight) - secondEmojiImage.set({ - left: canvas.width / 2 + 200, - top: mainTextBox.top + mainTextBox.height + firstEmojiImageMarginTop - }) + if (options.secondEmojiImage !== null) { + secondEmojiImage = new fabric.Image(options.secondEmojiImage, { + selectable: false, + zIndex: 10, + }); + secondEmojiImage.scaleToHeight(emojiImageHeight); + secondEmojiImage.set({ + left: canvas.width / 2 + 200, + top: mainTextBox.top + mainTextBox.height + firstEmojiImageMarginTop, + }); - canvas.add(secondEmojiImage) - } + canvas.add(secondEmojiImage); + } - if (options.secondEmojiText !== null && options.secondEmojiImage !== null) { - secondEmojiText = new fabric.Textbox( - options.secondEmojiText, - { - left: secondEmojiImage.left + 250, - width: 300, - top: ( - mainTextBox.top - + mainTextBox.height - + secondEmojiImageMarginTop - + emojiImageHeight / 8 - ), - fontFamily: 'Bebas Neue', - fontSize: mainTextSize, - fill: options.colors.baseText.value, - selectable: false, - zIndex: 10 - } - ) - - canvas.add(secondEmojiText) - - if (secondEmojiText._textLines.length > 1) { - secondEmojiText.set({ - fontSize: secondEmojiText.fontSize - (mainTextSize / 2), - lineHeight: 0.9 - }) - canvas.renderAll() - } else if (secondEmojiText.width > 500) { - secondEmojiText.set({ - fontSize: secondEmojiText.fontSize - (mainTextSize / 2), - lineHeight: 0.9 - }) - secondEmojiText.top += secondEmojiText.fontSize / 2.5 - canvas.renderAll() - } - - if (secondEmojiText._textLines.length > 2) { - canvas.remove(secondEmojiText) - - if (!window.showingMaxLinesWarning) { - window.showingMaxLinesWarning = true - - const errorMessage = alertifyjs.error( - "Text je moc dlouhý a nevejde se do 2 řádků. Prosím, zkrať ho." - ) - - errorMessage.callback = () => { - window.showingMaxLinesWarning = false - } - } - } + if (options.secondEmojiText !== null && options.secondEmojiImage !== null) { + secondEmojiText = new fabric.Textbox(options.secondEmojiText, { + left: secondEmojiImage.left + 250, + width: 300, + top: + mainTextBox.top + + mainTextBox.height + + secondEmojiImageMarginTop + + emojiImageHeight / 8, + fontFamily: "Bebas Neue", + fontSize: mainTextSize, + fill: options.colors.baseText.value, + selectable: false, + zIndex: 10, + }); + + canvas.add(secondEmojiText); + + if (secondEmojiText._textLines.length > 1) { + secondEmojiText.set({ + fontSize: secondEmojiText.fontSize - mainTextSize / 2, + lineHeight: 0.9, + }); + canvas.renderAll(); + } else if (secondEmojiText.width > 500) { + secondEmojiText.set({ + fontSize: secondEmojiText.fontSize - mainTextSize / 2, + lineHeight: 0.9, + }); + secondEmojiText.top += secondEmojiText.fontSize / 2.5; + canvas.renderAll(); + } + + if (secondEmojiText._textLines.length > 2) { + canvas.remove(secondEmojiText); + + if (!window.showingMaxLinesWarning) { + window.showingMaxLinesWarning = true; + + const errorMessage = alertifyjs.error( + "Text je moc dlouhý a nevejde se do 2 řádků. Prosím, zkrať ho.", + ); + + errorMessage.callback = () => { + window.showingMaxLinesWarning = false; + }; } - - /* END Second Emoji render */ - - - /* BEGIN Main text background render */ - - const backgroundHeight = ( - canvas.height - - mainTextBox.top - + mainTextBackgroundMarginTop - ) - - mainTextBoxBackground = new fabric.Rect( - { - width: canvas.width + 40, // FIXME: My god! - height: backgroundHeight * options.gradientHeightMultiplier, - left: -20, - top: ( - mainTextBox.top - - mainTextBackgroundMarginTop - - backgroundHeight * (options.gradientHeightMultiplier - 1) - ), - fill: new fabric.Gradient({ - type: 'linear', - gradientUnits: 'pixels', - coords: { - x1: 0, y1: 0, - x2: 0, y2: backgroundHeight * options.gradientHeightMultiplier - }, - colorStops: [ - { - offset: 1, - color: options.colors.background.value - }, - { - offset: 0.45, - color: options.colors.background.value - }, - { - offset: 0, - color: `${options.colors.background.value}00` - } - ] - }), - selectable: false, - zIndex: 9 - } - ) - - canvas.add(mainTextBoxBackground) - - /* END Main text background render */ + } } + /* END Second Emoji render */ + + /* BEGIN Main text background render */ + + const backgroundHeight = + canvas.height - mainTextBox.top + mainTextBackgroundMarginTop; + + mainTextBoxBackground = new fabric.Rect({ + width: canvas.width + 40, // FIXME: My god! + height: backgroundHeight * options.gradientHeightMultiplier, + left: -20, + top: + mainTextBox.top - + mainTextBackgroundMarginTop - + backgroundHeight * (options.gradientHeightMultiplier - 1), + fill: new fabric.Gradient({ + type: "linear", + gradientUnits: "pixels", + coords: { + x1: 0, + y1: 0, + x2: 0, + y2: backgroundHeight * options.gradientHeightMultiplier, + }, + colorStops: [ + { + offset: 1, + color: options.colors.background.value, + }, + { + offset: 0.45, + color: options.colors.background.value, + }, + { + offset: 0, + color: `${options.colors.background.value}00`, + }, + ], + }), + selectable: false, + zIndex: 9, + }); + + canvas.add(mainTextBoxBackground); + + /* END Main text 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, + }); + } - /* 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); + } - canvas.add(logoImage) - } + /* END Logo render */ - /* END Logo render */ - - - /* BEGIN Contracted by render */ - - if (options.contractedBy !== null) { - contractedByTextbox = new fabric.Textbox( - options.contractedBy, - { - left: canvas.width - contractedByTextMaxWidth - contractedByTextSidesMargin, - top: ( - canvas.height - - contractedByTextSidesMargin - - contractedByTextSize - ), - width: contractedByTextMaxWidth, - fontFamily: 'Roboto Condensed', - fontSize: contractedByTextSize, - textAlign: 'right', - fill: options.colors.contractedByText.value, - selectable: false, - zIndex: 10 - } - ) - - checkTextBoxHeight(contractedByTextbox, 1) - - canvas.add(contractedByTextbox) - } + /* BEGIN Contracted by render */ - /* END Contracted by render */ + if (options.contractedBy !== null) { + contractedByTextbox = new fabric.Textbox(options.contractedBy, { + left: + canvas.width - contractedByTextMaxWidth - contractedByTextSidesMargin, + top: canvas.height - contractedByTextSidesMargin - contractedByTextSize, + width: contractedByTextMaxWidth, + fontFamily: "Roboto Condensed", + fontSize: contractedByTextSize, + textAlign: "right", + fill: options.colors.contractedByText.value, + selectable: false, + zIndex: 10, + }); + checkTextBoxHeight(contractedByTextbox, 1); - /* BEGIN Main image render */ + canvas.add(contractedByTextbox); + } - if ( - options.mainImage !== null - && ( - !canvas.contains(mainImage) - || ( - mainImage === null - || options.mainImage.src !== mainImageSource - ) - ) - ) { - if (mainImage !== null) { - canvas.remove(mainImage) - } + /* END Contracted by render */ - mainImage = new fabric.Image( - options.mainImage, - { - left: 0, - top: 0, - zIndex: 0 - } - ) - - mainImage.controls = { - ...fabric.Image.prototype.controls, - mtr: new fabric.Control({ visible: false }) - } + /* BEGIN Main image render */ - if (mainImage.width >= mainImage.height) { - mainImage.scaleToHeight(canvas.height) - } else { - mainImage.scaleToWidth(canvas.width) - } + if ( + options.mainImage !== null && + (!canvas.contains(mainImage) || + mainImage === null || + options.mainImage.src !== mainImageSource) + ) { + if (mainImage !== null) { + canvas.remove(mainImage); + } - canvas.add(mainImage) - mainImageSource = options.mainImage.src - // canvas.centerObject(mainImage) - } else if ( - mainImage !== null - && options.mainImage === null - ) { - canvas.remove(mainImage) + mainImage = new fabric.Image(options.mainImage, { + left: 0, + top: 0, + zIndex: 0, + }); + + mainImage.controls = { + ...fabric.Image.prototype.controls, + mtr: new fabric.Control({ visible: false }), + }; + + if (mainImage.width >= mainImage.height) { + mainImage.scaleToHeight(canvas.height); + } else { + mainImage.scaleToWidth(canvas.width); } - /* END Main image render */ + canvas.add(mainImage); + mainImageSource = options.mainImage.src; + // canvas.centerObject(mainImage) + } else if (mainImage !== null && options.mainImage === null) { + canvas.remove(mainImage); + } + /* END Main image render */ - sortObjects(canvas) -} + sortObjects(canvas); +}; -export default redraw +export default redraw; diff --git a/frontend/src/views/newspaper_quote_bottom/NewspaperQuoteBottom.vue b/frontend/src/views/newspaper_quote_bottom/NewspaperQuoteBottom.vue index 10f7fbd18149365204f7fcf75ea63524e388a2b3..ff8b1ebaa7ee55510be5cda5181df347942693a9 100644 --- a/frontend/src/views/newspaper_quote_bottom/NewspaperQuoteBottom.vue +++ b/frontend/src/views/newspaper_quote_bottom/NewspaperQuoteBottom.vue @@ -1,288 +1,283 @@ <script setup> -import { watch, ref } from 'vue' +import { watch, ref } from "vue"; -import COLORS from '../../colors' -import PEOPLE from '../../people' -import TEMPLATES from '../../templates' -import DEFAULT_CONTRACTOR from '../../contractors' -import { generateDefaultLogos, LOGO_POSITIONS, generateLogoPositions } from '../../logos' -import { SOURCE_IMAGES } from '../utils/newspaper_quotes' -import { loadFonts, loadCanvasStorage, setCanvasStorage, updateAutoRedrawStorage } from '../../utils' +import COLORS from "../../colors"; +import PEOPLE from "../../people"; +import TEMPLATES from "../../templates"; +import DEFAULT_CONTRACTOR from "../../contractors"; +import { + generateDefaultLogos, + LOGO_POSITIONS, + generateLogoPositions, +} from "../../logos"; +import { SOURCE_IMAGES } from "../utils/newspaper_quotes"; +import { + loadFonts, + loadCanvasStorage, + setCanvasStorage, + updateAutoRedrawStorage, +} from "../../utils"; -import Canvas from '../../components/canvas/Canvas.vue' -import redraw from './canvas'; +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 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' +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 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([ - '700 12px Glegoo', - '12px Roboto Condensed', - 'bold 12px Roboto Condensed' -]) + "700 12px Glegoo", + "12px Roboto Condensed", + "bold 12px Roboto Condensed", +]); export default { - components: { - Canvas, - Navbar, - MainContainer, - ImageInput, - LongTextInput, - ShortTextInput, - InputSeparator, - MultipleColorPicker - }, - data () { - const predefinedColors = { - base: { - name: 'Základní barvy', - colors: { - background: COLORS.black, - highlight: COLORS.yellow1, - quotes: COLORS.yellow1, - baseText: COLORS.white, - highlightedText: COLORS.black, - contractedByText: COLORS.gray1 - } - }, - gray: { - name: 'Šedé pozadí', - colors: { - background: COLORS.gray1, - highlight: COLORS.yellow1, - quotes: COLORS.yellow1, - baseText: COLORS.black, - highlightedText: COLORS.black - } - } - } + components: { + Canvas, + Navbar, + MainContainer, + ImageInput, + LongTextInput, + ShortTextInput, + InputSeparator, + MultipleColorPicker, + }, + data() { + const predefinedColors = { + base: { + name: "Základní barvy", + colors: { + background: COLORS.black, + highlight: COLORS.yellow1, + quotes: COLORS.yellow1, + baseText: COLORS.white, + highlightedText: COLORS.black, + contractedByText: COLORS.gray1, + }, + }, + gray: { + name: "Šedé pozadí", + colors: { + background: COLORS.gray1, + highlight: COLORS.yellow1, + quotes: COLORS.yellow1, + baseText: COLORS.black, + highlightedText: COLORS.black, + }, + }, + }; - return { - mainImage: null, - sourceImage: null, - mainText: null, - personName: null, - personPosition: null, - contractedBy: DEFAULT_CONTRACTOR, - logoImage: null, - logoPosition: LOGO_POSITIONS.top_right, - logoOptions: generateLogoPositions( - [ - "top_right", - "top_left", - ] - ), - colorLabels: { - background: 'Pozadí', - highlight: 'Zvýraznění', - quotes: 'Uvozovky', - baseText: 'Text', - highlightedText: 'Zvýrazněný text' - }, - predefinedColors: predefinedColors, - colors: predefinedColors.base.colors, - predefinedLogoImages: generateDefaultLogos('defaultLight'), - predefinedSourceImages: SOURCE_IMAGES, - autoRedraw: false - } - }, - async created () { - await loadCanvasStorage(this) - }, - methods: { - async reloadCanvasProperties () { - const canvasProperties = { - mainImage: this.mainImage, - sourceImage: this.sourceImage, - mainText: this.mainText, - personName: this.personName, - personPosition: this.personPosition, - contractedBy: this.contractedBy, - logoImage: this.logoImage, - logoPosition: this.logoPosition, - colors: this.colors - } + return { + mainImage: null, + sourceImage: null, + mainText: null, + personName: null, + personPosition: null, + contractedBy: DEFAULT_CONTRACTOR, + logoImage: null, + logoPosition: LOGO_POSITIONS.top_right, + logoOptions: generateLogoPositions(["top_right", "top_left"]), + colorLabels: { + background: "Pozadí", + highlight: "Zvýraznění", + quotes: "Uvozovky", + baseText: "Text", + highlightedText: "Zvýrazněný text", + }, + predefinedColors: predefinedColors, + colors: predefinedColors.base.colors, + predefinedLogoImages: generateDefaultLogos("defaultLight"), + predefinedSourceImages: SOURCE_IMAGES, + autoRedraw: false, + }; + }, + async created() { + await loadCanvasStorage(this); + }, + methods: { + async reloadCanvasProperties() { + const canvasProperties = { + mainImage: this.mainImage, + sourceImage: this.sourceImage, + mainText: this.mainText, + personName: this.personName, + personPosition: this.personPosition, + contractedBy: this.contractedBy, + logoImage: this.logoImage, + logoPosition: this.logoPosition, + colors: this.colors, + }; - await this.$refs.canvas.redraw(canvasProperties) + await this.$refs.canvas.redraw(canvasProperties); - delete canvasProperties.colors - setCanvasStorage(canvasProperties) - } + delete canvasProperties.colors; + setCanvasStorage(canvasProperties); }, - mounted () { - this.$watch( - vm => [ - vm.mainImage, - vm.sourceImage, - vm.mainText, - vm.personName, - vm.personPosition, - vm.contractedBy, - vm.logoImage, - vm.logoPosition, - vm.colors - ], - async (value) => { - if (this.autoRedraw) { - await this.reloadCanvasProperties() - } - }, - { - immediate: true, - deep: true - } - ) + }, + mounted() { + this.$watch( + (vm) => [ + vm.mainImage, + vm.sourceImage, + vm.mainText, + vm.personName, + vm.personPosition, + vm.contractedBy, + vm.logoImage, + vm.logoPosition, + vm.colors, + ], + async (value) => { + if (this.autoRedraw) { + await this.reloadCanvasProperties(); + } + }, + { + immediate: true, + deep: true, + }, + ); - this.$watch( - vm => [vm.autoRedraw], - async (value) => { - updateAutoRedrawStorage(this.autoRedraw) + this.$watch( + (vm) => [vm.autoRedraw], + async (value) => { + updateAutoRedrawStorage(this.autoRedraw); - if (this.autoRedraw) { - await this.reloadCanvasProperties() - } - } - ) - } -} + if (this.autoRedraw) { + await this.reloadCanvasProperties(); + } + }, + ); + }, +}; </script> <template> - <header> - <Navbar - :defaultTemplate="TEMPLATES.newspaper_quote_bottom" - ></Navbar> - </header> - <main> - <MainContainer> - <template v-slot:left> - <Canvas - ref="canvas" - :redrawFunction="redraw" - width="2000" - height="2000" - /> - </template> + <header> + <Navbar :defaultTemplate="TEMPLATES.newspaper_quote_bottom"></Navbar> + </header> + <main> + <MainContainer> + <template v-slot:left> + <Canvas + ref="canvas" + :redrawFunction="redraw" + width="2000" + height="2000" + /> + </template> - <template v-slot:right> - <ReloadButton - :parentRefs="$refs" - @click="reloadCanvasProperties" - /> - <AutoReloadCheckbox - v-model="autoRedraw" - /> - <ImageInput - name="Obrázek" - v-model="mainImage" - :important="true" - zIndex="10" - /> - <LongTextInput - name="Hlavní text" - v-model="mainText" - :important="true" - :highlightable="true" - zIndex="9" - /> - <ShortTextInput - name="Jméno osoby" - v-model="personName" - v-model:relatedModel="personPosition" - :predefinedValues=" - [ - { - 'self': 'Ivan Bartoš', - 'related': 'ministr pro místní rozvoj' - }, - { - 'self': 'Olga Richterová', - 'related': 'místopředsedkyně\nPoslanecké sněmovny' - }, - { - 'self': 'Jan Lipavský', - 'related': 'ministr zahraničních věcí' - }, - { - 'self': 'Klára Kocmanová', - 'related': 'pirátská poslankyně' - }, - { - 'self': 'Jakub Michálek', - 'related': 'pirátský poslanec' - }, - { - 'self': 'Michal Šalamoun', - 'related': 'ministr pro legislativu' - } - ] - " - :important="true" - zIndex="8" - /> - <LongTextInput - name="Pozice osoby" - v-model="personPosition" - :important="false" - zIndex="7" - /> - <ImageInput - name="Obrázek zdroje" - v-model="sourceImage" - :important="true" - :predefinedImages="predefinedSourceImages" - :mustSelectPredefinedImage="false" - zIndex="6" - /> + <template v-slot:right> + <ReloadButton :parentRefs="$refs" @click="reloadCanvasProperties" /> + <AutoReloadCheckbox v-model="autoRedraw" /> + <ImageInput + name="Obrázek" + v-model="mainImage" + :important="true" + zIndex="10" + /> + <LongTextInput + name="Hlavní text" + v-model="mainText" + :important="true" + :highlightable="true" + zIndex="9" + /> + <ShortTextInput + name="Jméno osoby" + v-model="personName" + v-model:relatedModel="personPosition" + :predefinedValues="[ + { + self: 'Ivan Bartoš', + related: 'ministr pro místní rozvoj', + }, + { + self: 'Olga Richterová', + related: 'místopředsedkyně\nPoslanecké sněmovny', + }, + { + self: 'Jan Lipavský', + related: 'ministr zahraničních věcí', + }, + { + self: 'Klára Kocmanová', + related: 'pirátská poslankyně', + }, + { + self: 'Jakub Michálek', + related: 'pirátský poslanec', + }, + { + self: 'Michal Šalamoun', + related: 'ministr pro legislativu', + }, + ]" + :important="true" + zIndex="8" + /> + <LongTextInput + name="Pozice osoby" + v-model="personPosition" + :important="false" + zIndex="7" + /> + <ImageInput + name="Obrázek zdroje" + v-model="sourceImage" + :important="true" + :predefinedImages="predefinedSourceImages" + :mustSelectPredefinedImage="false" + zIndex="6" + /> - <InputSeparator /> + <InputSeparator /> - <ImageInput - name="Obrázek loga" - v-model="logoImage" - :important="false" - :predefinedImages="predefinedLogoImages" - :mustSelectPredefinedImage="true" - :disableImageInput="true" - zIndex="5" - /> - <SelectInput - name="Pozice loga" - :options="logoOptions" - v-model="logoPosition" - zIndex="4" - /> + <ImageInput + name="Obrázek loga" + v-model="logoImage" + :important="false" + :predefinedImages="predefinedLogoImages" + :mustSelectPredefinedImage="true" + :disableImageInput="true" + zIndex="5" + /> + <SelectInput + name="Pozice loga" + :options="logoOptions" + v-model="logoPosition" + zIndex="4" + /> - <MultipleColorPicker - name="Barvy" - v-model="colors" - :important="false" - :colorLabels="colorLabels" - :predefinedColors="predefinedColors" - :defaultPredefinedColors="predefinedColors.base" - zIndex="3" - ></MultipleColorPicker> + <MultipleColorPicker + name="Barvy" + v-model="colors" + :important="false" + :colorLabels="colorLabels" + :predefinedColors="predefinedColors" + :defaultPredefinedColors="predefinedColors.base" + zIndex="3" + ></MultipleColorPicker> - <ShortTextInput - name="Zadavatel a zpracovatel" - v-model="contractedBy" - :defaultValue="DEFAULT_CONTRACTOR" - :important="false" - zIndex="2" - /> - </template> - </MainContainer> - </main> + <ShortTextInput + name="Zadavatel a zpracovatel" + v-model="contractedBy" + :defaultValue="DEFAULT_CONTRACTOR" + :important="false" + zIndex="2" + /> + </template> + </MainContainer> + </main> </template> <style> diff --git a/frontend/src/views/newspaper_quote_bottom/canvas.js b/frontend/src/views/newspaper_quote_bottom/canvas.js index 75b85ea4088a850ddc2e29c2cb2dc4fe740920ca..7cd01cf73db89a061ca3277a51ad6eafd05f2e9b 100644 --- a/frontend/src/views/newspaper_quote_bottom/canvas.js +++ b/frontend/src/views/newspaper_quote_bottom/canvas.js @@ -1,567 +1,490 @@ -import { fabric } from 'fabric' -import { clearObjects, sortObjects, transformHighlightedText, checkTextBoxHeight } from '../../components/canvas/utils' -import { PaddedHighlightingTextbox } from '../../components/canvas/textbox' +import { fabric } from "fabric"; +import { + clearObjects, + sortObjects, + transformHighlightedText, + checkTextBoxHeight, +} from "../../components/canvas/utils"; +import { PaddedHighlightingTextbox } from "../../components/canvas/textbox"; -import tearImageURL from '../../assets/template/newspaper_quote/newspaper_tear_top.png' -import quoteImageURL from '../../assets/template/newspaper_quote/quote.svg' +import tearImageURL from "../../assets/template/newspaper_quote/newspaper_tear_top.png"; +import quoteImageURL from "../../assets/template/newspaper_quote/quote.svg"; -let mainTextBox = null -let mainTextBoxBackground = null +let mainTextBox = null; +let mainTextBoxBackground = null; -let personNameText = null -let personInfoSeparator = null -let personPositionText = null -let sourceText = null +let personNameText = null; +let personInfoSeparator = null; +let personPositionText = null; +let sourceText = null; -let contractedByTextbox = null +let contractedByTextbox = null; -let mainImage = null -let logoImage = null -let quoteImage = null -let tearImage = null -let sourceImage = null +let mainImage = null; +let logoImage = null; +let quoteImage = null; +let tearImage = null; +let sourceImage = null; -let leftQuote = null -let rightQuote = null +let leftQuote = null; +let rightQuote = null; -let tear = null -let tearFill = null +let tear = null; +let tearFill = null; -let mainImageSource = null -let previousLogoPosition = null +let mainImageSource = null; +let previousLogoPosition = null; const redraw = async (canvas, options) => { - clearObjects( - [ - mainTextBox, - mainTextBoxBackground, - personInfoSeparator, - personNameText, - personPositionText, - contractedByTextbox, - leftQuote, - rightQuote, - tear, - tearFill - ], - canvas - ) - - canvas.preserveObjectStacking = true - - const textMarginSides = Math.ceil(canvas.width * 0.11) - let mainTextMarginBottom = Math.ceil(canvas.height * 0.07) - const mainTextSize = Math.ceil(canvas.height * 0.043) - const mainTextLineHeight = 1.3 - - const nameTextMarginTop = Math.ceil(canvas.height * 0.025) - const positionTextSideGap = Math.ceil(canvas.width * 0.01) - const positionTextSeparatorWidth = Math.ceil(canvas.width * 0.0035) - const personInfoTextMaxWidth = Math.ceil(canvas.width * 0.5) - - const bottomTextSize = Math.ceil(canvas.height * 0.03) - const additionalContentExtraBottomMargin = Math.ceil(canvas.height * 0.1) - - const contractedByTextSize = Math.ceil(canvas.height * 0.02) - const contractedByTextMaxWidth = Math.ceil(canvas.width * 0.9) - const contractedByTextSidesMargin = Math.ceil(canvas.width * 0.03) - - const logoWidth = Math.ceil(canvas.width * 0.2) - const logoSideMargin = Math.ceil(canvas.width * 0.07) - - const sourceImageHeight = Math.ceil(canvas.height * 0.07) - const sourceImageMarginBottom = Math.ceil(canvas.width * 0.075) - const sourceImageMarginSide = Math.ceil(canvas.width * 0.07) - - const sourceTextMarginSide = Math.ceil(canvas.width * 0.005) - - const quoteHeight = Math.ceil(canvas.height * 0.042) - - const leftQuoteMarginSide = Math.ceil(canvas.width * 0.065) - const leftQuoteMarginSideExtra = Math.ceil(canvas.width * -0.005) - const leftQuoteMarginTop = 0 - - const rightQuoteMarginSide = Math.ceil(canvas.width * 0.0175) - const rightQuoteMarginSideExtra = Math.ceil(canvas.width * 0.005) - const rightQuoteMarginTop = 0 - - const tearMarginBottom = Math.ceil(canvas.height * 0.15) - - if (options.mainText !== null) { - /* BEGIN Name text render */ - - if (options.personName !== null) { - mainTextMarginBottom += additionalContentExtraBottomMargin - - personNameText = new fabric.Text( - options.personName, - { - left: textMarginSides, - top: ( - canvas.height - - mainTextMarginBottom - + nameTextMarginTop - ), - fontFamily: 'Roboto Condensed', - fontSize: bottomTextSize, - fontWeight: 'bold', - fill: options.colors.baseText.value, - selectable: false, - zIndex: 10 - } - ) - - canvas.add(personNameText) - - if (options.personPosition !== null) { - personInfoSeparator = new fabric.Rect({ - left: personNameText.left + personNameText.width + positionTextSideGap, - top: personNameText.top, - width: positionTextSeparatorWidth, - fill: options.colors.baseText.value, - selectable: false, - zIndex: 10 - }) - - canvas.add(personInfoSeparator) - - personPositionText = new fabric.Textbox( - options.personPosition, - { - left: personInfoSeparator.left + personInfoSeparator.width + positionTextSideGap, - top: personNameText.top, - width: personInfoTextMaxWidth - personNameText.width, - fontFamily: 'Roboto Condensed', - fontSize: bottomTextSize, - fill: options.colors.baseText.value, - selectable: false, - zIndex: 10 - } - ) - - checkTextBoxHeight(personPositionText, 2) - - canvas.add(personPositionText) - personInfoSeparator.set({height: personPositionText.height}) - canvas.renderAll() - } - } - - /* END Name text render */ - - - /* BEGIN Source image render */ - - const createNewSourceImage = ( - options.sourceImage !== null - && ( - sourceImage === null - || ( - options.sourceImage !== null - && options.sourceImage !== sourceImage._element - ) - ) - ) - - if (options.sourceImage !== null && options.personName === null) { - mainTextMarginBottom += additionalContentExtraBottomMargin - } - - if (createNewSourceImage) { - canvas.remove(sourceImage) - canvas.remove(sourceText) - - sourceImage = new fabric.Image(options.sourceImage, {}) - sourceImage.scaleToHeight(sourceImageHeight) - sourceImage.set({ - left: ( - canvas.width - - sourceImage.getScaledWidth() - - sourceImageMarginSide - ), - top: ( - canvas.height - - sourceImageHeight - - sourceImageMarginBottom - ), - selectable: false, - zIndex: 10, - }) - - canvas.add(sourceImage) - - - sourceText = new fabric.Text("Zdroj: ", { - fontSize: bottomTextSize, - fill: options.colors.baseText.value, - fontFamily: 'Roboto Condensed', - left: sourceImage.left - sourceTextMarginSide, - top: sourceImage.top, - selectable: false, - zIndex: 10 - }) - - sourceText.set({ - left: sourceText.left - sourceText.width - }) - - canvas.add(sourceText) - } else if (options.sourceImage === null) { - canvas.remove(sourceImage) - canvas.remove(sourceText) - } - - /* END Source image render */ - - - /* BEGIN Main text render */ - - const mainTextWidth = (canvas.width - textMarginSides * 2) - - const highlightedData = transformHighlightedText( - options.mainText, - mainTextSize, - mainTextWidth, - 'Glegoo', - options.colors.highlight.value, - options.colors.highlightedText.value, - {padWhenDiacritics: true} - ) - - mainTextBox = new PaddedHighlightingTextbox( - highlightedData.text, - { - width: canvas.width, - left: textMarginSides, - textAlign: 'left', - fontFamily: 'Glegoo', - fontStyle: 'bold', - fontSize: mainTextSize, - lineHeight: mainTextLineHeight, - fill: options.colors.baseText.value, - styles: highlightedData.styles, - selectable: false, - highlightPadding: (canvas.height * 0.008), - zIndex: 10, - } - ) - - checkTextBoxHeight(mainTextBox, 4) - - canvas.add(mainTextBox) - - const mainTextBoxTop = ( - canvas.height - - mainTextBox.height - - mainTextMarginBottom - ) - - mainTextBox.top = ( - mainTextBoxTop - - highlightedData.paddingBottom - ) - - canvas.renderAll() - - /* END Main text render */ - - - /* BEGIN Left quote render */ - - // Load on first render - if (quoteImage === null) { - quoteImage = new Image() - - const quoteImageLoadPromise = new Promise( - resolve => { - quoteImage.onload = () => { - resolve() - } - } - ) - - quoteImage.src = quoteImageURL - - await quoteImageLoadPromise - } - - leftQuote = new fabric.Image(quoteImage, { - selectable: false, - zIndex: 10 - }) - leftQuote.scaleToHeight(quoteHeight) - leftQuote.set({ - left: ( - leftQuoteMarginSide - + ( - (options.mainText[0] === "*") ? - leftQuoteMarginSideExtra : 0 - ) - ), - top: mainTextBoxTop + leftQuoteMarginTop - }) - - canvas.add(leftQuote) - - /* END Left quote render */ - - - /* BEGIN Right quote render */ - - rightQuote = new fabric.Image(quoteImage, { - flipX: true, - selectable: false, - zIndex: 10 - }) - rightQuote.scaleToHeight(quoteHeight) - rightQuote.set({ - left: ( - mainTextBox.left - + mainTextBox.__lineWidths[mainTextBox.__lineWidths.length - 1] - + rightQuoteMarginSide - + ( - (options.mainText[options.mainText.length - 1] === "*") ? - rightQuoteMarginSideExtra : 0 - ) - ), - top: ( - mainTextBoxTop - + mainTextBox.__lineHeights.slice(0, -1).reduce((partialSum, a) => partialSum + a, 0) - + rightQuoteMarginTop - ), - }) - - canvas.add(rightQuote) - - /* END Right quote render */ - - - /* BEGIN Tear render */ - - // Load on first render - if (tearImage === null) { - tearImage = new Image() - - const tearImageLoadPromise = new Promise( - resolve => { - tearImage.onload = () => { - resolve() - } - } - ) - - tearImage.src = tearImageURL - - await tearImageLoadPromise - } - - tear = new fabric.Image(tearImage, { - selectable: false, - zIndex: 11 - }) - tear.scaleToWidth(canvas.width) - tear.set({ - top: ( - mainTextBoxTop - - tearMarginBottom - ), - left: 0, - }) - - canvas.add(tear) - - /* END Tear render */ - - - /* BEGIN Tear fill render */ - - tearFill = new fabric.Polygon( - [ - {x: -canvas.width * 0.01, y: tear.top + (canvas.height * 0.05)}, - // Hacky seam fix - {x: canvas.width * 0.1, y: tear.top + (canvas.height * 0.025)}, - {x: canvas.width * 0.15, y: tear.top + (canvas.height * 0.02)}, - {x: canvas.width * 0.2, y: tear.top + (canvas.height * 0.033)}, - {x: canvas.width * 0.3, y: tear.top + (canvas.height * 0.065)}, - {x: canvas.width * 0.4, y: tear.top + (canvas.height * 0.06)}, - {x: canvas.width * 0.5, y: tear.top + (canvas.height * 0.07)}, - {x: canvas.width * 0.53, y: tear.top + (canvas.height * 0.065)}, - {x: canvas.width * 0.6, y: tear.top + (canvas.height * 0.08)}, - {x: canvas.width * 0.75, y: tear.top + (canvas.height * 0.05)}, - {x: canvas.width * 0.85, y: tear.top + (canvas.height * 0.08)}, - {x: canvas.width, y: tear.top + (canvas.height * 0.12)}, - {x: canvas.width, y: tear.top}, - {x: canvas.width, y: mainTextBoxTop}, - {x: -canvas.width * 0.01, y: mainTextBoxTop} - // Hacky seam fix - ], - { - top: tear.top + (canvas.height * 0.005), // Hacky seam fix - left: -(canvas.width * 0.005), - fill: options.colors.background.value, - selectable: false, - zIndex: 9 - } - ) - - canvas.add(tearFill) - - /* END Tear fill render */ - - - /* BEGIN Main text background render */ - - const backgroundHeight = ( - canvas.height - - mainTextBoxTop - ) - - mainTextBoxBackground = new fabric.Rect( - { - width: canvas.width * 1.01, - height: backgroundHeight, - left: -(canvas.width * 0.005), - top: mainTextBoxTop, - fill: options.colors.background.value, - selectable: false, - zIndex: 9 - } - ) - - canvas.add(mainTextBoxBackground) - - /* END Main text background render */ + clearObjects( + [ + mainTextBox, + mainTextBoxBackground, + personInfoSeparator, + personNameText, + personPositionText, + contractedByTextbox, + leftQuote, + rightQuote, + tear, + tearFill, + ], + canvas, + ); + + canvas.preserveObjectStacking = true; + + const textMarginSides = Math.ceil(canvas.width * 0.11); + let mainTextMarginBottom = Math.ceil(canvas.height * 0.07); + const mainTextSize = Math.ceil(canvas.height * 0.043); + const mainTextLineHeight = 1.3; + + const nameTextMarginTop = Math.ceil(canvas.height * 0.025); + const positionTextSideGap = Math.ceil(canvas.width * 0.01); + const positionTextSeparatorWidth = Math.ceil(canvas.width * 0.0035); + const personInfoTextMaxWidth = Math.ceil(canvas.width * 0.5); + + const bottomTextSize = Math.ceil(canvas.height * 0.03); + const additionalContentExtraBottomMargin = Math.ceil(canvas.height * 0.1); + + const contractedByTextSize = Math.ceil(canvas.height * 0.02); + const contractedByTextMaxWidth = Math.ceil(canvas.width * 0.9); + const contractedByTextSidesMargin = Math.ceil(canvas.width * 0.03); + + const logoWidth = Math.ceil(canvas.width * 0.2); + const logoSideMargin = Math.ceil(canvas.width * 0.07); + + const sourceImageHeight = Math.ceil(canvas.height * 0.07); + const sourceImageMarginBottom = Math.ceil(canvas.width * 0.075); + const sourceImageMarginSide = Math.ceil(canvas.width * 0.07); + + const sourceTextMarginSide = Math.ceil(canvas.width * 0.005); + + const quoteHeight = Math.ceil(canvas.height * 0.042); + + const leftQuoteMarginSide = Math.ceil(canvas.width * 0.065); + const leftQuoteMarginSideExtra = Math.ceil(canvas.width * -0.005); + const leftQuoteMarginTop = 0; + + const rightQuoteMarginSide = Math.ceil(canvas.width * 0.0175); + const rightQuoteMarginSideExtra = Math.ceil(canvas.width * 0.005); + const rightQuoteMarginTop = 0; + + const tearMarginBottom = Math.ceil(canvas.height * 0.15); + + if (options.mainText !== null) { + /* BEGIN Name text render */ + + if (options.personName !== null) { + mainTextMarginBottom += additionalContentExtraBottomMargin; + + personNameText = new fabric.Text(options.personName, { + left: textMarginSides, + top: canvas.height - mainTextMarginBottom + nameTextMarginTop, + fontFamily: "Roboto Condensed", + fontSize: bottomTextSize, + fontWeight: "bold", + fill: options.colors.baseText.value, + selectable: false, + zIndex: 10, + }); + + canvas.add(personNameText); + + if (options.personPosition !== null) { + personInfoSeparator = new fabric.Rect({ + left: + personNameText.left + personNameText.width + positionTextSideGap, + top: personNameText.top, + width: positionTextSeparatorWidth, + fill: options.colors.baseText.value, + selectable: false, + zIndex: 10, + }); + + canvas.add(personInfoSeparator); + + personPositionText = new fabric.Textbox(options.personPosition, { + left: + personInfoSeparator.left + + personInfoSeparator.width + + positionTextSideGap, + top: personNameText.top, + width: personInfoTextMaxWidth - personNameText.width, + fontFamily: "Roboto Condensed", + fontSize: bottomTextSize, + fill: options.colors.baseText.value, + selectable: false, + zIndex: 10, + }); + + checkTextBoxHeight(personPositionText, 2); + + canvas.add(personPositionText); + personInfoSeparator.set({ height: personPositionText.height }); + canvas.renderAll(); + } } + /* END Name text render */ - /* BEGIN Main image render */ - - 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, - { - left: 0, - top: 0, - zIndex: 0 - } - ) - - mainImage.controls = { - ...fabric.Image.prototype.controls, - mtr: new fabric.Control({ visible: false }) - } - - if (mainImage.width >= mainImage.height) { - mainImage.scaleToHeight(canvas.height) - } else { - mainImage.scaleToWidth(canvas.width) - } - - canvas.add(mainImage) - mainImageSource = options.mainImage.src - // canvas.centerObject(mainImage) - } else if ( - mainImage !== null - && options.mainImage === null - ) { - canvas.remove(mainImage) + /* BEGIN Source image render */ + + const createNewSourceImage = + options.sourceImage !== null && + (sourceImage === null || + (options.sourceImage !== null && + options.sourceImage !== sourceImage._element)); + + if (options.sourceImage !== null && options.personName === null) { + mainTextMarginBottom += additionalContentExtraBottomMargin; + } + + if (createNewSourceImage) { + canvas.remove(sourceImage); + canvas.remove(sourceText); + + sourceImage = new fabric.Image(options.sourceImage, {}); + sourceImage.scaleToHeight(sourceImageHeight); + sourceImage.set({ + left: + canvas.width - sourceImage.getScaledWidth() - sourceImageMarginSide, + top: canvas.height - sourceImageHeight - sourceImageMarginBottom, + selectable: false, + zIndex: 10, + }); + + canvas.add(sourceImage); + + sourceText = new fabric.Text("Zdroj: ", { + fontSize: bottomTextSize, + fill: options.colors.baseText.value, + fontFamily: "Roboto Condensed", + left: sourceImage.left - sourceTextMarginSide, + top: sourceImage.top, + selectable: false, + zIndex: 10, + }); + + sourceText.set({ + left: sourceText.left - sourceText.width, + }); + + canvas.add(sourceText); + } else if (options.sourceImage === null) { + canvas.remove(sourceImage); + canvas.remove(sourceText); + } + + /* END Source image render */ + + /* BEGIN Main text render */ + + const mainTextWidth = canvas.width - textMarginSides * 2; + + const highlightedData = transformHighlightedText( + options.mainText, + mainTextSize, + mainTextWidth, + "Glegoo", + options.colors.highlight.value, + options.colors.highlightedText.value, + { padWhenDiacritics: true }, + ); + + mainTextBox = new PaddedHighlightingTextbox(highlightedData.text, { + width: canvas.width, + left: textMarginSides, + textAlign: "left", + fontFamily: "Glegoo", + fontStyle: "bold", + fontSize: mainTextSize, + lineHeight: mainTextLineHeight, + fill: options.colors.baseText.value, + styles: highlightedData.styles, + selectable: false, + highlightPadding: canvas.height * 0.008, + zIndex: 10, + }); + + checkTextBoxHeight(mainTextBox, 4); + + canvas.add(mainTextBox); + + const mainTextBoxTop = + canvas.height - mainTextBox.height - mainTextMarginBottom; + + mainTextBox.top = mainTextBoxTop - highlightedData.paddingBottom; + + canvas.renderAll(); + + /* END Main text render */ + + /* BEGIN Left quote render */ + + // Load on first render + if (quoteImage === null) { + quoteImage = new Image(); + + const quoteImageLoadPromise = new Promise((resolve) => { + quoteImage.onload = () => { + resolve(); + }; + }); + + quoteImage.src = quoteImageURL; + + await quoteImageLoadPromise; + } + + leftQuote = new fabric.Image(quoteImage, { + selectable: false, + zIndex: 10, + }); + leftQuote.scaleToHeight(quoteHeight); + leftQuote.set({ + left: + leftQuoteMarginSide + + (options.mainText[0] === "*" ? leftQuoteMarginSideExtra : 0), + top: mainTextBoxTop + leftQuoteMarginTop, + }); + + canvas.add(leftQuote); + + /* END Left quote render */ + + /* BEGIN Right quote render */ + + rightQuote = new fabric.Image(quoteImage, { + flipX: true, + selectable: false, + zIndex: 10, + }); + rightQuote.scaleToHeight(quoteHeight); + rightQuote.set({ + left: + mainTextBox.left + + mainTextBox.__lineWidths[mainTextBox.__lineWidths.length - 1] + + rightQuoteMarginSide + + (options.mainText[options.mainText.length - 1] === "*" + ? rightQuoteMarginSideExtra + : 0), + top: + mainTextBoxTop + + mainTextBox.__lineHeights + .slice(0, -1) + .reduce((partialSum, a) => partialSum + a, 0) + + rightQuoteMarginTop, + }); + + canvas.add(rightQuote); + + /* END Right quote render */ + + /* BEGIN Tear render */ + + // Load on first render + if (tearImage === null) { + tearImage = new Image(); + + const tearImageLoadPromise = new Promise((resolve) => { + tearImage.onload = () => { + resolve(); + }; + }); + + tearImage.src = tearImageURL; + + await tearImageLoadPromise; } - /* END Main image 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) + tear = new fabric.Image(tearImage, { + selectable: false, + zIndex: 11, + }); + tear.scaleToWidth(canvas.width); + tear.set({ + top: mainTextBoxTop - tearMarginBottom, + left: 0, + }); + + canvas.add(tear); + + /* END Tear render */ + + /* BEGIN Tear fill render */ + + tearFill = new fabric.Polygon( + [ + { x: -canvas.width * 0.01, y: tear.top + canvas.height * 0.05 }, + // Hacky seam fix + { x: canvas.width * 0.1, y: tear.top + canvas.height * 0.025 }, + { x: canvas.width * 0.15, y: tear.top + canvas.height * 0.02 }, + { x: canvas.width * 0.2, y: tear.top + canvas.height * 0.033 }, + { x: canvas.width * 0.3, y: tear.top + canvas.height * 0.065 }, + { x: canvas.width * 0.4, y: tear.top + canvas.height * 0.06 }, + { x: canvas.width * 0.5, y: tear.top + canvas.height * 0.07 }, + { x: canvas.width * 0.53, y: tear.top + canvas.height * 0.065 }, + { x: canvas.width * 0.6, y: tear.top + canvas.height * 0.08 }, + { x: canvas.width * 0.75, y: tear.top + canvas.height * 0.05 }, + { x: canvas.width * 0.85, y: tear.top + canvas.height * 0.08 }, + { x: canvas.width, y: tear.top + canvas.height * 0.12 }, + { x: canvas.width, y: tear.top }, + { x: canvas.width, y: mainTextBoxTop }, + { x: -canvas.width * 0.01, y: mainTextBoxTop }, + // Hacky seam fix + ], + { + top: tear.top + canvas.height * 0.005, // Hacky seam fix + left: -(canvas.width * 0.005), + fill: options.colors.background.value, + selectable: false, + zIndex: 9, + }, + ); + + canvas.add(tearFill); + + /* END Tear fill render */ + + /* BEGIN Main text background render */ + + const backgroundHeight = canvas.height - mainTextBoxTop; + + mainTextBoxBackground = new fabric.Rect({ + width: canvas.width * 1.01, + height: backgroundHeight, + left: -(canvas.width * 0.005), + top: mainTextBoxTop, + fill: options.colors.background.value, + selectable: false, + zIndex: 9, + }); + + canvas.add(mainTextBoxBackground); + + /* END Main text background render */ + } + + /* BEGIN Main image render */ + + if ( + options.mainImage !== null && + (!canvas.contains(mainImage) || + mainImage === null || + options.mainImage.src !== mainImageSource) + ) { + if (mainImage !== null) { + canvas.remove(mainImage); } - /* END Logo render */ - - - /* BEGIN Contracted by render */ - - if (options.contractedBy !== null) { - contractedByTextbox = new fabric.Textbox( - options.contractedBy, - { - left: canvas.width - contractedByTextMaxWidth - contractedByTextSidesMargin, - top: ( - canvas.height - - contractedByTextSidesMargin - - contractedByTextSize - ), - width: contractedByTextMaxWidth, - fontFamily: 'Roboto Condensed', - fontSize: contractedByTextSize, - textAlign: 'right', - fill: options.colors.contractedByText.value, - selectable: false, - zIndex: 10 - } - ) - - checkTextBoxHeight(contractedByTextbox, 1) - - canvas.add(contractedByTextbox) + mainImage = new fabric.Image(options.mainImage, { + left: 0, + top: 0, + zIndex: 0, + }); + + mainImage.controls = { + ...fabric.Image.prototype.controls, + mtr: new fabric.Control({ visible: false }), + }; + + if (mainImage.width >= mainImage.height) { + mainImage.scaleToHeight(canvas.height); + } else { + mainImage.scaleToWidth(canvas.width); } - /* END Contracted by render */ + canvas.add(mainImage); + mainImageSource = options.mainImage.src; + // canvas.centerObject(mainImage) + } else if (mainImage !== null && options.mainImage === null) { + canvas.remove(mainImage); + } + + /* END Main image 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 Contracted by render */ + + if (options.contractedBy !== null) { + contractedByTextbox = new fabric.Textbox(options.contractedBy, { + left: + canvas.width - contractedByTextMaxWidth - contractedByTextSidesMargin, + top: canvas.height - contractedByTextSidesMargin - contractedByTextSize, + width: contractedByTextMaxWidth, + fontFamily: "Roboto Condensed", + fontSize: contractedByTextSize, + textAlign: "right", + fill: options.colors.contractedByText.value, + selectable: false, + zIndex: 10, + }); + + checkTextBoxHeight(contractedByTextbox, 1); + + canvas.add(contractedByTextbox); + } + /* END Contracted by render */ - sortObjects(canvas) -} + sortObjects(canvas); +}; -export default redraw +export default redraw; diff --git a/frontend/src/views/newspaper_quote_middle/NewspaperQuoteMiddle.vue b/frontend/src/views/newspaper_quote_middle/NewspaperQuoteMiddle.vue index 6cc011f07643a47fec1c66a90bf745c9d33be7c4..20f01241566f9cc200fe9f55bcb15affbcb57466 100644 --- a/frontend/src/views/newspaper_quote_middle/NewspaperQuoteMiddle.vue +++ b/frontend/src/views/newspaper_quote_middle/NewspaperQuoteMiddle.vue @@ -1,260 +1,260 @@ <script setup> -import { watch, ref } from 'vue' +import { watch, ref } from "vue"; -import COLORS from '../../colors' -import PEOPLE from '../../people' -import TEMPLATES from '../../templates' -import DEFAULT_CONTRACTOR from '../../contractors' -import { generateDefaultLogos, LOGO_POSITIONS, generateLogoPositions } from '../../logos' -import { SOURCE_IMAGES } from '../utils/newspaper_quotes' -import { loadFonts, loadCanvasStorage, setCanvasStorage, updateAutoRedrawStorage } from '../../utils' +import COLORS from "../../colors"; +import PEOPLE from "../../people"; +import TEMPLATES from "../../templates"; +import DEFAULT_CONTRACTOR from "../../contractors"; +import { + generateDefaultLogos, + LOGO_POSITIONS, + generateLogoPositions, +} from "../../logos"; +import { SOURCE_IMAGES } from "../utils/newspaper_quotes"; +import { + loadFonts, + loadCanvasStorage, + setCanvasStorage, + updateAutoRedrawStorage, +} from "../../utils"; -import Canvas from '../../components/canvas/Canvas.vue' -import redraw from './canvas'; +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 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' +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 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([ - '700 12px Glegoo', - '12px Roboto Condensed', - 'bold 12px Roboto Condensed' -]) + "700 12px Glegoo", + "12px Roboto Condensed", + "bold 12px Roboto Condensed", +]); export default { - components: { - Canvas, - Navbar, - MainContainer, - ImageInput, - LongTextInput, - ShortTextInput, - InputSeparator, - MultipleColorPicker - }, - data () { - const predefinedColors = { - base: { - name: 'Základní barvy', - colors: { - background: COLORS.gray1, - foreground: COLORS.black, - highlight: COLORS.yellow1, - quotes: COLORS.yellow1, - baseText: COLORS.white, - highlightedText: COLORS.black, - sourceText: COLORS.black, - contractedByText: COLORS.black - } - }, - gray: { - name: 'Šedé popředí', - colors: { - background: COLORS.black, - foreground: COLORS.gray1, - highlight: COLORS.yellow1, - quotes: COLORS.yellow1, - baseText: COLORS.black, - highlightedText: COLORS.black, - sourceText: COLORS.white, - contractedByText: COLORS.gray1 - } - } - } + components: { + Canvas, + Navbar, + MainContainer, + ImageInput, + LongTextInput, + ShortTextInput, + InputSeparator, + MultipleColorPicker, + }, + data() { + const predefinedColors = { + base: { + name: "Základní barvy", + colors: { + background: COLORS.gray1, + foreground: COLORS.black, + highlight: COLORS.yellow1, + quotes: COLORS.yellow1, + baseText: COLORS.white, + highlightedText: COLORS.black, + sourceText: COLORS.black, + contractedByText: COLORS.black, + }, + }, + gray: { + name: "Šedé popředí", + colors: { + background: COLORS.black, + foreground: COLORS.gray1, + highlight: COLORS.yellow1, + quotes: COLORS.yellow1, + baseText: COLORS.black, + highlightedText: COLORS.black, + sourceText: COLORS.white, + contractedByText: COLORS.gray1, + }, + }, + }; - return { - sourceImage: null, - mainText: null, - personName: null, - personPosition: null, - contractedBy: DEFAULT_CONTRACTOR, - logoImage: null, - logoPosition: LOGO_POSITIONS.top_right, - logoOptions: generateLogoPositions( - [ - "top_right", - "top_left", - "bottom_left", - ] - ), - colorLabels: { - background: 'Pozadí', - foreground: 'Popředí', - highlight: 'Zvýraznění', - quotes: 'Uvozovky', - baseText: 'Text', - highlightedText: 'Zvýrazněný text', - sourceText: 'Text označující zdroj' - }, - predefinedColors: predefinedColors, - colors: predefinedColors.base.colors, - predefinedLogoImages: generateDefaultLogos('defaultDark'), - predefinedSourceImages: SOURCE_IMAGES, - autoRedraw: false - } - }, - async created () { - await loadCanvasStorage(this) - }, - methods: { - async reloadCanvasProperties () { - const canvasProperties = { - sourceImage: this.sourceImage, - mainText: this.mainText, - personName: this.personName, - personPosition: this.personPosition, - contractedBy: this.contractedBy, - logoImage: this.logoImage, - logoPosition: this.logoPosition, - colors: this.colors - } + return { + sourceImage: null, + mainText: null, + personName: null, + personPosition: null, + contractedBy: DEFAULT_CONTRACTOR, + logoImage: null, + logoPosition: LOGO_POSITIONS.top_right, + logoOptions: generateLogoPositions([ + "top_right", + "top_left", + "bottom_left", + ]), + colorLabels: { + background: "Pozadí", + foreground: "Popředí", + highlight: "Zvýraznění", + quotes: "Uvozovky", + baseText: "Text", + highlightedText: "Zvýrazněný text", + sourceText: "Text označující zdroj", + }, + predefinedColors: predefinedColors, + colors: predefinedColors.base.colors, + predefinedLogoImages: generateDefaultLogos("defaultDark"), + predefinedSourceImages: SOURCE_IMAGES, + autoRedraw: false, + }; + }, + async created() { + await loadCanvasStorage(this); + }, + methods: { + async reloadCanvasProperties() { + const canvasProperties = { + sourceImage: this.sourceImage, + mainText: this.mainText, + personName: this.personName, + personPosition: this.personPosition, + contractedBy: this.contractedBy, + logoImage: this.logoImage, + logoPosition: this.logoPosition, + colors: this.colors, + }; - await this.$refs.canvas.redraw(canvasProperties) + await this.$refs.canvas.redraw(canvasProperties); - delete canvasProperties.colors - setCanvasStorage(canvasProperties) - } + delete canvasProperties.colors; + setCanvasStorage(canvasProperties); }, - mounted () { - this.$watch( - vm => [ - vm.sourceImage, - vm.mainText, - vm.personName, - vm.personPosition, - vm.contractedBy, - vm.logoImage, - vm.logoPosition, - vm.colors - ], - async (value) => { - if (this.autoRedraw) { - await this.reloadCanvasProperties() - } - }, - { - immediate: true, - deep: true - } - ) + }, + mounted() { + this.$watch( + (vm) => [ + vm.sourceImage, + vm.mainText, + vm.personName, + vm.personPosition, + vm.contractedBy, + vm.logoImage, + vm.logoPosition, + vm.colors, + ], + async (value) => { + if (this.autoRedraw) { + await this.reloadCanvasProperties(); + } + }, + { + immediate: true, + deep: true, + }, + ); - this.$watch( - vm => [vm.autoRedraw], - async (value) => { - updateAutoRedrawStorage(this.autoRedraw) + this.$watch( + (vm) => [vm.autoRedraw], + async (value) => { + updateAutoRedrawStorage(this.autoRedraw); - if (this.autoRedraw) { - await this.reloadCanvasProperties() - } - } - ) - } -} + if (this.autoRedraw) { + await this.reloadCanvasProperties(); + } + }, + ); + }, +}; </script> <template> - <header> - <Navbar - :defaultTemplate="TEMPLATES.newspaper_quote_middle" - ></Navbar> - </header> - <main> - <MainContainer> - <template v-slot:left> - <Canvas - ref="canvas" - :redrawFunction="redraw" - width="2000" - height="2000" - /> - </template> + <header> + <Navbar :defaultTemplate="TEMPLATES.newspaper_quote_middle"></Navbar> + </header> + <main> + <MainContainer> + <template v-slot:left> + <Canvas + ref="canvas" + :redrawFunction="redraw" + width="2000" + height="2000" + /> + </template> - <template v-slot:right> - <ReloadButton - :parentRefs="$refs" - @click="reloadCanvasProperties" - /> - <AutoReloadCheckbox - v-model="autoRedraw" - /> - <LongTextInput - name="Hlavní text" - v-model="mainText" - :important="true" - :highlightable="true" - zIndex="10" - /> - <ShortTextInput - name="Jméno osoby" - v-model="personName" - v-model:relatedModel="personPosition" - :predefinedValues="PEOPLE" - :important="true" - zIndex="9" - /> - <ShortTextInput - name="Pozice osoby" - v-model="personPosition" - :important="false" - zIndex="8" - /> - <ImageInput - name="Obrázek zdroje" - v-model="sourceImage" - :important="true" - :predefinedImages="predefinedSourceImages" - :mustSelectPredefinedImage="false" - zIndex="7" - /> + <template v-slot:right> + <ReloadButton :parentRefs="$refs" @click="reloadCanvasProperties" /> + <AutoReloadCheckbox v-model="autoRedraw" /> + <LongTextInput + name="Hlavní text" + v-model="mainText" + :important="true" + :highlightable="true" + zIndex="10" + /> + <ShortTextInput + name="Jméno osoby" + v-model="personName" + v-model:relatedModel="personPosition" + :predefinedValues="PEOPLE" + :important="true" + zIndex="9" + /> + <ShortTextInput + name="Pozice osoby" + v-model="personPosition" + :important="false" + zIndex="8" + /> + <ImageInput + name="Obrázek zdroje" + v-model="sourceImage" + :important="true" + :predefinedImages="predefinedSourceImages" + :mustSelectPredefinedImage="false" + zIndex="7" + /> - <InputSeparator /> + <InputSeparator /> - <ImageInput - name="Obrázek loga" - v-model="logoImage" - :important="false" - :predefinedImages="predefinedLogoImages" - :mustSelectPredefinedImage="true" - :disableImageInput="true" - zIndex="6" - /> - <SelectInput - name="Pozice loga" - :options="logoOptions" - v-model="logoPosition" - zIndex="5" - /> + <ImageInput + name="Obrázek loga" + v-model="logoImage" + :important="false" + :predefinedImages="predefinedLogoImages" + :mustSelectPredefinedImage="true" + :disableImageInput="true" + zIndex="6" + /> + <SelectInput + name="Pozice loga" + :options="logoOptions" + v-model="logoPosition" + zIndex="5" + /> - <MultipleColorPicker - name="Barvy" - v-model="colors" - :important="false" - :colorLabels="colorLabels" - :predefinedColors="predefinedColors" - :defaultPredefinedColors="predefinedColors.base" - zIndex="4" - ></MultipleColorPicker> + <MultipleColorPicker + name="Barvy" + v-model="colors" + :important="false" + :colorLabels="colorLabels" + :predefinedColors="predefinedColors" + :defaultPredefinedColors="predefinedColors.base" + zIndex="4" + ></MultipleColorPicker> - <ShortTextInput - name="Zadavatel a zpracovatel" - v-model="contractedBy" - :defaultValue="DEFAULT_CONTRACTOR" - :important="false" - zIndex="3" - /> - </template> - </MainContainer> - </main> + <ShortTextInput + name="Zadavatel a zpracovatel" + v-model="contractedBy" + :defaultValue="DEFAULT_CONTRACTOR" + :important="false" + zIndex="3" + /> + </template> + </MainContainer> + </main> </template> <style> diff --git a/frontend/src/views/newspaper_quote_middle/canvas.js b/frontend/src/views/newspaper_quote_middle/canvas.js index 877abda3b33cfcd6db8d227aaa5d24edd469db61..c33bf9d252e7bba11d220016daae08e9b2e160ed 100644 --- a/frontend/src/views/newspaper_quote_middle/canvas.js +++ b/frontend/src/views/newspaper_quote_middle/canvas.js @@ -1,639 +1,568 @@ -import { fabric } from 'fabric' -import { clearObjects, sortObjects, transformHighlightedText, checkTextBoxHeight } from '../../components/canvas/utils' -import { PaddedHighlightingTextbox } from '../../components/canvas/textbox' +import { fabric } from "fabric"; +import { + clearObjects, + sortObjects, + transformHighlightedText, + checkTextBoxHeight, +} from "../../components/canvas/utils"; +import { PaddedHighlightingTextbox } from "../../components/canvas/textbox"; + +import topTearImageURL from "../../assets/template/newspaper_quote/newspaper_tear_top.png"; +import bottomTearImageURL from "../../assets/template/newspaper_quote_middle/newspaper_tear_bottom.png"; +import quoteImageURL from "../../assets/template/newspaper_quote/quote.svg"; + +let mainTextBox = null; +let mainTextBoxForeground = null; + +let personNameText = null; +let personInfoSeparator = null; +let personPositionText = null; +let sourceText = null; + +let contractedByTextbox = null; + +let logoImage = null; +let quoteImage = null; +let topTearImage = null; +let bottomTearImage = null; +let sourceImage = null; + +let leftQuote = null; +let rightQuote = null; + +let topTear = null; +let bottomTear = null; +let topTearFill = null; +let bottomTearFill = null; + +let background = null; +let previousLogoPosition = null; -import topTearImageURL from '../../assets/template/newspaper_quote/newspaper_tear_top.png' -import bottomTearImageURL from '../../assets/template/newspaper_quote_middle/newspaper_tear_bottom.png' -import quoteImageURL from '../../assets/template/newspaper_quote/quote.svg' +const redraw = async (canvas, options) => { + clearObjects( + [ + background, + mainTextBox, + mainTextBoxForeground, + personNameText, + personInfoSeparator, + personPositionText, + contractedByTextbox, + leftQuote, + rightQuote, + topTear, + topTearFill, + bottomTear, + bottomTearFill, + ], + canvas, + ); + + canvas.preserveObjectStacking = true; + + const textMarginSides = Math.ceil(canvas.width * 0.11); + let mainTextPaddingBottom = Math.ceil(canvas.height * 0); + const mainTextMarginBottom = Math.ceil(canvas.height * 0.4); + const mainTextSize = Math.ceil(canvas.height * 0.043); + const mainTextLineHeight = 1.3; + + const nameTextMarginTop = Math.ceil(canvas.height * 0.025); + const positionTextSideGap = Math.ceil(canvas.width * 0.01); + const positionTextSeparatorWidth = Math.ceil(canvas.width * 0.0035); + + const bottomTextSize = Math.ceil(canvas.height * 0.03); + const additionalContentExtraBottomPadding = Math.ceil(canvas.height * 0.03); + + const contractedByTextSize = Math.ceil(canvas.height * 0.02); + const contractedByTextMaxWidth = Math.ceil(canvas.width * 0.9); + const contractedByTextSidesMargin = Math.ceil(canvas.width * 0.03); + + const logoWidth = Math.ceil(canvas.width * 0.2); + const logoSideMargin = Math.ceil(canvas.width * 0.07); + + const sourceImageHeight = Math.ceil(canvas.height * 0.07); + const sourceImageMarginTop = Math.ceil(canvas.width * 0.07); + const sourceImageMarginSide = Math.ceil(canvas.width * 0.14); + + const sourceTextMarginSide = Math.ceil(canvas.width * 0.005); + + const quoteHeight = Math.ceil(canvas.height * 0.042); + + const leftQuoteMarginSide = Math.ceil(canvas.width * 0.065); + const leftQuoteMarginSideExtra = Math.ceil(canvas.width * -0.005); + const leftQuoteMarginTop = 0; + + const rightQuoteMarginSide = Math.ceil(canvas.width * 0.0175); + const rightQuoteMarginSideExtra = Math.ceil(canvas.width * 0.005); + const rightQuoteMarginTop = 0; + + const topTearMarginBottom = Math.ceil(canvas.height * 0.225); + const bottomTearMarginTop = Math.ceil(canvas.height * 0.125); + + /* BEGIN Background render */ + + background = new fabric.Rect({ + width: canvas.width * 1.1, + height: canvas.height * 1.1, + top: -20, // FIXME: Why???? Fabric.js, what are you trying to tell me?! + left: -20, + fill: options.colors.background.value, + selectable: false, + zIndex: 0, + }); + + canvas.add(background); + + /* END Foreground render */ + + if (options.mainText !== null) { + /* BEGIN Name text render */ + + if (options.personName !== null) { + mainTextPaddingBottom += additionalContentExtraBottomPadding; + + let styles = { + 0: {}, + }; + let position = 0; + + for (let position = 0; position < options.personName.length; position++) { + styles[0][position] = { + fontWeight: "bold", + }; + } + + personNameText = new fabric.Text(options.personName, { + left: textMarginSides, + top: + canvas.height - + mainTextPaddingBottom + + nameTextMarginTop - + mainTextMarginBottom, + fontFamily: "Roboto Condensed", + fontSize: bottomTextSize, + styles: styles, + fill: options.colors.baseText.value, + selectable: false, + zIndex: 10, + }); + + canvas.add(personNameText); + + if (options.personPosition !== null) { + personInfoSeparator = new fabric.Rect({ + left: + personNameText.left + personNameText.width + positionTextSideGap, + top: personNameText.top, + width: positionTextSeparatorWidth, + fill: options.colors.baseText.value, + selectable: false, + zIndex: 10, + }); + + canvas.add(personInfoSeparator); + + personPositionText = new fabric.Text(options.personPosition, { + left: + personInfoSeparator.left + + personInfoSeparator.width + + positionTextSideGap, + top: personNameText.top, + fontFamily: "Roboto Condensed", + fontSize: bottomTextSize, + fill: options.colors.baseText.value, + selectable: false, + zIndex: 10, + }); + + checkTextBoxHeight(personPositionText, 2); + + canvas.add(personPositionText); + personInfoSeparator.set({ height: personPositionText.height }); + canvas.renderAll(); + } + } -let mainTextBox = null -let mainTextBoxForeground = null + /* END Name text render */ -let personNameText = null -let personInfoSeparator = null -let personPositionText = null -let sourceText = null + /* BEGIN Main text render */ -let contractedByTextbox = null + const mainTextWidth = canvas.width - textMarginSides * 2; -let logoImage = null -let quoteImage = null -let topTearImage = null -let bottomTearImage = null -let sourceImage = null + const highlightedData = transformHighlightedText( + options.mainText, + mainTextSize, + mainTextWidth, + "Glegoo", + options.colors.highlight.value, + options.colors.highlightedText.value, + { padWhenDiacritics: true }, + ); -let leftQuote = null -let rightQuote = null + mainTextBox = new PaddedHighlightingTextbox(highlightedData.text, { + width: canvas.width, + left: textMarginSides, + textAlign: "left", + fontFamily: "Glegoo", + fontStyle: "bold", + fontSize: mainTextSize, + lineHeight: mainTextLineHeight, + fill: options.colors.baseText.value, + styles: highlightedData.styles, + selectable: false, + highlightPadding: canvas.height * 0.008, + zIndex: 10, + }); -let topTear = null -let bottomTear = null -let topTearFill = null -let bottomTearFill = null + checkTextBoxHeight(mainTextBox, 4); -let background = null -let previousLogoPosition = null + canvas.add(mainTextBox); -const redraw = async (canvas, options) => { - clearObjects( - [ - background, - mainTextBox, - mainTextBoxForeground, - personNameText, - personInfoSeparator, - personPositionText, - contractedByTextbox, - leftQuote, - rightQuote, - topTear, - topTearFill, - bottomTear, - bottomTearFill - ], - canvas - ) - - canvas.preserveObjectStacking = true - - const textMarginSides = Math.ceil(canvas.width * 0.11) - let mainTextPaddingBottom = Math.ceil(canvas.height * 0) - const mainTextMarginBottom = Math.ceil(canvas.height * 0.4) - const mainTextSize = Math.ceil(canvas.height * 0.043) - const mainTextLineHeight = 1.3 - - const nameTextMarginTop = Math.ceil(canvas.height * 0.025) - const positionTextSideGap = Math.ceil(canvas.width * 0.01) - const positionTextSeparatorWidth = Math.ceil(canvas.width * 0.0035) - - const bottomTextSize = Math.ceil(canvas.height * 0.03) - const additionalContentExtraBottomPadding = Math.ceil(canvas.height * 0.03) - - const contractedByTextSize = Math.ceil(canvas.height * 0.02) - const contractedByTextMaxWidth = Math.ceil(canvas.width * 0.9) - const contractedByTextSidesMargin = Math.ceil(canvas.width * 0.03) - - const logoWidth = Math.ceil(canvas.width * 0.2) - const logoSideMargin = Math.ceil(canvas.width * 0.07) - - const sourceImageHeight = Math.ceil(canvas.height * 0.07) - const sourceImageMarginTop = Math.ceil(canvas.width * 0.07) - const sourceImageMarginSide = Math.ceil(canvas.width * 0.14) - - const sourceTextMarginSide = Math.ceil(canvas.width * 0.005) - - const quoteHeight = Math.ceil(canvas.height * 0.042) - - const leftQuoteMarginSide = Math.ceil(canvas.width * 0.065) - const leftQuoteMarginSideExtra = Math.ceil(canvas.width * -0.005) - const leftQuoteMarginTop = 0 - - const rightQuoteMarginSide = Math.ceil(canvas.width * 0.0175) - const rightQuoteMarginSideExtra = Math.ceil(canvas.width * 0.005) - const rightQuoteMarginTop = 0 - - const topTearMarginBottom = Math.ceil(canvas.height * 0.225) - const bottomTearMarginTop = Math.ceil(canvas.height * 0.125) - - - /* BEGIN Background render */ - - background = new fabric.Rect({ - width: canvas.width * 1.1, - height: canvas.height * 1.1, - top: -20, // FIXME: Why???? Fabric.js, what are you trying to tell me?! - left: -20, - fill: options.colors.background.value, - selectable: false, - zIndex: 0 - }) - - canvas.add(background) - - /* END Foreground render */ - - - if (options.mainText !== null) { - /* BEGIN Name text render */ - - if (options.personName !== null) { - mainTextPaddingBottom += additionalContentExtraBottomPadding - - let styles = { - 0: {} - } - let position = 0 - - for (let position = 0; position < options.personName.length; position++) { - styles[0][position] = { - fontWeight: 'bold' - } - } - - personNameText = new fabric.Text( - options.personName, - { - left: textMarginSides, - top: ( - canvas.height - - mainTextPaddingBottom - + nameTextMarginTop - - mainTextMarginBottom - ), - fontFamily: 'Roboto Condensed', - fontSize: bottomTextSize, - styles: styles, - fill: options.colors.baseText.value, - selectable: false, - zIndex: 10 - } - ) - - canvas.add(personNameText) - - if (options.personPosition !== null) { - personInfoSeparator = new fabric.Rect({ - left: personNameText.left + personNameText.width + positionTextSideGap, - top: personNameText.top, - width: positionTextSeparatorWidth, - fill: options.colors.baseText.value, - selectable: false, - zIndex: 10 - }) - - canvas.add(personInfoSeparator) - - personPositionText = new fabric.Text( - options.personPosition, - { - left: personInfoSeparator.left + personInfoSeparator.width + positionTextSideGap, - top: personNameText.top, - fontFamily: 'Roboto Condensed', - fontSize: bottomTextSize, - fill: options.colors.baseText.value, - selectable: false, - zIndex: 10 - } - ) - - checkTextBoxHeight(personPositionText, 2) - - canvas.add(personPositionText) - personInfoSeparator.set({height: personPositionText.height}) - canvas.renderAll() - } - } - - /* END Name text render */ - - - /* BEGIN Main text render */ - - const mainTextWidth = (canvas.width - textMarginSides * 2) - - const highlightedData = transformHighlightedText( - options.mainText, - mainTextSize, - mainTextWidth, - 'Glegoo', - options.colors.highlight.value, - options.colors.highlightedText.value, - {padWhenDiacritics: true} - ) - - mainTextBox = new PaddedHighlightingTextbox( - highlightedData.text, - { - width: canvas.width, - left: textMarginSides, - textAlign: 'left', - fontFamily: 'Glegoo', - fontStyle: 'bold', - fontSize: mainTextSize, - lineHeight: mainTextLineHeight, - fill: options.colors.baseText.value, - styles: highlightedData.styles, - selectable: false, - highlightPadding: (canvas.height * 0.008), - zIndex: 10 - } - ) - - checkTextBoxHeight(mainTextBox, 4) - - canvas.add(mainTextBox) - - const mainTextBoxTop = ( - canvas.height - - mainTextBox.height - - mainTextPaddingBottom - - mainTextMarginBottom - ) - - mainTextBox.top = ( - mainTextBoxTop - - highlightedData.paddingBottom - ) - - canvas.renderAll() - - /* END Main text render */ - - - /* BEGIN Left quote render */ - - // Load on first render - if (quoteImage === null) { - quoteImage = new Image() - - const quoteImageLoadPromise = new Promise( - resolve => { - quoteImage.onload = () => { - resolve() - } - } - ) - - quoteImage.src = quoteImageURL - - await quoteImageLoadPromise - } - - leftQuote = new fabric.Image(quoteImage, { - selectable: false, - zIndex: 10 - }) - leftQuote.scaleToHeight(quoteHeight) - leftQuote.set({ - left: ( - leftQuoteMarginSide - + ( - (options.mainText[0] === "*") ? - leftQuoteMarginSideExtra : 0 - ) - ), - top: mainTextBoxTop + leftQuoteMarginTop - }) - - canvas.add(leftQuote) - - /* END Left quote render */ - - - /* BEGIN Right quote render */ - - rightQuote = new fabric.Image(quoteImage, { - flipX: true, - selectable: false, - zIndex: 10 - }) - rightQuote.scaleToHeight(quoteHeight) - rightQuote.set({ - left: ( - mainTextBox.left - + mainTextBox.__lineWidths[mainTextBox.__lineWidths.length - 1] - + rightQuoteMarginSide - + ( - (options.mainText[options.mainText.length - 1] === "*") ? - rightQuoteMarginSideExtra : 0 - ) - ), - top: ( - mainTextBoxTop - + mainTextBox.__lineHeights.slice(0, -1).reduce((partialSum, a) => partialSum + a, 0) - + rightQuoteMarginTop - ), - }) - - canvas.add(rightQuote) - - /* END Right quote render */ - - - /* BEGIN Top tear render */ - - // Load on first render - if (topTearImage === null) { - topTearImage = new Image() - - const tearImageLoadPromise = new Promise( - resolve => { - topTearImage.onload = () => { - resolve() - } - } - ) - - topTearImage.src = topTearImageURL - - await tearImageLoadPromise - } - - topTear = new fabric.Image(topTearImage, { - selectable: false, - zIndex: 11 - }) - topTear.scaleToWidth(canvas.width) - topTear.set({ - top: ( - mainTextBoxTop - - topTearMarginBottom - ), - left: 0, - }) - - canvas.add(topTear) - - /* END Top tear render */ - - - /* BEGIN Top tear fill render */ - - topTearFill = new fabric.Polygon( - [ - {x: -canvas.width * 0.01, y: topTear.top + (canvas.height * 0.05)}, - // Hacky seam fix - {x: canvas.width * 0.1, y: topTear.top + (canvas.height * 0.025)}, - {x: canvas.width * 0.15, y: topTear.top + (canvas.height * 0.02)}, - {x: canvas.width * 0.2, y: topTear.top + (canvas.height * 0.033)}, - {x: canvas.width * 0.3, y: topTear.top + (canvas.height * 0.065)}, - {x: canvas.width * 0.4, y: topTear.top + (canvas.height * 0.06)}, - {x: canvas.width * 0.5, y: topTear.top + (canvas.height * 0.07)}, - {x: canvas.width * 0.53, y: topTear.top + (canvas.height * 0.065)}, - {x: canvas.width * 0.6, y: topTear.top + (canvas.height * 0.08)}, - {x: canvas.width * 0.75, y: topTear.top + (canvas.height * 0.05)}, - {x: canvas.width * 0.85, y: topTear.top + (canvas.height * 0.08)}, - {x: canvas.width, y: topTear.top + (canvas.height * 0.12)}, - {x: canvas.width, y: topTear.top}, - {x: canvas.width, y: mainTextBoxTop}, - {x: -canvas.width * 0.01, y: mainTextBoxTop} - // Hacky seam fix - ], - { - top: topTear.top + (canvas.height * 0.005), // Hacky seam fix - left: -(canvas.width * 0.005), // Hacky seam fix - fill: options.colors.foreground.value, - selectable: false, - zIndex: 9 - } - ) - - canvas.add(topTearFill) - - /* END Top tear fill render */ - - - /* BEGIN Main text foreground render */ - - const foregroundHeight = ( - canvas.height - - mainTextBoxTop - - mainTextMarginBottom - ) - - mainTextBoxForeground = new fabric.Rect( - { - width: canvas.width, - height: foregroundHeight, - left: 0, - top: mainTextBoxTop, - fill: options.colors.foreground.value, - selectable: false, - zIndex: 9 - } - ) - - canvas.add(mainTextBoxForeground) - - /* END Main text foreground render */ - - - /* BEGIN Bottom tear render */ - - const mainTextBoxForegroundBottomY = ( - mainTextBoxForeground.top - + mainTextBoxForeground.height - ) - - // Load on first render - if (bottomTearImage === null) { - bottomTearImage = new Image() - - const tearImageLoadPromise = new Promise( - resolve => { - bottomTearImage.onload = () => { - resolve() - } - } - ) - - bottomTearImage.src = bottomTearImageURL - - await tearImageLoadPromise - } - - bottomTear = new fabric.Image(bottomTearImage, { - selectable: false, - zIndex: 11 - }) - bottomTear.scaleToWidth(canvas.width) - bottomTear.set({ - top: ( - mainTextBoxForegroundBottomY - + bottomTearMarginTop - ), - left: 0, - }) - - canvas.add(bottomTear) - - /* END Bottom tear render */ - - - /* BEGIN Bottom tear fill render */ - - const bottomTearBottom = ( - bottomTear.top - + bottomTear.getScaledHeight() - ) - - bottomTearFill = new fabric.Polygon( - [ - {x: -canvas.width * 0.01, y: mainTextBoxForegroundBottomY}, - // Hacky seam fix - {x: canvas.width, y: mainTextBoxForegroundBottomY}, - {x: canvas.width, y: bottomTearBottom - (canvas.height * 0.063)}, - {x: canvas.width * 0.95, y: bottomTearBottom - (canvas.height * 0.06)}, - {x: canvas.width * 0.92, y: bottomTearBottom - (canvas.height * 0.045)}, - {x: canvas.width * 0.85, y: bottomTearBottom - (canvas.height * 0.07)}, - {x: canvas.width * 0.77, y: bottomTearBottom - (canvas.height * 0.06)}, - {x: canvas.width * 0.7, y: bottomTearBottom - (canvas.height * 0.08)}, - {x: canvas.width * 0.5, y: bottomTearBottom - (canvas.height * 0.005)}, - {x: canvas.width * 0.4, y: bottomTearBottom - (canvas.height * 0.045)}, - {x: canvas.width * 0.25, y: bottomTearBottom - (canvas.height * 0.03)}, - {x: canvas.width * 0.1, y: bottomTearBottom - (canvas.height * 0.085)}, - {x: canvas.width * 0.05, y: bottomTearBottom - (canvas.height * 0.055)}, - {x: -canvas.width * 0.01, y: bottomTearBottom - (canvas.height * 0.07)} - // Hacky seam fix - ], - { - top: mainTextBoxForegroundBottomY - (canvas.height * 0.005), // Hacky seam fix - left: -(canvas.width * 0.005), // Hacky seam fix - fill: options.colors.foreground.value, - selectable: false, - zIndex: 9 - } - ) - - canvas.add(bottomTearFill) - - /* END Bottom tear fill render */ - } + const mainTextBoxTop = + canvas.height - + mainTextBox.height - + mainTextPaddingBottom - + mainTextMarginBottom; + mainTextBox.top = mainTextBoxTop - highlightedData.paddingBottom; - /* 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 if (options.logoPosition.id == "top-left") { - logoImage.set({ - left: logoSideMargin, - top: logoSideMargin, - zIndex: 11, - }) - } else { - logoImage.set({ - left: logoSideMargin, - top: ( - canvas.height - - logoSideMargin - - logoImage.getScaledHeight() - ), - zIndex: 11, - }) - } - - canvas.add(logoImage) - } + canvas.renderAll(); - /* END Logo render */ - - - /* BEGIN Contracted by render */ - - if (options.contractedBy !== null) { - contractedByTextbox = new fabric.Textbox( - options.contractedBy, - { - left: canvas.width - contractedByTextMaxWidth - contractedByTextSidesMargin, - top: ( - canvas.height - - contractedByTextSidesMargin - - contractedByTextSize - ), - width: contractedByTextMaxWidth, - fontFamily: 'Roboto Condensed', - fontSize: contractedByTextSize, - textAlign: 'right', - fill: options.colors.contractedByText.value, - selectable: false, - zIndex: 10 - } - ) - - checkTextBoxHeight(contractedByTextbox, 1) - - canvas.add(contractedByTextbox) - } + /* END Main text render */ - /* END Contracted by render */ + /* BEGIN Left quote render */ + // Load on first render + if (quoteImage === null) { + quoteImage = new Image(); - /* BEGIN Source image render */ + const quoteImageLoadPromise = new Promise((resolve) => { + quoteImage.onload = () => { + resolve(); + }; + }); - const createNewSourceImage = ( - options.sourceImage !== null - && ( - sourceImage === null - || ( - options.sourceImage !== null - && options.sourceImage !== sourceImage._element - ) - ) - ) + quoteImage.src = quoteImageURL; - if (options.sourceImage !== null && options.personName === null) { - mainTextPaddingBottom += additionalContentExtraBottomPadding + await quoteImageLoadPromise; } - if (createNewSourceImage) { - canvas.remove(sourceImage) - canvas.remove(sourceText) - - sourceImage = new fabric.Image(options.sourceImage, {}) - sourceImage.scaleToHeight(sourceImageHeight) - sourceImage.set({ - left: sourceImageMarginSide, - top: sourceImageMarginTop, - selectable: false, - zIndex: 10, - }) - - canvas.add(sourceImage) - - sourceText = new fabric.Text("Zdroj: ", { - fontSize: bottomTextSize, - fill: options.colors.sourceText.value, - fontFamily: 'Roboto Condensed', - left: sourceImage.left - sourceTextMarginSide, - top: sourceImage.top, - selectable: false, - zIndex: 10, - }) - - sourceText.set({ - left: sourceText.left - sourceText.width - }) - - canvas.add(sourceText) - } else if (options.sourceImage === null) { - canvas.remove(sourceImage) - canvas.remove(sourceText) + leftQuote = new fabric.Image(quoteImage, { + selectable: false, + zIndex: 10, + }); + leftQuote.scaleToHeight(quoteHeight); + leftQuote.set({ + left: + leftQuoteMarginSide + + (options.mainText[0] === "*" ? leftQuoteMarginSideExtra : 0), + top: mainTextBoxTop + leftQuoteMarginTop, + }); + + canvas.add(leftQuote); + + /* END Left quote render */ + + /* BEGIN Right quote render */ + + rightQuote = new fabric.Image(quoteImage, { + flipX: true, + selectable: false, + zIndex: 10, + }); + rightQuote.scaleToHeight(quoteHeight); + rightQuote.set({ + left: + mainTextBox.left + + mainTextBox.__lineWidths[mainTextBox.__lineWidths.length - 1] + + rightQuoteMarginSide + + (options.mainText[options.mainText.length - 1] === "*" + ? rightQuoteMarginSideExtra + : 0), + top: + mainTextBoxTop + + mainTextBox.__lineHeights + .slice(0, -1) + .reduce((partialSum, a) => partialSum + a, 0) + + rightQuoteMarginTop, + }); + + canvas.add(rightQuote); + + /* END Right quote render */ + + /* BEGIN Top tear render */ + + // Load on first render + if (topTearImage === null) { + topTearImage = new Image(); + + const tearImageLoadPromise = new Promise((resolve) => { + topTearImage.onload = () => { + resolve(); + }; + }); + + topTearImage.src = topTearImageURL; + + await tearImageLoadPromise; } - /* END Source image render */ + topTear = new fabric.Image(topTearImage, { + selectable: false, + zIndex: 11, + }); + topTear.scaleToWidth(canvas.width); + topTear.set({ + top: mainTextBoxTop - topTearMarginBottom, + left: 0, + }); + + canvas.add(topTear); + + /* END Top tear render */ + + /* BEGIN Top tear fill render */ + + topTearFill = new fabric.Polygon( + [ + { x: -canvas.width * 0.01, y: topTear.top + canvas.height * 0.05 }, + // Hacky seam fix + { x: canvas.width * 0.1, y: topTear.top + canvas.height * 0.025 }, + { x: canvas.width * 0.15, y: topTear.top + canvas.height * 0.02 }, + { x: canvas.width * 0.2, y: topTear.top + canvas.height * 0.033 }, + { x: canvas.width * 0.3, y: topTear.top + canvas.height * 0.065 }, + { x: canvas.width * 0.4, y: topTear.top + canvas.height * 0.06 }, + { x: canvas.width * 0.5, y: topTear.top + canvas.height * 0.07 }, + { x: canvas.width * 0.53, y: topTear.top + canvas.height * 0.065 }, + { x: canvas.width * 0.6, y: topTear.top + canvas.height * 0.08 }, + { x: canvas.width * 0.75, y: topTear.top + canvas.height * 0.05 }, + { x: canvas.width * 0.85, y: topTear.top + canvas.height * 0.08 }, + { x: canvas.width, y: topTear.top + canvas.height * 0.12 }, + { x: canvas.width, y: topTear.top }, + { x: canvas.width, y: mainTextBoxTop }, + { x: -canvas.width * 0.01, y: mainTextBoxTop }, + // Hacky seam fix + ], + { + top: topTear.top + canvas.height * 0.005, // Hacky seam fix + left: -(canvas.width * 0.005), // Hacky seam fix + fill: options.colors.foreground.value, + selectable: false, + zIndex: 9, + }, + ); + + canvas.add(topTearFill); + + /* END Top tear fill render */ + + /* BEGIN Main text foreground render */ + const foregroundHeight = + canvas.height - mainTextBoxTop - mainTextMarginBottom; - sortObjects(canvas) -} + mainTextBoxForeground = new fabric.Rect({ + width: canvas.width, + height: foregroundHeight, + left: 0, + top: mainTextBoxTop, + fill: options.colors.foreground.value, + selectable: false, + zIndex: 9, + }); + + canvas.add(mainTextBoxForeground); + + /* END Main text foreground render */ + + /* BEGIN Bottom tear render */ + + const mainTextBoxForegroundBottomY = + mainTextBoxForeground.top + mainTextBoxForeground.height; + + // Load on first render + if (bottomTearImage === null) { + bottomTearImage = new Image(); + + const tearImageLoadPromise = new Promise((resolve) => { + bottomTearImage.onload = () => { + resolve(); + }; + }); + + bottomTearImage.src = bottomTearImageURL; + + await tearImageLoadPromise; + } + + bottomTear = new fabric.Image(bottomTearImage, { + selectable: false, + zIndex: 11, + }); + bottomTear.scaleToWidth(canvas.width); + bottomTear.set({ + top: mainTextBoxForegroundBottomY + bottomTearMarginTop, + left: 0, + }); + + canvas.add(bottomTear); + + /* END Bottom tear render */ + + /* BEGIN Bottom tear fill render */ + + const bottomTearBottom = bottomTear.top + bottomTear.getScaledHeight(); + + bottomTearFill = new fabric.Polygon( + [ + { x: -canvas.width * 0.01, y: mainTextBoxForegroundBottomY }, + // Hacky seam fix + { x: canvas.width, y: mainTextBoxForegroundBottomY }, + { x: canvas.width, y: bottomTearBottom - canvas.height * 0.063 }, + { x: canvas.width * 0.95, y: bottomTearBottom - canvas.height * 0.06 }, + { x: canvas.width * 0.92, y: bottomTearBottom - canvas.height * 0.045 }, + { x: canvas.width * 0.85, y: bottomTearBottom - canvas.height * 0.07 }, + { x: canvas.width * 0.77, y: bottomTearBottom - canvas.height * 0.06 }, + { x: canvas.width * 0.7, y: bottomTearBottom - canvas.height * 0.08 }, + { x: canvas.width * 0.5, y: bottomTearBottom - canvas.height * 0.005 }, + { x: canvas.width * 0.4, y: bottomTearBottom - canvas.height * 0.045 }, + { x: canvas.width * 0.25, y: bottomTearBottom - canvas.height * 0.03 }, + { x: canvas.width * 0.1, y: bottomTearBottom - canvas.height * 0.085 }, + { x: canvas.width * 0.05, y: bottomTearBottom - canvas.height * 0.055 }, + { x: -canvas.width * 0.01, y: bottomTearBottom - canvas.height * 0.07 }, + // Hacky seam fix + ], + { + top: mainTextBoxForegroundBottomY - canvas.height * 0.005, // Hacky seam fix + left: -(canvas.width * 0.005), // Hacky seam fix + fill: options.colors.foreground.value, + selectable: false, + zIndex: 9, + }, + ); + + canvas.add(bottomTearFill); + + /* END Bottom tear fill 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 if (options.logoPosition.id == "top-left") { + logoImage.set({ + left: logoSideMargin, + top: logoSideMargin, + zIndex: 11, + }); + } else { + logoImage.set({ + left: logoSideMargin, + top: canvas.height - logoSideMargin - logoImage.getScaledHeight(), + zIndex: 11, + }); + } -export default redraw + canvas.add(logoImage); + } + + /* END Logo render */ + + /* BEGIN Contracted by render */ + + if (options.contractedBy !== null) { + contractedByTextbox = new fabric.Textbox(options.contractedBy, { + left: + canvas.width - contractedByTextMaxWidth - contractedByTextSidesMargin, + top: canvas.height - contractedByTextSidesMargin - contractedByTextSize, + width: contractedByTextMaxWidth, + fontFamily: "Roboto Condensed", + fontSize: contractedByTextSize, + textAlign: "right", + fill: options.colors.contractedByText.value, + selectable: false, + zIndex: 10, + }); + + checkTextBoxHeight(contractedByTextbox, 1); + + canvas.add(contractedByTextbox); + } + + /* END Contracted by render */ + + /* BEGIN Source image render */ + + const createNewSourceImage = + options.sourceImage !== null && + (sourceImage === null || + (options.sourceImage !== null && + options.sourceImage !== sourceImage._element)); + + if (options.sourceImage !== null && options.personName === null) { + mainTextPaddingBottom += additionalContentExtraBottomPadding; + } + + if (createNewSourceImage) { + canvas.remove(sourceImage); + canvas.remove(sourceText); + + sourceImage = new fabric.Image(options.sourceImage, {}); + sourceImage.scaleToHeight(sourceImageHeight); + sourceImage.set({ + left: sourceImageMarginSide, + top: sourceImageMarginTop, + selectable: false, + zIndex: 10, + }); + + canvas.add(sourceImage); + + sourceText = new fabric.Text("Zdroj: ", { + fontSize: bottomTextSize, + fill: options.colors.sourceText.value, + fontFamily: "Roboto Condensed", + left: sourceImage.left - sourceTextMarginSide, + top: sourceImage.top, + selectable: false, + zIndex: 10, + }); + + sourceText.set({ + left: sourceText.left - sourceText.width, + }); + + canvas.add(sourceText); + } else if (options.sourceImage === null) { + canvas.remove(sourceImage); + canvas.remove(sourceText); + } + + /* END Source image render */ + + sortObjects(canvas); +}; + +export default redraw; diff --git a/frontend/src/views/poster/Poster.vue b/frontend/src/views/poster/Poster.vue index ab805e4728e3a0bea49d056eda0171ef61eb24f7..7cb44451ab84475b6acbb179237484fa36a56390 100644 --- a/frontend/src/views/poster/Poster.vue +++ b/frontend/src/views/poster/Poster.vue @@ -1,274 +1,272 @@ <script setup> -import { watch, ref } from 'vue' +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 COLORS from "../../colors"; +import PEOPLE from "../../people"; +import TEMPLATES from "../../templates"; +import DEFAULT_CONTRACTOR from "../../contractors"; +import { + loadFonts, + loadCanvasStorage, + setCanvasStorage, + updateAutoRedrawStorage, +} from "../../utils"; -import Canvas from '../../components/canvas/Canvas.vue' -import redraw from './canvas' +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 RangeInput from '../../components/inputs/RangeInput.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' +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 RangeInput from "../../components/inputs/RangeInput.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' -]) + "12px Bebas Neue", + "12px Roboto Condensed", + "bold 12px Roboto Condensed", +]); export default { - components: { - Canvas, - Navbar, - MainContainer, - ImageInput, - LongTextInput, - ShortTextInput, - RangeInput, - SelectInput, - InputSeparator, - MultipleColorPicker - }, - data () { - const predefinedColors = { - base: { - name: 'Základní barvy', - colors: { - background: COLORS.white, - baseText: COLORS.black, - contractedByText: COLORS.gray1, - arrow: COLORS.yellow1 - } - } - } + components: { + Canvas, + Navbar, + MainContainer, + ImageInput, + LongTextInput, + ShortTextInput, + RangeInput, + SelectInput, + InputSeparator, + MultipleColorPicker, + }, + data() { + const predefinedColors = { + base: { + name: "Základní barvy", + colors: { + background: COLORS.white, + baseText: COLORS.black, + contractedByText: COLORS.gray1, + arrow: COLORS.yellow1, + }, + }, + }; - return { - mainText: null, + return { + mainText: null, - mainImage: null, - personName: null, + mainImage: null, + personName: null, - personPosition: null, - firstColumn: null, - secondColumn: null, + personPosition: null, + firstColumn: null, + secondColumn: null, - socialMedia1: null, - socialMedia2: null, - socialMedia3: null, + socialMedia1: null, + socialMedia2: null, + socialMedia3: null, - contractedBy: DEFAULT_CONTRACTOR, + contractedBy: DEFAULT_CONTRACTOR, - colorLabels: { - background: 'Pozadí', - baseText: 'Text', - highlightedText: 'Zvýrazněný text', - arrow: 'Šipka' - }, + colorLabels: { + background: "Pozadí", + baseText: "Text", + 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, - personName: this.personName, - personPosition: this.personPosition, - firstColumn: this.firstColumn, - secondColumn: this.secondColumn, - socialMedia1: this.socialMedia1, - socialMedia2: this.socialMedia2, - socialMedia3: this.socialMedia3, - contractedBy: this.contractedBy, - colors: this.colors - } + predefinedColors: predefinedColors, + colors: predefinedColors.base.colors, + autoRedraw: false, + }; + }, + async created() { + await loadCanvasStorage(this); + }, + methods: { + async reloadCanvasProperties() { + const canvasProperties = { + mainImage: this.mainImage, + mainText: this.mainText, + personName: this.personName, + personPosition: this.personPosition, + firstColumn: this.firstColumn, + secondColumn: this.secondColumn, + socialMedia1: this.socialMedia1, + socialMedia2: this.socialMedia2, + socialMedia3: this.socialMedia3, + contractedBy: this.contractedBy, + colors: this.colors, + }; - await this.$refs.canvas.redraw(canvasProperties) + await this.$refs.canvas.redraw(canvasProperties); - // delete canvasProperties.colors - setCanvasStorage(canvasProperties) - } + // delete canvasProperties.colors + setCanvasStorage(canvasProperties); }, - mounted () { - this.$watch( - vm => [ - vm.mainText, - vm.mainImage, - vm.personName, - vm.personPosition, - vm.firstColumn, - vm.secondColumn, - vm.socialMedia1, - vm.socialMedia2, - vm.socialMedia3, - vm.contractedBy, - vm.colors - ], - async (value) => { - if (this.autoRedraw) { - await this.reloadCanvasProperties() - } - }, - { - immediate: true, - deep: true - } - ) + }, + mounted() { + this.$watch( + (vm) => [ + vm.mainText, + vm.mainImage, + vm.personName, + vm.personPosition, + vm.firstColumn, + vm.secondColumn, + vm.socialMedia1, + vm.socialMedia2, + vm.socialMedia3, + vm.contractedBy, + vm.colors, + ], + async (value) => { + if (this.autoRedraw) { + await this.reloadCanvasProperties(); + } + }, + { + immediate: true, + deep: true, + }, + ); - this.$watch( - vm => [vm.autoRedraw], - async (value) => { - updateAutoRedrawStorage(this.autoRedraw) + this.$watch( + (vm) => [vm.autoRedraw], + async (value) => { + updateAutoRedrawStorage(this.autoRedraw); - if (this.autoRedraw) { - await this.reloadCanvasProperties() - } - } - ) - }, -} + if (this.autoRedraw) { + await this.reloadCanvasProperties(); + } + }, + ); + }, +}; </script> <template> - <header> - <Navbar - :defaultTemplate="TEMPLATES.poster" - ></Navbar> - </header> - <main> - <MainContainer> - <template v-slot:left> - <Canvas - ref="canvas" - :redrawFunction="redraw" - width="3625" - height="5078" - /> - </template> + <header> + <Navbar :defaultTemplate="TEMPLATES.poster"></Navbar> + </header> + <main> + <MainContainer> + <template v-slot:left> + <Canvas + ref="canvas" + :redrawFunction="redraw" + width="3625" + height="5078" + /> + </template> - <template v-slot:right> - <ReloadButton - :parentRefs="$refs" - @click="reloadCanvasProperties" - /> - <AutoReloadCheckbox - v-model="autoRedraw" - /> + <template v-slot:right> + <ReloadButton :parentRefs="$refs" @click="reloadCanvasProperties" /> + <AutoReloadCheckbox v-model="autoRedraw" /> - <ImageInput - name="Obrázek" - v-model="mainImage" - :important="true" - zIndex="11" - /> + <ImageInput + name="Obrázek" + v-model="mainImage" + :important="true" + zIndex="11" + /> - <ShortTextInput - name="Nadpis" - v-model="mainText" - v-model:relatedModel="mainText" - :important="true" - zIndex="10" - /> + <ShortTextInput + name="Nadpis" + v-model="mainText" + v-model:relatedModel="mainText" + :important="true" + zIndex="10" + /> - <ShortTextInput - name="Jméno osoby" - v-model="personName" - v-model:relatedModel="personPosition" - :predefinedValues="PEOPLE" - :important="true" - zIndex="9" - /> - <LongTextInput - ref="refPersonPosition" - name="Pozice osoby" - v-model="personPosition" - :important="false" - zIndex="8" - /> + <ShortTextInput + name="Jméno osoby" + v-model="personName" + v-model:relatedModel="personPosition" + :predefinedValues="PEOPLE" + :important="true" + zIndex="9" + /> + <LongTextInput + ref="refPersonPosition" + name="Pozice osoby" + v-model="personPosition" + :important="false" + zIndex="8" + /> - <LongTextInput - name="Sloupec 1" - v-model="firstColumn" - :important="true" - :highlightable="true" - zIndex="7" - /> - <LongTextInput - name="Sloupec 2" - v-model="secondColumn" - :important="true" - :highlightable="true" - zIndex="6" - /> + <LongTextInput + name="Sloupec 1" + v-model="firstColumn" + :important="true" + :highlightable="true" + zIndex="7" + /> + <LongTextInput + name="Sloupec 2" + v-model="secondColumn" + :important="true" + :highlightable="true" + zIndex="6" + /> - <InputSeparator /> + <InputSeparator /> - <ShortTextInput - name="Sociální médium 1 (např. @tvůj_instagram)" - v-model="socialMedia1" - v-model:relatedModel="socialMedia1" - :important="true" - zIndex="9" - /> + <ShortTextInput + name="Sociální médium 1 (např. @tvůj_instagram)" + v-model="socialMedia1" + v-model:relatedModel="socialMedia1" + :important="true" + zIndex="9" + /> - <ShortTextInput - name="Sociální médium 2 (např. @pirati.cz)" - v-model="socialMedia2" - v-model:relatedModel="socialMedia2" - :important="true" - zIndex="8" - /> + <ShortTextInput + name="Sociální médium 2 (např. @pirati.cz)" + v-model="socialMedia2" + v-model:relatedModel="socialMedia2" + :important="true" + zIndex="8" + /> - <ShortTextInput - name="Sociální médium 3 (např. tvuj@ema.il)" - v-model="socialMedia3" - v-model:relatedModel="socialMedia3" - :important="true" - zIndex="7" - /> + <ShortTextInput + name="Sociální médium 3 (např. tvuj@ema.il)" + v-model="socialMedia3" + v-model:relatedModel="socialMedia3" + :important="true" + zIndex="7" + /> - <InputSeparator /> + <InputSeparator /> - <MultipleColorPicker - name="Barvy" - v-model="colors" - :important="false" - :colorLabels="colorLabels" - :predefinedColors="predefinedColors" - :defaultPredefinedColors="predefinedColors.base" - zIndex="5" - ></MultipleColorPicker> + <MultipleColorPicker + name="Barvy" + v-model="colors" + :important="false" + :colorLabels="colorLabels" + :predefinedColors="predefinedColors" + :defaultPredefinedColors="predefinedColors.base" + zIndex="5" + ></MultipleColorPicker> - <ShortTextInput - name="Zadavatel a zpracovatel" - v-model="contractedBy" - :defaultValue="DEFAULT_CONTRACTOR" - :important="false" - zIndex="4" - /> - </template> - </MainContainer> - </main> + <ShortTextInput + name="Zadavatel a zpracovatel" + v-model="contractedBy" + :defaultValue="DEFAULT_CONTRACTOR" + :important="false" + zIndex="4" + /> + </template> + </MainContainer> + </main> </template> <style> diff --git a/frontend/src/views/poster/canvas.js b/frontend/src/views/poster/canvas.js index 64ff1ff2b26a564345eae837c6d60b0afd853af6..e92293e6689fa74eebd662170d2dc19f4a4b789b 100644 --- a/frontend/src/views/poster/canvas.js +++ b/frontend/src/views/poster/canvas.js @@ -1,468 +1,408 @@ -import { fabric } from 'fabric' -import { clearObjects, sortObjects, transformHighlightedText, checkTextBoxHeight } from '../../components/canvas/utils' -import { PaddedHighlightingTextbox } from '../../components/canvas/textbox' -import overlayURL from '../../assets/template/poster/overlay.png' - -let mainImage = null -let mainImageSource = null -let overlayImage = null -let pointerDownEventAssigned = false -let socialMediaTextObject = null -let backgroundRect = null -let bottomBackgroundRect = null -let personNameText = null -let mainText = null -let firstColumn = null -let secondColumn = null - -let arrow = null - -let contractedByTextbox = null +import { fabric } from "fabric"; +import { + clearObjects, + sortObjects, + transformHighlightedText, + checkTextBoxHeight, +} from "../../components/canvas/utils"; +import { PaddedHighlightingTextbox } from "../../components/canvas/textbox"; +import overlayURL from "../../assets/template/poster/overlay.png"; + +let mainImage = null; +let mainImageSource = null; +let overlayImage = null; +let pointerDownEventAssigned = false; +let socialMediaTextObject = null; +let backgroundRect = null; +let bottomBackgroundRect = null; +let personNameText = null; +let mainText = null; +let firstColumn = null; +let secondColumn = null; + +let arrow = null; + +let contractedByTextbox = null; const removeDownEventListener = () => { - document.getElementsByClassName("upper-canvas")[0].removeEventListener("pointerdown", canvasPointerDownEvent) -} + document + .getElementsByClassName("upper-canvas")[0] + .removeEventListener("pointerdown", canvasPointerDownEvent); +}; -let upEventFunction = null -let canvasPointerDownEvent = null +let upEventFunction = null; +let canvasPointerDownEvent = null; const redraw = async (canvas, options) => { - clearObjects( - [ - socialMediaTextObject, - personNameText, - contractedByTextbox, - arrow, - mainText, - firstColumn, - secondColumn - ], - canvas - ) - - const leftMarginText = 300 - const leftMarginPersonText = 410 - const bottomMarginText = 200 - const bottomFontSize = 90 - - const topMarginText = 150 - - const mainTextMarginBottom = 1180 - const mainTextSize = 300 - - const nameTextSize = 100 - - const arrowWidth = Math.ceil(canvas.width * 0.0235) - const arrowHeight = Math.ceil(canvas.width * 0.0275) - const arrowThickness = Math.ceil(canvas.width * 0.01) - const arrowMarginLeft = 300 - const arrowMarginBottom = Math.ceil(canvas.height * 0.257) - - const columnsMarginTop = 600 - const columnsMarginBetween = 100 - const columnsMaxWidth = 1500 - const columnTextSize = 75 - const columnLineHeight = 1 - - const contractedByTextSize = Math.ceil(canvas.height * 0.01) - const contractedByTextMaxWidth = Math.ceil(canvas.width * 0.9) - const contractedByTextSidesMargin = Math.ceil(canvas.width * 0.03) - - 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 (backgroundRect === null) { - backgroundRect = new fabric.Rect({ - width: canvas.width * 1.1, - height: canvas.height * 1.1, - top: -20, // FIXME: Why???? Fabric.js, what are you trying to tell me?! - left: -20, - fill: options.colors.background.value, - selectable: false, - zIndex: 0 - }) - - canvas.add(backgroundRect) + clearObjects( + [ + socialMediaTextObject, + personNameText, + contractedByTextbox, + arrow, + mainText, + firstColumn, + secondColumn, + ], + canvas, + ); + + const leftMarginText = 300; + const leftMarginPersonText = 410; + const bottomMarginText = 200; + const bottomFontSize = 90; + + const topMarginText = 150; + + const mainTextMarginBottom = 1180; + const mainTextSize = 300; + + const nameTextSize = 100; + + const arrowWidth = Math.ceil(canvas.width * 0.0235); + const arrowHeight = Math.ceil(canvas.width * 0.0275); + const arrowThickness = Math.ceil(canvas.width * 0.01); + const arrowMarginLeft = 300; + const arrowMarginBottom = Math.ceil(canvas.height * 0.257); + + const columnsMarginTop = 600; + const columnsMarginBetween = 100; + const columnsMaxWidth = 1500; + const columnTextSize = 75; + const columnLineHeight = 1; + + const contractedByTextSize = Math.ceil(canvas.height * 0.01); + const contractedByTextMaxWidth = Math.ceil(canvas.width * 0.9); + const contractedByTextSidesMargin = Math.ceil(canvas.width * 0.03); + + 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 (backgroundRect === null) { + backgroundRect = new fabric.Rect({ + width: canvas.width * 1.1, + height: canvas.height * 1.1, + top: -20, // FIXME: Why???? Fabric.js, what are you trying to tell me?! + left: -20, + fill: options.colors.background.value, + selectable: false, + zIndex: 0, + }); + + canvas.add(backgroundRect); + } + + /* END Background render */ + + /* BEGIN Main image render */ + + if ( + options.mainImage !== null && + (!canvas.contains(mainImage) || + mainImage === null || + options.mainImage.src !== mainImageSource) + ) { + if (mainImage !== null) { + canvas.remove(mainImage); } - /* END Background render */ - - /* BEGIN Main image render */ - - 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, - { - left: 0, - top: 0, - zIndex: 10 - } - ) - - mainImage.controls = { - ...fabric.Image.prototype.controls, - mtr: new fabric.Control({ visible: false }) - } - - if (mainImage.width >= mainImage.height) { - mainImage.scaleToHeight(canvas.height) - } else { - mainImage.scaleToWidth(canvas.width) - } - - 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 + mainImage = new fabric.Image(options.mainImage, { + left: 0, + top: 0, + zIndex: 10, + }); + + mainImage.controls = { + ...fabric.Image.prototype.controls, + mtr: new fabric.Control({ visible: false }), + }; + + if (mainImage.width >= mainImage.height) { + mainImage.scaleToHeight(canvas.height); + } else { + mainImage.scaleToWidth(canvas.width); } - /* END Main image render */ - - /* BEGIN Overlay render */ - - if (overlayImage === null) { - overlayImage = new Image() - - await new Promise(resolve => { - overlayImage.onload = () => { - resolve() - } - - overlayImage.src = overlayURL - }) - - overlayImage = new fabric.Image( - overlayImage, - { - top: -20, // FIXME: Why???? Fabric.js, what are you trying to tell me?! - left: -20, - zIndex: 20, - selectable: false - } - ) - overlayImage.scaleToWidth(canvas.width + 22) - - canvas.add(overlayImage) + 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 Overlay render */ + + if (overlayImage === null) { + overlayImage = new Image(); + + await new Promise((resolve) => { + overlayImage.onload = () => { + resolve(); + }; + + overlayImage.src = overlayURL; + }); + + overlayImage = new fabric.Image(overlayImage, { + top: -20, // FIXME: Why???? Fabric.js, what are you trying to tell me?! + left: -20, + zIndex: 20, + selectable: false, + }); + overlayImage.scaleToWidth(canvas.width + 22); + + canvas.add(overlayImage); + } + + /* END Overlay render */ + + /* BEGIN Bottom background render */ + + if (bottomBackgroundRect === null) { + bottomBackgroundRect = new fabric.Rect({ + width: canvas.width * 1.1, + height: 1458, + top: 3620, // FIXME: Why???? Fabric.js, what are you trying to tell me?! + left: -20, + fill: options.colors.background.value, + zIndex: 30, + selectable: false, + }); + + canvas.add(bottomBackgroundRect); + } + + /* END Bottom background render */ + + /* BEGIN Arrow render */ + + arrow = new fabric.Polygon( + [ + { x: 0, y: 0 }, + { x: arrowThickness, y: 0 }, + { + x: arrowWidth, + y: Math.ceil(arrowHeight) / 2, + }, + { + x: arrowThickness, + y: arrowHeight, + }, + { x: 0, y: arrowHeight }, + { + x: arrowWidth - arrowThickness, + y: Math.ceil(arrowHeight) / 2, + }, + { x: 0, y: 0 }, + ], + { + top: canvas.height - arrowMarginBottom, + left: arrowMarginLeft, + fill: "#fec900", + selectable: false, + zIndex: 40, + }, + ); + + canvas.add(arrow); + + /* END Arrow render */ + + /* BEGIN Name / position text render */ + + if (options.personName !== null) { + let styles = { + 0: {}, + }; + + for (let position = 0; position < options.personName.length; position++) { + styles[0][position] = { + fontWeight: "bold", + }; } - /* END Overlay render */ - - /* BEGIN Bottom background render */ + let nameText = options.personName; - if (bottomBackgroundRect === null) { - bottomBackgroundRect = new fabric.Rect({ - width: canvas.width * 1.1, - height: 1458, - top: 3620, // FIXME: Why???? Fabric.js, what are you trying to tell me?! - left: -20, - fill: options.colors.background.value, - zIndex: 30, - selectable: false, - }) - - canvas.add(bottomBackgroundRect) + if (options.personPosition !== null) { + nameText += ` | ${options.personPosition}`; } - /* END Bottom background render */ - - /* BEGIN Arrow render */ - - arrow = new fabric.Polygon( - [ - {x: 0, y: 0}, - {x: arrowThickness, y: 0}, - { - x: arrowWidth, - y: Math.ceil(arrowHeight) / 2 - }, - { - x: arrowThickness, - y: arrowHeight - }, - {x: 0, y: arrowHeight}, - { - x: arrowWidth - arrowThickness, - y: Math.ceil(arrowHeight) / 2 - }, - {x: 0, y: 0} - ], - { - top: ( - canvas.height - - arrowMarginBottom - ), - left: arrowMarginLeft, - fill: "#fec900", - selectable: false, - zIndex: 40 - } - ) - - canvas.add(arrow) - - /* END Arrow render */ - - /* BEGIN Name / position text render */ - - if (options.personName !== null) { - let styles = { - 0: {} - } - - for (let position = 0; position < options.personName.length; position++) { - styles[0][position] = { - fontWeight: 'bold' - } - } - - let nameText = options.personName - - if (options.personPosition !== null) { - nameText += ` | ${options.personPosition}` - } - - personNameText = new fabric.Text( - nameText, - { - left: leftMarginPersonText, - top: ( - canvas.height - - bottomBackgroundRect.height - + topMarginText - ), - fontFamily: 'Roboto Condensed', - fontSize: nameTextSize, - styles: styles, - fill: "#000", - selectable: false, - zIndex: 40 - } - ) - - canvas.add(personNameText) + personNameText = new fabric.Text(nameText, { + left: leftMarginPersonText, + top: canvas.height - bottomBackgroundRect.height + topMarginText, + fontFamily: "Roboto Condensed", + fontSize: nameTextSize, + styles: styles, + fill: "#000", + selectable: false, + zIndex: 40, + }); + + canvas.add(personNameText); + } + + /* END Name / position text render */ + + /* BEGIN Main text render */ + + if (options.mainText !== null) { + mainText = new fabric.Text(options.mainText, { + left: leftMarginText, + top: canvas.height - mainTextMarginBottom, + fontFamily: "Bebas Neue", + fontSize: mainTextSize, + fill: "#000", + selectable: false, + zIndex: 40, + }); + + canvas.add(mainText); + } + + /* END Main text render */ + + /* BEGIN Column text render */ + + if (options.firstColumn !== null) { + firstColumn = new fabric.Textbox(options.firstColumn, { + left: leftMarginText, + top: canvas.height - bottomBackgroundRect.height + columnsMarginTop, + width: columnsMaxWidth, + fontFamily: "Roboto Condensed", + fontSize: columnTextSize, + lineHeight: columnLineHeight, + fill: "#000", + selectable: false, + zIndex: 40, + }); + + canvas.add(firstColumn); + } + + if (options.secondColumn !== null) { + secondColumn = new fabric.Textbox(options.secondColumn, { + left: leftMarginText + columnsMarginBetween + columnsMaxWidth, + top: canvas.height - bottomBackgroundRect.height + columnsMarginTop, + width: columnsMaxWidth, + fontFamily: "Roboto Condensed", + fontSize: columnTextSize, + lineHeight: columnLineHeight, + fill: "#000", + selectable: false, + zIndex: 40, + }); + + canvas.add(secondColumn); + } + + /* END Column text render */ + + /* BEGIN Bottom text render */ + + if (options.socialMedia1 || options.socialMedia2 || options.socialMedia3) { + let socialMediaText = `${ + options.socialMedia1 !== null ? options.socialMedia1 + " " : "" + }${ + options.socialMedia2 !== null ? options.socialMedia2 + " " : "" + }${options.socialMedia3 !== null ? options.socialMedia3 : ""}`; + + socialMediaTextObject = new fabric.Text(socialMediaText, { + left: leftMarginText, + top: canvas.height - bottomMarginText - bottomFontSize, + fontFamily: "Roboto Condensed", + fontSize: bottomFontSize, + fill: "#000", + selectable: false, + zIndex: 40, + }); + + canvas.add(socialMediaTextObject); + } + + /* END Bottom text render */ + + /* BEGIN Contracted by render */ + + if (options.contractedBy !== null) { + contractedByTextbox = new fabric.Textbox(options.contractedBy, { + left: + canvas.width - contractedByTextMaxWidth - contractedByTextSidesMargin, + top: canvas.height - contractedByTextSidesMargin - contractedByTextSize, + width: contractedByTextMaxWidth, + fontFamily: "Roboto Condensed", + fontSize: contractedByTextSize, + textAlign: "right", + fill: "#505050", + selectable: false, + zIndex: 40, + }); + + checkTextBoxHeight(contractedByTextbox, 1); + + canvas.add(contractedByTextbox); + } + + /* END Contracted by render */ + + sortObjects(canvas); + + canvasPointerDownEvent = (event) => { + let activeObject = canvas.getActiveObject(); + + if (activeObject === null) { + return; } - /* END Name / position text render */ - - /* BEGIN Main text render */ - - if (options.mainText !== null) { - mainText = new fabric.Text( - options.mainText, - { - left: leftMarginText, - top: ( - canvas.height - - mainTextMarginBottom - ), - fontFamily: 'Bebas Neue', - fontSize: mainTextSize, - fill: "#000", - selectable: false, - zIndex: 40 - } - ) - - canvas.add(mainText) - } + // if (activeObject._element.src == mainImage._element.src) { + // return + // } - /* END Main text render */ - - /* BEGIN Column text render */ - - if (options.firstColumn !== null) { - firstColumn = new fabric.Textbox( - options.firstColumn, - { - left: leftMarginText, - top: ( - canvas.height - - bottomBackgroundRect.height - + columnsMarginTop - ), - width: columnsMaxWidth, - fontFamily: 'Roboto Condensed', - fontSize: columnTextSize, - lineHeight: columnLineHeight, - fill: "#000", - selectable: false, - zIndex: 40 - } - ) - - canvas.add(firstColumn) - } + canvas.remove(overlayImage); + overlayImage = null; + }; - if (options.secondColumn !== null) { - secondColumn = new fabric.Textbox( - options.secondColumn, - { - left: leftMarginText + columnsMarginBetween + columnsMaxWidth, - top: ( - canvas.height - - bottomBackgroundRect.height - + columnsMarginTop - ), - width: columnsMaxWidth, - fontFamily: 'Roboto Condensed', - fontSize: columnTextSize, - lineHeight: columnLineHeight, - fill: "#000", - selectable: false, - zIndex: 40 - } - ) - - canvas.add(secondColumn) - } + if (!pointerDownEventAssigned) { + document + .getElementsByClassName("upper-canvas")[0] + .addEventListener("pointerdown", canvasPointerDownEvent); - /* END Column text render */ - - /* BEGIN Bottom text render */ - - if ( - options.socialMedia1 - || options.socialMedia2 - || options.socialMedia3 - ) { - let socialMediaText = `${ - (options.socialMedia1 !== null) ? - options.socialMedia1 + " " : - "" - }${ - (options.socialMedia2 !== null) ? - options.socialMedia2 + " " : - "" - }${ - (options.socialMedia3 !== null) ? - options.socialMedia3 : - "" - }` - - socialMediaTextObject = new fabric.Text( - socialMediaText, - { - left: leftMarginText, - top: ( - canvas.height - - bottomMarginText - - bottomFontSize - ), - fontFamily: 'Roboto Condensed', - fontSize: bottomFontSize, - fill: "#000", - selectable: false, - zIndex: 40 - } - ) - - canvas.add(socialMediaTextObject) - } + pointerDownEventAssigned = true; + } - /* END Bottom text render */ - - /* BEGIN Contracted by render */ - - if (options.contractedBy !== null) { - contractedByTextbox = new fabric.Textbox( - options.contractedBy, - { - left: canvas.width - contractedByTextMaxWidth - contractedByTextSidesMargin, - top: ( - canvas.height - - contractedByTextSidesMargin - - contractedByTextSize - ), - width: contractedByTextMaxWidth, - fontFamily: 'Roboto Condensed', - fontSize: contractedByTextSize, - textAlign: 'right', - fill: "#505050", - selectable: false, - zIndex: 40 - } - ) - - checkTextBoxHeight(contractedByTextbox, 1) - - canvas.add(contractedByTextbox) - } - - /* END Contracted by render */ - - sortObjects(canvas) - - canvasPointerDownEvent = (event) => { - let activeObject = canvas.getActiveObject() - - if (activeObject === null) { - return - } - - // if (activeObject._element.src == mainImage._element.src) { - // return - // } - - canvas.remove(overlayImage) - overlayImage = null - } - - if (!pointerDownEventAssigned) { - document.getElementsByClassName("upper-canvas")[0].addEventListener( - "pointerdown", - canvasPointerDownEvent - ) - - pointerDownEventAssigned = true - } - - upEventFunction = (event) => { - redraw(canvas, options) - } + upEventFunction = (event) => { + redraw(canvas, options); + }; - document.getElementsByClassName("upper-canvas")[0].addEventListener( - "pointerup", - upEventFunction - ) + document + .getElementsByClassName("upper-canvas")[0] + .addEventListener("pointerup", upEventFunction); - document.getElementsByClassName("upper-canvas")[0].addEventListener( - "pointerout", - upEventFunction - ) + document + .getElementsByClassName("upper-canvas")[0] + .addEventListener("pointerout", upEventFunction); - document.getElementsByClassName("upper-canvas")[0].addEventListener( - "pointercancel", - upEventFunction - ) -} + document + .getElementsByClassName("upper-canvas")[0] + .addEventListener("pointercancel", upEventFunction); +}; -export default redraw +export default redraw; diff --git a/frontend/src/views/regional_success/RegionalSuccess.vue b/frontend/src/views/regional_success/RegionalSuccess.vue index 5d553b7a46debb56cfbe044d263a0b07db8452a8..8db69369f0fddfa785b71ed6493267f46706284f 100644 --- a/frontend/src/views/regional_success/RegionalSuccess.vue +++ b/frontend/src/views/regional_success/RegionalSuccess.vue @@ -1,309 +1,301 @@ <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' +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' -]) + "12px Bebas Neue", + "12px Roboto Condensed", + "bold 12px Roboto Condensed", +]); export default { - components: { - Canvas, - Navbar, - MainContainer, - ImageInput, - LongTextInput, - ShortTextInput, - EmojiInput, - SelectInput, - InputSeparator, - MultipleColorPicker + 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); }, - 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 - } - } + }, + 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(); } - - 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) + }, + { + immediate: true, + deep: true, + }, + ); + + this.$watch( + (vm) => [vm.autoRedraw], + async (value) => { + updateAutoRedrawStorage(this.autoRedraw); + + if (this.autoRedraw) { + await this.reloadCanvasProperties(); } - }, - 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> + <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> diff --git a/frontend/src/views/regional_success/canvas.js b/frontend/src/views/regional_success/canvas.js index ff9c519031d5eb86155d77f227e9f8f478c1d027..3d0432c4ed53c45e5ca63f3494153b2fade8843c 100644 --- a/frontend/src/views/regional_success/canvas.js +++ b/frontend/src/views/regional_success/canvas.js @@ -1,552 +1,493 @@ -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 { 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 gradientRect = null - -let previousLogoPosition = null +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 gradientRect = null; + +let previousLogoPosition = null; const removeDownEventListener = () => { - document.getElementsByClassName("upper-canvas")[0].removeEventListener("pointerdown", canvasPointerDownEvent) -} + document + .getElementsByClassName("upper-canvas")[0] + .removeEventListener("pointerdown", canvasPointerDownEvent); +}; -let upEventFunction = null -let canvasPointerDownEvent = null +let upEventFunction = null; +let canvasPointerDownEvent = null; const redraw = async (canvas, options) => { - clearObjects( - [ - mainText, - firstRow, - firstEmoji, - secondRow, - secondEmoji, - thirdRow, - thirdEmoji, - fourthRow, - fourthEmoji, - nameText, - nameTextDesc, - gradientRect - ], - 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) - } + clearObjects( + [ + mainText, + firstRow, + firstEmoji, + secondRow, + secondEmoji, + thirdRow, + thirdEmoji, + fourthRow, + fourthEmoji, + nameText, + nameTextDesc, + gradientRect, + ], + 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(); + }; - 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) + 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, + }); } - /* 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) + canvas.add(logoImage); + } + + /* END Logo render */ + + /* BEGIN Main image render */ + gradientRect = new fabric.Rect({ + left: 0, + top: 400, + 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); } - /* END Logo render */ - - - /* BEGIN Main image render */ - gradientRect = new fabric.Rect({ - left: 0, - top: 400, - 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 + mainImage = new fabric.Image(options.mainImage, { + top: 391, + maxHeight: 782, + right: 0, + zIndex: 3, }); - 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 */ + mainImage.controls = { + ...fabric.Image.prototype.controls, + mtr: new fabric.Control({ visible: false }), + }; + canvas.add(mainImage); + mainImageSource = options.mainImage.src; + // canvas.centerObject(mainImage) - /* BEGIN Arrow background render */ + removeDownEventListener(); + pointerDownEventAssigned = false; + } else if (mainImage !== null && options.mainImage === null) { + canvas.remove(mainImage); - if (backgroundArrowsImage === null) { - backgroundArrowsImage = new Image() + removeDownEventListener(); + pointerDownEventAssigned = false; + } - await new Promise(resolve => { - backgroundArrowsImage.onload = () => { - resolve() - } + /* END Main image render */ - backgroundArrowsImage.src = backgroundArrowsImageURL - }) + /* BEGIN Arrow background render */ - backgroundArrowsImage = new fabric.Image( - backgroundArrowsImage, - { - top: 391, - left: 0, - selectable: false, - zIndex: 6 - } - ) + if (backgroundArrowsImage === null) { + backgroundArrowsImage = new Image(); - canvas.add(backgroundArrowsImage) - } + await new Promise((resolve) => { + backgroundArrowsImage.onload = () => { + resolve(); + }; - /* 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) - } + backgroundArrowsImage.src = backgroundArrowsImageURL; + }); - 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) - } + backgroundArrowsImage = new fabric.Image(backgroundArrowsImage, { + top: 391, + left: 0, + selectable: false, + zIndex: 6, + }); - 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) - } + 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, + }); - 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) - } + 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, + }); - /* 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 */ + 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, + }); - sortObjects(canvas) + 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, + }); - if (!pointerDownEventAssigned) { - document.getElementsByClassName("upper-canvas")[0].addEventListener( - "pointerdown", - canvasPointerDownEvent - ) + 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, + }); - pointerDownEventAssigned = true - } + 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, + }); - upEventFunction = (event) => { - redraw(canvas, options) - } + 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("pointerup", upEventFunction); - document.getElementsByClassName("upper-canvas")[0].addEventListener( - "pointerout", - upEventFunction - ) + document + .getElementsByClassName("upper-canvas")[0] + .addEventListener("pointerout", upEventFunction); - document.getElementsByClassName("upper-canvas")[0].addEventListener( - "pointercancel", - upEventFunction - ) -} + document + .getElementsByClassName("upper-canvas")[0] + .addEventListener("pointercancel", upEventFunction); +}; -export default redraw +export default redraw; diff --git a/frontend/src/views/text_banner/TextBanner.vue b/frontend/src/views/text_banner/TextBanner.vue index 1a4bad2f250367ec879e826a7663dfdec654fcc1..3de496cd7ec834f80ce43f7e839c9ef710d97323 100644 --- a/frontend/src/views/text_banner/TextBanner.vue +++ b/frontend/src/views/text_banner/TextBanner.vue @@ -1,213 +1,204 @@ <script setup> -import { watch, ref } from 'vue'; - -import COLORS from '../../colors'; -import TEMPLATES from '../../templates' -import DEFAULT_CONTRACTOR from '../../contractors' -import { LOGOS, LOGO_POSITIONS, generateLogoPositions } from '../../logos' -import { loadFonts, loadCanvasStorage, setCanvasStorage, updateAutoRedrawStorage } from '../../utils' - -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 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' +import { watch, ref } from "vue"; + +import COLORS from "../../colors"; +import TEMPLATES from "../../templates"; +import DEFAULT_CONTRACTOR from "../../contractors"; +import { LOGOS, LOGO_POSITIONS, generateLogoPositions } from "../../logos"; +import { + loadFonts, + loadCanvasStorage, + setCanvasStorage, + updateAutoRedrawStorage, +} from "../../utils"; + +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 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' - ] -) +await loadFonts(["12px Bebas Neue", "12px Roboto Condensed"]); export default { - components: { - Canvas, - Navbar, - MainContainer, - ImageInput, - LongTextInput, - ShortTextInput, - MultipleColorPicker + components: { + Canvas, + Navbar, + MainContainer, + ImageInput, + LongTextInput, + ShortTextInput, + MultipleColorPicker, + }, + data() { + const predefinedColors = { + base: { + name: "Základní barvy", + colors: { + background: COLORS.black, + highlight: COLORS.yellow1, + arrow: COLORS.yellow1, + baseText: COLORS.white, + highlightedText: COLORS.black, + contractedByText: COLORS.gray1, + }, + }, + }; + + return { + mainText: null, + contractedBy: DEFAULT_CONTRACTOR, + logoImage: null, + logoPosition: LOGO_POSITIONS.top_right, + logoOptions: generateLogoPositions([ + "top_right", + "top_left", + "bottom_left", + "bottom_right", + ]), + colorLabels: { + background: "Pozadí", + highlight: "Zvýraznění", + arrow: "Šipka", + baseText: "Běžný text", + highlightedText: "Zvýrazněný text", + }, + predefinedColors: predefinedColors, + colors: predefinedColors.base.colors, + predefinedLogoImages: [ + { + name: LOGOS.defaultLight.name, + src: LOGOS.defaultLight.src, + defaultSelected: true, + }, + ], + autoRedraw: false, + }; + }, + async created() { + await loadCanvasStorage(this); + }, + methods: { + async reloadCanvasProperties() { + const canvasProperties = { + mainText: this.mainText, + contractedBy: this.contractedBy, + logoImage: this.logoImage, + logoPosition: this.logoPosition, + colors: this.colors, + }; + + await this.$refs.canvas.redraw(canvasProperties); + + delete canvasProperties.colors; + setCanvasStorage(canvasProperties); }, - data () { - const predefinedColors = { - base: { - name: 'Základní barvy', - colors: { - background: COLORS.black, - highlight: COLORS.yellow1, - arrow: COLORS.yellow1, - baseText: COLORS.white, - highlightedText: COLORS.black, - contractedByText: COLORS.gray1 - } - } + }, + mounted() { + this.$watch( + (vm) => [ + vm.mainText, + vm.contractedBy, + vm.logoImage, + vm.logoPosition, + vm.colors, + ], + async (value) => { + if (this.autoRedraw) { + await this.reloadCanvasProperties(); } - - return { - mainText: null, - contractedBy: DEFAULT_CONTRACTOR, - logoImage: null, - logoPosition: LOGO_POSITIONS.top_right, - logoOptions: generateLogoPositions( - [ - "top_right", - "top_left", - "bottom_left", - "bottom_right", - ] - ), - colorLabels: { - background: 'Pozadí', - highlight: 'Zvýraznění', - arrow: 'Šipka', - baseText: 'Běžný text', - highlightedText: 'Zvýrazněný text' - }, - predefinedColors: predefinedColors, - colors: predefinedColors.base.colors, - predefinedLogoImages: [ - { - name: LOGOS.defaultLight.name, - src: LOGOS.defaultLight.src, - defaultSelected: true, - }, - ], - autoRedraw: false + }, + { + immediate: true, + deep: true, + }, + ); + + this.$watch( + (vm) => [vm.autoRedraw], + async (value) => { + updateAutoRedrawStorage(this.autoRedraw); + + if (this.autoRedraw) { + await this.reloadCanvasProperties(); } - }, - async created () { - await loadCanvasStorage(this) - }, - methods: { - async reloadCanvasProperties () { - const canvasProperties = { - mainText: this.mainText, - contractedBy: this.contractedBy, - logoImage: this.logoImage, - logoPosition: this.logoPosition, - colors: this.colors - } - - await this.$refs.canvas.redraw(canvasProperties) - - delete canvasProperties.colors - setCanvasStorage(canvasProperties) - } - }, - mounted () { - this.$watch( - vm => [ - vm.mainText, - vm.contractedBy, - vm.logoImage, - vm.logoPosition, - 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.text_banner" - ></Navbar> - </header> - <main> - <MainContainer> - <template v-slot:left> - <Canvas - ref="canvas" - :redrawFunction="redraw" - width="2000" - height="2500" - /> - </template> - - <template v-slot:right> - <ReloadButton - :parentRefs="$refs" - @click="reloadCanvasProperties" - /> - <AutoReloadCheckbox - v-model="autoRedraw" - /> - <LongTextInput - name="Text" - v-model="mainText" - :important="true" - :highlightable="true" - zIndex="10" - /> - - <ImageInput - name="Obrázek loga" - v-model="logoImage" - :important="false" - :predefinedImages="predefinedLogoImages" - :mustSelectPredefinedImage="true" - :disableImageInput="true" - zIndex="9" - /> - <SelectInput - name="Pozice loga" - :options="logoOptions" - v-model="logoPosition" - zIndex="8" - /> - - <MultipleColorPicker - name="Barvy" - v-model="colors" - :important="false" - :colorLabels="colorLabels" - :predefinedColors="predefinedColors" - :defaultPredefinedColors="predefinedColors.base" - zIndex="7" - ></MultipleColorPicker> - - <ShortTextInput - name="Zadavatel a zpracovatel" - v-model="contractedBy" - :defaultValue="DEFAULT_CONTRACTOR" - :important="false" - zIndex="4" - /> - </template> - </MainContainer> - </main> + <header> + <Navbar :defaultTemplate="TEMPLATES.text_banner"></Navbar> + </header> + <main> + <MainContainer> + <template v-slot:left> + <Canvas + ref="canvas" + :redrawFunction="redraw" + width="2000" + height="2500" + /> + </template> + + <template v-slot:right> + <ReloadButton :parentRefs="$refs" @click="reloadCanvasProperties" /> + <AutoReloadCheckbox v-model="autoRedraw" /> + <LongTextInput + name="Text" + v-model="mainText" + :important="true" + :highlightable="true" + zIndex="10" + /> + + <ImageInput + name="Obrázek loga" + v-model="logoImage" + :important="false" + :predefinedImages="predefinedLogoImages" + :mustSelectPredefinedImage="true" + :disableImageInput="true" + zIndex="9" + /> + <SelectInput + name="Pozice loga" + :options="logoOptions" + v-model="logoPosition" + zIndex="8" + /> + + <MultipleColorPicker + name="Barvy" + v-model="colors" + :important="false" + :colorLabels="colorLabels" + :predefinedColors="predefinedColors" + :defaultPredefinedColors="predefinedColors.base" + zIndex="7" + ></MultipleColorPicker> + + <ShortTextInput + name="Zadavatel a zpracovatel" + v-model="contractedBy" + :defaultValue="DEFAULT_CONTRACTOR" + :important="false" + zIndex="4" + /> + </template> + </MainContainer> + </main> </template> <style> diff --git a/frontend/src/views/text_banner/canvas.js b/frontend/src/views/text_banner/canvas.js index dcc0213195699a8fc04b43b5172dd90b453bee97..442785f6f48dc225fa21d3af68b6af2c628be2b5 100644 --- a/frontend/src/views/text_banner/canvas.js +++ b/frontend/src/views/text_banner/canvas.js @@ -1,260 +1,210 @@ -import { fabric } from 'fabric' -import { clearObjects, sortObjects, transformHighlightedText, checkTextBoxHeight } from '../../components/canvas/utils' -import { PaddedHighlightingTextbox } from '../../components/canvas/textbox' +import { fabric } from "fabric"; +import { + clearObjects, + sortObjects, + transformHighlightedText, + checkTextBoxHeight, +} from "../../components/canvas/utils"; +import { PaddedHighlightingTextbox } from "../../components/canvas/textbox"; -let textBox = null +let textBox = null; -let backgroundRect = null -let arrow = null +let backgroundRect = null; +let arrow = null; -let contractedByTextbox = null +let contractedByTextbox = null; -let logoImage = null -let previousLogoPosition = null +let logoImage = null; +let previousLogoPosition = null; const redraw = async (canvas, options) => { - clearObjects( - [ - textBox, - contractedByTextbox, - backgroundRect, - arrow - ], - canvas - ) - - const logoWidth = Math.ceil(canvas.width * 0.23) - const logoSideMargin = Math.ceil(canvas.width * 0.07) - const logoTopMargin = Math.ceil(canvas.width * 0.155) - - const textBoxWidth = Math.ceil(canvas.width * 0.8) - const textBoxMarginSide = Math.ceil(canvas.width * 0.13) - const textBoxMarginTop = logoTopMargin + Math.ceil(canvas.height * 0.15) - const textSize = Math.ceil(canvas.height * 0.07) - const textLineHeight = 1 - - const contractedByTextSize = Math.ceil(canvas.height * 0.02) - const contractedByTextMaxWidth = Math.ceil(canvas.width * 0.9) - const contractedByTextSidesMargin = Math.ceil(canvas.width * 0.03) - - const arrowWidth = Math.ceil(canvas.width * 0.047) - const arrowHeight = Math.ceil(canvas.width * 0.055) - const arrowThickness = Math.ceil(canvas.width * 0.019) - const arrowMarginLeft = Math.ceil(canvas.width * 0.07) - const arrowMarginLeftExtra = Math.ceil(canvas.width * -0.01) - const arrowMarginTop = Math.ceil(canvas.height * 0.017) - - canvas.preserveObjectStacking = true - - - /* BEGIN Background render */ - - backgroundRect = new fabric.Rect({ - width: canvas.width * 1.1, - height: canvas.height * 1.1, - top: -20, // FIXME: Why???? Fabric.js, what are you trying to tell me?! - left: -20, - fill: options.colors.background.value, - selectable: false, - zIndex: 0 - }) - - canvas.add(backgroundRect) - - /* 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: logoTopMargin, - zIndex: 11, - }) - } else if (options.logoPosition.id == "top-left") { - logoImage.set({ - left: logoSideMargin, - top: logoTopMargin, - zIndex: 11, - }) - } else if (options.logoPosition.id == "bottom-left") { - logoImage.set({ - left: logoSideMargin, - top: ( - canvas.height - - logoTopMargin - - logoImage.getScaledHeight() - ), - zIndex: 11, - }) - } else { - logoImage.set({ - left: ( - canvas.width - - logoWidth - - logoSideMargin - ), - top: ( - canvas.height - - logoTopMargin - - logoImage.getScaledHeight() - ), - zIndex: 11, - }) - } - - canvas.add(logoImage) + clearObjects([textBox, contractedByTextbox, backgroundRect, arrow], canvas); + + const logoWidth = Math.ceil(canvas.width * 0.23); + const logoSideMargin = Math.ceil(canvas.width * 0.07); + const logoTopMargin = Math.ceil(canvas.width * 0.155); + + const textBoxWidth = Math.ceil(canvas.width * 0.8); + const textBoxMarginSide = Math.ceil(canvas.width * 0.13); + const textBoxMarginTop = logoTopMargin + Math.ceil(canvas.height * 0.15); + const textSize = Math.ceil(canvas.height * 0.07); + const textLineHeight = 1; + + const contractedByTextSize = Math.ceil(canvas.height * 0.02); + const contractedByTextMaxWidth = Math.ceil(canvas.width * 0.9); + const contractedByTextSidesMargin = Math.ceil(canvas.width * 0.03); + + const arrowWidth = Math.ceil(canvas.width * 0.047); + const arrowHeight = Math.ceil(canvas.width * 0.055); + const arrowThickness = Math.ceil(canvas.width * 0.019); + const arrowMarginLeft = Math.ceil(canvas.width * 0.07); + const arrowMarginLeftExtra = Math.ceil(canvas.width * -0.01); + const arrowMarginTop = Math.ceil(canvas.height * 0.017); + + canvas.preserveObjectStacking = true; + + /* BEGIN Background render */ + + backgroundRect = new fabric.Rect({ + width: canvas.width * 1.1, + height: canvas.height * 1.1, + top: -20, // FIXME: Why???? Fabric.js, what are you trying to tell me?! + left: -20, + fill: options.colors.background.value, + selectable: false, + zIndex: 0, + }); + + canvas.add(backgroundRect); + + /* 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: logoTopMargin, + zIndex: 11, + }); + } else if (options.logoPosition.id == "top-left") { + logoImage.set({ + left: logoSideMargin, + top: logoTopMargin, + zIndex: 11, + }); + } else if (options.logoPosition.id == "bottom-left") { + logoImage.set({ + left: logoSideMargin, + top: canvas.height - logoTopMargin - logoImage.getScaledHeight(), + zIndex: 11, + }); + } else { + logoImage.set({ + left: canvas.width - logoWidth - logoSideMargin, + top: canvas.height - logoTopMargin - logoImage.getScaledHeight(), + zIndex: 11, + }); } - /* END Logo render */ - - - if (options.mainText !== null) { - /* BEGIN Text render */ - - const highlightedData = transformHighlightedText( - options.mainText, - textSize, - textBoxWidth, - 'Bebas Neue', - options.colors.highlight.value, - options.colors.highlightedText.value, - {padWhenDiacritics: true} - ) - - const textBoxTop = textBoxMarginTop - - textBox = new PaddedHighlightingTextbox( - highlightedData.text, - { - width: canvas.width, - left: textBoxMarginSide, - top: ( - textBoxTop - - highlightedData.paddingBottom - ), - textAlign: 'left', - fontFamily: 'Bebas Neue', - fontSize: textSize, - lineHeight: textLineHeight, - fill: options.colors.baseText.value, - styles: highlightedData.styles, - selectable: false, - highlightPadding: (canvas.height * 0.003), - zIndex: 10 - } - ) - - checkTextBoxHeight(textBox, 7) - - canvas.add(textBox) - - /* END Text render */ - - - /* BEGIN Contracted by render */ - - if (options.contractedBy !== null) { - contractedByTextbox = new fabric.Textbox( - options.contractedBy, - { - left: canvas.width - contractedByTextMaxWidth - contractedByTextSidesMargin, - top: ( - canvas.height - - contractedByTextSidesMargin - - contractedByTextSize - ), - width: contractedByTextMaxWidth, - fontFamily: 'Roboto Condensed', - fontSize: contractedByTextSize, - textAlign: 'right', - fill: options.colors.contractedByText.value, - selectable: false, - zIndex: 10 - } - ) - - checkTextBoxHeight(contractedByTextbox, 1) - - canvas.add(contractedByTextbox) - } - - /* END Contracted by render */ - - - /* BEGIN Arrow render */ - - arrow = new fabric.Polygon( - [ - {x: 0, y: 0}, - {x: arrowThickness, y: 0}, - { - x: arrowWidth, - y: Math.ceil(arrowHeight) / 2 - }, - { - x: arrowThickness, - y: arrowHeight - }, - {x: 0, y: arrowHeight}, - { - x: arrowWidth - arrowThickness, - y: Math.ceil(arrowHeight) / 2 - }, - {x: 0, y: 0} - ], - { - top: ( - textBoxTop - + arrowMarginTop - ), - left: ( - arrowMarginLeft - + ( - (options.mainText[0] === "*") ? - arrowMarginLeftExtra : 0 - ) - ), - fill: options.colors.arrow.value, - selectable: false, - zIndex: 10 - } - ) - - canvas.add(arrow) - - /* END Arrow render */ + canvas.add(logoImage); + } + + /* END Logo render */ + + if (options.mainText !== null) { + /* BEGIN Text render */ + + const highlightedData = transformHighlightedText( + options.mainText, + textSize, + textBoxWidth, + "Bebas Neue", + options.colors.highlight.value, + options.colors.highlightedText.value, + { padWhenDiacritics: true }, + ); + + const textBoxTop = textBoxMarginTop; + + textBox = new PaddedHighlightingTextbox(highlightedData.text, { + width: canvas.width, + left: textBoxMarginSide, + top: textBoxTop - highlightedData.paddingBottom, + textAlign: "left", + fontFamily: "Bebas Neue", + fontSize: textSize, + lineHeight: textLineHeight, + fill: options.colors.baseText.value, + styles: highlightedData.styles, + selectable: false, + highlightPadding: canvas.height * 0.003, + zIndex: 10, + }); + + checkTextBoxHeight(textBox, 7); + + canvas.add(textBox); + + /* END Text render */ + + /* BEGIN Contracted by render */ + + if (options.contractedBy !== null) { + contractedByTextbox = new fabric.Textbox(options.contractedBy, { + left: + canvas.width - contractedByTextMaxWidth - contractedByTextSidesMargin, + top: canvas.height - contractedByTextSidesMargin - contractedByTextSize, + width: contractedByTextMaxWidth, + fontFamily: "Roboto Condensed", + fontSize: contractedByTextSize, + textAlign: "right", + fill: options.colors.contractedByText.value, + selectable: false, + zIndex: 10, + }); + + checkTextBoxHeight(contractedByTextbox, 1); + + canvas.add(contractedByTextbox); } + /* END Contracted by render */ + + /* BEGIN Arrow render */ + + arrow = new fabric.Polygon( + [ + { x: 0, y: 0 }, + { x: arrowThickness, y: 0 }, + { + x: arrowWidth, + y: Math.ceil(arrowHeight) / 2, + }, + { + x: arrowThickness, + y: arrowHeight, + }, + { x: 0, y: arrowHeight }, + { + x: arrowWidth - arrowThickness, + y: Math.ceil(arrowHeight) / 2, + }, + { x: 0, y: 0 }, + ], + { + top: textBoxTop + arrowMarginTop, + left: + arrowMarginLeft + + (options.mainText[0] === "*" ? arrowMarginLeftExtra : 0), + fill: options.colors.arrow.value, + selectable: false, + zIndex: 10, + }, + ); + + canvas.add(arrow); + + /* END Arrow render */ + } - sortObjects(canvas) -} + sortObjects(canvas); +}; -export default redraw +export default redraw; diff --git a/frontend/src/views/twitter_banner/PersonInput.vue b/frontend/src/views/twitter_banner/PersonInput.vue index 4255c9e8f492c664947df8e3049dc8520333fe2b..c4b11fb11a89e3610d746505b2345cc6b5f09d92 100644 --- a/frontend/src/views/twitter_banner/PersonInput.vue +++ b/frontend/src/views/twitter_banner/PersonInput.vue @@ -1,70 +1,64 @@ <script setup> -import VueSelect from 'vue-select' -import InputHeading from "../../components/inputs/InputHeading.vue" +import VueSelect from "vue-select"; +import InputHeading from "../../components/inputs/InputHeading.vue"; </script> <script> export default { - components: { InputHeading, VueSelect }, - props: [ - 'name', - 'important', - 'zIndex', - 'mainImage', - 'personName', - 'personTwitter', - 'defaultSelection', - 'options' - ], - emits: ['update:mainImage', 'update:personName', 'update:personTwitter'], - data () { - return { - selectedOption: this.defaultSelection - } - }, - watch: { - selectedOption: { - async handler (value) { - const mainImage = new Image() + components: { InputHeading, VueSelect }, + props: [ + "name", + "important", + "zIndex", + "mainImage", + "personName", + "personTwitter", + "defaultSelection", + "options", + ], + emits: ["update:mainImage", "update:personName", "update:personTwitter"], + data() { + return { + selectedOption: this.defaultSelection, + }; + }, + watch: { + selectedOption: { + async handler(value) { + const mainImage = new Image(); - await new Promise( - resolve => { - mainImage.onload = () => { - resolve() - } + await new Promise((resolve) => { + mainImage.onload = () => { + resolve(); + }; - mainImage.src = value.mainImage - } - ) + mainImage.src = value.mainImage; + }); - this.$emit('update:mainImage', mainImage) - this.$emit('update:personName', value.title) - this.$emit('update:personTwitter', value.personTwitter) - }, - immediate: true - } - } -} + this.$emit("update:mainImage", mainImage); + this.$emit("update:personName", value.title); + this.$emit("update:personTwitter", value.personTwitter); + }, + immediate: true, + }, + }, +}; </script> <template> - <section - class="flex flex-col gap-2 bg-gray-100 p-4 drop-shadow-md" - :style="{'z-index': zIndex}" - > - <InputHeading - :name="name" - :important="important" - icon="cog" - ></InputHeading> - <VueSelect - :options="options" - :clearable="false" - v-model="selectedOption" - label="title" - ></VueSelect> - <small class="text-gray-600"> - <em>Obrázek se může chvíli načítat.</em> - </small> - </section> + <section + class="flex flex-col gap-2 bg-gray-100 p-4 drop-shadow-md" + :style="{ 'z-index': zIndex }" + > + <InputHeading :name="name" :important="important" icon="cog"></InputHeading> + <VueSelect + :options="options" + :clearable="false" + v-model="selectedOption" + label="title" + ></VueSelect> + <small class="text-gray-600"> + <em>Obrázek se může chvíli načítat.</em> + </small> + </section> </template> diff --git a/frontend/src/views/twitter_banner/TwitterBanner.vue b/frontend/src/views/twitter_banner/TwitterBanner.vue index 45ed10d517ea1df91665557827e020177b560035..f492fcfbeee6795807a350fb64928078967c1f4f 100644 --- a/frontend/src/views/twitter_banner/TwitterBanner.vue +++ b/frontend/src/views/twitter_banner/TwitterBanner.vue @@ -1,227 +1,220 @@ <script setup> -import { watch, ref } from 'vue' - -import COLORS from '../../colors' -import TEMPLATES from '../../templates' -import DEFAULT_CONTRACTOR from '../../contractors' -import { generateDefaultLogos, LOGO_POSITIONS, generateLogoPositions } from '../../logos' -import { loadFonts, loadCanvasStorage, setCanvasStorage, updateAutoRedrawStorage } from '../../utils' - -import defaultDarkLogoImage from '../../assets/logos/default-dark.png' - -import Canvas from '../../components/canvas/Canvas.vue' -import redraw from './canvas' - -import Navbar from '../../components/Navbar.vue' -import MainContainer from '../../components/MainContainer.vue' -import LongTextInput from '../../components/inputs/text/LongTextInput.vue' -import ShortTextInput from '../../components/inputs/text/ShortTextInput.vue' -import PersonInput from './PersonInput.vue' -import SelectInput from '../../components/inputs/SelectInput.vue' -import ReloadButton from '../../components/reload/ReloadButton.vue' -import AutoReloadCheckbox from '../../components/reload/AutoReloadCheckbox.vue' - -import klaraImage from '../../assets/template/twitter_banner/klara.png' -import ivanImage from '../../assets/template/twitter_banner/ivan.png' -import jakubImage from '../../assets/template/twitter_banner/jakub.png' -import lipoImage from '../../assets/template/twitter_banner/lipo.png' -import olgaImage from '../../assets/template/twitter_banner/olga.png' - -import twitterLogoImage from '../../assets/template/twitter_banner/twitter.png' +import { watch, ref } from "vue"; + +import COLORS from "../../colors"; +import TEMPLATES from "../../templates"; +import DEFAULT_CONTRACTOR from "../../contractors"; +import { + generateDefaultLogos, + LOGO_POSITIONS, + generateLogoPositions, +} from "../../logos"; +import { + loadFonts, + loadCanvasStorage, + setCanvasStorage, + updateAutoRedrawStorage, +} from "../../utils"; + +import defaultDarkLogoImage from "../../assets/logos/default-dark.png"; + +import Canvas from "../../components/canvas/Canvas.vue"; +import redraw from "./canvas"; + +import Navbar from "../../components/Navbar.vue"; +import MainContainer from "../../components/MainContainer.vue"; +import LongTextInput from "../../components/inputs/text/LongTextInput.vue"; +import ShortTextInput from "../../components/inputs/text/ShortTextInput.vue"; +import PersonInput from "./PersonInput.vue"; +import SelectInput from "../../components/inputs/SelectInput.vue"; +import ReloadButton from "../../components/reload/ReloadButton.vue"; +import AutoReloadCheckbox from "../../components/reload/AutoReloadCheckbox.vue"; + +import klaraImage from "../../assets/template/twitter_banner/klara.png"; +import ivanImage from "../../assets/template/twitter_banner/ivan.png"; +import jakubImage from "../../assets/template/twitter_banner/jakub.png"; +import lipoImage from "../../assets/template/twitter_banner/lipo.png"; +import olgaImage from "../../assets/template/twitter_banner/olga.png"; + +import twitterLogoImage from "../../assets/template/twitter_banner/twitter.png"; </script> <script> -await loadFonts([ - '12px Bebas Neue', - '12px Roboto Condensed', - '12px Roboto' -]) +await loadFonts(["12px Bebas Neue", "12px Roboto Condensed", "12px Roboto"]); export default { - components: { - Canvas, - Navbar, - MainContainer, - LongTextInput, - ShortTextInput, - PersonInput, + components: { + Canvas, + Navbar, + MainContainer, + LongTextInput, + ShortTextInput, + PersonInput, + }, + data() { + const personOptions = { + klara: { + title: "Klára Kocmanová", + personTwitter: "KlaraKocmanova", + mainImage: klaraImage, + }, + ivan: { + title: "Ivan Bartoš", + personTwitter: "PiratIvanBartos", + mainImage: ivanImage, + }, + jakub: { + title: "Jakub Michálek", + personTwitter: "JakubMichalek19", + mainImage: jakubImage, + }, + lipo: { + title: "Jan Lipavský", + personTwitter: "JanLipavsky", + mainImage: lipoImage, + }, + olga: { + title: "Olga Richterová", + personTwitter: "olgarichterova", + mainImage: olgaImage, + }, + }; + + return { + defaultSelection: personOptions.klara, + personOptions: personOptions, + mainImage: null, + mainText: null, + personName: null, + personTwitter: null, + contractedBy: DEFAULT_CONTRACTOR, + logoImageSource: defaultDarkLogoImage, + logoPosition: LOGO_POSITIONS.bottom_left, + logoOptions: generateLogoPositions(["bottom_left", "top_left"]), + twitterLogoImageSource: twitterLogoImage, + colors: { + background: COLORS.white, + text: COLORS.black, + highlight: COLORS.yellow1, + highlightedText: COLORS.black, + contractedByText: COLORS.gray2, + }, + autoRedraw: false, + }; + }, + async created() { + await loadCanvasStorage(this); + }, + methods: { + async reloadCanvasProperties() { + const canvasProperties = { + colors: this.colors, + logoImageSource: this.logoImageSource, + logoPosition: this.logoPosition, + twitterLogoImageSource: this.twitterLogoImageSource, + mainImage: this.mainImage, + mainText: this.mainText, + personName: this.personName, + personTwitter: this.personTwitter, + contractedBy: this.contractedBy, + }; + + await this.$refs.canvas.redraw(canvasProperties); + + delete canvasProperties.colors; + setCanvasStorage(canvasProperties); }, - data () { - const personOptions = { - klara: { - title: 'Klára Kocmanová', - personTwitter: 'KlaraKocmanova', - mainImage: klaraImage - }, - ivan: { - title: 'Ivan Bartoš', - personTwitter: 'PiratIvanBartos', - mainImage: ivanImage - }, - jakub: { - title: 'Jakub Michálek', - personTwitter: 'JakubMichalek19', - mainImage: jakubImage - }, - lipo: { - title: 'Jan Lipavský', - personTwitter: 'JanLipavsky', - mainImage: lipoImage - }, - olga: { - title: 'Olga Richterová', - personTwitter: 'olgarichterova', - mainImage: olgaImage - } + }, + mounted() { + this.$watch( + (vm) => [ + vm.colors, + vm.logoImageSource, + vm.logoPosition, + vm.twitterLogoImageSource, + vm.mainImage, + vm.mainText, + vm.personName, + vm.personTwitter, + vm.contractedBy, + ], + async (value) => { + if (this.autoRedraw) { + await this.reloadCanvasProperties(); } - - return { - defaultSelection: personOptions.klara, - personOptions: personOptions, - mainImage: null, - mainText: null, - personName: null, - personTwitter: null, - contractedBy: DEFAULT_CONTRACTOR, - logoImageSource: defaultDarkLogoImage, - logoPosition: LOGO_POSITIONS.bottom_left, - logoOptions: generateLogoPositions( - [ - "bottom_left", - "top_left", - ] - ), - twitterLogoImageSource: twitterLogoImage, - colors: { - background: COLORS.white, - text: COLORS.black, - highlight: COLORS.yellow1, - highlightedText: COLORS.black, - contractedByText: COLORS.gray2 - }, - autoRedraw: false + }, + { + immediate: true, + deep: true, + }, + ); + + this.$watch( + (vm) => [vm.autoRedraw], + async (value) => { + updateAutoRedrawStorage(this.autoRedraw); + + if (this.autoRedraw) { + await this.reloadCanvasProperties(); } - }, - async created () { - await loadCanvasStorage(this) - }, - methods: { - async reloadCanvasProperties () { - const canvasProperties = { - colors: this.colors, - logoImageSource: this.logoImageSource, - logoPosition: this.logoPosition, - twitterLogoImageSource: this.twitterLogoImageSource, - mainImage: this.mainImage, - mainText: this.mainText, - personName: this.personName, - personTwitter: this.personTwitter, - contractedBy: this.contractedBy - } - - await this.$refs.canvas.redraw(canvasProperties) - - delete canvasProperties.colors - setCanvasStorage(canvasProperties) - } - }, - mounted () { - this.$watch( - vm => [ - vm.colors, - vm.logoImageSource, - vm.logoPosition, - vm.twitterLogoImageSource, - vm.mainImage, - vm.mainText, - vm.personName, - vm.personTwitter, - vm.contractedBy - ], - 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.twitter_banner" - ></Navbar> - </header> - <main> - <MainContainer> - <template v-slot:left> - <Canvas - ref="canvas" - :redrawFunction="redraw" - width="2000" - height="2000" - /> - </template> - - <template v-slot:right> - <ReloadButton - :parentRefs="$refs" - @click="reloadCanvasProperties" - /> - <AutoReloadCheckbox - v-model="autoRedraw" - /> - <PersonInput - name="Osoba" - v-model:mainImage="mainImage" - v-model:personName="personName" - v-model:personTwitter="personTwitter" - :defaultSelection="defaultSelection" - :options="Object.values(personOptions)" - :important="true" - zIndex="10" - /> - <LongTextInput - name="Text" - v-model="mainText" - :important="true" - zIndex="9" - /> - - <SelectInput - name="Pozice loga" - :options="logoOptions" - v-model="logoPosition" - zIndex="8" - /> - - <ShortTextInput - name="Zadavatel a zpracovatel" - v-model="contractedBy" - :defaultValue="DEFAULT_CONTRACTOR" - :important="false" - zIndex="4" - /> - </template> - </MainContainer> - </main> + <header> + <Navbar :defaultTemplate="TEMPLATES.twitter_banner"></Navbar> + </header> + <main> + <MainContainer> + <template v-slot:left> + <Canvas + ref="canvas" + :redrawFunction="redraw" + width="2000" + height="2000" + /> + </template> + + <template v-slot:right> + <ReloadButton :parentRefs="$refs" @click="reloadCanvasProperties" /> + <AutoReloadCheckbox v-model="autoRedraw" /> + <PersonInput + name="Osoba" + v-model:mainImage="mainImage" + v-model:personName="personName" + v-model:personTwitter="personTwitter" + :defaultSelection="defaultSelection" + :options="Object.values(personOptions)" + :important="true" + zIndex="10" + /> + <LongTextInput + name="Text" + v-model="mainText" + :important="true" + zIndex="9" + /> + + <SelectInput + name="Pozice loga" + :options="logoOptions" + v-model="logoPosition" + zIndex="8" + /> + + <ShortTextInput + name="Zadavatel a zpracovatel" + v-model="contractedBy" + :defaultValue="DEFAULT_CONTRACTOR" + :important="false" + zIndex="4" + /> + </template> + </MainContainer> + </main> </template> <style> diff --git a/frontend/src/views/twitter_banner/canvas.js b/frontend/src/views/twitter_banner/canvas.js index a392bed8f8a09adb8c2c9073e040500c046bd638..56ce082d4d2bc05d0d7b3da09e61137e4bf59105 100644 --- a/frontend/src/views/twitter_banner/canvas.js +++ b/frontend/src/views/twitter_banner/canvas.js @@ -1,337 +1,281 @@ -import { fabric } from 'fabric' -import { clearObjects, sortObjects, transformHighlightedText, checkTextBoxHeight } from '../../components/canvas/utils' -import { PaddedHighlightingTextbox } from '../../components/canvas/textbox' +import { fabric } from "fabric"; +import { + clearObjects, + sortObjects, + transformHighlightedText, + checkTextBoxHeight, +} from "../../components/canvas/utils"; +import { PaddedHighlightingTextbox } from "../../components/canvas/textbox"; -let background = null +let background = null; -let personNameTextBox = null -let personTwitterTextBox = null -let mainTextBox = null +let personNameTextBox = null; +let personTwitterTextBox = null; +let mainTextBox = null; -let contractedByTextbox = null +let contractedByTextbox = null; -let mainImage = null -let logoImage = null -let twitterLogoImage = null -let previousLogoPosition = null +let mainImage = null; +let logoImage = null; +let twitterLogoImage = null; +let previousLogoPosition = null; const redraw = async (canvas, options) => { - clearObjects( - [ - mainTextBox, - personNameTextBox, - personTwitterTextBox, - contractedByTextbox, - ], - canvas - ) - - canvas.preserveObjectStacking = true - - - const mainImageOffsetSide = Math.ceil(canvas.width * 0.075) - const mainImageWidth = Math.ceil(canvas.width * 0.425) - const mainImageOffsetTop = Math.ceil(canvas.height * 0.17) - - const logoSideMargin = Math.ceil(canvas.width * 0.075) - const logoWidth = Math.ceil(canvas.width * 0.25) - - const twitterLogoImageWidth = Math.ceil(canvas.width * 0.08) - const twitterLogoOffsetTop = Math.ceil(canvas.height * 0.1) - - const mainTextBoxWidth = Math.ceil(canvas.width * 0.38) - const mainTextBoxMarginSide = Math.ceil(canvas.width * 0.06) - const mainTextBoxMarginTop = Math.ceil(canvas.height * 0.03) - const mainTextBoxFontSize = Math.ceil(canvas.height * 0.038) - const mainTextBoxLineHeight = 1 - const mainTextBoxCharSpacing = -Math.ceil(canvas.width * 0.015) - - const contractedByTextSize = Math.ceil(canvas.height * 0.02) - const contractedByTextMaxWidth = Math.ceil(canvas.width * 0.9) - const contractedByTextSidesMargin = Math.ceil(canvas.width * 0.03) - - const nameTextFontSize = Math.ceil(canvas.height * 0.065) - const nameTextOffsetTop = Math.ceil(canvas.height * 0.03) - - const twitterTextFontSize = Math.ceil(canvas.height * 0.037) - const twitterTextOffsetTop = Math.ceil(canvas.height * -0.007) - - - /* BEGIN Background render */ - - if (background === null) { - background = new fabric.Rect({ - width: canvas.width * 1.1, - height: canvas.height * 1.1, - top: -20, // FIXME: Why???? Fabric.js, what are you trying to tell me?! - left: -20, - fill: options.colors.background.value - }) - - canvas.add(background) + clearObjects( + [mainTextBox, personNameTextBox, personTwitterTextBox, contractedByTextbox], + canvas, + ); + + canvas.preserveObjectStacking = true; + + const mainImageOffsetSide = Math.ceil(canvas.width * 0.075); + const mainImageWidth = Math.ceil(canvas.width * 0.425); + const mainImageOffsetTop = Math.ceil(canvas.height * 0.17); + + const logoSideMargin = Math.ceil(canvas.width * 0.075); + const logoWidth = Math.ceil(canvas.width * 0.25); + + const twitterLogoImageWidth = Math.ceil(canvas.width * 0.08); + const twitterLogoOffsetTop = Math.ceil(canvas.height * 0.1); + + const mainTextBoxWidth = Math.ceil(canvas.width * 0.38); + const mainTextBoxMarginSide = Math.ceil(canvas.width * 0.06); + const mainTextBoxMarginTop = Math.ceil(canvas.height * 0.03); + const mainTextBoxFontSize = Math.ceil(canvas.height * 0.038); + const mainTextBoxLineHeight = 1; + const mainTextBoxCharSpacing = -Math.ceil(canvas.width * 0.015); + + const contractedByTextSize = Math.ceil(canvas.height * 0.02); + const contractedByTextMaxWidth = Math.ceil(canvas.width * 0.9); + const contractedByTextSidesMargin = Math.ceil(canvas.width * 0.03); + + const nameTextFontSize = Math.ceil(canvas.height * 0.065); + const nameTextOffsetTop = Math.ceil(canvas.height * 0.03); + + const twitterTextFontSize = Math.ceil(canvas.height * 0.037); + const twitterTextOffsetTop = Math.ceil(canvas.height * -0.007); + + /* BEGIN Background render */ + + if (background === null) { + background = new fabric.Rect({ + width: canvas.width * 1.1, + height: canvas.height * 1.1, + top: -20, // FIXME: Why???? Fabric.js, what are you trying to tell me?! + left: -20, + fill: options.colors.background.value, + }); + + canvas.add(background); + } + + /* END Background render */ + + /* BEGIN Twitter logo render */ + + if (twitterLogoImage === null) { + const twitterLogoImageElement = new Image(); + + await new Promise((resolve) => { + twitterLogoImageElement.onload = () => { + resolve(); + }; + + twitterLogoImageElement.src = options.twitterLogoImageSource; + }); + + twitterLogoImage = new fabric.Image(twitterLogoImageElement, { + selectable: false, + }); + twitterLogoImage.scaleToWidth(twitterLogoImageWidth); + twitterLogoImage.set({ + left: + canvas.width - + mainTextBoxMarginSide - + (mainTextBoxWidth + twitterLogoImageWidth) / 2, + top: twitterLogoOffsetTop, + zIndex: 11, + }); + + canvas.add(twitterLogoImage); + } + + /* END Twitter logo render */ + + if (options.personName !== null) { + /* BEGIN Name text render */ + + personNameTextBox = new fabric.Textbox(options.personName, { + width: mainTextBoxWidth, + textAlign: "center", + fontSize: nameTextFontSize, + fontFamily: "Bebas Neue", + fill: options.colors.text.value, + left: canvas.width - mainTextBoxMarginSide - mainTextBoxWidth, + top: + twitterLogoImage.top + + twitterLogoImage.getScaledHeight() + + nameTextOffsetTop, + selectable: false, + zIndex: 11, + }); + + canvas.add(personNameTextBox); + + /* END Name text render */ + + if (options.personTwitter !== null) { + /* BEGIN Twitter handle text render */ + + personTwitterTextBox = new fabric.Textbox(`@${options.personTwitter}`, { + width: mainTextBoxWidth, + textAlign: "center", + fontSize: twitterTextFontSize, + fontFamily: "Roboto", + fill: options.colors.text.value, + left: canvas.width - mainTextBoxMarginSide - mainTextBoxWidth, + top: + personNameTextBox.top + + personNameTextBox.height + + twitterTextOffsetTop, + selectable: false, + zIndex: 11, + }); + + canvas.add(personTwitterTextBox); + + /* END Twitter handle text render */ + + /* BEGIN Main text render */ + + if (options.mainText !== null) { + const highlightedData = transformHighlightedText( + options.mainText, + mainTextBoxFontSize, + mainTextBoxWidth, + "Roboto Condensed", + options.colors.highlight.value, + options.colors.highlightedText.value, + { prependLinesWithSpace: true }, + ); + + mainTextBox = new PaddedHighlightingTextbox(highlightedData.text, { + width: canvas.width, + textAlign: "left", + fontSize: mainTextBoxFontSize, + fontFamily: "Roboto Condensed", + lineHeight: mainTextBoxLineHeight, + charSpacing: mainTextBoxCharSpacing, + fill: options.colors.text.value, + styles: highlightedData.styles, + left: canvas.width - mainTextBoxMarginSide - mainTextBoxWidth, + top: + personTwitterTextBox.top + + personTwitterTextBox.height + + mainTextBoxMarginTop, + selectable: false, + highlightPadding: canvas.height * 0.003, + zIndex: 11, + }); + + checkTextBoxHeight(mainTextBox, 14); + + canvas.add(mainTextBox); + } + + /* END Main text render */ } + } - /* END Background render */ - - - /* BEGIN Twitter logo render */ - - if (twitterLogoImage === null) { - const twitterLogoImageElement = new Image() - - await new Promise( - resolve => { - twitterLogoImageElement.onload = () => { - resolve() - } + /* BEGIN Logo render */ - twitterLogoImageElement.src = options.twitterLogoImageSource - } - ) - - twitterLogoImage = new fabric.Image(twitterLogoImageElement, {selectable: false}) - twitterLogoImage.scaleToWidth(twitterLogoImageWidth) - twitterLogoImage.set({ - left: ( - canvas.width - - mainTextBoxMarginSide - - ( - mainTextBoxWidth - + twitterLogoImageWidth - ) / 2 - ), - top: twitterLogoOffsetTop, - zIndex: 11 - }) - - canvas.add(twitterLogoImage) + if (logoImage === null || previousLogoPosition != options.logoPosition.id) { + if (logoImage !== null) { + canvas.remove(logoImage); } - /* END Twitter logo render */ - - - if (options.personName !== null) { - /* BEGIN Name text render */ - - personNameTextBox = new fabric.Textbox( - options.personName, - { - width: mainTextBoxWidth, - textAlign: 'center', - fontSize: nameTextFontSize, - fontFamily: 'Bebas Neue', - fill: options.colors.text.value, - left: ( - canvas.width - - mainTextBoxMarginSide - - mainTextBoxWidth - ), - top: ( - twitterLogoImage.top - + twitterLogoImage.getScaledHeight() - + nameTextOffsetTop - ), - selectable: false, - zIndex: 11 - } - ) - - canvas.add(personNameTextBox) - - /* END Name text render */ - - - if (options.personTwitter !== null) { - /* BEGIN Twitter handle text render */ - - personTwitterTextBox = new fabric.Textbox( - `@${options.personTwitter}`, - { - width: mainTextBoxWidth, - textAlign: 'center', - fontSize: twitterTextFontSize, - fontFamily: 'Roboto', - fill: options.colors.text.value, - left: ( - canvas.width - - mainTextBoxMarginSide - - mainTextBoxWidth - ), - top: ( - personNameTextBox.top - + personNameTextBox.height - + twitterTextOffsetTop - ), - selectable: false, - zIndex: 11 - } - ) - - canvas.add(personTwitterTextBox) - - /* END Twitter handle text render */ - - - /* BEGIN Main text render */ - - if (options.mainText !== null) { - const highlightedData = transformHighlightedText( - options.mainText, - mainTextBoxFontSize, - mainTextBoxWidth, - 'Roboto Condensed', - options.colors.highlight.value, - options.colors.highlightedText.value, - {prependLinesWithSpace: true} - ) - - mainTextBox = new PaddedHighlightingTextbox( - highlightedData.text, - { - width: canvas.width, - textAlign: 'left', - fontSize: mainTextBoxFontSize, - fontFamily: 'Roboto Condensed', - lineHeight: mainTextBoxLineHeight, - charSpacing: mainTextBoxCharSpacing, - fill: options.colors.text.value, - styles: highlightedData.styles, - left: ( - canvas.width - - mainTextBoxMarginSide - - mainTextBoxWidth - ), - top: ( - personTwitterTextBox.top - + personTwitterTextBox.height - + mainTextBoxMarginTop - ), - selectable: false, - highlightPadding: (canvas.height * 0.003), - zIndex: 11 - } - ) - - checkTextBoxHeight(mainTextBox, 14) - - canvas.add(mainTextBox) - } - - /* END Main text render */ - } + const logoImageElement = new Image(); + + await new Promise((resolve) => { + logoImageElement.onload = () => { + resolve(); + }; + + logoImageElement.src = options.logoImageSource; + }); + + logoImage = new fabric.Image(logoImageElement, { selectable: false }); + logoImage.scaleToWidth(logoWidth); + + if (options.logoPosition.id == "top-left") { + logoImage.set({ + left: logoSideMargin, + top: logoSideMargin, + zIndex: 11, + }); + } else { + logoImage.set({ + left: logoSideMargin, + top: canvas.height - logoSideMargin - logoImage.getScaledHeight(), + zIndex: 11, + }); } + canvas.add(logoImage); + } - /* BEGIN Logo render */ - - if (logoImage === null || previousLogoPosition != options.logoPosition.id) { - if (logoImage !== null) { - canvas.remove(logoImage) - } - - const logoImageElement = new Image(); - - await new Promise( - resolve => { - logoImageElement.onload = () => { - resolve() - } - - logoImageElement.src = options.logoImageSource - } - ) - - logoImage = new fabric.Image(logoImageElement, {selectable: false}) - logoImage.scaleToWidth(logoWidth) - - if (options.logoPosition.id == "top-left") { - logoImage.set({ - left: logoSideMargin, - top: logoSideMargin, - zIndex: 11, - }) - } else { - logoImage.set({ - left: logoSideMargin, - top: ( - canvas.height - - logoSideMargin - - logoImage.getScaledHeight() - ), - zIndex: 11, - }) - } - - canvas.add(logoImage) - } + previousLogoPosition = options.logoPosition.id; - previousLogoPosition = options.logoPosition.id + /* END Logo render */ - /* END Logo render */ + /* BEGIN Contracted by render */ + if (options.contractedBy !== null) { + contractedByTextbox = new fabric.Textbox(options.contractedBy, { + left: + canvas.width - contractedByTextMaxWidth - contractedByTextSidesMargin, + top: canvas.height - contractedByTextSidesMargin - contractedByTextSize, + width: contractedByTextMaxWidth, + fontFamily: "Roboto Condensed", + fontSize: contractedByTextSize, + textAlign: "right", + fill: options.colors.contractedByText.value, + selectable: false, + zIndex: 10, + }); - /* BEGIN Contracted by render */ + checkTextBoxHeight(contractedByTextbox, 1); - if (options.contractedBy !== null) { - contractedByTextbox = new fabric.Textbox( - options.contractedBy, - { - left: canvas.width - contractedByTextMaxWidth - contractedByTextSidesMargin, - top: ( - canvas.height - - contractedByTextSidesMargin - - contractedByTextSize - ), - width: contractedByTextMaxWidth, - fontFamily: 'Roboto Condensed', - fontSize: contractedByTextSize, - textAlign: 'right', - fill: options.colors.contractedByText.value, - selectable: false, - zIndex: 10 - } - ) + canvas.add(contractedByTextbox); + } - checkTextBoxHeight(contractedByTextbox, 1) + /* END Contracted by render */ - canvas.add(contractedByTextbox) - } + /* BEGIN Main image render */ - /* END Contracted by render */ - - - /* BEGIN Main image render */ - - if ( - options.mainImage !== null - && ( - mainImage === null - || options.mainImage.src !== mainImage._element.src - ) - ) { - if (mainImage !== null) { - canvas.remove(mainImage) - } - - mainImage = new fabric.Image( - options.mainImage, - { - left: mainImageOffsetSide, - top: mainImageOffsetTop, - selectable: false, - zIndex: 0 - } - ) - mainImage.scaleToWidth(mainImageWidth) - - canvas.add(mainImage) - // canvas.centerObject(mainImage) + if ( + options.mainImage !== null && + (mainImage === null || options.mainImage.src !== mainImage._element.src) + ) { + if (mainImage !== null) { + canvas.remove(mainImage); } - /* END Main image render */ + mainImage = new fabric.Image(options.mainImage, { + left: mainImageOffsetSide, + top: mainImageOffsetTop, + selectable: false, + zIndex: 0, + }); + mainImage.scaleToWidth(mainImageWidth); + + canvas.add(mainImage); + // canvas.centerObject(mainImage) + } + /* END Main image render */ - sortObjects(canvas) -} + sortObjects(canvas); +}; -export default redraw +export default redraw; diff --git a/frontend/src/views/urgent_basic_photo_banner/UrgentBasicPhotoBanner.vue b/frontend/src/views/urgent_basic_photo_banner/UrgentBasicPhotoBanner.vue index 73efe42b931a691d4c49006a58cabc2682149324..a5ace52fb9dc95c0bc45b2914acaf784489c6a4e 100644 --- a/frontend/src/views/urgent_basic_photo_banner/UrgentBasicPhotoBanner.vue +++ b/frontend/src/views/urgent_basic_photo_banner/UrgentBasicPhotoBanner.vue @@ -1,252 +1,249 @@ <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 { generateDefaultLogos, LOGO_POSITIONS, generateLogoPositions } from '../../logos' -import { loadFonts, loadCanvasStorage, setCanvasStorage, updateAutoRedrawStorage } from '../../utils' - -import Canvas from '../../components/canvas/Canvas.vue' -import redraw from '../basic_photo_banner/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 RangeInput from '../../components/inputs/RangeInput.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' +import { watch, ref } from "vue"; + +import COLORS from "../../colors"; +import PEOPLE from "../../people"; +import TEMPLATES from "../../templates"; +import DEFAULT_CONTRACTOR from "../../contractors"; +import { + generateDefaultLogos, + LOGO_POSITIONS, + generateLogoPositions, +} from "../../logos"; +import { + loadFonts, + loadCanvasStorage, + setCanvasStorage, + updateAutoRedrawStorage, +} from "../../utils"; + +import Canvas from "../../components/canvas/Canvas.vue"; +import redraw from "../basic_photo_banner/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 RangeInput from "../../components/inputs/RangeInput.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' -]) + "12px Bebas Neue", + "12px Roboto Condensed", + "bold 12px Roboto Condensed", +]); export default { - components: { - Canvas, - Navbar, - MainContainer, - ImageInput, - LongTextInput, - ShortTextInput, - RangeInput, - InputSeparator, - MultipleColorPicker + components: { + Canvas, + Navbar, + MainContainer, + ImageInput, + LongTextInput, + ShortTextInput, + RangeInput, + InputSeparator, + MultipleColorPicker, + }, + data() { + const predefinedColors = { + base: { + name: "Základní barvy", + colors: { + background: COLORS.yellow1, + highlight: COLORS.black, + arrow: COLORS.black, + baseText: COLORS.black, + highlightedText: COLORS.white, + contractedByText: COLORS.gray3, + }, + }, + }; + + return { + mainImage: null, + mainText: null, + personName: null, + personPosition: null, + contractedBy: DEFAULT_CONTRACTOR, + logoImage: null, + logoPosition: LOGO_POSITIONS.top_right, + logoOptions: generateLogoPositions(["top_right", "top_left"]), + gradientHeightMultiplier: 1, + colorLabels: { + background: "Pozadí", + highlight: "Zvýraznění", + arrow: "Šipka", + baseText: "Text", + highlightedText: "Zvýrazněný text", + }, + predefinedColors: predefinedColors, + colors: predefinedColors.base.colors, + predefinedLogoImages: generateDefaultLogos("defaultLight"), + autoRedraw: false, + }; + }, + async created() { + await loadCanvasStorage(this); + }, + methods: { + async reloadCanvasProperties() { + const canvasProperties = { + mainImage: this.mainImage, + mainText: this.mainText, + personName: this.personName, + personPosition: this.personPosition, + contractedBy: this.contractedBy, + logoImage: this.logoImage, + logoPosition: this.logoPosition, + gradientHeightMultiplier: this.gradientHeightMultiplier, + colors: this.colors, + }; + + await this.$refs.canvas.redraw(canvasProperties); + + delete canvasProperties.colors; + setCanvasStorage(canvasProperties); }, - data () { - const predefinedColors = { - base: { - name: 'Základní barvy', - colors: { - background: COLORS.yellow1, - highlight: COLORS.black, - arrow: COLORS.black, - baseText: COLORS.black, - highlightedText: COLORS.white, - contractedByText: COLORS.gray3 - } - } + }, + mounted() { + this.$watch( + (vm) => [ + vm.mainImage, + vm.mainText, + vm.personName, + vm.personPosition, + vm.contractedBy, + vm.logoImage, + vm.logoPosition, + vm.gradientHeightMultiplier, + vm.colors, + ], + async (value) => { + if (this.autoRedraw) { + await this.reloadCanvasProperties(); } - - return { - mainImage: null, - mainText: null, - personName: null, - personPosition: null, - contractedBy: DEFAULT_CONTRACTOR, - logoImage: null, - logoPosition: LOGO_POSITIONS.top_right, - logoOptions: generateLogoPositions( - [ - "top_right", - "top_left", - ] - ), - gradientHeightMultiplier: 1, - colorLabels: { - background: 'Pozadí', - highlight: 'Zvýraznění', - arrow: 'Šipka', - baseText: 'Text', - highlightedText: 'Zvýrazněný text' - }, - predefinedColors: predefinedColors, - colors: predefinedColors.base.colors, - predefinedLogoImages: generateDefaultLogos('defaultLight'), - autoRedraw: false + }, + { + immediate: true, + deep: true, + }, + ); + + this.$watch( + (vm) => [vm.autoRedraw], + async (value) => { + updateAutoRedrawStorage(this.autoRedraw); + + if (this.autoRedraw) { + await this.reloadCanvasProperties(); } - }, - async created () { - await loadCanvasStorage(this) - }, - methods: { - async reloadCanvasProperties () { - const canvasProperties = { - mainImage: this.mainImage, - mainText: this.mainText, - personName: this.personName, - personPosition: this.personPosition, - contractedBy: this.contractedBy, - logoImage: this.logoImage, - logoPosition: this.logoPosition, - gradientHeightMultiplier: this.gradientHeightMultiplier, - colors: this.colors - } - - await this.$refs.canvas.redraw(canvasProperties) - - delete canvasProperties.colors - setCanvasStorage(canvasProperties) - } - }, - mounted () { - this.$watch( - vm => [ - vm.mainImage, - vm.mainText, - vm.personName, - vm.personPosition, - vm.contractedBy, - vm.logoImage, - vm.logoPosition, - vm.gradientHeightMultiplier, - 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.urgent_basic_photo_banner" - ></Navbar> - </header> - <main> - <MainContainer> - <template v-slot:left> - <Canvas - ref="canvas" - :redrawFunction="redraw" - width="2000" - height="2000" - /> - </template> - - <template v-slot:right> - <ReloadButton - :parentRefs="$refs" - @click="reloadCanvasProperties" - /> - <AutoReloadCheckbox - v-model="autoRedraw" - /> - <ImageInput - name="Obrázek" - v-model="mainImage" - :important="true" - zIndex="10" - /> - <LongTextInput - name="Hlavní text" - v-model="mainText" - :important="true" - :highlightable="true" - zIndex="9" - /> - <ShortTextInput - name="Jméno osoby" - v-model="personName" - v-model:relatedModel="personPosition" - :predefinedValues="PEOPLE" - :important="true" - zIndex="8" - /> - <ShortTextInput - ref="refPersonPosition" - name="Pozice osoby" - v-model="personPosition" - :important="false" - zIndex="7" - /> - - <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" - /> - - <RangeInput - name="Výška gradientu" - v-model="gradientHeightMultiplier" - min="0" - max="3" - /> - - <MultipleColorPicker - name="Barvy" - v-model="colors" - :important="false" - :colorLabels="colorLabels" - :predefinedColors="predefinedColors" - :defaultPredefinedColors="predefinedColors.base" - zIndex="5" - ></MultipleColorPicker> - - <ShortTextInput - name="Zadavatel a zpracovatel" - v-model="contractedBy" - :defaultValue="DEFAULT_CONTRACTOR" - :important="false" - zIndex="4" - /> - </template> - </MainContainer> - </main> + <header> + <Navbar :defaultTemplate="TEMPLATES.urgent_basic_photo_banner"></Navbar> + </header> + <main> + <MainContainer> + <template v-slot:left> + <Canvas + ref="canvas" + :redrawFunction="redraw" + width="2000" + height="2000" + /> + </template> + + <template v-slot:right> + <ReloadButton :parentRefs="$refs" @click="reloadCanvasProperties" /> + <AutoReloadCheckbox v-model="autoRedraw" /> + <ImageInput + name="Obrázek" + v-model="mainImage" + :important="true" + zIndex="10" + /> + <LongTextInput + name="Hlavní text" + v-model="mainText" + :important="true" + :highlightable="true" + zIndex="9" + /> + <ShortTextInput + name="Jméno osoby" + v-model="personName" + v-model:relatedModel="personPosition" + :predefinedValues="PEOPLE" + :important="true" + zIndex="8" + /> + <ShortTextInput + ref="refPersonPosition" + name="Pozice osoby" + v-model="personPosition" + :important="false" + zIndex="7" + /> + + <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" + /> + + <RangeInput + name="Výška gradientu" + v-model="gradientHeightMultiplier" + min="0" + max="3" + /> + + <MultipleColorPicker + name="Barvy" + v-model="colors" + :important="false" + :colorLabels="colorLabels" + :predefinedColors="predefinedColors" + :defaultPredefinedColors="predefinedColors.base" + zIndex="5" + ></MultipleColorPicker> + + <ShortTextInput + name="Zadavatel a zpracovatel" + v-model="contractedBy" + :defaultValue="DEFAULT_CONTRACTOR" + :important="false" + zIndex="4" + /> + </template> + </MainContainer> + </main> </template> <style> diff --git a/frontend/src/views/urgent_text_banner/UrgentTextBanner.vue b/frontend/src/views/urgent_text_banner/UrgentTextBanner.vue index 137bc55debe712c8e98e0a73302c0dc12eecaff1..152c0b5d289aa9705be14053d096ec0ef578191e 100644 --- a/frontend/src/views/urgent_text_banner/UrgentTextBanner.vue +++ b/frontend/src/views/urgent_text_banner/UrgentTextBanner.vue @@ -1,208 +1,204 @@ <script setup> -import { watch, ref } from 'vue'; - -import COLORS from '../../colors'; -import TEMPLATES from '../../templates' -import DEFAULT_CONTRACTOR from '../../contractors' -import { LOGOS, LOGO_POSITIONS, generateLogoPositions } from '../../logos' -import { loadFonts, loadCanvasStorage, setCanvasStorage, updateAutoRedrawStorage } from '../../utils' - -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 MultipleColorPicker from '../../components/inputs/colors/MultipleColorPicker.vue' -import SelectInput from '../../components/inputs/SelectInput.vue' -import ReloadButton from '../../components/reload/ReloadButton.vue' -import AutoReloadCheckbox from '../../components/reload/AutoReloadCheckbox.vue' +import { watch, ref } from "vue"; + +import COLORS from "../../colors"; +import TEMPLATES from "../../templates"; +import DEFAULT_CONTRACTOR from "../../contractors"; +import { LOGOS, LOGO_POSITIONS, generateLogoPositions } from "../../logos"; +import { + loadFonts, + loadCanvasStorage, + setCanvasStorage, + updateAutoRedrawStorage, +} from "../../utils"; + +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 MultipleColorPicker from "../../components/inputs/colors/MultipleColorPicker.vue"; +import SelectInput from "../../components/inputs/SelectInput.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' -]) + "12px Bebas Neue", + "12px Roboto Condensed", + "bold 12px Roboto Condensed", +]); export default { - components: { - Canvas, - Navbar, - MainContainer, - ImageInput, - LongTextInput, - ShortTextInput, - MultipleColorPicker + components: { + Canvas, + Navbar, + MainContainer, + ImageInput, + LongTextInput, + ShortTextInput, + MultipleColorPicker, + }, + data() { + const predefinedColors = { + base: { + name: "Základní barvy", + colors: { + background: COLORS.black, + cross: COLORS.yellow1, + text: COLORS.black, + contractedByText: COLORS.gray2, + }, + }, + }; + + return { + mainText: null, + contractedBy: DEFAULT_CONTRACTOR, + logoImage: null, + logoPosition: LOGO_POSITIONS.top_right, + logoOptions: generateLogoPositions([ + "top_right", + "top_left", + "bottom_left", + "bottom_right", + ]), + colorLabels: { + background: "Pozadí", + cross: "Kříž", + text: "Text", + }, + predefinedColors: predefinedColors, + colors: predefinedColors.base.colors, + predefinedLogoImages: [ + { + name: LOGOS.defaultDark.name, + src: LOGOS.defaultDark.src, + defaultSelected: true, + }, + ], + autoRedraw: false, + }; + }, + async created() { + await loadCanvasStorage(this); + }, + methods: { + async reloadCanvasProperties() { + const canvasProperties = { + mainText: this.mainText, + contractedBy: this.contractedBy, + logoImage: this.logoImage, + logoPosition: this.logoPosition, + colors: this.colors, + }; + + await this.$refs.canvas.redraw(canvasProperties); + + await delete canvasProperties.colors; + setCanvasStorage(canvasProperties); }, - data () { - const predefinedColors = { - base: { - name: 'Základní barvy', - colors: { - background: COLORS.black, - cross: COLORS.yellow1, - text: COLORS.black, - contractedByText: COLORS.gray2 - } - } + }, + mounted() { + this.$watch( + (vm) => [ + vm.mainText, + vm.contractedBy, + vm.logoImage, + vm.logoPosition, + vm.colors, + ], + async (value) => { + if (this.autoRedraw) { + await this.reloadCanvasProperties(); } - - return { - mainText: null, - contractedBy: DEFAULT_CONTRACTOR, - logoImage: null, - logoPosition: LOGO_POSITIONS.top_right, - logoOptions: generateLogoPositions( - [ - "top_right", - "top_left", - "bottom_left", - "bottom_right", - ] - ), - colorLabels: { - background: 'Pozadí', - cross: 'Kříž', - text: 'Text' - }, - predefinedColors: predefinedColors, - colors: predefinedColors.base.colors, - predefinedLogoImages: [ - { - name: LOGOS.defaultDark.name, - src: LOGOS.defaultDark.src, - defaultSelected: true, - }, - ], - autoRedraw: false + }, + { + immediate: true, + deep: true, + }, + ); + + this.$watch( + (vm) => [vm.autoRedraw], + async (value) => { + updateAutoRedrawStorage(this.autoRedraw); + + if (this.autoRedraw) { + await this.reloadCanvasProperties(); } - }, - async created () { - await loadCanvasStorage(this) - }, - methods: { - async reloadCanvasProperties () { - const canvasProperties = { - mainText: this.mainText, - contractedBy: this.contractedBy, - logoImage: this.logoImage, - logoPosition: this.logoPosition, - colors: this.colors - } - - await this.$refs.canvas.redraw(canvasProperties) - - await delete canvasProperties.colors - setCanvasStorage(canvasProperties) - } - }, - mounted () { - this.$watch( - vm => [ - vm.mainText, - vm.contractedBy, - vm.logoImage, - vm.logoPosition, - 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.urgent_text_banner" - ></Navbar> - </header> - <main> - <MainContainer> - <template v-slot:left> - <Canvas - ref="canvas" - :redrawFunction="redraw" - width="2000" - height="2500" - /> - </template> - - <template v-slot:right> - <ReloadButton - :parentRefs="$refs" - @click="reloadCanvasProperties" - /> - <AutoReloadCheckbox - v-model="autoRedraw" - /> - <LongTextInput - name="Text" - v-model="mainText" - :important="true" - :highlightable="false" - zIndex="10" - /> - - <ImageInput - name="Obrázek loga" - v-model="logoImage" - :important="false" - :predefinedImages="predefinedLogoImages" - :mustSelectPredefinedImage="true" - :disableImageInput="true" - zIndex="9" - /> - <SelectInput - name="Pozice loga" - :options="logoOptions" - v-model="logoPosition" - zIndex="8" - /> - - <MultipleColorPicker - name="Barvy" - v-model="colors" - :important="false" - :colorLabels="colorLabels" - :predefinedColors="predefinedColors" - :defaultPredefinedColors="predefinedColors.base" - zIndex="7" - ></MultipleColorPicker> - - <ShortTextInput - name="Zadavatel a zpracovatel" - v-model="contractedBy" - :defaultValue="DEFAULT_CONTRACTOR" - :important="false" - zIndex="4" - /> - </template> - </MainContainer> - </main> + <header> + <Navbar :defaultTemplate="TEMPLATES.urgent_text_banner"></Navbar> + </header> + <main> + <MainContainer> + <template v-slot:left> + <Canvas + ref="canvas" + :redrawFunction="redraw" + width="2000" + height="2500" + /> + </template> + + <template v-slot:right> + <ReloadButton :parentRefs="$refs" @click="reloadCanvasProperties" /> + <AutoReloadCheckbox v-model="autoRedraw" /> + <LongTextInput + name="Text" + v-model="mainText" + :important="true" + :highlightable="false" + zIndex="10" + /> + + <ImageInput + name="Obrázek loga" + v-model="logoImage" + :important="false" + :predefinedImages="predefinedLogoImages" + :mustSelectPredefinedImage="true" + :disableImageInput="true" + zIndex="9" + /> + <SelectInput + name="Pozice loga" + :options="logoOptions" + v-model="logoPosition" + zIndex="8" + /> + + <MultipleColorPicker + name="Barvy" + v-model="colors" + :important="false" + :colorLabels="colorLabels" + :predefinedColors="predefinedColors" + :defaultPredefinedColors="predefinedColors.base" + zIndex="7" + ></MultipleColorPicker> + + <ShortTextInput + name="Zadavatel a zpracovatel" + v-model="contractedBy" + :defaultValue="DEFAULT_CONTRACTOR" + :important="false" + zIndex="4" + /> + </template> + </MainContainer> + </main> </template> <style> diff --git a/frontend/src/views/urgent_text_banner/canvas.js b/frontend/src/views/urgent_text_banner/canvas.js index 0fe5a83684277befdd8790d93e0dfa43cc143bab..6fa9976366e706a848ab5bdf362b8aba187f4c79 100644 --- a/frontend/src/views/urgent_text_banner/canvas.js +++ b/frontend/src/views/urgent_text_banner/canvas.js @@ -1,204 +1,167 @@ -import { fabric } from 'fabric' -import { clearObjects, sortObjects, transformTextLineBreaks, checkTextBoxHeight } from '../../components/canvas/utils' -import backgroundURL from '../../assets/template/urgent_text_banner/background.png' - -let contractedByTextbox = null - -let textBox = null -let backgroundImage = null -let logoImage = null -let previousLogoPosition = null +import { fabric } from "fabric"; +import { + clearObjects, + sortObjects, + transformTextLineBreaks, + checkTextBoxHeight, +} from "../../components/canvas/utils"; +import backgroundURL from "../../assets/template/urgent_text_banner/background.png"; + +let contractedByTextbox = null; + +let textBox = null; +let backgroundImage = null; +let logoImage = null; +let previousLogoPosition = null; const redraw = async (canvas, options) => { - clearObjects( - [ - textBox, - contractedByTextbox - ], - canvas - ) - - const logoWidth = Math.ceil(canvas.width * 0.23) - const logoSideMargin = Math.ceil(canvas.width * 0.07) - const logoTopMargin = Math.ceil(canvas.height * 0.155) - - const contractedByTextSize = Math.ceil(canvas.height * 0.02) - const contractedByTextMaxWidth = Math.ceil(canvas.width * 0.9) - const contractedByTextSidesMargin = Math.ceil(canvas.width * 0.03) - - const textBoxWidth = Math.ceil(canvas.width * 0.65) - const textSize = Math.ceil(canvas.height * 0.0725) - const textLineHeight = 0.95 - - canvas.preserveObjectStacking = true - - - /* BEGIN Background render */ - - if (backgroundImage === null) { - backgroundImage = new Image() - - await new Promise(resolve => { - backgroundImage.onload = () => { - resolve() - } - - backgroundImage.src = backgroundURL - }) - - backgroundImage = new fabric.Image( - backgroundImage, - { - top: -20, // FIXME: Why???? Fabric.js, what are you trying to tell me?! - left: -20, - zIndex: 0, - selectable: false - } - ) - backgroundImage.scaleToWidth(canvas.width + 22) - - canvas.add(backgroundImage) - } - - /* 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: logoTopMargin, - zIndex: 11, - }) - } else if (options.logoPosition.id == "top-left") { - logoImage.set({ - left: logoSideMargin, - top: logoTopMargin, - zIndex: 11, - }) - } else if (options.logoPosition.id == "bottom-left") { - logoImage.set({ - left: logoSideMargin, - top: ( - canvas.height - - logoTopMargin - - logoImage.getScaledHeight() - ), - zIndex: 11, - }) - } else { - logoImage.set({ - left: ( - canvas.width - - logoWidth - - logoSideMargin - ), - top: ( - canvas.height - - logoTopMargin - - logoImage.getScaledHeight() - ), - zIndex: 11, - }) - } - - canvas.add(logoImage) - } - - /* END Logo render */ - - - /* BEGIN Contracted by render */ - - if (options.contractedBy !== null) { - contractedByTextbox = new fabric.Textbox( - options.contractedBy, - { - left: canvas.width - contractedByTextMaxWidth - contractedByTextSidesMargin, - top: ( - canvas.height - - contractedByTextSidesMargin - - contractedByTextSize - ), - width: contractedByTextMaxWidth, - fontFamily: 'Roboto Condensed', - fontSize: contractedByTextSize, - textAlign: 'right', - fill: options.colors.contractedByText.value, - selectable: false, - zIndex: 10 - } - ) - - checkTextBoxHeight(contractedByTextbox, 1) - - canvas.add(contractedByTextbox) + clearObjects([textBox, contractedByTextbox], canvas); + + const logoWidth = Math.ceil(canvas.width * 0.23); + const logoSideMargin = Math.ceil(canvas.width * 0.07); + const logoTopMargin = Math.ceil(canvas.height * 0.155); + + const contractedByTextSize = Math.ceil(canvas.height * 0.02); + const contractedByTextMaxWidth = Math.ceil(canvas.width * 0.9); + const contractedByTextSidesMargin = Math.ceil(canvas.width * 0.03); + + const textBoxWidth = Math.ceil(canvas.width * 0.65); + const textSize = Math.ceil(canvas.height * 0.0725); + const textLineHeight = 0.95; + + canvas.preserveObjectStacking = true; + + /* BEGIN Background render */ + + if (backgroundImage === null) { + backgroundImage = new Image(); + + await new Promise((resolve) => { + backgroundImage.onload = () => { + resolve(); + }; + + backgroundImage.src = backgroundURL; + }); + + backgroundImage = new fabric.Image(backgroundImage, { + top: -20, // FIXME: Why???? Fabric.js, what are you trying to tell me?! + left: -20, + zIndex: 0, + selectable: false, + }); + backgroundImage.scaleToWidth(canvas.width + 22); + + canvas.add(backgroundImage); + } + + /* 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: logoTopMargin, + zIndex: 11, + }); + } else if (options.logoPosition.id == "top-left") { + logoImage.set({ + left: logoSideMargin, + top: logoTopMargin, + zIndex: 11, + }); + } else if (options.logoPosition.id == "bottom-left") { + logoImage.set({ + left: logoSideMargin, + top: canvas.height - logoTopMargin - logoImage.getScaledHeight(), + zIndex: 11, + }); + } else { + logoImage.set({ + left: canvas.width - logoWidth - logoSideMargin, + top: canvas.height - logoTopMargin - logoImage.getScaledHeight(), + zIndex: 11, + }); } - /* END Contracted by render */ - - - /* BEGIN Text render */ - - if (options.mainText !== null) { - textBox = new fabric.Textbox( - transformTextLineBreaks( - options.mainText, - textSize, - 'Bebas Neue', - textBoxWidth - ), - { - width: textBoxWidth, - left: (canvas.width - textBoxWidth) / 2, - textAlign: 'center', - fontFamily: 'Bebas Neue', - fontSize: textSize, - lineHeight: textLineHeight, - fill: options.colors.text.value, - selectable: false, - zIndex: 10 - } - ) - - checkTextBoxHeight(textBox, 5) - - canvas.add(textBox) - - textBox.top = (canvas.height - textBox.height) / 2 - } - - /* END Text render */ - - - sortObjects(canvas) -} - -export default redraw + canvas.add(logoImage); + } + + /* END Logo render */ + + /* BEGIN Contracted by render */ + + if (options.contractedBy !== null) { + contractedByTextbox = new fabric.Textbox(options.contractedBy, { + left: + canvas.width - contractedByTextMaxWidth - contractedByTextSidesMargin, + top: canvas.height - contractedByTextSidesMargin - contractedByTextSize, + width: contractedByTextMaxWidth, + fontFamily: "Roboto Condensed", + fontSize: contractedByTextSize, + textAlign: "right", + fill: options.colors.contractedByText.value, + selectable: false, + zIndex: 10, + }); + + checkTextBoxHeight(contractedByTextbox, 1); + + canvas.add(contractedByTextbox); + } + + /* END Contracted by render */ + + /* BEGIN Text render */ + + if (options.mainText !== null) { + textBox = new fabric.Textbox( + transformTextLineBreaks( + options.mainText, + textSize, + "Bebas Neue", + textBoxWidth, + ), + { + width: textBoxWidth, + left: (canvas.width - textBoxWidth) / 2, + textAlign: "center", + fontFamily: "Bebas Neue", + fontSize: textSize, + lineHeight: textLineHeight, + fill: options.colors.text.value, + selectable: false, + zIndex: 10, + }, + ); + + checkTextBoxHeight(textBox, 5); + + canvas.add(textBox); + + textBox.top = (canvas.height - textBox.height) / 2; + } + + /* END Text render */ + + sortObjects(canvas); +}; + +export default redraw; diff --git a/frontend/src/views/utils/newspaper_quotes.js b/frontend/src/views/utils/newspaper_quotes.js index 04ea6f810b96ca2554d4e2b63a4fa41f0a6802f7..d433d9c2c1631a898d78b53401ed625ce949a76c 100644 --- a/frontend/src/views/utils/newspaper_quotes.js +++ b/frontend/src/views/utils/newspaper_quotes.js @@ -1,35 +1,35 @@ -import sourceImageCT from '../../assets/news_sources/ct.png' -import sourceImageDenikN from '../../assets/news_sources/denik_n.png' -import sourceImageSeznam from '../../assets/news_sources/seznam_zpravy.png' -import sourceImageCNN from '../../assets/news_sources/cnn.png' -import sourceImageCzechRadio from '../../assets/news_sources/rozhlas.png' +import sourceImageCT from "../../assets/news_sources/ct.png"; +import sourceImageDenikN from "../../assets/news_sources/denik_n.png"; +import sourceImageSeznam from "../../assets/news_sources/seznam_zpravy.png"; +import sourceImageCNN from "../../assets/news_sources/cnn.png"; +import sourceImageCzechRadio from "../../assets/news_sources/rozhlas.png"; const SOURCE_IMAGES = [ - { - name: 'Česká televize', - src: sourceImageCT, - defaultSelected: false - }, - { - name: 'Deník N', - src: sourceImageDenikN, - defaultSelected: false - }, - { - name: 'Seznam Zprávy', - src: sourceImageSeznam, - defaultSelected: false - }, - { - name: 'CNN', - src: sourceImageCNN, - defaultSelected: false - }, - { - name: 'Český Rozhlas', - src: sourceImageCzechRadio, - defaultSelected: false - } -] + { + name: "Česká televize", + src: sourceImageCT, + defaultSelected: false, + }, + { + name: "Deník N", + src: sourceImageDenikN, + defaultSelected: false, + }, + { + name: "Seznam Zprávy", + src: sourceImageSeznam, + defaultSelected: false, + }, + { + name: "CNN", + src: sourceImageCNN, + defaultSelected: false, + }, + { + name: "Český Rozhlas", + src: sourceImageCzechRadio, + defaultSelected: false, + }, +]; -export { SOURCE_IMAGES } +export { SOURCE_IMAGES }; diff --git a/frontend/tailwind.config.js b/frontend/tailwind.config.js index 528e5199bce54e6056c0d3a7a99cf516e76debe5..44417984a20d62f0a718a81094c08a97a46aadc2 100644 --- a/frontend/tailwind.config.js +++ b/frontend/tailwind.config.js @@ -1,13 +1,8 @@ /** @type {import('tailwindcss').Config} */ module.exports = { - content: [ - './public/**/*.html', - './src/**/*.{vue,js,ts,jsx,tsx}' - ], + content: ["./public/**/*.html", "./src/**/*.{vue,js,ts,jsx,tsx}"], theme: { extend: {}, }, - plugins: [ - require('@tailwindcss/typography') - ], -} + plugins: [require("@tailwindcss/typography")], +}; diff --git a/frontend/vite.config.js b/frontend/vite.config.js index d21e5bba73e8e69988976ae30c553bad7328e57e..d33d271e1df86a191e62a9eb66f72d3658e47114 100644 --- a/frontend/vite.config.js +++ b/frontend/vite.config.js @@ -1,8 +1,8 @@ -import { fileURLToPath, URL } from 'node:url' +import { fileURLToPath, URL } from "node:url"; -import { defineConfig } from 'vite' -import vue from '@vitejs/plugin-vue' -import vueJsx from '@vitejs/plugin-vue-jsx' +import { defineConfig } from "vite"; +import vue from "@vitejs/plugin-vue"; +import vueJsx from "@vitejs/plugin-vue-jsx"; import topLevelAwait from "vite-plugin-top-level-await"; // https://vitejs.dev/config/ @@ -14,15 +14,15 @@ export default defineConfig({ // The export name of top-level await promise for each chunk module promiseExportName: "__tla", // The function to generate import names of top-level await promise in each chunk module - promiseImportName: i => `__tla_${i}` - }) + promiseImportName: (i) => `__tla_${i}`, + }), ], resolve: { alias: { - '@': fileURLToPath(new URL('./src', import.meta.url)) - } + "@": fileURLToPath(new URL("./src", import.meta.url)), + }, }, build: { - assetsDir: 'static' - } -}) + assetsDir: "static", + }, +}); diff --git a/node_modules/.cache/prettier/.prettier-caches/f6ed4c60a1a514e28f171758d0fa4cd22c6dd701.json b/node_modules/.cache/prettier/.prettier-caches/f6ed4c60a1a514e28f171758d0fa4cd22c6dd701.json new file mode 100644 index 0000000000000000000000000000000000000000..aa014820e147a22836c3e659ff23d6b9bff0c099 --- /dev/null +++ b/node_modules/.cache/prettier/.prettier-caches/f6ed4c60a1a514e28f171758d0fa4cd22c6dd701.json @@ -0,0 +1 @@ +{"86332dd6682db39c77426833c14c2da9d2ea50ca":{"files":{"frontend/package.json":["rrdJfhPy9utQEpspZXG3Yz5jlD4=",true],"frontend/index.html":["bcDRa+yrj4hWphrBW/8Yz+OoU0U=",true],"frontend/package-lock.json":["hJU2Yx2l/MsVDr1fymTF6T8bii0=",true],"frontend/src/components/inputs/colors/MultipleColorPicker.vue":["lbHcSGzdgLCwbqFtO3ptYGLryUk=",true],"frontend/src/views/poster/canvas.js":["JMhzGut2aoXlGW3qfoZCL/BWmQY=",true],"frontend/src/contractors.js":["5AbHwq/wAYZHbtpsQFYEPLP9YIk=",true],"frontend/src/views/avatar/Avatar.vue":["d3d9YDFChFipkf+ivF3A9p+XezE=",true],"frontend/src/views/poster/Poster.vue":["HiA9cIObIQhfTmu9kDKReoldGJM=",true],"frontend/src/views/text_banner/canvas.js":["SGoPSci7AlRexdZrBYT52mjqDHo=",true],"frontend/src/components/reload/ReloadButton.vue":["hrfu0lxu2UdSUQxwvgOtHAdjCq0=",true],"frontend/src/views/base_event/BaseEvent.vue":["D4UnrcElXAz3BpATZ5pH/byC4x4=",true],"frontend/src/views/facebook_survey/canvas.js":["ezs6Bu+M7Omi44R0ZlDHZj8d8I0=",true]},"modified":1714037234560}} \ No newline at end of file diff --git a/server/server/templates/index.html b/server/server/templates/index.html index 65fe09896a07dae74cf05591b99602fe61aa210a..64ec2c35023f665d201b123aa278f840ce1c4f06 100644 --- a/server/server/templates/index.html +++ b/server/server/templates/index.html @@ -1,24 +1,30 @@ -<!DOCTYPE html> +<!doctype html> <html lang="en"> <head> - <meta charset="UTF-8"> - <link rel="icon" href="/static/favicon.ico"> - <link rel="stylesheet" href="https://styleguide.pirati.cz/2.12.x/css/styles.css"> - <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <meta charset="UTF-8" /> + <link rel="icon" href="/static/favicon.ico" /> + <link + rel="stylesheet" + href="https://styleguide.pirati.cz/2.12.x/css/styles.css" + /> + <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <script> - ;(function () { - var src = '//cdn.jsdelivr.net/npm/eruda'; - if (!/eruda=true/.test(window.location) && localStorage.getItem('active-eruda') != 'true') return; - document.write('<scr' + 'ipt src="' + src + '"></scr' + 'ipt>'); - document.write('<scr' + 'ipt>eruda.init();</scr' + 'ipt>'); + (function () { + var src = "//cdn.jsdelivr.net/npm/eruda"; + if ( + !/eruda=true/.test(window.location) && + localStorage.getItem("active-eruda") != "true" + ) + return; + document.write("<scr" + 'ipt src="' + src + '"></scr' + "ipt>"); + document.write("<scr" + "ipt>eruda.init();</scr" + "ipt>"); })(); </script> <title>Generátor grafiky</title> <script type="module" crossorigin src="/static/index-ec81bc4e.js"></script> - <link rel="stylesheet" href="/static/index-636cacf0.css"> + <link rel="stylesheet" href="/static/index-636cacf0.css" /> </head> <body> <div id="app"></div> - </body> </html>