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

finish first template, logo selections, UI improvements, mobile layout

parent 99e1f0dd
No related branches found
No related tags found
No related merge requests found
Showing
with 227 additions and 136 deletions
# Graphics Generator v2
This template should help get you started developing with Vue 3 in Vite.
## Recommended IDE Setup
[VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur) + [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin).
## Customize configuration
See [Vite Configuration Reference](https://vitejs.dev/config/).
......
......@@ -10,7 +10,7 @@
"dependencies": {
"fabric": "^5.3.0",
"vue": "^3.2.47",
"vue-meta": "^2.4.0",
"vue-meta": "^3.0.0-alpha.2",
"vue-router": "^4.1.6",
"vue-select": "^4.0.0-beta.6"
},
......@@ -1867,14 +1867,6 @@
"integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
"devOptional": true
},
"node_modules/deepmerge": {
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz",
"integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/delayed-stream": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
......@@ -4522,11 +4514,12 @@
"dev": true
},
"node_modules/vue-meta": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/vue-meta/-/vue-meta-2.4.0.tgz",
"integrity": "sha512-XEeZUmlVeODclAjCNpWDnjgw+t3WA6gdzs6ENoIAgwO1J1d5p1tezDhtteLUFwcaQaTtayRrsx7GL6oXp/m2Jw==",
"dependencies": {
"deepmerge": "^4.2.2"
"version": "3.0.0-alpha.2",
"resolved": "https://registry.npmjs.org/vue-meta/-/vue-meta-3.0.0-alpha.2.tgz",
"integrity": "sha512-mLDB/vGjn2Q58IFKs5Dtp/STWZ0sEbkdM67u+YXuMreK5EjNHnqhcylQb+xn50pwxCdQD98nGgJlEXkh16Hqug==",
"peerDependencies": {
"@vue/server-renderer": "^3.0.6",
"vue": "^3.0.0"
}
},
"node_modules/vue-router": {
......
......@@ -12,7 +12,7 @@
"dependencies": {
"fabric": "^5.3.0",
"vue": "^3.2.47",
"vue-meta": "^2.4.0",
"vue-meta": "^3.0.0-alpha.2",
"vue-router": "^4.1.6",
"vue-select": "^4.0.0-beta.6"
},
......
......@@ -7,10 +7,10 @@ import { RouterView } from 'vue-router'
</template>
<script>
const pirateStylesheet = document.createElement("link");
pirateStylesheet.rel = "stylesheet";
pirateStylesheet.href = "https://styleguide.pirati.cz/2.12.x/css/styles.css";
document.head.appendChild(pirateStylesheet);
const pirateStylesheet = document.createElement('link')
pirateStylesheet.rel = 'stylesheet'
pirateStylesheet.href = 'https://styleguide.pirati.cz/2.12.x/css/styles.css'
document.head.appendChild(pirateStylesheet)
window.config = {};
</script>
Graphics Generator v2/src/assets/favicon.png

9.55 KiB

Graphics Generator v2/src/assets/logos/default-dark.png

131 KiB

const COLORS = {
black: {
name: 'Černá',
value: '#000'
value: '#000000'
},
white: {
name: 'Bílá',
value: '#fff'
value: '#ffffff'
},
yellow1: {
name: 'Žlutá 1',
......
......@@ -32,15 +32,15 @@ export default {
</script>
<template>
<nav class="navbar navbar--simple">
<div class="justify-between container container--default navbar__content">
<div class="navbar__brand my-4 flex items-center lg:pr-8 lg:my-0">
<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="pl-4 font-bold text-xl hover:no-underline lg:border-r lg:border-grey-300 lg:pr-8 flex gap-2 items-center"
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"
>
{{ this.headerName }}
<img
......@@ -51,7 +51,7 @@ export default {
</RouterLink>
</div>
<div
class="navbar__actions navbar__section navbar__section--expandable container-padding--zero lg:container-padding--auto self-start flex flex-col sm:flex-row lg:flex-col sm:space-x-4 space-y-2 sm:space-y-0 lg:space-y-2 xl:flex-row xl:space-x-2 xl:space-y-0"
class="self-start flex flex-col sm:flex-row lg:flex-col sm:space-x-4 space-y-2 sm:space-y-0 lg:space-y-2 xl:flex-row xl:space-x-2 xl:space-y-0"
>
<div class="relative">
<button
......
......@@ -12,9 +12,13 @@ export default {
},
methods: {
redraw (options) {
console.info(`Redrawing canvas with options: `, options)
this.redrawFunction(this.canvas, options);
},
downloadImage () {
this.canvas.discardActiveObject().renderAll()
let link = document.createElement('a')
link.download = "Vyhrajem.png"
......
<script setup>
import InputHeading from "./InputHeading.vue"
import VueSelect from 'vue-select'
</script>
<script>
export default {
components: { InputHeading },
props: ['name', 'important', 'modelValue'],
props: ['name', 'important', 'modelValue', 'predefinedImages'],
emits: ['update:modelValue'],
data () {
let data = {
hasFile: false
}
if (this.predefinedImages !== undefined) {
for (const image of this.predefinedImages) {
if (!image.defaultSelected) {
continue
}
data['selectedImage'] = image
}
}
return data
},
methods: {
handleFileInput (event) {
if (event.target.files.length !== 0) {
this.hasFile = true
const image = new Image()
image.onload = () => {
this.$emit('update:modelValue', image)
}
image.src = window.URL.createObjectURL(event.target.files[0])
} else {
this.hasFile = false
}
},
setSelectedImage (value) {
this.selectedImage = value
const image = new Image()
image.onload = () => {
this.$emit('update:modelValue', image)
}
image.src = value.src
},
clearFileInput (event) {
this.hasFile = false
this.$refs.fileInput.value = ''
if (this.predefinedImages !== undefined) {
this.setSelectedImage(this.selectedImage)
} else {
this.$emit('update:modelValue', null)
}
}
},
mounted () {
if (this.predefinedImages !== undefined) {
this.setSelectedImage(this.selectedImage)
}
}
}
</script>
<template>
<section class="flex flex-col gap-2 bg-gray-100 p-4 drop-shadow-md">
<section class="flex flex-col gap-2 bg-gray-100 p-4 z-10 drop-shadow-md">
<InputHeading
:name="this.name"
:important="this.important"
icon="file-picture"
></InputHeading>
<hr class="hr--unstyled border-t-gray-300">
<div class="flex justify-between gap-2">
<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="modelValue !== null"
v-if="hasFile"
@click="clearFileInput"
>
<i class="ico--cross"></i>
</button>
</div>
<div
class="mt-2 flex flex-col gap-2"
v-if="predefinedImages !== undefined"
>
<div>
<small>Nebo vyber ze senamu:</small>
</div>
<VueSelect
ref="predefinedImageSelect"
:options="predefinedImages"
:clearable="false"
:searchable="false"
:modelValue="selectedImage"
@update:modelValue="setSelectedImage"
label="name"
>
<!-- BEGIN Hide search -->
<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>
</template>
<style>
@import "vue-select/dist/vue-select.css";
</style>
......@@ -36,6 +36,7 @@ export default {
<template>
<li class="grid grid-cols-2 justify-between items-center gap-4">
<span class="font-condensed">{{ label }}</span>
<div>
<VueSelect
:options="colorOptions"
......@@ -47,6 +48,7 @@ export default {
<div class="flex gap-2 items-center">
<div
class="w-8 h-8 rounded-md border border-gray-200"
alt="Náhled barvy"
:style="{ background: option.value }"
></div>
<div>
......@@ -54,10 +56,12 @@ export default {
</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"
alt="Náhled vybrané barvy"
:style="{ background: option.value }"
></div>
<div>
......
<script setup>
import InputHeading from "../InputHeading.vue"
import InputHeading from '../InputHeading.vue'
import { sanitizeValue } from './utils'
</script>
<script>
......@@ -20,7 +21,7 @@ export default {
<textarea
class="p-2 font-condensed bg-gray-200 rounded-sm"
:value="modelValue"
@input="$emit('update:modelValue', $event.target.value)"
@input="$emit('update:modelValue', sanitizeValue($event.target.value))"
></textarea>
</section>
</template>
<script setup>
import InputHeading from "../InputHeading.vue"
import InputHeading from '../InputHeading.vue'
import { sanitizeValue } from './utils'
</script>
<script>
......@@ -21,7 +22,7 @@ export default {
class="p-2 font-condensed bg-gray-200 rounded-sm"
type="text"
:value="modelValue"
@input="$emit('update:modelValue', $event.target.value)"
@input="$emit('update:modelValue', sanitizeValue($event.target.value))"
>
</section>
</template>
const sanitizeValue = (value) => {
if (value == '') {
value = null
}
return value
}
export { sanitizeValue }
import './index.css'
import { createApp } from 'vue'
import App from './App.vue'
import VueMeta from 'vue-meta'
import router from './router'
import App from './App.vue'
const app = createApp(App)
// FIXME
app.use(VueMeta);
app.use(router)
app.mount('#app')
......@@ -7,7 +7,8 @@ for (let [identifier, templateData] of Object.entries(TEMPLATES)) {
routes.push({
path: templateData.path,
name: identifier,
component: templateData.component
component: templateData.component,
meta: templateData.meta
})
}
......@@ -16,4 +17,26 @@ const router = createRouter({
routes: routes
})
router.beforeEach(
(to, from, next) => {
// BEGIN Title
document.title = 'Generátor grafiky'
if (to.meta.title) {
document.title = `${to.meta.title} | ${document.title}`
}
// END Title
// BEGIN Favicon
const link = document.createElement('link')
link.rel = 'icon'
link.href = to.meta.favicon
document.head.appendChild(link)
// END Favicon
next()
}
)
export default router
import basicPhotoBannerImage from './assets/todo.png'
import favicon from './assets/favicon.png'
const TEMPLATES = {
'basic-photo-banner': {
name: 'Základní banner s fotkou',
image: basicPhotoBannerImage,
path: '/',
component: import('./views/basic_photo_banner/BasicPhotoBanner.vue')
component: import('./views/basic_photo_banner/BasicPhotoBanner.vue'),
meta: {
title: 'Základní banner s fotkou',
favicon: favicon
}
},
}
......
......@@ -13,6 +13,9 @@ import LongTextInput from '../../components/inputs/text/LongTextInput.vue';
import ShortTextInput from '../../components/inputs/text/ShortTextInput.vue';
import InputSeparator from '../../components/inputs/InputSeparator.vue';
import MultipleColorPicker from '../../components/inputs/colors/MultipleColorPicker.vue'
import defaultLogoLight from '../../assets/logos/default-light.png'
import defaultLogoDark from '../../assets/logos/default-dark.png'
</script>
<script>
......@@ -46,6 +49,18 @@ export default {
color: COLORS.black
}
},
predefinedImages: [
{
name: 'Základní - světlé',
src: defaultLogoLight,
defaultSelected: true
},
{
name: 'Základní - tmavé',
src: defaultLogoDark,
defaultSelected: false
}
]
}
},
mounted () {
......@@ -75,10 +90,6 @@ export default {
deep: true
}
)
},
name: 'Základní banner s fotkou',
metaInfo: {
title: 'Generátor grafiky'
}
}
</script>
......@@ -86,8 +97,8 @@ export default {
<template>
<header>
<Navbar
headerName='Generátor grafiky'
templateIdentifier='basic-photo-banner'
headerName="Generátor grafiky"
templateIdentifier="basic-photo-banner"
></Navbar>
</header>
<main>
......@@ -127,6 +138,7 @@ export default {
name="Obrázek loga"
v-bind:important="false"
v-model="logoImage"
:predefinedImages="predefinedImages"
/>
<MultipleColorPicker
name="Barvy"
......
import { fabric } from 'fabric'
import { clearObjects, sortObjects, transformHighlightedText } from '../../components/canvas/utils'
import defaultLogoImage from '../../assets/logos/default.png'
let mainTextBox = null
let mainTextBoxBackground = null
......@@ -26,18 +25,16 @@ const redraw = async (canvas, options) => {
)
canvas.preserveObjectStacking = true
console.info(`Redrawing canvas with options: `, options)
const textMarginLeft = Math.ceil(canvas.width * 0.12)
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.06)
const mainTextSize = Math.ceil(canvas.height * 0.0725)
const mainTextHeightLimit = Math.ceil(mainTextSize * 3)
const mainTextHeightLimit = Math.ceil(mainTextSize * 3.3)
const mainTextLineHeight = 0.98 // Hacky fix for highlight seam
const mainTextBackgroundTopMargin = Math.ceil(canvas.height * 0.075)
const nameTextSize = Math.ceil(canvas.height * 0.03)
const nameTextMarginTop = Math.ceil(canvas.height * 0.015)
const nameTextExtraBottomMargin = Math.ceil(canvas.height * 0.06)
......@@ -45,7 +42,7 @@ const redraw = async (canvas, options) => {
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.045)
const arrowMarginLeft = Math.ceil(canvas.width * 0.065)
const arrowMarginTop = Math.ceil(canvas.height * 0.011)
const logoWidth = Math.ceil(canvas.width * 0.2)
......@@ -93,6 +90,7 @@ const redraw = async (canvas, options) => {
fontSize: nameTextSize,
styles: styles,
fill: options.colors.baseTextColor.color.value,
selectable: false,
zIndex: 10
}
)
......@@ -122,6 +120,7 @@ const redraw = async (canvas, options) => {
lineHeight: mainTextLineHeight,
fill: options.colors.baseTextColor.color.value,
styles: highlightedData.styles,
selectable: false,
zIndex: 10
}
)
......@@ -129,7 +128,7 @@ const redraw = async (canvas, options) => {
canvas.add(mainTextBox)
while (mainTextBox.height > mainTextHeightLimit) {
mainTextBox.fontSize -= 3
mainTextBox.fontSize -= 4
canvas.renderAll()
}
......@@ -170,6 +169,7 @@ const redraw = async (canvas, options) => {
),
left: arrowMarginLeft,
fill: options.colors.arrow.color.value,
selectable: false,
zIndex: 10
}
)
......@@ -187,103 +187,40 @@ const redraw = async (canvas, options) => {
height: (
canvas.height
- mainTextBox.top
+ mainTextBackgroundMarginTop
),
left: 0,
top: mainTextBox.top,
top: mainTextBox.top - mainTextBackgroundMarginTop,
fill: options.colors.background.color.value,
selectable: false,
zIndex: 9
}
)
canvas.add(mainTextBoxBackground)
mainTextBoxBackgroundGradientRect = new fabric.Rect(
{
width: canvas.width,
height: mainTextBackgroundTopMargin,
left: 0,
top: (
mainTextBox.top
- mainTextBackgroundTopMargin * 0.98 // Hacky fix - remove seam between rectangles
),
fill: new fabric.Gradient({
type: 'linear',
gradientUnits: 'pixels',
coords: {
x1: 0, y1: 0,
x2: 0, y2: mainTextBackgroundTopMargin
},
colorStops:[
{
offset: 0,
color: '#00000000' // Transparent
},
{
offset: 1,
color: options.colors.background.color.value
}
]
}),
zIndex: 9
}
)
canvas.add(mainTextBoxBackgroundGradientRect)
/* END Main text background render */
}
/* BEGIN Logo render */
// There has been no logo image created yet, or a new one has been supplied.
// A logo is provided, and it either hasn't been rendered yet or is a new one.
let createNewLogo = (
options.logoImage !== null
&& (
logoImage === null
|| (
options.logoImage !== null
&& options.logoImage !== logoImage._element
) || (
options.logoImage === null
&& logoImage !== null
&& logoImage.isCustomLogo
)
)
)
if (createNewLogo) {
canvas.remove(logoImage)
const isCustomLogo = (options.logoImage !== null)
if (!isCustomLogo) {
const logoLoadPromise = new Promise(
resolve => {
const logoImageElement = new Image()
logoImageElement.onload = () => {
logoImage = new fabric.Image(
logoImageElement,
{
isCustomLogo: false
}
)
resolve()
}
logoImageElement.src = defaultLogoImage
}
)
await logoLoadPromise
} else {
logoImage = new fabric.Image(
options.logoImage,
{
isCustomLogo: true
}
)
}
logoImage = new fabric.Image(options.logoImage, {})
logoImage.scaleToWidth(logoWidth)
logoImage.set({
left: (
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment