From d67dd610735420c9f92fc0708aa4698c86ab4242 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Valenta?= <tomas@imaniti.org> Date: Thu, 25 Apr 2024 11:27:24 +0200 Subject: [PATCH] run/add new hooks, add new template --- .gitlab-ci.yml | 1 - .pre-commit-config.yaml | 4 + frontend/.eslintrc.cjs | 16 +- frontend/index.html | 27 +- frontend/postcss.config.js | 2 +- frontend/src/App.vue | 4 +- frontend/src/assets/fonts/glegoo/style.css | 16 +- frontend/src/assets/previews/base_event.png | Bin 0 -> 5226 bytes .../assets/template/base_event/bg_black.png | Bin 0 -> 20490 bytes .../assets/template/base_event/bg_white.png | Bin 0 -> 22028 bytes frontend/src/colors.js | 52 +- frontend/src/components/MainContainer.vue | 24 +- frontend/src/components/Navbar.vue | 139 +- frontend/src/components/canvas/Canvas.vue | 147 +-- frontend/src/components/canvas/textbox.js | 194 +-- frontend/src/components/canvas/utils.js | 454 ++++--- frontend/src/components/inputs/EmojiInput.vue | 152 +-- frontend/src/components/inputs/ImageInput.vue | 330 +++-- .../src/components/inputs/InputHeading.vue | 38 +- .../src/components/inputs/InputSeparator.vue | 2 +- frontend/src/components/inputs/RangeInput.vue | 44 +- .../src/components/inputs/SelectInput.vue | 72 +- .../components/inputs/colors/ColorPicker.vue | 141 +- .../inputs/colors/MultipleColorPicker.vue | 114 +- .../components/inputs/text/LongTextInput.vue | 50 +- .../components/inputs/text/ShortTextInput.vue | 105 +- frontend/src/components/inputs/text/utils.js | 12 +- .../components/reload/AutoReloadCheckbox.vue | 34 +- .../src/components/reload/ReloadButton.vue | 30 +- frontend/src/contractors.js | 4 +- frontend/src/logos.js | 88 +- frontend/src/main.js | 14 +- frontend/src/people.js | 52 +- frontend/src/router/index.js | 80 +- frontend/src/templates.js | 200 +-- frontend/src/utils.js | 128 +- frontend/src/views/avatar/Avatar.vue | 159 ++- frontend/src/views/avatar/canvas.js | 4 +- frontend/src/views/base_event/BaseEvent.vue | 171 +++ frontend/src/views/base_event/canvas.js | 149 +++ .../basic_photo_banner/BasicPhotoBanner.vue | 471 ++++--- .../src/views/basic_photo_banner/canvas.js | 743 +++++------ .../views/facebook_survey/FacebookSurvey.vue | 471 ++++--- frontend/src/views/facebook_survey/canvas.js | 763 +++++------ .../NewspaperQuoteBottom.vue | 517 ++++---- .../views/newspaper_quote_bottom/canvas.js | 1013 +++++++------- .../NewspaperQuoteMiddle.vue | 466 +++---- .../views/newspaper_quote_middle/canvas.js | 1159 ++++++++--------- frontend/src/views/poster/Poster.vue | 466 ++++--- frontend/src/views/poster/canvas.js | 828 ++++++------ .../regional_success/RegionalSuccess.vue | 578 ++++---- frontend/src/views/regional_success/canvas.js | 973 +++++++------- frontend/src/views/text_banner/TextBanner.vue | 387 +++--- frontend/src/views/text_banner/canvas.js | 446 +++---- .../src/views/twitter_banner/PersonInput.vue | 110 +- .../views/twitter_banner/TwitterBanner.vue | 417 +++--- frontend/src/views/twitter_banner/canvas.js | 564 ++++---- .../UrgentBasicPhotoBanner.vue | 469 ++++--- .../urgent_text_banner/UrgentTextBanner.vue | 380 +++--- .../src/views/urgent_text_banner/canvas.js | 363 +++--- frontend/src/views/utils/newspaper_quotes.js | 64 +- frontend/tailwind.config.js | 11 +- frontend/vite.config.js | 22 +- ...d4c60a1a514e28f171758d0fa4cd22c6dd701.json | 1 + server/server/templates/index.html | 30 +- 65 files changed, 7356 insertions(+), 7609 deletions(-) create mode 100644 frontend/src/assets/previews/base_event.png create mode 100644 frontend/src/assets/template/base_event/bg_black.png create mode 100644 frontend/src/assets/template/base_event/bg_white.png create mode 100644 frontend/src/views/base_event/BaseEvent.vue create mode 100644 frontend/src/views/base_event/canvas.js create mode 100644 node_modules/.cache/prettier/.prettier-caches/f6ed4c60a1a514e28f171758d0fa4cd22c6dd701.json diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index b74e7e84..94579c6a 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 479c120f..ec049d38 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 b64731a0..b2fc3102 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 a54c0bea..fe40e4aa 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 33ad091d..12a703d9 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 5b90105d..dcd008a0 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 2dc6b2e2..2acfe75e 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 GIT binary patch literal 5226 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 F1ON(KXxIP% literal 0 HcmV?d00001 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 GIT binary patch literal 20490 zcmeAS@N?(olHy`uVBq!ia0y~yU|Yt(z*NS;#K6F?>EDu61_p);sS%!Oo}O9^91IK$ zTnr41EDVec3=GTI7#J9#>@p?>1|~*k24)5ZhBgKU1|ddfuo{p$DMnT>yN7{+K^n@Q z!oa|w0aXLC$&itWftP`SVGaWWgApSWg9rly!zu;_hRk+|UHcdyfP(=9Js23kCc<cU zPu~CqBRxX{J!3Nl1|tI_6DtD~D<e|{Ljx-VLn~8D21W+P1qc^`%(@Y{U=jlZV@sy9 zbAYF_vqC{pep+TuDg#5soZ5-D9)}%dj>cd1>JE{4v*6K$g;qCRU0J3_wJL>hY;{`A zr*_>x<I;pBJ1n@?v9jBXt>)7^x65h=SJMObj?K-J7j;khv46=0rDM&H>-OyazN4C* z<E&QjSqGT_)6*X6!bxV^wb<eX4+uChq@||4DxNf@>G_Xc?fvV^4d*?reRuxibp07T zA3Bc|E|V7f$oBEevZclM)cZDUnxk%~E@%Ft=}3P}cud{nn4*XWGX<3=&r|Oc=w7N9 zwrj>z-l-K`7PG=lPl<2Rdv2Lu7`D3f+CKIA#2pLP>J-m?#xW`4h>%K0K!7`IXhg@w zRTGnxe1CKD|KIR<`iZ3zRx`Qvv0ho=RQBgV{fFJrn=LFKs<jF`obS%9?_oL^yWr}E zx9^j0=gnpKm;S)(^^a`dglCh#xVd-BG5)=M;Ap9P@VAE3vI!qNr}N&N_{XF*NwC6x zasDYr=IsveXWfpjmS4{A(6-C#E(3e}fr!pc*KO~~zIylezVX|Cbqu|UMH@rBU&}Et z@K$7oM3hAM`dB6B=jtV<<R_)-Rpb_cB8<VtzM>#8IXksPAt^OIGtXA({qFrr3YjUk zO5vuy2EGN(sTr9bRYj@6RemAKRoTgwDN6QsTs9R}6}bhusU?XD6}dTi#a0!zN?>!X z@`|lM!um=IU?nBlwn`Dc0SeCfMX3rVdM0`Xx~>(OWkyPNTnaWtDQQ+gE^bgGic->S zl`=|73as??%gf94%8m8%i_-NCEiEne4UF`SjC6}q(sYX}^GXscbn}XpK}JB#a7isr zF3Kz@$;{7F0GXMXlwVq6tE9xGpr8OXydt;2*B5SlUNJNjlJj%*D-sLz4fPE4b942P zbrhGlmX+YwQ5;fPkg6Y)TAW{6l$`2XmYP?hjBErb2f<AMc_}?Jw*c&^irfMe%Tkiz z>IzDWa=>a*lJ!$_Qgc)DN{aOj^$bzemt>|P0t>}AAQ`ZCkR4KyTL3p7MK#RtV8!4t zvU15!E(KZY>0+w{a<5fNesX4t6_{z7WN4OVnQEz<n4FfXYhr1dtZQLukfdv1nV4*9 zkeF&=k!FHqlxJRXNn&1dD#)mc+ycGK%oM96%VYzS#AHj|q$INxT@wp~B;CZMv{c<> zV*?B0Br~H#LnCA({EITvGxHL2kX;2bDkU?;%Fr^++&s-7NjJ$PIR)ez6Wt_3QxjbS zV{@bA<P>ug<1{m{Ye4Z}<rv^;t7N2SfDj4DNi0drFUqx5^2y9Atw2bGWag#@mn4D$ z&CuM?#K73d(8$2Z!ra^vNq1OkQE_H|9>`2^5ee3wl5FLcUzD3zVyl#yo0y)eUyzp$ zmH<VIm48uYYF<eqD4p6W8G^+tato}Si&7Iy@{2<9^K)#KKyC$>x^QXV)RM%M#F9jp z#FA87B}6$K0xO5L4UDV|AT9$L=bN9BnN~?iwH+vP*g&G(BeS?9zo-%{1j$jssf7?8 zgqxEIQmvq%0L~OviOCQz6epIYrhwfEmrBk^%u7#Au~mX5SD2PeEJ?vQ)hN->+|*Pz z$=E1G*Tf_xO*bhq(NZ_jJT*DRG{rL2B+(ev^y2)qlJdl&RLAtxyb@a__srY^a9Am5 zfMZY-RdsnrDkypkj7)S5OmvM*!2t#eDrkV|L%e9C4=OW)p+2_J#|RUM0;EF5j!OX| z7Ube)$7Q1rF6ls}9>hRUsY6Q)jSE^@p`b8o2}$8Q8eF5nMN$Zmq<A!SjRqG<AwZJi z(bPq?;Nn7br&9A$Y?aED?Ct(-lE2Nsz`$AH5n0T@z%2~Ij105pNH8!ku$OrHy0X7u zk>p@vxKzGx0Rw{qgQtsQNX4x;ck3ri`P%s4|2Mt3wpFGFIQd?`sR^7Ws@A%AQ`fGU zDc`#tEDfvG-eg@<-Wh4=6RSB-wfn?GqpURoEc<>uby}m_F=1tgTX5<lRhIl8fz?)) z^(v<8T23)6bjae{FK{R5(e}QNTe9Bu`&lL1+Zx;2{{8a#%k#>;UG`S|cm2!jXG!k9 z|Fh;-@x2ER68dx^@7>&)`<w4E1A~LXGE={|_jc!pd3P^lXxLMq%CRZ!^tH98r`^kw zVPN1mw(@einC^~;1@ZG2GBntn)b9?lXJBAj{EP8Gz@&J$m30gZEN)+!8<<Y&?}o`Z z%zH9DbCo;;Lu24y1_kw>3x6>(2uNJzXHc;RDREl-i*Z8XB>A0lUa>Pc6ol9_Z1VZ^ zxEv$}GIv7cB>DOUP%RBPlm7hRxXRDaa9~9pL&}B9=H_O%My2cL_pbVWz!Ag!3fyNt zEpC}!9Y6oJ)o}|31_7SpzYHftPWE4dn=U&^{&3J=1_q9vSL_aM9(6q~Uzr&c3|7fA zL@N9=<bqkn;{PeR1m*{h*-zXfVI~L|oa`@x`CB1ml6*4Uu?n7bJ-r}LC4|^B98&&i zm<BhY=ZU*2+ytSM{akPpBqqsE4Td>LLdE`=E6hQR6aGl(!yI(Q`eeUW08Cx%<UcF; z;8OZ0`&R|Rq~=cgvqB9nrF^nq3vT*d%lv|Z3mO5X$l;fEe$K~Z3lAS-Y}l1BO@5=n zbx>f-SXI6GwKH6jo#9o}6ZaUf%P&_ySn!J7A$G!_j)^c&f@JrAWkHczH%WdY+_F=i zbyu2T>Mr?wTI>c_7d1(K>ta|8mrVMzLKEiRB@>>wuZ0Iqu8O@7Ja9uM{_$voo9FyV z*$@`E9-JrpRp6R<RqTb9!3;k#*WAJ)L&W3G*W&fBYzr=c(nQbBwZ(DoYVTSwXntH8 zz5mC(>ixP_6|e9Ay*sI|Z^Qf_&w8c5y}un_vSH`W%G1}@CfXOC?B4<l`6Wl5^-71W zjSDU*h<bcQSp7cN?@sx5uYaFOxBL6fI=x*kXU2sOKc?v(-0pX>Uj!B@OAdZn*6lyn z&iKoTEyq^gt^R&CE0gng=lwhP{vDdP*Oh&{{l~cvs^ia<|D33`4;%qM;_MX)Pg`4e zSH&N0Y18?#?{IDH|0|b+OA2mYy1n_r@1nGy6P=!cy;=M>!G`bq*S#BFtN%PbJ-+bh zpVsc4lQZ1bXQXDkP22j+>uGDMcRv3=?LS;9_OBiv7h+&|HR~07s@0wA-`5_Od;R<B z`d|M4&R@>|fBL}A#!2C`@7*bR_~c~0)#pv$X4}o4=ovk=b6w8a6PvF6t=@I}cK)eP z?-Nh<Pl073<s18#SO4Co9{1~}x2t!+X~WD<M=Up|>BnyK`Q5E`YsL1jUDw>N+0JTJ zkJ}RXo>jc&(aH4rC$D(x|C|^HjX{O1&&PQ0*S}i1s^-`1YZhPE=l_a18<zL(&8w>4 zXY$+Rc79xTKJ+8^r?zz&eJ}O0K0FfB^PgEYQ7zhglKe_oTyuSU|8n2=cf0vzZNp0T zBsv{d^%al5SM}8N^SZve%6Pk$zrRkb|6TWW_2T3GuIa3Pvurkg?z2vsb$Z+8)!Aof z{kQoPa^&S%-?P5;pRTg+7yEnb;Ay?RPh9?=T-0o?7xUrIySqP6pSHHH-}72sdA_~9 z-TZ%N;-?9l=VxTUu6%lRQFHF~*xmY@*1MiO9@h8u^|gDOKkZwn|Jq#c->-v*+uQ0I zA6y7ecPswf<l5b~&tbNCUc}a_vnQ6vf=lAQ7o6?U%lB+vo&8cazVqJim&W<&PoBR{ zt~)v*P5=A8b)a~j^ha*rDtVXv$?FoG4qpwAF?{vwPNc4`^YWdp;Vk=qpS$^0+T8Z@ z8RP#uO0%zi|5fq(Ouk!eRn4ORPdfiie*4z^Z0_98cjx-|W(LL8*Z*JfT;9UG;KhrL z&hGtxE3W%rTle?biNmEIJ}fliKlWvlSmpP*oA<t-^VYWD!~UAzA2+|>8~!Yl|NGb4 z_b-<(K7M=M?y?=r_t)OuHtGAm&8xMQC-eTFdf#Ztozr{QpU<6}45~TY_WhD%H_A>A zzy4)ecYE7H&&ld~(N({1e!qA7`eoy5GRE}}7pc#$eRkyJ_G{~-t@rL+S@6f@|4DtF zf3u&?oqO`%2K}gl!w>iEn{lrG)t8yp)AjG~UtZm^P|xzJUi`jQ|0>qaufBR>@A{;2 zy=bw!CsHpx>Fl5Ws;csE>e;u4@2eP0k}q|D7Nj$`?%DkL+|$$C!Z)SVOzv!|`1sN| zzyCk?k9U!k_a{Ei=i{wse$KWn_x{8;=WngNoSygh(DTRE{}1eJtbV_*ddGfQ#r)8{ zHMSdm{WH|PxO>UVM@H*+ZeFdu%gl26>9srm|9hAgz<1rMxbR$okMzm@U(gb(pXb-5 zwefeh)w(C{y7p$Kaa{G=t$*xR%{I@wv8V9xkLB~89=@;JEpG0Uq48&pZ~iWeeJjg$ z#^gmkzVh;{uk&*WzS0*jHVUdenyOs?DYG}Y-THsQ`Mj1rp<h3Jjj#W*_~E}*AEJ0l zD_>7do%i!$yIhZ4&hB@&7T*67p3<^F@-okHkx746NJBH&i7Bt2$9-*nKJRpV-i!+y za$dg8m$8bOv2<(J*IA*fuTSZ*7OnVnGhOcfrll87_|3Hwe$8}S^!vT)CqHJp_wBEI zFr7UwW`{ym>)YG&B+mc*tZrZUY3<$J<x_d;FZR|JE1xSlJyk2dTTE|P;lrkd^<Hjg z7QMc+qu}7v@R*6GpDwz+;YrcXXfgeJd#kVC{C=<c|6%@n|35T;eSK|PZ28+ICxwf5 z-ZA~WPVf0n{r7Kr^0bUTO}cj{!_indyE^y2qwU7o><kQz8)vgi78X}mKU^GhxTWp* z_MFaY?p9S<+3@qTzh9r9d!Bu9&DYoWcIW%d&g{9mX4<sp<?s9QYJdOVTe?Z>`TYF_ z7wdm~ntnb1PDsh0=j;C}RXts_!E|lmtKIu+^Iw=h%DulW_r+>~*y!5g>S~kiqGsZ9 zSGOEn`BQTa>-RN#^zBXGH(veTZ{WT@VMnaBTg0W=+E=ZXoBGM_()4n>_HEWv_qm4P zVtdAG_Yc#`%f!k`SNOg^J^SjVq9;FQxAspctX~`Fc>2vFsjKy$>cd_?30~&YDL2O? zGwDl0;Eh?8FMAfPKi|E(X7BIY-`k>{exKOoe%kWzef^)`FUDG@ou6Y^q^FZLmseQo z->+roYo9cxCg-ky;{F#}y|^DJD=W>ty-&G*=bU@nD<8M%-~DxT_U+s2XV|{p<gLH+ z%_VQUe;Yq@*Q>=9IR1O^wRrumoqKCPALC8_#Zmd<#YX8{x3cUx#Qo;QtPQUHzjFB^ z_Vxd-EMIgu&gAmir<3RJd2&h8xj$rkjcd3J{~YOvs^j77X4qy|rl+&6UiJO`ZTZ+I z*9{gvyea=L<B7D`(lg$(^x|qx-xrHj=gzKHwSP4UT4^_x>{y?F^i=(cc)5k&LAmKq ztMFg-@U?NDo1f3CPCS3l*}Z@63;W0|8%iGBarl?;zOh;V&CT6eUuX5#zxi`oZ*R(z z6<><pziw5y`uWuAr!9v*zOWAY<Dr)|Cv2+m`}>#wu8x=2wX(?kbmYYfx5?KYMgJ@N z$NSlRZ`9FsIeQm9`RMH4pL=`X;;)~s)%@1}-}3*;WntSN^WW_%ezNrD#cP$7ucv+% z*E?0;nBXXO?VDHK6>Vsvq~-a!)YDvlpW5FoP2b`Ja`>^8!HeSk%+=I?`RCo<mwaze z<@NJ>vvz#AzPCI+@&1ji{dQ89!{pk3|5`V{I`qr$_fhLJWY1^j=jHF;x%clTZ~e^t z{hGTjZ2jD4y~teCD{8Wu|D62=fhD#%qNlI_@}1pwJJ>@1{y(YZewrfpCQgmHWc{k^ zLB^4s{VzA~jLCB={(PZ1GUB%O&%oo*7M0+nv(n~Y_I%3vS9H7d`m`R?&Czx{pZ)uN zeSP%o|MPi;rQFWBDN9(GU#NcYU{B>|FF)1US1*0~%D#Wv{y*=m)8EHzThn#E<Z^QV z)9KRZXPd8o5_Ltt^v~albN>&`+pGWPrnGM7&X{+{TDcc7@2>YRzv#2w>4Vm-mhD|n zv%|bk-`aX#=Ihhy?{hc5HqTp?Q~CAj+VH-rxr>kYi}Kdlnf!lw=5*J(pKreJm;c7U z;E8)Jw1wzyu<!5N{O9^Uvur+oOca}-_wt3tn@6)wKb2Y@Q}O6Y^zHdS{Eo_hVLQj0 zb9>+7C;S&}wcls&es}B7(|O@>clR&f9d-W8m6ygppB}r|nYlCO-TGU%9^Fu@xb&oR zb=W$Mo%Qd&<@$@r{`|ap{mJ-_Jl##z*Zo(2oiMGWq~!hGaET{^U-o=@H9ha|qlokW zAK$f?{TX_D|MKb==l1-p5B$mfb7!6N-_MQahyF26`Lm)LT8wfW_;l{vp*361S-+DA z{v37b;>V)*ufgs#{@3<fv8v|wwnLxpt-n`a&wDJfsyO(hmU*4=tl90+Pk&uHAJ-*! zXHVhbXT8#TJ01u>joF#BIV$Sg*Dr7G?OpwLS2nkE?4|zl|0PeJUfrC2PQ%KmAn(Dw z-S6cds$INR`S8ifpX&d<&Ac!2_v*^a>8obc277!`W`R0uMfu(R{hF6&&vyI!Z|kH_ z!N;`zt+`(P;KA9qZ(Z5bw{EHVx$QPrc*pJ^TVl)KPJEny?Z<YPl4a3)({FF9J-+H{ z_NVB@B7Z|=o@W0$<odg+>fFBX_f}2SezxtL=FNX>=Xh&gZ#?d1aq`monxz@%dt>y< zLG{u;E`wS6)%-GN>_3LCzV5PK-nT_@;vbF{XkLGH{oJ~BheY=8ez$A&qt9ouGAnm} z|GH@XeC_+()!*FqugE)nZEfp>FTL&i<&IyTf6Zd++g;h^@9%A0e`Vfg(TM$dZ&yv# zUbOjM6~9c+JG&UiV_oZOY&X5TIK4W){_WQ5UUdoY-@ljt`7GP(?2$kE_v`jN`Ep>= z+TxU}b05wyn<W1b+FEy?du!9r|7YW8m{cZ}Ouw=5*YUN{?r}<1O<(WFNZVH2eZT+4 zJMsLG=eO@%TOVEhU^@G?_0hlowaf4M|BzkXZ{ODS%hUfIa=j{bzB=sQ_Y=F^=l^}! zzv%IXzt;CPt%4kntqS`&>t0FWNzIDQQBnJLe_nZNUd73$dsWNhS1QD`?cEmsNm)bx z`#!n9r3?%VU%kIBmiznay8CDGyq||2eVm{B?~rSFe4%UVcH5FK5nEoaxxYoTKJmew z101Vej;DlqpZ@Xq*@|*O*>-35e%`sC!VdaukC}Q36t2O`&#$?^BeUxAQBVg#?UC)L z2hLOHe%qK|t6jC>`PA^mm!-A2r!%g9JM|C8+0FKU0*~JRz{S9zx#U-1yjlL8fEzo$ zy{mq|_LSxHX|t||F3Y$)^XjFb89JM`@1CbtIaQ{8;r$Nr+D*K|Qp?QEMgMB1PdWA{ z@z3x3=i@C(f1TO();dZ*f9}1KkD{B?W9`4Mdg&|fzTeqk`suB47p-1pKG%;ad3=cV z;^WzNzib;+qn~d%w$jz##>&^|!PdQ#<QLAodg;>M^7){2;t^v1>cqd=-=D>onVa`! zF1{zI^YX=tirn|v_VuP!A1=@TdHPRl^5WXLF{^Z5zUX+D@$qr2y=F`w+Zyj#dUi#B z&MvaQIJswk&EL<Bk3ugk_nU87@+IPZW3#HdoS9F?kD%lCrx!o_I@`=u{=>@4>1}!E zgN!1}-ro)Xa^(N2wYpXF0<M4as$=m0wGwSs$$wq+{=)mPl5J=5RzAv<ULF&1lh=R$ zU;T^C-T6On|7k71Zy6nbH2vI~o38F$7gs&;+3u~U`|)WwD1Tdfrt*FN+FR=QaeDsl zwP%!URxe%pgzKaDw>P>r&oVa6xn^Nn_3x+onf`g|S3rS~2<|)ezjBY&cyl${X#JJA z#Hk+xK8x#V`AGjsooe=RMen5SYV-Um9dn;uT0gb^{Csb2AelAiDX5@0z5mmurQ7pw z?%Mk2HHdst_2mDJn=_@Cm7deNYL&m|W%0%4h3i0ZKMfSf6(ROPc`tl6PO4P3<+{wq ztv&Iz)5)!S*Z+C+d}rdN(38HdT$5$mrTl-+x_W8T`sL~0-^H5i#TXnq{-KSNw>bLw zn|ph+efH|VKiDqUCuei9zNj)i{ba;X@!a`ZKP?vX>tETrSN`+wj)teJ?=NCC5803` z$iT29c;BzRcYg0$sL;w~{7g#CB;<AXpHKJx-~YdR7ys7OGVesEq@P}8XFffC*357F z2UM=f?x{Ipx99ts9pzVUPMmG_&;H`%Cq+9&C!P&j=CgOj6Uk@#F<a{H-U^h@wd7y+ zVhgCIep=N2`&ZnJuag(*ZvEPIO*42I&-RstmWy{SS*$$$wCnsIQ&x)q{dly#=Iox; zr)vVQt^1qyMa8%NlFU8+$DnbfI#t1KhihxWh3L%NR_<SwTg~$CY{>dLD@M<J@6MMe zKANB2|M0kd*}oIpPsJ}ZdSCy;@e8O>)qis{`k-^`u7pJWhno+HGc<gNc<62Q_s`b& zBZarv!|um_%w2ps;q$E7v-ifa_<T|p236CC7XO;)^nUO6+ZKMix9|S6KB_yfaQ64~ z>3?Tl?{4s{OKF8h)~1Bd$9VU$^M#e&ZsX*Qul<mHYy!`uKPLo0t&tBa>Xvw24L|+v z&8uIx4#%(m@hpDd+iic^gaQrMI<`%j@aKdWOqqKrSlO=PpPw3wnWx^Zxq0dK_ME#` zckUnC!X_E?D7md+&B<{4>K{xD0+VXKZf@6qS-9x`6N~d3_H6!K?Qb6&`F{4jJ6|3+ z^XtESA@R`a|NrXuPxZH)$&)<)_|E?2zo+N#G5m63%1ZIxn(L26_8xDl4!igKzFg&V zUOql4R-+dM@!&k3@NC<;z`q`U7Kgoly7uR3_PWZ1Fz=nFZy(EB+fP63o1bS@^ylp1 z+S=`P4?nl}$*q3C=#!rpwWZ)9$K>B!db__&s+qng_2VP;`L$_l%D2?}e}{Ic@6|rf zIjQZFJ;&<vB}q^y{hWH#eEatO8`ASx&%Zd<%02J?UhC<nbMNg)y!y&?aa?`W<17C4 zuP*c3{JF5Ac|WA5t!Z_`VBy0z?~^k^P9EP-clXw!D-oZv-q-&)SOD*s3tJX`(zOp3 z-MnFX&n8%8OW9?a`edAluiNx`x9FV2%Jk=ZDnB2)^?RAA-`>=ZkM8a+m$m$tX1P~? z`sulKzvDlNubWw3%F6$VyZisL=TpPmU)S!8d3WWdvF-Xjx9-(lW?ub%{ZsFPS6^nz z+kC&Xr}DE|{vCr)rrYyxi`iNf<Rz@(uehAt-}~8c|CdMg`afO1y?^=5IMncDzt8{W z;Nr_*m3(LRoT9{khyK6#c{3uvdeQ$U+umBAiac}MU}0ia@#S7_@ikF9A05!=pJVa! z+T`<g#&`T=1Fd)O;`(0n*mu74<>oW~p4#<CKfamff9+qWgoQaMAIy!7RgQW7;Gwf> z|NFOc(HYjIzs`I*cdkqR)x&-J9&)|^mh1ncan+0`?#n=<Z!OEeA2>Ms_U&F~Pt6+B zdlw%cUsZ9-bF%umd6vref0VtySNd)5_dc2W&p+?(mXG|N@$+;$ciH=2ng0%LIkqx+ z*Ux{pcQ2~0P|lcfLE!cGU%fe?F_}B@Z{NI<%IkWxzOJ@pP5GB*dAsnB&3~?an9LIY z>!x?z0xh+l6H7r^_+e$@`FkIIb~S$7@%8;bz52vI+{VjI{c=GqMw@+?)9=+izVNW- zv39-L{Q@rC%x6oDkChxgJG<>|FsS)`?@ma>^*P})K1)n~dDgc$`nk*d?H`1A%rCBg zvg+O*u4~`+{XTcI>;L?=Nvo??fA!Zm{pJSuw-2lg3JyOzJyPpdN`_pV)Ym7f{a?|( zIp_1T<EKs*U(BC7`?l3{>HaXqw^O&&`<FkweE(AF>0>3g_c_;I1PvGc{;@5$^=4E1 z?_cxY{Caoq-Mf3t?A*n2&+i-Te-XCdvUb(?0|ys1U!Fa?t4yHpUcn0G<M(y$WS;cj zs{j8@y6SStJ{$Jp{POS0ujOPfpZF8(fBxaVeH+j1kVy>9zxOTI|Hzl~3Kf&&pA><D zM=|KH$Ei7yo2%n0O{bsU`>A{W^y0to?Z1XREP{5YOAX(c%l)hRcWB8;;nhX%&eie% zn4c-Xma9#<yZHC_67#>F#s41IYQOK!%f4N`pd$MAw41s=|5j@M2-#is)_k$`hi~7% zyRWw~sd{#Ho@H`I)#`-Y_Xl=1hH8iPd_Cy?y+X4}<=VHfGl$}S9DeVvUBk%00cuOE z`LOiczF!Zne-gh}`#i_;KU4o-J8x5;jEc8guP42&ZU6nN?&C4(pQo?K*7}B?`+1_7 zf2xgL{li6E`_eZg)z^HKpI&)(QLdqsn#rHt=j-cUOys)u?OD-I@o#TrUt8T-wAKlf zCHH(iwa|Ud+qE;EutR1R<gWJLn!Uf^;+JR3{yZ%*E7N$R{P8RMez9`p=&ebviJR|! zoo&|p^_lDHy!!7l^Q+etzM3L^>z3Aul+chFdw)#4zUa!X2a8q~_Xg|z2|9jXJT`sJ z?{Cj)zy90U>u|B;(9G%QcBI|A>Uw=+etxQoJ!Jmk!-~2q_hQTMPTl|Eopt*DlGoy= zcb>d+&C=C7es$jedGG39z0|xD%Ubzyp6K^!HvisPr&q^Smi|5FE3CicLEE-%xBPu> z?_a*#pn7A%r6-*i6?@mc%AI-FX!X*iFPaNjv#X1D#=LKAKAbBd^!322-`qb<-|OG9 z6RkaP?OT}Jr{*M38h9A^cg4o)uzTS%UVMLi*WUKdb@NxgVme*5)7z@w@3X!?LrTr0 zru=!Z=$yoVhg@^7$66l}Wjn_U>R49)_BG$Kb?WCe$6s#p)<5|_Gk^a|jrmWDcAm3- z*Ktq9DpSv1ctghLN2kD{-!5OZ;Y_xhy3jLqX|aD#pG4k{mORPt1#Xr1zjE*W`}qC# zK1mDn57+1C%76Wpd{?P#4QSNlTkDV2+qQLu3%q~*a^`KT^ed~s?)mg;5v%!;*uQ>X z=AZfB`S0qcrQ4^=Tzau&=bt0H_GkPKx%koO`nPlQEEm5}e0zWU{yEZOzAB&w*Z)N> z;r`LLqZv=~dx3kL{jc10&&KZn)eWDf`+u1)lUVuw%d=&RI%7k(Z(jYn+TVWeew($0 zS*g1piS4=b``4<e+G@V@EIwWJn`^f@;~?)=`JbMX)z?MsT=XRG?#0l(trPp}=El#= zu9p9OZF^UrNSJ@W!md@@4bB?;`rCZ%TbRSA=0tF@=>B!_w0ZaU?*1_G75jNx^E><Z za;y9IeNB{k_d0sxCwVUKs_X0iKKpV0=ZRhJ{_`uVZ(h3X-Zyo*-~5$-{bLUA|7m`) zZo2qfz4C`oPC^F{xp&uHGFX`SBA$1S^o>1*hgX!F*FRjeD6{{f>aoC!nl1Gu2U&l* z2Ng+VZ}74*m?RIG<FZ*Lzje#w-S>4{mb|z9TXJ%*?7u&+vL975wEzAkrn_U|2F(xC z^LOWZy1Q@n+n4(Kn)31cclK6a*X<VPzy2?->f`?F6=&RM#7fN719dJ}UCsVfzi8#V z{r_J4EPD5GxAlCjQ>}};{1j^v&)++w+!^;jy5IKM@=PBUdkI+ECM)gVA=m!=o0In2 z6y!ZP_w@AQtN9;w>!mO6eO?`(sQLQNHA~g$yZioI+WmZE?B2J3XU@yF7x&wK|M&YN z@B82KbsPS6zIWLlz2V85LoZg{3zW5~S#gH{T>aH+VgE80udZ?r{aqCGUd3MH_4MhW z)>J}>eO8*MyZgdcUdXUj*T1)C<9B2nI&L5L_4nPIS3|G-j(b;I`)B#yq;G2;mOZ|^ zsWkhoy`|pE7Zp3de|;g&{jcJ>e^+0~2F_!<-_OytG&IioyUh2g{}=7z<OvJ;WNp)O z-_P)<3xGAcuAG^+HoE;>1E^okD?eA{+spHRp8m7F8@lH>d)WQ@=bxU2z6aI(XD8=_ z684_T&!7RKi{j@&&6G8lU#xmMO<zaPs_501nTw*rZ>mY$-kz)dIruZUS5Z7|r=f>n zcJ*<WPtD-@#}@ami_<<`U2h<`=K0j{<>s+58A0XutMj+Mdr=U7pEo!6wRxVFj_lf` z_qX>HI{(|k{G9F6{ig39`{&l*Tsl3b?4|1E*?M|ERZGtL)(15|)U|AI&W=49y7#iw z@@Lz<_5O686VoZq&Htw=S1SJaj=}1xZn<M$o-KPZ#}VQPLs))3n{#v5*N?i<T%j+n zyfpTH|Krrx;`O)oFRu>sKK-Y)`&hmBX>04}XJ=pkd}?*8@^+oLn6>w&OQ%_P3;$II zWyHL?`}=?YIlJ}e4y)>CJ&T$l&bV9sUDop7vkUgz|KC5VwLcYqd-mz4R_}KtUwQ2k z{?*JUL!(N<y>I`|llHSCOH83o`Y#m<nFn=0tMl?j#mA%5+m5Zg$IQNe!;kz=57$4s zEcfnp^u3!mElN*qduz>Qd_v84-j2VW?`?j*F%GTy<K8bXefjI{b-TaKoBI7~T8@7H z-c2t~fZ|@qwr}05xz{X8!@RvO9{e`jPTID3CaBcC1FC-Wo<C}OvFe`pEWN(CwNLEM zUHdl8|5LLeyxg&Wx2B?*fkA$)r;B4q(B8rqA68A(=DPg%Q+LYk+xffRT=Je)Ue>qr zFKBS__47E>{Cj(HZ<p`+eeUL=kK10atNnO%`m~agknacox9ivMoU?Ux>+ucimoHCa z-dz&eDOm=ZIs-+JeqK&bT)n5edx>lAPq5=+%kP?gdMz4zRl0ZT&cttp+qP}5nIs?b zW4;EcpKxGB-IYtAD*ofq>3K0b5;793F4h;_{wDkOt+}b++@imCR_EpA-v^~0f4%9q z_b<1u|LMKG?ql1wZKw72K6xx}UH|u(?_PF3zO^2k)6d=5TYY`sym>D~wym;xb2<H9 z+}5I_cdl6;&J6&yLi1kF`TF|WwN2lj7VZ4WE$(W+^x5(&Q)8}17fHpgyE13D;o7%* z)1TH}R6WM3`u&xz<<^3Khg>i2%F50Ec}`kJ#)K<i=Hc2}=kgkMh6V<m=&Ik-)<*X- zZ~c?7HS1_sc-_a(3yZ&K8E-E=XD(N}Me9Ci#InEDyXt=)liqLl>Gi*V|E_6AE%%ve zl={jwbbb_v=!8EBd;S-KJK{aB+?C`aYX9GR_b&PG-{jo-&FSZUd`bWJ>0R~v-sC9x zE1D<yk9k9f2{>=vy!!Ri`MPWO@82(90czmCu($k@ukJg~;$#267l%$x{$KJe_4=9J zyV7PSJJe46vw#cQ{bRhMf4kg4^u}3chK3I}&N45k^Q;Syg;vZAucka<4+T$(%U$JP zAm>>Z;0u@Pd%_;N3^WG_lG^7{7Xa?GJ`DWJ@T%hp`$lL!WDn3+vG3>xm3$Q;_6@rf zemeHRtX?wz3HwK|XZv5VPbiusf5Z>!ABS0<bphNktAe~gHA{h~6+r6zCdpsvgPA8X z>Cb{?aPxYfuy2JqNg-6l-lGAg?#qNf3X*X1oIW)lSy9Kp(Bl4;*@O8c|D+{Qe<&1A zl2`G8IbxHFy~jM52`rv<6S!a|Oe(tf;K72xhA9?zuAArI+tXOg+{VPvkN{5s4!Sd+ zvTGWxUi$Q-ZnTt|i3Aq|LsQ~9d55>2brbZwt*k(91}!YPGUEyRN_YrQQ?d6@24#W| zE9w|bm47;VK`U$K6}%_;FTtYQf!CvMLKe(1kJNrT-h!#?nEHerGRJ7MO5UMH#eM}p zOkI-FPsh8kXc1sP$^WVo?74m4zg)K7y{m`WLDaJ@ARX*7V@OpPYTuwW>Cb|Hpul2r z`^vmR?<7Bb>I5tm2eKF}C3cek)eMk<2_g0kt0w$eun#6BVSkeU6*wS4QiYTLD8L4Z z8IC!BYIcB|KH~{HY?PEeK=LGi(;`s3NL=M#z%)s|#SIiM2CL)`M0nN-D1br<wEQMn z>8Iliu$veEVw~dtsW|~=t;Xah?26#huICkd0N+V|rsYuM5<(`)xA?=1JE3BKf(2%r z@`OJM+A!mI+&?w*fD<0bgh@}>Z^9g;;Pt8b3|z+LQ?m{@9rwIqH<<E-U2-YZq6H40 zn%}@=CaC{(1oufn!F9s34xBhai*io#gJY%l6?@8@C+v`}(ZRsK4LXzmG~9<-k}~ZH z`=3T=_$tg+v45ZpbEc%qPsXD#PNecrMpamd8o7RAhBP#LUa_aNKVjzqCn1AX@)HCn z$r}hkeeEFTQFlNCX2QYAe;O{r0`ZXAPsUW38(4fkF`t6@jbrK)_8z!{JSNE-n1NDS zW8mM04<2;~X248fQvS&}73{&DSL`h=pP044xlQ6KKS$dW_7HHEH&`Vvz;=>fLLBBG z1{M1R_!6G4exI0E!RE~auK)Y$%x{|_((o~R@26A$zuw+#|65*Pnu~$q(1(ZZ^8dEm z-u)jQ9Q=A}xD@Y#eRIG|Z5pzW7nez?nb_=0Jw2(9&zbRxAa`|R)~!vYuQx5-F3k?g zrLFn_{VMhgPQk*X)cq6lDp+a>h*z;!5QBNtD(N?$f?L^V1_lL#$!h*JACFE?+qy-9 zOX2L9Ps~Xv>p;uRK#LZ-#qJdBSbsi%Aye%qBc#YXv+{C!h`qv76?=s;m~m4k|7nPV z1=W(7PuQ<?z#@FBiv5Bzn2TPj{A4tR#e;|9N&Xe4uz<VeQRlz}Q@2v-C*w)DdF@Zw zRbY8&g3u)S2`Vs6QXX{<hhUmIC;w@90*ky!sy`W5Le~!PdrWx3z69KCP6)AA(3~Vc zK?&w^7mqrJU9eP_<^PE}$Q_!!0+J`mPjG{&i<<PO0lrA*O3xGaC9p>E0y7nRg{v?} zm@52a%!Fmvj@eJxuQ0+KyGF%+fgQ}MN6J4LOP!%s2?(F$U!e}O%EF`0VJ<A?hRl7! zenlQ;M5$+;Lo6&?hD?0I4xd(a*y>T|APXxrLRz1&UonU3I`G4@&fzXhs%P>O_7+&0 zG?1GlFTep+=WtZTUO@)tic=nS4goM<iB9;_Z~^A@W3TocJm|QXp(VXC{rR@H);-Qj z&+3Y9x&Ht5N9z7ddwI+1SLXXoe=s;4Kwb>#ruvidV9;NoYU8Sl>htc~tljysIqF{K z|CgWh_dTETik*SwpPTnr=2OXu`T{!IHIs{8neX5Gb6)V@<=Ivj*YEt;{M_7scG*Ss z9(x9XPtv;DHI1J<>Ksml>_2j^XH^{ogW=wbL4O;JJn9@Cpov}dsB`#%CMM`v=b*Nt zu5+&>sD+zUwRq_-#>12UG^_~#xp37lMusC_K+b7X{K+V}=$GoZw2-gN3>`0fr@dmI zqV$vTCYl&189hTYPhRyWW8@N4y*wcEmZFONnfHV}W%{e>H)aQ3<!4Y>W*)Ule!^)` z{(6k+YKD{i4qv_BoL%vXok8HTwC+`YBmYm#0e>e|8^c}PoMR1MmB{d<Xy@zaaaXG! zEGS&DH&eX!(a*D&XGz<ycslpne7pb8s{Q8vI-B2X&mgej<yl|%zWtdp3e9IeG5?r5 zy&AIK^UA!PpkTGUSpNyUcJYM5N&W}?A^Vqr(t+Vi+k$|v%{-I-FxWYL_3nDb&LALs zmEXwY6SKy$U#hO4!0LR(4l2{9EI{yQD*a@ff+T-a@h2mXH<Es*Ps|dXucnKD0zz$- zd`jaJb_1@f`kJfc8612<>^bH=VK=Zs;?I7<9w3IqpYen}Ko5yO^$EKH+p74W5POD( z87t}pR8I0I<iPw8_*cPZl02xu_XK6Pq(#3R6IARQyjIk~`5r3v4O}Y`d;=BxhEqs< z2^IT>!oa_iK-KFJm#@tX9(4?lmm)k2ssUCoAsn<Kdy>3EDUt<$C;eeq<%S3WP{TFA z7vW-1iQb?UjId#s@=wN)`G_F8B7TxzK@3TQyNZ3o4kR1)PWZ!+v;^U#QpZos9n%p8 zfEu#`)(9thSf1oxAcho1XI1PQq>%i0WzrvpEO&&HFL{1qUZII(fZ|F11=o-a$W^g# zU_o-xDvvsblK}`XeVOowL1;0;g_?>#8C5)yEO7Y5>@fr3Vimtn%pUWQ_}-tGJy?+( zFyRUN1f(FDAaasl!4=6tf|KMyg+4r%-+I<DtVD8vsoGD_CUS=W75j!OJL}$s?q%nb zYQF_4e$Y#<D=Y8+x%PvZfe|!1{q)}TUS?3`FhQ>>KIn@bL&J<0wE@+W<Q=+R)Oue0 z&A^bf=)0qmihaYD3%@5V`p(F3#N~bS5|28Dl?%SBy1r*-=;*x1&N=B1!=&X1{>e#y z7=)G~_??sgFicv4;AblQWK?l`@7;9|lnM9p2RVIW_Gr5|U1TpmgMwO>{F3%3>=PLF z>T6bkGSL@14^dG3eA&0;MJ)q^;l<wyp!k_}3E?0Sa0DX^1V^wSk^#D)6oO;`D23d3 ziSXem&pL*WNCtkH3`&9s7lV^vH^PNixIt+cDab%+_|;{Ei+(BnWDJ>!q(Sy1|AJWv z7l3+r455pWZ1DQTyn+wm<Q4Ig<Q+tjA|ZD2ABJPz2tU4>`-I&9Db@rSPVz4(LO2Q3 z%3I)s#OFE5zd#6yFLaVW;S9n>3I>zp58ObqutLSY!4N6d6+G)0o+5=&fM*>;>P3W? z8$9b679$101`r=f{{zoD24y7mIurgdv@J!1fTSX*s73HWMeP)%R0b-FMUc#2GZ9pj zBLW0el>cBxgy0Fall%|LkvzCr#l9iN@4a^)G}&$P`NVwTx6oo}s_p^RrU>(yRqPcm zBXl>+dBUE86rmH;e=<rUxn9Jh&Or>3q8Ob$>l~gTLX*)|#a<yA$vBOZ{3iquj$NQ~ zlK%u3f}db9Nqzz+BDM~2sMssiAn{MA*eetu@mHwWE2JR!4wfqR3K2;BnUFyghl(%z zRPJ}8w0>59XJlyHfBMqzhE1Mz4h>)IKpQ;1GbSe`>I+Ei<!5L(@S+yf6LbiGNr75d z1)$8x;`W|dOZg{b<AvX#Efx3JLwr6lJ1zJQ+VHWLUxNQ6|BC7=P}BV4?}oxje;R%* zgGt$+<X@2tlUg<5PeavGsMHFvll&{ZVNzNkU7#oukl4!)>J+aqhDoiO{HLJ`R3?F> zI+~xbU*Uzi*1-qVa)mh%+;Vkz58CEYB`=_TlHbD`rtS`?B@FWws3rUo?h4+M{2rPx zb!R>59Hd^>f;PLnXU_5jHM*hZfg9a=Felxb{HH+#RBUqe++$a9`ow(6391fMq<gT! z%xhAySEz((O7X07I0SP9s865*v+0t<CuVT^1o_Z|^CZ7V+dXy$hlDS73brct3XCul zLMHxch`J1of+f9A*sn~1scQxGW?+Vcdo!>Q^UyuXzXI-}S)O$cZ(!;))qgU6yaY3E z{uA~i9#C%th^yEuz`a|g_>*xXEb6Zap5$L)2lHX=#6JyMFehD+J;}d94JKtf=}*Hd zSRjE0*H-Ysr2bC&)1U=6(DWp~gg#7a@8mxXg)m(#4xgB{mO~Q^NAnZ*5SRl6cuw-K zXo2}&Ws<x>8Z49!c+`OkI7lQOQvS)93`?jSlb*0o@rHU)K<gyGMmNlagh}!SRxmf* zP_a)Cff@I}v+jTgEW{!eeli}0g;+|<6ZR>vlzxKmB)^6u)a(hGljH+@U>ws)@*f0Y z#_>%0(_jztddlP{?0-Pz6gaLotJo_{2Zv?fJ@%BDPuM{zyT$E2vytDY<~3l6o_p*H zKOL9B63z+Fx(TUJlNmm#*q_jVdVzs;l6=ZUsOK6Ms@R{9fyub4*q;!CaRMjFr*y!S zO;WKx!3C2!Jn@f$FwEOM4xgGoz$}%R`Gma@<{AU7ll)GwFjycnNuC83z78{0>^UaG z%>D!#VS<?r8ez(Sn?3ajyCW>Z0~k;8A6f{F@C8Pb<XK=D3)BhZXoMN}al)Sk@8Awn zKgs`UHe71*p9Os|sTEu&`Cm<iNo7s^vp^3nrF4@2RX<GX)ucZQ&Vj-kRMf4o0uANC zq%1t@0?c8qJ>v4I`4=obDcDVtzrqOf)y7GG6yCzs`F?5+fdx_k^CbBt(_!Y_n(#+q zDNG%xQI@#`=3&rir7X-m(=y1A=ofHmIrC<w@jZ)@8x{;#CPmgwPy)yL%xe~3Z_58W zVZi_z^~?kZ+>FfL;N9Qf&AxgmNrNHF=TmbCSbxtw_6Z6n`6t10lR_kDU>2H09UiIJ zdrX2k&}70N1w*j;7k@JZIeltQfdz~Q|4Du!xbH(%>{pn=T$`!<)6o|ewxAJ|EO(gU zR-SbM%5d|%KQ%|eU6ckIqK2tE<yj{HD+d@}s{eHCfrk?7N&ZK$0&PK@N1XsS%-&L$ zPtC930UfVmzrqME<^HKT3zk_y{goB4YL21Q>r?Y9m@5MItJtq#g6s1A)C?~a1Kz9H zONhgy7$^K$Pz3YHA%&lgtuP0&IDcyPf`t-C=M(m1Se^j2w|aVELHog@&VUVOLX*l* z$DJ?}TD(3rZ-SdJ;|cp>n5P8HPV&2fqgG-sKWM2(j|(is#60Q@bYQNNRQ&0f2{U_> z^QUGZnAs^^PuP#bIlWKV<>AIPtJwcwhM90v<>$foFgKjgImusS36gs7qLv|2?I$?% zWqh$~=$ZWI1gxxN@Tg0f1@#?6(Iojun7<orPWJb}0^gzWiMtpqaU^J*>^}p`@D73} z`*Yx&DNo$nVD>LiIoZDk7DWdNRP2plr7MH-5Bs+_PUeO>GB7wUe=Z#P<4Mm11zr`7 zKIV?262JBHXTDs?wXi?;=f&rsjP>|C;|H_T{X6*k`|IQx7*y-z8QyRDqfuQ~_3P8u z*?bHPDL-l%Vh(*$e*N?1%g4*lTQM+9c=(;MK=E|H+J1h928sRr4W}dPSbo+rFdP9X zJ^E?!F;EN~d(ZqJYMQ(z$W+5Rc?R<he_nj}&A`y{@i)Vc-ly&@_t_a7dhW9yIICmN z^~a8ZA?c4D!@I<vhPE(ctt0EsoP`-xH%(snGgSMHmZ!(pz_e^j`e~>N)505Bcjh=$ zOG5NC`N<z)TIM`G?gP{EE#apjFHFnah`KWeVOs2`$qU0Ad1LC+<1gS^(taA6z_c(& z)b*T(YIz_rO}_aP)E0}Lr^h>BTKZCc8p^=5G)LC;9EEAAm?qB*bC^Zz)8iU2E$5Pc z8nVE&2uIe<IS<nkGfiF{=6#E~Pmj-pX>m*VStte5;uujk=P*=Dg3L5|b(miyx}F}t z2-lMQvrq%3g)y>D1|DKQ)8yG<4wIPn^td2gOWMzk)-WvxBkSJ4yq{1rP2L*j{TWS9 zkH^8Zq$U2`$PLrd8Bv!5^Gkx>G<j*5UuN_@Jw6StCFSQvNthPT$hsW3U*e|8OT+v! zW7gB-d*NEre-?_vv@o3RzYR-=CJ}XaPC$b~;qa%&Vz6|mI!!(tW_!oRKRf!M>CmD7 z>2Xn*v6FP{^`1hrM?mV&LP3}n2B-VK!>rg6S+@t~LWT369_zxa(BAOphdxY)Yee0i zb5MsQ$WD`A5A#Pv*VE(PFfFS${`t`jb;*Gy9s4?%6C)-+J?;-vb$ZjEiVBea6CZyw zM9h5J4${$kpZ&lio&8r~&Pw>H3(GYP?5FcnVVeIW{M0=Kbx1?J&VEsth7ane^DSYj j_9XmV3rnaC^|kEFC6yzUcCmDT&Q<et^>bP0l+XkKI2qL$ literal 0 HcmV?d00001 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 GIT binary patch literal 22028 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≀p-g4Pj$JR-I7OaOx<irIxi=V}OJILQB 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 T#KK%aCu@4T`njxgN@xNA<09`q literal 0 HcmV?d00001 diff --git a/frontend/src/colors.js b/frontend/src/colors.js index 9b65e359..096771ff 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 8c3b477e..97d930ed 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 20f57f1b..d8cd0150 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 64caaf5c..cc21e136 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 5954a3b3..6584210b 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 4645b1e1..49bc1877 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 998b8cfc..1fff0410 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 d6541400..8d4fc239 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 d1a3af03..7000610f 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 0d4e8d6d..d02a45ae 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 98c2cfc9..48906d0f 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 d7b43528..3bb7bcb2 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 0bce0030..fc05888e 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 e7e9a225..ab634e4d 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 0423cab5..33857504 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 94c051b4..3c967c84 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 2fc05e25..da63c119 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 464433eb..6f117e48 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 49b7d30d..dbfba1bf 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 9945933a..c546266a 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 6963cb49..f3fb2da5 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 e30ce3c7..9c552f3a 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 e7e08ce3..67ee96c1 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 32a31950..5def58f7 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 66f1f061..78c2d7ac 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 fe59f7fb..7d701c7b 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 32fa8df8..e516552f 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 d4b80a9a..cc505bd8 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 00000000..2e08d289 --- /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 00000000..cf40ff72 --- /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 c3ae42f5..ec3a3f89 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 7c2e810a..305a61e6 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 d9042fce..f25dfece 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 ecda2228..652c0026 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 10f7fbd1..ff8b1eba 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 75b85ea4..7cd01cf7 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 6cc011f0..20f01241 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 877abda3..c33bf9d2 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 ab805e47..7cb44451 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 64ff1ff2..e92293e6 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 5d553b7a..8db69369 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 ff9c5190..3d0432c4 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 1a4bad2f..3de496cd 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 dcc02131..442785f6 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 4255c9e8..c4b11fb1 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 45ed10d5..f492fcfb 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 a392bed8..56ce082d 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 73efe42b..a5ace52f 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 137bc55d..152c0b5d 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 0fe5a836..6fa99763 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 04ea6f81..d433d9c2 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 528e5199..44417984 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 d21e5bba..d33d271e 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 00000000..aa014820 --- /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 65fe0989..64ec2c35 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> -- GitLab