Skip to content
Snippets Groups Projects
Commit 6025964d authored by Tomáš Valenta's avatar Tomáš Valenta
Browse files

wip - twitter banner template

parent d0920c70
Branches
No related tags found
No related merge requests found
Showing with 537 additions and 14 deletions
......@@ -18,13 +18,13 @@ help:
@echo " run Run the application on port ${PORT}"
venv:
cd server; ${PYTHON} -m venv ${VENV}
cd server && ${PYTHON} -m venv ${VENV}
install: venv
cd server; \
cd server && \
${VENV}/bin/pip install -r requirements/base.txt
cd frontend; \
cd frontend && \
npm install
install-hooks:
......@@ -34,16 +34,16 @@ hooks:
pre-commit run -a
build: venv
cd frontend; \
rm -fr dist; \
npm run build; \
rm -fr ../server/server/templates ../server/server/static; \
mkdir ../server/server/templates ../server/server/static; \
cp dist/index.html ../server/server/templates/; \
cp dist/static/* ../server/server/static/; \
cp dist/favicon.ico ../server/server/static/; \
cd frontend && \
rm -fr dist && \
npm run build && \
rm -fr ../server/server/templates ../server/server/static && \
mkdir ../server/server/templates ../server/server/static && \
cp dist/index.html ../server/server/templates/ && \
cp dist/static/* ../server/server/static/ && \
cp dist/favicon.ico ../server/server/static/ && \
rm -fr dist
run: venv
cd server; \
cd server && \
${VENV}/bin/python -m gunicorn -w 4 -b :${PORT} "server:create_app()"
frontend/src/assets/template/twitter_banner/ivan.png

1.14 MiB

frontend/src/assets/template/twitter_banner/jakub.png

585 KiB

frontend/src/assets/template/twitter_banner/klara.png

879 KiB

frontend/src/assets/template/twitter_banner/lipo.png

3.33 MiB

frontend/src/assets/template/twitter_banner/olga.png

671 KiB

......@@ -27,7 +27,7 @@ export default {
@input="$emit('update:modelValue', sanitizeValue($event.target.value))"
></textarea>
<small v-if="highlightable">
Pro zvýraznění části textu ho <i>*obal v hvězdičkách.*</i><br>
Pro zvýraznění části textu ho <i>*obal do hvězdiček.*</i><br>
Nezapomeň na druhou hvězdičku.
</small>
</section>
......
......@@ -4,6 +4,7 @@ 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/facebook_survey.png'
const TEMPLATES = {
basic_photo_banner: {
......@@ -59,6 +60,15 @@ const TEMPLATES = {
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'
}
}
}
......
<script setup>
import VueSelect from 'vue-select'
import InputHeading from "../../components/inputs/InputHeading.vue"
</script>
<script>
export default {
components: { InputHeading, VueSelect },
props: ['name', 'important', 'zIndex', 'modelValue', 'options'],
emits: ['update:modelValue'],
data () {
return {
selectedOption: this.modelValue
}
},
watch: {
async selectedOption (value) {
const mainImage = new Image()
await new Promise(
resolve => {
mainImage.onload = () => {
resolve()
}
mainImage.src = value.mainImage
}
)
value.mainImage = mainImage
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="cog"
></InputHeading>
<VueSelect
:options="options"
v-model="selectedOption"
label="title"
></VueSelect>
</section>
</template>
<script setup>
import { watch, ref } from 'vue'
import COLORS from '../../colors'
import TEMPLATES from '../../templates'
import { generateDefaultLogos } from '../../logos'
import { loadFonts } 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 PersonInput from './PersonInput.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'
</script>
<script>
loadFonts([
'12px Bebas Neue',
'12px Roboto Condensed',
'bold 12px Roboto Condensed'
])
export default {
components: {
Canvas,
Navbar,
MainContainer,
LongTextInput,
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 {
selectedOption: personOptions.klara,
personOptions: personOptions,
mainImage: null,
mainText: null,
personName: null,
personTwitter: null,
logoImage: defaultDarkLogoImage,
colors: {
background: COLORS.white,
text: COLORS.black,
}
}
},
mounted () {
this.$watch(
vm => [
vm.mainImage,
vm.mainText,
vm.personName,
vm.personTwitter
],
async (value) => {
await this.$refs.canvas.redraw(
{
mainImage: this.mainImage,
mainText: this.mainText,
personName: this.personName,
personTwitter: this.personTwitter
}
);
},
{
immediate: true,
deep: true
}
)
},
watch: {
selectedOption (value) {
this.mainImage = value.mainImage
this.personName = value.title
this.personTwitter = value.personTwitter
}
}
}
</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>
<PersonInput
name="Člověk"
v-model="selectedOption"
:options="Object.values(personOptions)"
:important="true"
zIndex="10"
/>
<LongTextInput
name="Text"
v-model="mainText"
:important="true"
zIndex="9"
/>
</template>
</MainContainer>
</main>
</template>
<style>
@import "vue-select/dist/vue-select.css";
</style>
import { fabric } from 'fabric'
import { clearObjects, sortObjects, transformHighlightedText } from '../../components/canvas/utils'
let mainTextBox = null
let mainTextBoxBackground = null
let personInfoText = null
let mainImage = null
let logoImage = null
let arrow = null
const redraw = async (canvas, options) => {
clearObjects(
[
mainTextBox,
mainTextBoxBackground,
personInfoText,
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 = 0.98 // Hacky fix for highlight seam
const nameTextSize = Math.ceil(canvas.height * 0.03)
const nameTextMarginTop = Math.ceil(canvas.height * 0.015)
const nameTextExtraBottomMargin = Math.ceil(canvas.height * 0.06)
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.065)
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'
}
}
let nameText = options.personName;
if (options.personPosition) {
nameText += `|${options.personPosition}`
styles[0][options.personName.length] = {
fontWeight: 'bold'
}
}
personInfoText = new fabric.Text(
nameText,
{
left: textMarginLeft,
top: (
canvas.height
- mainTextMarginBottom
+ nameTextMarginTop
),
fontFamily: 'Roboto Condensed',
fontSize: nameTextSize,
styles: styles,
fill: options.colors.baseText.value,
selectable: false,
zIndex: 10
}
)
canvas.add(personInfoText)
}
/* END Name text render */
/* BEGIN Main text render */
const highlightedData = transformHighlightedText(
options.mainText,
options.colors.highlight.value,
options.colors.highlightedText.value
)
mainTextBox = new fabric.Textbox(
highlightedData.text,
{
width: canvas.width - textMarginLeft - textMarginRight,
left: textMarginLeft,
textAlign: 'left',
fontFamily: 'Bebas Neue',
fontSize: mainTextSize,
lineHeight: mainTextLineHeight,
fill: options.colors.baseText.value,
styles: highlightedData.styles,
selectable: false,
zIndex: 10
}
)
canvas.add(mainTextBox)
while (mainTextBox.height > mainTextHeightLimit) {
mainTextBox.fontSize -= 4
canvas.renderAll()
}
mainTextBox.top = (
canvas.height
- mainTextBox.height
- mainTextMarginBottom
)
/* 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: (
mainTextBox.top
+ arrowMarginTop
),
left: arrowMarginLeft,
fill: options.colors.arrow.value,
selectable: false,
zIndex: 10
}
)
canvas.add(arrow)
/* END Arrow render */
/* BEGIN Main text background render */
const backgroundHeight = (
canvas.height
- mainTextBox.top
+ mainTextBackgroundMarginTop
)
mainTextBoxBackground = new fabric.Rect(
{
width: canvas.width,
height: backgroundHeight,
left: 0,
top: mainTextBox.top - mainTextBackgroundMarginTop,
fill: new fabric.Gradient({
type: 'linear',
gradientUnits: 'pixels',
coords: {
x1: 0, y1: 0,
x2: 0, y2: backgroundHeight
},
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
)
)
)
if (createNewLogo) {
canvas.remove(logoImage)
logoImage = new fabric.Image(options.logoImage, {selectable: false})
logoImage.scaleToWidth(logoWidth)
logoImage.set({
left: (
canvas.width
- logoWidth
- logoSideMargin
),
top: logoSideMargin,
zIndex: 11,
})
canvas.add(logoImage)
}
/* END Logo 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: 0,
top: 0,
zIndex: 0
}
)
if (mainImage.width >= mainImage.height) {
mainImage.scaleToHeight(canvas.height)
} else {
mainImage.scaleToWidth(canvas.width)
}
canvas.add(mainImage)
// canvas.centerObject(mainImage)
} else if (
mainImage !== null
&& options.mainImage === null
) {
canvas.remove(mainImage)
}
/* END Main image render */
sortObjects(canvas)
}
export default redraw
......@@ -5,7 +5,7 @@
<link rel="icon" href="/static/favicon.ico">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Generátor grafiky</title>
<script type="module" crossorigin src="/static/index-8627706a.js"></script>
<script type="module" crossorigin src="/static/index-af58fe31.js"></script>
<link rel="stylesheet" href="/static/index-656e3210.css">
</head>
<body>
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment