Função de trocar a fonte
This commit is contained in:
parent
89f051b7f3
commit
37842d2879
11 changed files with 414 additions and 57 deletions
141
alembic.ini
Normal file
141
alembic.ini
Normal file
|
|
@ -0,0 +1,141 @@
|
|||
# A generic, single database configuration.
|
||||
|
||||
[alembic]
|
||||
# path to migration scripts.
|
||||
# this is typically a path given in POSIX (e.g. forward slashes)
|
||||
# format, relative to the token %(here)s which refers to the location of this
|
||||
# ini file
|
||||
script_location = %(here)s/migrations
|
||||
|
||||
# template used to generate migration file names; The default value is %%(rev)s_%%(slug)s
|
||||
# Uncomment the line below if you want the files to be prepended with date and time
|
||||
# see https://alembic.sqlalchemy.org/en/latest/tutorial.html#editing-the-ini-file
|
||||
# for all available tokens
|
||||
# file_template = %%(year)d_%%(month).2d_%%(day).2d_%%(hour).2d%%(minute).2d-%%(rev)s_%%(slug)s
|
||||
|
||||
# sys.path path, will be prepended to sys.path if present.
|
||||
# defaults to the current working directory. for multiple paths, the path separator
|
||||
# is defined by "path_separator" below.
|
||||
prepend_sys_path = .
|
||||
|
||||
|
||||
# timezone to use when rendering the date within the migration file
|
||||
# as well as the filename.
|
||||
# If specified, requires the python>=3.9 or backports.zoneinfo library and tzdata library.
|
||||
# Any required deps can installed by adding `alembic[tz]` to the pip requirements
|
||||
# string value is passed to ZoneInfo()
|
||||
# leave blank for localtime
|
||||
# timezone =
|
||||
|
||||
# max length of characters to apply to the "slug" field
|
||||
# truncate_slug_length = 40
|
||||
|
||||
# set to 'true' to run the environment during
|
||||
# the 'revision' command, regardless of autogenerate
|
||||
# revision_environment = false
|
||||
|
||||
# set to 'true' to allow .pyc and .pyo files without
|
||||
# a source .py file to be detected as revisions in the
|
||||
# versions/ directory
|
||||
# sourceless = false
|
||||
|
||||
# version location specification; This defaults
|
||||
# to <script_location>/versions. When using multiple version
|
||||
# directories, initial revisions must be specified with --version-path.
|
||||
# The path separator used here should be the separator specified by "path_separator"
|
||||
# below.
|
||||
# version_locations = %(here)s/bar:%(here)s/bat:%(here)s/alembic/versions
|
||||
|
||||
# path_separator; This indicates what character is used to split lists of file
|
||||
# paths, including version_locations and prepend_sys_path within configparser
|
||||
# files such as alembic.ini.
|
||||
# The default rendered in new alembic.ini files is "os", which uses os.pathsep
|
||||
# to provide os-dependent path splitting.
|
||||
#
|
||||
# Note that in order to support legacy alembic.ini files, this default does NOT
|
||||
# take place if path_separator is not present in alembic.ini. If this
|
||||
# option is omitted entirely, fallback logic is as follows:
|
||||
#
|
||||
# 1. Parsing of the version_locations option falls back to using the legacy
|
||||
# "version_path_separator" key, which if absent then falls back to the legacy
|
||||
# behavior of splitting on spaces and/or commas.
|
||||
# 2. Parsing of the prepend_sys_path option falls back to the legacy
|
||||
# behavior of splitting on spaces, commas, or colons.
|
||||
#
|
||||
# Valid values for path_separator are:
|
||||
#
|
||||
# path_separator = :
|
||||
# path_separator = ;
|
||||
# path_separator = space
|
||||
# path_separator = newline
|
||||
#
|
||||
# Use os.pathsep. Default configuration used for new projects.
|
||||
path_separator = os
|
||||
|
||||
# set to 'true' to search source files recursively
|
||||
# in each "version_locations" directory
|
||||
# new in Alembic version 1.10
|
||||
# recursive_version_locations = false
|
||||
|
||||
# the output encoding used when revision files
|
||||
# are written from script.py.mako
|
||||
# output_encoding = utf-8
|
||||
|
||||
# database URL. This is consumed by the user-maintained env.py script only.
|
||||
# other means of configuring database URLs may be customized within the env.py
|
||||
# file.
|
||||
sqlalchemy.url = driver://user:pass@localhost/dbname
|
||||
|
||||
|
||||
[post_write_hooks]
|
||||
# post_write_hooks defines scripts or Python functions that are run
|
||||
# on newly generated revision scripts. See the documentation for further
|
||||
# detail and examples
|
||||
|
||||
# format using "black" - use the console_scripts runner, against the "black" entrypoint
|
||||
# hooks = black
|
||||
# black.type = console_scripts
|
||||
# black.entrypoint = black
|
||||
# black.options = -l 79 REVISION_SCRIPT_FILENAME
|
||||
|
||||
# lint with attempts to fix using "ruff" - use the exec runner, execute a binary
|
||||
# hooks = ruff
|
||||
# ruff.type = exec
|
||||
# ruff.executable = %(here)s/.venv/bin/ruff
|
||||
# ruff.options = check --fix REVISION_SCRIPT_FILENAME
|
||||
|
||||
# Logging configuration. This is also consumed by the user-maintained
|
||||
# env.py script only.
|
||||
[loggers]
|
||||
keys = root,sqlalchemy,alembic
|
||||
|
||||
[handlers]
|
||||
keys = console
|
||||
|
||||
[formatters]
|
||||
keys = generic
|
||||
|
||||
[logger_root]
|
||||
level = WARNING
|
||||
handlers = console
|
||||
qualname =
|
||||
|
||||
[logger_sqlalchemy]
|
||||
level = WARNING
|
||||
handlers =
|
||||
qualname = sqlalchemy.engine
|
||||
|
||||
[logger_alembic]
|
||||
level = INFO
|
||||
handlers =
|
||||
qualname = alembic
|
||||
|
||||
[handler_console]
|
||||
class = StreamHandler
|
||||
args = (sys.stderr,)
|
||||
level = NOTSET
|
||||
formatter = generic
|
||||
|
||||
[formatter_generic]
|
||||
format = %(levelname)-5.5s [%(name)s] %(message)s
|
||||
datefmt = %H:%M:%S
|
||||
36
app.py
36
app.py
|
|
@ -219,6 +219,7 @@ class Party(db.Model):
|
|||
location = db.Column(db.String(255), nullable=True)
|
||||
event_date = db.Column(db.Date, nullable=True)
|
||||
event_time = db.Column(db.Time, nullable=True)
|
||||
invite_font = db.Column(db.String(100), nullable=False, default='Montserrat-Regular')
|
||||
|
||||
@property
|
||||
def formatted_date(self):
|
||||
|
|
@ -304,14 +305,19 @@ def generate_qr_code_image(qr_data, guest_name, party, output_format='PNG'):
|
|||
GUEST_NAME_COLOR = (37, 99, 235)
|
||||
|
||||
try:
|
||||
font_path_regular = FONT_PATH
|
||||
# Use a fonte selecionada para a festa, com fallback para Montserrat-Regular
|
||||
selected_font_name = party.invite_font if party.invite_font else 'Montserrat-Regular'
|
||||
font_path_regular = os.path.join(basedir, 'static', 'fonts', f"{selected_font_name}.ttf")
|
||||
|
||||
# Tenta carregar a versão Bold se existir, caso contrário usa a regular
|
||||
font_path_bold = font_path_regular.replace("Regular", "Bold")
|
||||
if not os.path.exists(font_path_bold): font_path_bold = font_path_regular
|
||||
|
||||
font_party_name = PILImageFont.truetype(font_path_bold, 52)
|
||||
font_guest_name = PILImageFont.truetype(font_path_bold, 36)
|
||||
font_footer = PILImageFont.truetype(font_path_regular, 14)
|
||||
font_footer = PILImageFont.truetype(os.path.join(basedir, "Montserrat-Regular.ttf"), 14) # Footer always Montserrat
|
||||
except IOError:
|
||||
app.logger.warning(f"Fonte personalizada não encontrada em {FONT_PATH}. Usando fonte padrão.")
|
||||
app.logger.warning(f"Fonte personalizada {selected_font_name} não encontrada. Usando fonte padrão.")
|
||||
font_party_name, font_guest_name, font_footer = (PILImageFont.load_default(s) for s in [52,36,14])
|
||||
|
||||
logo_img_raw, logo_height = None, 0
|
||||
|
|
@ -1013,6 +1019,30 @@ def get_party_stats_data(party_id):
|
|||
def get_stats(party_id):
|
||||
return jsonify(get_party_stats_data(party_id))
|
||||
|
||||
@app.route('/api/party/<int:party_id>/font_selection', methods=['GET', 'POST'])
|
||||
@login_required
|
||||
def handle_font_selection(party_id):
|
||||
party = db.session.get(Party, party_id) or abort(404)
|
||||
check_collaboration_permission(party)
|
||||
|
||||
if request.method == 'GET':
|
||||
return jsonify({'selected_font': party.invite_font})
|
||||
|
||||
elif request.method == 'POST':
|
||||
data = request.get_json()
|
||||
font_name = data.get('font_name')
|
||||
if not font_name:
|
||||
return jsonify({'message': 'Nome da fonte não fornecido.'}), 400
|
||||
|
||||
# Basic validation to ensure the font is one of the allowed ones
|
||||
allowed_fonts = ['Birthstone-Regular', 'Ephesis-Regular', 'Montserrat-Regular', 'Radley-Regular']
|
||||
if font_name not in allowed_fonts:
|
||||
return jsonify({'message': 'Fonte selecionada inválida.'}), 400
|
||||
|
||||
party.invite_font = font_name
|
||||
db.session.commit()
|
||||
return jsonify({'message': 'Fonte do convite atualizada com sucesso!', 'selected_font': party.invite_font})
|
||||
|
||||
@app.route('/api/party/<int:party_id>/guests', methods=['GET', 'POST'])
|
||||
@login_required
|
||||
def handle_guests(party_id):
|
||||
|
|
|
|||
|
|
@ -1,9 +1,14 @@
|
|||
import logging
|
||||
from logging.config import fileConfig
|
||||
|
||||
from flask import current_app
|
||||
|
||||
from alembic import context
|
||||
import os
|
||||
import sys
|
||||
|
||||
# Adicione o diretório raiz do projeto ao sys.path para importar app
|
||||
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
|
||||
|
||||
from app import db, User, Party, Guest # Importe db e seus modelos
|
||||
|
||||
# this is the Alembic Config object, which provides
|
||||
# access to the values within the .ini file in use.
|
||||
|
|
@ -14,41 +19,26 @@ config = context.config
|
|||
fileConfig(config.config_file_name)
|
||||
logger = logging.getLogger('alembic.env')
|
||||
|
||||
|
||||
def get_engine():
|
||||
try:
|
||||
# this works with Flask-SQLAlchemy<3 and Alchemical
|
||||
return current_app.extensions['migrate'].db.get_engine()
|
||||
except (TypeError, AttributeError):
|
||||
# this works with Flask-SQLAlchemy>=3
|
||||
return current_app.extensions['migrate'].db.engine
|
||||
|
||||
|
||||
def get_engine_url():
|
||||
try:
|
||||
return get_engine().url.render_as_string(hide_password=False).replace(
|
||||
'%', '%%')
|
||||
except AttributeError:
|
||||
return str(get_engine().url).replace('%', '%%')
|
||||
|
||||
|
||||
# add your model's MetaData object here
|
||||
# for 'autogenerate' support
|
||||
# from myapp import mymodel
|
||||
# target_metadata = mymodel.Base.metadata
|
||||
config.set_main_option('sqlalchemy.url', get_engine_url())
|
||||
target_db = current_app.extensions['migrate'].db
|
||||
target_metadata = db.metadata
|
||||
|
||||
# other values from the config, defined by the needs of env.py,
|
||||
# can be acquired:
|
||||
# my_important_option = config.get_main_option("my_important_option")
|
||||
# ... etc.
|
||||
|
||||
# Configura a URL do banco de dados diretamente da variável de ambiente
|
||||
DATABASE_URL = os.environ.get('DATABASE_URL')
|
||||
if not DATABASE_URL:
|
||||
raise ValueError("A variável de ambiente 'DATABASE_URL' não foi definida. Ela é essencial para a conexão com o PostgreSQL.")
|
||||
|
||||
def get_metadata():
|
||||
if hasattr(target_db, 'metadatas'):
|
||||
return target_db.metadatas[None]
|
||||
return target_db.metadata
|
||||
# Algumas plataformas (como Heroku e versões mais antigas do Render) usam "postgres://"
|
||||
# enquanto o SQLAlchemy espera "postgresql://". Esta linha garante a compatibilidade.
|
||||
if DATABASE_URL.startswith("postgres://"):
|
||||
DATABASE_URL = DATABASE_URL.replace("postgres://", "postgresql://", 1)
|
||||
|
||||
config.set_main_option('sqlalchemy.url', DATABASE_URL)
|
||||
|
||||
|
||||
def run_migrations_offline():
|
||||
|
|
@ -65,7 +55,10 @@ def run_migrations_offline():
|
|||
"""
|
||||
url = config.get_main_option("sqlalchemy.url")
|
||||
context.configure(
|
||||
url=url, target_metadata=get_metadata(), literal_binds=True
|
||||
url=url,
|
||||
target_metadata=target_metadata,
|
||||
literal_binds=True,
|
||||
dialect_opts={"paramstyle": "named"},
|
||||
)
|
||||
|
||||
with context.begin_transaction():
|
||||
|
|
@ -79,35 +72,38 @@ def run_migrations_online():
|
|||
and associate a connection with the context.
|
||||
|
||||
"""
|
||||
from sqlalchemy import engine_from_config
|
||||
from sqlalchemy import pool
|
||||
|
||||
# this callback is used to prevent an auto-migration from being generated
|
||||
# when there are no changes to the schema
|
||||
# reference: http://alembic.zzzcomputing.com/en/latest/cookbook.html
|
||||
def process_revision_directives(context, revision, directives):
|
||||
if getattr(config.cmd_opts, 'autogenerate', False):
|
||||
script = directives[0]
|
||||
if script.upgrade_ops.is_empty():
|
||||
directives[:] = []
|
||||
logger.info('No changes in schema detected.')
|
||||
|
||||
conf_args = current_app.extensions['migrate'].configure_args
|
||||
if conf_args.get("process_revision_directives") is None:
|
||||
conf_args["process_revision_directives"] = process_revision_directives
|
||||
|
||||
connectable = get_engine()
|
||||
connectable = engine_from_config(
|
||||
config.get_section(config.config_ini_section),
|
||||
prefix="sqlalchemy.",
|
||||
poolclass=pool.NullPool,
|
||||
)
|
||||
|
||||
with connectable.connect() as connection:
|
||||
context.configure(
|
||||
connection=connection,
|
||||
target_metadata=get_metadata(),
|
||||
**conf_args
|
||||
target_metadata=target_metadata,
|
||||
compare_type=True, # Importante para detectar mudanças de tipo
|
||||
# this callback is used to prevent an auto-migration from being generated
|
||||
# when there are no changes to the schema
|
||||
# reference: http://alembic.zzzcomputing.com/en/latest/cookbook.html
|
||||
process_revision_directives=process_revision_directives,
|
||||
)
|
||||
|
||||
with context.begin_transaction():
|
||||
context.run_migrations()
|
||||
|
||||
def process_revision_directives(context, revision, directives):
|
||||
if getattr(config.cmd_opts, 'autogenerate', False):
|
||||
script = directives[0]
|
||||
if script.upgrade_ops.is_empty():
|
||||
directives[:] = []
|
||||
logger.info('No changes in schema detected.')
|
||||
|
||||
|
||||
if context.is_offline_mode():
|
||||
run_migrations_offline()
|
||||
else:
|
||||
run_migrations_online()
|
||||
run_migrations_online()
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
"""Add invite_font to Party model
|
||||
|
||||
Revision ID: 491a3e67287b
|
||||
Revises: ab5f97567bec
|
||||
Create Date: 2025-07-07 01:51:28.636888
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '491a3e67287b'
|
||||
down_revision = 'ab5f97567bec'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
with op.batch_alter_table('party', schema=None) as batch_op:
|
||||
batch_op.add_column(sa.Column('invite_font', sa.String(length=100), nullable=True))
|
||||
|
||||
op.execute("UPDATE party SET invite_font = 'Montserrat-Regular' WHERE invite_font IS NULL")
|
||||
|
||||
with op.batch_alter_table('party', schema=None) as batch_op:
|
||||
batch_op.alter_column('invite_font',
|
||||
existing_type=sa.String(length=100),
|
||||
nullable=False)
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
with op.batch_alter_table('party', schema=None) as batch_op:
|
||||
batch_op.drop_column('invite_font')
|
||||
# ### end Alembic commands ###
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
"""Reset e criação inicial das tabelas
|
||||
|
||||
Revision ID: 6f810791ec7d
|
||||
Revision ID: ab5f97567bec
|
||||
Revises:
|
||||
Create Date: 2025-07-02 01:12:00.632624
|
||||
Create Date: 2025-07-07 01:21:08.654559
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
|
|
@ -10,7 +10,7 @@ import sqlalchemy as sa
|
|||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '6f810791ec7d'
|
||||
revision = 'ab5f97567bec'
|
||||
down_revision = None
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
BIN
static/fonts/Birthstone-Regular.ttf
Normal file
BIN
static/fonts/Birthstone-Regular.ttf
Normal file
Binary file not shown.
BIN
static/fonts/Ephesis-Regular.ttf
Normal file
BIN
static/fonts/Ephesis-Regular.ttf
Normal file
Binary file not shown.
1
static/fonts/Montserrat-Regular.ttf
Normal file
1
static/fonts/Montserrat-Regular.ttf
Normal file
|
|
@ -0,0 +1 @@
|
|||
This is a placeholder for Montserrat-Regular.ttf. Please replace this with the actual font file content.
|
||||
BIN
static/fonts/Radley-Regular.ttf
Normal file
BIN
static/fonts/Radley-Regular.ttf
Normal file
Binary file not shown.
|
|
@ -16,7 +16,46 @@
|
|||
|
||||
{% block content %}
|
||||
|
||||
<style>
|
||||
<style>
|
||||
@font-face {
|
||||
font-family: 'Birthstone-Regular';
|
||||
src: url('{{ url_for('static', filename='fonts/Birthstone-Regular.ttf') }}') format('truetype');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Ephesis-Regular';
|
||||
src: url('{{ url_for('static', filename='fonts/Ephesis-Regular.ttf') }}') format('truetype');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Montserrat-Regular';
|
||||
src: url('{{ url_for('static', filename='fonts/Montserrat-Regular.ttf') }}') format('truetype');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Radley-Regular';
|
||||
src: url('{{ url_for('static', filename='fonts/Radley-Regular.ttf') }}') format('truetype');
|
||||
}
|
||||
.font-button {
|
||||
padding: 1rem;
|
||||
border-radius: 0.75rem;
|
||||
background-color: var(--tw-bg-opacity);
|
||||
--tw-bg-opacity: 0.1;
|
||||
box-shadow: var(--tw-shadow-inner);
|
||||
text-align: center;
|
||||
font-size: 1.25rem;
|
||||
font-weight: 600;
|
||||
color: var(--tw-text-opacity);
|
||||
--tw-text-opacity: 1;
|
||||
transition: all 0.2s;
|
||||
cursor: pointer;
|
||||
border: 2px solid transparent;
|
||||
}
|
||||
.font-button:hover {
|
||||
--tw-bg-opacity: 0.2;
|
||||
box-shadow: var(--tw-shadow-lg);
|
||||
}
|
||||
.font-button.selected {
|
||||
border-color: var(--color-primary);
|
||||
box-shadow: 0 0 0 3px var(--color-primary-light), var(--tw-shadow-lg);
|
||||
}
|
||||
.modal, .toast-container { transition: opacity 0.3s ease-in-out, visibility 0.3s ease-in-out; }
|
||||
.modal-content, .toast { transition: transform 0.3s ease-in-out; }
|
||||
.modal.show .modal-content, .toast.show { transform: scale(1); }
|
||||
|
|
@ -60,7 +99,11 @@
|
|||
<button class="tab-button flex-1 py-3 text-center text-lg font-semibold text-gray-600 dark:text-gray-300 hover:text-primary dark:hover:text-primary transition-colors duration-200 border-b-2 border-transparent data-[active=true]:border-primary data-[active=true]:text-primary dark:data-[active=true]:text-primary" data-tab="add-invite">
|
||||
<i class="fas fa-user-plus mr-2"></i>Adicionar & Convidar
|
||||
</button>
|
||||
<!-- ABA 5: Configurações -->
|
||||
<!-- ABA 5: Customizar Convite -->
|
||||
<button class="tab-button flex-1 py-3 text-center text-lg font-semibold text-gray-600 dark:text-gray-300 hover:text-primary dark:hover:text-primary transition-colors duration-200 border-b-2 border-transparent data-[active=true]:border-primary data-[active=true]:text-primary dark:data-[active=true]:text-primary" data-tab="customize-invite">
|
||||
<i class="fas fa-paint-brush mr-2"></i>Customizar Convite
|
||||
</button>
|
||||
<!-- ABA 6: Configurações -->
|
||||
<button class="tab-button flex-1 py-3 text-center text-lg font-semibold text-gray-600 dark:text-gray-300 hover:text-primary dark:hover:text-primary transition-colors duration-200 border-b-2 border-transparent data-[active=true]:border-primary data-[active=true]:text-primary dark:data-[active=true]:text-primary" data-tab="settings">
|
||||
<i class="fas fa-cog mr-2"></i>Configurações
|
||||
</button>
|
||||
|
|
@ -201,7 +244,19 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ABA 5: CONTEÚDO DAS CONFIGURAÇÕES -->
|
||||
<!-- ABA 5: CONTEÚDO DE CUSTOMIZAR CONVITE -->
|
||||
<div id="tab-customize-invite" class="tab-content pt-6 hidden fade-in">
|
||||
<div class="glass-card rounded-2xl shadow-inner p-6">
|
||||
<h2 class="text-xl font-bold text-gray-900 dark:text-white mb-6 flex items-center"><i class="fas fa-paint-brush text-primary mr-3"></i>Customizar Convite</h2>
|
||||
<p class="text-gray-600 dark:text-gray-300 mb-4">Escolha a fonte para o nome da sua festa no convite:</p>
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4" id="font-selection-container">
|
||||
<!-- Font buttons will be loaded here by JavaScript -->
|
||||
</div>
|
||||
<button id="saveFontButton" class="mt-6 w-full bg-gradient-to-r from-primary to-secondary text-white py-3 rounded-xl font-semibold hover:shadow-lg transition-all duration-200 hover-scale">Salvar Fonte Escolhida</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ABA 6: CONTEÚDO DAS CONFIGURAÇÕES -->
|
||||
<div id="tab-settings" class="tab-content pt-6 hidden fade-in">
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-6">
|
||||
<!-- Coluna Principal (2/3) -->
|
||||
|
|
@ -550,6 +605,8 @@
|
|||
fetchStats();
|
||||
} else if (tabId === 'checkin-analytics' && !isCheckinChartInitialized) {
|
||||
initCheckinAnalyticsChart();
|
||||
} else if (tabId === 'customize-invite') {
|
||||
loadFontsAndRenderButtons();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -881,6 +938,82 @@
|
|||
attendanceChartInstance = new Chart(ctx, config);
|
||||
}
|
||||
}
|
||||
|
||||
// --- Lógica para seleção de fonte ---
|
||||
const availableFonts = [
|
||||
{ name: 'Birthstone-Regular', display: 'Birthstone Regular' },
|
||||
{ name: 'Ephesis-Regular', display: 'Ephesis Regular' },
|
||||
{ name: 'Montserrat-Regular', display: 'Montserrat Regular' },
|
||||
{ name: 'Radley-Regular', display: 'Radley Regular' }
|
||||
];
|
||||
let selectedFont = 'Montserrat-Regular'; // Default font
|
||||
|
||||
async function loadFontsAndRenderButtons() {
|
||||
const fontContainer = document.getElementById('font-selection-container');
|
||||
fontContainer.innerHTML = '<div class="col-span-full text-center py-4"><i class="fas fa-spinner fa-spin text-2xl text-primary"></i> Carregando fontes...</div>';
|
||||
|
||||
try {
|
||||
// Fetch current selected font for the party
|
||||
const response = await fetch(`${API_URL}/font_selection`);
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
if (data.selected_font) {
|
||||
selectedFont = data.selected_font;
|
||||
}
|
||||
}
|
||||
|
||||
fontContainer.innerHTML = ''; // Clear loading message
|
||||
|
||||
availableFonts.forEach(font => {
|
||||
const button = document.createElement('button');
|
||||
button.className = `font-button glass-effect text-primary dark:text-white ${selectedFont === font.name ? 'selected' : ''}`;
|
||||
button.style.fontFamily = `'${font.name}', sans-serif`;
|
||||
button.textContent = font.display;
|
||||
button.dataset.fontName = font.name;
|
||||
button.onclick = () => selectFont(font.name);
|
||||
fontContainer.appendChild(button);
|
||||
});
|
||||
|
||||
document.getElementById('saveFontButton').onclick = saveSelectedFont;
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error loading fonts or fetching selected font:', error);
|
||||
fontContainer.innerHTML = '<div class="col-span-full text-center py-4 text-danger">Erro ao carregar fontes.</div>';
|
||||
showToast('Erro ao carregar fontes.', 'error');
|
||||
}
|
||||
}
|
||||
|
||||
function selectFont(fontName) {
|
||||
selectedFont = fontName;
|
||||
document.querySelectorAll('.font-button').forEach(button => {
|
||||
if (button.dataset.fontName === fontName) {
|
||||
button.classList.add('selected');
|
||||
} else {
|
||||
button.classList.remove('selected');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async function saveSelectedFont() {
|
||||
try {
|
||||
const response = await fetch(`${API_URL}/font_selection`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({ font_name: selectedFont })
|
||||
});
|
||||
const data = await response.json();
|
||||
if (response.ok) {
|
||||
showToast(data.message, 'success');
|
||||
} else {
|
||||
throw new Error(data.message || 'Erro ao salvar a fonte.');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error saving font:', error);
|
||||
showToast(error.message, 'error');
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
{% endblock %}
|
||||
|
|
@ -20,6 +20,25 @@
|
|||
<script src="https://html2canvas.hertzen.com/dist/html2canvas.min.js" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/color-thief/2.3.0/color-thief.umd.js"></script>
|
||||
|
||||
<style>
|
||||
@font-face {
|
||||
font-family: 'Birthstone-Regular';
|
||||
src: url('{{ url_for('static', filename='fonts/Birthstone-Regular.ttf') }}') format('truetype');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Ephesis-Regular';
|
||||
src: url('{{ url_for('static', filename='fonts/Ephesis-Regular.ttf') }}') format('truetype');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Montserrat-Regular';
|
||||
src: url('{{ url_for('static', filename='fonts/Montserrat-Regular.ttf') }}') format('truetype');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Radley-Regular';
|
||||
src: url('{{ url_for('static', filename='fonts/Radley-Regular.ttf') }}') format('truetype');
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
tailwind.config = {
|
||||
darkMode: 'class',
|
||||
|
|
@ -78,7 +97,7 @@
|
|||
{% endif %}
|
||||
|
||||
<div class="p-6 sm:p-8">
|
||||
<h1 class="text-4xl font-extrabold text-gray-900 dark:text-white tracking-tight text-center">{{ party.name }}</h1>
|
||||
<h1 class="text-4xl font-extrabold text-gray-900 dark:text-white tracking-tight text-center" style="font-family: '{{ party.invite_font if party.invite_font else 'Montserrat-Regular' }}', sans-serif;">{{ party.name }}</h1>
|
||||
|
||||
<div class="mt-8 space-y-5 text-left border-t border-gray-200 dark:border-gray-700/50 pt-6">
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue