all up to date

pull/1/head
Borja Robert 2 years ago
commit 501cf2bcf5

10
.gitignore vendored

@ -0,0 +1,10 @@
.DS_Store
node_modules
/build
/.svelte-kit
/package
.env
.env.*
!.env.example
vite.config.js.timestamp-*
vite.config.ts.timestamp-*

@ -0,0 +1 @@
engine-strict=true

@ -0,0 +1,22 @@
FROM node:18
WORKDIR /app
# COPY package.json and package-lock.json files
COPY package*.json ./
COPY vite.config.js ./
# COPY tsconfig.json file
COPY jsconfig.json ./
# COPY
COPY . .
RUN npm install
RUN npm run build
EXPOSE 3000
CMD [ "node", "/app/build" ]

@ -0,0 +1,57 @@
# Carteles de DMD
Aplicación para que los grupos creen sus propios carteles.
## TODO
- [x] Separar fecha y hora en campos distintos.
- [x] Quitar el 'de' de la fecha
- [x] Generar texto de boletín junto al cartel.
- [ ] Guardar una copia de todos los carteles que se creen y crear una página 'admin' que los muestre con fecha, permita borrarlos y tal. Se pueden guardar todos en Sqlite y listo.
### Verde
- [x] Cambiar color de 'dirección' de rojo a verde
- [x] Cambiar la plantilla por modelo nuevo
### Azul
- [x] Fecha y hora juntas en la esquina superior derecha
### Corazón
- [x] Incluir todos los elementos
- [ ] Confirmar con Esther
### Nueva plantilla
- [x] Incluir
# Carteles de DMD
Aplicación para que los grupos creen sus propios carteles.
## TODO
- [x] Separar fecha y hora en campos distintos.
- [x] Quitar el 'de' de la fecha
- [x] Generar texto de boletín junto al cartel.
- [ ] Guardar una copia de todos los carteles que se creen y crear una página 'admin' que los muestre con fecha, permita borrarlos y tal. Se pueden guardar todos en Sqlite y listo.
### Verde
- [x] Cambiar color de 'dirección' de rojo a verde
- [x] Cambiar la plantilla por modelo nuevo
### Azul
- [x] Fecha y hora juntas en la esquina superior derecha
### Corazón
- [x] Incluir todos los elementos
- [ ] Confirmar con Esther
### Nueva plantilla
- [x] Incluir

@ -0,0 +1,4 @@
{
"schemaVersion": 2,
"dockerfilePath": "./Dockerfile"
}

@ -0,0 +1,17 @@
{
"extends": "./.svelte-kit/tsconfig.json",
"compilerOptions": {
"allowJs": true,
"checkJs": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"skipLibCheck": true,
"sourceMap": true,
"strict": true
}
// Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias and https://kit.svelte.dev/docs/configuration#files
//
// If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes
// from the referenced tsconfig.json - TypeScript does not merge them in
}

2682
package-lock.json generated

File diff suppressed because it is too large Load Diff

@ -0,0 +1,25 @@
{
"name": "carteles2",
"version": "0.0.1",
"private": true,
"scripts": {
"dev": "vite dev",
"build": "vite build",
"preview": "vite preview",
"check": "svelte-kit sync && svelte-check --tsconfig ./jsconfig.json",
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./jsconfig.json --watch"
},
"devDependencies": {
"@sveltejs/adapter-auto": "^1.0.0",
"@sveltejs/kit": "^1.0.0",
"svelte": "^3.54.0",
"svelte-check": "^2.9.2",
"typescript": "^4.9.3",
"vite": "^4.0.0"
},
"type": "module",
"dependencies": {
"html2canvas": "^1.4.1",
"quill": "^1.3.7"
}
}

File diff suppressed because it is too large Load Diff

@ -0,0 +1,975 @@
@import url('https://fonts.googleapis.com/css2?family=Alegreya+Sans+SC:wght@300;400;700&display=swap');
* {
box-sizing: border-box;
margin: 0;
padding: 0;
font-size: 14px;
font-family: 'Arial', sans-serif;
}
html {
-webkit-text-size-adjust: 100%; /* 2 */
}
:root {
font-family: Arial, sans-serif;
}
body {
min-height: 100vh;
margin: 0;
}
ul,ol {
margin-left: 1rem;
}
/* Quill css */
/*!
* * Quill Editor v1.3.6
* * https://quilljs.com/
* * Copyright (c) 2014, Jason Chen
* * Copyright (c) 2013, salesforce.com
* */
.ql-container {
box-sizing: border-box;
font-family: Helvetica, Arial, sans-serif;
font-size: 13px;
height: 100%;
margin: 0px;
position: relative;
}
.ql-container.ql-disabled .ql-tooltip {
visibility: hidden;
}
.ql-container.ql-disabled .ql-editor ul[data-checked] > li::before {
pointer-events: none;
}
.ql-clipboard {
left: -100000px;
height: 1px;
overflow-y: hidden;
position: absolute;
top: 50%;
}
.ql-clipboard p {
margin: 0;
padding: 0;
}
.ql-editor {
min-height: 8rem;
box-sizing: border-box;
line-height: 1.42;
height: 100%;
outline: none;
overflow-y: auto;
padding: 12px 15px;
tab-size: 4;
-moz-tab-size: 4;
text-align: left;
white-space: pre-wrap;
word-wrap: break-word;
}
.ql-editor > * {
cursor: text;
}
.ql-editor p,
.ql-editor ol,
.ql-editor ul,
.ql-editor pre,
.ql-editor blockquote,
.ql-editor h1,
.ql-editor h2,
.ql-editor h3,
.ql-editor h4,
.ql-editor h5,
.ql-editor h6 {
margin: 0;
padding: 0;
counter-reset: list-1 list-2 list-3 list-4 list-5 list-6 list-7 list-8 list-9;
}
.ql-editor ol,
.ql-editor ul {
padding-left: 1.5em;
}
.ql-editor ol > li,
.ql-editor ul > li {
list-style-type: none;
}
.ql-editor ul > li::before {
content: '\2022';
}
.ql-editor ul[data-checked=true],
.ql-editor ul[data-checked=false] {
pointer-events: none;
}
.ql-editor ul[data-checked=true] > li *,
.ql-editor ul[data-checked=false] > li * {
pointer-events: all;
}
.ql-editor ul[data-checked=true] > li::before,
.ql-editor ul[data-checked=false] > li::before {
color: #777;
cursor: pointer;
pointer-events: all;
}
.ql-editor ul[data-checked=true] > li::before {
content: '\2611';
}
.ql-editor ul[data-checked=false] > li::before {
content: '\2610';
}
.ql-editor li::before {
display: inline-block;
white-space: nowrap;
width: 1.2em;
}
.ql-editor li:not(.ql-direction-rtl)::before {
margin-left: -1.5em;
margin-right: 0.3em;
text-align: right;
}
.ql-editor li.ql-direction-rtl::before {
margin-left: 0.3em;
margin-right: -1.5em;
}
.ql-editor ol li:not(.ql-direction-rtl),
.ql-editor ul li:not(.ql-direction-rtl) {
padding-left: 1.5em;
}
.ql-editor ol li.ql-direction-rtl,
.ql-editor ul li.ql-direction-rtl {
padding-right: 1.5em;
}
.ql-editor ol li {
counter-reset: list-1 list-2 list-3 list-4 list-5 list-6 list-7 list-8 list-9;
counter-increment: list-0;
}
.ql-editor ol li:before {
content: counter(list-0, decimal) '. ';
}
.ql-editor ol li.ql-indent-1 {
counter-increment: list-1;
}
.ql-editor ol li.ql-indent-1:before {
content: counter(list-1, lower-alpha) '. ';
}
.ql-editor ol li.ql-indent-1 {
counter-reset: list-2 list-3 list-4 list-5 list-6 list-7 list-8 list-9;
}
.ql-editor ol li.ql-indent-2 {
counter-increment: list-2;
}
.ql-editor ol li.ql-indent-2:before {
content: counter(list-2, lower-roman) '. ';
}
.ql-editor ol li.ql-indent-2 {
counter-reset: list-3 list-4 list-5 list-6 list-7 list-8 list-9;
}
.ql-editor ol li.ql-indent-3 {
counter-increment: list-3;
}
.ql-editor ol li.ql-indent-3:before {
content: counter(list-3, decimal) '. ';
}
.ql-editor ol li.ql-indent-3 {
counter-reset: list-4 list-5 list-6 list-7 list-8 list-9;
}
.ql-editor ol li.ql-indent-4 {
counter-increment: list-4;
}
.ql-editor ol li.ql-indent-4:before {
content: counter(list-4, lower-alpha) '. ';
}
.ql-editor ol li.ql-indent-4 {
counter-reset: list-5 list-6 list-7 list-8 list-9;
}
.ql-editor ol li.ql-indent-5 {
counter-increment: list-5;
}
.ql-editor ol li.ql-indent-5:before {
content: counter(list-5, lower-roman) '. ';
}
.ql-editor ol li.ql-indent-5 {
counter-reset: list-6 list-7 list-8 list-9;
}
.ql-editor ol li.ql-indent-6 {
counter-increment: list-6;
}
.ql-editor ol li.ql-indent-6:before {
content: counter(list-6, decimal) '. ';
}
.ql-editor ol li.ql-indent-6 {
counter-reset: list-7 list-8 list-9;
}
.ql-editor ol li.ql-indent-7 {
counter-increment: list-7;
}
.ql-editor ol li.ql-indent-7:before {
content: counter(list-7, lower-alpha) '. ';
}
.ql-editor ol li.ql-indent-7 {
counter-reset: list-8 list-9;
}
.ql-editor ol li.ql-indent-8 {
counter-increment: list-8;
}
.ql-editor ol li.ql-indent-8:before {
content: counter(list-8, lower-roman) '. ';
}
.ql-editor ol li.ql-indent-8 {
counter-reset: list-9;
}
.ql-editor ol li.ql-indent-9 {
counter-increment: list-9;
}
.ql-editor ol li.ql-indent-9:before {
content: counter(list-9, decimal) '. ';
}
.ql-editor .ql-indent-1:not(.ql-direction-rtl) {
padding-left: 3em;
}
.ql-editor li.ql-indent-1:not(.ql-direction-rtl) {
padding-left: 4.5em;
}
.ql-editor .ql-indent-1.ql-direction-rtl.ql-align-right {
padding-right: 3em;
}
.ql-editor li.ql-indent-1.ql-direction-rtl.ql-align-right {
padding-right: 4.5em;
}
.ql-editor .ql-indent-2:not(.ql-direction-rtl) {
padding-left: 6em;
}
.ql-editor li.ql-indent-2:not(.ql-direction-rtl) {
padding-left: 7.5em;
}
.ql-editor .ql-indent-2.ql-direction-rtl.ql-align-right {
padding-right: 6em;
}
.ql-editor li.ql-indent-2.ql-direction-rtl.ql-align-right {
padding-right: 7.5em;
}
.ql-editor .ql-indent-3:not(.ql-direction-rtl) {
padding-left: 9em;
}
.ql-editor li.ql-indent-3:not(.ql-direction-rtl) {
padding-left: 10.5em;
}
.ql-editor .ql-indent-3.ql-direction-rtl.ql-align-right {
padding-right: 9em;
}
.ql-editor li.ql-indent-3.ql-direction-rtl.ql-align-right {
padding-right: 10.5em;
}
.ql-editor .ql-indent-4:not(.ql-direction-rtl) {
padding-left: 12em;
}
.ql-editor li.ql-indent-4:not(.ql-direction-rtl) {
padding-left: 13.5em;
}
.ql-editor .ql-indent-4.ql-direction-rtl.ql-align-right {
padding-right: 12em;
}
.ql-editor li.ql-indent-4.ql-direction-rtl.ql-align-right {
padding-right: 13.5em;
}
.ql-editor .ql-indent-5:not(.ql-direction-rtl) {
padding-left: 15em;
}
.ql-editor li.ql-indent-5:not(.ql-direction-rtl) {
padding-left: 16.5em;
}
.ql-editor .ql-indent-5.ql-direction-rtl.ql-align-right {
padding-right: 15em;
}
.ql-editor li.ql-indent-5.ql-direction-rtl.ql-align-right {
padding-right: 16.5em;
}
.ql-editor .ql-indent-6:not(.ql-direction-rtl) {
padding-left: 18em;
}
.ql-editor li.ql-indent-6:not(.ql-direction-rtl) {
padding-left: 19.5em;
}
.ql-editor .ql-indent-6.ql-direction-rtl.ql-align-right {
padding-right: 18em;
}
.ql-editor li.ql-indent-6.ql-direction-rtl.ql-align-right {
padding-right: 19.5em;
}
.ql-editor .ql-indent-7:not(.ql-direction-rtl) {
padding-left: 21em;
}
.ql-editor li.ql-indent-7:not(.ql-direction-rtl) {
padding-left: 22.5em;
}
.ql-editor .ql-indent-7.ql-direction-rtl.ql-align-right {
padding-right: 21em;
}
.ql-editor li.ql-indent-7.ql-direction-rtl.ql-align-right {
padding-right: 22.5em;
}
.ql-editor .ql-indent-8:not(.ql-direction-rtl) {
padding-left: 24em;
}
.ql-editor li.ql-indent-8:not(.ql-direction-rtl) {
padding-left: 25.5em;
}
.ql-editor .ql-indent-8.ql-direction-rtl.ql-align-right {
padding-right: 24em;
}
.ql-editor li.ql-indent-8.ql-direction-rtl.ql-align-right {
padding-right: 25.5em;
}
.ql-editor .ql-indent-9:not(.ql-direction-rtl) {
padding-left: 27em;
}
.ql-editor li.ql-indent-9:not(.ql-direction-rtl) {
padding-left: 28.5em;
}
.ql-editor .ql-indent-9.ql-direction-rtl.ql-align-right {
padding-right: 27em;
}
.ql-editor li.ql-indent-9.ql-direction-rtl.ql-align-right {
padding-right: 28.5em;
}
.ql-editor .ql-video {
display: block;
max-width: 100%;
}
.ql-editor .ql-video.ql-align-center {
margin: 0 auto;
}
.ql-editor .ql-video.ql-align-right {
margin: 0 0 0 auto;
}
.ql-editor .ql-bg-black {
background-color: #000;
}
.ql-editor .ql-bg-red {
background-color: #e60000;
}
.ql-editor .ql-bg-orange {
background-color: #f90;
}
.ql-editor .ql-bg-yellow {
background-color: #ff0;
}
.ql-editor .ql-bg-green {
background-color: #008a00;
}
.ql-editor .ql-bg-blue {
background-color: #06c;
}
.ql-editor .ql-bg-purple {
background-color: #93f;
}
.ql-editor .ql-color-white {
color: #fff;
}
.ql-editor .ql-color-red {
color: #e60000;
}
.ql-editor .ql-color-orange {
color: #f90;
}
.ql-editor .ql-color-yellow {
color: #ff0;
}
.ql-editor .ql-color-green {
color: #008a00;
}
.ql-editor .ql-color-blue {
color: #06c;
}
.ql-editor .ql-color-purple {
color: #93f;
}
.ql-editor .ql-font-serif {
font-family: Georgia, Times New Roman, serif;
}
.ql-editor .ql-font-monospace {
font-family: Monaco, Courier New, monospace;
}
.ql-editor .ql-size-small {
font-size: 0.75em;
}
.ql-editor .ql-size-large {
font-size: 1.5em;
}
.ql-editor .ql-size-huge {
font-size: 2.5em;
}
.ql-editor .ql-direction-rtl {
direction: rtl;
text-align: inherit;
}
.ql-editor .ql-align-center {
text-align: center;
}
.ql-editor .ql-align-justify {
text-align: justify;
}
.ql-editor .ql-align-right {
text-align: right;
}
.ql-editor.ql-blank::before {
color: rgba(0,0,0,0.6);
content: attr(data-placeholder);
font-style: italic;
left: 15px;
pointer-events: none;
position: absolute;
right: 15px;
}
.ql-snow.ql-toolbar:after,
.ql-snow .ql-toolbar:after {
clear: both;
content: '';
display: table;
}
.ql-snow.ql-toolbar button,
.ql-snow .ql-toolbar button {
background: none;
border: none;
cursor: pointer;
display: block;
float: left;
height: 24px;
padding: 3px 5px;
width: 28px;
}
.ql-snow.ql-toolbar button svg,
.ql-snow .ql-toolbar button svg {
float: left;
height: 100%;
}
.ql-snow.ql-toolbar button:active:hover,
.ql-snow .ql-toolbar button:active:hover {
outline: none;
}
.ql-snow.ql-toolbar input.ql-image[type=file],
.ql-snow .ql-toolbar input.ql-image[type=file] {
display: none;
}
.ql-snow.ql-toolbar button:hover,
.ql-snow .ql-toolbar button:hover,
.ql-snow.ql-toolbar button:focus,
.ql-snow .ql-toolbar button:focus,
.ql-snow.ql-toolbar button.ql-active,
.ql-snow .ql-toolbar button.ql-active,
.ql-snow.ql-toolbar .ql-picker-label:hover,
.ql-snow .ql-toolbar .ql-picker-label:hover,
.ql-snow.ql-toolbar .ql-picker-label.ql-active,
.ql-snow .ql-toolbar .ql-picker-label.ql-active,
.ql-snow.ql-toolbar .ql-picker-item:hover,
.ql-snow .ql-toolbar .ql-picker-item:hover,
.ql-snow.ql-toolbar .ql-picker-item.ql-selected,
.ql-snow .ql-toolbar .ql-picker-item.ql-selected {
color: #06c;
}
.ql-snow.ql-toolbar button:hover .ql-fill,
.ql-snow .ql-toolbar button:hover .ql-fill,
.ql-snow.ql-toolbar button:focus .ql-fill,
.ql-snow .ql-toolbar button:focus .ql-fill,
.ql-snow.ql-toolbar button.ql-active .ql-fill,
.ql-snow .ql-toolbar button.ql-active .ql-fill,
.ql-snow.ql-toolbar .ql-picker-label:hover .ql-fill,
.ql-snow .ql-toolbar .ql-picker-label:hover .ql-fill,
.ql-snow.ql-toolbar .ql-picker-label.ql-active .ql-fill,
.ql-snow .ql-toolbar .ql-picker-label.ql-active .ql-fill,
.ql-snow.ql-toolbar .ql-picker-item:hover .ql-fill,
.ql-snow .ql-toolbar .ql-picker-item:hover .ql-fill,
.ql-snow.ql-toolbar .ql-picker-item.ql-selected .ql-fill,
.ql-snow .ql-toolbar .ql-picker-item.ql-selected .ql-fill,
.ql-snow.ql-toolbar button:hover .ql-stroke.ql-fill,
.ql-snow .ql-toolbar button:hover .ql-stroke.ql-fill,
.ql-snow.ql-toolbar button:focus .ql-stroke.ql-fill,
.ql-snow .ql-toolbar button:focus .ql-stroke.ql-fill,
.ql-snow.ql-toolbar button.ql-active .ql-stroke.ql-fill,
.ql-snow .ql-toolbar button.ql-active .ql-stroke.ql-fill,
.ql-snow.ql-toolbar .ql-picker-label:hover .ql-stroke.ql-fill,
.ql-snow .ql-toolbar .ql-picker-label:hover .ql-stroke.ql-fill,
.ql-snow.ql-toolbar .ql-picker-label.ql-active .ql-stroke.ql-fill,
.ql-snow .ql-toolbar .ql-picker-label.ql-active .ql-stroke.ql-fill,
.ql-snow.ql-toolbar .ql-picker-item:hover .ql-stroke.ql-fill,
.ql-snow .ql-toolbar .ql-picker-item:hover .ql-stroke.ql-fill,
.ql-snow.ql-toolbar .ql-picker-item.ql-selected .ql-stroke.ql-fill,
.ql-snow .ql-toolbar .ql-picker-item.ql-selected .ql-stroke.ql-fill {
fill: #06c;
}
.ql-snow.ql-toolbar button:hover .ql-stroke,
.ql-snow .ql-toolbar button:hover .ql-stroke,
.ql-snow.ql-toolbar button:focus .ql-stroke,
.ql-snow .ql-toolbar button:focus .ql-stroke,
.ql-snow.ql-toolbar button.ql-active .ql-stroke,
.ql-snow .ql-toolbar button.ql-active .ql-stroke,
.ql-snow.ql-toolbar .ql-picker-label:hover .ql-stroke,
.ql-snow .ql-toolbar .ql-picker-label:hover .ql-stroke,
.ql-snow.ql-toolbar .ql-picker-label.ql-active .ql-stroke,
.ql-snow .ql-toolbar .ql-picker-label.ql-active .ql-stroke,
.ql-snow.ql-toolbar .ql-picker-item:hover .ql-stroke,
.ql-snow .ql-toolbar .ql-picker-item:hover .ql-stroke,
.ql-snow.ql-toolbar .ql-picker-item.ql-selected .ql-stroke,
.ql-snow .ql-toolbar .ql-picker-item.ql-selected .ql-stroke,
.ql-snow.ql-toolbar button:hover .ql-stroke-miter,
.ql-snow .ql-toolbar button:hover .ql-stroke-miter,
.ql-snow.ql-toolbar button:focus .ql-stroke-miter,
.ql-snow .ql-toolbar button:focus .ql-stroke-miter,
.ql-snow.ql-toolbar button.ql-active .ql-stroke-miter,
.ql-snow .ql-toolbar button.ql-active .ql-stroke-miter,
.ql-snow.ql-toolbar .ql-picker-label:hover .ql-stroke-miter,
.ql-snow .ql-toolbar .ql-picker-label:hover .ql-stroke-miter,
.ql-snow.ql-toolbar .ql-picker-label.ql-active .ql-stroke-miter,
.ql-snow .ql-toolbar .ql-picker-label.ql-active .ql-stroke-miter,
.ql-snow.ql-toolbar .ql-picker-item:hover .ql-stroke-miter,
.ql-snow .ql-toolbar .ql-picker-item:hover .ql-stroke-miter,
.ql-snow.ql-toolbar .ql-picker-item.ql-selected .ql-stroke-miter,
.ql-snow .ql-toolbar .ql-picker-item.ql-selected .ql-stroke-miter {
stroke: #06c;
}
@media (pointer: coarse) {
.ql-snow.ql-toolbar button:hover:not(.ql-active),
.ql-snow .ql-toolbar button:hover:not(.ql-active) {
color: #444;
}
.ql-snow.ql-toolbar button:hover:not(.ql-active) .ql-fill,
.ql-snow .ql-toolbar button:hover:not(.ql-active) .ql-fill,
.ql-snow.ql-toolbar button:hover:not(.ql-active) .ql-stroke.ql-fill,
.ql-snow .ql-toolbar button:hover:not(.ql-active) .ql-stroke.ql-fill {
fill: #444;
}
.ql-snow.ql-toolbar button:hover:not(.ql-active) .ql-stroke,
.ql-snow .ql-toolbar button:hover:not(.ql-active) .ql-stroke,
.ql-snow.ql-toolbar button:hover:not(.ql-active) .ql-stroke-miter,
.ql-snow .ql-toolbar button:hover:not(.ql-active) .ql-stroke-miter {
stroke: #444;
}
}
.ql-snow {
box-sizing: border-box;
}
.ql-snow * {
box-sizing: border-box;
}
.ql-snow .ql-hidden {
display: none;
}
.ql-snow .ql-out-bottom,
.ql-snow .ql-out-top {
visibility: hidden;
}
.ql-snow .ql-tooltip {
position: absolute;
transform: translateY(10px);
}
.ql-snow .ql-tooltip a {
cursor: pointer;
text-decoration: none;
}
.ql-snow .ql-tooltip.ql-flip {
transform: translateY(-10px);
}
.ql-snow .ql-formats {
display: inline-block;
vertical-align: middle;
}
.ql-snow .ql-formats:after {
clear: both;
content: '';
display: table;
}
.ql-snow .ql-stroke {
fill: none;
stroke: #444;
stroke-linecap: round;
stroke-linejoin: round;
stroke-width: 2;
}
.ql-snow .ql-stroke-miter {
fill: none;
stroke: #444;
stroke-miterlimit: 10;
stroke-width: 2;
}
.ql-snow .ql-fill,
.ql-snow .ql-stroke.ql-fill {
fill: #444;
}
.ql-snow .ql-empty {
fill: none;
}
.ql-snow .ql-even {
fill-rule: evenodd;
}
.ql-snow .ql-thin,
.ql-snow .ql-stroke.ql-thin {
stroke-width: 1;
}
.ql-snow .ql-transparent {
opacity: 0.4;
}
.ql-snow .ql-direction svg:last-child {
display: none;
}
.ql-snow .ql-direction.ql-active svg:last-child {
display: inline;
}
.ql-snow .ql-direction.ql-active svg:first-child {
display: none;
}
.ql-snow .ql-editor h1 {
font-size: 2em;
}
.ql-snow .ql-editor h2 {
font-size: 1.5em;
}
.ql-snow .ql-editor h3 {
font-size: 1.17em;
}
.ql-snow .ql-editor h4 {
font-size: 1em;
}
.ql-snow .ql-editor h5 {
font-size: 0.83em;
}
.ql-snow .ql-editor h6 {
font-size: 0.67em;
}
.ql-snow .ql-editor a {
text-decoration: underline;
}
.ql-snow .ql-editor blockquote {
border-left: 4px solid #ccc;
margin-bottom: 5px;
margin-top: 5px;
padding-left: 16px;
}
.ql-snow .ql-editor code,
.ql-snow .ql-editor pre {
background-color: #f0f0f0;
border-radius: 3px;
}
.ql-snow .ql-editor pre {
white-space: pre-wrap;
margin-bottom: 5px;
margin-top: 5px;
padding: 5px 10px;
}
.ql-snow .ql-editor code {
font-size: 85%;
padding: 2px 4px;
}
.ql-snow .ql-editor pre.ql-syntax {
background-color: #23241f;
color: #f8f8f2;
overflow: visible;
}
.ql-snow .ql-editor img {
max-width: 100%;
}
.ql-snow .ql-picker {
color: #444;
display: block;
float: left;
font-size: 14px;
font-weight: 500;
height: 24px;
position: relative;
}
.ql-snow .ql-picker-label {
cursor: pointer;
display: inline-block;
height: 100%;
padding-left: 8px;
padding-right: 2px;
position: relative;
width: 100%;
}
.ql-snow .ql-picker-label::before {
display: inline-block;
line-height: 22px;
}
.ql-snow .ql-picker-options {
background-color: #fff;
display: none;
min-width: 100%;
padding: 4px 8px;
position: absolute;
white-space: nowrap;
}
.ql-snow .ql-picker-options .ql-picker-item {
cursor: pointer;
display: block;
padding-bottom: 5px;
padding-top: 5px;
}
.ql-snow .ql-picker.ql-expanded .ql-picker-label {
color: #ccc;
z-index: 2;
}
.ql-snow .ql-picker.ql-expanded .ql-picker-label .ql-fill {
fill: #ccc;
}
.ql-snow .ql-picker.ql-expanded .ql-picker-label .ql-stroke {
stroke: #ccc;
}
.ql-snow .ql-picker.ql-expanded .ql-picker-options {
display: block;
margin-top: -1px;
top: 100%;
z-index: 1;
}
.ql-snow .ql-color-picker,
.ql-snow .ql-icon-picker {
width: 28px;
}
.ql-snow .ql-color-picker .ql-picker-label,
.ql-snow .ql-icon-picker .ql-picker-label {
padding: 2px 4px;
}
.ql-snow .ql-color-picker .ql-picker-label svg,
.ql-snow .ql-icon-picker .ql-picker-label svg {
right: 4px;
}
.ql-snow .ql-icon-picker .ql-picker-options {
padding: 4px 0px;
}
.ql-snow .ql-icon-picker .ql-picker-item {
height: 24px;
width: 24px;
padding: 2px 4px;
}
.ql-snow .ql-color-picker .ql-picker-options {
padding: 3px 5px;
width: 152px;
}
.ql-snow .ql-color-picker .ql-picker-item {
border: 1px solid transparent;
float: left;
height: 16px;
margin: 2px;
padding: 0px;
width: 16px;
}
.ql-snow .ql-picker:not(.ql-color-picker):not(.ql-icon-picker) svg {
position: absolute;
margin-top: -9px;
right: 0;
top: 50%;
width: 18px;
}
.ql-snow .ql-picker.ql-header .ql-picker-label[data-label]:not([data-label=''])::before,
.ql-snow .ql-picker.ql-font .ql-picker-label[data-label]:not([data-label=''])::before,
.ql-snow .ql-picker.ql-size .ql-picker-label[data-label]:not([data-label=''])::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-label]:not([data-label=''])::before,
.ql-snow .ql-picker.ql-font .ql-picker-item[data-label]:not([data-label=''])::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-label]:not([data-label=''])::before {
content: attr(data-label);
}
.ql-snow .ql-picker.ql-header {
width: 98px;
}
.ql-snow .ql-picker.ql-header .ql-picker-label::before,
.ql-snow .ql-picker.ql-header .ql-picker-item::before {
content: 'Normal';
}
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="1"]::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="1"]::before {
content: 'Heading 1';
}
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="2"]::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="2"]::before {
content: 'Heading 2';
}
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="3"]::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="3"]::before {
content: 'Heading 3';
}
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="4"]::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="4"]::before {
content: 'Heading 4';
}
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="5"]::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="5"]::before {
content: 'Heading 5';
}
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="6"]::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="6"]::before {
content: 'Heading 6';
}
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="1"]::before {
font-size: 2em;
}
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="2"]::before {
font-size: 1.5em;
}
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="3"]::before {
font-size: 1.17em;
}
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="4"]::before {
font-size: 1em;
}
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="5"]::before {
font-size: 0.83em;
}
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="6"]::before {
font-size: 0.67em;
}
.ql-snow .ql-picker.ql-font {
width: 108px;
}
.ql-snow .ql-picker.ql-font .ql-picker-label::before,
.ql-snow .ql-picker.ql-font .ql-picker-item::before {
content: 'Sans Serif';
}
.ql-snow .ql-picker.ql-font .ql-picker-label[data-value=serif]::before,
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value=serif]::before {
content: 'Serif';
}
.ql-snow .ql-picker.ql-font .ql-picker-label[data-value=monospace]::before,
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value=monospace]::before {
content: 'Monospace';
}
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value=serif]::before {
font-family: Georgia, Times New Roman, serif;
}
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value=monospace]::before {
font-family: Monaco, Courier New, monospace;
}
.ql-snow .ql-picker.ql-size {
width: 98px;
}
.ql-snow .ql-picker.ql-size .ql-picker-label::before,
.ql-snow .ql-picker.ql-size .ql-picker-item::before {
content: 'Normal';
}
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value=small]::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value=small]::before {
content: 'Small';
}
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value=large]::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value=large]::before {
content: 'Large';
}
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value=huge]::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value=huge]::before {
content: 'Huge';
}
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value=small]::before {
font-size: 10px;
}
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value=large]::before {
font-size: 18px;
}
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value=huge]::before {
font-size: 32px;
}
.ql-snow .ql-color-picker.ql-background .ql-picker-item {
background-color: #fff;
}
.ql-snow .ql-color-picker.ql-color .ql-picker-item {
background-color: #000;
}
.ql-toolbar.ql-snow {
border: 1px solid #ccc;
box-sizing: border-box;
font-family: 'Helvetica Neue', 'Helvetica', 'Arial', sans-serif;
padding: 8px;
}
.ql-toolbar.ql-snow .ql-formats {
margin-right: 15px;
}
.ql-toolbar.ql-snow .ql-picker-label {
border: 1px solid transparent;
}
.ql-toolbar.ql-snow .ql-picker-options {
border: 1px solid transparent;
box-shadow: rgba(0,0,0,0.2) 0 2px 8px;
}
.ql-toolbar.ql-snow .ql-picker.ql-expanded .ql-picker-label {
border-color: #ccc;
}
.ql-toolbar.ql-snow .ql-picker.ql-expanded .ql-picker-options {
border-color: #ccc;
}
.ql-toolbar.ql-snow .ql-color-picker .ql-picker-item.ql-selected,
.ql-toolbar.ql-snow .ql-color-picker .ql-picker-item:hover {
border-color: #000;
}
.ql-toolbar.ql-snow + .ql-container.ql-snow {
border-top: 0px;
}
.ql-snow .ql-tooltip {
background-color: #fff;
border: 1px solid #ccc;
box-shadow: 0px 0px 5px #ddd;
color: #444;
padding: 5px 12px;
white-space: nowrap;
}
.ql-snow .ql-tooltip::before {
content: "Visit URL:";
line-height: 26px;
margin-right: 8px;
}
.ql-snow .ql-tooltip input[type=text] {
display: none;
border: 1px solid #ccc;
font-size: 13px;
height: 26px;
margin: 0px;
padding: 3px 5px;
width: 170px;
}
.ql-snow .ql-tooltip a.ql-preview {
display: inline-block;
max-width: 200px;
overflow-x: hidden;
text-overflow: ellipsis;
vertical-align: top;
}
.ql-snow .ql-tooltip a.ql-action::after {
border-right: 1px solid #ccc;
content: 'Edit';
margin-left: 16px;
padding-right: 8px;
}
.ql-snow .ql-tooltip a.ql-remove::before {
content: 'Remove';
margin-left: 8px;
}
.ql-snow .ql-tooltip a {
line-height: 26px;
}
.ql-snow .ql-tooltip.ql-editing a.ql-preview,
.ql-snow .ql-tooltip.ql-editing a.ql-remove {
display: none;
}
.ql-snow .ql-tooltip.ql-editing input[type=text] {
display: inline-block;
}
.ql-snow .ql-tooltip.ql-editing a.ql-action::after {
border-right: 0px;
content: 'Save';
padding-right: 0px;
}
.ql-snow .ql-tooltip[data-mode=link]::before {
content: "Enter link:";
}
.ql-snow .ql-tooltip[data-mode=formula]::before {
content: "Enter formula:";
}
.ql-snow .ql-tooltip[data-mode=video]::before {
content: "Enter video:";
}
.ql-snow a {
color: #06c;
}
.ql-container.ql-snow {
border: 1px solid #ccc;
}

9
src/app.d.ts vendored

@ -0,0 +1,9 @@
// See https://kit.svelte.dev/docs/types#app
// for information about these interfaces
// and what to do when importing types
declare namespace App {
// interface Error {}
// interface Locals {}
// interface PageData {}
// interface Platform {}
}

@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
<meta name="viewport" content="width=device-width" />
%sveltekit.head%
</head>
<body data-sveltekit-preload-data="hover">
<div style="display: contents">%sveltekit.body%</div>
</body>
</html>

@ -0,0 +1,14 @@
/**
* @async
* @function send
* @param {HTMLFormElement} form
* @returns {Promise<JSON>}
*/
export const send = async (form) => {
const response = await fetch(form.action, {
method: form.method,
body: new FormData(form),
headers: { accept: "application/json" },
});
return await response.json();
};

@ -0,0 +1,38 @@
<script>
import { templates } from '$lib/templates/templates';
/** @type number */ export let templateIndex;
import { address, multiplier } from '$lib/stores/store';
</script>
{#if templates[templateIndex] && $address}
<div
class="address"
style="
top: {templates[templateIndex].address.top}px;
height: {templates[templateIndex].address.height}px;
left: {templates[templateIndex].address.left}px;
right: {templates[templateIndex].address.right}px;
color: {templates[templateIndex].address.color};
font-size: {templates[templateIndex].address.fontSize * $multiplier}px;
font-family: {templates[templateIndex].address.fontFamily};
line-height: {templates[templateIndex].address.lineHeight};
text-align: {templates[templateIndex].address.textAlign};
font-weight: {templates[templateIndex].address.fontWeight};
line-height: {templates[templateIndex].address.lineHeight}px;
"
>
{$address}
</div>
{/if}
<style>
.address {
position: absolute;
display: flex;
flex-direction: column;
justify-content: center;
overflow: hidden;
white-space: pre-wrap;
}
</style>

@ -0,0 +1,35 @@
<script>
import { templates } from '$lib/templates/templates';
/** @type number */ export let templateIndex;
import { city, multiplier } from '$lib/stores/store';
</script>
{#if templates[templateIndex] && $city}
<div
class="city"
style="
top: {templates[templateIndex].city.top}px;
height: {templates[templateIndex].city.height}px;
left: {templates[templateIndex].city.left}px;
right: {templates[templateIndex].city.right}px;
color: {templates[templateIndex].city.color};
font-size: {templates[templateIndex].city.fontSize * $multiplier}px;
font-family: {templates[templateIndex].city.fontFamily};
line-height: {templates[templateIndex].city.lineHeight};
text-align: {templates[templateIndex].city.textAlign};
font-weight: {templates[templateIndex].city.fontWeight};
line-height: {templates[templateIndex].city.lineHeight}px;
"
>
{$city}
</div>
{/if}
<style>
.city {
position: absolute;
overflow: hidden;
text-transform: uppercase;
}
</style>

@ -0,0 +1,36 @@
<script>
import { templates } from '$lib/templates/templates';
/** @type number */ export let templateIndex;
import { content, multiplier } from '$lib/stores/store';
</script>
{#if templates[templateIndex] && $content}
<div
class="content"
style="
top: {templates[templateIndex].content.top}px;
height: {templates[templateIndex].content.height}px;
left: {templates[templateIndex].content.left}px;
right: {templates[templateIndex].content.right}px;
color: {templates[templateIndex].content.color};
font-size: {templates[templateIndex].content.fontSize * $multiplier}px;
font-family: {templates[templateIndex].content.fontFamily};
text-align: {templates[templateIndex].content.textAlign};
font-weight: {templates[templateIndex].content.fontWeight};
line-height: {templates[templateIndex].content.lineHeight}px;
"
>
{@html $content}
</div>
{/if}
<style>
.content {
position: absolute;
display: flex;
flex-direction: column;
overflow: hidden;
justify-content: center;
}
</style>

@ -0,0 +1,41 @@
<script>
import { templates } from '$lib/templates/templates';
import { date, newdate, multiplier } from '$lib/stores/store';
import { convertDate } from '$lib/convertDate';
/** @type number */ export let templateIndex;
// /** @type {string} */ let newdate;
$: if ($date !== '') {
$newdate = convertDate($date);
}
</script>
{#if templates[templateIndex] && newdate}
<div
class="date"
style="
top: {templates[templateIndex].date.top}px;
height: {templates[templateIndex].date.height}px;
left: {templates[templateIndex].date.left}px;
right: {templates[templateIndex].date.right}px;
color: {templates[templateIndex].date.color};
font-size: {templates[templateIndex].date.fontSize * $multiplier}px;
font-family: {templates[templateIndex].date.fontFamily};
text-align: {templates[templateIndex].date.textAlign};
font-weight: {templates[templateIndex].date.fontWeight};
line-height: {templates[templateIndex].date.lineHeight}px;
"
>
{$newdate}
</div>
{/if}
<style>
.date {
position: absolute;
text-transform: uppercase;
overflow: hidden;
display: block;
}
</style>

@ -0,0 +1,34 @@
<script>
import { templates } from '$lib/templates/templates';
/** @type number */ export let templateIndex;
import { heading, multiplier } from '$lib/stores/store';
</script>
{#if $heading && templates[templateIndex].heading}
<div
class="heading"
style="
top: {templates[templateIndex].heading.top}px;
height: {templates[templateIndex].heading.height}px;
left: {templates[templateIndex].heading.left}px;
right: {templates[templateIndex].heading.right}px;
color: {templates[templateIndex].heading.color};
font-size: {templates[templateIndex].heading.fontSize * $multiplier}px;
font-family: {templates[templateIndex].heading.fontFamily};
text-align: {templates[templateIndex].heading.textAlign};
font-weight: {templates[templateIndex].heading.fontWeight};
line-height: {templates[templateIndex].heading.lineHeight}px;
"
>
{$heading}
</div>
{/if}
<style>
.heading {
position: absolute;
overflow: hidden;
text-transform: uppercase;
}
</style>

@ -0,0 +1,39 @@
<script>
import { templates } from '$lib/templates/templates';
/** @type number */ export let templateIndex;
import { subtitle, multiplier } from '$lib/stores/store';
console.log($multiplier);
</script>
{#if templates[templateIndex] && $subtitle}
<div
class="subtitle"
style="
top: {templates[templateIndex].subtitle.top}px;
height: {templates[templateIndex].subtitle.height}px;
left: {templates[templateIndex].subtitle.left}px;
right: {templates[templateIndex].subtitle.right}px;
color: {templates[templateIndex].subtitle.color};
font-size: {templates[templateIndex].subtitle.fontSize * $multiplier}px;
font-family: {templates[templateIndex].subtitle.fontFamily};
text-align: {templates[templateIndex].subtitle.textAlign};
font-weight: {templates[templateIndex].subtitle.fontWeight};
line-height: {templates[templateIndex].subtitle.lineHeight}px;
"
>
{$subtitle}
</div>
{/if}
<style>
.subtitle {
position: absolute;
overflow: hidden;
display: flex;
/* flex-direction: column; */
justify-content: center;
align-items: center;
text-transform: uppercase;
}
</style>

@ -0,0 +1,70 @@
<script>
import {
heading,
title,
subtitle,
content,
date,
newdate,
time,
weekday,
address,
city
} from '$lib/stores/store';
import { convertDate } from '$lib/convertDate';
/** @type {string} */ let textDate = '';
/** @type {boolean} */ let checked = false;
$: if ($date !== '') {
textDate = convertDate($date);
}
</script>
<div class="text-container">
<input type="checkbox" bind:checked />
<label for="checked">Mostrar texto</label>
{#if checked}
<div class="text" contenteditable="true">
<p>Estimada/o amiga/o:</p>
<p>
El {$weekday.toLowerCase()}, {textDate}, celebramos el acto <strong>'{$title}'</strong> en {$city}.
</p>
<p>Como siempre, la entrada es gratuita hasta completar aforo.</p>
<ul>
<li>
<p style="white-space: pre-wrap;">
<strong>Lugar</strong>:<br />{$address}<br /><em>{$city}</em>
</p>
</li>
<li>
<p><strong>Fecha</strong>:<br />{$newdate}<br />{$time}</p>
</li>
</ul>
<p>{@html $content}</p>
</div>
{/if}
</div>
<style>
.text-container {
margin: 1rem auto;
}
.text {
margin: 1rem auto;
padding: 1rem;
border: 1px solid lightgrey;
background-color: #fff;
}
p {
margin: 1rem 0;
}
label {
color: #333;
}
li {
margin: 1rem 0;
}
</style>

@ -0,0 +1,35 @@
<script>
import { templates } from '$lib/templates/templates';
/** @type number */ export let templateIndex;
import { time, multiplier } from '$lib/stores/store';
</script>
{#if templates[templateIndex] && $time !== ''}
<div
class="time"
style="
top: {templates[templateIndex].time.top}px;
height: {templates[templateIndex].time.height}px;
left: {templates[templateIndex].time.left}px;
right: {templates[templateIndex].time.right}px;
color: {templates[templateIndex].time.color};
font-size: {templates[templateIndex].time.fontSize * $multiplier}px;
font-family: {templates[templateIndex].time.fontFamily};
text-align: {templates[templateIndex].time.textAlign};
font-weight: {templates[templateIndex].time.fontWeight};
line-height: {templates[templateIndex].time.lineHeight}px;
"
>
{$time}
</div>
{/if}
<style>
.time {
position: absolute;
text-transform: uppercase;
overflow: hidden;
display: block;
}
</style>

@ -0,0 +1,38 @@
<script>
import { templates } from '$lib/templates/templates';
/** @type number */ export let templateIndex;
import { title, multiplier } from '$lib/stores/store';
</script>
{#if templates[templateIndex] && $title}
<div
class="title"
style="
top: {templates[templateIndex].title.top}px;
height: {templates[templateIndex].title.height}px;
left: {templates[templateIndex].title.left}px;
right: {templates[templateIndex].title.right}px;
color: {templates[templateIndex].title.color};
font-size: {templates[templateIndex].title.fontSize * $multiplier}px;
font-family: {templates[templateIndex].title.fontFamily};
text-align: {templates[templateIndex].title.textAlign};
font-weight: {templates[templateIndex].title.fontWeight};
line-height: {templates[templateIndex].title.lineHeight}px;
"
>
{$title}
</div>
{/if}
<style>
.title {
position: absolute;
overflow: hidden;
display: flex;
justify-content: center;
align-items: center;
text-transform: uppercase;
}
</style>

@ -0,0 +1,34 @@
<script>
import { templates } from '$lib/templates/templates';
/** @type number */ export let templateIndex;
import { weekday, multiplier } from '$lib/stores/store';
</script>
{#if templates[templateIndex] && $weekday}
<div
class="weekday"
style="
top: {templates[templateIndex].weekday.top}px;
height: {templates[templateIndex].weekday.height}px;
left: {templates[templateIndex].weekday.left}px;
right: {templates[templateIndex].weekday.right}px;
color: {templates[templateIndex].weekday.color};
font-size: {templates[templateIndex].weekday.fontSize * $multiplier}px;
font-family: {templates[templateIndex].weekday.fontFamily};
text-align: {templates[templateIndex].weekday.textAlign};
font-weight: {templates[templateIndex].weekday.fontWeight};
line-height: {templates[templateIndex].weekday.lineHeight}px;
"
>
{$weekday}
</div>
{/if}
<style>
.weekday {
position: absolute;
text-transform: uppercase;
overflow: hidden;
}
</style>

@ -0,0 +1,23 @@
import { getMonthName } from '$lib/monthName';
/**
* @param {string} date - Fecha del evento
* @returns {string} - Fecha en dia (número) mes (letras)
*/
export const convertDate = (date) => {
if (date == undefined) {
return '';
}
/** @type {string} */ const monthNumber = date.split('-')[1];
/** @type {string} */ let day = date.split('-')[2].split('T')[0];
/** @type {number} */ const dayNumber = Number(day);
/** @type {string} */ const month = getMonthName(monthNumber);
/** Removes 0 on single digit days */
if (dayNumber < 10) {
day = day.charAt(1);
}
/** @type {string} */ const res = `${day} de ${month}`;
return res;
};

Binary file not shown.

@ -0,0 +1,38 @@
/** @typedef {typeof import('better-sqlite3')} better-sqlite3 */
import Database from 'better-sqlite3';
export const db = new Database('./src/lib/db/carteles_dev.sqlite', { verbose: console.log });
db.pragma("journal_mode = WAL");
db.pragma("synchronous = normal");
db.pragma("temp_store = memory");
const createUserTable = db.prepare(`CREATE TABLE IF NOT EXISTS Users (
email TEXT PRIMARY KEY UNIQUE NOT NULL,
password TEXT,
isAdmin INTEGER
);`);
const createPostersTable = db.prepare(`CREATE TABLE IF NOT EXISTS Posters (
id INTEGER PRIMARY KEY UNIQUE NOT NULL,
createdAt TEXT NOT NULL,
image BLOB NOT NULL,
content TEXT
);`);
createUserTable.run();
createPostersTable.run();
/**
* @typedef {Object} User
* @property {string} [email]
* @property {string} [password]
* @property {boolean} [isAdmin]
*/
/**
* @typedef {Object} Poster
* @property {number} [id]
* @property {string} [createdAt] Date
* @property {Blob} [image]
* @property {string} [content] Stringified contents
*/

@ -0,0 +1,95 @@
import { db } from '$lib/db/db.js';
/**
* Return of functions that change change DB, either 'success' or 'failure' with explanation
* @typedef {Object} ChangeDBResult
* @property {string} [error]
* @property {string} [success]
* @property {string} [id]
*/
/**
* @function addUserToDB Adds user to the db
* @param {string} email
* @param {string} passwordHash
* @param {boolean} isAdmin
* @returns {ChangeDBResult} return
*/
export const addUserToDB = (email, passwordHash, isAdmin) => {
if (!email || !passwordHash || !isAdmin) {
return {
error: "Either email, password or isAdmin are missing"
};
}
const addUser = db.prepare(`INSERT INTO Users(email,password,isAdmin) VALUES(?,?,?);`)
const result = addUser.run(email, passwordHash, isAdmin);
if (result.changes === 1) {
return {
success: `User '${email}' added to DB`
}
}
return { error: `Could not add user '${email}' to DB`, };
}
/** Removes user from db
* @param {string} email
* @returns {ChangeDBResult} return
*/
export const delUserFromDB = (email) => {
if (!email) {
return {
error: "Email not provided"
}
}
const delUser = db.prepare(`DELETE FROM Users WHERE email=${email};`);
const result = delUser.run();
if (result.changes === 1) {
return {
success: `User '${email}' removed successfully`
}
}
return { error: `Could not remove user '${email}' from DB` }
}
/**
* @function addPosterToDB
* @param {any} image
* @param {string} content
* @returns {ChangeDBResult} return
*/
export const addPosterToDB = (image, content) => {
const id = crypto.randomUUID(); // Create random ID for Poster
console.log("New id is: ", id);
const createdAt = Date.now().toString;
if (!image) { // Return error if mandatory info is missing
return {
error: "Image missing"
}
}
const addPoster = db.prepare(`INSERT INTO Posters(id,createdAt,image,content) VALUES ($id,$createdAt,$image,$content);`);
const result = addPoster.run({
id: id,
createdAt: createdAt,
image: image,
content: content
});
if (result.changes === 1) {
return {
success: `Poster added to DB with id ${id}`,
id
}
}
return { // Default return just in case
error: `Could not save poster '${id}' to DB. Something went wrong`
}
}
export const getAllPostersFromDB = () => {
const getPosters = db.prepare(`SELECT * FROM Posters;`);
const result = getPosters.all();
return result;
}

@ -0,0 +1,111 @@
<script>
import { colabs } from '$lib/stores/store';
/** @type {HTMLInputElement} */ let fileinput;
const onFileSelected = (/** @type {Event} */ e) => {
// @ts-ignore
let image = e.target.files[0];
let reader = new FileReader();
reader.readAsDataURL(image);
reader.onload = (e) => {
// @ts-ignore
$colabs = [...$colabs, { image: e.target.result, text: '' }];
};
};
const removecolabs = (/** @type {number} */ index) => {
if (index > -1) {
$colabs.splice(index, 1);
$colabs = $colabs;
}
};
</script>
<div class="colabs">
{#if $colabs[0]}
{#each $colabs as organization, i}
<div class="organization">
<div
class="remove"
on:click|preventDefault={() => {
removecolabs(i);
}}
>
x
</div>
<img class="organization-logo" src={organization.image} alt={organization.text} />
<input
class="logo-title"
type="text"
bind:value={organization.text}
placeholder="Nombre(opcional)"
/>
</div>
{/each}
{/if}
</div>
<button
on:click|preventDefault={() => {
fileinput.click();
}}>Añadir logo</button
>
<input
style="display:none;"
type="file"
accept=".jpg, .jpeg, .png"
on:change={(e) => onFileSelected(e)}
bind:this={fileinput}
/>
<style>
.colabs {
display: grid;
grid-template-columns: repeat(3, min-content);
align-items: center;
justify-content: start;
}
.organization {
position: relative;
display: grid;
grid-template-rows: min-content min-content;
grid-gap: 0.5rem;
align-items: center;
justify-content: center;
margin: 0.25rem 1rem;
background-color: #ddd;
padding: 0.25rem;
}
.organization-logo {
max-height: 72px;
max-width: 144px;
margin: 0 auto;
background-color: #fff;
}
.remove {
position: absolute;
top: -4px;
right: -0.25rem;
color: red;
cursor: pointer;
background-color: white;
padding: 0 0.25rem;
z-index: 2;
}
input {
display: block;
width: 100%;
}
button {
display: block;
padding: 0.5rem;
cursor: pointer;
margin: 1rem;
}
</style>

@ -0,0 +1,220 @@
<script>
import { onMount } from 'svelte';
import html2canvas from 'html2canvas';
import Organized from '$lib/form/Organized.svelte';
import Colabs from './Colabs.svelte';
import {
title,
heading,
subtitle,
content,
date,
time,
weekday,
address,
city,
canvas
} from '$lib/stores/store';
/** @type {HTMLDivElement} */ let editor;
const handleSubmit = async () => {
console.log('submit');
};
const getContent = () => {
if (!editor) {
console.log('no hay editor');
return;
}
if (editor && editor.firstChild !== null) {
// @ts-ignore
$content = editor.firstChild.innerHTML;
}
};
const saveCanvas = async () => {
if ($canvas !== undefined) {
const img = await html2canvas($canvas, { scale: 2 });
const image = img.toDataURL('image/png');
const data = new FormData();
data.append('image', image);
data.append(
'content',
JSON.stringify({
title: $title,
heading: $heading,
subtitle: $subtitle,
content: $content,
date: $date,
time: $time,
weekday: $weekday,
address: $address,
city: $city
})
);
const req = await fetch('/admin', {
method: 'POST',
body: data
});
console.log(req);
}
};
const downloadCanvas = async () => {
if ($canvas !== undefined) {
const res = await html2canvas($canvas, { scale: 2 });
const image = res.toDataURL('image/png');
const link = document.createElement('a');
link.download = `${$date.split('-')[0]}-${$date.split('-')[1]}-${
$date.split('-')[2].split('T')[0]
}-${$city.replace(' ', '_')}-cartel.png`;
link.href = image;
link.click();
}
};
const quillOptions = {
modules: {
toolbar: [['bold', 'italic']]
},
theme: 'snow'
};
onMount(async () => {
const { default: Quill } = await import('quill');
let quill = new Quill(editor, {
modules: {
toolbar: quillOptions
},
theme: 'snow',
placeholder:
'Participan:\nFernando Marín (DMD)\nPresenta:\nFernanda del Castillo (DMD Asturias)'
});
});
</script>
<form on:submit|preventDefault={handleSubmit}>
<div class="form-group">
<label for="heading">Tipo de actividad</label>
<input
bind:value={$heading}
type="text"
name="heading"
placeholder="Ej: Taller, charla, coloquio, debate, curso"
/>
</div>
<div clasS="form-group">
<label for="title">Título</label>
<input bind:value={$title} type="text" name="title" placeholder="Título" />
</div>
<div class="form-group">
<label for="subtitle">Subtítulo</label>
<input bind:value={$subtitle} type="text" name="subtitle" placeholder="Subtítulo" />
</div>
<div class="form-group">
<label for="content">Contenido</label>
<div id="editor" on:click={getContent}>
<div on:click={getContent} on:keyup={getContent} bind:this={editor} />
</div>
</div>
<div class="form-group when">
<div class="daymonth">
<label for="date">Fecha</label>
<input bind:value={$date} type="date" name="date" />
</div>
<div class="time">
<label for="time">Hora</label>
<input bind:value={$time} type="time" name="time" />
</div>
<div class="dayname">
<label for="weekday">Día de la semana</label>
<input bind:value={$weekday} type="text" name="weekday" placeholder="Ej. Lunes" />
</div>
</div>
<div class="form-group">
<label for="address">Dirección</label>
<textarea
bind:value={$address}
type="text"
name="address"
placeholder="Ej. Calle del Abeto, 4"
/>
<label for="city">Ciudad</label>
<input bind:value={$city} type="text" name="" placeholder="Ej. Cádiz" />
</div>
<div class="form-group ">
<label for="organizedBy">Organiza:</label>
<Organized />
<label for="colabs">Colabora:</label>
<Colabs />
</div>
</form>
<button on:click|preventDefault={downloadCanvas}>Descargar</button>
<button on:click|preventDefault={saveCanvas}>Guardar</button>
<style>
.form-group {
margin: 1rem 0;
}
form {
margin: 1rem;
display: block;
}
.form-group {
display: block;
}
input {
display: block;
width: 100%;
font-size: 0.8rem;
font-family: sans-serif;
padding: 0.5rem;
}
textarea {
display: block;
width: 100%;
font-size: 0.8rem;
font-family: sans-serif;
padding: 0.5rem;
min-height: 3rem;
line-height: 1.3rem;
resize: vertical;
}
label {
display: block;
font-size: 0.8rem;
font-family: monospace;
margin: 0.25rem 0;
}
#editor {
background-color: white;
}
button {
margin: 1rem;
padding: 0.5rem;
font-size: 1.1rem;
cursor: pointer;
}
.form-group.when {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
grid-gap: 0.25rem;
}
</style>

@ -0,0 +1,108 @@
<script>
import { organizedBy } from '$lib/stores/store';
/** @type {HTMLInputElement} */ let fileinput;
const onFileSelected = (/** @type {Event} */ e) => {
// @ts-ignore
let image = e.target.files[0];
let reader = new FileReader();
reader.readAsDataURL(image);
reader.onload = (e) => {
// @ts-ignore
$organizedBy = [...$organizedBy, { image: e.target.result, text: '' }];
};
};
const removeOrganizer = (/** @type {number} */ index) => {
if (index > -1) {
$organizedBy.splice(index, 1);
$organizedBy = $organizedBy;
}
};
</script>
<div class="form-organized-by">
{#if $organizedBy[0]}
{#each $organizedBy as organization, i}
<div class="organization">
<div
class="remove"
on:click|preventDefault={() => {
removeOrganizer(i);
}}
>
x
</div>
<img class="organized-logo" src={organization.image} alt={organization.text} />
<input
class="logo-title"
type="text"
bind:value={organization.text}
placeholder="Nombre (opcional)"
/>
</div>
{/each}
{/if}
</div>
<button
on:click|preventDefault={() => {
fileinput.click();
}}>Añadir logo</button
>
<input
style="display:none;"
type="file"
accept=".jpg, .jpeg, .png"
on:change={(e) => onFileSelected(e)}
bind:this={fileinput}
/>
<style>
.form-organized-by {
display: grid;
grid-template-columns: repeat(3, min-content);
align-items: center;
justify-content: start;
}
.organization {
position: relative;
display: grid;
grid-template-rows: min-content min-content;
grid-gap: 0.5rem;
align-items: center;
justify-content: center;
margin: 0.25rem 1rem;
background-color: #ddd;
padding: 0.25rem;
}
.organized-logo {
max-height: 72px;
max-width: 128px;
margin: 0 auto;
background-color: #fff;
}
.remove {
position: absolute;
top: -4px;
right: -0.25rem;
color: red;
cursor: pointer;
background-color: white;
padding: 0 0.25rem;
}
input {
display: block;
width: 100%;
}
button {
display: inline-block;
padding: 0.5rem;
cursor: pointer;
margin: 1rem;
}
</style>

@ -0,0 +1,23 @@
<header>
<h1><a href="/">Carteles dmd</a></h1>
</header>
<style>
header {
max-height: 4rem;
margin: 0;
padding: 0.5rem 1rem;
}
h1 {
text-align: left;
font-weight: 700;
color: firebrick;
font-family: 'Alegreya Sans SC', sans-serif;
}
a {
color: inherit;
text-decoration: none;
cursor: pointer;
font-size: 2rem;
}
</style>

@ -0,0 +1,45 @@
/** @return {string} */
export const getMonthName = (/** @type {string} */ month) => {
let res
switch (month) {
case "01":
res = "enero";
break;
case "02":
res = "febrero";
break;
case "03":
res = "marzo";
break;
case "04":
res = "abril";
break;
case "05":
res = "mayo";
break;
case "06":
res = "junio";
break;
case "07":
res = "julio";
break;
case "08":
res = "agosto";
break;
case "09":
res = "septiembre";
break;
case "10":
res = "octubre";
break;
case "11":
res = "noviembre";
break;
case "12":
res = "diciembre";
break;
default:
res = "";
}
return res;
}

@ -0,0 +1,193 @@
<script>
/** @type {number} */ export let templateIndex;
/** @type {string} */ export let templateImage;
import { organizedBy, colabs, canvas } from '$lib/stores/store';
import Heading from '$lib/components/Heading.svelte';
import Title from '$lib/components/Title.svelte';
import Subtitle from '$lib/components/Subtitle.svelte';
import Content from '$lib/components/Content.svelte';
import Time from '$lib/components/Time.svelte';
import Date from '$lib/components/Date.svelte';
import Weekday from '$lib/components/Weekday.svelte';
import Address from '$lib/components/Address.svelte';
import Text from '$lib/components/Text.svelte';
import City from '$lib/components/City.svelte';
</script>
<div class="preview">
<div class="result" bind:this={$canvas} style="background-image: url('/{templateImage}');">
{#if templateIndex >= 0}
<Heading {templateIndex} />
<Title {templateIndex} />
<Subtitle {templateIndex} />
<Content {templateIndex} />
<Date {templateIndex} />
<Time {templateIndex} />
<Weekday {templateIndex} />
<Address {templateIndex} />
<City {templateIndex} />
{/if}
{#if $organizedBy[0] && $colabs[0]}
<div class="orgcolab">
<div class="org">Organiza:</div>
<div class="colab">Colabora:</div>
<div class="orgmixed">
{#each $organizedBy as organization}
<div class="organization">
<img src={organization.image} alt={organization.text} />
<div class="caption">{organization.text}</div>
</div>
{/each}
</div>
<div class="colabmixed">
{#each $colabs as organization}
<div class="organization">
<img src={organization.image} alt={organization.text} />
<div class="caption">{organization.text}</div>
</div>
{/each}
</div>
</div>
{/if}
{#if $organizedBy[0] && !$colabs[0]}
<div class="imagetitle">Organiza:</div>
<div class="organized-by">
{#each $organizedBy as organization}
<div class="organization">
<img src={organization.image} alt={organization.text} />
<div class="caption">{organization.text}</div>
</div>
{/each}
</div>
{/if}
{#if $colabs[0] && !$organizedBy[0]}
<div class="imagetitle">Colabora:</div>
<div class="organized-by">
{#each $colabs as organization}
<div class="organization">
<img src={organization.image} alt={organization.text} />
<div class="caption">{organization.text}</div>
</div>
{/each}
</div>
{/if}
</div>
<div class="text">
<Text />
</div>
</div>
<style>
.preview {
padding: 8px;
/* display: grid; */
/* justify-content: center; */
/* grid-template-columns: 1fr; */
/* align-items: start; */
}
.result {
display: block;
width: 600px;
/* height: 8px; */
aspect-ratio: 1/1.4142;
/* min-height: 842px; */
/* align-self: center; */
/* width: auto; */
background-size: cover;
position: relative;
/* padding: 1rem; */
}
.imagetitle {
position: absolute;
top: 725px;
left: 16px;
font-size: 0.8rem;
color: #444;
text-transform: uppercase;
}
.organized-by {
position: absolute;
top: 745px;
left: 1rem;
right: 1rem;
display: grid;
grid-template-columns: repeat(auto-fit, minmax(min(100%/3, max(64px, 100%/5)), 1fr));
grid-template-rows: min-content;
grid-auto-rows: auto;
justify-content: center;
align-items: center;
}
.organization {
margin: 0 1rem;
display: flex;
flex-direction: column;
text-align: center;
align-items: center;
justify-content: center;
}
.organization img {
max-height: 64px;
max-width: 128px;
margin: 0 auto;
opacity: 100%;
}
.caption {
margin: 0.5rem 0;
font-size: 0.7rem;
font-family: sans-serif;
text-transform: uppercase;
color: #666;
}
.orgcolab {
position: absolute;
top: 725px;
left: 16px;
height: 7rem;
overflow: hidden;
display: grid;
grid-template-columns: 1fr 1fr;
grid-template-rows: min-content 1fr;
grid-gap: 0.25rem;
justify-content: start;
align-items: center;
width: 100%;
}
.org {
grid-row: 1/2;
grid-column: 1/2;
font-size: 0.8rem;
text-transform: uppercase;
}
.colab {
font-size: 0.8rem;
grid-row: 1/2;
grid-column: 2/3;
text-transform: uppercase;
}
.orgmixed {
grid-row: 2/3;
grid-column: 1/2;
display: flex;
}
.colabmixed {
grid-row: 2/3;
grid-column: 2/3;
display: flex;
}
</style>

@ -0,0 +1,16 @@
import { writable } from 'svelte/store'
export const title = writable("");
export const heading = writable("");
export const subtitle = writable("");
export const content = writable("");
export const date = writable("");
export const newdate = writable("");
export const time = writable("");
export const weekday = writable("");
export const address = writable("");
export const city = writable("");
export const organizedBy = writable([]);
export const colabs = writable([]);
export const canvas = writable();
export const multiplier = writable(1);

@ -0,0 +1,117 @@
import '$lib/templates/templates'
/** @type {import('$lib/templates/templates').Template} */
export const azul = {
name: "azul",
description: "Azul",
image: "plantilla_azul.png",
heading: {
top: 415,
height: 32,
left: 420,
right: 8,
fontSize: 20,
color: "white",
fontFamily: "'Alegreya Sans SC', sans-serif",
lineHeight: 32,
textAlign: "left",
fontWeight: 700
},
title: {
top: 445,
height: 120,
left: 16,
right: 16,
fontSize: 40,
color: "firebrick",
fontFamily: "'Alegreya Sans SC', sans-serif",
lineHeight: 40,
textAlign: "center",
fontWeight: 700
},
subtitle: {
top: 575,
height: 32,
left: 16,
right: 16,
fontSize: 24,
color: "white",
fontFamily: "'Alegreya Sans SC', sans-serif",
lineHeight: 32,
textAlign: "center",
fontWeight: 400
},
content: {
top: 630,
height: 78,
left: 8,
right: 8,
fontSize: 20,
color: "white",
fontFamily: "sans-serif",
lineHeight: 21,
textAlign: "center",
fontWeight: 400
},
date: {
top: 40,
height: 32,
left: 350,
right: 30,
fontSize: 28,
color: "#fff",
fontFamily: "'Alegreya Sans SC', sans-serif",
lineHeight: 25,
textAlign: "right",
fontWeight: 700
},
time: {
top: 70,
height: 32,
left: 350,
right: 30,
fontSize: 30,
color: "#fff",
fontFamily: "sans-serif",
lineHeight: 32,
textAlign: "right",
fontWeight: 700
},
weekday: {
top: 12,
height: 32,
left: 350,
right: 30,
fontSize: 20,
color: "#fff",
fontFamily: "'Alegreya Sans SC', sans-serif",
lineHeight: 32,
textAlign: "right",
fontWeight: 700
},
address: {
top: 12,
height: 78,
left: 30,
right: 350,
fontSize: 16,
color: "white",
fontFamily: "sans-serif",
lineHeight: 20,
textAlign: "left",
fontWeight: 400
},
city: {
top: 85,
height: 70,
left: 30,
right: 410,
fontSize: 18,
color: "white",
fontFamily: "sans-serif",
lineHeight: 22,
textAlign: "left",
fontWeight: 400
},
}

@ -0,0 +1,116 @@
import '$lib/templates/templates'
/** @type {import('$lib/templates/templates').Template} */
export const corazon = {
name: "corazon",
image: "corazon.png",
heading: {
top: 8,
height: 32,
left: 8,
right: 100,
fontSize: 32,
color: "white",
fontFamily: "'Alegreya Sans SC', sans-serif",
lineHeight: 32,
textAlign: "left",
fontWeight: 700
},
title: {
top: 88,
height: 64,
left: 8,
right: 8,
fontSize: 32,
color: "white",
fontFamily: "'Alegreya Sans SC', sans-serif",
lineHeight: 32,
textAlign: "center",
fontWeight: 300
},
subtitle: {
top: 160,
height: 48,
left: 8,
right: 8,
fontSize: 48,
color: "white",
fontFamily: "'Alegreya Sans SC', sans-serif",
lineHeight: 48,
textAlign: "center",
fontWeight: 700
},
content: {
top: 480,
height: 8.5,
left: 1.5,
right: 17,
fontSize: 0,
color: "#222",
fontFamily: "sans-serif",
lineHeight: 1.1,
textAlign: "left",
fontWeight: 700
},
date: {
top: 540,
height: 32,
left: 128,
right: 8,
fontSize: 32,
color: "#fff",
fontFamily: "'Arial', sans-serif",
lineHeight: 32,
textAlign: "left",
fontWeight: 700
},
time: {
top: 600,
height: 32,
left: 128,
right: 8,
fontSize: 32,
color: "#fff",
fontFamily: "'Arial', sans-serif",
lineHeight: 32,
textAlign: "left",
fontWeight: 700
},
weekday: {
top: 8,
height: 32,
left: 8,
right: 80,
fontSize: 0,
color: "#fff",
fontFamily: "sans-serif",
lineHeight: 1.8,
textAlign: "left",
fontWeight: 700
},
address: {
top: 650,
height: 48,
left: 128,
right: 8,
fontSize: 24,
color: "white",
fontFamily: "'Alegreya Sans SC', sans-serif",
lineHeight: 24,
textAlign: "left",
fontWeight: 400
},
city: {
top: 8,
height: 32,
left: 80,
right: 8,
fontSize: 32,
color: "white",
fontFamily: "'Alegreya Sans SC', sans-serif",
lineHeight: 36,
textAlign: "right",
fontWeight: 700
},
}

@ -0,0 +1,115 @@
import '$lib/templates/templates'
/** @type {import('$lib/templates/templates').Template} */
export const posits = {
name: "posits",
image: "plantilla_posits.jpg",
heading: {
top: 100,
height: 1.2,
left: 8,
right: 1,
fontSize: 0,
color: "white",
fontFamily: "sans-serif",
lineHeight: 1.1,
textAlign: "left",
fontWeight: 400
},
title: {
top: 450,
height: 32,
left: 80,
right: 80,
fontSize: 32,
color: "#000",
fontFamily: "'Alegreya Sans SC', sans-serif",
lineHeight: 38,
textAlign: "center",
fontWeight: 700
},
subtitle: {
top: 300,
height: 3,
left: 8,
right: 2,
fontSize: 0,
color: "firebrick",
fontFamily: "'Arial', sans-serif",
lineHeight: 1.5,
textAlign: "center",
fontWeight: 400
},
content: {
top: 480,
height: 8.5,
left: 1.5,
right: 17,
fontSize: 0,
color: "#222",
fontFamily: "sans-serif",
lineHeight: 1.1,
textAlign: "left",
fontWeight: 400
},
date: {
top: 600,
height: 48,
left: 428,
right: 32,
fontSize: 20,
color: "firebrick",
fontFamily: "sans-serif",
lineHeight: 22,
textAlign: "center",
fontWeight: 700
},
time: {
top: 655,
height: 32,
left: 428,
right: 32,
fontSize: 32,
color: "firebrick",
fontFamily: "sans-serif",
lineHeight: 32,
textAlign: "center",
fontWeight: 700
},
weekday: {
top: 600,
height: 5,
left: 18.5,
right: 5,
fontSize: 20,
color: "#fff",
fontFamily: "sans-serif",
lineHeight: 28,
textAlign: "center",
fontWeight: 700
},
address: {
top: 555,
height: 96,
left: 32,
right: 385,
fontSize: 18,
color: "#000",
fontFamily: "'Arial', sans-serif",
lineHeight: 24,
textAlign: "center",
fontWeight: 400
},
city: {
top: 660,
height: 48,
left: 32,
right: 385,
fontSize: 24,
color: "#000",
fontFamily: "'Arial', sans-serif",
lineHeight: 24,
textAlign: "center",
fontWeight: 700
},
}

@ -0,0 +1,38 @@
import { verde } from '$lib/templates/verde';
import { azul } from '$lib/templates/azul';
import { corazon } from '$lib/templates/corazon';
import { posits } from '$lib/templates/posits';
/**
* @typedef {{
* top: number;
* height: number;
* right: number;
* left: number;
* color: string;
* fontSize: number;
* fontFamily: string;
* lineHeight: number;
* textAlign: string;
* fontWeight: number;
* }} Element
*/
/**
* @typedef {{
* name: string;
* description: string;
* image: string;
* heading: Element;
* title: Element;
* subtitle: Element;
* date: Element;
* time: Element;
* weekday: Element;
* content: Element;
* address: Element;
* city: Element;
* }} Template
*/
/** @type Array<Template> */
export const templates = [verde, azul, corazon, posits];

@ -0,0 +1,117 @@
import '$lib/templates/templates'
/** @type {import('$lib/templates/templates').Template} */
export const verde = {
name: "verde",
description: "Una plantilla con todos los elementos",
image: "plantilla_verde.png",
heading: {
top: 100,
height: 32,
left: 140,
right: 20,
fontSize: 24,
color: "white",
fontFamily: "'Alegreya Sans SC', sans-serif",
lineHeight: 32,
textAlign: "left",
fontWeight: 700
},
title: {
top: 150,
height: 86,
left: 132,
right: 16,
fontSize: 40,
color: "white",
fontFamily: "'Alegreya Sans SC', sans-serif",
lineHeight: 40,
textAlign: "center",
fontWeight: 700
},
subtitle: {
top: 295,
height: 82,
left: 132,
right: 16,
fontSize: 18,
color: "firebrick",
fontFamily: "'Alegreya Sans SC', sans-serif",
lineHeight: 20,
textAlign: "center",
fontWeight: 400
},
content: {
top: 430,
height: 164,
left: 32,
right: 320,
fontSize: 14,
color: "#222",
fontFamily: "'Helvetica', sans-serif",
lineHeight: 18,
textAlign: "left",
fontWeight: 400
},
date: {
top: 628,
height: 32,
left: 275,
right: 32,
fontSize: 18,
color: "#fff",
fontFamily: "sans-serif",
lineHeight: 32,
textAlign: "center",
fontWeight: 700
},
time: {
top: 650,
height: 32,
left: 275,
right: 32,
fontSize: 25
,
color: "#fff",
fontFamily: "sans-serif",
lineHeight: 32,
textAlign: "center",
fontWeight: 700
},
weekday: {
top: 605,
height: 32,
left: 245,
right: 32,
fontSize: 18,
color: "#fff",
fontFamily: "sans-serif",
lineHeight: 32,
textAlign: "center",
fontWeight: 700
},
address: {
top: 610,
height: 70,
left: 32,
right: 300,
fontSize: 16,
color: "#91b756",
fontFamily: "sans-serif",
lineHeight: 22,
textAlign: "left",
fontWeight: 700
},
city: {
top: 685,
height: 24,
left: 32,
right: 300,
fontSize: 24,
color: "#91b756",
fontFamily: "sans-serif",
lineHeight: 24,
textAlign: "left",
fontWeight: 700
},
}

@ -0,0 +1,43 @@
<script>
// import { multiplier } from '$lib/stores/store';
import Header from '$lib/header/Header.svelte';
import '../app.css';
// import { browser } from '$app/environment';
// let browserType = '';
// if (browser) {
// const agent = navigator.userAgent;
// if (agent.match(/chrome|chromium|crios/i)) {
// browserType = 'chrome';
// $multiplier = 1;
// } else if (agent.match(/firefox|fxios/i)) {
// browserType = 'firefox';
// $multiplier = 1;
// } else if (agent.match(/safari/i)) {
// browserType = 'safari';
// } else if (agent.match(/opr\//i)) {
// browserType = 'opera';
// } else if (agent.match(/edg/i)) {
// browserType = 'edge';
// } else {
// browserType = 'No browser detection';
// }
// }
// console.log('Browser is: ', browserType);
</script>
<Header />
<main>
<slot />
</main>
<style>
main {
width: 100%;
padding: 0;
margin: 0 auto;
box-sizing: border-box;
}
</style>

@ -0,0 +1 @@
export const prerender = true;

@ -0,0 +1,85 @@
<script>
import { templates } from "$lib/templates/templates";
</script>
<svelte:head>
<title>Carteles DMD</title>
<meta
name="description"
content="Aplicación para la generación automática de carteles de actividades de DMD"
/>
</svelte:head>
<section>
<p>Elige plantilla</p>
<div class="main">
<ul>
{#each templates as template}
<li>
<a href="/{template.name}" data-sveltekit-prefetch
><img
src="/{template.image}"
alt="Plantilla en blanco estilo {template.name}"
/>
{#if template.description}
<div class="caption">{@html template.description}</div>
{:else}
<div class="caption">{template.name}</div>
{/if}
</a>
</li>
{/each}
</ul>
</div>
</section>
<style>
section {
max-width: 800px;
margin: 0 auto;
display: grid;
min-height: 90vh;
}
.main {
display: grid;
grid-auto-columns: 1fr;
grid-auto-rows: 1fr;
grid-gap: 1rem;
padding: 1rem 0;
}
p {
font-size: 1.5rem;
color: #333;
}
ul {
list-style: none;
display: flex;
}
li {
line-height: 2rem;
display: inline-block;
padding: 0.25rem;
background-color: rebeccapurple;
box-shadow: 0 0 0.5rem 0 rgba(0, 0, 0, 0.7);
margin: 0 0.5rem;
border-radius: 0.5rem;
align-self: center;
}
a {
text-transform: capitalize;
cursor: pointer;
color: white;
text-decoration: none;
text-align: center;
font-weight: 400;
font-size: 1.4rem;
}
img {
max-height: 200px;
border-radius: 0.25rem 0.25rem 0 0;
}
.caption {
text-align: left;
padding: 0.25rem;
}
</style>

@ -0,0 +1,14 @@
<script>
import { page } from '$app/stores';
</script>
{#if $page.error}
<div class="error-message"><h1>{$page.status}: {$page.error.message}</h1></div>
{/if}
<style>
.error-message {
max-width: 480px;
margin: 0 auto;
}
</style>

@ -0,0 +1,21 @@
import { error } from '@sveltejs/kit';
import { templates } from '$lib/templates/templates';
/** @type {import('./$types').PageLoad} */
export function load(event) {
/** @type {string} */
const slug = event.params.slug;
/** @type {number} */ const templateIndex = templates.map((e) => e.name).indexOf(slug);
/** @type {string} */ const templateImage = templates[templateIndex].image;
if (templateIndex !== -1) {
return {
slug,
templateIndex,
templateImage
}
}
throw error(404, "Not found");
}

@ -0,0 +1,52 @@
<script>
/**
* Un objeto con los datos que llegan de la funcion load de +page.js
* @typedef {{
* slug: string;
* templateIndex: number;
* templateImage: string;
* }}
* LoadFunctionData
*/
/** @type LoadFunctionData */ export let data;
import Form from '$lib/form/Form.svelte';
import Preview from '$lib/preview/Preview.svelte';
const templateIndex = data.templateIndex;
const templateImage = data.templateImage;
</script>
<svelte:head>
<title>Carteles DMD - Plantilla {data.slug}</title>
</svelte:head>
<div class="main">
<section class="form"><Form /></section>
{#if templateIndex !== undefined}
<section class="preview">
<Preview {templateIndex} {templateImage} />
</section>
{/if}
</div>
<style>
.main {
display: grid;
grid-template-columns: 1fr 616px;
grid-gap: 0.5rem;
min-height: calc(100vh - 5rem);
}
.form {
grid-column: 1/2;
background-color: rgb(236, 240, 240);
}
.preview {
grid-column: 2/3;
background-color: rgb(240, 240, 240);
}
</style>

@ -0,0 +1,33 @@
import { error } from '@sveltejs/kit';
import fs from 'fs';
import stream from 'stream';
// import { canvas } from '$lib/stores/store';
import { getAllPostersFromDB, addPosterToDB } from '$lib/db/utils';
/** @type {import('./$types').PageServerLoad} */
export const load = () => {
const posters = getAllPostersFromDB();
if (posters !== undefined) {
return {
posters
}
}
throw error(404, "error");
};
/** @type {import('./$types').Actions} */
export const actions = {
default: async ({ request }) => {
console.log("Estoy aquí");
const path = crypto.randomUUID();
const data = await request.formData();
const image = data.get("image")?.toString();
const content = data.get("content");
fs.writeFileSync(`./static/${path}.png`, image, 'base64url');
// const savetoDB = addPosterToDB(image, content);
return {
success: true
}
}
}

@ -0,0 +1,31 @@
<script>
/** @type {import('./$types').PageData} */
export let data;
$: posters = data.posters;
/** @type {Blob} */ let image = new Blob();
/** @type {string} */ let template = '';
/** @type {string} */ let content = '';
</script>
<h1>Test</h1>
<ul>
{#each posters as poster}
<li>{poster.name}</li>
{/each}
</ul>
<form method="POST">
<input name="template" bind:value={template} placeholder="Plantilla" type="text" />
<input name="content" bind:value={content} placeholder="Contenido" type="text" />
<input name="image" bind:value={image} type="file" />
<button>Send</button>
</form>
<style>
input {
display: block;
margin: 1rem 0;
}
</style>

@ -0,0 +1,66 @@
import { error } from '@sveltejs/kit';
import { api } from './api';
/**
* @typedef {{
* uid: string;
* created_at: Date;
* text: string;
* done: boolean;
* pending_delete: boolean;
* }} Todo
*/
/** @type {import('./$types').PageServerLoad} */
export const load = async ({ locals }) => {
// locals.userid comes from src/hooks.js
const response = await api('GET', `todos/${locals.userid}`);
if (response.status === 404) {
// user hasn't created a todo list.
// start with an empty array
return {
/** @type {Todo[]} */
todos: []
};
}
if (response.status === 200) {
return {
/** @type {Todo[]} */
todos: await response.json()
};
}
throw error(response.status);
};
/** @type {import('./$types').Actions} */
export const actions = {
add: async ({ request, locals }) => {
const form = await request.formData();
await api('POST', `todos/${locals.userid}`, {
text: form.get('text')
});
},
edit: async ({ request, locals }) => {
const form = await request.formData();
await api('PATCH', `todos/${locals.userid}/${form.get('uid')}`, {
text: form.get('text')
});
},
toggle: async ({ request, locals }) => {
const form = await request.formData();
await api('PATCH', `todos/${locals.userid}/${form.get('uid')}`, {
done: !!form.get('done')
});
},
delete: async ({ request, locals }) => {
const form = await request.formData();
await api('DELETE', `todos/${locals.userid}/${form.get('uid')}`);
}
};

@ -0,0 +1,182 @@
<script>
import { enhance } from '$app/forms';
import { invalidateAll } from '$app/navigation';
import { scale } from 'svelte/transition';
import { flip } from 'svelte/animate';
/** @type {import('./$types').PageData} */
export let data;
$: todos = data.todos;
</script>
<svelte:head>
<title>Todos</title>
<meta name="description" content="A todo list app" />
</svelte:head>
<div class="todos">
<h1>Todos</h1>
<form
class="new"
action="/todos?/add"
method="post"
use:enhance={() => {
return ({ form, result }) => {
if (result.type === 'success') {
form.reset();
invalidateAll();
}
};
}}
>
<input name="text" aria-label="Add todo" placeholder="+ tap to add a todo" />
</form>
{#each todos as todo (todo.uid)}
<div
class="todo"
class:done={todo.done}
transition:scale|local={{ start: 0.7 }}
animate:flip={{ duration: 200 }}
>
<form
action="/todos?/toggle"
method="post"
use:enhance={({ data }) => {
todo.done = !!data.get('done');
}}
>
<input type="hidden" name="uid" value={todo.uid} />
<input type="hidden" name="done" value={todo.done ? '' : 'true'} />
<button class="toggle" aria-label="Mark todo as {todo.done ? 'not done' : 'done'}" />
</form>
<form class="text" action="/todos?/edit" method="post" use:enhance>
<input type="hidden" name="uid" value={todo.uid} />
<input aria-label="Edit todo" type="text" name="text" value={todo.text} />
<button class="save" aria-label="Save todo" />
</form>
<form
action="/todos?/delete"
method="post"
use:enhance={() => {
todo.pending_delete = true;
}}
>
<input type="hidden" name="uid" value={todo.uid} />
<button class="delete" aria-label="Delete todo" disabled={todo.pending_delete} />
</form>
</div>
{/each}
</div>
<style>
.todos {
width: 100%;
max-width: var(--column-width);
margin: var(--column-margin-top) auto 0 auto;
line-height: 1;
}
.new {
margin: 0 0 0.5rem 0;
}
input {
border: 1px solid transparent;
}
input:focus-visible {
border: 1px solid #ff3e00 !important;
outline: none;
}
.new input {
font-size: 28px;
width: 100%;
padding: 0.5em 1em 0.3em 1em;
box-sizing: border-box;
background: rgba(255, 255, 255, 0.05);
border-radius: 8px;
text-align: center;
}
.todo {
display: grid;
grid-template-columns: 2rem 1fr 2rem;
grid-gap: 0.5rem;
align-items: center;
margin: 0 0 0.5rem 0;
padding: 0.5rem;
background-color: white;
border-radius: 8px;
filter: drop-shadow(2px 4px 6px rgba(0, 0, 0, 0.1));
transform: translate(-1px, -1px);
transition: filter 0.2s, transform 0.2s;
}
.done {
transform: none;
opacity: 0.4;
filter: drop-shadow(0 0 1px rgba(0, 0, 0, 0.1));
}
form.text {
position: relative;
display: flex;
align-items: center;
flex: 1;
}
.todo input {
flex: 1;
padding: 0.5em 2em 0.5em 0.8em;
border-radius: 3px;
}
.todo button {
width: 2em;
height: 2em;
border: none;
background-color: transparent;
background-position: 50% 50%;
background-repeat: no-repeat;
}
button.toggle {
border: 1px solid rgba(0, 0, 0, 0.2);
border-radius: 50%;
box-sizing: border-box;
background-size: 1em auto;
}
.done .toggle {
background-image: url("data:image/svg+xml,%3Csvg width='22' height='16' viewBox='0 0 22 16' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M20.5 1.5L7.4375 14.5L1.5 8.5909' stroke='%23676778' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
}
.delete {
background-image: url("data:image/svg+xml,%3Csvg width='24' height='24' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M4.5 5V22H19.5V5H4.5Z' fill='%23676778' stroke='%23676778' stroke-width='1.5' stroke-linejoin='round'/%3E%3Cpath d='M10 10V16.5' stroke='white' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3Cpath d='M14 10V16.5' stroke='white' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3Cpath d='M2 5H22' stroke='%23676778' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3Cpath d='M8 5L9.6445 2H14.3885L16 5H8Z' fill='%23676778' stroke='%23676778' stroke-width='1.5' stroke-linejoin='round'/%3E%3C/svg%3E%0A");
opacity: 0.2;
}
.delete:hover,
.delete:focus {
transition: opacity 0.2s;
opacity: 1;
}
.save {
position: absolute;
right: 0;
opacity: 0;
background-image: url("data:image/svg+xml,%3Csvg width='24' height='24' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M20.5 2H3.5C2.67158 2 2 2.67157 2 3.5V20.5C2 21.3284 2.67158 22 3.5 22H20.5C21.3284 22 22 21.3284 22 20.5V3.5C22 2.67157 21.3284 2 20.5 2Z' fill='%23676778' stroke='%23676778' stroke-width='1.5' stroke-linejoin='round'/%3E%3Cpath d='M17 2V11H7.5V2H17Z' fill='white' stroke='white' stroke-width='1.5' stroke-linejoin='round'/%3E%3Cpath d='M13.5 5.5V7.5' stroke='%23676778' stroke-width='1.5' stroke-linecap='round'/%3E%3Cpath d='M5.99844 2H18.4992' stroke='%23676778' stroke-width='1.5' stroke-linecap='round'/%3E%3C/svg%3E%0A");
}
.todo input:focus + .save,
.save:focus {
transition: opacity 0.2s;
opacity: 1;
}
</style>

@ -0,0 +1,25 @@
/*
This module is used by the /todos endpoint to
make calls to api.svelte.dev, which stores todos
for each user.
(The data on the todo app will expire periodically; no
guarantees are made. Don't use it to organise your life.)
*/
const base = 'https://api.svelte.dev';
/**
* @param {string} method
* @param {string} resource
* @param {Record<string, unknown>} [data]
*/
export function api(method, resource, data) {
return fetch(`${base}/${resource}`, {
method,
headers: {
'content-type': 'application/json'
},
body: data && JSON.stringify(data)
});
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 264 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 183 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 769 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 890 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 109 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 260 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 MiB

@ -0,0 +1,3 @@
# https://www.robotstxt.org/robotstxt.html
User-agent: *
Disallow:

@ -0,0 +1 @@
{"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"}

@ -0,0 +1,10 @@
import adapter from '@sveltejs/adapter-auto';
/** @type {import('@sveltejs/kit').Config} */
const config = {
kit: {
adapter: adapter()
}
};
export default config;

@ -0,0 +1,8 @@
import { sveltekit } from '@sveltejs/kit/vite';
/** @type {import('vite').UserConfig} */
const config = {
plugins: [sveltekit()]
};
export default config;
Loading…
Cancel
Save