Compare commits
3 Commits
c233b020fc
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| eec72c77ab | |||
| 160ec2b48b | |||
| 7c57f939c0 |
14
Dockerfile
14
Dockerfile
@@ -1,11 +1,15 @@
|
||||
FROM python:3.12 AS builder
|
||||
ENV POETRY_HOME="/opt/poetry"
|
||||
ENV PATH="$POETRY_HOME/bin:$PATH"
|
||||
RUN apt update && \
|
||||
apt install -y gettext
|
||||
WORKDIR /app
|
||||
RUN curl -sSL https://install.python-poetry.org | python3 -
|
||||
COPY pyproject.toml poetry.lock README.md ./
|
||||
RUN poetry config virtualenvs.in-project true
|
||||
RUN poetry install --with app --no-root
|
||||
COPY locales ./locales
|
||||
RUN cd locales/ru/LC_MESSAGES && msgfmt messages.po
|
||||
|
||||
FROM node:24 AS node-builder
|
||||
ENV PATH=/app/node_modules/.bin:$PATH
|
||||
@@ -18,16 +22,12 @@ RUN npm run build
|
||||
FROM python:3.12-slim
|
||||
ENV PATH="/app/.venv/bin:$PATH"
|
||||
WORKDIR /app
|
||||
RUN apt update && \
|
||||
apt install -y locales gettext && \
|
||||
sed -i -e 's/# ru_RU.UTF-8 UTF-8/ru_RU.UTF-8 UTF-8/' /etc/locale.gen && \
|
||||
dpkg-reconfigure --frontend=noninteractive locales
|
||||
ENV LANG=ru_RU.UTF-8
|
||||
ENV LC_ALL=ru_RU.UTF-8
|
||||
|
||||
ENV TZ="Europe/Moscow"
|
||||
COPY --from=builder /app ./
|
||||
COPY --from=node-builder /app/dist ./static/dist
|
||||
COPY gallery gallery/
|
||||
RUN cd gallery/easel/route/view/locales/ru/LC_MESSAGES && msgfmt messages.po
|
||||
#COPY --from=builder /app/gallery/easel/route/view/locales /app/gallery/easel/route/view/locales
|
||||
COPY --from=builder --parents locales/**/*.mo ./
|
||||
|
||||
CMD ["uvicorn", "gallery.main:app", "--host", "0.0.0.0", "--port", "80", "--log-config", "gallery/logging.yaml"]
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
# API Gallery
|
||||
|
||||
Weather and TV program API
|
||||
|
||||

|
||||
|
||||
## View
|
||||
|
||||
https://api.shmyga.ru
|
||||
|
||||
BIN
docs/screenshot.png
Normal file
BIN
docs/screenshot.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 237 KiB |
@@ -1,5 +1,3 @@
|
||||
import locale as _locale
|
||||
|
||||
from fastapi import FastAPI
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
|
||||
@@ -9,11 +7,8 @@ from gallery.util import root_path
|
||||
from .route import api, doc
|
||||
from .route.view import router as view_router
|
||||
|
||||
DEFAULT_LOCALE = "ru_RU.UTF-8"
|
||||
|
||||
|
||||
def build_app(api_bundle: ApiBundle, *, locale: str = DEFAULT_LOCALE) -> FastAPI:
|
||||
_locale.setlocale(_locale.LC_TIME, locale)
|
||||
def build_app(api_bundle: ApiBundle) -> FastAPI:
|
||||
app = FastAPI(
|
||||
title="Gallery",
|
||||
docs_url=None,
|
||||
|
||||
@@ -32,10 +32,10 @@
|
||||
type="button"
|
||||
aria-expanded="false"
|
||||
data-bs-toggle="dropdown"
|
||||
aria-label="Select language (default)">
|
||||
<span class="me-2 language-icon-active">🇬🇧</span>
|
||||
aria-label="{{_('Select language')}} (default)">
|
||||
<span class="fi fi-gb me-2 language-icon-active"></span>
|
||||
<span class="d-lg-none ms-2"
|
||||
id="bd-language-text">Select language</span>
|
||||
id="bd-language-text">{{_("Select language")}}</span>
|
||||
</button>
|
||||
<ul class="dropdown-menu dropdown-menu-end"
|
||||
aria-labelledby="bd-language-text">
|
||||
@@ -44,8 +44,8 @@
|
||||
class="dropdown-item d-flex align-items-center"
|
||||
data-bs-language-value="en"
|
||||
aria-pressed="false">
|
||||
<span class="me-2 language-icon">🇬🇧</span>
|
||||
English
|
||||
<span class="fi fi-gb me-2 language-icon-active"></span>
|
||||
{{_("English")}}
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
@@ -53,8 +53,8 @@
|
||||
class="dropdown-item d-flex align-items-center"
|
||||
data-bs-language-value="ru"
|
||||
aria-pressed="false">
|
||||
<span class="me-2 language-icon">🇷🇺</span>
|
||||
Russian
|
||||
<span class="fi fi-ru me-2 language-icon-active"></span>
|
||||
{{_("Russian")}}
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
@@ -66,9 +66,9 @@
|
||||
aria-expanded="false"
|
||||
data-bs-toggle="dropdown"
|
||||
aria-label="Toggle theme (auto)">
|
||||
<i class="bi me-2 opacity-50 theme-icon-active"></i>
|
||||
<span class="bi bi-circle-half me-2 opacity-50 theme-icon-active"></span>
|
||||
<span class="d-lg-none ms-2"
|
||||
id="bd-theme-text">Toggle theme</span>
|
||||
id="bd-theme-text">{{_("Toggle theme")}}</span>
|
||||
</button>
|
||||
<ul class="dropdown-menu dropdown-menu-end"
|
||||
aria-labelledby="bd-theme-text">
|
||||
@@ -77,8 +77,8 @@
|
||||
class="dropdown-item d-flex align-items-center"
|
||||
data-bs-theme-value="light"
|
||||
aria-pressed="false">
|
||||
<i class="bi bi-sun-fill me-2 opacity-50 theme-icon"></i>
|
||||
Light
|
||||
<span class="bi bi-sun-fill me-2 opacity-50 theme-icon"></span>
|
||||
{{_("Light")}}
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
@@ -86,8 +86,8 @@
|
||||
class="dropdown-item d-flex align-items-center"
|
||||
data-bs-theme-value="dark"
|
||||
aria-pressed="false">
|
||||
<i class="bi bi-moon-stars-fill me-2 opacity-50 theme-icon"></i>
|
||||
Dark
|
||||
<span class="bi bi-moon-stars-fill me-2 opacity-50 theme-icon"></span>
|
||||
{{_("Dark")}}
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
@@ -95,8 +95,8 @@
|
||||
class="dropdown-item d-flex align-items-center active"
|
||||
data-bs-theme-value="auto"
|
||||
aria-pressed="true">
|
||||
<i class="bi bi-circle-half me-2 opacity-50 theme-icon"></i>
|
||||
Auto
|
||||
<span class="bi bi-circle-half me-2 opacity-50 theme-icon"></span>
|
||||
{{_("Auto")}}
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}Index{% endblock %}
|
||||
{% block title %}{{_("Index")}}{% endblock %}
|
||||
{% block head %}
|
||||
{{ super() }}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h1>View</h1>
|
||||
<h1>{{_("View")}}</h1>
|
||||
<div class="list-group mb-5">
|
||||
{% for section in sections %}
|
||||
<a href="{{section.link}}"
|
||||
@@ -18,7 +18,7 @@
|
||||
{% endfor %}
|
||||
</div>
|
||||
<hr class="col-3 col-md-2 mb-5">
|
||||
<h1>Docs</h1>
|
||||
<h1>{{_("Docs")}}</h1>
|
||||
<a href="/docs"
|
||||
target="_blank">
|
||||
<h4>Swagger</h4>
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Gallery\n"
|
||||
"Last-Translator: shmyga <shmyga.z@gmail.com>\n"
|
||||
"Language: ru\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
|
||||
|
||||
msgid "Weather"
|
||||
msgstr "Погода"
|
||||
|
||||
msgid "TV program"
|
||||
msgstr "Телепрограмма"
|
||||
@@ -1,6 +1,7 @@
|
||||
import datetime
|
||||
from pathlib import Path
|
||||
|
||||
from babel.dates import format_date
|
||||
from fastapi import APIRouter
|
||||
from fastapi.responses import HTMLResponse, RedirectResponse
|
||||
from fastapi.templating import Jinja2Templates
|
||||
@@ -20,7 +21,13 @@ templates = Jinja2Templates(
|
||||
base_dir / "templates",
|
||||
]
|
||||
)
|
||||
templates.env.globals.update({"_": _})
|
||||
templates.env.globals.update(
|
||||
{
|
||||
"_": _,
|
||||
"version": __version__,
|
||||
"format_date": format_date,
|
||||
}
|
||||
)
|
||||
templates.env.filters["timedelta_format"] = timedelta_format
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}
|
||||
Программа | {{response.channel.name}} | {{response.date.strftime('%a, %d %B %Y')}}
|
||||
{{_("TV program")}} | {{response.channel.name}} | {{format_date(response.date, 'E, d MMMM Y', locale=request.state.language)}}
|
||||
{% endblock %}
|
||||
|
||||
{% block header %}
|
||||
@@ -15,7 +15,7 @@
|
||||
href="../tag/{{tag_util.create_tag('day', response.date, -1)}}">⬅️</a>
|
||||
<a class="button"
|
||||
href="../..">⬆️</a>
|
||||
<span>{{response.channel.name}} | {{response.date.strftime('%a, %d %B %Y')}}</span>
|
||||
<span>{{response.channel.name}} | {{format_date(response.date, 'E, d MMMM Y', locale=request.state.language)}}</span>
|
||||
<a class="button"
|
||||
href="../tag/{{tag_util.create_tag('day', response.date, 1)}}">➡️</a>
|
||||
</h4>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}
|
||||
{{'Прямые трансляции' if live else _("TV program")}} | {{response.date.strftime('%a, %d %B %Y')}}
|
||||
{{_("Live broadcasts") if live else _("TV program")}} | {{format_date(response.date, 'E, d MMMM Y', locale=request.state.language)}}
|
||||
{% endblock %}
|
||||
|
||||
{% block header %}
|
||||
@@ -15,7 +15,7 @@
|
||||
href="../tag/{{tag_util.create_tag('day', response.date, -1)}}">⬅️</a>
|
||||
<a class="button"
|
||||
href="..">⬆️</a>
|
||||
<span>{{'Прямые трансляции' if live else 'Программа'}} | {{response.date.strftime('%a, %d %B %Y')}}</span>
|
||||
<span>{{_("Live broadcasts") if live else _("TV program")}} | {{format_date(response.date, 'E, d MMMM Y', locale=request.state.language)}}</span>
|
||||
<a class="button"
|
||||
href="../tag/{{tag_util.create_tag('day', response.date, 1)}}">➡️</a>
|
||||
</h4>
|
||||
@@ -36,8 +36,6 @@
|
||||
<td colspan="3">
|
||||
<div>{{response.channel.name}}</div>
|
||||
</td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
{% for value in values %}
|
||||
<tr class="{{'table-success' if not live and value.live else ''}}">
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import gettext
|
||||
from contextvars import ContextVar
|
||||
from pathlib import Path
|
||||
|
||||
from fastapi import Cookie, Header, Request
|
||||
|
||||
from gallery.util import root_path
|
||||
|
||||
_translation: ContextVar[gettext.GNUTranslations | gettext.NullTranslations] = (
|
||||
ContextVar("translation")
|
||||
)
|
||||
@@ -19,7 +20,7 @@ async def set_language(
|
||||
|
||||
try:
|
||||
t = gettext.translation(
|
||||
"messages", localedir=Path(__file__).parent / "locales", languages=[lang]
|
||||
"messages", localedir=root_path / "locales", languages=[lang]
|
||||
)
|
||||
except FileNotFoundError:
|
||||
t = gettext.NullTranslations()
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import datetime
|
||||
from pathlib import Path
|
||||
|
||||
from fastapi import APIRouter, FastAPI
|
||||
from fastapi import APIRouter
|
||||
from fastapi.responses import HTMLResponse, RedirectResponse
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
from fastapi.templating import Jinja2Templates
|
||||
|
||||
from babel.dates import format_date
|
||||
from gallery.easel.core import AppRequest
|
||||
from gallery.sketch.weather.model import WeatherResponse
|
||||
from gallery.version import __version__
|
||||
@@ -22,7 +21,13 @@ templates = Jinja2Templates(
|
||||
base_dir / "templates",
|
||||
]
|
||||
)
|
||||
templates.env.globals.update({"_": _})
|
||||
templates.env.globals.update(
|
||||
{
|
||||
"_": _,
|
||||
"version": __version__,
|
||||
"format_date": format_date,
|
||||
}
|
||||
)
|
||||
templates.env.filters["wind_direction_icon"] = wind_direction_icon
|
||||
templates.env.filters["cloudness_icon"] = cloudness_icon
|
||||
|
||||
@@ -32,15 +37,16 @@ def build_weather_response(request: AppRequest, response: WeatherResponse):
|
||||
request=request,
|
||||
name="weather.html",
|
||||
context={
|
||||
"version": __version__,
|
||||
"tag_util": TagUtil,
|
||||
"datetime": datetime,
|
||||
"response": response,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.get("/weather", response_class=HTMLResponse)
|
||||
async def get_weather_index(request: AppRequest, query: str | None = None):
|
||||
weather_api = request.app.state.api.weather
|
||||
@@ -49,7 +55,6 @@ async def get_weather_index(request: AppRequest, query: str | None = None):
|
||||
request=request,
|
||||
name="index.html",
|
||||
context={
|
||||
"version": __version__,
|
||||
"locations": locations,
|
||||
},
|
||||
)
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}Weather{% endblock %}
|
||||
{% block title %}{{_("Weather")}}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h1>Weather</h1>
|
||||
<h1>{{_("Weather")}}</h1>
|
||||
<form action=""
|
||||
method="get"
|
||||
class="mb-4">
|
||||
@@ -11,9 +11,9 @@
|
||||
class="form-control"
|
||||
id="query"
|
||||
name="query"
|
||||
placeholder="Enter the city name">
|
||||
placeholder="{{_('Enter the city name')}}">
|
||||
<button class="btn btn-primary"
|
||||
type="submit">Search</button>
|
||||
type="submit">{{_("Search")}}</button>
|
||||
</div>
|
||||
</form>
|
||||
<ul id="locations"
|
||||
@@ -22,6 +22,7 @@
|
||||
<a href="weather/{{location.id}}"
|
||||
class="list-group-item list-group-item-action px-4"
|
||||
onclick="saveLocation({id:'{{location.id}}', name:'{{location.name}}'});">
|
||||
<span class="fi fi-{{location.country_code}} me-1"></span>
|
||||
<span class="text-primary">{{location.name}}</span>
|
||||
<span class="small ms-1 text-secondary">
|
||||
{{location.country}}, {{location.district}}, {{location.subdistrict}}
|
||||
|
||||
@@ -13,12 +13,12 @@
|
||||
href="../tag/{{tag_util.create_tag('day', response.date, -1)}}">⬅️</a>
|
||||
<a class="button"
|
||||
href="../tag/days-10">⬆️</a>
|
||||
<span>{{response.location}} | {{response.date.strftime('%a, %d %B %Y')}}</span>
|
||||
<span>{{response.location}} | {{format_date(response.date, 'E, d MMMM Y', locale=request.state.language)}}</span>
|
||||
<a class="button"
|
||||
href="../tag/{{tag_util.create_tag('day', response.date, 1)}}">➡️</a>
|
||||
{% endif %}
|
||||
{% if response.period == 'days' %}
|
||||
<span>{{response.location}} | {{response.date.strftime('%a, %d %B %Y')}}</span>
|
||||
<span>{{response.location}} | {{format_date(response.date, 'E, d MMMM Y', locale=request.state.language)}}</span>
|
||||
{% endif %}
|
||||
</h4>
|
||||
<div class="table-responsive">
|
||||
@@ -36,9 +36,9 @@
|
||||
{% endif %}
|
||||
{% if response.period == 'days' %}
|
||||
<td class="date {{'now' if value.date.date() == datetime.date.today() else ''}}">
|
||||
<span class="value">
|
||||
<span class="value {{'text-danger' if value.date.weekday() in [5,6] else ''}}">
|
||||
<a href="../tag/{{tag_util.create_tag('day', value.date.date())}}">
|
||||
{{value.date.strftime('%a %d')}}
|
||||
{{format_date(value.date, 'E d', locale=request.state.language)}}
|
||||
</a>
|
||||
</span>
|
||||
</td>
|
||||
@@ -49,7 +49,7 @@
|
||||
<tr>
|
||||
<td colspan="{{response.values | length}}"
|
||||
class="header">
|
||||
Облачность
|
||||
{{_("Cloudiness")}}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -65,7 +65,7 @@
|
||||
<tr>
|
||||
<td colspan="{{response.values | length}}"
|
||||
class="header">
|
||||
Температура, °C
|
||||
{{_("Temperature, °C")}}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -84,7 +84,7 @@
|
||||
<tr>
|
||||
<td colspan="{{response.values | length}}"
|
||||
class="header">
|
||||
Направление ветра
|
||||
{{_("Wind direction")}}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -98,7 +98,7 @@
|
||||
<tr>
|
||||
<td colspan="{{response.values | length}}"
|
||||
class="header">
|
||||
Скорость ветра, м/с
|
||||
{{_("Wind speed, m/s")}}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -118,7 +118,7 @@
|
||||
<tr>
|
||||
<td colspan="{{response.values | length}}"
|
||||
class="header">
|
||||
Осадки, мм
|
||||
{{_("Precipitation, mm")}}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -133,7 +133,7 @@
|
||||
<tr>
|
||||
<td colspan="{{response.values | length}}"
|
||||
class="header">
|
||||
Давление, мм рт. ст.
|
||||
{{_("Pressure, mmHg")}}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -151,7 +151,7 @@
|
||||
<tr>
|
||||
<td colspan="{{response.values | length}}"
|
||||
class="header">
|
||||
Влажность, %
|
||||
{{_("Humidity, %")}}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
|
||||
@@ -87,6 +87,7 @@ class GismeteoApi(WeatherApi):
|
||||
lat=item["coordinates"]["latitude"],
|
||||
lon=item["coordinates"]["longitude"],
|
||||
country=item["translations"]["kk"]["country"]["name"],
|
||||
country_code=item["country"]["code"].lower(),
|
||||
district=item["translations"]["kk"]["district"]["name"],
|
||||
subdistrict=(
|
||||
item["translations"]["kk"]["subdistrict"]["name"]
|
||||
|
||||
@@ -15,6 +15,7 @@ class Location(Model):
|
||||
lat: float
|
||||
lon: float
|
||||
country: str
|
||||
country_code: str
|
||||
district: str
|
||||
subdistrict: str
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
__version__ = "0.2.1"
|
||||
__version__ = "0.2.2"
|
||||
|
||||
77
locales/ru/LC_MESSAGES/messages.po
Normal file
77
locales/ru/LC_MESSAGES/messages.po
Normal file
@@ -0,0 +1,77 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Gallery\n"
|
||||
"Last-Translator: shmyga <shmyga.z@gmail.com>\n"
|
||||
"Language: ru\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
|
||||
|
||||
msgid "Index"
|
||||
msgstr "Содержание"
|
||||
|
||||
msgid "View"
|
||||
msgstr "Просмотр"
|
||||
|
||||
msgid "Docs"
|
||||
msgstr "Документация"
|
||||
|
||||
msgid "Toggle theme"
|
||||
msgstr "Переключить тему"
|
||||
|
||||
msgid "Light"
|
||||
msgstr "Светлая"
|
||||
|
||||
msgid "Dark"
|
||||
msgstr "Тёмная"
|
||||
|
||||
msgid "Auto"
|
||||
msgstr "Авто"
|
||||
|
||||
msgid "Select language"
|
||||
msgstr "Выберите язык"
|
||||
|
||||
msgid "English"
|
||||
msgstr "Английский"
|
||||
|
||||
msgid "Russian"
|
||||
msgstr "Русский"
|
||||
|
||||
# weather
|
||||
msgid "Weather"
|
||||
msgstr "Погода"
|
||||
|
||||
msgid "Enter the city name"
|
||||
msgstr "Введите название города"
|
||||
|
||||
msgid "Search"
|
||||
msgstr "Поиск"
|
||||
|
||||
msgid "Cloudiness"
|
||||
msgstr "Облачность"
|
||||
|
||||
msgid "Temperature, °C"
|
||||
msgstr "Температура, °C"
|
||||
|
||||
msgid "Wind direction"
|
||||
msgstr "Направление ветра"
|
||||
|
||||
msgid "Wind speed, m/s"
|
||||
msgstr "Скорость ветра, м/с"
|
||||
|
||||
msgid "Precipitation, mm"
|
||||
msgstr "Осадки, мм"
|
||||
|
||||
msgid "Pressure, mmHg"
|
||||
msgstr "Давление, мм рт. ст."
|
||||
|
||||
msgid "Humidity, %"
|
||||
msgstr "Влажность, %"
|
||||
|
||||
# tv
|
||||
msgid "TV program"
|
||||
msgstr "Телепрограмма"
|
||||
|
||||
msgid "Live broadcasts"
|
||||
msgstr "Прямые трансляции"
|
||||
17
poetry.lock
generated
17
poetry.lock
generated
@@ -196,6 +196,21 @@ tests = ["attrs[tests-no-zope]", "zope-interface"]
|
||||
tests-mypy = ["mypy (>=1.6) ; platform_python_implementation == \"CPython\" and python_version >= \"3.8\"", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.8\""]
|
||||
tests-no-zope = ["attrs[tests-mypy]", "cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "pympler", "pytest (>=4.3.0)", "pytest-xdist[psutil]"]
|
||||
|
||||
[[package]]
|
||||
name = "babel"
|
||||
version = "2.18.0"
|
||||
description = "Internationalization utilities"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
groups = ["app"]
|
||||
files = [
|
||||
{file = "babel-2.18.0-py3-none-any.whl", hash = "sha256:e2b422b277c2b9a9630c1d7903c2a00d0830c409c59ac8cae9081c92f1aeba35"},
|
||||
{file = "babel-2.18.0.tar.gz", hash = "sha256:b80b99a14bd085fcacfa15c9165f651fbb3406e66cc603abf11c5750937c992d"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
dev = ["backports.zoneinfo ; python_version < \"3.9\"", "freezegun (>=1.0,<2.0)", "jinja2 (>=3.0)", "pytest (>=6.0)", "pytest-cov", "pytz", "setuptools", "tzdata ; sys_platform == \"win32\""]
|
||||
|
||||
[[package]]
|
||||
name = "beautifulsoup4"
|
||||
version = "4.12.3"
|
||||
@@ -1898,4 +1913,4 @@ multidict = ">=4.0"
|
||||
[metadata]
|
||||
lock-version = "2.1"
|
||||
python-versions = "^3.12"
|
||||
content-hash = "7295e9ec7f7492017c5bbda489026f19bbf155f0ea82402d348b0aa4c03beaca"
|
||||
content-hash = "39107ded0683832d1ca3d637dc50fac7dd441375ead30cdb56d64edee13c97e2"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[tool.poetry]
|
||||
name = "gallery"
|
||||
version = "0.2.1"
|
||||
version = "0.2.2"
|
||||
description = ""
|
||||
authors = ["shmyga <shmyga.z@gmail.com>"]
|
||||
readme = "README.md"
|
||||
@@ -17,6 +17,7 @@ aiocache = {extras = ["redis"], version = "^0.12.2"}
|
||||
[tool.poetry.group.app.dependencies]
|
||||
fastapi = "^0.111.1"
|
||||
jinja2 = "^3.1.4"
|
||||
babel = "^2.18.0"
|
||||
|
||||
[tool.poetry.group.test.dependencies]
|
||||
pytest = "^8.3.1"
|
||||
|
||||
@@ -2,5 +2,5 @@
|
||||
set -e
|
||||
cd "$(dirname $(dirname "$0"))" || exit
|
||||
|
||||
cd gallery/easel/route/view/locales/ru/LC_MESSAGES || exit
|
||||
cd locales/ru/LC_MESSAGES || exit
|
||||
msgfmt messages.po
|
||||
15
static/package-lock.json
generated
15
static/package-lock.json
generated
@@ -1,17 +1,18 @@
|
||||
{
|
||||
"name": "gallery-static",
|
||||
"version": "0.2.1",
|
||||
"name": "gallery",
|
||||
"version": "0.2.2",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "gallery-static",
|
||||
"version": "0.2.1",
|
||||
"name": "gallery",
|
||||
"version": "0.2.2",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@popperjs/core": "^2.11.8",
|
||||
"bootstrap": "^5.3.8",
|
||||
"bootstrap-icons": "^1.13.1",
|
||||
"flag-icons": "^7.5.0",
|
||||
"sass": "^1.99.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -699,6 +700,12 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/flag-icons": {
|
||||
"version": "7.5.0",
|
||||
"resolved": "https://registry.npmjs.org/flag-icons/-/flag-icons-7.5.0.tgz",
|
||||
"integrity": "sha512-kd+MNXviFIg5hijH766tt+3x76ele1AXlo4zDdCxIvqWZhKt4T83bOtxUOOMlTx/EcFdUMH5yvQgYlFh1EqqFg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/fsevents": {
|
||||
"version": "2.3.3",
|
||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "gallery",
|
||||
"version": "0.2.1",
|
||||
"version": "0.2.2",
|
||||
"scripts": {
|
||||
"build": "vite build",
|
||||
"dev": "vite build --watch"
|
||||
@@ -12,6 +12,7 @@
|
||||
"@popperjs/core": "^2.11.8",
|
||||
"bootstrap": "^5.3.8",
|
||||
"bootstrap-icons": "^1.13.1",
|
||||
"flag-icons": "^7.5.0",
|
||||
"sass": "^1.99.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -7,7 +7,7 @@ class AppLinkElement extends HTMLElement {
|
||||
<a href="${this.getAttribute("href")}"
|
||||
class="d-flex align-items-center text-body text-decoration-none">
|
||||
<span class="fs-4">
|
||||
<i class="bi bi-${this.getAttribute("icon")}"></i>
|
||||
<span class="bi bi-${this.getAttribute("icon")} me-1"></span>
|
||||
<span>${this.textContent}</span>
|
||||
</span>
|
||||
</a>`;
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
const languageSwitcherText = document.querySelector("#bd-language-text");
|
||||
const activeLanguageIcon = document.querySelector(".language-icon-active");
|
||||
const btnToActive = document.querySelector(`[data-bs-language-value="${language}"]`);
|
||||
const activeLanguageIconContent = btnToActive?.querySelector("span")?.textContent;
|
||||
const activeLanguageIconClass = btnToActive.querySelector(".fi").className.match(/fi-[\w-]+/)[0];
|
||||
|
||||
document.querySelectorAll("[data-bs-language-value]").forEach((element) => {
|
||||
element.classList.remove("active");
|
||||
@@ -37,7 +37,9 @@
|
||||
|
||||
btnToActive.classList.add("active");
|
||||
btnToActive.setAttribute("aria-pressed", "true");
|
||||
activeLanguageIcon.textContent = activeLanguageIconContent;
|
||||
const classesToRemove = Array.from(activeLanguageIcon.classList).filter((className) => className.startsWith("fi-"));
|
||||
activeLanguageIcon.classList.remove(...classesToRemove);
|
||||
activeLanguageIcon.classList.add(activeLanguageIconClass);
|
||||
const languageSwitcherLabel = `${languageSwitcherText.textContent} (${btnToActive.dataset.bsLanguageValue})`;
|
||||
languageSwitcher.setAttribute("aria-label", languageSwitcherLabel);
|
||||
|
||||
|
||||
40
static/src/lib/bootstrap-icons.scss
vendored
Normal file
40
static/src/lib/bootstrap-icons.scss
vendored
Normal file
@@ -0,0 +1,40 @@
|
||||
//$bootstrap-icons: "circle-half", "moon-stars-fill", "brightness-high", "gear", "sun-fill", "tv";
|
||||
|
||||
.bi {
|
||||
display: inline-block;
|
||||
text-transform: none;
|
||||
line-height: 1;
|
||||
vertical-align: -0.125em;
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
|
||||
mask-size: contain;
|
||||
mask-position: 50%;
|
||||
mask-repeat: no-repeat;
|
||||
background-color: currentColor;
|
||||
|
||||
//@each $icon in $bootstrap-icons {
|
||||
// &.bi-#{$icon} {
|
||||
// mask-image: url("bootstrap-icons/icons/#{$icon}.svg");
|
||||
// }
|
||||
///
|
||||
|
||||
&.bi-circle-half {
|
||||
mask-image: url(bootstrap-icons/icons/circle-half.svg);
|
||||
}
|
||||
&.bi-moon-stars-fill {
|
||||
mask-image: url(bootstrap-icons/icons/moon-stars-fill.svg);
|
||||
}
|
||||
&.bi-brightness-high {
|
||||
mask-image: url(bootstrap-icons/icons/brightness-high.svg);
|
||||
}
|
||||
&.bi-gear {
|
||||
mask-image: url(bootstrap-icons/icons/gear.svg);
|
||||
}
|
||||
&.bi-sun-fill {
|
||||
mask-image: url(bootstrap-icons/icons/sun-fill.svg);
|
||||
}
|
||||
&.bi-tv {
|
||||
mask-image: url(bootstrap-icons/icons/tv.svg);
|
||||
}
|
||||
}
|
||||
52
static/src/lib/bootstrap.scss
vendored
Normal file
52
static/src/lib/bootstrap.scss
vendored
Normal file
@@ -0,0 +1,52 @@
|
||||
@import "bootstrap/scss/mixins/banner";
|
||||
@include bsBanner("");
|
||||
|
||||
|
||||
// scss-docs-start import-stack
|
||||
// Configuration
|
||||
@import "bootstrap/scss/functions";
|
||||
@import "bootstrap/scss/variables";
|
||||
@import "bootstrap/scss/variables-dark";
|
||||
@import "bootstrap/scss/maps";
|
||||
@import "bootstrap/scss/mixins";
|
||||
@import "bootstrap/scss/utilities";
|
||||
|
||||
// Layout & components
|
||||
@import "bootstrap/scss/root";
|
||||
@import "bootstrap/scss/reboot";
|
||||
@import "bootstrap/scss/type";
|
||||
//@import "bootstrap/scss/images";
|
||||
//@import "bootstrap/scss/containers";
|
||||
@import "bootstrap/scss/grid";
|
||||
@import "bootstrap/scss/tables";
|
||||
@import "bootstrap/scss/forms";
|
||||
@import "bootstrap/scss/buttons";
|
||||
//@import "bootstrap/scss/transitions";
|
||||
@import "bootstrap/scss/dropdown";
|
||||
//@import "bootstrap/scss/button-group";
|
||||
@import "bootstrap/scss/nav";
|
||||
@import "bootstrap/scss/navbar";
|
||||
//@import "bootstrap/scss/card";
|
||||
//@import "bootstrap/scss/accordion";
|
||||
//@import "bootstrap/scss/breadcrumb";
|
||||
//@import "bootstrap/scss/pagination";
|
||||
//@import "bootstrap/scss/badge";
|
||||
//@import "bootstrap/scss/alert";
|
||||
//@import "bootstrap/scss/progress";
|
||||
@import "bootstrap/scss/list-group";
|
||||
//@import "bootstrap/scss/close";
|
||||
//@import "bootstrap/scss/toasts";
|
||||
//@import "bootstrap/scss/modal";
|
||||
//@import "bootstrap/scss/tooltip";
|
||||
//@import "bootstrap/scss/popover";
|
||||
//@import "bootstrap/scss/carousel";
|
||||
//@import "bootstrap/scss/spinners";
|
||||
//@import "bootstrap/scss/offcanvas";
|
||||
//@import "bootstrap/scss/placeholders";
|
||||
|
||||
// Helpers
|
||||
@import "bootstrap/scss/helpers";
|
||||
|
||||
// Utilities
|
||||
@import "bootstrap/scss/utilities/api";
|
||||
// scss-docs-end import-stack
|
||||
@@ -1,6 +1,10 @@
|
||||
@import "bootstrap/scss/bootstrap";
|
||||
$bootstrap-icons-font-dir: "bootstrap-icons/font/fonts";
|
||||
@import "bootstrap-icons/font/bootstrap-icons";
|
||||
@use "flag-icons/sass/flag-icons" with (
|
||||
$flag-icons-path: "flag-icons/flags",
|
||||
$flag-icons-included-countries: ("gb", "ru", "by", "ua", "kz")
|
||||
);
|
||||
|
||||
@import "./lib/bootstrap";
|
||||
@import "./lib/bootstrap-icons";
|
||||
|
||||
@import "./widget.scss";
|
||||
@import "./weather.scss";
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
const themeSwitcherText = document.querySelector("#bd-theme-text");
|
||||
const activeThemeIcon = document.querySelector(".theme-icon-active");
|
||||
const btnToActive = document.querySelector(`[data-bs-theme-value="${theme}"]`);
|
||||
const activeThemeIconClass = btnToActive.querySelector("i.bi").className.match(/bi-[\w-]+/)[0];
|
||||
const activeThemeIconClass = btnToActive.querySelector(".bi").className.match(/bi-[\w-]+/)[0];
|
||||
|
||||
document.querySelectorAll("[data-bs-theme-value]").forEach((element) => {
|
||||
element.classList.remove("active");
|
||||
|
||||
Reference in New Issue
Block a user