Compare commits
6 Commits
c233b020fc
...
0.2.3
| Author | SHA1 | Date | |
|---|---|---|---|
| 91e2c9d123 | |||
| 91d9c37612 | |||
| b5f2c272bb | |||
| eec72c77ab | |||
| 160ec2b48b | |||
| 7c57f939c0 |
9
.vscode/settings.json
vendored
Normal file
9
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"python-envs.pythonProjects": [
|
||||||
|
{
|
||||||
|
"path": ".",
|
||||||
|
"envManager": "ms-python.python:poetry",
|
||||||
|
"packageManager": "ms-python.python:poetry"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
14
Dockerfile
14
Dockerfile
@@ -1,11 +1,15 @@
|
|||||||
FROM python:3.12 AS builder
|
FROM python:3.12 AS builder
|
||||||
ENV POETRY_HOME="/opt/poetry"
|
ENV POETRY_HOME="/opt/poetry"
|
||||||
ENV PATH="$POETRY_HOME/bin:$PATH"
|
ENV PATH="$POETRY_HOME/bin:$PATH"
|
||||||
|
RUN apt update && \
|
||||||
|
apt install -y gettext
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
RUN curl -sSL https://install.python-poetry.org | python3 -
|
RUN curl -sSL https://install.python-poetry.org | python3 -
|
||||||
COPY pyproject.toml poetry.lock README.md ./
|
COPY pyproject.toml poetry.lock README.md ./
|
||||||
RUN poetry config virtualenvs.in-project true
|
RUN poetry config virtualenvs.in-project true
|
||||||
RUN poetry install --with app --no-root
|
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
|
FROM node:24 AS node-builder
|
||||||
ENV PATH=/app/node_modules/.bin:$PATH
|
ENV PATH=/app/node_modules/.bin:$PATH
|
||||||
@@ -18,16 +22,12 @@ RUN npm run build
|
|||||||
FROM python:3.12-slim
|
FROM python:3.12-slim
|
||||||
ENV PATH="/app/.venv/bin:$PATH"
|
ENV PATH="/app/.venv/bin:$PATH"
|
||||||
WORKDIR /app
|
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"
|
ENV TZ="Europe/Moscow"
|
||||||
COPY --from=builder /app ./
|
COPY --from=builder /app ./
|
||||||
COPY --from=node-builder /app/dist ./static/dist
|
COPY --from=node-builder /app/dist ./static/dist
|
||||||
COPY gallery gallery/
|
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"]
|
CMD ["uvicorn", "gallery.main:app", "--host", "0.0.0.0", "--port", "80", "--log-config", "gallery/logging.yaml"]
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
# API Gallery
|
# API Gallery
|
||||||
|
|
||||||
|
Weather and TV program API
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
## View
|
## View
|
||||||
|
|
||||||
https://api.shmyga.ru
|
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 import FastAPI
|
||||||
from fastapi.staticfiles import StaticFiles
|
from fastapi.staticfiles import StaticFiles
|
||||||
|
|
||||||
@@ -9,11 +7,8 @@ from gallery.util import root_path
|
|||||||
from .route import api, doc
|
from .route import api, doc
|
||||||
from .route.view import router as view_router
|
from .route.view import router as view_router
|
||||||
|
|
||||||
DEFAULT_LOCALE = "ru_RU.UTF-8"
|
|
||||||
|
|
||||||
|
def build_app(api_bundle: ApiBundle) -> FastAPI:
|
||||||
def build_app(api_bundle: ApiBundle, *, locale: str = DEFAULT_LOCALE) -> FastAPI:
|
|
||||||
_locale.setlocale(_locale.LC_TIME, locale)
|
|
||||||
app = FastAPI(
|
app = FastAPI(
|
||||||
title="Gallery",
|
title="Gallery",
|
||||||
docs_url=None,
|
docs_url=None,
|
||||||
|
|||||||
@@ -21,10 +21,12 @@
|
|||||||
|
|
||||||
<body>
|
<body>
|
||||||
<div class="app col-lg-8 mx-auto p-3 py-md-5">
|
<div class="app col-lg-8 mx-auto p-3 py-md-5">
|
||||||
<header class="d-flex align-items-center pb-3 mb-5 border-bottom">
|
<header class="app-header pb-3 mb-5 border-bottom">
|
||||||
<app-link href="/"
|
<div class="link-list">
|
||||||
icon="gear">API Gallery</app-link>
|
<app-link href="/"
|
||||||
{% block header %}{% endblock %}
|
icon="gear">API Gallery</app-link>
|
||||||
|
{% block header %}{% endblock %}
|
||||||
|
</div>
|
||||||
<ul class="navbar-nav flex-row flex-wrap ms-md-auto">
|
<ul class="navbar-nav flex-row flex-wrap ms-md-auto">
|
||||||
<li class="nav-item dropdown">
|
<li class="nav-item dropdown">
|
||||||
<button class="btn btn-link nav-link py-2 px-0 px-lg-2 dropdown-toggle d-flex align-items-center"
|
<button class="btn btn-link nav-link py-2 px-0 px-lg-2 dropdown-toggle d-flex align-items-center"
|
||||||
@@ -32,10 +34,10 @@
|
|||||||
type="button"
|
type="button"
|
||||||
aria-expanded="false"
|
aria-expanded="false"
|
||||||
data-bs-toggle="dropdown"
|
data-bs-toggle="dropdown"
|
||||||
aria-label="Select language (default)">
|
aria-label="{{_('Select language')}} (default)">
|
||||||
<span class="me-2 language-icon-active">🇬🇧</span>
|
<span class="fi fir fi-gb me-2 language-icon-active"></span>
|
||||||
<span class="d-lg-none ms-2"
|
<span class="d-lg-none ms-2"
|
||||||
id="bd-language-text">Select language</span>
|
id="bd-language-text">{{_("Select language")}}</span>
|
||||||
</button>
|
</button>
|
||||||
<ul class="dropdown-menu dropdown-menu-end"
|
<ul class="dropdown-menu dropdown-menu-end"
|
||||||
aria-labelledby="bd-language-text">
|
aria-labelledby="bd-language-text">
|
||||||
@@ -44,8 +46,8 @@
|
|||||||
class="dropdown-item d-flex align-items-center"
|
class="dropdown-item d-flex align-items-center"
|
||||||
data-bs-language-value="en"
|
data-bs-language-value="en"
|
||||||
aria-pressed="false">
|
aria-pressed="false">
|
||||||
<span class="me-2 language-icon">🇬🇧</span>
|
<span class="fi fir fi-gb me-2 language-icon-active"></span>
|
||||||
English
|
{{_("English")}}
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
@@ -53,8 +55,8 @@
|
|||||||
class="dropdown-item d-flex align-items-center"
|
class="dropdown-item d-flex align-items-center"
|
||||||
data-bs-language-value="ru"
|
data-bs-language-value="ru"
|
||||||
aria-pressed="false">
|
aria-pressed="false">
|
||||||
<span class="me-2 language-icon">🇷🇺</span>
|
<span class="fi fir fi-ru me-2 language-icon-active"></span>
|
||||||
Russian
|
{{_("Russian")}}
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
@@ -66,9 +68,9 @@
|
|||||||
aria-expanded="false"
|
aria-expanded="false"
|
||||||
data-bs-toggle="dropdown"
|
data-bs-toggle="dropdown"
|
||||||
aria-label="Toggle theme (auto)">
|
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"
|
<span class="d-lg-none ms-2"
|
||||||
id="bd-theme-text">Toggle theme</span>
|
id="bd-theme-text">{{_("Toggle theme")}}</span>
|
||||||
</button>
|
</button>
|
||||||
<ul class="dropdown-menu dropdown-menu-end"
|
<ul class="dropdown-menu dropdown-menu-end"
|
||||||
aria-labelledby="bd-theme-text">
|
aria-labelledby="bd-theme-text">
|
||||||
@@ -77,8 +79,8 @@
|
|||||||
class="dropdown-item d-flex align-items-center"
|
class="dropdown-item d-flex align-items-center"
|
||||||
data-bs-theme-value="light"
|
data-bs-theme-value="light"
|
||||||
aria-pressed="false">
|
aria-pressed="false">
|
||||||
<i class="bi bi-sun-fill me-2 opacity-50 theme-icon"></i>
|
<span class="bi bi-sun-fill me-2 opacity-50 theme-icon"></span>
|
||||||
Light
|
{{_("Light")}}
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
@@ -86,8 +88,8 @@
|
|||||||
class="dropdown-item d-flex align-items-center"
|
class="dropdown-item d-flex align-items-center"
|
||||||
data-bs-theme-value="dark"
|
data-bs-theme-value="dark"
|
||||||
aria-pressed="false">
|
aria-pressed="false">
|
||||||
<i class="bi bi-moon-stars-fill me-2 opacity-50 theme-icon"></i>
|
<span class="bi bi-moon-stars-fill me-2 opacity-50 theme-icon"></span>
|
||||||
Dark
|
{{_("Dark")}}
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
@@ -95,8 +97,8 @@
|
|||||||
class="dropdown-item d-flex align-items-center active"
|
class="dropdown-item d-flex align-items-center active"
|
||||||
data-bs-theme-value="auto"
|
data-bs-theme-value="auto"
|
||||||
aria-pressed="true">
|
aria-pressed="true">
|
||||||
<i class="bi bi-circle-half me-2 opacity-50 theme-icon"></i>
|
<span class="bi bi-circle-half me-2 opacity-50 theme-icon"></span>
|
||||||
Auto
|
{{_("Auto")}}
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
{% block title %}Index{% endblock %}
|
{% block title %}{{_("Index")}}{% endblock %}
|
||||||
{% block head %}
|
{% block head %}
|
||||||
{{ super() }}
|
{{ super() }}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<h1>View</h1>
|
<h1>{{_("View")}}</h1>
|
||||||
<div class="list-group mb-5">
|
<div class="list-group mb-5">
|
||||||
{% for section in sections %}
|
{% for section in sections %}
|
||||||
<a href="{{section.link}}"
|
<a href="{{section.link}}"
|
||||||
@@ -18,7 +18,7 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
<hr class="col-3 col-md-2 mb-5">
|
<hr class="col-3 col-md-2 mb-5">
|
||||||
<h1>Docs</h1>
|
<h1>{{_("Docs")}}</h1>
|
||||||
<a href="/docs"
|
<a href="/docs"
|
||||||
target="_blank">
|
target="_blank">
|
||||||
<h4>Swagger</h4>
|
<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
|
import datetime
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
|
from babel.dates import format_date
|
||||||
from fastapi import APIRouter
|
from fastapi import APIRouter
|
||||||
from fastapi.responses import HTMLResponse, RedirectResponse
|
from fastapi.responses import HTMLResponse, RedirectResponse
|
||||||
from fastapi.templating import Jinja2Templates
|
from fastapi.templating import Jinja2Templates
|
||||||
@@ -20,7 +21,13 @@ templates = Jinja2Templates(
|
|||||||
base_dir / "templates",
|
base_dir / "templates",
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
templates.env.globals.update({"_": _})
|
templates.env.globals.update(
|
||||||
|
{
|
||||||
|
"_": _,
|
||||||
|
"version": __version__,
|
||||||
|
"format_date": format_date,
|
||||||
|
}
|
||||||
|
)
|
||||||
templates.env.filters["timedelta_format"] = timedelta_format
|
templates.env.filters["timedelta_format"] = timedelta_format
|
||||||
|
|
||||||
router = APIRouter()
|
router = APIRouter()
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
{% block title %}
|
{% 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 %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block header %}
|
{% block header %}
|
||||||
<span class="fs-4 text-body ms-2 me-2">/</span>
|
|
||||||
<app-link href="/schedule"
|
<app-link href="/schedule"
|
||||||
icon="tv">{{_("TV program")}}</app-link>
|
icon="tv">{{_("TV program")}}</app-link>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@@ -15,7 +14,7 @@
|
|||||||
href="../tag/{{tag_util.create_tag('day', response.date, -1)}}">⬅️</a>
|
href="../tag/{{tag_util.create_tag('day', response.date, -1)}}">⬅️</a>
|
||||||
<a class="button"
|
<a class="button"
|
||||||
href="../..">⬆️</a>
|
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"
|
<a class="button"
|
||||||
href="../tag/{{tag_util.create_tag('day', response.date, 1)}}">➡️</a>
|
href="../tag/{{tag_util.create_tag('day', response.date, 1)}}">➡️</a>
|
||||||
</h4>
|
</h4>
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
{% block title %}
|
{% 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 %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block header %}
|
{% block header %}
|
||||||
<span class="fs-4 text-body ms-2 me-2">/</span>
|
|
||||||
<app-link href="/schedule"
|
<app-link href="/schedule"
|
||||||
icon="tv">{{_("TV program")}}</app-link>
|
icon="tv">{{_("TV program")}}</app-link>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@@ -15,7 +14,7 @@
|
|||||||
href="../tag/{{tag_util.create_tag('day', response.date, -1)}}">⬅️</a>
|
href="../tag/{{tag_util.create_tag('day', response.date, -1)}}">⬅️</a>
|
||||||
<a class="button"
|
<a class="button"
|
||||||
href="..">⬆️</a>
|
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"
|
<a class="button"
|
||||||
href="../tag/{{tag_util.create_tag('day', response.date, 1)}}">➡️</a>
|
href="../tag/{{tag_util.create_tag('day', response.date, 1)}}">➡️</a>
|
||||||
</h4>
|
</h4>
|
||||||
@@ -36,8 +35,6 @@
|
|||||||
<td colspan="3">
|
<td colspan="3">
|
||||||
<div>{{response.channel.name}}</div>
|
<div>{{response.channel.name}}</div>
|
||||||
</td>
|
</td>
|
||||||
<td></td>
|
|
||||||
<td></td>
|
|
||||||
</tr>
|
</tr>
|
||||||
{% for value in values %}
|
{% for value in values %}
|
||||||
<tr class="{{'table-success' if not live and value.live else ''}}">
|
<tr class="{{'table-success' if not live and value.live else ''}}">
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import gettext
|
import gettext
|
||||||
from contextvars import ContextVar
|
from contextvars import ContextVar
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
from fastapi import Cookie, Header, Request
|
from fastapi import Cookie, Header, Request
|
||||||
|
|
||||||
|
from gallery.util import root_path
|
||||||
|
|
||||||
_translation: ContextVar[gettext.GNUTranslations | gettext.NullTranslations] = (
|
_translation: ContextVar[gettext.GNUTranslations | gettext.NullTranslations] = (
|
||||||
ContextVar("translation")
|
ContextVar("translation")
|
||||||
)
|
)
|
||||||
@@ -19,7 +20,7 @@ async def set_language(
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
t = gettext.translation(
|
t = gettext.translation(
|
||||||
"messages", localedir=Path(__file__).parent / "locales", languages=[lang]
|
"messages", localedir=root_path / "locales", languages=[lang]
|
||||||
)
|
)
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
t = gettext.NullTranslations()
|
t = gettext.NullTranslations()
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
import datetime
|
import datetime
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from fastapi import APIRouter, FastAPI
|
from fastapi import APIRouter
|
||||||
from fastapi.responses import HTMLResponse, RedirectResponse
|
from fastapi.responses import HTMLResponse, RedirectResponse
|
||||||
from fastapi.staticfiles import StaticFiles
|
|
||||||
from fastapi.templating import Jinja2Templates
|
from fastapi.templating import Jinja2Templates
|
||||||
|
from babel.dates import format_date
|
||||||
from gallery.easel.core import AppRequest
|
from gallery.easel.core import AppRequest
|
||||||
from gallery.sketch.weather.model import WeatherResponse
|
from gallery.sketch.weather.model import WeatherResponse
|
||||||
from gallery.version import __version__
|
from gallery.version import __version__
|
||||||
@@ -22,7 +21,13 @@ templates = Jinja2Templates(
|
|||||||
base_dir / "templates",
|
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["wind_direction_icon"] = wind_direction_icon
|
||||||
templates.env.filters["cloudness_icon"] = cloudness_icon
|
templates.env.filters["cloudness_icon"] = cloudness_icon
|
||||||
|
|
||||||
@@ -32,15 +37,16 @@ def build_weather_response(request: AppRequest, response: WeatherResponse):
|
|||||||
request=request,
|
request=request,
|
||||||
name="weather.html",
|
name="weather.html",
|
||||||
context={
|
context={
|
||||||
"version": __version__,
|
|
||||||
"tag_util": TagUtil,
|
"tag_util": TagUtil,
|
||||||
"datetime": datetime,
|
"datetime": datetime,
|
||||||
"response": response,
|
"response": response,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
router = APIRouter()
|
router = APIRouter()
|
||||||
|
|
||||||
|
|
||||||
@router.get("/weather", response_class=HTMLResponse)
|
@router.get("/weather", response_class=HTMLResponse)
|
||||||
async def get_weather_index(request: AppRequest, query: str | None = None):
|
async def get_weather_index(request: AppRequest, query: str | None = None):
|
||||||
weather_api = request.app.state.api.weather
|
weather_api = request.app.state.api.weather
|
||||||
@@ -49,7 +55,6 @@ async def get_weather_index(request: AppRequest, query: str | None = None):
|
|||||||
request=request,
|
request=request,
|
||||||
name="index.html",
|
name="index.html",
|
||||||
context={
|
context={
|
||||||
"version": __version__,
|
|
||||||
"locations": locations,
|
"locations": locations,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
{% block title %}Weather{% endblock %}
|
{% block title %}{{_("Weather")}}{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<h1>Weather</h1>
|
<h1>{{_("Weather")}}</h1>
|
||||||
<form action=""
|
<form action=""
|
||||||
method="get"
|
method="get"
|
||||||
class="mb-4">
|
class="mb-4">
|
||||||
@@ -11,9 +11,9 @@
|
|||||||
class="form-control"
|
class="form-control"
|
||||||
id="query"
|
id="query"
|
||||||
name="query"
|
name="query"
|
||||||
placeholder="Enter the city name">
|
placeholder="{{_('Enter the city name')}}">
|
||||||
<button class="btn btn-primary"
|
<button class="btn btn-primary"
|
||||||
type="submit">Search</button>
|
type="submit">{{_("Search")}}</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
<ul id="locations"
|
<ul id="locations"
|
||||||
@@ -22,6 +22,7 @@
|
|||||||
<a href="weather/{{location.id}}"
|
<a href="weather/{{location.id}}"
|
||||||
class="list-group-item list-group-item-action px-4"
|
class="list-group-item list-group-item-action px-4"
|
||||||
onclick="saveLocation({id:'{{location.id}}', name:'{{location.name}}'});">
|
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="text-primary">{{location.name}}</span>
|
||||||
<span class="small ms-1 text-secondary">
|
<span class="small ms-1 text-secondary">
|
||||||
{{location.country}}, {{location.district}}, {{location.subdistrict}}
|
{{location.country}}, {{location.district}}, {{location.subdistrict}}
|
||||||
|
|||||||
@@ -2,8 +2,8 @@
|
|||||||
{% block title %}{{_("Weather")}} | {{response.location}} | {{response.date.strftime('%a, %d %B %Y')}}{% endblock %}
|
{% block title %}{{_("Weather")}} | {{response.location}} | {{response.date.strftime('%a, %d %B %Y')}}{% endblock %}
|
||||||
|
|
||||||
{% block header %}
|
{% block header %}
|
||||||
<span class="fs-4 text-body ms-2 me-2">/</span>
|
<app-link href="/weather"
|
||||||
<app-link href="/weather" icon="brightness-high">{{_("Weather")}}</app-link>
|
icon="brightness-high">{{_("Weather")}}</app-link>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
@@ -13,12 +13,12 @@
|
|||||||
href="../tag/{{tag_util.create_tag('day', response.date, -1)}}">⬅️</a>
|
href="../tag/{{tag_util.create_tag('day', response.date, -1)}}">⬅️</a>
|
||||||
<a class="button"
|
<a class="button"
|
||||||
href="../tag/days-10">⬆️</a>
|
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"
|
<a class="button"
|
||||||
href="../tag/{{tag_util.create_tag('day', response.date, 1)}}">➡️</a>
|
href="../tag/{{tag_util.create_tag('day', response.date, 1)}}">➡️</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if response.period == 'days' %}
|
{% 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 %}
|
{% endif %}
|
||||||
</h4>
|
</h4>
|
||||||
<div class="table-responsive">
|
<div class="table-responsive">
|
||||||
@@ -36,9 +36,9 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
{% if response.period == 'days' %}
|
{% if response.period == 'days' %}
|
||||||
<td class="date {{'now' if value.date.date() == datetime.date.today() else ''}}">
|
<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())}}">
|
<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>
|
</a>
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
@@ -49,7 +49,7 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<td colspan="{{response.values | length}}"
|
<td colspan="{{response.values | length}}"
|
||||||
class="header">
|
class="header">
|
||||||
Облачность
|
{{_("Cloudiness")}}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
@@ -65,7 +65,7 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<td colspan="{{response.values | length}}"
|
<td colspan="{{response.values | length}}"
|
||||||
class="header">
|
class="header">
|
||||||
Температура, °C
|
{{_("Temperature, °C")}}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
@@ -84,7 +84,7 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<td colspan="{{response.values | length}}"
|
<td colspan="{{response.values | length}}"
|
||||||
class="header">
|
class="header">
|
||||||
Направление ветра
|
{{_("Wind direction")}}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
@@ -98,7 +98,7 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<td colspan="{{response.values | length}}"
|
<td colspan="{{response.values | length}}"
|
||||||
class="header">
|
class="header">
|
||||||
Скорость ветра, м/с
|
{{_("Wind speed, m/s")}}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
@@ -118,7 +118,7 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<td colspan="{{response.values | length}}"
|
<td colspan="{{response.values | length}}"
|
||||||
class="header">
|
class="header">
|
||||||
Осадки, мм
|
{{_("Precipitation, mm")}}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
@@ -133,7 +133,7 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<td colspan="{{response.values | length}}"
|
<td colspan="{{response.values | length}}"
|
||||||
class="header">
|
class="header">
|
||||||
Давление, мм рт. ст.
|
{{_("Pressure, mmHg")}}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
@@ -151,7 +151,7 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<td colspan="{{response.values | length}}"
|
<td colspan="{{response.values | length}}"
|
||||||
class="header">
|
class="header">
|
||||||
Влажность, %
|
{{_("Humidity, %")}}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
|
|||||||
@@ -87,6 +87,7 @@ class GismeteoApi(WeatherApi):
|
|||||||
lat=item["coordinates"]["latitude"],
|
lat=item["coordinates"]["latitude"],
|
||||||
lon=item["coordinates"]["longitude"],
|
lon=item["coordinates"]["longitude"],
|
||||||
country=item["translations"]["kk"]["country"]["name"],
|
country=item["translations"]["kk"]["country"]["name"],
|
||||||
|
country_code=item["country"]["code"].lower(),
|
||||||
district=item["translations"]["kk"]["district"]["name"],
|
district=item["translations"]["kk"]["district"]["name"],
|
||||||
subdistrict=(
|
subdistrict=(
|
||||||
item["translations"]["kk"]["subdistrict"]["name"]
|
item["translations"]["kk"]["subdistrict"]["name"]
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ class Location(Model):
|
|||||||
lat: float
|
lat: float
|
||||||
lon: float
|
lon: float
|
||||||
country: str
|
country: str
|
||||||
|
country_code: str
|
||||||
district: str
|
district: str
|
||||||
subdistrict: 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-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]"]
|
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]]
|
[[package]]
|
||||||
name = "beautifulsoup4"
|
name = "beautifulsoup4"
|
||||||
version = "4.12.3"
|
version = "4.12.3"
|
||||||
@@ -1898,4 +1913,4 @@ multidict = ">=4.0"
|
|||||||
[metadata]
|
[metadata]
|
||||||
lock-version = "2.1"
|
lock-version = "2.1"
|
||||||
python-versions = "^3.12"
|
python-versions = "^3.12"
|
||||||
content-hash = "7295e9ec7f7492017c5bbda489026f19bbf155f0ea82402d348b0aa4c03beaca"
|
content-hash = "39107ded0683832d1ca3d637dc50fac7dd441375ead30cdb56d64edee13c97e2"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[tool.poetry]
|
[tool.poetry]
|
||||||
name = "gallery"
|
name = "gallery"
|
||||||
version = "0.2.1"
|
version = "0.2.3"
|
||||||
description = ""
|
description = ""
|
||||||
authors = ["shmyga <shmyga.z@gmail.com>"]
|
authors = ["shmyga <shmyga.z@gmail.com>"]
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
@@ -17,6 +17,7 @@ aiocache = {extras = ["redis"], version = "^0.12.2"}
|
|||||||
[tool.poetry.group.app.dependencies]
|
[tool.poetry.group.app.dependencies]
|
||||||
fastapi = "^0.111.1"
|
fastapi = "^0.111.1"
|
||||||
jinja2 = "^3.1.4"
|
jinja2 = "^3.1.4"
|
||||||
|
babel = "^2.18.0"
|
||||||
|
|
||||||
[tool.poetry.group.test.dependencies]
|
[tool.poetry.group.test.dependencies]
|
||||||
pytest = "^8.3.1"
|
pytest = "^8.3.1"
|
||||||
|
|||||||
@@ -2,5 +2,5 @@
|
|||||||
set -e
|
set -e
|
||||||
cd "$(dirname $(dirname "$0"))" || exit
|
cd "$(dirname $(dirname "$0"))" || exit
|
||||||
|
|
||||||
cd gallery/easel/route/view/locales/ru/LC_MESSAGES || exit
|
cd locales/ru/LC_MESSAGES || exit
|
||||||
msgfmt messages.po
|
msgfmt messages.po
|
||||||
15
static/package-lock.json
generated
15
static/package-lock.json
generated
@@ -1,17 +1,18 @@
|
|||||||
{
|
{
|
||||||
"name": "gallery-static",
|
"name": "gallery",
|
||||||
"version": "0.2.1",
|
"version": "0.2.3",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "gallery-static",
|
"name": "gallery",
|
||||||
"version": "0.2.1",
|
"version": "0.2.3",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@popperjs/core": "^2.11.8",
|
"@popperjs/core": "^2.11.8",
|
||||||
"bootstrap": "^5.3.8",
|
"bootstrap": "^5.3.8",
|
||||||
"bootstrap-icons": "^1.13.1",
|
"bootstrap-icons": "^1.13.1",
|
||||||
|
"flag-icons": "^7.5.0",
|
||||||
"sass": "^1.99.0"
|
"sass": "^1.99.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"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": {
|
"node_modules/fsevents": {
|
||||||
"version": "2.3.3",
|
"version": "2.3.3",
|
||||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "gallery",
|
"name": "gallery",
|
||||||
"version": "0.2.1",
|
"version": "0.2.3",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "vite build",
|
"build": "vite build",
|
||||||
"dev": "vite build --watch"
|
"dev": "vite build --watch"
|
||||||
@@ -12,6 +12,7 @@
|
|||||||
"@popperjs/core": "^2.11.8",
|
"@popperjs/core": "^2.11.8",
|
||||||
"bootstrap": "^5.3.8",
|
"bootstrap": "^5.3.8",
|
||||||
"bootstrap-icons": "^1.13.1",
|
"bootstrap-icons": "^1.13.1",
|
||||||
|
"flag-icons": "^7.5.0",
|
||||||
"sass": "^1.99.0"
|
"sass": "^1.99.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ class AppLinkElement extends HTMLElement {
|
|||||||
<a href="${this.getAttribute("href")}"
|
<a href="${this.getAttribute("href")}"
|
||||||
class="d-flex align-items-center text-body text-decoration-none">
|
class="d-flex align-items-center text-body text-decoration-none">
|
||||||
<span class="fs-4">
|
<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>${this.textContent}</span>
|
||||||
</span>
|
</span>
|
||||||
</a>`;
|
</a>`;
|
||||||
|
|||||||
@@ -28,7 +28,7 @@
|
|||||||
const languageSwitcherText = document.querySelector("#bd-language-text");
|
const languageSwitcherText = document.querySelector("#bd-language-text");
|
||||||
const activeLanguageIcon = document.querySelector(".language-icon-active");
|
const activeLanguageIcon = document.querySelector(".language-icon-active");
|
||||||
const btnToActive = document.querySelector(`[data-bs-language-value="${language}"]`);
|
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) => {
|
document.querySelectorAll("[data-bs-language-value]").forEach((element) => {
|
||||||
element.classList.remove("active");
|
element.classList.remove("active");
|
||||||
@@ -37,7 +37,9 @@
|
|||||||
|
|
||||||
btnToActive.classList.add("active");
|
btnToActive.classList.add("active");
|
||||||
btnToActive.setAttribute("aria-pressed", "true");
|
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})`;
|
const languageSwitcherLabel = `${languageSwitcherText.textContent} (${btnToActive.dataset.bsLanguageValue})`;
|
||||||
languageSwitcher.setAttribute("aria-label", languageSwitcherLabel);
|
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
|
||||||
15
static/src/lib/flag-icons.scss
Normal file
15
static/src/lib/flag-icons.scss
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
@use "flag-icons/sass/flag-icons" with (
|
||||||
|
$flag-icons-path: "flag-icons/flags",
|
||||||
|
$flag-icons-included-countries: (
|
||||||
|
"gb",
|
||||||
|
"ru",
|
||||||
|
"by",
|
||||||
|
"ua",
|
||||||
|
"kz",
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
.fir {
|
||||||
|
@extend .fis;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
@import "bootstrap/scss/bootstrap";
|
@import "./lib/flag-icons";
|
||||||
$bootstrap-icons-font-dir: "bootstrap-icons/font/fonts";
|
@import "./lib/bootstrap";
|
||||||
@import "bootstrap-icons/font/bootstrap-icons";
|
@import "./lib/bootstrap-icons";
|
||||||
|
|
||||||
@import "./widget.scss";
|
@import "./widget.scss";
|
||||||
@import "./weather.scss";
|
@import "./weather.scss";
|
||||||
@@ -17,3 +17,26 @@ $bootstrap-icons-font-dir: "bootstrap-icons/font/fonts";
|
|||||||
height: 2rem;
|
height: 2rem;
|
||||||
background-size: contain;
|
background-size: contain;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.app-header {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
|
||||||
|
> .link-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
gap: 1rem;
|
||||||
|
|
||||||
|
app-link {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
&:not(:last-child)::after {
|
||||||
|
margin-left: 1rem;
|
||||||
|
content: "|";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@import "./mobile";
|
||||||
|
|||||||
34
static/src/mobile.scss
Normal file
34
static/src/mobile.scss
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
@import "./lib/bootstrap";
|
||||||
|
|
||||||
|
@include media-breakpoint-down(md) {
|
||||||
|
.app-header {
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
> .link-list {
|
||||||
|
border-bottom: var(--bs-border-width) var(--bs-border-style) var(--bs-border-color) !important;
|
||||||
|
padding-bottom: map-get($spacers, 1);
|
||||||
|
margin-bottom: map-get($spacers, 1);
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0;
|
||||||
|
|
||||||
|
app-link {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
&:not(:last-child)::after {
|
||||||
|
content: "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-nav {
|
||||||
|
flex-direction: column !important;
|
||||||
|
|
||||||
|
> .nav-item {
|
||||||
|
.btn {
|
||||||
|
padding: 0.125rem !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -34,7 +34,7 @@
|
|||||||
const themeSwitcherText = document.querySelector("#bd-theme-text");
|
const themeSwitcherText = document.querySelector("#bd-theme-text");
|
||||||
const activeThemeIcon = document.querySelector(".theme-icon-active");
|
const activeThemeIcon = document.querySelector(".theme-icon-active");
|
||||||
const btnToActive = document.querySelector(`[data-bs-theme-value="${theme}"]`);
|
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) => {
|
document.querySelectorAll("[data-bs-theme-value]").forEach((element) => {
|
||||||
element.classList.remove("active");
|
element.classList.remove("active");
|
||||||
|
|||||||
@@ -31,7 +31,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.temperature {
|
.temperature {
|
||||||
padding: 0;
|
padding: 0 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.temperature .value {
|
.temperature .value {
|
||||||
|
|||||||
Reference in New Issue
Block a user